[Python-checkins] cpython: Issue #8109: The ssl module now has support for server-side SNI, thanks to a

antoine.pitrou python-checkins at python.org
Sat Jan 5 21:22:50 CET 2013


http://hg.python.org/cpython/rev/927afb7bca2a
changeset:   81298:927afb7bca2a
user:        Antoine Pitrou <solipsis at pitrou.net>
date:        Sat Jan 05 21:20:29 2013 +0100
summary:
  Issue #8109: The ssl module now has support for server-side SNI, thanks to a :meth:`SSLContext.set_servername_callback` method.
Patch by Daniel Black.

files:
  Doc/library/ssl.rst        |   71 +++++++
  Lib/ssl.py                 |   92 +++++---
  Lib/test/keycert3.pem      |   73 +++++++
  Lib/test/keycert4.pem      |   73 +++++++
  Lib/test/make_ssl_certs.py |  112 ++++++++++-
  Lib/test/pycacert.pem      |   78 +++++++
  Lib/test/pycakey.pem       |   28 ++
  Lib/test/test_ssl.py       |  138 +++++++++++++-
  Misc/ACKS                  |    1 +
  Misc/NEWS                  |    4 +
  Modules/_ssl.c             |  253 ++++++++++++++++++++++++-
  11 files changed, 881 insertions(+), 42 deletions(-)


diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -533,6 +533,19 @@
 
    .. versionadded:: 3.2
 
+.. data:: ALERT_DESCRIPTION_HANDSHAKE_FAILURE
+          ALERT_DESCRIPTION_INTERNAL_ERROR
+          ALERT_DESCRIPTION_*
+
+   Alert Descriptions from :rfc:`5246` and others. The `IANA TLS Alert Registry
+   <http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6>`_
+   contains this list and references to the RFCs where their meaning is defined.
+
+   Used as the return value of the callback function in
+   :meth:`SSLContext.set_servername_callback`.
+
+   .. versionadded:: 3.4
+
 
 SSL Sockets
 -----------
@@ -780,6 +793,55 @@
 
    .. versionadded:: 3.3
 
+.. method:: SSLContext.set_servername_callback(server_name_callback)
+
+   Register a callback function that will be called after the TLS Client Hello
+   handshake message has been received by the SSL/TLS server when the TLS client
+   specifies a server name indication. The server name indication mechanism
+   is specified in :rfc:`6066` section 3 - Server Name Indication.
+
+   Only one callback can be set per ``SSLContext``.  If *server_name_callback*
+   is ``None`` then the callback is disabled. Calling this function a
+   subsequent time will disable the previously registered callback.
+
+   The callback function, *server_name_callback*, will be called with three
+   arguments; the first being the :class:`ssl.SSLSocket`, the second is a string
+   that represents the server name that the client is intending to communicate
+   and the third argument is the original :class:`SSLContext`. The server name
+   argument is the IDNA decoded server name.
+
+   A typical use of this callback is to change the :class:`ssl.SSLSocket`'s
+   :attr:`SSLSocket.context` attribute to a new object of type
+   :class:`SSLContext` representing a certificate chain that matches the server
+   name.
+
+   Due to the early negotiation phase of the TLS connection, only limited
+   methods and attributes are usable like
+   :meth:`SSLSocket.selected_npn_protocol` and :attr:`SSLSocket.context`.
+   :meth:`SSLSocket.getpeercert`, :meth:`SSLSocket.getpeercert`,
+   :meth:`SSLSocket.cipher` and :meth:`SSLSocket.compress` methods require that
+   the TLS connection has progressed beyond the TLS Client Hello and therefore
+   will not contain return meaningful values nor can they be called safely.
+
+   The *server_name_callback* function must return ``None`` to allow the
+   the TLS negotiation to continue.  If a TLS failure is required, a constant
+   :const:`ALERT_DESCRIPTION_* <ALERT_DESCRIPTION_INTERNAL_ERROR>` can be
+   returned.  Other return values will result in a TLS fatal error with
+   :const:`ALERT_DESCRIPTION_INTERNAL_ERROR`.
+
+   If there is a IDNA decoding error on the server name, the TLS connection
+   will terminate with an :const:`ALERT_DESCRIPTION_INTERNAL_ERROR` fatal TLS
+   alert message to the client.
+
+   If an exception is raised from the *server_name_callback* function the TLS
+   connection will terminate with a fatal TLS alert message
+   :const:`ALERT_DESCRIPTION_HANDSHAKE_FAILURE`.
+
+   This method will raise :exc:`NotImplementedError` if the OpenSSL library
+   had OPENSSL_NO_TLSEXT defined when it was built.
+
+   .. versionadded:: 3.4
+
 .. method:: SSLContext.load_dh_params(dhfile)
 
    Load the key generation parameters for Diffie-Helman (DH) key exchange.
@@ -1313,3 +1375,12 @@
 
    `RFC 4366: Transport Layer Security (TLS) Extensions <http://www.ietf.org/rfc/rfc4366>`_
        Blake-Wilson et. al.
+
+   `RFC 5246: The Transport Layer Security (TLS) Protocol Version 1.2 <http://www.ietf.org/rfc/rfc5246>`_
+       T. Dierks et. al.
+
+   `RFC 6066: Transport Layer Security (TLS) Extensions <http://www.ietf.org/rfc/rfc6066>`_
+       D. Eastlake
+
+   `IANA TLS: Transport Layer Security (TLS) Parameters <http://www.iana.org/assignments/tls-parameters/tls-parameters.xml>`_
+       IANA
diff --git a/Lib/ssl.py b/Lib/ssl.py
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -52,6 +52,37 @@
 PROTOCOL_SSLv3
 PROTOCOL_SSLv23
 PROTOCOL_TLSv1
