Ticket #2061: chained-certs-2061.patch

File chained-certs-2061.patch, 6.8 KB (added by Hynek Schlawack, 8 years ago)
  • twisted/internet/_sslverify.py

    diff --git a/twisted/internet/_sslverify.py b/twisted/internet/_sslverify.py
    index 3af601b..20119fa 100644
    a b class OpenSSLCertificateOptions(object): 
    626626    A factory for SSL context objects for both SSL servers and clients.
    627627    """
    628628
     629    _contextFactory = SSL.Context
    629630    _context = None
    630631    # Older versions of PyOpenSSL didn't provide OP_ALL.  Fudge it here, just in case.
    631632    _OP_ALL = getattr(SSL, 'OP_ALL', 0x0000FFFF)
    class OpenSSLCertificateOptions(object): 
    646647                 enableSingleUseKeys=True,
    647648                 enableSessions=True,
    648649                 fixBrokenPeers=False,
    649                  enableSessionTickets=False):
     650                 enableSessionTickets=False,
     651                 extraCertChain=None):
    650652        """
    651653        Create an OpenSSL context SSL connection context factory.
    652654
    class OpenSSLCertificateOptions(object): 
    694696        controlling session tickets. This option is off by default, as some
    695697        server implementations don't correctly process incoming empty session
    696698        ticket extensions in the hello.
     699
     700        @param extraCertChain: List of certificates that I{complete} your
     701            verification chain if the certificate authority that signed your
     702            C{certificate} isn't widely supported.  Do I{not} add
     703            C{certificate} to it.
     704
     705        @type extraCertChain: C{list} of L{OpenSSL.crypto.X509}
    697706        """
    698707
    699708        if (privateKey is None) != (certificate is None):
    class OpenSSLCertificateOptions(object): 
    708717            raise ValueError("Specify client CA certificate information if and"
    709718                             " only if enabling certificate verification")
    710719        self.verify = verify
     720        if extraCertChain is not None and None in (privateKey, certificate):
     721            raise ValueError("A private key and a certificate are required "
     722                             "when adding a supplemental certificate chain.")
     723        self.extraCertChain = extraCertChain or []
    711724
    712725        self.caCerts = caCerts
    713726        self.verifyDepth = verifyDepth
    class OpenSSLCertificateOptions(object): 
    741754
    742755
    743756    def _makeContext(self):
    744         ctx = SSL.Context(self.method)
     757        ctx = self._contextFactory(self.method)
    745758        # Disallow insecure SSLv2. Applies only for SSLv23_METHOD.
    746759        ctx.set_options(SSL.OP_NO_SSLv2)
    747760
    748761        if self.certificate is not None and self.privateKey is not None:
    749762            ctx.use_certificate(self.certificate)
    750763            ctx.use_privatekey(self.privateKey)
     764            for extraCert in self.extraCertChain:
     765                ctx.add_extra_chain_cert(extraCert)
    751766            # Sanity check
    752767            ctx.check_privatekey()
    753768
  • twisted/test/test_sslverify.py

    diff --git a/twisted/test/test_sslverify.py b/twisted/test/test_sslverify.py
    index caa6b49..df906d5 100644
    a b class WritingProtocol(protocol.Protocol): 
    118118        self.factory.onLost.errback(reason)
    119119
    120120
     121
     122class FakeContext:
     123    """
     124    Introspectable fake of an OpenSSL.SSL.Context.
     125    """
     126
     127    def __init__(self, method):
     128        self._method = method
     129        self._extraCertChain = []
     130
     131    def set_options(self, options):
     132        pass
     133
     134    def use_certificate(self, certificate):
     135        self._certificate = certificate
     136
     137    def use_privatekey(self, privateKey):
     138        self._privateKey = privateKey
     139
     140    def check_privatekey(self):
     141        return None
     142
     143    def set_verify(self, flags, callback):
     144        self._verify = flags, callback
     145
     146    def set_verify_depth(self, depth):
     147        self._verifyDepth = depth
     148
     149    def set_session_id(self, sessionID):
     150        self._sessionID = sessionID
     151
     152    def add_extra_chain_cert(self, cert):
     153        self._extraCertChain.append(cert)
     154
     155
     156
    121157class OpenSSLOptions(unittest.TestCase):
    122158    serverPort = clientConn = None
    123159    onServerLost = onClientLost = None
    class OpenSSLOptions(unittest.TestCase): 
    144180            O=b"CA Test Certificate",
    145181            CN=b"ca2")[1]
    146182        self.caCerts = [self.caCert1, self.caCert2]
     183        self.extraCertChain = self.caCerts
    147184
    148185
    149186    def tearDown(self):
    class OpenSSLOptions(unittest.TestCase): 
    211248                                                   certificate=self.sCert)
    212249        self.assertEqual(opts.privateKey, self.sKey)
    213250        self.assertEqual(opts.certificate, self.sCert)
     251        self.assertEqual(opts.extraCertChain, [])
    214252
    215253
    216254    def test_constructorDoesNotAllowVerifyWithoutCACerts(self):
    class OpenSSLOptions(unittest.TestCase): 
    247285        self.assertEqual(self.caCerts, opts.caCerts)
    248286
    249287
     288    def test_constructorSetsExtraChain(self):
     289        """
     290        Setting C{extraCertChain} sets both if C{certificate} and C{privateKey}
     291        are set too.
     292        """
     293        opts = sslverify.OpenSSLCertificateOptions(
     294            privateKey=self.sKey,
     295            certificate=self.sCert,
     296            extraCertChain=self.extraCertChain,
     297        )
     298        self.assertEqual(self.extraCertChain, opts.extraCertChain)
     299
     300
     301    def test_constructorDoesNotAllowExtraChainWithoutPrivateKey(self):
     302        """
     303        A C{extraCertChain} without C{privateKey} doesn't make sense and is
     304        thus rejected.
     305        """
     306        self.assertRaises(
     307            ValueError,
     308            sslverify.OpenSSLCertificateOptions,
     309            certificate=self.sCert,
     310            extraCertChain=self.extraCertChain,
     311        )
     312
     313
     314    def test_constructorDoesNotAllowExtraChainWithOutPrivateKey(self):
     315        """
     316        A C{extraCertChain} without C{certificate} doesn't make sense and is
     317        thus rejected.
     318        """
     319        self.assertRaises(
     320            ValueError,
     321            sslverify.OpenSSLCertificateOptions,
     322            privateKey=self.sKey,
     323            extraCertChain=self.extraCertChain,
     324        )
     325
     326
     327    def test_extraChainFilesAreAddedIfSupplied(self):
     328        opts = sslverify.OpenSSLCertificateOptions(
     329            privateKey=self.sKey,
     330            certificate=self.sCert,
     331            extraCertChain=self.extraCertChain,
     332        )
     333        opts._contextFactory = lambda method: FakeContext(method)
     334        ctx = opts.getContext()
     335        self.assertEqual(self.sKey, ctx._privateKey)
     336        self.assertEqual(self.sCert, ctx._certificate)
     337        self.assertEqual(self.extraCertChain, ctx._extraCertChain)
     338
     339
    250340    def test_abbreviatingDistinguishedNames(self):
    251341        """
    252342        Check that abbreviations used in certificates correctly map to
  • new file twisted/topfiles/2061.feature

    diff --git a/twisted/topfiles/2061.feature b/twisted/topfiles/2061.feature
    new file mode 100644
    index 0000000..7b2cba3
    - +  
     1twisted.internet.ssl.CertificateOptions now supports chain certificates.