Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
2014-04-17 Jeff Tang <mrjefftang@gmail.com>

* OpenSSL/crypto.py: Add EC_Key support to ``PKey``

2014-03-30 Fedor Brunner <fedor.brunner@azet.sk>

* OpenSSL/SSL.py: Add ``get_finished``, ``get_peer_finished``
Expand Down
35 changes: 32 additions & 3 deletions OpenSSL/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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
"""
Expand All @@ -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)
Expand Down Expand Up @@ -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")

Expand Down
46 changes: 40 additions & 6 deletions OpenSSL/test/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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):
"""
Expand Down
10 changes: 7 additions & 3 deletions doc/api/crypto.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -110,6 +110,7 @@

.. py:data:: TYPE_RSA
TYPE_DSA
TYPE_EC

Key type constants.

Expand Down Expand Up @@ -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()
Expand Down