+
+The following constants identify various SSL alert message descriptions as per
+http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6
+
+ALERT_DESCRIPTION_CLOSE_NOTIFY
+ALERT_DESCRIPTION_UNEXPECTED_MESSAGE
+ALERT_DESCRIPTION_BAD_RECORD_MAC
+ALERT_DESCRIPTION_RECORD_OVERFLOW
+ALERT_DESCRIPTION_DECOMPRESSION_FAILURE
+ALERT_DESCRIPTION_HANDSHAKE_FAILURE
+ALERT_DESCRIPTION_BAD_CERTIFICATE
+ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE
+ALERT_DESCRIPTION_CERTIFICATE_REVOKED
+ALERT_DESCRIPTION_CERTIFICATE_EXPIRED
+ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN
+ALERT_DESCRIPTION_ILLEGAL_PARAMETER
+ALERT_DESCRIPTION_UNKNOWN_CA
+ALERT_DESCRIPTION_ACCESS_DENIED
+ALERT_DESCRIPTION_DECODE_ERROR
+ALERT_DESCRIPTION_DECRYPT_ERROR
+ALERT_DESCRIPTION_PROTOCOL_VERSION
+ALERT_DESCRIPTION_INSUFFICIENT_SECURITY
+ALERT_DESCRIPTION_INTERNAL_ERROR
+ALERT_DESCRIPTION_USER_CANCELLED
+ALERT_DESCRIPTION_NO_RENEGOTIATION
+ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION
+ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE
+ALERT_DESCRIPTION_UNRECOGNIZED_NAME
+ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE
+ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE
+ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY
 """
 
 import textwrap
@@ -66,35 +97,24 @@
     SSLSyscallError, SSLEOFError,
     )
 from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
-from _ssl import (
-    OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1,
-    OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE
-    )
-try:
-    from _ssl import OP_NO_COMPRESSION
-except ImportError:
-    pass
-try:
-    from _ssl import OP_SINGLE_ECDH_USE
-except ImportError:
-    pass
 from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes
-from _ssl import (
-    SSL_ERROR_ZERO_RETURN,
-    SSL_ERROR_WANT_READ,
-    SSL_ERROR_WANT_WRITE,
-    SSL_ERROR_WANT_X509_LOOKUP,
-    SSL_ERROR_SYSCALL,
-    SSL_ERROR_SSL,
-    SSL_ERROR_WANT_CONNECT,
-    SSL_ERROR_EOF,
-    SSL_ERROR_INVALID_ERROR_CODE,
-    )
+
+def _import_symbols(prefix):
+    for n in dir(_ssl):
+        if n.startswith(prefix):
+            globals()[n] = getattr(_ssl, n)
+
+_import_symbols('OP_')
+_import_symbols('ALERT_DESCRIPTION_')
+_import_symbols('SSL_ERROR_')
+
 from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN
+
 from _ssl import (PROTOCOL_SSLv3, PROTOCOL_SSLv23,
                   PROTOCOL_TLSv1)
 from _ssl import _OPENSSL_API_VERSION
 
+
 _PROTOCOL_NAMES = {
     PROTOCOL_TLSv1: "TLSv1",
     PROTOCOL_SSLv23: "SSLv23",
@@ -190,7 +210,7 @@
     """An SSLContext holds various SSL-related configuration options and
     data, such as certificates and possibly a private key."""
 
-    __slots__ = ('protocol',)
+    __slots__ = ('protocol', '__weakref__')
 
     def __new__(cls, protocol, *args, **kwargs):
         self = _SSLContext.__new__(cls, protocol)
@@ -238,7 +258,7 @@
                  _context=None):
 
         if _context:
-            self.context = _context
+            self._context = _context
         else:
             if server_side and not certfile:
                 raise ValueError("certfile must be specified for server-side "
@@ -247,16 +267,16 @@
                 raise ValueError("certfile must be specified")
             if certfile and not keyfile:
                 keyfile = certfile
-            self.context = SSLContext(ssl_version)
-            self.context.verify_mode = cert_reqs
+            self._context = SSLContext(ssl_version)
+            self._context.verify_mode = cert_reqs
             if ca_certs:
-                self.context.load_verify_locations(ca_certs)
+                self._context.load_verify_locations(ca_certs)
             if certfile:
-                self.context.load_cert_chain(certfile, keyfile)
+                self._context.load_cert_chain(certfile, keyfile)
             if npn_protocols:
-                self.context.set_npn_protocols(npn_protocols)
+                self._context.set_npn_protocols(npn_protocols)
             if ciphers:
-                self.context.set_ciphers(ciphers)
+                self._context.set_ciphers(ciphers)
             self.keyfile = keyfile
             self.certfile = certfile
             self.cert_reqs = cert_reqs
@@ -298,7 +318,7 @@
         if connected:
             # create the SSL object
             try:
-                self._sslobj = self.context._wrap_socket(self, server_side,
+                self._sslobj = self._context._wrap_socket(self, server_side,
                                                          server_hostname)
                 if do_handshake_on_connect:
                     timeout = self.gettimeout()
@@ -310,6 +330,14 @@
             except OSError as x:
                 self.close()
                 raise x
+    @property
+    def context(self):
+        return self._context
+
+    @context.setter
+    def context(self, ctx):
+        self._context = ctx
+        self._sslobj.context = ctx
 
     def dup(self):
         raise NotImplemented("Can't dup() %s instances" %
diff --git a/Lib/test/keycert3.pem b/Lib/test/keycert3.pem
new file mode 100644
--- /dev/null
+++ b/Lib/test/keycert3.pem
@@ -0,0 +1,73 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMLgD0kAKDb5cFyP
+jbwNfR5CtewdXC+kMXAWD8DLxiTTvhMW7qVnlwOm36mZlszHKvsRf05lT4pegiFM
+9z2j1OlaN+ci/X7NU22TNN6crYSiN77FjYJP464j876ndSxyD+rzys386T+1r1aZ
+aggEdkj1TsSsv1zWIYKlPIjlvhuxAgMBAAECgYA0aH+T2Vf3WOPv8KdkcJg6gCRe
+yJKXOWgWRcicx/CUzOEsTxmFIDPLxqAWA3k7v0B+3vjGw5Y9lycV/5XqXNoQI14j
+y09iNsumds13u5AKkGdTJnZhQ7UKdoVHfuP44ZdOv/rJ5/VD6F4zWywpe90pcbK+
+AWDVtusgGQBSieEl1QJBAOyVrUG5l2yoUBtd2zr/kiGm/DYyXlIthQO/A3/LngDW
+5/ydGxVsT7lAVOgCsoT+0L4efTh90PjzW8LPQrPBWVMCQQDS3h/FtYYd5lfz+FNL
+9CEe1F1w9l8P749uNUD0g317zv1tatIqVCsQWHfVHNdVvfQ+vSFw38OORO00Xqs9
+1GJrAkBkoXXEkxCZoy4PteheO/8IWWLGGr6L7di6MzFl1lIqwT6D8L9oaV2vynFT
+DnKop0pa09Unhjyw57KMNmSE2SUJAkEArloTEzpgRmCq4IK2/NpCeGdHS5uqRlbh
+1VIa/xGps7EWQl5Mn8swQDel/YP3WGHTjfx7pgSegQfkyaRtGpZ9OQJAa9Vumj8m
+JAAtI0Bnga8hgQx7BhTQY4CadDxyiRGOGYhwUzYVCqkb2sbVRH9HnwUaJT7cWBY3
+RnJdHOMXWem7/w==
+-----END PRIVATE KEY-----
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 12723342612721443281 (0xb09264b1f2da21d1)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+        Validity
+            Not Before: Jan  4 19:47:07 2013 GMT
+            Not After : Nov 13 19:47:07 2022 GMT
+        Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:c2:e0:0f:49:00:28:36:f9:70:5c:8f:8d:bc:0d:
+                    7d:1e:42:b5:ec:1d:5c:2f:a4:31:70:16:0f:c0:cb:
+                    c6:24:d3:be:13:16:ee:a5:67:97:03:a6:df:a9:99:
+                    96:cc:c7:2a:fb:11:7f:4e:65:4f:8a:5e:82:21:4c:
+                    f7:3d:a3:d4:e9:5a:37:e7:22:fd:7e:cd:53:6d:93:
+                    34:de:9c:ad:84:a2:37:be:c5:8d:82:4f:e3:ae:23:
+                    f3:be:a7:75:2c:72:0f:ea:f3:ca:cd:fc:e9:3f:b5:
+                    af:56:99:6a:08:04:76:48:f5:4e:c4:ac:bf:5c:d6:
+                    21:82:a5:3c:88:e5:be:1b:b1
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: sha1WithRSAEncryption
+         2f:42:5f:a3:09:2c:fa:51:88:c7:37:7f:ea:0e:63:f0:a2:9a:
+         e5:5a:e2:c8:20:f0:3f:60:bc:c8:0f:b6:c6:76:ce:db:83:93:
+         f5:a3:33:67:01:8e:04:cd:00:9a:73:fd:f3:35:86:fa:d7:13:
+         e2:46:c6:9d:c0:29:53:d4:a9:90:b8:77:4b:e6:83:76:e4:92:
+         d6:9c:50:cf:43:d0:c6:01:77:61:9a:de:9b:70:f7:72:cd:59:
+         00:31:69:d9:b4:ca:06:9c:6d:c3:c7:80:8c:68:e6:b5:a2:f8:
+         ef:1d:bb:16:9f:77:77:ef:87:62:22:9b:4d:69:a4:3a:1a:f1:
+         21:5e:8c:32:ac:92:fd:15:6b:18:c2:7f:15:0d:98:30:ca:75:
+         8f:1a:71:df:da:1d:b2:ef:9a:e8:2d:2e:02:fd:4a:3c:aa:96:
+         0b:06:5d:35:b3:3d:24:87:4b:e0:b0:58:60:2f:45:ac:2e:48:
+         8a:b0:99:10:65:27:ff:cc:b1:d8:fd:bd:26:6b:b9:0c:05:2a:
+         f4:45:63:35:51:07:ed:83:85:fe:6f:69:cb:bb:40:a8:ae:b6:
+         3b:56:4a:2d:a4:ed:6d:11:2c:4d:ed:17:24:fd:47:bc:d3:41:
+         a2:d3:06:fe:0c:90:d8:d8:94:26:c4:ff:cc:a1:d8:42:77:eb:
+         fc:a9:94:71
+-----BEGIN CERTIFICATE-----
+MIICpDCCAYwCCQCwkmSx8toh0TANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJY
+WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV
+BAMMDW91ci1jYS1zZXJ2ZXIwHhcNMTMwMTA0MTk0NzA3WhcNMjIxMTEzMTk0NzA3
+WjBfMQswCQYDVQQGEwJYWTEXMBUGA1UEBxMOQ2FzdGxlIEFudGhyYXgxIzAhBgNV
+BAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRIwEAYDVQQDEwlsb2NhbGhv
+c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMLgD0kAKDb5cFyPjbwNfR5C
+tewdXC+kMXAWD8DLxiTTvhMW7qVnlwOm36mZlszHKvsRf05lT4pegiFM9z2j1Ola
+N+ci/X7NU22TNN6crYSiN77FjYJP464j876ndSxyD+rzys386T+1r1aZaggEdkj1
+TsSsv1zWIYKlPIjlvhuxAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAC9CX6MJLPpR
+iMc3f+oOY/CimuVa4sgg8D9gvMgPtsZ2ztuDk/WjM2cBjgTNAJpz/fM1hvrXE+JG
+xp3AKVPUqZC4d0vmg3bkktacUM9D0MYBd2Ga3ptw93LNWQAxadm0ygacbcPHgIxo
+5rWi+O8duxafd3fvh2Iim01ppDoa8SFejDKskv0VaxjCfxUNmDDKdY8acd/aHbLv
+mugtLgL9SjyqlgsGXTWzPSSHS+CwWGAvRawuSIqwmRBlJ//Msdj9vSZruQwFKvRF
+YzVRB+2Dhf5vacu7QKiutjtWSi2k7W0RLE3tFyT9R7zTQaLTBv4MkNjYlCbE/8yh
+2EJ36/yplHE=
+-----END CERTIFICATE-----
diff --git a/Lib/test/keycert4.pem b/Lib/test/keycert4.pem
new file mode 100644
--- /dev/null
+++ b/Lib/test/keycert4.pem
@@ -0,0 +1,73 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK5UQiMI5VkNs2Qv
+L7gUaiDdFevNUXRjU4DHAe3ZzzYLZNE69h9gO9VCSS16tJ5fT5VEu0EZyGr0e3V2
+NkX0ZoU0Hc/UaY4qx7LHmn5SYZpIxhJnkf7SyHJK1zUaGlU0/LxYqIuGCtF5dqx1
+L2OQhEx1GM6RydHdgX69G64LXcY5AgMBAAECgYAhsRMfJkb9ERLMl/oG/5sLQu9L
+pWDKt6+ZwdxzlZbggQ85CMYshjLKIod2DLL/sLf2x1PRXyRG131M1E3k8zkkz6de
+R1uDrIN/x91iuYzfLQZGh8bMY7Yjd2eoroa6R/7DjpElGejLxOAaDWO0ST2IFQy9
+myTGS2jSM97wcXfsSQJBANP3jelJoS5X6BRjTSneY21wcocxVuQh8pXpErALVNsT
+drrFTeaBuZp7KvbtnIM5g2WRNvaxLZlAY/hXPJvi6ncCQQDSix1cebml6EmPlEZS
+Mm8gwI2F9ufUunwJmBJcz826Do0ZNGByWDAM/JQZH4FX4GfAFNuj8PUb+GQfadkx
+i1DPAkEA0lVsNHojvuDsIo8HGuzarNZQT2beWjJ1jdxh9t7HrTx7LIps6rb/fhOK
+Zs0R6gVAJaEbcWAPZ2tFyECInAdnsQJAUjaeXXjuxFkjOFym5PvqpvhpivEx78Bu
+JPTr3rAKXmfGMxxfuOa0xK1wSyshP6ZR/RBn/+lcXPKubhHQDOegwwJAJF1DBQnN
++/tLmOPULtDwfP4Zixn+/8GmGOahFoRcu6VIGHmRilJTn6MOButw7Glv2YdeC6l/
+e83Gq6ffLVfKNQ==
+-----END PRIVATE KEY-----
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 12723342612721443282 (0xb09264b1f2da21d2)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+        Validity
+            Not Before: Jan  4 19:47:07 2013 GMT
+            Not After : Nov 13 19:47:07 2022 GMT
+        Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=fakehostname
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:ae:54:42:23:08:e5:59:0d:b3:64:2f:2f:b8:14:
+                    6a:20:dd:15:eb:cd:51:74:63:53:80:c7:01:ed:d9:
+                    cf:36:0b:64:d1:3a:f6:1f:60:3b:d5:42:49:2d:7a:
+                    b4:9e:5f:4f:95:44:bb:41:19:c8:6a:f4:7b:75:76:
+                    36:45:f4:66:85:34:1d:cf:d4:69:8e:2a:c7:b2:c7:
+                    9a:7e:52:61:9a:48:c6:12:67:91:fe:d2:c8:72:4a:
+                    d7:35:1a:1a:55:34:fc:bc:58:a8:8b:86:0a:d1:79:
+                    76:ac:75:2f:63:90:84:4c:75:18:ce:91:c9:d1:dd:
+                    81:7e:bd:1b:ae:0b:5d:c6:39
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: sha1WithRSAEncryption
+         ad:45:8a:8e:ef:c6:ef:04:41:5c:2c:4a:84:dc:02:76:0c:d0:
+         66:0f:f0:16:04:58:4d:fd:68:b7:b8:d3:a8:41:a5:5c:3c:6f:
+         65:3c:d1:f8:ce:43:35:e7:41:5f:53:3d:c9:2c:c3:7d:fc:56:
+         4a:fa:47:77:38:9d:bb:97:28:0a:3b:91:19:7f:bc:74:ae:15:
+         6b:bd:20:36:67:45:a5:1e:79:d7:75:e6:89:5c:6d:54:84:d1:
+         95:d7:a7:b4:33:3c:af:37:c4:79:8f:5e:75:dc:75:c2:18:fb:
+         61:6f:2d:dc:38:65:5b:ba:67:28:d0:88:d7:8d:b9:23:5a:8e:
+         e8:c6:bb:db:ce:d5:b8:41:2a:ce:93:08:b6:95:ad:34:20:18:
+         d5:3b:37:52:74:50:0b:07:2c:b0:6d:a4:4c:7b:f4:e0:fd:d1:
+         af:17:aa:20:cd:62:e3:f0:9d:37:69:db:41:bd:d4:1c:fb:53:
+         20:da:88:9d:76:26:67:ce:01:90:a7:80:1d:a9:5b:39:73:68:
+         54:0a:d1:2a:03:1b:8f:3c:43:5d:5d:c4:51:f1:a7:e7:11:da:
+         31:2c:49:06:af:04:f4:b8:3c:99:c4:20:b9:06:36:a2:00:92:
+         61:1d:0c:6d:24:05:e2:82:e1:47:db:a0:5f:ba:b9:fb:ba:fa:
+         49:12:1e:ce
+-----BEGIN CERTIFICATE-----
+MIICpzCCAY8CCQCwkmSx8toh0jANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJY
+WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV
+BAMMDW91ci1jYS1zZXJ2ZXIwHhcNMTMwMTA0MTk0NzA3WhcNMjIxMTEzMTk0NzA3
+WjBiMQswCQYDVQQGEwJYWTEXMBUGA1UEBxMOQ2FzdGxlIEFudGhyYXgxIzAhBgNV
+BAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRUwEwYDVQQDEwxmYWtlaG9z
+dG5hbWUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAK5UQiMI5VkNs2QvL7gU
+aiDdFevNUXRjU4DHAe3ZzzYLZNE69h9gO9VCSS16tJ5fT5VEu0EZyGr0e3V2NkX0
+ZoU0Hc/UaY4qx7LHmn5SYZpIxhJnkf7SyHJK1zUaGlU0/LxYqIuGCtF5dqx1L2OQ
+hEx1GM6RydHdgX69G64LXcY5AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAK1Fio7v
+xu8EQVwsSoTcAnYM0GYP8BYEWE39aLe406hBpVw8b2U80fjOQzXnQV9TPcksw338
+Vkr6R3c4nbuXKAo7kRl/vHSuFWu9IDZnRaUeedd15olcbVSE0ZXXp7QzPK83xHmP
+XnXcdcIY+2FvLdw4ZVu6ZyjQiNeNuSNajujGu9vO1bhBKs6TCLaVrTQgGNU7N1J0
+UAsHLLBtpEx79OD90a8XqiDNYuPwnTdp20G91Bz7UyDaiJ12JmfOAZCngB2pWzlz
+aFQK0SoDG488Q11dxFHxp+cR2jEsSQavBPS4PJnEILkGNqIAkmEdDG0kBeKC4Ufb
+oF+6ufu6+kkSHs4=
+-----END CERTIFICATE-----
diff --git a/Lib/test/make_ssl_certs.py b/Lib/test/make_ssl_certs.py
--- a/Lib/test/make_ssl_certs.py
+++ b/Lib/test/make_ssl_certs.py
@@ -2,6 +2,7 @@
 and friends."""
 
 import os
