Ticket #6499: 6499-chains-for-strports-v2.diff

File 6499-chains-for-strports-v2.diff, 9.4 KB (added by Hynek Schlawack, 8 years ago)

Topfile + grammar.

  • twisted/internet/endpoints.py

    diff --git twisted/internet/endpoints.py twisted/internet/endpoints.py
    index aa8b30b..fc71868 100644
    parsed by the L{clientFromString} and L{serverFromString} functions. 
    1515from __future__ import division, absolute_import
    1616
    1717import os
     18import re
    1819import socket
    1920
    2021from zope.interface import implementer, directlyProvides
    def _parseUNIX(factory, address, mode='666', backlog=50, lockfile=True): 
    903904
    904905
    905906def _parseSSL(factory, port, privateKey="server.pem", certKey=None,
    906               sslmethod=None, interface='', backlog=50):
     907              sslmethod=None, interface='', backlog=50, extraCertChain=None):
    907908    """
    908909    Internal parser function for L{_parseServer} to convert the string
    909910    arguments for an SSL (over TCP/IPv4) stream endpoint into the structured
    def _parseSSL(factory, port, privateKey="server.pem", certKey=None, 
    932933        "SSLv2_METHOD", "SSLv3_METHOD", "TLSv1_METHOD".
    933934    @type sslmethod: C{str}
    934935
     936    @param extraCertChain: The path of a file containing one or more
     937        certificates in PEM format that document the chain of trust from a
     938        trustworthy CA to the CA that signed your L{certKey}.
     939    @type extraCertChain: C{str}
     940
    935941    @return: a 2-tuple of (args, kwargs), describing  the parameters to
    936942        L{IReactorSSL.listenSSL} (or, modulo argument 2, the factory, arguments
    937943        to L{SSL4ServerEndpoint}.
    def _parseSSL(factory, port, privateKey="server.pem", certKey=None, 
    947953    certPEM = FilePath(certKey).getContent()
    948954    keyPEM = FilePath(privateKey).getContent()
    949955    privateCertificate = ssl.PrivateCertificate.loadPEM(certPEM + keyPEM)
     956    if extraCertChain is not None:
     957        matches = re.findall(
     958            r'(-----BEGIN CERTIFICATE-----\n.+?\n-----END CERTIFICATE-----)',
     959            FilePath(extraCertChain).getContent(),
     960            flags=re.DOTALL
     961        )
     962        if not matches:
     963            raise ValueError(
     964                "Specified chain file '%s' doesn't contain any valid "
     965                "certificates in PEM format." % (extraCertChain,)
     966            )
     967        chainCertificates = [ssl.Certificate.loadPEM(chainCertPEM).original
     968                             for chainCertPEM in matches]
     969    else:
     970        chainCertificates = None
     971
    950972    cf = ssl.CertificateOptions(
    951973        privateKey=privateCertificate.privateKey.original,
    952974        certificate=privateCertificate.original,
     975        extraCertChain=chainCertificates,
    953976        **kw
    954977    )
    955978    return ((int(port), factory, cf),
    _NO_DEFAULT = object() 
    11621185
    11631186def _parseServer(description, factory, default=None):
    11641187    """
    1165     Parse a stports description into a 2-tuple of arguments and keyword values.
     1188    Parse a strports description into a 2-tuple of arguments and keyword
     1189    values.
    11661190
    11671191    @param description: A description in the format explained by
    11681192        L{serverFromString}.
  • new file twisted/internet/test/fake_CAs/chain.pem

    diff --git twisted/internet/test/fake_CAs/chain.pem twisted/internet/test/fake_CAs/chain.pem
    new file mode 100644
    index 0000000..0d28d6a
    - +  
     1
     2This is a concatenation of thing1.pem and thing2.pem.
     3
     4-----BEGIN CERTIFICATE-----
     5MIICwjCCAisCAgTSMA0GCSqGSIb3DQEBBAUAMIGoMREwDwYDVQQLEwhTZWN1cml0
     6eTEcMBoGA1UEChMTVHdpc3RlZCBNYXRyaXggTGFiczEeMBwGA1UEAxMVZmFrZS1j
     7YS0xLmV4YW1wbGUuY29tMREwDwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMx
     8IjAgBgkqhkiG9w0BCQEWE25vcmVwbHlAZXhhbXBsZS5jb20xETAPBgNVBAcTCE5l
     9dyBZb3JrMB4XDTEwMDkyMTAxMjUxNFoXDTExMDkyMTAxMjUxNFowgagxETAPBgNV
     10BAsTCFNlY3VyaXR5MRwwGgYDVQQKExNUd2lzdGVkIE1hdHJpeCBMYWJzMR4wHAYD
     11VQQDExVmYWtlLWNhLTEuZXhhbXBsZS5jb20xETAPBgNVBAgTCE5ldyBZb3JrMQsw
     12CQYDVQQGEwJVUzEiMCAGCSqGSIb3DQEJARYTbm9yZXBseUBleGFtcGxlLmNvbTER
     13MA8GA1UEBxMITmV3IFlvcmswgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALRb
     14VqC0CsaFgq1vbwPfs8zoP3ZYC/0sPMv0RJN+f3Dc7Q6YgNHS7o7TM3uAy/McADeW
     15rwVuNJGe9k+4ZBHysmBH1sG64fHT5TlK9saPcUQqkubSWj4cKSDtVbQERWqC5Dy+
     16qTQeZGYoPEMlnRXgMpST04DG//Dgzi4PYqUOjwxTAgMBAAEwDQYJKoZIhvcNAQEE
     17BQADgYEAqNEdMXWEs8Co76wxL3/cSV3MjiAroVxJdI/3EzlnfPi1JeibbdWw31fC
     18bn6428KTjjfhS31zo1yHG3YNXFEJXRscwLAH7ogz5kJwZMy/oS/96EFM10bkNwkK
     19v+nWKN8i3t/E5TEIl3BPN8tchtWmH0rycVuzs5LwaewwR1AnUE4=
     20-----END CERTIFICATE-----
     21-----BEGIN CERTIFICATE-----
     22MIICwjCCAisCAgTSMA0GCSqGSIb3DQEBBAUAMIGoMREwDwYDVQQLEwhTZWN1cml0
     23eTEcMBoGA1UEChMTVHdpc3RlZCBNYXRyaXggTGFiczEeMBwGA1UEAxMVZmFrZS1j
     24YS0yLmV4YW1wbGUuY29tMREwDwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMx
     25IjAgBgkqhkiG9w0BCQEWE25vcmVwbHlAZXhhbXBsZS5jb20xETAPBgNVBAcTCE5l
     26dyBZb3JrMB4XDTEwMDkyMTAxMjUzMVoXDTExMDkyMTAxMjUzMVowgagxETAPBgNV
     27BAsTCFNlY3VyaXR5MRwwGgYDVQQKExNUd2lzdGVkIE1hdHJpeCBMYWJzMR4wHAYD
     28VQQDExVmYWtlLWNhLTIuZXhhbXBsZS5jb20xETAPBgNVBAgTCE5ldyBZb3JrMQsw
     29CQYDVQQGEwJVUzEiMCAGCSqGSIb3DQEJARYTbm9yZXBseUBleGFtcGxlLmNvbTER
     30MA8GA1UEBxMITmV3IFlvcmswgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMNn
     31b3EcKqBedQed1qJC4uGVx8PYmn2vxL3QwCVW1w0VjpZXyhCq/2VrYBhJAXRzpfvE
     32dCqhtJKcdifwavUrTfr4yXu1MvWA0YuaAkj1TbmlHHQYACf3h+MPOXroYzhT72bO
     33FSSLDWuitj0ozR+2Fk15QwLWUxaYLmwylxXAf7vpAgMBAAEwDQYJKoZIhvcNAQEE
     34BQADgYEADB2N6VHHhm5M2rJqqGDXMm2dU+7abxiuN+PUygN2LXIsqdGBS6U7/rta
     35lJNVeRaM423c8imfuklkIBG9Msn5+xm1xIMIULoi/efActDLbsX1x6IyHQrG5aDP
     36/RMKBio9RjS8ajgSwyYVUZiCZBsn/T0/JS8K61YLpiv4Tg8uXmM=
     37-----END CERTIFICATE-----
  • twisted/internet/test/test_endpoints.py

    diff --git twisted/internet/test/test_endpoints.py twisted/internet/test/test_endpoints.py
    index 19554f6..4e8d3c4 100644
    if not _PY3: 
    4141    from twisted.internet.endpoints import StandardErrorBehavior
    4242
    4343    casPath = getModule(__name__).filePath.sibling("fake_CAs")
     44    chainPath = casPath.child("chain.pem")
     45    notACertPath = getModule(__name__).filePath.sibling('__init__.py')
    4446    escapedPEMPathName = endpoints.quoteStringArgument(pemPath.path)
    4547    escapedCAsPathName = endpoints.quoteStringArgument(casPath.path)
     48    escapedChainPathName = endpoints.quoteStringArgument(chainPath.path)
     49    notACertQuotedPathName = endpoints.quoteStringArgument(notACertPath)
     50
    4651
    4752try:
    4853    from twisted.test.test_sslverify import makeCertificate
    class ServerStringTests(unittest.TestCase): 
    18051810        server = endpoints.serverFromString(
    18061811            reactor,
    18071812            "ssl:1234:backlog=12:privateKey=%s:"
    1808             "certKey=%s:sslmethod=TLSv1_METHOD:interface=10.0.0.1"
    1809             % (escapedPEMPathName, escapedPEMPathName))
     1813            "certKey=%s:sslmethod=TLSv1_METHOD:interface=10.0.0.1:"
     1814            "extraCertChain=%s"
     1815            % (escapedPEMPathName, escapedPEMPathName, escapedChainPathName))
    18101816        self.assertIsInstance(server, endpoints.SSL4ServerEndpoint)
    18111817        self.assertIdentical(server._reactor, reactor)
    18121818        self.assertEqual(server._port, 1234)
    18131819        self.assertEqual(server._backlog, 12)
    18141820        self.assertEqual(server._interface, "10.0.0.1")
    18151821        self.assertEqual(server._sslContextFactory.method, TLSv1_METHOD)
    1816         ctx = server._sslContextFactory.getContext()
    1817         self.assertIsInstance(ctx, ContextType)
     1822        cf = server._sslContextFactory
     1823        self.assertEqual(cf.certificate.digest('sha1'),
     1824                         testCertificate.original.digest('sha1'))
     1825        # Test chain file is just a concatenation of thing1.pem and thing2.pem
     1826        # so we can check that loading has succeeded and order has been
     1827        # preserved.
     1828        expectedChainCerts = [
     1829            Certificate.loadPEM(casPath.child("thing%d.pem" % (n,))
     1830                                .getContent())
     1831            for n in [1, 2]
     1832        ]
     1833        self.assertEqual(cf.extraCertChain[0].digest('sha1'),
     1834                         expectedChainCerts[0].digest('sha1'))
     1835        self.assertEqual(cf.extraCertChain[1].digest('sha1'),
     1836                         expectedChainCerts[1].digest('sha1'))
     1837        self.assertIsInstance(cf.getContext(), ContextType)
    18181838
    18191839
    18201840    def test_sslWithDefaults(self):
    class ServerStringTests(unittest.TestCase): 
    18311851        self.assertEqual(server._backlog, 50)
    18321852        self.assertEqual(server._interface, "")
    18331853        self.assertEqual(server._sslContextFactory.method, SSLv23_METHOD)
     1854        self.assertEqual(server._sslContextFactory.extraCertChain, [])
    18341855        ctx = server._sslContextFactory.getContext()
    18351856        self.assertIsInstance(ctx, ContextType)
    18361857
     1858
     1859    def test_sslChainFileMustContainCert(self):
     1860        """
     1861        If C{extraCertChain} is passed, it has to contain at least one valid
     1862        certificate in PEM format.
     1863        """
     1864        reactor = object()
     1865        # NB the strports string is the same as in the valid case except for
     1866        # a different chain file.  We use __init__.py which will never contain
     1867        # any certificates.
     1868        self.assertRaises(
     1869            ValueError,
     1870            endpoints.serverFromString,
     1871            reactor,
     1872            "ssl:1234:backlog=12:privateKey=%s:"
     1873            "certKey=%s:sslmethod=TLSv1_METHOD:interface=10.0.0.1:"
     1874            "extraCertChain=%s"
     1875            % (escapedPEMPathName, escapedPEMPathName, notACertQuotedPathName)
     1876        )
     1877
     1878
    18371879    if skipSSL:
    18381880        test_ssl.skip = test_sslWithDefaults.skip = skipSSL
     1881        test_sslChainFileMustContainCert.skip = skipSSL
    18391882
    18401883
    18411884    def test_unix(self):
  • new file twisted/topfiles/6499.feature

    diff --git twisted/topfiles/6499.feature twisted/topfiles/6499.feature
    new file mode 100644
    index 0000000..c240d8d
    - +  
     1strports now support the specification of chain certificates.