From 7b8d57a7b806b7af9c5650e8152357d5a5755ca7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 17 Jan 2014 12:08:54 -0600 Subject: [PATCH 01/45] Expose support for using ecdhe with SSL connections --- OpenSSL/SSL.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 8da25e2e0..d960eb36e 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -119,6 +119,15 @@ class _memoryview(object): SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE +NID_X9_62_prime192v1 = _lib.NID_X9_62_prime192v1 +NID_X9_62_prime192v2 = _lib.NID_X9_62_prime192v2 +NID_X9_62_prime192v3 = _lib.NID_X9_62_prime192v3 +NID_X9_62_prime239v1 = _lib.NID_X9_62_prime239v1 +NID_X9_62_prime239v2 = _lib.NID_X9_62_prime239v2 +NID_X9_62_prime239v3 = _lib.NID_X9_62_prime239v3 +NID_X9_62_prime256v1 = _lib.NID_X9_62_prime256v1 + + class Error(Exception): """ An error occurred in an `OpenSSL.SSL` API. @@ -581,6 +590,26 @@ def load_tmp_dh(self, dhfile): _lib.SSL_CTX_set_tmp_dh(self._context, dh) + def set_tmp_ecdh_by_curve_name(self, curve_name): + """ + Configure this connection to people to use Elliptical Curve + Diffie-Hellman key exchanges. + + :param curve_name: One of the named curve constsants. + :return: None + """ + if _lib.Cryptography_HAS_EC: + ecdh = _lib.EC_KEY_new_by_curve_name(curve_name) + if ecdh == _ffi.NULL: + raise ValueError( + "OpenSSL could not load the requested elliptic curve" + ) + _lib.SSL_CTX_set_tmp_ecdh(self._context, ecdh) + _lib.EC_KEY_free(ecdh) + else: + raise ValueError("OpenSSL is compiled without ECDH support") + + def set_cipher_list(self, cipher_list): """ Change the cipher list From a683fc0b8fabb4d763c77d7d04d8bf87799c9ca3 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 17 Jan 2014 12:45:56 -0600 Subject: [PATCH 02/45] Corrected a typo --- OpenSSL/SSL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index d960eb36e..325b82568 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -595,7 +595,7 @@ def set_tmp_ecdh_by_curve_name(self, curve_name): Configure this connection to people to use Elliptical Curve Diffie-Hellman key exchanges. - :param curve_name: One of the named curve constsants. + :param curve_name: One of the named curve constants. :return: None """ if _lib.Cryptography_HAS_EC: From 12dc084f3a4e087d240e8393d9dc529240b12e44 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 17 Jan 2014 12:51:31 -0600 Subject: [PATCH 03/45] Added tests --- OpenSSL/SSL.py | 2 ++ OpenSSL/test/test_ssl.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 325b82568..e80ea3c49 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -127,6 +127,8 @@ class _memoryview(object): NID_X9_62_prime239v3 = _lib.NID_X9_62_prime239v3 NID_X9_62_prime256v1 = _lib.NID_X9_62_prime256v1 +OPENSSL_NO_EC = not _lib.Cryptography_HAS_EC + class Error(Exception): """ diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index 95cb538b4..c884148e7 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -33,6 +33,7 @@ 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 NID_X9_62_prime256v1, OPENSSL_NO_EC from OpenSSL.SSL import ( Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) @@ -1057,6 +1058,17 @@ def test_load_tmp_dh(self): # XXX What should I assert here? -exarkun + if not OPENSSL_NO_EC: + def test_set_tmp_ecdh_by_curve_name(self): + """ + :py:obj:`Context.set_tmp_ecdh_by_curve_name` sets the Eliptical + Curve for Diffie-Hellman by the named curve. + """ + context = Context(TLSv1_METHOD) + context.set_tmp_ecdh_by_curve_name(NID_X9_62_prime256v1) + # XXX What should I assert here? -alex + + def test_set_cipher_list(self): """ :py:obj:`Context.set_cipher_list` accepts a :py:obj:`str` naming the ciphers which From 2c04e86b244055f4f3c9e05eb5cb1e1be787db77 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 17 Jan 2014 12:52:29 -0600 Subject: [PATCH 04/45] Added a gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..539da7411 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.py[co] From 807853c55afd447e821c30232801a72c88a048b4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 17 Jan 2014 13:03:27 -0600 Subject: [PATCH 05/45] Use the internal name --- OpenSSL/SSL.py | 2 +- OpenSSL/test/test_ssl.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index e80ea3c49..62ce6c1c4 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -127,7 +127,7 @@ class _memoryview(object): NID_X9_62_prime239v3 = _lib.NID_X9_62_prime239v3 NID_X9_62_prime256v1 = _lib.NID_X9_62_prime256v1 -OPENSSL_NO_EC = not _lib.Cryptography_HAS_EC +_Cryptography_HAS_EC = _lib.Cryptography_HAS_EC class Error(Exception): diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index c884148e7..5e08e9ba4 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -33,7 +33,7 @@ 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 NID_X9_62_prime256v1, OPENSSL_NO_EC +from OpenSSL.SSL import NID_X9_62_prime256v1, _Cryptography_HAS_EC from OpenSSL.SSL import ( Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) @@ -1058,7 +1058,7 @@ def test_load_tmp_dh(self): # XXX What should I assert here? -exarkun - if not OPENSSL_NO_EC: + if _Cryptography_HAS_EC: def test_set_tmp_ecdh_by_curve_name(self): """ :py:obj:`Context.set_tmp_ecdh_by_curve_name` sets the Eliptical From d5419e2a78be9d39ddd3983c38fa7e0ec3b23265 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 19 Jan 2014 21:03:36 -0600 Subject: [PATCH 06/45] Added documentation --- doc/api/ssl.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index b506757d3..76fb0ad06 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -116,6 +116,18 @@ Context, Connection. .. versionadded:: 0.14 +.. py:data:: NID_X9_62_prime192v1 + NID_X9_62_prime192v2 + NID_X9_62_prime192v3 + NID_X9_62_prime239v1 + NID_X9_62_prime239v2 + NID_X9_62_prime239v3 + NID_X9_62_prime256v1 + + Constants used with :py:meth:`Context.set_tmp_ecdh_by_curve_name` to + specify which elliptical curve should be used. + + .. py:data:: OPENSSL_VERSION_NUMBER An integer giving the version number of the OpenSSL library used to build this @@ -322,6 +334,16 @@ Context objects have the following methods: Load parameters for Ephemeral Diffie-Hellman from *dhfile*. +.. py:method:: Context.set_tmp_ecdh_by_curve_name(curve_name) + + Configure this connection to people to use Elliptical Curve Diffie-Hellman + key exchanges. + + ``curve_name`` should be one of the named curve constants, such as + :py:data:`NID_X9_62_prime256v1`. + + Raises a ``ValueError`` if the linked OpenSSL was not compiled with + elliptical curve support, or the specified curve is not available. .. py:method:: Context.set_app_data(data) From b4e5c8d71227afdead269fdcc7a81cfc674efaff Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Wed, 5 Mar 2014 12:54:15 -0800 Subject: [PATCH 07/45] Expose all of the EC curve name constants It would be great if there were a clean way to enumerate them rather than just listing them like this, but I don't know of one. --- OpenSSL/SSL.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 03aa47bee..a3e4cd017 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -122,13 +122,148 @@ class _memoryview(object): SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE +NID_X9_62_c2pnb163v1 = _lib.NID_X9_62_c2pnb163v1 +SN_X9_62_c2pnb163v1 = _ffi.string(_lib.SN_X9_62_c2pnb163v1) +NID_X9_62_c2pnb163v2 = _lib.NID_X9_62_c2pnb163v2 +SN_X9_62_c2pnb163v2 = _ffi.string(_lib.SN_X9_62_c2pnb163v2) +NID_X9_62_c2pnb163v3 = _lib.NID_X9_62_c2pnb163v3 +SN_X9_62_c2pnb163v3 = _ffi.string(_lib.SN_X9_62_c2pnb163v3) +NID_X9_62_c2pnb176v1 = _lib.NID_X9_62_c2pnb176v1 +SN_X9_62_c2pnb176v1 = _ffi.string(_lib.SN_X9_62_c2pnb176v1) +NID_X9_62_c2tnb191v1 = _lib.NID_X9_62_c2tnb191v1 +SN_X9_62_c2tnb191v1 = _ffi.string(_lib.SN_X9_62_c2tnb191v1) +NID_X9_62_c2tnb191v2 = _lib.NID_X9_62_c2tnb191v2 +SN_X9_62_c2tnb191v2 = _ffi.string(_lib.SN_X9_62_c2tnb191v2) +NID_X9_62_c2tnb191v3 = _lib.NID_X9_62_c2tnb191v3 +SN_X9_62_c2tnb191v3 = _ffi.string(_lib.SN_X9_62_c2tnb191v3) +NID_X9_62_c2onb191v4 = _lib.NID_X9_62_c2onb191v4 +SN_X9_62_c2onb191v4 = _ffi.string(_lib.SN_X9_62_c2onb191v4) +NID_X9_62_c2onb191v5 = _lib.NID_X9_62_c2onb191v5 +SN_X9_62_c2onb191v5 = _ffi.string(_lib.SN_X9_62_c2onb191v5) +NID_X9_62_c2pnb208w1 = _lib.NID_X9_62_c2pnb208w1 +SN_X9_62_c2pnb208w1 = _ffi.string(_lib.SN_X9_62_c2pnb208w1) +NID_X9_62_c2tnb239v1 = _lib.NID_X9_62_c2tnb239v1 +SN_X9_62_c2tnb239v1 = _ffi.string(_lib.SN_X9_62_c2tnb239v1) +NID_X9_62_c2tnb239v2 = _lib.NID_X9_62_c2tnb239v2 +SN_X9_62_c2tnb239v2 = _ffi.string(_lib.SN_X9_62_c2tnb239v2) +NID_X9_62_c2tnb239v3 = _lib.NID_X9_62_c2tnb239v3 +SN_X9_62_c2tnb239v3 = _ffi.string(_lib.SN_X9_62_c2tnb239v3) +NID_X9_62_c2onb239v4 = _lib.NID_X9_62_c2onb239v4 +SN_X9_62_c2onb239v4 = _ffi.string(_lib.SN_X9_62_c2onb239v4) +NID_X9_62_c2onb239v5 = _lib.NID_X9_62_c2onb239v5 +SN_X9_62_c2onb239v5 = _ffi.string(_lib.SN_X9_62_c2onb239v5) +NID_X9_62_c2pnb272w1 = _lib.NID_X9_62_c2pnb272w1 +SN_X9_62_c2pnb272w1 = _ffi.string(_lib.SN_X9_62_c2pnb272w1) +NID_X9_62_c2pnb304w1 = _lib.NID_X9_62_c2pnb304w1 +SN_X9_62_c2pnb304w1 = _ffi.string(_lib.SN_X9_62_c2pnb304w1) +NID_X9_62_c2tnb359v1 = _lib.NID_X9_62_c2tnb359v1 +SN_X9_62_c2tnb359v1 = _ffi.string(_lib.SN_X9_62_c2tnb359v1) +NID_X9_62_c2pnb368w1 = _lib.NID_X9_62_c2pnb368w1 +SN_X9_62_c2pnb368w1 = _ffi.string(_lib.SN_X9_62_c2pnb368w1) +NID_X9_62_c2tnb431r1 = _lib.NID_X9_62_c2tnb431r1 +SN_X9_62_c2tnb431r1 = _ffi.string(_lib.SN_X9_62_c2tnb431r1) NID_X9_62_prime192v1 = _lib.NID_X9_62_prime192v1 +SN_X9_62_prime192v1 = _ffi.string(_lib.SN_X9_62_prime192v1) NID_X9_62_prime192v2 = _lib.NID_X9_62_prime192v2 +SN_X9_62_prime192v2 = _ffi.string(_lib.SN_X9_62_prime192v2) NID_X9_62_prime192v3 = _lib.NID_X9_62_prime192v3 +SN_X9_62_prime192v3 = _ffi.string(_lib.SN_X9_62_prime192v3) NID_X9_62_prime239v1 = _lib.NID_X9_62_prime239v1 +SN_X9_62_prime239v1 = _ffi.string(_lib.SN_X9_62_prime239v1) NID_X9_62_prime239v2 = _lib.NID_X9_62_prime239v2 +SN_X9_62_prime239v2 = _ffi.string(_lib.SN_X9_62_prime239v2) NID_X9_62_prime239v3 = _lib.NID_X9_62_prime239v3 +SN_X9_62_prime239v3 = _ffi.string(_lib.SN_X9_62_prime239v3) NID_X9_62_prime256v1 = _lib.NID_X9_62_prime256v1 +SN_X9_62_prime256v1 = _ffi.string(_lib.SN_X9_62_prime256v1) +NID_secp112r1 = _lib.NID_secp112r1 +SN_secp112r1 = _ffi.string(_lib.SN_secp112r1) +NID_secp112r2 = _lib.NID_secp112r2 +SN_secp112r2 = _ffi.string(_lib.SN_secp112r2) +NID_secp128r1 = _lib.NID_secp128r1 +SN_secp128r1 = _ffi.string(_lib.SN_secp128r1) +NID_secp128r2 = _lib.NID_secp128r2 +SN_secp128r2 = _ffi.string(_lib.SN_secp128r2) +NID_secp160k1 = _lib.NID_secp160k1 +SN_secp160k1 = _ffi.string(_lib.SN_secp160k1) +NID_secp160r1 = _lib.NID_secp160r1 +SN_secp160r1 = _ffi.string(_lib.SN_secp160r1) +NID_secp160r2 = _lib.NID_secp160r2 +SN_secp160r2 = _ffi.string(_lib.SN_secp160r2) +NID_sect163k1 = _lib.NID_sect163k1 +SN_sect163k1 = _ffi.string(_lib.SN_sect163k1) +NID_sect163r1 = _lib.NID_sect163r1 +SN_sect163r1 = _ffi.string(_lib.SN_sect163r1) +NID_sect163r2 = _lib.NID_sect163r2 +SN_sect163r2 = _ffi.string(_lib.SN_sect163r2) +NID_secp192k1 = _lib.NID_secp192k1 +SN_secp192k1 = _ffi.string(_lib.SN_secp192k1) +NID_secp224k1 = _lib.NID_secp224k1 +SN_secp224k1 = _ffi.string(_lib.SN_secp224k1) +NID_secp224r1 = _lib.NID_secp224r1 +SN_secp224r1 = _ffi.string(_lib.SN_secp224r1) +NID_secp256k1 = _lib.NID_secp256k1 +SN_secp256k1 = _ffi.string(_lib.SN_secp256k1) +NID_secp384r1 = _lib.NID_secp384r1 +SN_secp384r1 = _ffi.string(_lib.SN_secp384r1) +NID_secp521r1 = _lib.NID_secp521r1 +SN_secp521r1 = _ffi.string(_lib.SN_secp521r1) +NID_sect113r1 = _lib.NID_sect113r1 +SN_sect113r1 = _ffi.string(_lib.SN_sect113r1) +NID_sect113r2 = _lib.NID_sect113r2 +SN_sect113r2 = _ffi.string(_lib.SN_sect113r2) +NID_sect131r1 = _lib.NID_sect131r1 +SN_sect131r1 = _ffi.string(_lib.SN_sect131r1) +NID_sect131r2 = _lib.NID_sect131r2 +SN_sect131r2 = _ffi.string(_lib.SN_sect131r2) +NID_sect193r1 = _lib.NID_sect193r1 +SN_sect193r1 = _ffi.string(_lib.SN_sect193r1) +NID_sect193r2 = _lib.NID_sect193r2 +SN_sect193r2 = _ffi.string(_lib.SN_sect193r2) +NID_sect233k1 = _lib.NID_sect233k1 +SN_sect233k1 = _ffi.string(_lib.SN_sect233k1) +NID_sect233r1 = _lib.NID_sect233r1 +SN_sect233r1 = _ffi.string(_lib.SN_sect233r1) +NID_sect239k1 = _lib.NID_sect239k1 +SN_sect239k1 = _ffi.string(_lib.SN_sect239k1) +NID_sect283k1 = _lib.NID_sect283k1 +SN_sect283k1 = _ffi.string(_lib.SN_sect283k1) +NID_sect283r1 = _lib.NID_sect283r1 +SN_sect283r1 = _ffi.string(_lib.SN_sect283r1) +NID_sect409k1 = _lib.NID_sect409k1 +SN_sect409k1 = _ffi.string(_lib.SN_sect409k1) +NID_sect409r1 = _lib.NID_sect409r1 +SN_sect409r1 = _ffi.string(_lib.SN_sect409r1) +NID_sect571k1 = _lib.NID_sect571k1 +SN_sect571k1 = _ffi.string(_lib.SN_sect571k1) +NID_sect571r1 = _lib.NID_sect571r1 +SN_sect571r1 = _ffi.string(_lib.SN_sect571r1) +NID_wap_wsg_idm_ecid_wtls1 = _lib.NID_wap_wsg_idm_ecid_wtls1 +SN_wap_wsg_idm_ecid_wtls1 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls1) +NID_wap_wsg_idm_ecid_wtls3 = _lib.NID_wap_wsg_idm_ecid_wtls3 +SN_wap_wsg_idm_ecid_wtls3 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls3) +NID_wap_wsg_idm_ecid_wtls4 = _lib.NID_wap_wsg_idm_ecid_wtls4 +SN_wap_wsg_idm_ecid_wtls4 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls4) +NID_wap_wsg_idm_ecid_wtls5 = _lib.NID_wap_wsg_idm_ecid_wtls5 +SN_wap_wsg_idm_ecid_wtls5 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls5) +NID_wap_wsg_idm_ecid_wtls6 = _lib.NID_wap_wsg_idm_ecid_wtls6 +SN_wap_wsg_idm_ecid_wtls6 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls6) +NID_wap_wsg_idm_ecid_wtls7 = _lib.NID_wap_wsg_idm_ecid_wtls7 +SN_wap_wsg_idm_ecid_wtls7 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls7) +NID_wap_wsg_idm_ecid_wtls8 = _lib.NID_wap_wsg_idm_ecid_wtls8 +SN_wap_wsg_idm_ecid_wtls8 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls8) +NID_wap_wsg_idm_ecid_wtls9 = _lib.NID_wap_wsg_idm_ecid_wtls9 +SN_wap_wsg_idm_ecid_wtls9 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls9) +NID_wap_wsg_idm_ecid_wtls10 = _lib.NID_wap_wsg_idm_ecid_wtls10 +SN_wap_wsg_idm_ecid_wtls10 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls10) +NID_wap_wsg_idm_ecid_wtls11 = _lib.NID_wap_wsg_idm_ecid_wtls11 +SN_wap_wsg_idm_ecid_wtls11 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls11) +NID_wap_wsg_idm_ecid_wtls12 = _lib.NID_wap_wsg_idm_ecid_wtls12 +SN_wap_wsg_idm_ecid_wtls12 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls12) +NID_ipsec3 = _lib.NID_ipsec3 +SN_ipsec3 = _ffi.string(_lib.SN_ipsec3) +NID_ipsec4 = _lib.NID_ipsec4 +SN_ipsec4 = _ffi.string(_lib.SN_ipsec4) _Cryptography_HAS_EC = _lib.Cryptography_HAS_EC From 9bca0edb8a68b5fdb23287d0412ed8205bc1d91a Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Wed, 5 Mar 2014 14:41:41 -0800 Subject: [PATCH 08/45] Add SSL.ELLIPTIC_CURVE_DESCRIPTIONS to expose the actual supported curves Different OpenSSL builds support different curves. Determine the supported curves at startup and expose the list. --- OpenSSL/SSL.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index a3e4cd017..9fe700199 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -266,6 +266,15 @@ class _memoryview(object): SN_ipsec4 = _ffi.string(_lib.SN_ipsec4) _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 = {c.nid : _ffi.string(c.comment) + for c in _curves} + del _num_curves + del _curves class Error(Exception): From 76a6133518e5671cc6380304fdc06aae66542d7c Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Wed, 12 Mar 2014 15:02:56 -0700 Subject: [PATCH 09/45] Fix docstring and Python 2.6 issues in ECDHE support --- OpenSSL/SSL.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 9fe700199..602a98c8f 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -271,8 +271,8 @@ class _memoryview(object): _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 = {c.nid : _ffi.string(c.comment) - for c in _curves} + ELLIPTIC_CURVE_DESCRIPTIONS = dict((c.nid, _ffi.string(c.comment)) + for c in _curves) del _num_curves del _curves @@ -751,10 +751,10 @@ def load_tmp_dh(self, dhfile): def set_tmp_ecdh_by_curve_name(self, curve_name): """ - Configure this connection to people to use Elliptical Curve - Diffie-Hellman key exchanges. + Select a curve to use for ECDHE key exchange. :param curve_name: One of the named curve constants. + :type curve_name: int :return: None """ if _lib.Cryptography_HAS_EC: From f05a273f1bd6a083cb922cc3c5565ad01cbc05e0 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Thu, 13 Mar 2014 17:22:25 -0700 Subject: [PATCH 10/45] Identify elliptic curves by short name, not NID Using NIDs is awkward and requires updating pyOpenSSL every time a new curve is added. This approach avoids needing to update pyOpenSSL each time a new curve is added, and it results in more readable code and a more readable dict ELLIPTIC_CURVE_DESCRIPTIONS. --- OpenSSL/SSL.py | 20 +++++++++++++++----- OpenSSL/test/test_ssl.py | 27 +++++++++++++++++---------- doc/api/ssl.rst | 34 ++++++++++++++++++---------------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 602a98c8f..f6d62d406 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -271,7 +271,8 @@ class _memoryview(object): _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((c.nid, _ffi.string(c.comment)) + ELLIPTIC_CURVE_DESCRIPTIONS = dict((_ffi.string(_lib.OBJ_nid2sn(c.nid)), + _ffi.string(c.comment)) for c in _curves) del _num_curves del _curves @@ -749,16 +750,25 @@ def load_tmp_dh(self, dhfile): _lib.SSL_CTX_set_tmp_dh(self._context, dh) - def set_tmp_ecdh_by_curve_name(self, curve_name): + def set_tmp_ecdh_curve(self, curve_name): """ Select a curve to use for ECDHE key exchange. - :param curve_name: One of the named curve constants. - :type curve_name: int + The valid values of *curve_name* are the keys in + :py:data:OpenSSL.SSL.ELLIPTIC_CURVE_DESCRIPTIONS. + + Raises a ``ValueError`` if the linked OpenSSL was not compiled with + elliptical curve support, or the specified curve is not available. + + :param curve_name: The 'short name' of a curve, e.g. 'prime256v1' + :type curve_name: str :return: None """ if _lib.Cryptography_HAS_EC: - ecdh = _lib.EC_KEY_new_by_curve_name(curve_name) + nid = _lib.OBJ_sn2nid(curve_name) + if nid == _lib.NID_undef: + raise ValueError("No such OpenSSL object '%s'" % curve_name) + ecdh = _lib.EC_KEY_new_by_curve_name(nid) if ecdh == _ffi.NULL: raise ValueError( "OpenSSL could not load the requested elliptic curve" diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index 2686d120d..0fc8f2979 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -35,7 +35,8 @@ 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 NID_X9_62_prime256v1, _Cryptography_HAS_EC +from OpenSSL.SSL import ( + _Cryptography_HAS_EC, ELLIPTIC_CURVE_DESCRIPTIONS) from OpenSSL.SSL import ( Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) @@ -1173,15 +1174,21 @@ def test_load_tmp_dh(self): # XXX What should I assert here? -exarkun - if _Cryptography_HAS_EC: - def test_set_tmp_ecdh_by_curve_name(self): - """ - :py:obj:`Context.set_tmp_ecdh_by_curve_name` sets the Eliptical - Curve for Diffie-Hellman by the named curve. - """ - context = Context(TLSv1_METHOD) - context.set_tmp_ecdh_by_curve_name(NID_X9_62_prime256v1) - # XXX What should I assert here? -alex + def test_set_tmp_ecdh_curve(self): + """ + :py:obj:`Context.set_tmp_ecdh_curve` sets the Eliptical + Curve for Diffie-Hellman by the named curve. + """ + context = Context(TLSv1_METHOD) + for curve in ELLIPTIC_CURVE_DESCRIPTIONS.keys(): + context.set_tmp_ecdh_curve(curve) # Must not throw. + + if _Cryptography_HAS_EC: + # If EC is compiled in, there must be at least one curve + # Tn theory there could be an OpenSSL that violates this + # assumption. If so, this test will fail and we'll find + # out. + self.assertTrue(ELLIPTIC_CURVE_DESCRIPTIONS) def test_set_cipher_list_bytes(self): diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index 76fb0ad06..f169cbc09 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -116,16 +116,15 @@ Context, Connection. .. versionadded:: 0.14 -.. py:data:: NID_X9_62_prime192v1 - NID_X9_62_prime192v2 - NID_X9_62_prime192v3 - NID_X9_62_prime239v1 - NID_X9_62_prime239v2 - NID_X9_62_prime239v3 - NID_X9_62_prime256v1 +.. py:data:: ELLIPTIC_CURVE_DESCRIPTIONS - Constants used with :py:meth:`Context.set_tmp_ecdh_by_curve_name` to - specify which elliptical curve should be used. + 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 @@ -334,16 +333,19 @@ Context objects have the following methods: Load parameters for Ephemeral Diffie-Hellman from *dhfile*. -.. py:method:: Context.set_tmp_ecdh_by_curve_name(curve_name) +.. py:method:: Context.set_tmp_ecdh_curve(curve_name) + + Select a curve to use for ECDHE key exchange. - Configure this connection to people to use Elliptical Curve Diffie-Hellman - key exchanges. + The valid values of *curve_name* are the keys in + :py:data:`ELLIPTIC_CURVE_DESCRIPTIONS`. - ``curve_name`` should be one of the named curve constants, such as - :py:data:`NID_X9_62_prime256v1`. + Raises a ``ValueError`` if the linked OpenSSL was not compiled with + elliptical curve support, or the specified curve is not available. - Raises a ``ValueError`` if the linked OpenSSL was not compiled with - elliptical curve support, or the specified curve is not available. + :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) From 4064ea18c2a68bb5d6e82f40be7ecddc0752d6f9 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Thu, 13 Mar 2014 17:40:54 -0700 Subject: [PATCH 11/45] Stop exposing all the NID and SN objects for elliptic curves, now that we access them by name. --- OpenSSL/SSL.py | 144 ------------------------------------------------- 1 file changed, 144 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index f6d62d406..2229ccfd1 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -121,150 +121,6 @@ class _memoryview(object): SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE - -NID_X9_62_c2pnb163v1 = _lib.NID_X9_62_c2pnb163v1 -SN_X9_62_c2pnb163v1 = _ffi.string(_lib.SN_X9_62_c2pnb163v1) -NID_X9_62_c2pnb163v2 = _lib.NID_X9_62_c2pnb163v2 -SN_X9_62_c2pnb163v2 = _ffi.string(_lib.SN_X9_62_c2pnb163v2) -NID_X9_62_c2pnb163v3 = _lib.NID_X9_62_c2pnb163v3 -SN_X9_62_c2pnb163v3 = _ffi.string(_lib.SN_X9_62_c2pnb163v3) -NID_X9_62_c2pnb176v1 = _lib.NID_X9_62_c2pnb176v1 -SN_X9_62_c2pnb176v1 = _ffi.string(_lib.SN_X9_62_c2pnb176v1) -NID_X9_62_c2tnb191v1 = _lib.NID_X9_62_c2tnb191v1 -SN_X9_62_c2tnb191v1 = _ffi.string(_lib.SN_X9_62_c2tnb191v1) -NID_X9_62_c2tnb191v2 = _lib.NID_X9_62_c2tnb191v2 -SN_X9_62_c2tnb191v2 = _ffi.string(_lib.SN_X9_62_c2tnb191v2) -NID_X9_62_c2tnb191v3 = _lib.NID_X9_62_c2tnb191v3 -SN_X9_62_c2tnb191v3 = _ffi.string(_lib.SN_X9_62_c2tnb191v3) -NID_X9_62_c2onb191v4 = _lib.NID_X9_62_c2onb191v4 -SN_X9_62_c2onb191v4 = _ffi.string(_lib.SN_X9_62_c2onb191v4) -NID_X9_62_c2onb191v5 = _lib.NID_X9_62_c2onb191v5 -SN_X9_62_c2onb191v5 = _ffi.string(_lib.SN_X9_62_c2onb191v5) -NID_X9_62_c2pnb208w1 = _lib.NID_X9_62_c2pnb208w1 -SN_X9_62_c2pnb208w1 = _ffi.string(_lib.SN_X9_62_c2pnb208w1) -NID_X9_62_c2tnb239v1 = _lib.NID_X9_62_c2tnb239v1 -SN_X9_62_c2tnb239v1 = _ffi.string(_lib.SN_X9_62_c2tnb239v1) -NID_X9_62_c2tnb239v2 = _lib.NID_X9_62_c2tnb239v2 -SN_X9_62_c2tnb239v2 = _ffi.string(_lib.SN_X9_62_c2tnb239v2) -NID_X9_62_c2tnb239v3 = _lib.NID_X9_62_c2tnb239v3 -SN_X9_62_c2tnb239v3 = _ffi.string(_lib.SN_X9_62_c2tnb239v3) -NID_X9_62_c2onb239v4 = _lib.NID_X9_62_c2onb239v4 -SN_X9_62_c2onb239v4 = _ffi.string(_lib.SN_X9_62_c2onb239v4) -NID_X9_62_c2onb239v5 = _lib.NID_X9_62_c2onb239v5 -SN_X9_62_c2onb239v5 = _ffi.string(_lib.SN_X9_62_c2onb239v5) -NID_X9_62_c2pnb272w1 = _lib.NID_X9_62_c2pnb272w1 -SN_X9_62_c2pnb272w1 = _ffi.string(_lib.SN_X9_62_c2pnb272w1) -NID_X9_62_c2pnb304w1 = _lib.NID_X9_62_c2pnb304w1 -SN_X9_62_c2pnb304w1 = _ffi.string(_lib.SN_X9_62_c2pnb304w1) -NID_X9_62_c2tnb359v1 = _lib.NID_X9_62_c2tnb359v1 -SN_X9_62_c2tnb359v1 = _ffi.string(_lib.SN_X9_62_c2tnb359v1) -NID_X9_62_c2pnb368w1 = _lib.NID_X9_62_c2pnb368w1 -SN_X9_62_c2pnb368w1 = _ffi.string(_lib.SN_X9_62_c2pnb368w1) -NID_X9_62_c2tnb431r1 = _lib.NID_X9_62_c2tnb431r1 -SN_X9_62_c2tnb431r1 = _ffi.string(_lib.SN_X9_62_c2tnb431r1) -NID_X9_62_prime192v1 = _lib.NID_X9_62_prime192v1 -SN_X9_62_prime192v1 = _ffi.string(_lib.SN_X9_62_prime192v1) -NID_X9_62_prime192v2 = _lib.NID_X9_62_prime192v2 -SN_X9_62_prime192v2 = _ffi.string(_lib.SN_X9_62_prime192v2) -NID_X9_62_prime192v3 = _lib.NID_X9_62_prime192v3 -SN_X9_62_prime192v3 = _ffi.string(_lib.SN_X9_62_prime192v3) -NID_X9_62_prime239v1 = _lib.NID_X9_62_prime239v1 -SN_X9_62_prime239v1 = _ffi.string(_lib.SN_X9_62_prime239v1) -NID_X9_62_prime239v2 = _lib.NID_X9_62_prime239v2 -SN_X9_62_prime239v2 = _ffi.string(_lib.SN_X9_62_prime239v2) -NID_X9_62_prime239v3 = _lib.NID_X9_62_prime239v3 -SN_X9_62_prime239v3 = _ffi.string(_lib.SN_X9_62_prime239v3) -NID_X9_62_prime256v1 = _lib.NID_X9_62_prime256v1 -SN_X9_62_prime256v1 = _ffi.string(_lib.SN_X9_62_prime256v1) -NID_secp112r1 = _lib.NID_secp112r1 -SN_secp112r1 = _ffi.string(_lib.SN_secp112r1) -NID_secp112r2 = _lib.NID_secp112r2 -SN_secp112r2 = _ffi.string(_lib.SN_secp112r2) -NID_secp128r1 = _lib.NID_secp128r1 -SN_secp128r1 = _ffi.string(_lib.SN_secp128r1) -NID_secp128r2 = _lib.NID_secp128r2 -SN_secp128r2 = _ffi.string(_lib.SN_secp128r2) -NID_secp160k1 = _lib.NID_secp160k1 -SN_secp160k1 = _ffi.string(_lib.SN_secp160k1) -NID_secp160r1 = _lib.NID_secp160r1 -SN_secp160r1 = _ffi.string(_lib.SN_secp160r1) -NID_secp160r2 = _lib.NID_secp160r2 -SN_secp160r2 = _ffi.string(_lib.SN_secp160r2) -NID_sect163k1 = _lib.NID_sect163k1 -SN_sect163k1 = _ffi.string(_lib.SN_sect163k1) -NID_sect163r1 = _lib.NID_sect163r1 -SN_sect163r1 = _ffi.string(_lib.SN_sect163r1) -NID_sect163r2 = _lib.NID_sect163r2 -SN_sect163r2 = _ffi.string(_lib.SN_sect163r2) -NID_secp192k1 = _lib.NID_secp192k1 -SN_secp192k1 = _ffi.string(_lib.SN_secp192k1) -NID_secp224k1 = _lib.NID_secp224k1 -SN_secp224k1 = _ffi.string(_lib.SN_secp224k1) -NID_secp224r1 = _lib.NID_secp224r1 -SN_secp224r1 = _ffi.string(_lib.SN_secp224r1) -NID_secp256k1 = _lib.NID_secp256k1 -SN_secp256k1 = _ffi.string(_lib.SN_secp256k1) -NID_secp384r1 = _lib.NID_secp384r1 -SN_secp384r1 = _ffi.string(_lib.SN_secp384r1) -NID_secp521r1 = _lib.NID_secp521r1 -SN_secp521r1 = _ffi.string(_lib.SN_secp521r1) -NID_sect113r1 = _lib.NID_sect113r1 -SN_sect113r1 = _ffi.string(_lib.SN_sect113r1) -NID_sect113r2 = _lib.NID_sect113r2 -SN_sect113r2 = _ffi.string(_lib.SN_sect113r2) -NID_sect131r1 = _lib.NID_sect131r1 -SN_sect131r1 = _ffi.string(_lib.SN_sect131r1) -NID_sect131r2 = _lib.NID_sect131r2 -SN_sect131r2 = _ffi.string(_lib.SN_sect131r2) -NID_sect193r1 = _lib.NID_sect193r1 -SN_sect193r1 = _ffi.string(_lib.SN_sect193r1) -NID_sect193r2 = _lib.NID_sect193r2 -SN_sect193r2 = _ffi.string(_lib.SN_sect193r2) -NID_sect233k1 = _lib.NID_sect233k1 -SN_sect233k1 = _ffi.string(_lib.SN_sect233k1) -NID_sect233r1 = _lib.NID_sect233r1 -SN_sect233r1 = _ffi.string(_lib.SN_sect233r1) -NID_sect239k1 = _lib.NID_sect239k1 -SN_sect239k1 = _ffi.string(_lib.SN_sect239k1) -NID_sect283k1 = _lib.NID_sect283k1 -SN_sect283k1 = _ffi.string(_lib.SN_sect283k1) -NID_sect283r1 = _lib.NID_sect283r1 -SN_sect283r1 = _ffi.string(_lib.SN_sect283r1) -NID_sect409k1 = _lib.NID_sect409k1 -SN_sect409k1 = _ffi.string(_lib.SN_sect409k1) -NID_sect409r1 = _lib.NID_sect409r1 -SN_sect409r1 = _ffi.string(_lib.SN_sect409r1) -NID_sect571k1 = _lib.NID_sect571k1 -SN_sect571k1 = _ffi.string(_lib.SN_sect571k1) -NID_sect571r1 = _lib.NID_sect571r1 -SN_sect571r1 = _ffi.string(_lib.SN_sect571r1) -NID_wap_wsg_idm_ecid_wtls1 = _lib.NID_wap_wsg_idm_ecid_wtls1 -SN_wap_wsg_idm_ecid_wtls1 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls1) -NID_wap_wsg_idm_ecid_wtls3 = _lib.NID_wap_wsg_idm_ecid_wtls3 -SN_wap_wsg_idm_ecid_wtls3 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls3) -NID_wap_wsg_idm_ecid_wtls4 = _lib.NID_wap_wsg_idm_ecid_wtls4 -SN_wap_wsg_idm_ecid_wtls4 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls4) -NID_wap_wsg_idm_ecid_wtls5 = _lib.NID_wap_wsg_idm_ecid_wtls5 -SN_wap_wsg_idm_ecid_wtls5 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls5) -NID_wap_wsg_idm_ecid_wtls6 = _lib.NID_wap_wsg_idm_ecid_wtls6 -SN_wap_wsg_idm_ecid_wtls6 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls6) -NID_wap_wsg_idm_ecid_wtls7 = _lib.NID_wap_wsg_idm_ecid_wtls7 -SN_wap_wsg_idm_ecid_wtls7 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls7) -NID_wap_wsg_idm_ecid_wtls8 = _lib.NID_wap_wsg_idm_ecid_wtls8 -SN_wap_wsg_idm_ecid_wtls8 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls8) -NID_wap_wsg_idm_ecid_wtls9 = _lib.NID_wap_wsg_idm_ecid_wtls9 -SN_wap_wsg_idm_ecid_wtls9 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls9) -NID_wap_wsg_idm_ecid_wtls10 = _lib.NID_wap_wsg_idm_ecid_wtls10 -SN_wap_wsg_idm_ecid_wtls10 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls10) -NID_wap_wsg_idm_ecid_wtls11 = _lib.NID_wap_wsg_idm_ecid_wtls11 -SN_wap_wsg_idm_ecid_wtls11 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls11) -NID_wap_wsg_idm_ecid_wtls12 = _lib.NID_wap_wsg_idm_ecid_wtls12 -SN_wap_wsg_idm_ecid_wtls12 = _ffi.string(_lib.SN_wap_wsg_idm_ecid_wtls12) -NID_ipsec3 = _lib.NID_ipsec3 -SN_ipsec3 = _ffi.string(_lib.SN_ipsec3) -NID_ipsec4 = _lib.NID_ipsec4 -SN_ipsec4 = _ffi.string(_lib.SN_ipsec4) - _Cryptography_HAS_EC = _lib.Cryptography_HAS_EC ELLIPTIC_CURVE_DESCRIPTIONS = {} # In case there's no EC support if _Cryptography_HAS_EC: From 63e99fe6f9ee799ae9914a3704b1fe540cc8a4b7 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Fri, 28 Mar 2014 09:54:37 +0100 Subject: [PATCH 12/45] Require cryptography>=0.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 058ad7bc3..f12714d5b 100755 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ maintainer_email = 'exarkun@twistedmatrix.com', url = 'https://github.com/pyca/pyopenssl', license = 'APL2', - install_requires=["cryptography>=0.2.1", "six>=1.5.2"], + install_requires=["cryptography>=0.3", "six>=1.5.2"], long_description = """\ High-level wrapper around a subset of the OpenSSL library, includes * SSL.Connection objects, wrapping the methods of Python's portable From f5df243ea7262d3b850265d7fb6bd776969270fc Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Thu, 3 Apr 2014 12:17:28 +0200 Subject: [PATCH 13/45] Move test that checks for curve descriptions to a separate test This also ninja-fixes the typo that was in the previous comment. --- OpenSSL/test/test_ssl.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index 0fc8f2979..b07514f03 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -1183,12 +1183,18 @@ def test_set_tmp_ecdh_curve(self): for curve in ELLIPTIC_CURVE_DESCRIPTIONS.keys(): context.set_tmp_ecdh_curve(curve) # Must not throw. + + 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: - # If EC is compiled in, there must be at least one curve - # Tn theory there could be an OpenSSL that violates this - # assumption. If so, this test will fail and we'll find - # out. - self.assertTrue(ELLIPTIC_CURVE_DESCRIPTIONS) + self.assertNotEqual(len(ELLIPTIC_CURVE_DESCRIPTIONS), 0) def test_set_cipher_list_bytes(self): From 5fb416adb91d46945f82cd9b053a8daf7a5ac030 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Fri, 4 Apr 2014 12:19:52 -0700 Subject: [PATCH 14/45] ecdh: curve names are strings, not bytes --- OpenSSL/SSL.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index e6a629b15..5b748ee8c 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -130,9 +130,10 @@ class _memoryview(object): _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)), - _ffi.string(c.comment)) - for c in _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 @@ -625,7 +626,7 @@ def set_tmp_ecdh_curve(self, curve_name): :return: None """ if _lib.Cryptography_HAS_EC: - nid = _lib.OBJ_sn2nid(curve_name) + nid = _lib.OBJ_sn2nid(curve_name.encode('ascii')) if nid == _lib.NID_undef: raise ValueError("No such OpenSSL object '%s'" % curve_name) ecdh = _lib.EC_KEY_new_by_curve_name(nid) From 61d7d393fb8ef50deed4424db94e3ea72f95c4c2 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Fri, 4 Apr 2014 12:16:56 -0700 Subject: [PATCH 15/45] ecdh: Use different exception types for different errors This sneakily fixes some test cases typos, too. --- OpenSSL/SSL.py | 45 +++++++++++++++++++++++++++++++++------- OpenSSL/test/test_ssl.py | 43 +++++++++++++++++++++++++++++++++++--- doc/api/ssl.rst | 6 ++++-- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 5b748ee8c..a3a525e45 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -230,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. @@ -618,8 +649,10 @@ def set_tmp_ecdh_curve(self, curve_name): The valid values of *curve_name* are the keys in :py:data:OpenSSL.SSL.ELLIPTIC_CURVE_DESCRIPTIONS. - Raises a ``ValueError`` if the linked OpenSSL was not compiled with - elliptical curve support, or the specified curve is not available. + 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 @@ -628,16 +661,14 @@ def set_tmp_ecdh_curve(self, curve_name): if _lib.Cryptography_HAS_EC: nid = _lib.OBJ_sn2nid(curve_name.encode('ascii')) if nid == _lib.NID_undef: - raise ValueError("No such OpenSSL object '%s'" % curve_name) + raise UnknownObject(curve_name) ecdh = _lib.EC_KEY_new_by_curve_name(nid) if ecdh == _ffi.NULL: - raise ValueError( - "OpenSSL could not load the requested elliptic curve" - ) + raise UnsupportedEllipticCurve(sn) _lib.SSL_CTX_set_tmp_ecdh(self._context, ecdh) _lib.EC_KEY_free(ecdh) else: - raise ValueError("OpenSSL is compiled without ECDH support") + raise ECNotAvailable() def set_cipher_list(self, cipher_list): diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index b07514f03..a1cb41728 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -36,7 +36,8 @@ 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) + _Cryptography_HAS_EC, ELLIPTIC_CURVE_DESCRIPTIONS, + ECNotAvailable, UnknownObject) from OpenSSL.SSL import ( Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) @@ -1176,14 +1177,50 @@ def test_load_tmp_dh(self): def test_set_tmp_ecdh_curve(self): """ - :py:obj:`Context.set_tmp_ecdh_curve` sets the Eliptical - Curve for Diffie-Hellman by the named curve. + :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_bad_sn(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. It raises + :py:obj:`ECNotAvailable` if EC is not available at all. + """ + 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.assertFalse(True) + + + 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.assertFalse(True) + + def test_has_curve_descriptions(self): """ If the underlying cryptography bindings claim to have elliptic diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index b581450bc..54d790ca8 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -334,8 +334,10 @@ Context objects have the following methods: The valid values of *curve_name* are the keys in :py:data:`ELLIPTIC_CURVE_DESCRIPTIONS`. - Raises a ``ValueError`` if the linked OpenSSL was not compiled with - elliptical curve support, or the specified curve is not available. + 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 From 594b980d93079cbc9472c147d43a94d5dfd0156d Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 15:21:48 -0400 Subject: [PATCH 16/45] Try reporting the version of OpenSSL that was used at the end of each build. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 595e37cb6..1cde8c424 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,6 +67,8 @@ script: coverage run --branch --source=OpenSSL setup.py bdist_wheel test - | coverage report -m + - | + python -c "import OpenSSL.SSL; print OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION)" after_success: - coveralls From b41d1f448cd44753387f3145a34cc6533e92835b Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:02:04 -0400 Subject: [PATCH 17/45] Add a test for the False case of Cryptography_HAS_EC. --- OpenSSL/test/test_ssl.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index ca896d98a..b4f81b4fe 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -21,6 +21,7 @@ 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 @@ -1185,6 +1186,23 @@ def test_set_tmp_ecdh_curve(self): 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. + """ + self.addCleanup( + setattr, + _lib, "Cryptography_HAS_EC", _lib.Cryptography_HAS_EC) + _lib.Cryptography_HAS_EC = False + + context = Context(TLSv1_METHOD) + self.assertRaises( + ECNotAvailable, + context.set_tmp_ecdh_curve, next(iter(ELLIPTIC_CURVE_DESCRIPTIONS))) + + def test_set_tmp_ecdh_curve_bad_sn(self): """ :py:obj:`Context.set_tmp_ecdh_curve` raises From 3dfd45111b4e6cba89908626ab18e73506a35462 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:02:19 -0400 Subject: [PATCH 18/45] Some docstring cleanups. --- OpenSSL/test/test_ssl.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index b4f81b4fe..d2fb3a431 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -1178,8 +1178,8 @@ def test_load_tmp_dh(self): 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. + :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(): @@ -1205,10 +1205,9 @@ def test_set_tmp_ecdh_curve_not_available(self): def test_set_tmp_ecdh_curve_bad_sn(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. It raises - :py:obj:`ECNotAvailable` if EC is not available at all. + :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: @@ -1224,8 +1223,8 @@ def test_set_tmp_ecdh_curve_bad_sn(self): 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:`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) From 5c7b52f9ed2d5abc1f21f61e0f107e8205db1e7b Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:02:51 -0400 Subject: [PATCH 19/45] explicitly fail with a useful message --- OpenSSL/test/test_ssl.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index d2fb3a431..db33afb3d 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -1217,7 +1217,9 @@ def test_set_tmp_ecdh_curve_bad_sn(self): except UnknownObject: self.assertTrue(_Cryptography_HAS_EC) else: - self.assertFalse(True) + self.fail( + "set_tmp_ecdh_curve did not fail when called with " + "non-existent curve name") def test_set_tmp_ecdh_curve_not_a_curve(self): @@ -1235,7 +1237,9 @@ def test_set_tmp_ecdh_curve_not_a_curve(self): except UnknownObject: self.assertTrue(_Cryptography_HAS_EC) else: - self.assertFalse(True) + 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): From 32b59af04c551d442e941529d224048bb34de5db Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:13:00 -0400 Subject: [PATCH 20/45] Try explicitly installing the Python dependencies since "setup.py test" does not install them in a way that makes them available later. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1cde8c424..12d08fe31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,6 +63,8 @@ script: sudo apt-get -y update sudo apt-get install -y --force-yes libssl-dev/lucid fi + - | + pip install -e . - | coverage run --branch --source=OpenSSL setup.py bdist_wheel test - | From f11e3393c4f76ee27d9f213e352357098239243e Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:30:12 -0400 Subject: [PATCH 21/45] Factor the guts out so they can be unit tested separately. --- OpenSSL/SSL.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index f04f732a8..7187a9bd4 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -642,6 +642,23 @@ def load_tmp_dh(self, dhfile): _lib.SSL_CTX_set_tmp_dh(self._context, dh) + def _set_tmp_ecdh_curve_by_nid(self, nid): + """ + Select a curve to use by the OpenSSL NID associated with that curve. + + :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(sn) + _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. @@ -662,13 +679,8 @@ def set_tmp_ecdh_curve(self, curve_name): nid = _lib.OBJ_sn2nid(curve_name.encode('ascii')) if nid == _lib.NID_undef: raise UnknownObject(curve_name) - ecdh = _lib.EC_KEY_new_by_curve_name(nid) - if ecdh == _ffi.NULL: - raise UnsupportedEllipticCurve(sn) - _lib.SSL_CTX_set_tmp_ecdh(self._context, ecdh) - _lib.EC_KEY_free(ecdh) - else: - raise ECNotAvailable() + return self._set_tmp_ecdh_curve_by_nid(nid) + raise ECNotAvailable() def set_cipher_list(self, cipher_list): From 6749ec20140881ee2531e2bb5c87ae8e1dfd9a37 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:30:21 -0400 Subject: [PATCH 22/45] Fix some rST markup. --- OpenSSL/SSL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 7187a9bd4..be636ae88 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -1299,7 +1299,7 @@ def makefile(self): 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") From 689c489b124513be3bd0720b384f74d287ea1d76 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:42:49 -0400 Subject: [PATCH 23/45] Support for Python 2.6 - which lacks addCleanup --- OpenSSL/test/test_ssl.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index db33afb3d..5e9fd837c 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -1192,15 +1192,16 @@ def test_set_tmp_ecdh_curve_not_available(self): elliptic curve support is not available from the underlying OpenSSL version at all. """ - self.addCleanup( - setattr, - _lib, "Cryptography_HAS_EC", _lib.Cryptography_HAS_EC) - _lib.Cryptography_HAS_EC = False + 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))) + 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_sn(self): From 01787c44e1d78caad9bdd9d6854aa85ac6b75688 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 16:48:39 -0400 Subject: [PATCH 24/45] Change the version reporter to work on Python 3. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 12d08fe31..f5db00296 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,7 @@ script: - | coverage report -m - | - python -c "import OpenSSL.SSL; print OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION)" + python -c "import OpenSSL.SSL; print(OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION))" after_success: - coveralls From c48cd8177b3bf107073a2c075173722fdbeb809e Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 17:00:58 -0400 Subject: [PATCH 25/45] Add a test for the failure condition of EC_KEY_new_by_curve_name --- OpenSSL/SSL.py | 9 ++++++--- OpenSSL/test/test_ssl.py | 24 ++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index be636ae88..86410c0a8 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -642,10 +642,13 @@ def load_tmp_dh(self, dhfile): _lib.SSL_CTX_set_tmp_dh(self._context, dh) - def _set_tmp_ecdh_curve_by_nid(self, nid): + 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 @@ -654,7 +657,7 @@ def _set_tmp_ecdh_curve_by_nid(self, nid): """ ecdh = _lib.EC_KEY_new_by_curve_name(nid) if ecdh == _ffi.NULL: - raise UnsupportedEllipticCurve(sn) + raise UnsupportedEllipticCurve(name) _lib.SSL_CTX_set_tmp_ecdh(self._context, ecdh) _lib.EC_KEY_free(ecdh) @@ -679,7 +682,7 @@ def set_tmp_ecdh_curve(self, curve_name): 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(nid) + return self._set_tmp_ecdh_curve_by_nid(curve_name, nid) raise ECNotAvailable() diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index 5e9fd837c..beb5d2809 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -38,7 +38,7 @@ SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_NO_INTERNAL) from OpenSSL.SSL import ( _Cryptography_HAS_EC, ELLIPTIC_CURVE_DESCRIPTIONS, - ECNotAvailable, UnknownObject) + ECNotAvailable, UnknownObject, UnsupportedEllipticCurve) from OpenSSL.SSL import ( Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) @@ -1204,7 +1204,7 @@ def test_set_tmp_ecdh_curve_not_available(self): _lib.Cryptography_HAS_EC = has_ec - def test_set_tmp_ecdh_curve_bad_sn(self): + 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 @@ -1223,6 +1223,26 @@ def test_set_tmp_ecdh_curve_bad_sn(self): "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 From cc30c93a5fa15490820b5786b2d336a2c4dde5f1 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 17:06:50 -0400 Subject: [PATCH 26/45] Some versions of Python do not support the unicode literal syntax. --- OpenSSL/test/test_ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index beb5d2809..fdeff9dfb 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -1233,7 +1233,7 @@ def test_set_tmp_ecdh_curve_bad_nid(self): context = Context(TLSv1_METHOD) try: context._set_tmp_ecdh_curve_by_nid( - u"curve", _lib.OBJ_sn2nid(b"sha256")) + u("curve"), _lib.OBJ_sn2nid(b"sha256")) except UnsupportedEllipticCurve: pass else: From f43678bc99019f55908752f9885f680cd70d42ca Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 17 Apr 2014 18:54:32 -0400 Subject: [PATCH 27/45] Get rid of some top-level code, remove the code that reflects a weird edge case (but the weird edge case is still there inside OpenSSL), and add a bit of docs. --- OpenSSL/SSL.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 86410c0a8..e5ae08552 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -124,18 +124,29 @@ class _memoryview(object): 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 +def _get_elliptic_curves(lib): + """ + Load the names of the supported elliptic curves from OpenSSL. + + :param lib: The OpenSSL library binding object. + :return: A set of :py:obj:`unicode` giving the names of the elliptic curves + the underlying library supports. + """ + if lib.Cryptography_HAS_EC: + num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) + builtin_curves = _ffi.new('EC_builtin_curve[]', num_curves) + # The return value on this call should be num_curves again. We could + # check it to make sure but if it *isn't* then.. what could we do? + # Abort the whole process, I suppose...? -exarkun + lib.EC_get_builtin_curves(builtin_curves, num_curves) + return set( + _ffi.string(lib.OBJ_nid2sn(c.nid)).decode("ascii") + for c in builtin_curves) + else: + return set() + +ELLIPTIC_CURVES = _get_elliptic_curves() + class Error(Exception): From c09fd581772ebe9d30bcf6b24665b4b75175029b Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Fri, 18 Apr 2014 22:00:10 -0400 Subject: [PATCH 28/45] Switch to an explicit curve object. Happily, this eliminates just about all of the error cases. --- OpenSSL/SSL.py | 66 ++--------------------- OpenSSL/crypto.py | 69 ++++++++++++++++++++++++ OpenSSL/test/test_crypto.py | 81 +++++++++++++++++++++++++++- OpenSSL/test/test_ssl.py | 102 +++--------------------------------- doc/api/crypto.rst | 22 ++++++++ doc/api/ssl.rst | 27 ++-------- leakcheck/crypto.py | 18 ++++++- 7 files changed, 203 insertions(+), 182 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index e5ae08552..c5b233ba4 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -124,31 +124,6 @@ class _memoryview(object): SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE -def _get_elliptic_curves(lib): - """ - Load the names of the supported elliptic curves from OpenSSL. - - :param lib: The OpenSSL library binding object. - :return: A set of :py:obj:`unicode` giving the names of the elliptic curves - the underlying library supports. - """ - if lib.Cryptography_HAS_EC: - num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) - builtin_curves = _ffi.new('EC_builtin_curve[]', num_curves) - # The return value on this call should be num_curves again. We could - # check it to make sure but if it *isn't* then.. what could we do? - # Abort the whole process, I suppose...? -exarkun - lib.EC_get_builtin_curves(builtin_curves, num_curves) - return set( - _ffi.string(lib.OBJ_nid2sn(c.nid)).decode("ascii") - for c in builtin_curves) - else: - return set() - -ELLIPTIC_CURVES = _get_elliptic_curves() - - - class Error(Exception): """ An error occurred in an `OpenSSL.SSL` API. @@ -653,48 +628,17 @@ def load_tmp_dh(self, dhfile): _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): + def set_tmp_ecdh_curve(self, curve): """ 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: A curve object to use as returned by either + :py:meth:`OpenSSL.crypto.get_elliptic_curve` or + :py:meth:`OpenSSL.crypto.get_elliptic_curves`. - :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() + _lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY()) def set_cipher_list(self, cipher_list): diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 65e28d792..948402c84 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -263,6 +263,75 @@ def bits(self): +class _EllipticCurve(object): + """ + :ivar _lib: The :py:mod:`cryptography` binding instance used to interface + with OpenSSL. + + :ivar _nid: The OpenSSL NID identifying the curve this object represents. + :type _nid: :py:class:`int` + + :ivar name: The OpenSSL short name identifying the curve this object + represents. + :type name: :py:class:`unicode` + """ + @classmethod + def _get_elliptic_curves(cls, lib): + """ + Load the names of the supported elliptic curves from OpenSSL. + + :param lib: The OpenSSL library binding object. + :return: A set of :py:obj:`unicode` giving the names of the elliptic curves + the underlying library supports. + """ + if lib.Cryptography_HAS_EC: + num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) + builtin_curves = _ffi.new('EC_builtin_curve[]', num_curves) + # The return value on this call should be num_curves again. We could + # check it to make sure but if it *isn't* then.. what could we do? + # Abort the whole process, I suppose...? -exarkun + lib.EC_get_builtin_curves(builtin_curves, num_curves) + return set( + cls.from_nid(lib, c.nid) + for c in builtin_curves) + else: + return set() + + + @classmethod + def from_nid(cls, lib, nid): + return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii")) + + + def __init__(self, lib, nid, name): + self._lib = lib + self._nid = nid + self.name = name + + + def __repr__(self): + return "" % (self.name,) + + + def _to_EC_KEY(self): + key = self._lib.EC_KEY_new_by_curve_name(self._nid) + return _ffi.gc(key, _lib.EC_KEY_free) + + + +def get_elliptic_curves(): + return _EllipticCurve._get_elliptic_curves(_lib) + + + +def get_elliptic_curve(name): + for curve in get_elliptic_curves(): + if curve.name == name: + return curve + raise ValueError("unknown curve name", name) + + + class X509Name(object): def __init__(self, name): """ diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index a3685a969..b2889859a 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -25,9 +25,10 @@ from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12 from OpenSSL.crypto import CRL, Revoked, load_crl from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType -from OpenSSL.crypto import sign, verify +from OpenSSL.crypto import ( + sign, verify, get_elliptic_curve, get_elliptic_curves) from OpenSSL.test.util import TestCase, b -from OpenSSL._util import native +from OpenSSL._util import native, lib def normalize_certificate_pem(pem): return dump_certificate(FILETYPE_PEM, load_certificate(FILETYPE_PEM, pem)) @@ -3058,5 +3059,81 @@ def test_sign_nulls(self): verify(good_cert, sig, content, "sha1") + +class EllipticCurveTests(TestCase): + """ + Tests for :py:class:`_EllipticCurve`, :py:obj:`get_elliptic_curve`, and + :py:obj:`get_elliptic_curves`. + """ + def test_set(self): + """ + :py:obj:`get_elliptic_curves` returns a :py:obj:`set`. + """ + self.assertIsInstance(get_elliptic_curves(), set) + + + def test_some_curves(self): + """ + If :py:mod:`cryptography` has elliptic curve support then the set + returned by :py:obj:`get_elliptic_curves` has some elliptic curves in + it. + + There could be an OpenSSL that violates this assumption. If so, this + test will fail and we'll find out. + """ + curves = get_elliptic_curves() + if lib.Cryptography_HAS_EC: + self.assertTrue(curves) + else: + self.assertFalse(curves) + + + def test_a_curve(self): + """ + :py:obj:`get_elliptic_curve` can be used to retrieve a particular + supported curve. + """ + curves = get_elliptic_curves() + if curves: + curve = next(iter(curves)) + self.assertEqual(curve.name, get_elliptic_curve(curve.name).name) + else: + self.assertRaises(ValueError, get_elliptic_curve, u"prime256v1") + + + def test_not_a_curve(self): + """ + :py:obj:`get_elliptic_curve` raises :py:class:`ValueError` if called + with a name which does not identify a supported curve. + """ + self.assertRaises( + ValueError, get_elliptic_curve, u"this curve was just invented") + + + def test_repr(self): + """ + The string representation of a curve object includes simply states the + object is a curve and what its name is. + """ + curves = get_elliptic_curves() + if curves: + curve = next(iter(curves)) + self.assertEqual("" % (curve.name,), repr(curve)) + + + def test_to_EC_KEY(self): + """ + The curve object can export a version of itself as an EC_KEY* via the + private :py:meth:`_EllipticCurve._to_EC_KEY`. + """ + curves = get_elliptic_curves() + if curves: + curve = next(iter(curves)) + # It's not easy to assert anything about this object. However, see + # leakcheck/crypto.py for a test that demonstrates it at least does + # not leak memory. + curve._to_EC_KEY() + + if __name__ == '__main__': main() diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index fdeff9dfb..62311437d 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -20,6 +20,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.crypto import get_elliptic_curves from OpenSSL.SSL import _lib from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS @@ -36,9 +37,6 @@ 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) @@ -1179,101 +1177,13 @@ def test_load_tmp_dh(self): 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. + Diffie-Hellman to the specified curve. """ 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) + for curve in get_elliptic_curves(): + # The only easily "assertable" thing is that it does not raise an + # exception. + context.set_tmp_ecdh_curve(curve) def test_set_cipher_list_bytes(self): diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index ee93cfba9..974e516d3 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -119,6 +119,28 @@ Generic exception used in the :py:mod:`.crypto` module. +.. py:function:: get_elliptic_curves + + Return a set of objects representing the elliptic curves supported in the + OpenSSL build in use. + + The curve objects have a :py:class:`unicode` ``name`` attribute by which + they identifying themselves. + + The curve objects are useful as values for the argument accepted by + :py:meth:`Context.set_tmp_ecdh_curve` to specify which elliptical curve + should be used for ECDHE key exchange. + + +.. py:function:: get_elliptic_curve + + Return a single curve object selected by name. + + See :py:func:`get_elliptic_curves` for information about curve objects. + + If the named curve is not supported then :py:class:`ValueError` is raised. + + .. py:function:: dump_certificate(type, cert) Dump the certificate *cert* into a buffer string encoded with the type diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index b7eca7059..9016e9867 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -110,17 +110,6 @@ 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 @@ -327,21 +316,15 @@ 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. +.. py:method:: Context.set_tmp_ecdh_curve(curve) - The valid values of *curve_name* are the keys in - :py:data:`ELLIPTIC_CURVE_DESCRIPTIONS`. + Select a curve to use for ECDHE key exchange. - 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``. + The valid values of *curve* are the objects returned by + :py:func:`OpenSSL.crypto.get_elliptic_curves` or + :py:func:`OpenSSL.crypto.get_elliptic_curve`. - :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) diff --git a/leakcheck/crypto.py b/leakcheck/crypto.py index f5fe2f840..ca79b7ccc 100644 --- a/leakcheck/crypto.py +++ b/leakcheck/crypto.py @@ -5,7 +5,7 @@ from OpenSSL.crypto import ( FILETYPE_PEM, TYPE_DSA, Error, PKey, X509, load_privatekey, CRL, Revoked, - _X509_REVOKED_dup) + get_elliptic_curves, _X509_REVOKED_dup) from OpenSSL._util import lib as _lib @@ -145,6 +145,22 @@ def check_X509_REVOKED_dup(self): +class Checker_EllipticCurve(BaseChecker): + """ + Leak checks for :py:obj:`_EllipticCurve`. + """ + def check_to_EC_KEY(self): + """ + Repeatedly create an EC_KEY* from an :py:obj:`_EllipticCurve`. The + structure should be automatically garbage collected. + """ + curves = get_elliptic_curves() + if curves: + curve = next(iter(curves)) + for i in xrange(self.iterations * 1000): + curve._to_EC_KEY() + + def vmsize(): return [x for x in file('/proc/self/status').readlines() if 'VmSize' in x] From f49adaedc4a502f91086125dfcc07c9b4de86a45 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 08:47:31 -0400 Subject: [PATCH 29/45] revert to pyca/master .gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 276d4e85a..867434b8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -*.py[co] build dist -*.egg-info +*.egg-info \ No newline at end of file From 2065be5dcb2d288db660cfab51cd37c94735ccef Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 08:48:18 -0400 Subject: [PATCH 30/45] These three exceptions are no longer used. --- OpenSSL/SSL.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index c5b233ba4..a4b66ed4e 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -216,37 +216,6 @@ 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. From aaf516dc7780648fc43ec07f9896e7dffa22a699 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 09:10:45 -0400 Subject: [PATCH 31/45] Some doc fixups --- OpenSSL/crypto.py | 59 +++++++++++++++++++++++++++++++++++++++------- doc/api/crypto.rst | 2 +- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 948402c84..0c4b2f826 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -265,15 +265,7 @@ def bits(self): class _EllipticCurve(object): """ - :ivar _lib: The :py:mod:`cryptography` binding instance used to interface - with OpenSSL. - - :ivar _nid: The OpenSSL NID identifying the curve this object represents. - :type _nid: :py:class:`int` - - :ivar name: The OpenSSL short name identifying the curve this object - represents. - :type name: :py:class:`unicode` + A representation of a supported elliptic curve. """ @classmethod def _get_elliptic_curves(cls, lib): @@ -300,10 +292,35 @@ def _get_elliptic_curves(cls, lib): @classmethod def from_nid(cls, lib, nid): + """ + Instantiate a new :py:class:`_EllipticCurve` associated with the given + OpenSSL NID. + + :param lib: The OpenSSL library binding object. + + :param nid: The OpenSSL NID the resulting curve object will represent. + This must be a curve NID (and not, for example, a hash NID) or + subsequent operations will fail in unpredictable ways. + :type nid: :py:class:`int` + + :return: The curve object. + """ return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii")) def __init__(self, lib, nid, name): + """ + :param _lib: The :py:mod:`cryptography` binding instance used to + interface with OpenSSL. + + :param _nid: The OpenSSL NID identifying the curve this object + represents. + :type _nid: :py:class:`int` + + :param name: The OpenSSL short name identifying the curve this object + represents. + :type name: :py:class:`unicode` + """ self._lib = lib self._nid = nid self.name = name @@ -314,17 +331,41 @@ def __repr__(self): def _to_EC_KEY(self): + """ + Create a new OpenSSL EC_KEY structure initialized to use this curve. + + The structure is automatically garbage collected when the Python object + is garbage collected. + """ key = self._lib.EC_KEY_new_by_curve_name(self._nid) return _ffi.gc(key, _lib.EC_KEY_free) def get_elliptic_curves(): + """ + Return a set of objects representing the elliptic curves supported in the + OpenSSL build in use. + + The curve objects have a :py:class:`unicode` ``name`` attribute by which + they identify themselves. + + The curve objects are useful as values for the argument accepted by + :py:meth:`Context.set_tmp_ecdh_curve` to specify which elliptical curve + should be used for ECDHE key exchange. + """ return _EllipticCurve._get_elliptic_curves(_lib) def get_elliptic_curve(name): + """ + Return a single curve object selected by name. + + See :py:func:`get_elliptic_curves` for information about curve objects. + + If the named curve is not supported then :py:class:`ValueError` is raised. + """ for curve in get_elliptic_curves(): if curve.name == name: return curve diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index 974e516d3..15f46621d 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -125,7 +125,7 @@ OpenSSL build in use. The curve objects have a :py:class:`unicode` ``name`` attribute by which - they identifying themselves. + they identify themselves. The curve objects are useful as values for the argument accepted by :py:meth:`Context.set_tmp_ecdh_curve` to specify which elliptical curve From d5839e2a950d475b518023b23dee06862aa5ba77 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 09:26:44 -0400 Subject: [PATCH 32/45] strings are hard, be explicit --- OpenSSL/crypto.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 0c4b2f826..6e32ad116 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -364,6 +364,10 @@ def get_elliptic_curve(name): See :py:func:`get_elliptic_curves` for information about curve objects. + :param name: The OpenSSL short name identifying the curve object to + retrieve. + :type name: :py:class:`unicode` + If the named curve is not supported then :py:class:`ValueError` is raised. """ for curve in get_elliptic_curves(): From 3e4e33562d6f06ebe1205bd03425a9cc772a92d9 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 09:28:28 -0400 Subject: [PATCH 33/45] Mirror the OpenSSL API as is our idiom. --- OpenSSL/SSL.py | 2 +- OpenSSL/test/test_ssl.py | 6 +++--- doc/api/ssl.rst | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index a4b66ed4e..ec49c277a 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -597,7 +597,7 @@ def load_tmp_dh(self, dhfile): _lib.SSL_CTX_set_tmp_dh(self._context, dh) - def set_tmp_ecdh_curve(self, curve): + def set_tmp_ecdh(self, curve): """ Select a curve to use for ECDHE key exchange. diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index 62311437d..4c3b12a36 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -1174,16 +1174,16 @@ def test_load_tmp_dh(self): # XXX What should I assert here? -exarkun - def test_set_tmp_ecdh_curve(self): + def test_set_tmp_ecdh(self): """ - :py:obj:`Context.set_tmp_ecdh_curve` sets the elliptic curve for + :py:obj:`Context.set_tmp_ecdh` sets the elliptic curve for Diffie-Hellman to the specified curve. """ context = Context(TLSv1_METHOD) for curve in get_elliptic_curves(): # The only easily "assertable" thing is that it does not raise an # exception. - context.set_tmp_ecdh_curve(curve) + context.set_tmp_ecdh(curve) def test_set_cipher_list_bytes(self): diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index 9016e9867..a75af1f7d 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -317,7 +317,7 @@ Context objects have the following methods: Load parameters for Ephemeral Diffie-Hellman from *dhfile*. -.. py:method:: Context.set_tmp_ecdh_curve(curve) +.. py:method:: Context.set_tmp_ecdh(curve) Select a curve to use for ECDHE key exchange. From 3b04e35e18fb9cb6436bc0e41462c59d488d16d9 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 09:29:10 -0400 Subject: [PATCH 34/45] all the way --- OpenSSL/crypto.py | 4 ++-- doc/api/crypto.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 6e32ad116..380593b7b 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -351,8 +351,8 @@ def get_elliptic_curves(): they identify themselves. The curve objects are useful as values for the argument accepted by - :py:meth:`Context.set_tmp_ecdh_curve` to specify which elliptical curve - should be used for ECDHE key exchange. + :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be + used for ECDHE key exchange. """ return _EllipticCurve._get_elliptic_curves(_lib) diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index 15f46621d..b360e89c0 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -128,8 +128,8 @@ they identify themselves. The curve objects are useful as values for the argument accepted by - :py:meth:`Context.set_tmp_ecdh_curve` to specify which elliptical curve - should be used for ECDHE key exchange. + :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be + used for ECDHE key exchange. .. py:function:: get_elliptic_curve From eb86f3ab2d8d237e1369e9308e7df330fa1f8a9e Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 09:45:57 -0400 Subject: [PATCH 35/45] restore python 3.2 compatibility --- OpenSSL/test/test_crypto.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index b2889859a..ba0055f9c 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -11,7 +11,7 @@ from subprocess import PIPE, Popen from datetime import datetime, timedelta -from six import binary_type +from six import u, b, binary_type from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType @@ -27,7 +27,7 @@ from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType from OpenSSL.crypto import ( sign, verify, get_elliptic_curve, get_elliptic_curves) -from OpenSSL.test.util import TestCase, b +from OpenSSL.test.util import TestCase from OpenSSL._util import native, lib def normalize_certificate_pem(pem): @@ -3098,7 +3098,7 @@ def test_a_curve(self): curve = next(iter(curves)) self.assertEqual(curve.name, get_elliptic_curve(curve.name).name) else: - self.assertRaises(ValueError, get_elliptic_curve, u"prime256v1") + self.assertRaises(ValueError, get_elliptic_curve, u("prime256v1")) def test_not_a_curve(self): From 5a82db9e1d9215e4fc1308e9f5631f8c2ceb5170 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 09:51:29 -0400 Subject: [PATCH 36/45] restore even more python 3.2 compat! --- OpenSSL/test/test_crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index ba0055f9c..b37dc949a 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -3107,7 +3107,7 @@ def test_not_a_curve(self): with a name which does not identify a supported curve. """ self.assertRaises( - ValueError, get_elliptic_curve, u"this curve was just invented") + ValueError, get_elliptic_curve, u("this curve was just invented")) def test_repr(self): From 3611b4ea11f711c34b00714646e0e0202d60a2be Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 19 Apr 2014 18:34:06 -0400 Subject: [PATCH 37/45] ChangeLog --- ChangeLog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ChangeLog b/ChangeLog index 53fb1df40..6416e181e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2014-04-19 Jean-Paul Calderone + + * OpenSSL/crypto.py: Based on work from Alex Gaynor, Andrew + Lutomirski, Tobias Oberstein, Laurens Van Houtven, and Hynek + Schlawack, add ``get_elliptic_curve`` and ``get_elliptic_curves`` + to support TLS ECDHE modes. + * OpenSSL/SSL.py: Add ``Context.set_tmp_ecdh`` to configure a TLS + context with a particular elliptic curve for ECDHE modes. + 2014-04-19 Markus Unterwaditzer * OpenSSL/SSL.py: ``Connection.send`` and ``Connection.sendall`` From 9c7f069ab8d325957290ec85d99ea834c870680d Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 30 Apr 2014 18:17:19 -0400 Subject: [PATCH 38/45] Add a helper for testing equality. --- OpenSSL/test/util.py | 130 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/OpenSSL/test/util.py b/OpenSSL/test/util.py index 4e4d81240..c87ce099f 100644 --- a/OpenSSL/test/util.py +++ b/OpenSSL/test/util.py @@ -300,3 +300,133 @@ def assertConsistentType(self, theType, name, *constructionArgs): self.assertTrue(isinstance(theType, type)) instance = theType(*constructionArgs) self.assertIdentical(type(instance), theType) + + + +class EqualityTestsMixin(object): + """ + A mixin defining tests for the standard implementation of C{==} and C{!=}. + """ + def anInstance(self): + """ + Return an instance of the class under test. Each call to this method + must return a different object. All objects returned must be equal to + each other. + """ + raise NotImplementedError() + + + def anotherInstance(self): + """ + Return an instance of the class under test. Each call to this method + must return a different object. The objects must not be equal to the + objects returned by C{anInstance}. They may or may not be equal to + each other (they will not be compared against each other). + """ + raise NotImplementedError() + + + def test_identicalEq(self): + """ + An object compares equal to itself using the C{==} operator. + """ + o = self.anInstance() + self.assertTrue(o == o) + + + def test_identicalNe(self): + """ + An object doesn't compare not equal to itself using the C{!=} operator. + """ + o = self.anInstance() + self.assertFalse(o != o) + + + def test_sameEq(self): + """ + Two objects that are equal to each other compare equal to each other + using the C{==} operator. + """ + a = self.anInstance() + b = self.anInstance() + self.assertTrue(a == b) + + + def test_sameNe(self): + """ + Two objects that are equal to each other do not compare not equal to + each other using the C{!=} operator. + """ + a = self.anInstance() + b = self.anInstance() + self.assertFalse(a != b) + + + def test_differentEq(self): + """ + Two objects that are not equal to each other do not compare equal to + each other using the C{==} operator. + """ + a = self.anInstance() + b = self.anotherInstance() + self.assertFalse(a == b) + + + def test_differentNe(self): + """ + Two objects that are not equal to each other compare not equal to each + other using the C{!=} operator. + """ + a = self.anInstance() + b = self.anotherInstance() + self.assertTrue(a != b) + + + def test_anotherTypeEq(self): + """ + The object does not compare equal to an object of an unrelated type + (which does not implement the comparison) using the C{==} operator. + """ + a = self.anInstance() + b = object() + self.assertFalse(a == b) + + + def test_anotherTypeNe(self): + """ + The object compares not equal to an object of an unrelated type (which + does not implement the comparison) using the C{!=} operator. + """ + a = self.anInstance() + b = object() + self.assertTrue(a != b) + + + def test_delegatedEq(self): + """ + The result of comparison using C{==} is delegated to the right-hand + operand if it is of an unrelated type. + """ + class Delegate(object): + def __eq__(self, other): + # Do something crazy and obvious. + return [self] + + a = self.anInstance() + b = Delegate() + self.assertEqual(a == b, [b]) + + + def test_delegateNe(self): + """ + The result of comparison using C{!=} is delegated to the right-hand + operand if it is of an unrelated type. + """ + class Delegate(object): + def __ne__(self, other): + # Do something crazy and obvious. + return [self] + + a = self.anInstance() + b = Delegate() + self.assertEqual(a != b, [b]) From 1be7708ebd00177499955850f4c98b16e2ae7848 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 30 Apr 2014 18:17:41 -0400 Subject: [PATCH 39/45] Use that helper to define tests for equality of _EllipticCurve instances. --- OpenSSL/test/test_crypto.py | 43 ++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index b37dc949a..8dc19cfd4 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -27,7 +27,7 @@ from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType from OpenSSL.crypto import ( sign, verify, get_elliptic_curve, get_elliptic_curves) -from OpenSSL.test.util import TestCase +from OpenSSL.test.util import EqualityTestsMixin, TestCase from OpenSSL._util import native, lib def normalize_certificate_pem(pem): @@ -3135,5 +3135,46 @@ def test_to_EC_KEY(self): curve._to_EC_KEY() + +class EllipticCurveFactory(object): + """ + A helper to get the names of two curves. + """ + def __init__(self): + curves = iter(get_elliptic_curves()) + try: + self.curve_name = next(curves).name + self.another_curve_name = next(curves).name + except StopIteration: + self.curve_name = self.another_curve_name = None + + + +class EllipticCurveEqualityTests(TestCase, EqualityTestsMixin): + """ + Tests :py:type:`_EllipticCurve`\ 's implementation of ``==`` and ``!=``. + """ + curve_factory = EllipticCurveFactory() + + if curve_factory.curve_name is None: + skip = "There are no curves available there can be no curve objects." + + + def anInstance(self): + """ + Get the curve object for an arbitrary curve supported by the system. + """ + return get_elliptic_curve(self.curve_factory.curve_name) + + + def anotherInstance(self): + """ + Get the curve object for an arbitrary curve supported by the system - + but not the one returned by C{anInstance}. + """ + return get_elliptic_curve(self.curve_factory.another_curve_name) + + + if __name__ == '__main__': main() From 73945e3fbc15ad19fd098af00cbf31d4c4bbaf7a Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 30 Apr 2014 18:18:01 -0400 Subject: [PATCH 40/45] Cache the _EllipticCurve instances so the inherited-from-object equality does what we want. --- OpenSSL/crypto.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 380593b7b..1312c0a12 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -266,15 +266,23 @@ def bits(self): class _EllipticCurve(object): """ A representation of a supported elliptic curve. + + @cvar _curves: :py:obj:`None` until an attempt is made to load the curves. + Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve` + instances each of which represents one curve supported by the system. + @type _curves: :py:type:`NoneType` or :py:type:`set` """ + _curves = None + @classmethod - def _get_elliptic_curves(cls, lib): + def _load_elliptic_curves(cls, lib): """ - Load the names of the supported elliptic curves from OpenSSL. + Get the curves supported by OpenSSL. :param lib: The OpenSSL library binding object. - :return: A set of :py:obj:`unicode` giving the names of the elliptic curves - the underlying library supports. + + :return: A :py:type:`set` of ``cls`` instances giving the names of the + elliptic curves the underlying library supports. """ if lib.Cryptography_HAS_EC: num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) @@ -286,8 +294,22 @@ def _get_elliptic_curves(cls, lib): return set( cls.from_nid(lib, c.nid) for c in builtin_curves) - else: - return set() + return set() + + + @classmethod + def _get_elliptic_curves(cls, lib): + """ + Get, cache, and return the curves supported by OpenSSL. + + :param lib: The OpenSSL library binding object. + + :return: A :py:type:`set` of ``cls`` instances giving the names of the + elliptic curves the underlying library supports. + """ + if cls._curves is None: + cls._curves = cls._load_elliptic_curves(lib) + return cls._curves @classmethod From 22c3124d9535f0cdcb9199ad25cc25f99141dd8d Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 1 May 2014 07:49:47 -0400 Subject: [PATCH 41/45] Directly test that curves work well as set elements. --- OpenSSL/test/test_crypto.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 8dc19cfd4..34e60a3d9 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -3176,5 +3176,37 @@ def anotherInstance(self): +class EllipticCurveHashTests(TestCase): + """ + Tests for :py:type:`_EllipticCurve`\ 's implementation of hashing (thus use + as an item in a :py:type:`dict` or :py:type:`set`). + """ + curve_factory = EllipticCurveFactory() + + if curve_factory.curve_name is None: + skip = "There are no curves available there can be no curve objects." + + + def test_contains(self): + """ + The ``in`` operator reports that a :py:type:`set` containing a curve + does contain that curve. + """ + curve = get_elliptic_curve(self.curve_factory.curve_name) + curves = set([curve]) + self.assertIn(curve, curves) + + + def test_does_not_contain(self): + """ + The ``in`` operator reports that a :py:type:`set` not containing a + curve does not contain that curve. + """ + curve = get_elliptic_curve(self.curve_factory.curve_name) + curves = set([get_elliptic_curve(self.curve_factory.another_curve_name)]) + self.assertNotIn(curve, curves) + + + if __name__ == '__main__': main() From 15f3644811f0844965dee19978d15ef40911e333 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 1 May 2014 07:58:02 -0400 Subject: [PATCH 42/45] Add some Python 2.6 compatibility. --- OpenSSL/test/util.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/OpenSSL/test/util.py b/OpenSSL/test/util.py index c87ce099f..21bbdc45f 100644 --- a/OpenSSL/test/util.py +++ b/OpenSSL/test/util.py @@ -210,6 +210,23 @@ def failUnlessIn(self, containee, container, msg=None): return containee assertIn = failUnlessIn + def assertNotIn(self, containee, container, msg=None): + """ + Fail the test if C{containee} is found in C{container}. + + @param containee: the value that should not be in C{container} + @param container: a sequence type, or in the case of a mapping type, + will follow semantics of 'if key in dict.keys()' + @param msg: if msg is None, then the failure message will be + '%r in %r' % (first, second) + """ + if containee in container: + raise self.failureException(msg or "%r in %r" + % (containee, container)) + return containee + failIfIn = assertNotIn + + def failUnlessIdentical(self, first, second, msg=None): """ Fail the test if :py:data:`first` is not :py:data:`second`. This is an From 40da72daab527ed11684ee0749c292c3b7a3fdcd Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 1 May 2014 09:25:17 -0400 Subject: [PATCH 43/45] Try to work around the strange Python 3 behavior. --- OpenSSL/crypto.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 1312c0a12..d62ce84d1 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -274,6 +274,12 @@ class _EllipticCurve(object): """ _curves = None + def __ne__(self, other): + if isinstance(other, _EllipticCurve): + return super(_EllipticCurve, self).__ne__(other) + return NotImplemented + + @classmethod def _load_elliptic_curves(cls, lib): """ From f22abcd359fd3c5e5280d38570b9c33a338384ac Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 1 May 2014 09:31:19 -0400 Subject: [PATCH 44/45] Apparently that code is a no-go on Python 2. It seemed to work on Python 3 though. --- OpenSSL/crypto.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index d62ce84d1..c48c84f7a 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -5,7 +5,8 @@ from six import ( integer_types as _integer_types, - text_type as _text_type) + text_type as _text_type, + PY3 as _PY3) from OpenSSL._util import ( ffi as _ffi, @@ -274,10 +275,11 @@ class _EllipticCurve(object): """ _curves = None - def __ne__(self, other): - if isinstance(other, _EllipticCurve): - return super(_EllipticCurve, self).__ne__(other) - return NotImplemented + if _PY3: + def __ne__(self, other): + if isinstance(other, _EllipticCurve): + return super(_EllipticCurve, self).__ne__(other) + return NotImplemented @classmethod From a538105f9de4d22e7dd7fbfe2811bd8d38071c0f Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Thu, 1 May 2014 09:32:46 -0400 Subject: [PATCH 45/45] do some explaining --- OpenSSL/crypto.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index c48c84f7a..03fe853a4 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -276,7 +276,14 @@ class _EllipticCurve(object): _curves = None if _PY3: + # This only necessary on Python 3. Morever, it is broken on Python 2. def __ne__(self, other): + """ + Implement cooperation with the right-hand side argument of ``!=``. + + Python 3 seems to have dropped this cooperation in this very narrow + circumstance. + """ if isinstance(other, _EllipticCurve): return super(_EllipticCurve, self).__ne__(other) return NotImplemented