+import shutil
 import sys
 import tempfile
 from subprocess import *
@@ -20,11 +21,52 @@
 
     [req_x509_extensions]
     subjectAltName         = DNS:{hostname}
+
+    [ ca ]
+    default_ca      = CA_default
+
+    [ CA_default ]
+    dir = cadir
+    database  = $dir/index.txt
+    default_md = sha1
+    default_days = 3600
+    certificate = pycacert.pem
+    private_key = pycakey.pem
+    serial    = $dir/serial
+    RANDFILE  = $dir/.rand
+
+    policy          = policy_match
+
+    [ policy_match ]
+    countryName             = match
+    stateOrProvinceName     = optional
+    organizationName        = match
+    organizationalUnitName  = optional
+    commonName              = supplied
+    emailAddress            = optional
+
+    [ policy_anything ]
+    countryName   = optional
+    stateOrProvinceName = optional
+    localityName    = optional
+    organizationName  = optional
+    organizationalUnitName  = optional
+    commonName    = supplied
+    emailAddress    = optional
+
+
+    [ v3_ca ]
+
+    subjectKeyIdentifier=hash
+    authorityKeyIdentifier=keyid:always,issuer
+    basicConstraints = CA:true
+
     """
 
 here = os.path.abspath(os.path.dirname(__file__))
 
-def make_cert_key(hostname):
+def make_cert_key(hostname, sign=False):
+    print("creating cert for " + hostname)
     tempnames = []
     for i in range(3):
         with tempfile.NamedTemporaryFile(delete=False) as f:
@@ -33,10 +75,25 @@
     try:
         with open(req_file, 'w') as f:
             f.write(req_template.format(hostname=hostname))
-        args = ['req', '-new', '-days', '3650', '-nodes', '-x509',
+        args = ['req', '-new', '-days', '3650', '-nodes',
                 '-newkey', 'rsa:1024', '-keyout', key_file,
-                '-out', cert_file, '-config', req_file]
+                '-config', req_file]
+        if sign:
+            with tempfile.NamedTemporaryFile(delete=False) as f:
+                tempnames.append(f.name)
+                reqfile = f.name
+            args += ['-out', reqfile ]
+
+        else:
+            args += ['-x509', '-out', cert_file ]
         check_call(['openssl'] + args)
+
+        if sign:
+            args = ['ca', '-config', req_file, '-out', cert_file, '-outdir', 'cadir',
+                    '-policy', 'policy_anything', '-batch', '-infiles', reqfile ]
+            check_call(['openssl'] + args)
+
+
         with open(cert_file, 'r') as f:
             cert = f.read()
         with open(key_file, 'r') as f:
@@ -46,6 +103,32 @@
         for name in tempnames:
             os.remove(name)
 
+TMP_CADIR = 'cadir'
+
+def unmake_ca():
+    shutil.rmtree(TMP_CADIR)
+
+def make_ca():
+    os.mkdir(TMP_CADIR)
+    with open(os.path.join('cadir','index.txt'),'a+') as f:
+        pass # empty file
+    with open(os.path.join('cadir','index.txt.attr'),'w+') as f:
+        f.write('unique_subject = no')
+
+    with tempfile.NamedTemporaryFile("w") as t:
+        t.write(req_template.format(hostname='our-ca-server'))
+        t.flush()
+        with tempfile.NamedTemporaryFile() as f:
+            args = ['req', '-new', '-days', '3650', '-extensions', 'v3_ca', '-nodes',
+                    '-newkey', 'rsa:2048', '-keyout', 'pycakey.pem',
+                    '-out', f.name,
+                    '-subj', '/C=XY/L=Castle Anthrax/O=Python Software Foundation CA/CN=our-ca-server']
+            check_call(['openssl'] + args)
+            args = ['ca', '-config', t.name, '-create_serial',
+                    '-out', 'pycacert.pem', '-batch', '-outdir', TMP_CADIR,
+                    '-keyfile', 'pycakey.pem', '-days', '3650',
+                    '-selfsign', '-extensions', 'v3_ca', '-infiles', f.name ]
+            check_call(['openssl'] + args)
 
 if __name__ == '__main__':
     os.chdir(here)
@@ -54,11 +137,34 @@
         f.write(cert)
     with open('ssl_key.pem', 'w') as f:
         f.write(key)
+    print("password protecting ssl_key.pem in ssl_key.passwd.pem")
+    check_call(['openssl','rsa','-in','ssl_key.pem','-out','ssl_key.passwd.pem','-des3','-passout','pass:somepass'])
+    check_call(['openssl','rsa','-in','ssl_key.pem','-out','keycert.passwd.pem','-des3','-passout','pass:somepass'])
+
     with open('keycert.pem', 'w') as f:
         f.write(key)
         f.write(cert)
+
+    with open('keycert.passwd.pem', 'a+') as f:
+        f.write(cert)
+
     # For certificate matching tests
+    make_ca()
     cert, key = make_cert_key('fakehostname')
     with open('keycert2.pem', 'w') as f:
         f.write(key)
         f.write(cert)
+
+    cert, key = make_cert_key('localhost', True)
+    with open('keycert3.pem', 'w') as f:
+        f.write(key)
+        f.write(cert)
+
+    cert, key = make_cert_key('fakehostname', True)
+    with open('keycert4.pem', 'w') as f:
+        f.write(key)
+        f.write(cert)
+
+    unmake_ca()
+    print("\n\nPlease change the values in test_ssl.py, test_parse_cert function related to notAfter,notBefore and serialNumber")
+    check_call(['openssl','x509','-in','keycert.pem','-dates','-serial','-noout'])
diff --git a/Lib/test/pycacert.pem b/Lib/test/pycacert.pem
new file mode 100644
--- /dev/null
+++ b/Lib/test/pycacert.pem
@@ -0,0 +1,78 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 12723342612721443280 (0xb09264b1f2da21d0)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+        Validity
+            Not Before: Jan  4 19:47:07 2013 GMT
+            Not After : Jan  2 19:47:07 2023 GMT
+        Subject: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e7:de:e9:e3:0c:9f:00:b6:a1:fd:2b:5b:96:d2:
+                    6f:cc:e0:be:86:b9:20:5e:ec:03:7a:55:ab:ea:a4:
+                    e9:f9:49:85:d2:66:d5:ed:c7:7a:ea:56:8e:2d:8f:
+                    e7:42:e2:62:28:a9:9f:d6:1b:8e:eb:b5:b4:9c:9f:
+                    14:ab:df:e6:94:8b:76:1d:3e:6d:24:61:ed:0c:bf:
+                    00:8a:61:0c:df:5c:c8:36:73:16:00:cd:47:ba:6d:
+                    a4:a4:74:88:83:23:0a:19:fc:09:a7:3c:4a:4b:d3:
+                    e7:1d:2d:e4:ea:4c:54:21:f3:26:db:89:37:18:d4:
+                    02:bb:40:32:5f:a4:ff:2d:1c:f7:d4:bb:ec:8e:cf:
+                    5c:82:ac:e6:7c:08:6c:48:85:61:07:7f:25:e0:5c:
+                    e0:bc:34:5f:e0:b9:04:47:75:c8:47:0b:8d:bc:d6:
+                    c8:68:5f:33:83:62:d2:20:44:35:b1:ad:81:1a:8a:
+                    cd:bc:35:b0:5c:8b:47:d6:18:e9:9c:18:97:cc:01:
+                    3c:29:cc:e8:1e:e4:e4:c1:b8:de:e7:c2:11:18:87:
+                    5a:93:34:d8:a6:25:f7:14:71:eb:e4:21:a2:d2:0f:
+                    2e:2e:d4:62:00:35:d3:d6:ef:5c:60:4b:4c:a9:14:
+                    e2:dd:15:58:46:37:33:26:b7:e7:2e:5d:ed:42:e4:
+                    c5:4d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                BC:DD:62:D9:76:DA:1B:D2:54:6B:CF:E0:66:9B:1E:1E:7B:56:0C:0B
+            X509v3 Authority Key Identifier: 
+                keyid:BC:DD:62:D9:76:DA:1B:D2:54:6B:CF:E0:66:9B:1E:1E:7B:56:0C:0B
+
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha1WithRSAEncryption
+         7d:0a:f5:cb:8d:d3:5d:bd:99:8e:f8:2b:0f:ba:eb:c2:d9:a6:
+         27:4f:2e:7b:2f:0e:64:d8:1c:35:50:4e:ee:fc:90:b9:8d:6d:
+         a8:c5:c6:06:b0:af:f3:2d:bf:3b:b8:42:07:dd:18:7d:6d:95:
+         54:57:85:18:60:47:2f:eb:78:1b:f9:e8:17:fd:5a:0d:87:17:
+         28:ac:4c:6a:e6:bc:29:f4:f4:55:70:29:42:de:85:ea:ab:6c:
+         23:06:64:30:75:02:8e:53:bc:5e:01:33:37:cc:1e:cd:b8:a4:
+         fd:ca:e4:5f:65:3b:83:1c:86:f1:55:02:a0:3a:8f:db:91:b7:
+         40:14:b4:e7:8d:d2:ee:73:ba:e3:e5:34:2d:bc:94:6f:4e:24:
+         06:f7:5f:8b:0e:a7:8e:6b:de:5e:75:f4:32:9a:50:b1:44:33:
+         9a:d0:05:e2:78:82:ff:db:da:8a:63:eb:a9:dd:d1:bf:a0:61:
+         ad:e3:9e:8a:24:5d:62:0e:e7:4c:91:7f:ef:df:34:36:3b:2f:
+         5d:f5:84:b2:2f:c4:6d:93:96:1a:6f:30:28:f1:da:12:9a:64:
+         b4:40:33:1d:bd:de:2b:53:a8:ea:be:d6:bc:4e:96:f5:44:fb:
+         32:18:ae:d5:1f:f6:69:af:b6:4e:7b:1d:58:ec:3b:a9:53:a3:
+         5e:58:c8:9e
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIJALCSZLHy2iHQMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV
+BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW
+MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xMzAxMDQxOTQ3MDdaFw0yMzAxMDIx
+OTQ3MDdaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg
+Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAOfe6eMMnwC2of0rW5bSb8zgvoa5IF7sA3pV
+q+qk6flJhdJm1e3HeupWji2P50LiYiipn9Ybjuu1tJyfFKvf5pSLdh0+bSRh7Qy/
+AIphDN9cyDZzFgDNR7ptpKR0iIMjChn8Cac8SkvT5x0t5OpMVCHzJtuJNxjUArtA
+Ml+k/y0c99S77I7PXIKs5nwIbEiFYQd/JeBc4Lw0X+C5BEd1yEcLjbzWyGhfM4Ni
+0iBENbGtgRqKzbw1sFyLR9YY6ZwYl8wBPCnM6B7k5MG43ufCERiHWpM02KYl9xRx
+6+QhotIPLi7UYgA109bvXGBLTKkU4t0VWEY3Mya35y5d7ULkxU0CAwEAAaNQME4w
+HQYDVR0OBBYEFLzdYtl22hvSVGvP4GabHh57VgwLMB8GA1UdIwQYMBaAFLzdYtl2
+2hvSVGvP4GabHh57VgwLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
+AH0K9cuN0129mY74Kw+668LZpidPLnsvDmTYHDVQTu78kLmNbajFxgawr/Mtvzu4
+QgfdGH1tlVRXhRhgRy/reBv56Bf9Wg2HFyisTGrmvCn09FVwKULeheqrbCMGZDB1
+Ao5TvF4BMzfMHs24pP3K5F9lO4MchvFVAqA6j9uRt0AUtOeN0u5zuuPlNC28lG9O
+JAb3X4sOp45r3l519DKaULFEM5rQBeJ4gv/b2opj66nd0b+gYa3jnookXWIO50yR
+f+/fNDY7L131hLIvxG2TlhpvMCjx2hKaZLRAMx293itTqOq+1rxOlvVE+zIYrtUf
+9mmvtk57HVjsO6lTo15YyJ4=
+-----END CERTIFICATE-----
diff --git a/Lib/test/pycakey.pem b/Lib/test/pycakey.pem
new file mode 100644
--- /dev/null
+++ b/Lib/test/pycakey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDn3unjDJ8AtqH9
+K1uW0m/M4L6GuSBe7AN6VavqpOn5SYXSZtXtx3rqVo4tj+dC4mIoqZ/WG47rtbSc
+nxSr3+aUi3YdPm0kYe0MvwCKYQzfXMg2cxYAzUe6baSkdIiDIwoZ/AmnPEpL0+cd
+LeTqTFQh8ybbiTcY1AK7QDJfpP8tHPfUu+yOz1yCrOZ8CGxIhWEHfyXgXOC8NF/g
+uQRHdchHC4281shoXzODYtIgRDWxrYEais28NbBci0fWGOmcGJfMATwpzOge5OTB
+uN7nwhEYh1qTNNimJfcUcevkIaLSDy4u1GIANdPW71xgS0ypFOLdFVhGNzMmt+cu
+Xe1C5MVNAgMBAAECggEBAJPM7QuUrPn4cLN/Ysd15lwTWn9oHDFFgkYFvCs66gXE
+ju/6Kx2BjWE4wTJby09AHM/MqB0DvguT7Mf1Q2j3tPQ1HZowg8OwRDleuwp6KIls
+jBbhL0Jdl/5HC67ktWvZ9wNvO/wFG1rQfT6FVajf9LUbWEaSZbOG2SLhHfsHorzu
+xjTJaI3bQ/0+79B1exwk5ruwhzFRd/XpY8hls7D/RfPIuHDlBghkW3N59KFWrf5h
+6bNEh2THm0+IyGcGqs0FD+QCOXyvsjwSUswqrr2ctLREOeDcd5ReUjSxYgjcJRrm
+J7ceIY/+uwDJxw/OlnmBvF6pQMkKwYW2gFztu+g2t4UCgYEA/9yo01Exz4crxXsy
+tAlnDJM++nZcm07rtFjTKHUfKY/cCgNTa8udM0svnfwlid/dpgLsI38gx04HHC1i
+EZ4acz+ToIWedLxM0nq73//xeRWEazOvCz1mMTZaMldahTWAyzN8qVK2B/625Yy4
+wNYWyweBBwEB8MzaCs73spksXOsCgYEA5/7wvhiofYGFAfMuANeJIwDL2OtBnoOv
+mVNfCmi3GC38fzwyi5ZpskWDiS2woJ+LQfs9Qu4EcZbUFLd7gbeOvb5gmFUtYope
+LitUUKunIR18MkQ+mQDBpQPQPhk4QJP5reCbWkrfTu7b5o/iS41s6fBTFmuzhLcT
+C71vFdCyeKcCgYAiCCqYeOtELDmBOeLDmaCQRqGQ1N96dOPbCBmF/xYXBCCDYG/f
+HaUaJnz96YTgstsbcrYP/p/Qgqtlbw/lQf9IpwMuzbcG1ejt8g89OyDWNyt2ytgU
+iaUnFJCos3/Byh0Iah/BsdOueo2/OJl2ZMOBW80orlSgv86cs2y037TL4wKBgQDm
+OOyW+MlbowhnIvfoBfwlLEkefnej4nKD6WRLZBcue5Qyf355X06Mhsc9foXlH+6G
+D9h/bswiHNdhp6N82rdgPGiHQx/CxiUoE/+b/nvgNO5mw6qLE2EXbG1e8pAMJcyE
+bHw+YkawggDfELI036fRj5gki8SeUz8nS1nNgElbyQKBgCRDX9Jh+MwSLu4QBWdt
+/fi+lv3K6kun/fI7EOV1vCV/j871tICu7pu5BrOLxAHqoVfU9AUX299/2KjCb5pv
+kjogiUK6qWCWBlfuqDNWGCoUGt1rhznUva0nNjSMy5rinBhhjpROZC2pw48lOluP
+UuvXsaPph7GTqPuy4Kab12YC
+-----END PRIVATE KEY-----
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -48,6 +48,11 @@
 CAPATH = data_file("capath")
 BYTES_CAPATH = os.fsencode(CAPATH)
 
+# Two keys and certs signed by the same CA (for SNI tests)
+SIGNED_CERTFILE = data_file("keycert3.pem")
+SIGNED_CERTFILE2 = data_file("keycert4.pem")
+SIGNING_CA = data_file("pycacert.pem")
+
 SVN_PYTHON_ORG_ROOT_CERT = data_file("https_svn_python_org_root.pem")
 
 EMPTYCERT = data_file("nullcert.pem")
@@ -59,6 +64,7 @@
 DHFILE = data_file("dh512.pem")
 BYTES_DHFILE = os.fsencode(DHFILE)
 
+
 def handle_error(prefix):
     exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
     if support.verbose:
@@ -89,6 +95,8 @@
     else:
         return func
 
+needs_sni = unittest.skipUnless(ssl.HAS_SNI, "SNI support needed for this test")
+
 
 class BasicSocketTests(unittest.TestCase):
 
@@ -142,6 +150,7 @@
                           (('organizationName', 'Python Software Foundation'),),
                           (('commonName', 'localhost'),))
                         )
+        # Note the next three asserts will fail if the keys are regenerated
         self.assertEqual(p['notAfter'], 'Oct  5 23:01:56 2020 GMT')
         self.assertEqual(p['notBefore'], 'Oct  8 23:01:56 2010 GMT')
         self.assertEqual(p['serialNumber'], 'D7C7381919AFC24E')
@@ -585,6 +594,34 @@
         self.assertRaises(ValueError, ctx.set_ecdh_curve, "foo")
         self.assertRaises(ValueError, ctx.set_ecdh_curve, b"foo")
 
+    @needs_sni
+    def test_sni_callback(self):
+        ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+
+        # set_servername_callback expects a callable, or None
+        self.assertRaises(TypeError, ctx.set_servername_callback)
+        self.assertRaises(TypeError, ctx.set_servername_callback, 4)
+        self.assertRaises(TypeError, ctx.set_servername_callback, "")
+        self.assertRaises(TypeError, ctx.set_servername_callback, ctx)
+
+        def dummycallback(sock, servername, ctx):
+            pass
+        ctx.set_servername_callback(None)
+        ctx.set_servername_callback(dummycallback)
+
+    @needs_sni
+    def test_sni_callback_refcycle(self):
+        # Reference cycles through the servername callback are detected
+        # and cleared.
+        ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+        def dummycallback(sock, servername, ctx, cycle=ctx):
+            pass
+        ctx.set_servername_callback(dummycallback)
+        wr = weakref.ref(ctx)
+        del ctx, dummycallback
+        gc.collect()
+        self.assertIs(wr(), None)
+
 
 class SSLErrorTests(unittest.TestCase):
 
@@ -1249,7 +1286,7 @@
                 raise AssertionError("Use of invalid cert should have failed!")
 
     def server_params_test(client_context, server_context, indata=b"FOO\n",
-                           chatty=True, connectionchatty=False):
+                           chatty=True, connectionchatty=False, sni_name=None):
         """
         Launch a server, connect a client to it and try various reads
         and writes.
@@ -1259,7 +1296,8 @@
                                     chatty=chatty,
                                     connectionchatty=False)
         with server:
-            with client_context.wrap_socket(socket.socket()) as s:
+            with client_context.wrap_socket(socket.socket(),
+                    server_hostname=sni_name) as s:
                 s.connect((HOST, server.port))
                 for arg in [indata, bytearray(indata), memoryview(indata)]:
                     if connectionchatty:
@@ -1283,6 +1321,7 @@
                 stats.update({
                     'compression': s.compression(),
                     'cipher': s.cipher(),
+                    'peercert': s.getpeercert(),
                     'client_npn_protocol': s.selected_npn_protocol()
                 })
                 s.close()
@@ -1988,6 +2027,100 @@
                     if len(stats['server_npn_protocols']) else 'nothing'
                 self.assertEqual(server_result, expected, msg % (server_result, "server"))
 
+        def sni_contexts(self):
+            server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+            server_context.load_cert_chain(SIGNED_CERTFILE)
+            other_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+            other_context.load_cert_chain(SIGNED_CERTFILE2)
+            client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+            client_context.verify_mode = ssl.CERT_REQUIRED
+            client_context.load_verify_locations(SIGNING_CA)
+            return server_context, other_context, client_context
+
+        def check_common_name(self, stats, name):
+            cert = stats['peercert']
+            self.assertIn((('commonName', name),), cert['subject'])
+
+        @needs_sni
+        def test_sni_callback(self):
+            calls = []
+            server_context, other_context, client_context = self.sni_contexts()
+
+            def servername_cb(ssl_sock, server_name, initial_context):
+                calls.append((server_name, initial_context))
+                ssl_sock.context = other_context
+            server_context.set_servername_callback(servername_cb)
+
+            stats = server_params_test(client_context, server_context,
+                                       chatty=True,
+                                       sni_name='supermessage')
+            # The hostname was fetched properly, and the certificate was
+            # changed for the connection.
+            self.assertEqual(calls, [("supermessage", server_context)])
+            # CERTFILE4 was selected
+            self.check_common_name(stats, 'fakehostname')
+
+            # Check disabling the callback
+            calls = []
+            server_context.set_servername_callback(None)
+
+            stats = server_params_test(client_context, server_context,
+                                       chatty=True,
+                                       sni_name='notfunny')
+            # Certificate didn't change
+            self.check_common_name(stats, 'localhost')
+            self.assertEqual(calls, [])
+
+        @needs_sni
+        def test_sni_callback_alert(self):
+            # Returning a TLS alert is reflected to the connecting client
+            server_context, other_context, client_context = self.sni_contexts()
+
+            def cb_returning_alert(ssl_sock, server_name, initial_context):
+                return ssl.ALERT_DESCRIPTION_ACCESS_DENIED
+            server_context.set_servername_callback(cb_returning_alert)
+
+            with self.assertRaises(ssl.SSLError) as cm:
+                stats = server_params_test(client_context, server_context,
+                                           chatty=False,
+                                           sni_name='supermessage')
+            self.assertEqual(cm.exception.reason, 'TLSV1_ALERT_ACCESS_DENIED')
+
+        @needs_sni
+        def test_sni_callback_raising(self):
+            # Raising fails the connection with a TLS handshake failure alert.
+            server_context, other_context, client_context = self.sni_contexts()
+
+            def cb_raising(ssl_sock, server_name, initial_context):
+                1/0
+            server_context.set_servername_callback(cb_raising)
+
+            with self.assertRaises(ssl.SSLError) as cm, \
+                 support.captured_stderr() as stderr:
+                stats = server_params_test(client_context, server_context,
+                                           chatty=False,
+                                           sni_name='supermessage')
+            self.assertEqual(cm.exception.reason, 'SSLV3_ALERT_HANDSHAKE_FAILURE')
+            self.assertIn("ZeroDivisionError", stderr.getvalue())
+
+        @needs_sni
+        def test_sni_callback_wrong_return_type(self):
+            # Returning the wrong return type terminates the TLS connection
+            # with an internal error alert.
+            server_context, other_context, client_context = self.sni_contexts()
+
+            def cb_wrong_return_type(ssl_sock, server_name, initial_context):
+                return "foo"
+            server_context.set_servername_callback(cb_wrong_return_type)
+
+            with self.assertRaises(ssl.SSLError) as cm, \
+                 support.captured_stderr() as stderr:
+                stats = server_params_test(client_context, server_context,
+                                           chatty=False,
+                                           sni_name='supermessage')
+            self.assertEqual(cm.exception.reason, 'TLSV1_ALERT_INTERNAL_ERROR')
+            self.assertIn("TypeError", stderr.getvalue())
+
 
 def test_main(verbose=False):
     if support.verbose:
