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

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

Updates as per zooko’s & exarkun’s feedback.

  • doc/core/howto/endpoints.xhtml

    diff --git doc/core/howto/endpoints.xhtml doc/core/howto/endpoints.xhtml
    index 6e2ba2a..29a8106 100644
    particular action, then disposed of.</p> 
    101101
    102102<p><code class="API" base="twisted.internet.endpoints">connectProtocol</code>
    103103connects a <code class="API" base="twisted.internet.protocol">Protocol</code>
    104 instance to a given ><code class="API"
     104instance to a given <code class="API"
    105105base="twisted.internet.interfaces">IStreamClientEndpoint</code>. It returns
    106106a <code>Deferred</code> which fires with the <code>Protocol</code> once the
    107107connection has been made. Connection attempts may fail, and so
    available.</p> 
    238238  IPv6 address literal instead.  For example,
    239239  <code>tcp6:port=80:interface=2001\:0DB8\:f00e\:eb00\:\:1</code>.
    240240  </li>
    241   <li>SSL.  All TCP arguments are supported, plus: certKey, privateKey, and
    242   sslmethod.  certKey (optional, defaults to the value of privateKey) gives a
     241  <li>SSL.  All TCP arguments are supported, plus: certKey, privateKey, extraCertChain, and sslmethod.
     242  certKey (optional, defaults to the value of privateKey) gives a
    243243  filesystem path to a certificate (PEM format).  privateKey gives a filesystem
    244   path to a a private key (PEM format).  sslmethod indicates which SSL/TLS
    245   version to use (a value like TLSv1_METHOD). For example,
    246   <code>ssl:port=443:privateKey=/etc/ssl/server.pem:sslmethod=SSLv3_METHOD</code>.
     244  path to a private key (PEM format).
     245  extraCertChain gives a filesystem path to a file with one or more concatenated certificates in PEM format that establish the chain from a root CA to the one that signed your certificate.
     246  sslmethod indicates which SSL/TLS version to use (a value like TLSv1_METHOD).
     247  For example, <code>ssl:port=443:privateKey=/etc/ssl/server.pem:extraCertChain=/etc/ssl/chain.pem:sslmethod=SSLv3_METHOD</code>.
    247248  </li>
    248249  <li>UNIX.  Supported arguments: address, mode, backlog, lockfile.  address
    249250  gives a filesystem path to listen on with a UNIX domain socket server.  mode
  • twisted/internet/endpoints.py

    diff --git twisted/internet/endpoints.py twisted/internet/endpoints.py
    index aa8b30b..e94a839 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 establish the chain from a root CA to
     938        the CA that signed your L{certKey}.
     939    @type extraCertChain: L{bytes}
     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        chainCertificates = [ssl.Certificate.loadPEM(chainCertPEM).original
     963                             for chainCertPEM in matches]
     964        if not chainCertificates:
     965            raise ValueError(
     966                "Specified chain file '%s' doesn't contain any valid "
     967                "certificates in PEM format." % (extraCertChain,)
     968            )
     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..5de40ce 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")
    4445    escapedPEMPathName = endpoints.quoteStringArgument(pemPath.path)
    4546    escapedCAsPathName = endpoints.quoteStringArgument(casPath.path)
     47    escapedChainPathName = endpoints.quoteStringArgument(chainPath.path)
     48
    4649
    4750try:
    4851    from twisted.test.test_sslverify import makeCertificate
    class ServerStringTests(unittest.TestCase): 
    18191822
    18201823    def test_sslWithDefaults(self):
    18211824        """
    1822         An SSL strport description with minimal arguments returns a properly
    1823         initialized L{SSL4ServerEndpoint} instance.
     1825        An SSL string endpoint description with minimal arguments returns
     1826        a properly initialized L{SSL4ServerEndpoint} instance.
    18241827        """
    18251828        reactor = object()
    18261829        server = endpoints.serverFromString(
    class ServerStringTests(unittest.TestCase): 
    18341837        ctx = server._sslContextFactory.getContext()
    18351838        self.assertIsInstance(ctx, ContextType)
    18361839
     1840
     1841    # Use a class variable to ensure we use the exactly same endpoint string
     1842    # except for the chain file itself.
     1843    SSL_CHAIN_TEMPLATE = ("ssl:1234:privateKey=%s:extraCertChain=%%s"
     1844                          % (escapedPEMPathName,))
     1845
     1846
     1847    def test_sslChainLoads(self):
     1848        """
     1849        Specifying a chain file loads the contained certificates in the right
     1850        order.
     1851        """
     1852        server = endpoints.serverFromString(
     1853            object(),
     1854            self.SSL_CHAIN_TEMPLATE % (escapedChainPathName,)
     1855        )
     1856        # Test chain file is just a concatenation of thing1.pem and thing2.pem
     1857        # so we can check that loading has succeeded and order has been
     1858        # preserved.
     1859        expectedChainCerts = [
     1860            Certificate.loadPEM(casPath.child("thing%d.pem" % (n,))
     1861                                .getContent())
     1862            for n in [1, 2]
     1863        ]
     1864        cf = server._sslContextFactory
     1865        self.assertEqual(cf.extraCertChain[0].digest('sha1'),
     1866                         expectedChainCerts[0].digest('sha1'))
     1867        self.assertEqual(cf.extraCertChain[1].digest('sha1'),
     1868                         expectedChainCerts[1].digest('sha1'))
     1869
     1870
     1871    def test_sslChainFileMustContainCert(self):
     1872        """
     1873        If C{extraCertChain} is passed, it has to contain at least one valid
     1874        certificate in PEM format.
     1875        """
     1876        fp = FilePath(self.mktemp())
     1877        fp.create()
     1878        # NB the endpoint string is the same as in the valid case except for
     1879        # a different chain file.  We use __init__.py which will never contain
     1880        # any certificates.
     1881        self.assertRaises(
     1882            ValueError,
     1883            endpoints.serverFromString,
     1884            object(),
     1885            self.SSL_CHAIN_TEMPLATE % (fp.path,)
     1886        )
     1887
     1888
    18371889    if skipSSL:
    18381890        test_ssl.skip = test_sslWithDefaults.skip = skipSSL
     1891        test_sslChainLoads.skip = skipSSL
     1892        test_sslChainFileMustContainCert.skip = skipSSL
    18391893
    18401894
    18411895    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..d212c2b
    - +  
     1SSL server endpoint string descriptions now support the specification of chain certificates.