[Python-checkins] cpython: Issue #27866: Add SSLContext.get_ciphers() method to get a list of all enabled

christian.heimes python-checkins at python.org
Mon Sep 5 18:05:04 EDT 2016


https://hg.python.org/cpython/rev/ca8d7cb55a8e
changeset:   103073:ca8d7cb55a8e
user:        Christian Heimes <christian at python.org>
date:        Tue Sep 06 00:04:45 2016 +0200
summary:
  Issue #27866: Add SSLContext.get_ciphers() method to get a list of all enabled ciphers.

files:
  Doc/library/ssl.rst     |   56 +++++++++++++
  Lib/test/test_ssl.py    |    9 ++
  Misc/NEWS               |    3 +
  Modules/_ssl.c          |  117 ++++++++++++++++++++++++++++
  Modules/clinic/_ssl.c.h |   27 ++++++-
  5 files changed, 211 insertions(+), 1 deletions(-)


diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -1259,6 +1259,62 @@
 
    .. 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
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -834,6 +834,15 @@
         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
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -77,6 +77,9 @@
 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
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -1519,6 +1519,76 @@
     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 @@
     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 @@
     _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
--- a/Modules/clinic/_ssl.c.h
+++ b/Modules/clinic/_ssl.c.h
@@ -378,6 +378,27 @@
     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 @@
     #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 @@
 #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]*/

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


More information about the Python-checkins mailing list