From 67c9b083c85ea7a52f2080d94e1527f6aafdecd1 Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Wed, 16 Apr 2014 14:14:48 -0400 Subject: [PATCH 1/6] Add EC support to PKey --- OpenSSL/crypto.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 65e28d792..1e75ade68 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -22,7 +22,7 @@ TYPE_RSA = _lib.EVP_PKEY_RSA TYPE_DSA = _lib.EVP_PKEY_DSA - +TYPE_EC = _lib.EVP_PKEY_EC class Error(Exception): """ @@ -160,12 +160,13 @@ def __init__(self): self._initialized = False - def generate_key(self, type, bits): + def generate_key(self, type, bits, curve=None): """ Generate a key of a given type, with a given number of a bits :param type: The key type (TYPE_RSA or TYPE_DSA) :param bits: The number of bits + :param curve: None or the curve name for TYPE_EC :return: None """ @@ -175,6 +176,9 @@ def generate_key(self, type, bits): if not isinstance(bits, int): raise TypeError("bits must be an integer") + if curve and not isinstance(curve, str): + raise TypeError("curve must be a string") + # TODO Check error return exponent = _lib.BN_new() exponent = _ffi.gc(exponent, _lib.BN_free) @@ -214,6 +218,24 @@ def generate_key(self, type, bits): if not _lib.EVP_PKEY_assign_DSA(self._pkey, dsa): # TODO: This is untested. _raise_current_error() + + elif type == TYPE_EC: + nid = _lib.OBJ_sn2nid(curve.encode()) + if nid == _lib.NID_undef: + raise Error("No such curve name: %s", curve) + + ec = _lib.EC_KEY_new_by_curve_name(nid) + if ec == _ffi.NULL: + # TODO: This is untested. + _raise_current_error() + + if not _lib.EC_KEY_generate_key(ec): + # TODO: This is untested. + _raise_current_error() + + if not _lib.EVP_PKEY_assign_EC_KEY(self._pkey, ec): + # TODO: This is untested. + _raise_current_error() else: raise Error("No such key type") From f470175f9d9bf32e19c3cb55c07a0958c8444c4a Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Thu, 17 Apr 2014 13:21:09 -0400 Subject: [PATCH 2/6] Add check for Cryptography_HAS_EC for TYPE_EC --- OpenSSL/crypto.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 1e75ade68..1b3280a24 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -22,7 +22,11 @@ TYPE_RSA = _lib.EVP_PKEY_RSA TYPE_DSA = _lib.EVP_PKEY_DSA -TYPE_EC = _lib.EVP_PKEY_EC +# Some versions of OpenSSL do not support EC +if _lib.Cryptography_HAS_EC == 1: + TYPE_EC = _lib.EVP_PKEY_EC +else: + TYPE_EC = None class Error(Exception): """ From aabb0d036d861b5d9a578df0eab12cec5d87b0ed Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Thu, 17 Apr 2014 13:29:51 -0400 Subject: [PATCH 3/6] Try/catch for setting TYPE_EC --- OpenSSL/crypto.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 1b3280a24..8d074ad27 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -23,9 +23,9 @@ TYPE_RSA = _lib.EVP_PKEY_RSA TYPE_DSA = _lib.EVP_PKEY_DSA # Some versions of OpenSSL do not support EC -if _lib.Cryptography_HAS_EC == 1: +try: TYPE_EC = _lib.EVP_PKEY_EC -else: +except AttributeError: TYPE_EC = None class Error(Exception): From 84718dcc177f69727fd190e5ce19e20ae8b661b1 Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Thu, 17 Apr 2014 16:05:01 -0400 Subject: [PATCH 4/6] Add tests for TYPE_EC key generation. --- OpenSSL/test/test_crypto.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index a3685a969..5b108a90d 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -13,7 +13,7 @@ from six import binary_type -from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType +from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, TYPE_EC, Error, PKey, PKeyType from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType from OpenSSL.crypto import X509Store, X509StoreType, X509Req, X509ReqType from OpenSSL.crypto import X509Extension, X509ExtensionType @@ -621,6 +621,15 @@ def test_failedGeneration(self): # self.assertRaises(Error, key.generate_key, TYPE_DSA, -7) + # Skip these tests when EC is not available in OpenSSL + if TYPE_EC is not None: + # Invalid curve name + self.assertRaises(TypeError, key.generate_key, TYPE_EC, 0, 2) + # Non-existant curve + self.assertRaises(Error, key.generate_key, TYPE_EC, 0, 'bad-curve') + # Missing curve name + self.assertRaises(Error, key.generate_key, TYPE_EC, 0) + def test_rsaGeneration(self): """ @@ -650,6 +659,19 @@ def test_dsaGeneration(self): # self.assertEqual(key.bits(), bits) # self.assertRaises(TypeError, key.check) + if TYPE_EC is not None: + def test_ecGeneration(self): + """ + :py:meth:`PKeyType.generate_key` generates an EC key when passed + :py:data:`TYPE_EC` as a type and a supported curve name. + """ + curve = 'secp384r1' + bits = 384 + key = PKey() + key.generate_key(TYPE_EC, 0, curve) + self.assertEqual(key.type(), TYPE_EC) + self.assertEqual(key.bits(), bits) + self.assertRaises(TypeError, key.check) def test_regeneration(self): """ @@ -662,6 +684,12 @@ def test_regeneration(self): self.assertEqual(key.type(), type) self.assertEqual(key.bits(), bits) + if TYPE_EC is not None: + for type, bits, curve in [(TYPE_EC, 384, 'secp384r1'), + (TYPE_EC, 256, 'prime256v1')]: + key.generate_key(type, bits, curve) + self.assertEqual(key.type(), type) + self.assertEqual(key.bits(), bits) def test_inconsistentKey(self): """ From 28df86922e8264e2dfc03a0e50114bbda2cd8c80 Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Thu, 17 Apr 2014 16:17:09 -0400 Subject: [PATCH 5/6] Add additional check for curve parameter and update test docstring --- OpenSSL/crypto.py | 3 +++ OpenSSL/test/test_crypto.py | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 8d074ad27..d7f29a288 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -180,6 +180,9 @@ def generate_key(self, type, bits, curve=None): if not isinstance(bits, int): raise TypeError("bits must be an integer") + if type == TYPE_EC and curve is None: + raise ValueError("curve must be specified") + if curve and not isinstance(curve, str): raise TypeError("curve must be a string") diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 5b108a90d..20cc01ea7 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -589,11 +589,14 @@ def test_pregeneration(self): def test_failedGeneration(self): """ - :py:meth:`PKeyType.generate_key` takes two arguments, the first giving the key - type as one of :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` and the second giving the - number of bits to generate. If an invalid type is specified or - generation fails, :py:exc:`Error` is raised. If an invalid number of bits is - specified, :py:exc:`ValueError` or :py:exc:`Error` is raised. + :py:meth:`PKeyType.generate_key` takes three arguments, the first + giving the key type as one of :py:data:`TYPE_RSA` or + :py:data:`TYPE_DSA` or :py:data:`TYPE_EC` and the second giving the + number of bits to generate, and a third argument when + :py:data:`TYPE_EC` is used to specify the elliptic curve. If an + invalid type is specified or generation fails, :py:exc:`Error` is + raised. If an invalid number of bits or curve is specified, + :py:exc:`ValueError` or :py:exc:`Error` is raised. """ key = PKey() self.assertRaises(TypeError, key.generate_key) @@ -628,7 +631,10 @@ def test_failedGeneration(self): # Non-existant curve self.assertRaises(Error, key.generate_key, TYPE_EC, 0, 'bad-curve') # Missing curve name - self.assertRaises(Error, key.generate_key, TYPE_EC, 0) + self.assertRaises(ValueError, key.generate_key, TYPE_EC, 0) + else: + self.assertRaises(TypeError, key.generate_key, TYPE_EC, 0, + 'secp384r1') def test_rsaGeneration(self): From 0d71fb2319ba515cce90b4215c6d1a954b029169 Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Thu, 17 Apr 2014 16:28:17 -0400 Subject: [PATCH 6/6] Updated documentation and ChangeLog for TYPE_EC --- ChangeLog | 4 ++++ OpenSSL/crypto.py | 2 +- doc/api/crypto.rst | 10 +++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 164c9ad6c..065deb1da 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2014-04-17 Jeff Tang + + * OpenSSL/crypto.py: Add EC_Key support to ``PKey`` + 2014-03-30 Fedor Brunner * OpenSSL/SSL.py: Add ``get_finished``, ``get_peer_finished`` diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index d7f29a288..fe65c7b42 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -168,7 +168,7 @@ def generate_key(self, type, bits, curve=None): """ Generate a key of a given type, with a given number of a bits - :param type: The key type (TYPE_RSA or TYPE_DSA) + :param type: The key type (TYPE_RSA or TYPE_DSA or TYPE_EC) :param bits: The number of bits :param curve: None or the curve name for TYPE_EC diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index ee93cfba9..f122e6c2e 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -52,7 +52,7 @@ .. py:class:: PKey() - A class representing DSA or RSA keys. + A class representing DSA or RSA or EC keys. .. py:data:: PKCS7Type @@ -110,6 +110,7 @@ .. py:data:: TYPE_RSA TYPE_DSA + TYPE_EC Key type constants. @@ -516,10 +517,13 @@ The PKey object has the following methods: Return the number of bits of the key. -.. py:method:: PKey.generate_key(type, bits) +.. py:method:: PKey.generate_key(type, bits, curve) Generate a public/private key pair of the type *type* (one of - :py:const:`TYPE_RSA` and :py:const:`TYPE_DSA`) with the size *bits*. + :py:const:`TYPE_RSA` and :py:const:`TYPE_DSA` and :py:const:`TYPE_EC`) with + the size *bits* and. For PKeys with :py:const:`TYPE_EC`, the *bits* + parameter is ignored and the *curve* name is used. Valid *curve* names are + determined by the underlying OpenSSL build (openssl ecparam -list_curves). .. py:method:: PKey.type()