From 05089ab835383f7919b6a7a7be6a5451de4a12bb Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 26 Aug 2016 12:58:08 +0200 Subject: [PATCH] Add SSLContext.get_ciphers() --- Doc/library/ssl.rst | 56 +++++++++++++++++++++++ Lib/test/test_ssl.py | 9 ++++ Modules/_ssl.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ Modules/clinic/_ssl.c.h | 27 ++++++++++- 4 files changed, 208 insertions(+), 1 deletion(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 63020f5652a2ad965264fc84549262c0276449e5..f812aec3dadb70d225189123b717b7ea04b3e636 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1241,6 +1241,62 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.4 +.. method:: SSLContext.get_ciphers() + + Get a list of enabled ciphers. The list is in order of cipher priority. + See :meth:`SSLContext.set_ciphers`. + + Example:: + + >>> ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + >>> ctx.set_ciphers('ECDHE+AESGCM:!ECDSA') + >>> ctx.get_ciphers() # OpenSSL 1.0.x + [{'alg_bits': 256, + 'description': 'ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA ' + 'Enc=AESGCM(256) Mac=AEAD', + 'id': 50380848, + 'name': 'ECDHE-RSA-AES256-GCM-SHA384', + 'protocol': 'TLSv1/SSLv3', + 'strength_bits': 256}, + {'alg_bits': 128, + 'description': 'ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA ' + 'Enc=AESGCM(128) Mac=AEAD', + 'id': 50380847, + 'name': 'ECDHE-RSA-AES128-GCM-SHA256', + 'protocol': 'TLSv1/SSLv3', + 'strength_bits': 128}] + + On OpenSSL 1.1 and newer the cipher dict contains additional fields:: + >>> ctx.get_ciphers() # OpenSSL 1.1+ + [{'aead': True, + 'alg_bits': 256, + 'auth': 'auth-rsa', + 'description': 'ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA ' + 'Enc=AESGCM(256) Mac=AEAD', + 'digest': None, + 'id': 50380848, + 'kea': 'kx-ecdhe', + 'name': 'ECDHE-RSA-AES256-GCM-SHA384', + 'protocol': 'TLSv1.2', + 'strength_bits': 256, + 'symmetric': 'aes-256-gcm'}, + {'aead': True, + 'alg_bits': 128, + 'auth': 'auth-rsa', + 'description': 'ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA ' + 'Enc=AESGCM(128) Mac=AEAD', + 'digest': None, + 'id': 50380847, + 'kea': 'kx-ecdhe', + 'name': 'ECDHE-RSA-AES128-GCM-SHA256', + 'protocol': 'TLSv1.2', + 'strength_bits': 128, + 'symmetric': 'aes-128-gcm'}] + + Availability: OpenSSL 1.0.2+ + + .. versionadded:: 3.6 + .. method:: SSLContext.set_default_verify_paths() Load a set of default "certification authority" (CA) certificates from diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index db4a106d6fa04a2ea0bb631b626dcfde9a18e76d..c9c6b7205516f4c07f7d0064f986a2cdcd8efc35 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -833,6 +833,15 @@ class ContextTests(unittest.TestCase): with self.assertRaisesRegex(ssl.SSLError, "No cipher can be selected"): ctx.set_ciphers("^$:,;?*'dorothyx") + @unittest.skipIf(ssl.OPENSSL_VERSION_INFO < (1, 0, 2, 0, 0), 'OpenSSL too old') + def test_get_ciphers(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + ctx.set_ciphers('ECDHE+AESGCM:!ECDSA') + names = set(d['name'] for d in ctx.get_ciphers()) + self.assertEqual(names, + {'ECDHE-RSA-AES256-GCM-SHA384', + 'ECDHE-RSA-AES128-GCM-SHA256'}) + @skip_if_broken_ubuntu_ssl def test_options(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 151029a50b91f212e2c3b5e1aac5d70968650d53..fe19195366af91268d317dcaa916268fbc1c80bf 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -1519,6 +1519,76 @@ cipher_to_tuple(const SSL_CIPHER *cipher) return NULL; } +#if OPENSSL_VERSION_NUMBER >= 0x10002000UL +static PyObject * +cipher_to_dict(const SSL_CIPHER *cipher) +{ + const char *cipher_name, *cipher_protocol; + + unsigned long cipher_id; + int alg_bits, strength_bits, len; + char buf[512] = {0}; +#if OPENSSL_VERSION_1_1 + int aead, nid; + const char *skcipher = NULL, *digest = NULL, *kx = NULL, *auth = NULL; +#endif + PyObject *retval; + + retval = PyDict_New(); + if (retval == NULL) { + goto error; + } + + /* can be NULL */ + cipher_name = SSL_CIPHER_get_name(cipher); + cipher_protocol = SSL_CIPHER_get_version(cipher); + cipher_id = SSL_CIPHER_get_id(cipher); + SSL_CIPHER_description(cipher, buf, sizeof(buf) - 1); + len = strlen(buf); + if (len > 1 && buf[len-1] == '\n') + buf[len-1] = '\0'; + strength_bits = SSL_CIPHER_get_bits(cipher, &alg_bits); + +#if OPENSSL_VERSION_1_1 + aead = SSL_CIPHER_is_aead(cipher); + nid = SSL_CIPHER_get_cipher_nid(cipher); + skcipher = nid != NID_undef ? OBJ_nid2ln(nid) : NULL; + nid = SSL_CIPHER_get_digest_nid(cipher); + digest = nid != NID_undef ? OBJ_nid2ln(nid) : NULL; + nid = SSL_CIPHER_get_kx_nid(cipher); + kx = nid != NID_undef ? OBJ_nid2ln(nid) : NULL; + nid = SSL_CIPHER_get_auth_nid(cipher); + auth = nid != NID_undef ? OBJ_nid2ln(nid) : NULL; +#endif + + retval = Py_BuildValue( + "{sksssssssisi" +#if OPENSSL_VERSION_1_1 + "sOssssssss" +#endif + "}", + "id", cipher_id, + "name", cipher_name, + "protocol", cipher_protocol, + "description", buf, + "strength_bits", strength_bits, + "alg_bits", alg_bits +#if OPENSSL_VERSION_1_1 + ,"aead", aead ? Py_True : Py_False, + "symmetric", skcipher, + "digest", digest, + "kea", kx, + "auth", auth +#endif + ); + return retval; + + error: + Py_XDECREF(retval); + return NULL; +} +#endif + /*[clinic input] _ssl._SSLSocket.shared_ciphers [clinic start generated code]*/ @@ -2478,6 +2548,52 @@ _ssl__SSLContext_set_ciphers_impl(PySSLContext *self, const char *cipherlist) Py_RETURN_NONE; } +#if OPENSSL_VERSION_NUMBER >= 0x10002000UL +/*[clinic input] +_ssl._SSLContext.get_ciphers +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_get_ciphers_impl(PySSLContext *self) +/*[clinic end generated code: output=a56e4d68a406dfc4 input=a2aadc9af89b79c5]*/ +{ + SSL *ssl = NULL; + STACK_OF(SSL_CIPHER) *sk = NULL; + SSL_CIPHER *cipher; + int i=0; + PyObject *result = NULL, *dct; + + ssl = SSL_new(self->ctx); + if (ssl == NULL) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto exit; + } + sk = SSL_get_ciphers(ssl); + + result = PyList_New(sk_SSL_CIPHER_num(sk)); + if (result == NULL) { + goto exit; + } + + for (i = 0; i < sk_SSL_CIPHER_num(sk); i++) { + cipher = sk_SSL_CIPHER_value(sk, i); + dct = cipher_to_dict(cipher); + if (dct == NULL) { + Py_CLEAR(result); + goto exit; + } + PyList_SET_ITEM(result, i, dct); + } + + exit: + if (ssl != NULL) + SSL_free(ssl); + return result; + +} +#endif + + #ifdef OPENSSL_NPN_NEGOTIATED static int do_protocol_selection(int alpn, unsigned char **out, unsigned char *outlen, @@ -3645,6 +3761,7 @@ static struct PyMethodDef context_methods[] = { _SSL__SSLCONTEXT_SET_SERVERNAME_CALLBACK_METHODDEF _SSL__SSLCONTEXT_CERT_STORE_STATS_METHODDEF _SSL__SSLCONTEXT_GET_CA_CERTS_METHODDEF + _SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF {NULL, NULL} /* sentinel */ }; diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 0bdc35e5f9cdaf8f1966025e397c22ccbb6b57b3..ee8213a1a41febdfaaf417439875697f736b966c 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -378,6 +378,27 @@ exit: return return_value; } +#if (OPENSSL_VERSION_NUMBER >= 0x10002000UL) + +PyDoc_STRVAR(_ssl__SSLContext_get_ciphers__doc__, +"get_ciphers($self, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF \ + {"get_ciphers", (PyCFunction)_ssl__SSLContext_get_ciphers, METH_NOARGS, _ssl__SSLContext_get_ciphers__doc__}, + +static PyObject * +_ssl__SSLContext_get_ciphers_impl(PySSLContext *self); + +static PyObject * +_ssl__SSLContext_get_ciphers(PySSLContext *self, PyObject *Py_UNUSED(ignored)) +{ + return _ssl__SSLContext_get_ciphers_impl(self); +} + +#endif /* (OPENSSL_VERSION_NUMBER >= 0x10002000UL) */ + PyDoc_STRVAR(_ssl__SSLContext__set_npn_protocols__doc__, "_set_npn_protocols($self, protos, /)\n" "--\n" @@ -1128,6 +1149,10 @@ exit: #define _SSL__SSLSOCKET_SELECTED_ALPN_PROTOCOL_METHODDEF #endif /* !defined(_SSL__SSLSOCKET_SELECTED_ALPN_PROTOCOL_METHODDEF) */ +#ifndef _SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF + #define _SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF +#endif /* !defined(_SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF) */ + #ifndef _SSL__SSLCONTEXT_SET_ECDH_CURVE_METHODDEF #define _SSL__SSLCONTEXT_SET_ECDH_CURVE_METHODDEF #endif /* !defined(_SSL__SSLCONTEXT_SET_ECDH_CURVE_METHODDEF) */ @@ -1143,4 +1168,4 @@ exit: #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=6057f95343369849 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2e7907a7d8f97ccf input=a9049054013a1b77]*/ -- 2.7.4