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 65e28d792..fe65c7b42 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 - +# Some versions of OpenSSL do not support EC +try: + TYPE_EC = _lib.EVP_PKEY_EC +except AttributeError: + TYPE_EC = None class Error(Exception): """ @@ -160,12 +164,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 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 :return: None """ @@ -175,6 +180,12 @@ def generate_key(self, type, bits): 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") + # TODO Check error return exponent = _lib.BN_new() exponent = _ffi.gc(exponent, _lib.BN_free) @@ -214,6 +225,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") diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index a3685a969..20cc01ea7 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 @@ -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) @@ -621,6 +624,18 @@ 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(ValueError, key.generate_key, TYPE_EC, 0) + else: + self.assertRaises(TypeError, key.generate_key, TYPE_EC, 0, + 'secp384r1') + def test_rsaGeneration(self): """ @@ -650,6 +665,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 +690,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): """ 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()