@@ -2011,6 +2144,7 @@
     for filename in [
         CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE,
         ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY,
+        SIGNED_CERTFILE, SIGNED_CERTFILE2, SIGNING_CA,
         BADCERT, BADKEY, EMPTYCERT]:
         if not os.path.exists(filename):
             raise support.TestFailed("Can't read certificate file %r" % filename)
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -116,6 +116,7 @@
 Philippe Biondi
 Stuart Bishop
 Roy Bixler
+Daniel Black
 Jonathan Black
 Renaud Blanch
 Mike Bland
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -204,6 +204,10 @@
 Library
 -------
 
+- Issue #8109: The ssl module now has support for server-side SNI, thanks
+  to a :meth:`SSLContext.set_servername_callback` method.  Patch by Daniel
+  Black.
+
 - Issue #16860: In tempfile, use O_CLOEXEC when available to set the
   close-on-exec flag atomically.
 
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -181,12 +181,16 @@
     char *npn_protocols;
     int npn_protocols_len;
 #endif
+#ifndef OPENSSL_NO_TLSEXT
+    PyObject *set_hostname; 
+#endif
 } PySSLContext;
 
 typedef struct {
     PyObject_HEAD
     PyObject *Socket; /* weakref to socket on which we're layered */
     SSL *ssl;
+    PySSLContext *ctx; /* weakref to SSL context */
     X509 *peer_cert;
     int shutdown_seen_zero;
     enum py_ssl_server_or_client socket_type;
@@ -437,11 +441,12 @@
  */
 
 static PySSLSocket *
-newPySSLSocket(SSL_CTX *ctx, PySocketSockObject *sock,
+newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
                enum py_ssl_server_or_client socket_type,
                char *server_hostname)
 {
     PySSLSocket *self;
+    SSL_CTX *ctx = sslctx->ctx;
 
     self = PyObject_New(PySSLSocket, &PySSLSocket_Type);
     if (self == NULL)
@@ -450,6 +455,8 @@
     self->peer_cert = NULL;
     self->ssl = NULL;
     self->Socket = NULL;
+    self->ctx = sslctx;
+    Py_INCREF(sslctx);
 
     /* Make sure the SSL error state is initialized */
     (void) ERR_get_state();
@@ -458,6 +465,7 @@
     PySSL_BEGIN_ALLOW_THREADS
     self->ssl = SSL_new(ctx);
     PySSL_END_ALLOW_THREADS
+    SSL_set_app_data(self->ssl,self);
     SSL_set_fd(self->ssl, sock->sock_fd);
 #ifdef SSL_MODE_AUTO_RETRY
     SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY);
@@ -1164,6 +1172,38 @@
 #endif
 }
 
