cpython: be9fe0c66075 (original) (raw)
Mercurial > cpython
changeset 94261:be9fe0c66075
add support for ALPN (closes #20188) [#20188]
Benjamin Peterson benjamin@python.org | |
---|---|
date | Fri, 23 Jan 2015 16:35:37 -0500 |
parents | 67ebf7ae686d |
children | eaa38b75cc78 |
files | Doc/library/ssl.rst Lib/ssl.py Lib/test/test_ssl.py Misc/NEWS Modules/_ssl.c |
diffstat | 5 files changed, 231 insertions(+), 29 deletions(-)[+] [-] Doc/library/ssl.rst 34 Lib/ssl.py 27 Lib/test/test_ssl.py 64 Misc/NEWS 3 Modules/_ssl.c 132 |
line wrap: on
line diff
--- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -673,6 +673,13 @@ Constants .. versionadded:: 3.3 +.. data:: HAS_ALPN +
- Whether the OpenSSL library has built-in support for the *Application-Layer
- Protocol Negotiation* TLS extension as described in :rfc:
7301
. + - .. versionadded:: 3.5 +
.. data:: HAS_ECDH Whether the OpenSSL library has built-in support for Elliptic Curve-based @@ -959,9 +966,18 @@ SSL sockets also have the following addi .. versionadded:: 3.3 +.. method:: SSLSocket.selected_alpn_protocol() +
- Return the protocol that was selected during the TLS handshake. If
- :meth:
SSLContext.set_alpn_protocols
was not called, if the other party does - not support ALPN, or if the handshake has not happened yet,
None
is - returned. +
- .. versionadded:: 3.5 +
.. method:: SSLSocket.selected_npn_protocol()
- Return the higher-level protocol that was selected during the TLS/SSL
handshake. If :meth:
SSLContext.set_npn_protocols
was not called, or if the other party does not support NPN, or if the handshake has not yet happened, this will returnNone
. @@ -1160,6 +1176,20 @@ to speed up repeated connections from th when connected, the :meth:SSLSocket.cipher
method of SSL sockets will give the currently selected cipher.
+.. method:: SSLContext.set_alpn_protocols(protocols) +
- Specify which protocols the socket should advertise during the SSL/TLS
- handshake. It should be a list of ASCII strings, like ``['http/1.1',
- 'spdy/2']``, ordered by preference. The selection of a protocol will happen
- during the handshake, and will play out according to :rfc:
7301
. After a - successful handshake, the :meth:
SSLSocket.selected_alpn_protocol
method will - return the agreed-upon protocol. +
- This method will raise :exc:
NotImplementedError
if :data:HAS_ALPN
is - False. +
- .. versionadded:: 3.5 +
.. method:: SSLContext.set_npn_protocols(protocols) Specify which protocols the socket should advertise during the SSL/TLS @@ -1200,7 +1230,7 @@ to speed up repeated connections from th Due to the early negotiation phase of the TLS connection, only limited methods and attributes are usable like
- :meth:
SSLSocket.selected_alpn_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
--- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -122,7 +122,7 @@ def _import_symbols(prefix): import_symbols('ALERT_DESCRIPTION') import_symbols('SSL_ERROR') -from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN +from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN from _ssl import _OPENSSL_API_VERSION @@ -374,6 +374,17 @@ class SSLContext(_SSLContext): self._set_npn_protocols(protos)
- def set_alpn_protocols(self, alpn_protocols):
protos = bytearray()[](#l2.17)
for protocol in alpn_protocols:[](#l2.18)
b = bytes(protocol, 'ascii')[](#l2.19)
if len(b) == 0 or len(b) > 255:[](#l2.20)
raise SSLError('ALPN protocols must be 1 to 255 in length')[](#l2.21)
protos.append(len(b))[](#l2.22)
protos.extend(b)[](#l2.23)
self._set_alpn_protocols(protos)[](#l2.25)
+ def _load_windows_store_certs(self, storename, purpose): certs = bytearray() for cert, encoding, trust in enum_certificates(storename): @@ -567,6 +578,13 @@ class SSLObject: if _ssl.HAS_NPN: return self._sslobj.selected_npn_protocol()
- def selected_alpn_protocol(self):
"""Return the currently selected ALPN protocol as a string, or ``None``[](#l2.35)
if a next protocol was not negotiated or if ALPN is not supported by one[](#l2.36)
of the peers."""[](#l2.37)
if _ssl.HAS_ALPN:[](#l2.38)
return self._sslobj.selected_alpn_protocol()[](#l2.39)
+
def cipher(self):
"""Return the currently selected cipher as a 3-tuple (name,[](#l2.42) ssl_version, secret_bits)
."""
@@ -783,6 +801,13 @@ class SSLSocket(socket):
else:
return self._sslobj.selected_npn_protocol()
- def selected_alpn_protocol(self):
self._checkClosed()[](#l2.49)
if not self._sslobj or not _ssl.HAS_ALPN:[](#l2.50)
return None[](#l2.51)
else:[](#l2.52)
return self._sslobj.selected_alpn_protocol()[](#l2.53)
+ def cipher(self): self._checkClosed() if not self._sslobj:
--- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1761,7 +1761,8 @@ else: try: self.sslconn = self.server.context.wrap_socket( self.sock, server_side=True)
self.server.selected_protocols.append(self.sslconn.selected_npn_protocol())[](#l3.7)
self.server.selected_npn_protocols.append(self.sslconn.selected_npn_protocol())[](#l3.8)
self.server.selected_alpn_protocols.append(self.sslconn.selected_alpn_protocol())[](#l3.9) except (ssl.SSLError, ConnectionResetError) as e:[](#l3.10) # We treat ConnectionResetError as though it were an[](#l3.11) # SSLError - OpenSSL on Ubuntu abruptly closes the[](#l3.12)
@@ -1869,7 +1870,8 @@ else: def init(self, certificate=None, ssl_version=None, certreqs=None, cacerts=None, chatty=True, connectionchatty=False, starttls_server=False,
npn_protocols=None, ciphers=None, context=None):[](#l3.17)
npn_protocols=None, alpn_protocols=None,[](#l3.18)
ciphers=None, context=None):[](#l3.19) if context:[](#l3.20) self.context = context[](#l3.21) else:[](#l3.22)
@@ -1884,6 +1886,8 @@ else: self.context.load_cert_chain(certificate) if npn_protocols: self.context.set_npn_protocols(npn_protocols)
if alpn_protocols:[](#l3.27)
self.context.set_alpn_protocols(alpn_protocols)[](#l3.28) if ciphers:[](#l3.29) self.context.set_ciphers(ciphers)[](#l3.30) self.chatty = chatty[](#l3.31)
@@ -1893,7 +1897,8 @@ else: self.port = support.bind_port(self.sock) self.flag = None self.active = False
self.selected_protocols = [][](#l3.36)
self.selected_npn_protocols = [][](#l3.37)
self.selected_alpn_protocols = [][](#l3.38) self.shared_ciphers = [][](#l3.39) self.conn_errors = [][](#l3.40) threading.Thread.__init__(self)[](#l3.41)
@@ -2120,11 +2125,13 @@ else: 'compression': s.compression(), 'cipher': s.cipher(), 'peercert': s.getpeercert(),
'client_alpn_protocol': s.selected_alpn_protocol(),[](#l3.46) 'client_npn_protocol': s.selected_npn_protocol(),[](#l3.47) 'version': s.version(),[](#l3.48) })[](#l3.49) s.close()[](#l3.50)
stats['server_npn_protocols'] = server.selected_protocols[](#l3.51)
stats['server_alpn_protocols'] = server.selected_alpn_protocols[](#l3.52)
stats['server_npn_protocols'] = server.selected_npn_protocols[](#l3.53) stats['server_shared_ciphers'] = server.shared_ciphers[](#l3.54) return stats[](#l3.55)
@@ -3022,6 +3029,55 @@ else: if "ADH" not in parts and "EDH" not in parts and "DHE" not in parts: self.fail("Non-DH cipher: " + cipher[0])
def test_selected_alpn_protocol(self):[](#l3.61)
# selected_alpn_protocol() is None unless ALPN is used.[](#l3.62)
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)[](#l3.63)
context.load_cert_chain(CERTFILE)[](#l3.64)
stats = server_params_test(context, context,[](#l3.65)
chatty=True, connectionchatty=True)[](#l3.66)
self.assertIs(stats['client_alpn_protocol'], None)[](#l3.67)
@unittest.skipUnless(ssl.HAS_ALPN, "ALPN support required")[](#l3.69)
def test_selected_alpn_protocol_if_server_uses_alpn(self):[](#l3.70)
# selected_alpn_protocol() is None unless ALPN is used by the client.[](#l3.71)
client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)[](#l3.72)
client_context.load_verify_locations(CERTFILE)[](#l3.73)
server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)[](#l3.74)
server_context.load_cert_chain(CERTFILE)[](#l3.75)
server_context.set_alpn_protocols(['foo', 'bar'])[](#l3.76)
stats = server_params_test(client_context, server_context,[](#l3.77)
chatty=True, connectionchatty=True)[](#l3.78)
self.assertIs(stats['client_alpn_protocol'], None)[](#l3.79)
@unittest.skipUnless(ssl.HAS_ALPN, "ALPN support needed for this test")[](#l3.81)
def test_alpn_protocols(self):[](#l3.82)
server_protocols = ['foo', 'bar', 'milkshake'][](#l3.83)
protocol_tests = [[](#l3.84)
(['foo', 'bar'], 'foo'),[](#l3.85)
(['bar', 'foo'], 'bar'),[](#l3.86)
(['milkshake'], 'milkshake'),[](#l3.87)
(['http/3.0', 'http/4.0'], 'foo')[](#l3.88)
][](#l3.89)
for client_protocols, expected in protocol_tests:[](#l3.90)
server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)[](#l3.91)
server_context.load_cert_chain(CERTFILE)[](#l3.92)
server_context.set_alpn_protocols(server_protocols)[](#l3.93)
client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)[](#l3.94)
client_context.load_cert_chain(CERTFILE)[](#l3.95)
client_context.set_alpn_protocols(client_protocols)[](#l3.96)
stats = server_params_test(client_context, server_context,[](#l3.97)
chatty=True, connectionchatty=True)[](#l3.98)
msg = "failed trying %s (s) and %s (c).\n" \[](#l3.100)
"was expecting %s, but got %%s from the %%s" \[](#l3.101)
% (str(server_protocols), str(client_protocols),[](#l3.102)
str(expected))[](#l3.103)
client_result = stats['client_alpn_protocol'][](#l3.104)
self.assertEqual(client_result, expected, msg % (client_result, "client"))[](#l3.105)
server_result = stats['server_alpn_protocols'][-1] \[](#l3.106)
if len(stats['server_alpn_protocols']) else 'nothing'[](#l3.107)
self.assertEqual(server_result, expected, msg % (server_result, "server"))[](#l3.108)
+ def test_selected_npn_protocol(self): # selected_npn_protocol() is None unless NPN is used context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -203,6 +203,9 @@ Core and Builtins Library ------- +- Issue #20188: Support Application-Layer Protocol Negotiation (ALPN) in the ssl
--- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -109,6 +109,11 @@ struct py_ssl_library_code {
define HAVE_SNI 0
#endif +/* ALPN added in OpenSSL 1.0.2 / +#if OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined(OPENSSL_NO_TLSEXT) +# define HAVE_ALPN +#endif + enum py_ssl_error { / these mirror ssl.h */ PY_SSL_ERROR_NONE, @@ -180,9 +185,13 @@ typedef struct { PyObject_HEAD SSL_CTX *ctx; #ifdef OPENSSL_NPN_NEGOTIATED
+#endif #ifndef OPENSSL_NO_TLSEXT PyObject *set_hostname; #endif @@ -1460,7 +1469,20 @@ static PyObject *PySSL_selected_npn_prot if (out == NULL) Py_RETURN_NONE;
+} +#endif + +#ifdef HAVE_ALPN +static PyObject *PySSL_selected_alpn_protocol(PySSLSocket *self) {
} #endif @@ -2054,6 +2076,9 @@ static PyMethodDef PySSLMethods[] = { #ifdef OPENSSL_NPN_NEGOTIATED {"selected_npn_protocol", (PyCFunction)PySSL_selected_npn_protocol, METH_NOARGS}, #endif +#ifdef HAVE_ALPN
+#endif {"compression", (PyCFunction)PySSL_compression, METH_NOARGS}, {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS, PySSL_SSLshutdown_doc}, @@ -2159,6 +2184,9 @@ context_new(PyTypeObject *type, PyObject #ifdef OPENSSL_NPN_NEGOTIATED self->npn_protocols = NULL; #endif +#ifdef HAVE_ALPN
+#endif #ifndef OPENSSL_NO_TLSEXT self->set_hostname = NULL; #endif @@ -2218,7 +2246,10 @@ context_dealloc(PySSLContext *self) context_clear(self); SSL_CTX_free(self->ctx); #ifdef OPENSSL_NPN_NEGOTIATED
#endif Py_TYPE(self)->tp_free(self); } @@ -2244,6 +2275,23 @@ set_ciphers(PySSLContext *self, PyObject Py_RETURN_NONE; } +static int +do_protocol_selection(unsigned char **out, unsigned char *outlen,
const unsigned char *remote_protocols, unsigned int remote_protocols_len,[](#l5.90)
unsigned char *our_protocols, unsigned int our_protocols_len)[](#l5.91)
- if (our_protocols == NULL) {
our_protocols = (unsigned char*)"";[](#l5.94)
our_protocols_len = 0;[](#l5.95)
- }
- SSL_select_next_proto(out, outlen,
remote_protocols, remote_protocols_len,[](#l5.99)
our_protocols, our_protocols_len);[](#l5.100)
+} + #ifdef OPENSSL_NPN_NEGOTIATED /* this callback gets passed to SSL_CTX_set_next_protos_advertise_cb */ static int @@ -2254,10 +2302,10 @@ static int PySSLContext *ssl_ctx = (PySSLContext *) args; if (ssl_ctx->npn_protocols == NULL) {
*data = (unsigned char *) "";[](#l5.112)
*data = (unsigned char *) ssl_ctx->npn_protocols;[](#l5.116)
} @@ -2270,23 +2318,9 @@ static int const unsigned char *server, unsigned int server_len, void *args)*data = ssl_ctx->npn_protocols;[](#l5.117) *len = ssl_ctx->npn_protocols_len;[](#l5.118)
- if (client == NULL) {
client = (unsigned char *) "";[](#l5.131)
client_len = 0;[](#l5.132)
- } else {
client_len = ssl_ctx->npn_protocols_len;[](#l5.134)
- }
- PySSLContext *ctx = (PySSLContext *)args;
- return do_protocol_selection(out, outlen, server, server_len,
ctx->npn_protocols, ctx->npn_protocols_len);[](#l5.144)
} #endif @@ -2329,6 +2363,50 @@ static PyObject * #endif } +#ifdef HAVE_ALPN +static int +_selectALPN_cb(SSL *s,
const unsigned char **out, unsigned char *outlen,[](#l5.155)
const unsigned char *client_protocols, unsigned int client_protocols_len,[](#l5.156)
void *args)[](#l5.157)
- PySSLContext *ctx = (PySSLContext *)args;
- return do_protocol_selection((unsigned char **)out, outlen,
client_protocols, client_protocols_len,[](#l5.161)
ctx->alpn_protocols, ctx->alpn_protocols_len);[](#l5.162)
+} +#endif + +static PyObject * +_set_alpn_protocols(PySSLContext *self, PyObject *args) +{ +#ifdef HAVE_ALPN
- PyMem_FREE(self->alpn_protocols);
- self->alpn_protocols = PyMem_Malloc(protos.len);
- if (!self->alpn_protocols)
return PyErr_NoMemory();[](#l5.178)
- memcpy(self->alpn_protocols, protos.buf, protos.len);
- self->alpn_protocols_len = protos.len;
- PyBuffer_Release(&protos);
- if (SSL_CTX_set_alpn_protos(self->ctx, self->alpn_protocols, self->alpn_protocols_len))
return PyErr_NoMemory();[](#l5.184)
- SSL_CTX_set_alpn_select_cb(self->ctx, _selectALPN_cb, self);
- PyErr_SetString(PyExc_NotImplementedError,
"The ALPN extension requires OpenSSL 1.0.2 or later.");[](#l5.191)
- return NULL;
+#endif +} + static PyObject * get_verify_mode(PySSLContext *self, void *c) { @@ -3307,6 +3385,8 @@ static struct PyMethodDef context_method METH_VARARGS | METH_KEYWORDS, NULL}, {"set_ciphers", (PyCFunction) set_ciphers, METH_VARARGS, NULL},
- {"_set_alpn_protocols", (PyCFunction) _set_alpn_protocols,
{"_set_npn_protocols", (PyCFunction) _set_npn_protocols, METH_VARARGS, NULL}, {"load_cert_chain", (PyCFunction) load_cert_chain, @@ -4502,6 +4582,14 @@ PyInit__ssl(void) Py_INCREF(r); PyModule_AddObject(m, "HAS_NPN", r); +#ifdef HAVE_ALPNMETH_VARARGS, NULL},[](#l5.204)
- r = Py_True;
+ /* Mappings for error codes */ err_codes_to_names = PyDict_New(); err_names_to_codes = PyDict_New();