summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Heimes <christian@python.org>2016-09-06 00:04:45 +0200
committerChristian Heimes <christian@python.org>2016-09-06 00:04:45 +0200
commit8b5d969b2f06427f0460be47b20e0e2bc9400535 (patch)
tree9ccd08b56f5a493668a8f7853603ce146a4da78d
parentc4a3f724702fba76349034c07e43915d221fcf4d (diff)
downloadcpython-8b5d969b2f06427f0460be47b20e0e2bc9400535.tar.gz
Issue #27866: Add SSLContext.get_ciphers() method to get a list of all enabled ciphers.
-rw-r--r--Doc/library/ssl.rst56
-rw-r--r--Lib/test/test_ssl.py9
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/_ssl.c117
-rw-r--r--Modules/clinic/_ssl.c.h27
5 files changed, 211 insertions, 1 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 04fad06a64..892c0ea124 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -1259,6 +1259,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 f6afa267c5..f19cf43336 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -834,6 +834,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/Misc/NEWS b/Misc/NEWS
index c7d87411a2..2987921181 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -77,6 +77,9 @@ Core and Builtins
Library
-------
+- Issue #27866: Add SSLContext.get_ciphers() method to get a list of all
+ enabled ciphers.
+
- Issue #27744: Add AF_ALG (Linux Kernel crypto) to socket module.
- Issue #26470: Port ssl and hashlib module to OpenSSL 1.1.0.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 151029a50b..fe19195366 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 0bdc35e5f9..ee8213a1a4 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]*/