+static PySSLContext *PySSL_get_context(PySSLSocket *self, void *closure) {
+    Py_INCREF(self->ctx);
+    return self->ctx;
+}
+
+static int PySSL_set_context(PySSLSocket *self, PyObject *value,
+                                   void *closure) {
+
+    if (PyObject_TypeCheck(value, &PySSLContext_Type)) {
+
+        Py_INCREF(value);
+        Py_DECREF(self->ctx);
+        self->ctx = (PySSLContext *) value;
+        SSL_set_SSL_CTX(self->ssl, self->ctx->ctx);
+    } else {
+        PyErr_SetString(PyExc_TypeError, "The value must be a SSLContext");
+        return -1;
+    }
+
+    return 0;
+}
+
+PyDoc_STRVAR(PySSL_set_context_doc,
+"_setter_context(ctx)\n\
+\
+This changes the context associated with the SSLSocket. This is typically\n\
+used from within a callback function set by the set_servername_callback\n\
+on the SSLContext to change the certificate information associated with the\n\
+SSLSocket before the cryptographic exchange handshake messages\n");
+
+
+
 static void PySSL_dealloc(PySSLSocket *self)
 {
     if (self->peer_cert)        /* Possible not to have one? */
@@ -1171,6 +1211,7 @@
     if (self->ssl)
         SSL_free(self->ssl);
     Py_XDECREF(self->Socket);
+    Py_XDECREF(self->ctx);
     PyObject_Del(self);
 }
 
