Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP4:GA
python-Twisted
PR-1147.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File PR-1147.patch of Package python-Twisted
Index: Twisted-15.2.1/src/twisted/words/newsfragments/9561.bugfix =================================================================== --- /dev/null +++ Twisted-15.2.1/src/twisted/words/newsfragments/9561.bugfix @@ -0,0 +1 @@ +twisted.words.protocols.jabber.xmlstream.TLSInitiatingInitializer now properly verifies the server's certificate against platform CAs and the stream's domain, mitigating CVE-2019-12855. Index: Twisted-15.2.1/src/twisted/words/newsfragments/9561.feature =================================================================== --- /dev/null +++ Twisted-15.2.1/src/twisted/words/newsfragments/9561.feature @@ -0,0 +1 @@ +twisted.words.protocols.jabber.xmlstream.TLSInitiatingInitializer and twisted.words.protocols.jabber.client.XMPPClientFactory now take an optional configurationForTLS for customizing certificate options for StartTLS. Index: Twisted-15.2.1/twisted/words/protocols/jabber/client.py =================================================================== --- Twisted-15.2.1.orig/twisted/words/protocols/jabber/client.py +++ Twisted-15.2.1/twisted/words/protocols/jabber/client.py @@ -184,14 +184,10 @@ class BasicAuthenticator(xmlstream.Conne xs.version = (0, 0) xmlstream.ConnectAuthenticator.associateWithStream(self, xs) - inits = [ (xmlstream.TLSInitiatingInitializer, False), - (IQAuthInitializer, True), - ] - - for initClass, required in inits: - init = initClass(xs) - init.required = required - xs.initializers.append(init) + xs.initializers = [ + xmlstream.TLSInitiatingInitializer(xs, required=False), + IQAuthInitializer(xs), + ] # TODO: move registration into an Initializer? @@ -280,7 +276,7 @@ class SessionInitializer(xmlstream.BaseF -def XMPPClientFactory(jid, password): +def XMPPClientFactory(jid, password, configurationForTLS=None): """ Client factory for XMPP 1.0 (only). @@ -292,12 +288,23 @@ def XMPPClientFactory(jid, password): @param jid: Jabber ID to connect with. @type jid: L{jid.JID} + @param password: password to authenticate with. - @type password: C{unicode} + @type password: L{unicode} + + @param configurationForTLS: An object which creates appropriately + configured TLS connections. This is passed to C{startTLS} on the + transport and is preferably created using + L{twisted.internet.ssl.optionsForClientTLS}. If C{None}, the default is + to verify the server certificate against the trust roots as provided by + the platform. See L{twisted.internet._sslverify.platformTrust}. + @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or C{None} + @return: XML stream factory. @rtype: L{xmlstream.XmlStreamFactory} """ - a = XMPPAuthenticator(jid, password) + a = XMPPAuthenticator(jid, password, + configurationForTLS=configurationForTLS) return xmlstream.XmlStreamFactory(a) @@ -332,16 +339,29 @@ class XMPPAuthenticator(xmlstream.Connec resource binding step, and this is stored in this instance variable. @type jid: L{jid.JID} + @ivar password: password to be used during SASL authentication. - @type password: C{unicode} + @type password: L{unicode} """ namespace = 'jabber:client' - def __init__(self, jid, password): + def __init__(self, jid, password, configurationForTLS=None): + """ + @param configurationForTLS: An object which creates appropriately + configured TLS connections. This is passed to C{startTLS} on the + transport and is preferably created using + L{twisted.internet.ssl.optionsForClientTLS}. If C{None}, the + default is to verify the server certificate against the trust roots + as provided by the platform. See + L{twisted.internet._sslverify.platformTrust}. + @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or + C{None} + """ xmlstream.ConnectAuthenticator.__init__(self, jid.host) self.jid = jid self.password = password + self._configurationForTLS = configurationForTLS def associateWithStream(self, xs): @@ -355,14 +375,12 @@ class XMPPAuthenticator(xmlstream.Connec """ xmlstream.ConnectAuthenticator.associateWithStream(self, xs) - xs.initializers = [CheckVersionInitializer(xs)] - inits = [ (xmlstream.TLSInitiatingInitializer, False), - (sasl.SASLInitiatingInitializer, True), - (BindInitializer, False), - (SessionInitializer, False), + xs.initializers = [ + CheckVersionInitializer(xs), + xmlstream.TLSInitiatingInitializer( + xs, required=True, + configurationForTLS=self._configurationForTLS), + sasl.SASLInitiatingInitializer(xs, required=True), + BindInitializer(xs, required=True), + SessionInitializer(xs, required=False), ] - - for initClass, required in inits: - init = initClass(xs) - init.required = required - xs.initializers.append(init) Index: Twisted-15.2.1/twisted/words/protocols/jabber/xmlstream.py =================================================================== --- Twisted-15.2.1.orig/twisted/words/protocols/jabber/xmlstream.py +++ Twisted-15.2.1/twisted/words/protocols/jabber/xmlstream.py @@ -300,6 +300,7 @@ class BaseFeatureInitiatingInitializer(o @cvar feature: tuple of (uri, name) of the stream feature root element. @type feature: tuple of (C{str}, C{str}) + @ivar required: whether the stream feature is required to be advertized by the receiving entity. @type required: C{bool} @@ -308,10 +309,10 @@ class BaseFeatureInitiatingInitializer(o implements(ijabber.IInitiatingInitializer) feature = None - required = False - def __init__(self, xs): + def __init__(self, xs, required=False): self.xmlstream = xs + self.required = required def initialize(self): @@ -386,13 +387,31 @@ class TLSInitiatingInitializer(BaseFeatu set the C{wanted} attribute to False instead of removing it from the list of initializers, so a proper exception L{TLSRequired} can be raised. - @cvar wanted: indicates if TLS negotiation is wanted. + @ivar wanted: indicates if TLS negotiation is wanted. @type wanted: C{bool} """ feature = (NS_XMPP_TLS, 'starttls') wanted = True _deferred = None + _configurationForTLS = None + + def __init__(self, xs, required=True, configurationForTLS=None): + """ + @param configurationForTLS: An object which creates appropriately + configured TLS connections. This is passed to C{startTLS} on the + transport and is preferably created using + L{twisted.internet.ssl.optionsForClientTLS}. If C{None}, the + default is to verify the server certificate against the trust roots + as provided by the platform. See + L{twisted.internet._sslverify.platformTrust}. + @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or + C{None} + """ + super(TLSInitiatingInitializer, self).__init__( + xs, required=required) + self._configurationForTLS = configurationForTLS + def onProceed(self, obj): """ @@ -400,7 +419,10 @@ class TLSInitiatingInitializer(BaseFeatu """ self.xmlstream.removeObserver('/failure', self.onFailure) - ctx = ssl.CertificateOptions() + if self._configurationForTLS: + ctx = self._configurationForTLS + else: + ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host) self.xmlstream.transport.startTLS(ctx) self.xmlstream.reset() self.xmlstream.sendHeader() Index: Twisted-15.2.1/twisted/words/test/test_jabberclient.py =================================================================== --- Twisted-15.2.1.orig/twisted/words/test/test_jabberclient.py +++ Twisted-15.2.1/twisted/words/test/test_jabberclient.py @@ -7,11 +7,20 @@ Tests for L{twisted.words.protocols.jabb from hashlib import sha1 from twisted.internet import defer +from twisted.python.compat import unicode from twisted.trial import unittest from twisted.words.protocols.jabber import client, error, jid, xmlstream from twisted.words.protocols.jabber.sasl import SASLInitiatingInitializer from twisted.words.xish import utility +try: + from twisted.internet import ssl +except ImportError: + ssl = None + skipWhenNoSSL = "SSL not available" +else: + skipWhenNoSSL = None + IQ_AUTH_GET = '/iq[@type="get"]/query[@xmlns="jabber:iq:auth"]' IQ_AUTH_SET = '/iq[@type="set"]/query[@xmlns="jabber:iq:auth"]' NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind' @@ -375,11 +384,51 @@ class SessionInitializerTests(Initiating +class BasicAuthenticatorTests(unittest.TestCase): + """ + Test for both BasicAuthenticator and basicClientFactory. + """ + + def test_basic(self): + """ + Authenticator and stream are properly constructed by the factory. + + The L{xmlstream.XmlStream} protocol created by the factory has the new + L{client.BasicAuthenticator} instance in its C{authenticator} + attribute. It is set up with C{jid} and C{password} as passed to the + factory, C{otherHost} taken from the client JID. The stream futher has + two initializers, for TLS and authentication, of which the first has + its C{required} attribute set to C{True}. + """ + self.client_jid = jid.JID('user@example.com/resource') + + # Get an XmlStream instance. Note that it gets initialized with the + # XMPPAuthenticator (that has its associateWithXmlStream called) that + # is in turn initialized with the arguments to the factory. + xs = client.basicClientFactory(self.client_jid, + 'secret').buildProtocol(None) + + # test authenticator's instance variables + self.assertEqual('example.com', xs.authenticator.otherHost) + self.assertEqual(self.client_jid, xs.authenticator.jid) + self.assertEqual('secret', xs.authenticator.password) + + # test list of initializers + tls, auth = xs.initializers + + self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer) + self.assertIsInstance(auth, client.IQAuthInitializer) + + self.assertFalse(tls.required) + + + class XMPPAuthenticatorTests(unittest.TestCase): """ Test for both XMPPAuthenticator and XMPPClientFactory. """ - def testBasic(self): + + def test_basic(self): """ Test basic operations. @@ -408,7 +457,38 @@ class XMPPAuthenticatorTests(unittest.Te self.assert_(isinstance(bind, client.BindInitializer)) self.assert_(isinstance(session, client.SessionInitializer)) - self.assertFalse(tls.required) + self.assertTrue(tls.required) self.assertTrue(sasl.required) - self.assertFalse(bind.required) + self.assertTrue(bind.required) self.assertFalse(session.required) + + + def test_tlsConfiguration(self): + """ + A TLS configuration is passed to the TLS initializer. + """ + configs = [] + + def init(self, xs, required=True, configurationForTLS=None): + configs.append(configurationForTLS) + + self.client_jid = jid.JID('user@example.com/resource') + + # Get an XmlStream instance. Note that it gets initialized with the + # XMPPAuthenticator (that has its associateWithXmlStream called) that + # is in turn initialized with the arguments to the factory. + configurationForTLS = ssl.CertificateOptions() + factory = client.XMPPClientFactory( + self.client_jid, 'secret', + configurationForTLS=configurationForTLS) + self.patch(xmlstream.TLSInitiatingInitializer, "__init__", init) + xs = factory.buildProtocol(None) + + # test list of initializers + version, tls, sasl, bind, session = xs.initializers + + self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer) + self.assertIs(configurationForTLS, configs[0]) + + + test_tlsConfiguration.skip = skipWhenNoSSL Index: Twisted-15.2.1/twisted/words/test/test_jabberxmlstream.py =================================================================== --- Twisted-15.2.1.orig/twisted/words/test/test_jabberxmlstream.py +++ Twisted-15.2.1/twisted/words/test/test_jabberxmlstream.py @@ -18,7 +18,15 @@ from twisted.words.test.test_xmlstream i from twisted.words.xish import domish from twisted.words.protocols.jabber import error, ijabber, jid, xmlstream - +try: + from twisted.internet import ssl +except ImportError: + ssl = None + skipWhenNoSSL = "SSL not available" +else: + skipWhenNoSSL = None + from twisted.internet.ssl import CertificateOptions + from twisted.internet._sslverify import ClientTLSOptions NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls' @@ -661,7 +669,7 @@ class TLSInitiatingInitializerTests(unit self.savedSSL = xmlstream.ssl - self.authenticator = xmlstream.Authenticator() + self.authenticator = xmlstream.ConnectAuthenticator(u'example.com') self.xmlstream = xmlstream.XmlStream(self.authenticator) self.xmlstream.send = self.output.append self.xmlstream.connectionMade() @@ -675,9 +683,18 @@ class TLSInitiatingInitializerTests(unit xmlstream.ssl = self.savedSSL - def testWantedSupported(self): + def test_initRequired(self): + """ + Passing required sets the instance variable. + """ + self.init = xmlstream.TLSInitiatingInitializer(self.xmlstream, + required=True) + self.assertTrue(self.init.required) + + + def test_wantedSupported(self): """ - Test start when TLS is wanted and the SSL library available. + When TLS is wanted and SSL available, StartTLS is initiated. """ self.xmlstream.transport = proto_helpers.StringTransport() self.xmlstream.transport.startTLS = lambda ctx: self.done.append('TLS') @@ -686,7 +703,8 @@ class TLSInitiatingInitializerTests(unit d = self.init.start() d.addCallback(self.assertEqual, xmlstream.Reset) - starttls = self.output[0] + self.assertEqual(2, len(self.output)) + starttls = self.output[1] self.assertEqual('starttls', starttls.name) self.assertEqual(NS_XMPP_TLS, starttls.uri) self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS) @@ -694,40 +712,90 @@ class TLSInitiatingInitializerTests(unit return d - if not xmlstream.ssl: - testWantedSupported.skip = "SSL not available" + test_wantedSupported.skip = skipWhenNoSSL + + + def test_certificateVerify(self): + """ + The server certificate will be verified. + """ + + def fakeStartTLS(contextFactory): + self.assertIsInstance(contextFactory, ClientTLSOptions) + self.assertEqual(contextFactory._hostname, u"example.com") + self.done.append('TLS') + + self.xmlstream.transport = proto_helpers.StringTransport() + self.xmlstream.transport.startTLS = fakeStartTLS + self.xmlstream.reset = lambda: self.done.append('reset') + self.xmlstream.sendHeader = lambda: self.done.append('header') + + d = self.init.start() + self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS) + self.assertEqual(['TLS', 'reset', 'header'], self.done) + return d + + test_certificateVerify.skip = skipWhenNoSSL + + + def test_certificateVerifyContext(self): + """ + A custom contextFactory is passed through to startTLS. + """ + ctx = CertificateOptions() + self.init = xmlstream.TLSInitiatingInitializer( + self.xmlstream, configurationForTLS=ctx) + + self.init.contextFactory = ctx + + def fakeStartTLS(contextFactory): + self.assertIs(ctx, contextFactory) + self.done.append('TLS') + + self.xmlstream.transport = proto_helpers.StringTransport() + self.xmlstream.transport.startTLS = fakeStartTLS + self.xmlstream.reset = lambda: self.done.append('reset') + self.xmlstream.sendHeader = lambda: self.done.append('header') + + d = self.init.start() + self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS) + self.assertEqual(['TLS', 'reset', 'header'], self.done) + return d + + test_certificateVerifyContext.skip = skipWhenNoSSL - def testWantedNotSupportedNotRequired(self): + def test_wantedNotSupportedNotRequired(self): """ - Test start when TLS is wanted and the SSL library available. + No StartTLS is initiated when wanted, not required, SSL not available. """ xmlstream.ssl = None + self.init.required = False d = self.init.start() d.addCallback(self.assertEqual, None) - self.assertEqual([], self.output) + self.assertEqual(1, len(self.output)) return d - def testWantedNotSupportedRequired(self): + def test_wantedNotSupportedRequired(self): """ - Test start when TLS is wanted and the SSL library available. + TLSNotSupported is raised when TLS is required but not available. """ xmlstream.ssl = None self.init.required = True d = self.init.start() self.assertFailure(d, xmlstream.TLSNotSupported) - self.assertEqual([], self.output) + self.assertEqual(1, len(self.output)) return d - def testNotWantedRequired(self): + def test_notWantedRequired(self): """ - Test start when TLS is not wanted, but required by the server. + TLSRequired is raised when TLS is not wanted, but required by server. """ tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls')) tls.addElement('required') @@ -735,29 +803,30 @@ class TLSInitiatingInitializerTests(unit self.init.wanted = False d = self.init.start() - self.assertEqual([], self.output) + self.assertEqual(1, len(self.output)) self.assertFailure(d, xmlstream.TLSRequired) return d - def testNotWantedNotRequired(self): + def test_notWantedNotRequired(self): """ - Test start when TLS is not wanted, but required by the server. + No StartTLS is initiated when not wanted and not required. """ tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls')) self.xmlstream.features = {(tls.uri, tls.name): tls} self.init.wanted = False + self.init.required = False d = self.init.start() d.addCallback(self.assertEqual, None) - self.assertEqual([], self.output) + self.assertEqual(1, len(self.output)) return d - def testFailed(self): + def test_failed(self): """ - Test failed TLS negotiation. + TLSFailed is raised when the server responds with a failure. """ # Pretend that ssl is supported, it isn't actually used when the # server starts out with a failure in response to our initial
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor