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

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

Add endpoints documentation.

  • doc/core/howto/endpoints.xhtml

    diff --git doc/core/howto/endpoints.xhtml doc/core/howto/endpoints.xhtml
    index 6e2ba2a..19532ea 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
    243   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>.
     241  <li>SSL.  All TCP arguments are supported, plus: certKey, privateKey,
     242  extraCertChain and sslmethod.  certKey (optional, defaults to the value of
     243  privateKey) gives a filesystem path to a certificate (PEM format).
     244  privateKey gives a filesystem path to a private key (PEM format).
     245  extraCertChain gives a filesystem path to a file with one or more
     246  concatenated certificates in PEM format that establish the chain of trust
     247  from a trustworthy CA to the one that signed you certificate.  sslmethod
     248  indicates which SSL/TLS version to use (a value like TLSv1_METHOD).  For
     249  example,
     250  <code>ssl:port=443:privateKey=/etc/ssl/server.pem:extraCertChain=/etc/ssl/chain.pem:sslmethod=SSLv3_METHOD</code>.
    247251  </li>
    248252  <li>UNIX.  Supported arguments: address, mode, backlog, lockfile.  address
    249253  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..497d054 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 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.