@@ -1606,6 +1647,12 @@
 
 #endif /* HAVE_OPENSSL_FINISHED */
 
+static PyGetSetDef ssl_getsetlist[] = {
+    {"context", (getter) PySSL_get_context,
+                (setter) PySSL_set_context, PySSL_set_context_doc},
+    {NULL},            /* sentinel */
+};
+
 static PyMethodDef PySSLMethods[] = {
     {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS},
     {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS,
@@ -1660,6 +1707,8 @@
     0,                                  /*tp_iter*/
     0,                                  /*tp_iternext*/
     PySSLMethods,                       /*tp_methods*/
+    0,                                  /*tp_members*/
+    ssl_getsetlist,                     /*tp_getset*/
 };
 
 
@@ -1716,6 +1765,9 @@
 #ifdef OPENSSL_NPN_NEGOTIATED
     self->npn_protocols = NULL;
 #endif
+#ifndef OPENSSL_NO_TLSEXT
+    self->set_hostname = NULL; 
+#endif
     /* Defaults */
     SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL);
     SSL_CTX_set_options(self->ctx,
@@ -1729,9 +1781,28 @@
     return (PyObject *)self;
 }
 
+static int
+context_traverse(PySSLContext *self, visitproc visit, void *arg)
+{
+#ifndef OPENSSL_NO_TLSEXT
+    Py_VISIT(self->set_hostname);
+#endif
+    return 0;
+}
+
+static int
+context_clear(PySSLContext *self)
+{
+#ifndef OPENSSL_NO_TLSEXT
+    Py_CLEAR(self->set_hostname);
+#endif
+    return 0;
+}
+
 static void
 context_dealloc(PySSLContext *self)
 {
+    context_clear(self);
     SSL_CTX_free(self->ctx);
 #ifdef OPENSSL_NPN_NEGOTIATED
     PyMem_Free(self->npn_protocols);
@@ -2223,7 +2294,7 @@
 #endif
     }
 
-    res = (PyObject *) newPySSLSocket(self->ctx, sock, server_side,
+    res = (PyObject *) newPySSLSocket(self, sock, server_side,
                                       hostname);
     if (hostname != NULL)
         PyMem_Free(hostname);
@@ -2308,6 +2379,131 @@
 }
 #endif
 
