Issue #27866: Add SSLContext.get_ciphers() method to get a list of all enabled ciphers.

This commit is contained in:
Christian Heimes 2016-09-06 00:04:45 +02:00
parent dffa3949c7
commit 25bfcd5d9e
5 changed files with 211 additions and 1 deletions

View File

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

View File

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

View File

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

View File

@ -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 */
};

View File

@ -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]*/