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.
This commit is contained in:
Antoine Pitrou 2013-01-05 21:20:29 +01:00
parent 3c9850aad7
commit 58ddc9d743
11 changed files with 881 additions and 42 deletions

View File

@ -533,6 +533,19 @@ Constants
.. versionadded:: 3.2 .. 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 SSL Sockets
----------- -----------
@ -780,6 +793,55 @@ to speed up repeated connections from the same clients.
.. versionadded:: 3.3 .. 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) .. method:: SSLContext.load_dh_params(dhfile)
Load the key generation parameters for Diffie-Helman (DH) key exchange. Load the key generation parameters for Diffie-Helman (DH) key exchange.
@ -1313,3 +1375,12 @@ use the ``openssl ciphers`` command on your system.
`RFC 4366: Transport Layer Security (TLS) Extensions <http://www.ietf.org/rfc/rfc4366>`_ `RFC 4366: Transport Layer Security (TLS) Extensions <http://www.ietf.org/rfc/rfc4366>`_
Blake-Wilson et. al. 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

View File

@ -52,6 +52,37 @@ PROTOCOL_SSLv2
PROTOCOL_SSLv3 PROTOCOL_SSLv3
PROTOCOL_SSLv23 PROTOCOL_SSLv23
PROTOCOL_TLSv1 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 import textwrap
@ -66,35 +97,24 @@ from _ssl import (
SSLSyscallError, SSLEOFError, SSLSyscallError, SSLEOFError,
) )
from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED 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 RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes
from _ssl import (
SSL_ERROR_ZERO_RETURN, def _import_symbols(prefix):
SSL_ERROR_WANT_READ, for n in dir(_ssl):
SSL_ERROR_WANT_WRITE, if n.startswith(prefix):
SSL_ERROR_WANT_X509_LOOKUP, globals()[n] = getattr(_ssl, n)
SSL_ERROR_SYSCALL,
SSL_ERROR_SSL, _import_symbols('OP_')
SSL_ERROR_WANT_CONNECT, _import_symbols('ALERT_DESCRIPTION_')
SSL_ERROR_EOF, _import_symbols('SSL_ERROR_')
SSL_ERROR_INVALID_ERROR_CODE,
)
from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN
from _ssl import (PROTOCOL_SSLv3, PROTOCOL_SSLv23, from _ssl import (PROTOCOL_SSLv3, PROTOCOL_SSLv23,
PROTOCOL_TLSv1) PROTOCOL_TLSv1)
from _ssl import _OPENSSL_API_VERSION from _ssl import _OPENSSL_API_VERSION
_PROTOCOL_NAMES = { _PROTOCOL_NAMES = {
PROTOCOL_TLSv1: "TLSv1", PROTOCOL_TLSv1: "TLSv1",
PROTOCOL_SSLv23: "SSLv23", PROTOCOL_SSLv23: "SSLv23",
@ -190,7 +210,7 @@ class SSLContext(_SSLContext):
"""An SSLContext holds various SSL-related configuration options and """An SSLContext holds various SSL-related configuration options and
data, such as certificates and possibly a private key.""" data, such as certificates and possibly a private key."""
__slots__ = ('protocol',) __slots__ = ('protocol', '__weakref__')
def __new__(cls, protocol, *args, **kwargs): def __new__(cls, protocol, *args, **kwargs):
self = _SSLContext.__new__(cls, protocol) self = _SSLContext.__new__(cls, protocol)
@ -238,7 +258,7 @@ class SSLSocket(socket):
_context=None): _context=None):
if _context: if _context:
self.context = _context self._context = _context
else: else:
if server_side and not certfile: if server_side and not certfile:
raise ValueError("certfile must be specified for server-side " raise ValueError("certfile must be specified for server-side "
@ -247,16 +267,16 @@ class SSLSocket(socket):
raise ValueError("certfile must be specified") raise ValueError("certfile must be specified")
if certfile and not keyfile: if certfile and not keyfile:
keyfile = certfile keyfile = certfile
self.context = SSLContext(ssl_version) self._context = SSLContext(ssl_version)
self.context.verify_mode = cert_reqs self._context.verify_mode = cert_reqs
if ca_certs: if ca_certs:
self.context.load_verify_locations(ca_certs) self._context.load_verify_locations(ca_certs)
if certfile: if certfile:
self.context.load_cert_chain(certfile, keyfile) self._context.load_cert_chain(certfile, keyfile)
if npn_protocols: if npn_protocols:
self.context.set_npn_protocols(npn_protocols) self._context.set_npn_protocols(npn_protocols)
if ciphers: if ciphers:
self.context.set_ciphers(ciphers) self._context.set_ciphers(ciphers)
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
self.cert_reqs = cert_reqs self.cert_reqs = cert_reqs
@ -298,7 +318,7 @@ class SSLSocket(socket):
if connected: if connected:
# create the SSL object # create the SSL object
try: try:
self._sslobj = self.context._wrap_socket(self, server_side, self._sslobj = self._context._wrap_socket(self, server_side,
server_hostname) server_hostname)
if do_handshake_on_connect: if do_handshake_on_connect:
timeout = self.gettimeout() timeout = self.gettimeout()
@ -310,6 +330,14 @@ class SSLSocket(socket):
except OSError as x: except OSError as x:
self.close() self.close()
raise x 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): def dup(self):
raise NotImplemented("Can't dup() %s instances" % raise NotImplemented("Can't dup() %s instances" %

73
Lib/test/keycert3.pem Normal file
View File

@ -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-----

73
Lib/test/keycert4.pem Normal file
View File

@ -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-----

View File

@ -2,6 +2,7 @@
and friends.""" and friends."""
import os import os
import shutil
import sys import sys
import tempfile import tempfile
from subprocess import * from subprocess import *
@ -20,11 +21,52 @@ req_template = """
[req_x509_extensions] [req_x509_extensions]
subjectAltName = DNS:{hostname} 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__)) 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 = [] tempnames = []
for i in range(3): for i in range(3):
with tempfile.NamedTemporaryFile(delete=False) as f: with tempfile.NamedTemporaryFile(delete=False) as f:
@ -33,10 +75,25 @@ def make_cert_key(hostname):
try: try:
with open(req_file, 'w') as f: with open(req_file, 'w') as f:
f.write(req_template.format(hostname=hostname)) 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, '-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) 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: with open(cert_file, 'r') as f:
cert = f.read() cert = f.read()
with open(key_file, 'r') as f: with open(key_file, 'r') as f:
@ -46,6 +103,32 @@ def make_cert_key(hostname):
for name in tempnames: for name in tempnames:
os.remove(name) 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__': if __name__ == '__main__':
os.chdir(here) os.chdir(here)
@ -54,11 +137,34 @@ if __name__ == '__main__':
f.write(cert) f.write(cert)
with open('ssl_key.pem', 'w') as f: with open('ssl_key.pem', 'w') as f:
f.write(key) 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: with open('keycert.pem', 'w') as f:
f.write(key) f.write(key)
f.write(cert) f.write(cert)
with open('keycert.passwd.pem', 'a+') as f:
f.write(cert)
# For certificate matching tests # For certificate matching tests
make_ca()
cert, key = make_cert_key('fakehostname') cert, key = make_cert_key('fakehostname')
with open('keycert2.pem', 'w') as f: with open('keycert2.pem', 'w') as f:
f.write(key) f.write(key)
f.write(cert) 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'])

78
Lib/test/pycacert.pem Normal file
View File

@ -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-----

28
Lib/test/pycakey.pem Normal file
View File

@ -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-----

View File

@ -48,6 +48,11 @@ KEY_PASSWORD = "somepass"
CAPATH = data_file("capath") CAPATH = data_file("capath")
BYTES_CAPATH = os.fsencode(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") SVN_PYTHON_ORG_ROOT_CERT = data_file("https_svn_python_org_root.pem")
EMPTYCERT = data_file("nullcert.pem") EMPTYCERT = data_file("nullcert.pem")
@ -59,6 +64,7 @@ NOKIACERT = data_file("nokia.pem")
DHFILE = data_file("dh512.pem") DHFILE = data_file("dh512.pem")
BYTES_DHFILE = os.fsencode(DHFILE) BYTES_DHFILE = os.fsencode(DHFILE)
def handle_error(prefix): def handle_error(prefix):
exc_format = ' '.join(traceback.format_exception(*sys.exc_info())) exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
if support.verbose: if support.verbose:
@ -89,6 +95,8 @@ def skip_if_broken_ubuntu_ssl(func):
else: else:
return func return func
needs_sni = unittest.skipUnless(ssl.HAS_SNI, "SNI support needed for this test")
class BasicSocketTests(unittest.TestCase): class BasicSocketTests(unittest.TestCase):
@ -142,6 +150,7 @@ class BasicSocketTests(unittest.TestCase):
(('organizationName', 'Python Software Foundation'),), (('organizationName', 'Python Software Foundation'),),
(('commonName', 'localhost'),)) (('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['notAfter'], 'Oct 5 23:01:56 2020 GMT')
self.assertEqual(p['notBefore'], 'Oct 8 23:01:56 2010 GMT') self.assertEqual(p['notBefore'], 'Oct 8 23:01:56 2010 GMT')
self.assertEqual(p['serialNumber'], 'D7C7381919AFC24E') self.assertEqual(p['serialNumber'], 'D7C7381919AFC24E')
@ -585,6 +594,34 @@ class ContextTests(unittest.TestCase):
self.assertRaises(ValueError, ctx.set_ecdh_curve, "foo") self.assertRaises(ValueError, ctx.set_ecdh_curve, "foo")
self.assertRaises(ValueError, ctx.set_ecdh_curve, b"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): class SSLErrorTests(unittest.TestCase):
@ -1249,7 +1286,7 @@ else:
raise AssertionError("Use of invalid cert should have failed!") raise AssertionError("Use of invalid cert should have failed!")
def server_params_test(client_context, server_context, indata=b"FOO\n", 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 Launch a server, connect a client to it and try various reads
and writes. and writes.
@ -1259,7 +1296,8 @@ else:
chatty=chatty, chatty=chatty,
connectionchatty=False) connectionchatty=False)
with server: 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)) s.connect((HOST, server.port))
for arg in [indata, bytearray(indata), memoryview(indata)]: for arg in [indata, bytearray(indata), memoryview(indata)]:
if connectionchatty: if connectionchatty:
@ -1283,6 +1321,7 @@ else:
stats.update({ stats.update({
'compression': s.compression(), 'compression': s.compression(),
'cipher': s.cipher(), 'cipher': s.cipher(),
'peercert': s.getpeercert(),
'client_npn_protocol': s.selected_npn_protocol() 'client_npn_protocol': s.selected_npn_protocol()
}) })
s.close() s.close()
@ -1988,6 +2027,100 @@ else:
if len(stats['server_npn_protocols']) else 'nothing' if len(stats['server_npn_protocols']) else 'nothing'
self.assertEqual(server_result, expected, msg % (server_result, "server")) 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): def test_main(verbose=False):
if support.verbose: if support.verbose:
@ -2011,6 +2144,7 @@ def test_main(verbose=False):
for filename in [ for filename in [
CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE, CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE,
ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY, ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY,
SIGNED_CERTFILE, SIGNED_CERTFILE2, SIGNING_CA,
BADCERT, BADKEY, EMPTYCERT]: BADCERT, BADKEY, EMPTYCERT]:
if not os.path.exists(filename): if not os.path.exists(filename):
raise support.TestFailed("Can't read certificate file %r" % filename) raise support.TestFailed("Can't read certificate file %r" % filename)

View File

@ -116,6 +116,7 @@ Dominic Binks
Philippe Biondi Philippe Biondi
Stuart Bishop Stuart Bishop
Roy Bixler Roy Bixler
Daniel Black
Jonathan Black Jonathan Black
Renaud Blanch Renaud Blanch
Mike Bland Mike Bland

View File

@ -204,6 +204,10 @@ Core and Builtins
Library 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 - Issue #16860: In tempfile, use O_CLOEXEC when available to set the
close-on-exec flag atomically. close-on-exec flag atomically.

View File

@ -181,12 +181,16 @@ typedef struct {
char *npn_protocols; char *npn_protocols;
int npn_protocols_len; int npn_protocols_len;
#endif #endif
#ifndef OPENSSL_NO_TLSEXT
PyObject *set_hostname;
#endif
} PySSLContext; } PySSLContext;
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
PyObject *Socket; /* weakref to socket on which we're layered */ PyObject *Socket; /* weakref to socket on which we're layered */
SSL *ssl; SSL *ssl;
PySSLContext *ctx; /* weakref to SSL context */
X509 *peer_cert; X509 *peer_cert;
int shutdown_seen_zero; int shutdown_seen_zero;
enum py_ssl_server_or_client socket_type; enum py_ssl_server_or_client socket_type;
@ -437,11 +441,12 @@ _setSSLError (char *errstr, int errcode, char *filename, int lineno) {
*/ */
static PySSLSocket * static PySSLSocket *
newPySSLSocket(SSL_CTX *ctx, PySocketSockObject *sock, newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
enum py_ssl_server_or_client socket_type, enum py_ssl_server_or_client socket_type,
char *server_hostname) char *server_hostname)
{ {
PySSLSocket *self; PySSLSocket *self;
SSL_CTX *ctx = sslctx->ctx;
self = PyObject_New(PySSLSocket, &PySSLSocket_Type); self = PyObject_New(PySSLSocket, &PySSLSocket_Type);
if (self == NULL) if (self == NULL)
@ -450,6 +455,8 @@ newPySSLSocket(SSL_CTX *ctx, PySocketSockObject *sock,
self->peer_cert = NULL; self->peer_cert = NULL;
self->ssl = NULL; self->ssl = NULL;
self->Socket = NULL; self->Socket = NULL;
self->ctx = sslctx;
Py_INCREF(sslctx);
/* Make sure the SSL error state is initialized */ /* Make sure the SSL error state is initialized */
(void) ERR_get_state(); (void) ERR_get_state();
@ -458,6 +465,7 @@ newPySSLSocket(SSL_CTX *ctx, PySocketSockObject *sock,
PySSL_BEGIN_ALLOW_THREADS PySSL_BEGIN_ALLOW_THREADS
self->ssl = SSL_new(ctx); self->ssl = SSL_new(ctx);
PySSL_END_ALLOW_THREADS PySSL_END_ALLOW_THREADS
SSL_set_app_data(self->ssl,self);
SSL_set_fd(self->ssl, sock->sock_fd); SSL_set_fd(self->ssl, sock->sock_fd);
#ifdef SSL_MODE_AUTO_RETRY #ifdef SSL_MODE_AUTO_RETRY
SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY); SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY);
@ -1164,6 +1172,38 @@ static PyObject *PySSL_compression(PySSLSocket *self) {
#endif #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) static void PySSL_dealloc(PySSLSocket *self)
{ {
if (self->peer_cert) /* Possible not to have one? */ if (self->peer_cert) /* Possible not to have one? */
@ -1171,6 +1211,7 @@ static void PySSL_dealloc(PySSLSocket *self)
if (self->ssl) if (self->ssl)
SSL_free(self->ssl); SSL_free(self->ssl);
Py_XDECREF(self->Socket); Py_XDECREF(self->Socket);
Py_XDECREF(self->ctx);
PyObject_Del(self); PyObject_Del(self);
} }
@ -1606,6 +1647,12 @@ If the TLS handshake is not yet complete, None is returned");
#endif /* HAVE_OPENSSL_FINISHED */ #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[] = { static PyMethodDef PySSLMethods[] = {
{"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS}, {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS},
{"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS, {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS,
@ -1660,6 +1707,8 @@ static PyTypeObject PySSLSocket_Type = {
0, /*tp_iter*/ 0, /*tp_iter*/
0, /*tp_iternext*/ 0, /*tp_iternext*/
PySSLMethods, /*tp_methods*/ PySSLMethods, /*tp_methods*/
0, /*tp_members*/
ssl_getsetlist, /*tp_getset*/
}; };
@ -1715,6 +1764,9 @@ context_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->ctx = ctx; self->ctx = ctx;
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED
self->npn_protocols = NULL; self->npn_protocols = NULL;
#endif
#ifndef OPENSSL_NO_TLSEXT
self->set_hostname = NULL;
#endif #endif
/* Defaults */ /* Defaults */
SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL); SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL);
@ -1729,9 +1781,28 @@ context_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return (PyObject *)self; 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 static void
context_dealloc(PySSLContext *self) context_dealloc(PySSLContext *self)
{ {
context_clear(self);
SSL_CTX_free(self->ctx); SSL_CTX_free(self->ctx);
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED
PyMem_Free(self->npn_protocols); PyMem_Free(self->npn_protocols);
@ -2223,7 +2294,7 @@ context_wrap_socket(PySSLContext *self, PyObject *args, PyObject *kwds)
#endif #endif
} }
res = (PyObject *) newPySSLSocket(self->ctx, sock, server_side, res = (PyObject *) newPySSLSocket(self, sock, server_side,
hostname); hostname);
if (hostname != NULL) if (hostname != NULL)
PyMem_Free(hostname); PyMem_Free(hostname);
@ -2308,6 +2379,131 @@ set_ecdh_curve(PySSLContext *self, PyObject *name)
} }
#endif #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[] = { static PyGetSetDef context_getsetlist[] = {
{"options", (getter) get_options, {"options", (getter) get_options,
(setter) set_options, NULL}, (setter) set_options, NULL},
@ -2337,6 +2533,8 @@ static struct PyMethodDef context_methods[] = {
{"set_ecdh_curve", (PyCFunction) set_ecdh_curve, {"set_ecdh_curve", (PyCFunction) set_ecdh_curve,
METH_O, NULL}, METH_O, NULL},
#endif #endif
{"set_servername_callback", (PyCFunction) set_servername_callback,
METH_VARARGS, PySSL_set_servername_callback_doc},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
@ -2360,10 +2558,10 @@ static PyTypeObject PySSLContext_Type = {
0, /*tp_getattro*/ 0, /*tp_getattro*/
0, /*tp_setattro*/ 0, /*tp_setattro*/
0, /*tp_as_buffer*/ 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_doc*/
0, /*tp_traverse*/ (traverseproc) context_traverse, /*tp_traverse*/
0, /*tp_clear*/ (inquiry) context_clear, /*tp_clear*/
0, /*tp_richcompare*/ 0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/ 0, /*tp_weaklistoffset*/
0, /*tp_iter*/ 0, /*tp_iter*/
@ -2743,6 +2941,51 @@ PyInit__ssl(void)
PyModule_AddIntConstant(m, "CERT_REQUIRED", PyModule_AddIntConstant(m, "CERT_REQUIRED",
PY_SSL_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 */ /* protocol versions */
#ifndef OPENSSL_NO_SSL2 #ifndef OPENSSL_NO_SSL2
PyModule_AddIntConstant(m, "PROTOCOL_SSLv2", PyModule_AddIntConstant(m, "PROTOCOL_SSLv2",