+#ifndef OPENSSL_NO_TLSEXT
+static int
+_servername_callback(SSL *s, int *al, void *args)
+{
+    int ret;
+    PySSLContext *ssl_ctx = (PySSLContext *) args;
+    PySSLSocket *ssl;
+    PyObject *servername_o;
+    PyObject *servername_idna;
+    PyObject *result;
+    /* The high-level ssl.SSLSocket object */
+    PyObject *ssl_socket;
+    PyGILState_STATE gstate;
+    const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
+
+    gstate = PyGILState_Ensure();
+
+    if (ssl_ctx->set_hostname == NULL) {
+        /* remove race condition in this the call back while if removing the
+         * callback is in progress */
+        PyGILState_Release(gstate);
+        return ret;
+    }
+
+    ssl = SSL_get_app_data(s);
+    assert(PySSLSocket_Check(ssl));
+    ssl_socket = PyWeakref_GetObject(ssl->Socket);
+    Py_INCREF(ssl_socket);
+    if (ssl_socket == Py_None) {
+        goto error;
+    }
+ 
+    servername_o = PyBytes_FromString(servername);
+    if (servername_o == NULL) {
+        PyErr_WriteUnraisable((PyObject *) ssl_ctx);
+        goto error;
+    }
+    servername_idna = PyUnicode_FromEncodedObject(servername_o, "idna", NULL);
+    if (servername_idna == NULL) {
+        PyErr_WriteUnraisable(servername_o);
+        Py_DECREF(servername_o);
+        goto error;
+    }
+    Py_DECREF(servername_o);
+    result = PyObject_CallFunctionObjArgs(ssl_ctx->set_hostname, ssl_socket,
+                                          servername_idna, ssl_ctx, NULL);
+    Py_DECREF(ssl_socket);
+    Py_DECREF(servername_idna);
+
+    if (result == NULL) {
+        PyErr_WriteUnraisable(ssl_ctx->set_hostname);
+        *al = SSL_AD_HANDSHAKE_FAILURE;
+        ret = SSL_TLSEXT_ERR_ALERT_FATAL;
+    }
+    else {
+        if (result != Py_None) {
+            *al = (int) PyLong_AsLong(result);
+            if (PyErr_Occurred()) {
+                PyErr_WriteUnraisable(result);
+                *al = SSL_AD_INTERNAL_ERROR;
+            }
+            ret = SSL_TLSEXT_ERR_ALERT_FATAL;
+        }
+        else {
+            ret = SSL_TLSEXT_ERR_OK;
+        }
+        Py_DECREF(result);
+    }
+
+    PyGILState_Release(gstate);
+    return ret;
+
+error:
+    Py_DECREF(ssl_socket);
+    *al = SSL_AD_INTERNAL_ERROR;
+    ret = SSL_TLSEXT_ERR_ALERT_FATAL;
+    PyGILState_Release(gstate);
+    return ret;
+}
+
+PyDoc_STRVAR(PySSL_set_servername_callback_doc,
+"set_servername_callback(method)\n\
+\
+This sets a callback that will be called when a server name is provided by\n\
+the SSL/TLS client in the SNI extension.\n\
+\
+If the argument is None then the callback is disabled. The method is called\n\
+with the SSLSocket, the server name as a string, and the SSLContext object.\n\
+See RFC 6066 for details of the SNI");
+#endif
+
+static PyObject *
+set_servername_callback(PySSLContext *self, PyObject *args)
+{
+#ifndef OPENSSL_NO_TLSEXT
+    PyObject *cb;
+
+    if (!PyArg_ParseTuple(args, "O", &cb))
+        return NULL;
+
+    Py_CLEAR(self->set_hostname);
+    if (cb == Py_None) {
+        SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
+    }
+    else {
+        if (!PyCallable_Check(cb)) {
+            SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
+            PyErr_SetString(PyExc_TypeError,
+                            "not a callable object");
+            return NULL;
+        }
+        Py_INCREF(cb);
+        self->set_hostname = cb;
+        SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback);
+        SSL_CTX_set_tlsext_servername_arg(self->ctx, self);
+    }
+    Py_RETURN_NONE;
+#else
+    PyErr_SetString(PyExc_NotImplementedError,
+                    "The TLS extension servername callback, "
+                    "SSL_CTX_set_tlsext_servername_callback, "
+                    "is not in the current OpenSSL library.");
+#endif
+}
+
 static PyGetSetDef context_getsetlist[] = {
     {"options", (getter) get_options,
                 (setter) set_options, NULL},
@@ -2337,6 +2533,8 @@
     {"set_ecdh_curve", (PyCFunction) set_ecdh_curve,
                        METH_O, NULL},
 #endif
+    {"set_servername_callback", (PyCFunction) set_servername_callback,
+                    METH_VARARGS, PySSL_set_servername_callback_doc},
     {NULL, NULL}        /* sentinel */
 };
 
@@ -2360,10 +2558,10 @@
     0,                                         /*tp_getattro*/
     0,                                         /*tp_setattro*/
     0,                                         /*tp_as_buffer*/
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /*tp_flags*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
     0,                                         /*tp_doc*/
-    0,                                         /*tp_traverse*/
-    0,                                         /*tp_clear*/
+    (traverseproc) context_traverse,           /*tp_traverse*/
+    (inquiry) context_clear,                   /*tp_clear*/
     0,                                         /*tp_richcompare*/
     0,                                         /*tp_weaklistoffset*/
     0,                                         /*tp_iter*/
@@ -2743,6 +2941,51 @@
     PyModule_AddIntConstant(m, "CERT_REQUIRED",
                             PY_SSL_CERT_REQUIRED);
 
+    /* Alert Descriptions from ssl.h */
+    /* note RESERVED constants no longer intended for use have been removed */
+    /* http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6 */
+
+#define ADD_AD_CONSTANT(s) \
+    PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_"#s, \
+                            SSL_AD_##s)
+
+    ADD_AD_CONSTANT(CLOSE_NOTIFY);
+    ADD_AD_CONSTANT(UNEXPECTED_MESSAGE);
+    ADD_AD_CONSTANT(BAD_RECORD_MAC);
+    ADD_AD_CONSTANT(RECORD_OVERFLOW);
+    ADD_AD_CONSTANT(DECOMPRESSION_FAILURE);
+    ADD_AD_CONSTANT(HANDSHAKE_FAILURE);
+    ADD_AD_CONSTANT(BAD_CERTIFICATE);
+    ADD_AD_CONSTANT(UNSUPPORTED_CERTIFICATE);
+    ADD_AD_CONSTANT(CERTIFICATE_REVOKED);
+    ADD_AD_CONSTANT(CERTIFICATE_EXPIRED);
+    ADD_AD_CONSTANT(CERTIFICATE_UNKNOWN);
+    ADD_AD_CONSTANT(ILLEGAL_PARAMETER);
+    ADD_AD_CONSTANT(UNKNOWN_CA);
+    ADD_AD_CONSTANT(ACCESS_DENIED);
+    ADD_AD_CONSTANT(DECODE_ERROR);
+    ADD_AD_CONSTANT(DECRYPT_ERROR);
+    ADD_AD_CONSTANT(PROTOCOL_VERSION);
+    ADD_AD_CONSTANT(INSUFFICIENT_SECURITY);
+    ADD_AD_CONSTANT(INTERNAL_ERROR);
+    ADD_AD_CONSTANT(USER_CANCELLED);
+    ADD_AD_CONSTANT(NO_RENEGOTIATION);
+    ADD_AD_CONSTANT(UNSUPPORTED_EXTENSION);
+    ADD_AD_CONSTANT(CERTIFICATE_UNOBTAINABLE);
+    ADD_AD_CONSTANT(UNRECOGNIZED_NAME);
+    /* Not all constants are in old OpenSSL versions */
+#ifdef SSL_AD_BAD_CERTIFICATE_STATUS_RESPONSE
+    ADD_AD_CONSTANT(BAD_CERTIFICATE_STATUS_RESPONSE);
+#endif
+#ifdef SSL_AD_BAD_CERTIFICATE_HASH_VALUE
+    ADD_AD_CONSTANT(BAD_CERTIFICATE_HASH_VALUE);
+#endif
+#ifdef SSL_AD_UNKNOWN_PSK_IDENTITY
+    ADD_AD_CONSTANT(UNKNOWN_PSK_IDENTITY);
+#endif
+
+#undef ADD_AD_CONSTANT
+
     /* protocol versions */
 #ifndef OPENSSL_NO_SSL2
     PyModule_AddIntConstant(m, "PROTOCOL_SSLv2",

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list