diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | OpenSSL/SSL.py | 90 | ||||
-rw-r--r-- | OpenSSL/test/test_ssl.py | 104 | ||||
-rw-r--r-- | doc/api/ssl.rst | 26 |
4 files changed, 221 insertions, 2 deletions
@@ -1,3 +1,4 @@ +*.py[co] build dist -*.egg-info
\ No newline at end of file +*.egg-info diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index fbb18f0..86410c0 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -124,6 +124,19 @@ SSL_CB_CONNECT_EXIT = _lib.SSL_CB_CONNECT_EXIT SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE +_Cryptography_HAS_EC = _lib.Cryptography_HAS_EC +ELLIPTIC_CURVE_DESCRIPTIONS = {} # In case there's no EC support +if _Cryptography_HAS_EC: + _num_curves = _lib.EC_get_builtin_curves(_ffi.NULL, 0) + _curves = _ffi.new('EC_builtin_curve[]', _num_curves) + if _lib.EC_get_builtin_curves(_curves, _num_curves) == _num_curves: + ELLIPTIC_CURVE_DESCRIPTIONS = dict( + (_ffi.string(_lib.OBJ_nid2sn(c.nid)).decode('ascii'), + _ffi.string(c.comment).decode('utf-8')) + for c in _curves) + del _num_curves + del _curves + class Error(Exception): """ @@ -217,6 +230,37 @@ def _asFileDescriptor(obj): +class ECNotAvailable(ValueError): + """ + Raised if a request for an elliptic curve fails because OpenSSL + is compiled without elliptic curve support. + """ + def __init__(self): + ValueError.__init__(self, "OpenSSL is compiled without EC support") + + + +class UnknownObject(ValueError): + """ + Raised if OpenSSL does not recognize the requested object. + """ + def __init__(self, sn): + ValueError.__init__(self, "OpenSSL does not recognize %r" % sn) + self.sn = sn + + + +class UnsupportedEllipticCurve(ValueError): + """ + Raised if OpenSSL does not support the requested elliptic curve. + """ + def __init__(self, sn): + ValueError.__init__( + self, "OpenSSL does not support the elliptic curve %r" % sn) + self.sn = sn + + + def SSLeay_version(type): """ Return a string describing the version of OpenSSL in use. @@ -598,6 +642,50 @@ class Context(object): _lib.SSL_CTX_set_tmp_dh(self._context, dh) + def _set_tmp_ecdh_curve_by_nid(self, name, nid): + """ + Select a curve to use by the OpenSSL NID associated with that curve. + + :param name: The name of the curve identified by the NID. + :type name: str + + :param nid: The OpenSSL NID to use. + :type nid: int + + :raise UnsupportedEllipticCurve: If the given NID does not identify a + supported curve. + """ + ecdh = _lib.EC_KEY_new_by_curve_name(nid) + if ecdh == _ffi.NULL: + raise UnsupportedEllipticCurve(name) + _lib.SSL_CTX_set_tmp_ecdh(self._context, ecdh) + _lib.EC_KEY_free(ecdh) + + + def set_tmp_ecdh_curve(self, curve_name): + """ + Select a curve to use for ECDHE key exchange. + + The valid values of *curve_name* are the keys in + :py:data:OpenSSL.SSL.ELLIPTIC_CURVE_DESCRIPTIONS. + + Raises a subclass of ``ValueError`` if the linked OpenSSL was + not compiled with elliptical curve support or the specified + curve is not available. You can check the specific subclass, + but, in general, you should just handle ``ValueError``. + + :param curve_name: The 'short name' of a curve, e.g. 'prime256v1' + :type curve_name: str + :return: None + """ + if _lib.Cryptography_HAS_EC: + nid = _lib.OBJ_sn2nid(curve_name.encode('ascii')) + if nid == _lib.NID_undef: + raise UnknownObject(curve_name) + return self._set_tmp_ecdh_curve_by_nid(curve_name, nid) + raise ECNotAvailable() + + def set_cipher_list(self, cipher_list): """ Change the cipher list @@ -1214,7 +1302,7 @@ class Connection(object): The makefile() method is not implemented, since there is no dup semantics for SSL connections - :raise NotImplementedError + :raise: NotImplementedError """ raise NotImplementedError("Cannot make file object of OpenSSL.SSL.Connection") diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index bfe3114..fdeff9d 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -21,6 +21,7 @@ from OpenSSL.crypto import PKey, X509, X509Extension, X509Store from OpenSSL.crypto import dump_privatekey, load_privatekey from OpenSSL.crypto import dump_certificate, load_certificate +from OpenSSL.SSL import _lib from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN @@ -35,6 +36,9 @@ from OpenSSL.SSL import ( SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH, SESS_CACHE_NO_AUTO_CLEAR, SESS_CACHE_NO_INTERNAL_LOOKUP, SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_NO_INTERNAL) +from OpenSSL.SSL import ( + _Cryptography_HAS_EC, ELLIPTIC_CURVE_DESCRIPTIONS, + ECNotAvailable, UnknownObject, UnsupportedEllipticCurve) from OpenSSL.SSL import ( Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) @@ -1172,6 +1176,106 @@ class ContextTests(TestCase, _LoopbackMixin): # XXX What should I assert here? -exarkun + def test_set_tmp_ecdh_curve(self): + """ + :py:obj:`Context.set_tmp_ecdh_curve` sets the elliptic curve for + Diffie-Hellman to the specified named curve. + """ + context = Context(TLSv1_METHOD) + for curve in ELLIPTIC_CURVE_DESCRIPTIONS.keys(): + context.set_tmp_ecdh_curve(curve) # Must not throw. + + + def test_set_tmp_ecdh_curve_not_available(self): + """ + :py:obj:`Context.set_tmp_ecdh_curve` raises :py:obj:`ECNotAvailable` if + elliptic curve support is not available from the underlying OpenSSL + version at all. + """ + has_ec = _lib.Cryptography_HAS_EC + try: + _lib.Cryptography_HAS_EC = False + + context = Context(TLSv1_METHOD) + self.assertRaises( + ECNotAvailable, + context.set_tmp_ecdh_curve, next(iter(ELLIPTIC_CURVE_DESCRIPTIONS))) + finally: + _lib.Cryptography_HAS_EC = has_ec + + + def test_set_tmp_ecdh_curve_bad_curve_name(self): + """ + :py:obj:`Context.set_tmp_ecdh_curve` raises :py:obj:`UnknownObject` if + passed a curve_name that OpenSSL does not recognize and EC is + available. + """ + context = Context(TLSv1_METHOD) + try: + context.set_tmp_ecdh_curve('not_an_elliptic_curve') + except ECNotAvailable: + self.assertFalse(_Cryptography_HAS_EC) + except UnknownObject: + self.assertTrue(_Cryptography_HAS_EC) + else: + self.fail( + "set_tmp_ecdh_curve did not fail when called with " + "non-existent curve name") + + + def test_set_tmp_ecdh_curve_bad_nid(self): + """ + :py:obj:`Context._set_tmp_ecdh_curve_by_nid`, an implementation detail + of :py:obj:`Context.set_tmp_ecdh_curve`, raises + :py:obj:`UnsupportedEllipticCurve` raises if passed a NID that does not + identify a supported curve. + """ + context = Context(TLSv1_METHOD) + try: + context._set_tmp_ecdh_curve_by_nid( + u("curve"), _lib.OBJ_sn2nid(b"sha256")) + except UnsupportedEllipticCurve: + pass + else: + self.fail( + "_set_tmp_ecdh_curve_by_nid did not raise " + "UnsupportedEllipticCurve for a NID that does not " + "identify a supported curve.") + + + def test_set_tmp_ecdh_curve_not_a_curve(self): + """ + :py:obj:`Context.set_tmp_ecdh_curve` raises + :py:obj:`UnsupportedEllipticCurve` if passed a curve_name that OpenSSL + cannot instantiate as an elliptic curve. It raises + :py:obj:`ECNotAvailable` if EC is not available at all. + """ + context = Context(TLSv1_METHOD) + try: + context.set_tmp_ecdh_curve('sha256') + except ECNotAvailable: + self.assertFalse(_Cryptography_HAS_EC) + except UnknownObject: + self.assertTrue(_Cryptography_HAS_EC) + else: + self.fail( + "set_tmp_ecdh_curve did not fail when called with " + "a name that is not an elliptic curve") + + + def test_has_curve_descriptions(self): + """ + If the underlying cryptography bindings claim to have elliptic + curve support, there should be at least one curve. + + (In theory there could be an OpenSSL that violates this + assumption. If so, this test will fail and we'll find out.) + + """ + if _Cryptography_HAS_EC: + self.assertNotEqual(len(ELLIPTIC_CURVE_DESCRIPTIONS), 0) + + def test_set_cipher_list_bytes(self): """ :py:obj:`Context.set_cipher_list` accepts a :py:obj:`bytes` naming the diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index e1c1d8a..b7eca70 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -110,6 +110,17 @@ Context, Connection. .. versionadded:: 0.14 +.. py:data:: ELLIPTIC_CURVE_DESCRIPTIONS + + A dictionary mapping short names of elliptic curves to textual + descriptions. This dictionary contains exactly the set of curves + supported by the OpenSSL build in use. + + The keys are the curve names that can be passed into + Constants used with :py:meth:`Context.set_tmp_ecdh_curve` to + specify which elliptical curve should be used for ECDHE key exchange. + + .. py:data:: OPENSSL_VERSION_NUMBER An integer giving the version number of the OpenSSL library used to build this @@ -316,6 +327,21 @@ Context objects have the following methods: Load parameters for Ephemeral Diffie-Hellman from *dhfile*. +.. py:method:: Context.set_tmp_ecdh_curve(curve_name) + + Select a curve to use for ECDHE key exchange. + + The valid values of *curve_name* are the keys in + :py:data:`ELLIPTIC_CURVE_DESCRIPTIONS`. + + Raises a subclass of ``ValueError`` if the linked OpenSSL was not + compiled with elliptical curve support or the specified curve is + not available. You can check the specific subclass, but, in + general, you should just handle ``ValueError``. + + :param curve_name: The 'short name' of a curve, e.g. 'prime256v1' + :type curve_name: str + :return: None .. py:method:: Context.set_app_data(data) |