aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/hazmat/primitives/asymmetric/ec.rst39
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst20
-rw-r--r--src/cryptography/hazmat/backends/openssl/ec.py19
-rw-r--r--src/cryptography/hazmat/bindings/openssl/pem.py16
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/ec.py19
-rw-r--r--tests/hazmat/primitives/test_ec.py164
6 files changed, 266 insertions, 11 deletions
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index 8b9a584b..cf616a0d 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -326,6 +326,45 @@ Key Interfaces
:returns: An :class:`EllipticCurvePrivateNumbers` instance.
+.. class:: EllipticCurvePrivateKeyWithSerialization
+
+ .. versionadded:: 0.8
+
+ Extends :class:`EllipticCurvePrivateKey`.
+
+ .. method:: private_numbers()
+
+ Create a :class:`EllipticCurvePrivateNumbers` object.
+
+ :returns: An :class:`EllipticCurvePrivateNumbers` instance.
+
+ .. method:: private_bytes(encoding, format, encryption_algorithm)
+
+ Allows serialization of the key to bytes. Encoding (
+ :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or
+ :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`),
+ format (
+ :attr:`~cryptography.hazmat.primitives.serialization.Format.TraditionalOpenSSL`
+ or
+ :attr:`~cryptography.hazmat.primitives.serialization.Format.PKCS8`) and
+ encryption algorithm (such as
+ :class:`~cryptography.hazmat.primitives.serialization.BestAvailableEncryption`
+ or :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`)
+ are chosen to define the exact serialization.
+
+ :param encoding: A value from the
+ :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum.
+
+ :param format: A value from the
+ :class:`~cryptography.hazmat.primitives.serialization.Format` enum.
+
+ :param encryption_algorithm: An instance of an object conforming to the
+ :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption`
+ interface.
+
+ :return bytes: Serialized key.
+
+
.. class:: EllipticCurvePublicKey
.. versionadded:: 0.5
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index e11b02ab..49a0e36e 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -290,8 +290,11 @@ Serialization Formats
.. versionadded:: 0.8
- An enumeration for private key formats. Used with
- :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`.
+ An enumeration for private key formats. Used with the ``private_bytes``
+ method available on
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`
+ and
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`.
.. attribute:: TraditionalOpenSSL
@@ -311,8 +314,11 @@ Serialization Encodings
.. versionadded:: 0.8
- An enumeration for encoding types. Used with
- :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`.
+ An enumeration for encoding types. Used with the ``private_bytes`` method
+ available on
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`
+ and
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`.
.. attribute:: PEM
@@ -329,8 +335,10 @@ Serialization Encryption Types
.. class:: KeySerializationEncryption
Objects with this interface are usable as encryption types with methods
- like
- :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`.
+ like ``private_bytes`` available on
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`
+ and
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`.
All other classes in this section represent the available choices for
encryption and have this interface. They are used with
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`.
diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py
index 52c93da9..19d646e8 100644
--- a/src/cryptography/hazmat/backends/openssl/ec.py
+++ b/src/cryptography/hazmat/backends/openssl/ec.py
@@ -148,7 +148,7 @@ class _ECDSAVerificationContext(object):
return True
-@utils.register_interface(ec.EllipticCurvePrivateKeyWithNumbers)
+@utils.register_interface(ec.EllipticCurvePrivateKeyWithSerialization)
class _EllipticCurvePrivateKey(object):
def __init__(self, backend, ec_key_cdata):
self._backend = backend
@@ -200,6 +200,23 @@ class _EllipticCurvePrivateKey(object):
public_numbers=self.public_key().public_numbers()
)
+ def private_bytes(self, encoding, format, encryption_algorithm):
+ evp_pkey = self._backend._lib.EVP_PKEY_new()
+ assert evp_pkey != self._backend._ffi.NULL
+ evp_pkey = self._backend._ffi.gc(
+ evp_pkey, self._backend._lib.EVP_PKEY_free
+ )
+ res = self._backend._lib.EVP_PKEY_set1_EC_KEY(evp_pkey, self._ec_key)
+ assert res == 1
+ return self._backend._private_key_bytes(
+ encoding,
+ format,
+ encryption_algorithm,
+ self._backend._lib.PEM_write_bio_ECPrivateKey,
+ evp_pkey,
+ self._ec_key
+ )
+
@utils.register_interface(ec.EllipticCurvePublicKeyWithNumbers)
class _EllipticCurvePublicKey(object):
diff --git a/src/cryptography/hazmat/bindings/openssl/pem.py b/src/cryptography/hazmat/bindings/openssl/pem.py
index d0c70f5d..98c7648f 100644
--- a/src/cryptography/hazmat/bindings/openssl/pem.py
+++ b/src/cryptography/hazmat/bindings/openssl/pem.py
@@ -72,9 +72,23 @@ int PEM_write_bio_PUBKEY(BIO *, EVP_PKEY *);
"""
MACROS = """
+int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *,
+ unsigned char *, int, pem_password_cb *,
+ void *);
"""
CUSTOMIZATIONS = """
+// Cryptography_HAS_EC is provided by ec.py so we don't need to define it here
+#ifdef OPENSSL_NO_EC
+int (*PEM_write_bio_ECPrivateKey)(BIO *, EC_KEY *, const EVP_CIPHER *,
+ unsigned char *, int, pem_password_cb *,
+ void *) = NULL;
+#endif
+
"""
-CONDITIONAL_NAMES = {}
+CONDITIONAL_NAMES = {
+ "Cryptography_HAS_EC": [
+ "PEM_write_bio_ECPrivateKey"
+ ]
+}
diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py
index c7749ca5..52e14816 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/ec.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py
@@ -57,13 +57,30 @@ class EllipticCurvePrivateKey(object):
@six.add_metaclass(abc.ABCMeta)
-class EllipticCurvePrivateKeyWithNumbers(EllipticCurvePrivateKey):
+class EllipticCurvePrivateKeyWithSerialization(EllipticCurvePrivateKey):
@abc.abstractmethod
def private_numbers(self):
"""
Returns an EllipticCurvePrivateNumbers.
"""
+ @abc.abstractmethod
+ def private_bytes(self, encoding, format, encryption_algorithm):
+ """
+ Returns the key serialized as bytes.
+ """
+
+
+EllipticCurvePrivateKeyWithNumbers = utils.deprecated(
+ EllipticCurvePrivateKeyWithSerialization,
+ __name__,
+ (
+ "The EllipticCurvePrivateKeyWithNumbers interface has been renamed to "
+ "EllipticCurvePrivateKeyWithSerialization"
+ ),
+ utils.DeprecatedIn08
+)
+
@six.add_metaclass(abc.ABCMeta)
class EllipticCurvePublicKey(object):
diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py
index ea621ad6..f1c49cf7 100644
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -10,8 +10,10 @@ import os
import pytest
from cryptography import exceptions, utils
-from cryptography.hazmat.backends.interfaces import EllipticCurveBackend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.backends.interfaces import (
+ EllipticCurveBackend, PEMSerializationBackend
+)
+from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
encode_rfc6979_signature
@@ -31,6 +33,13 @@ _HASH_TYPES = {
}
+def _skip_if_no_serialization(key, backend):
+ if not isinstance(key, ec.EllipticCurvePrivateKeyWithSerialization):
+ pytest.skip(
+ "{0} does not support EC key serialization".format(backend)
+ )
+
+
def _skip_ecdsa_vector(backend, curve_type, hash_type):
if not backend.elliptic_curve_signature_algorithm_supported(
ec.ECDSA(hash_type()),
@@ -63,12 +72,22 @@ class DummySignatureAlgorithm(object):
algorithm = None
+@utils.register_interface(serialization.KeySerializationEncryption)
+class DummyKeyEncryption(object):
+ pass
+
+
@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
def test_skip_curve_unsupported(backend):
with pytest.raises(pytest.skip.Exception):
_skip_curve_unsupported(backend, DummyCurve())
+def test_skip_no_serialization():
+ with pytest.raises(pytest.skip.Exception):
+ _skip_if_no_serialization("fakebackend", "fakekey")
+
+
def test_ec_numbers():
numbers = ec.EllipticCurvePrivateNumbers(
1,
@@ -378,3 +397,144 @@ class TestECNumbersEquality(object):
1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP521R1())
)
assert priv != object()
+
+
+@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
+@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
+class TestECSerialization(object):
+ @pytest.mark.parametrize(
+ ("fmt", "password"),
+ itertools.product(
+ [
+ serialization.Format.TraditionalOpenSSL,
+ serialization.Format.PKCS8
+ ],
+ [
+ b"s",
+ b"longerpassword",
+ b"!*$&(@#$*&($T@%_somesymbols",
+ b"\x01" * 1000,
+ ]
+ )
+ )
+ def test_private_bytes_encrypted_pem(self, backend, fmt, password):
+ key_bytes = load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "PKCS8", "ec_private_key.pem"),
+ lambda pemfile: pemfile.read().encode()
+ )
+ key = serialization.load_pem_private_key(key_bytes, None, backend)
+ _skip_if_no_serialization(key, backend)
+ serialized = key.private_bytes(
+ serialization.Encoding.PEM,
+ fmt,
+ serialization.BestAvailableEncryption(password)
+ )
+ loaded_key = serialization.load_pem_private_key(
+ serialized, password, backend
+ )
+ loaded_priv_num = loaded_key.private_numbers()
+ priv_num = key.private_numbers()
+ assert loaded_priv_num == priv_num
+
+ @pytest.mark.parametrize(
+ "fmt",
+ [serialization.Format.TraditionalOpenSSL, serialization.Format.PKCS8],
+ )
+ def test_private_bytes_unencrypted_pem(self, backend, fmt):
+ key_bytes = load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "PKCS8", "ec_private_key.pem"),
+ lambda pemfile: pemfile.read().encode()
+ )
+ key = serialization.load_pem_private_key(key_bytes, None, backend)
+ _skip_if_no_serialization(key, backend)
+ serialized = key.private_bytes(
+ serialization.Encoding.PEM,
+ fmt,
+ serialization.NoEncryption()
+ )
+ loaded_key = serialization.load_pem_private_key(
+ serialized, None, backend
+ )
+ loaded_priv_num = loaded_key.private_numbers()
+ priv_num = key.private_numbers()
+ assert loaded_priv_num == priv_num
+
+ def test_private_bytes_traditional_openssl_unencrypted_pem(self, backend):
+ key_bytes = load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "PEM_Serialization", "ec_private_key.pem"),
+ lambda pemfile: pemfile.read().encode()
+ )
+ key = serialization.load_pem_private_key(key_bytes, None, backend)
+ serialized = key.private_bytes(
+ serialization.Encoding.PEM,
+ serialization.Format.TraditionalOpenSSL,
+ serialization.NoEncryption()
+ )
+ assert serialized == key_bytes
+
+ def test_private_bytes_invalid_encoding(self, backend):
+ key = load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "PKCS8", "ec_private_key.pem"),
+ lambda pemfile: serialization.load_pem_private_key(
+ pemfile.read().encode(), None, backend
+ )
+ )
+ _skip_if_no_serialization(key, backend)
+ with pytest.raises(TypeError):
+ key.private_bytes(
+ "notencoding",
+ serialization.Format.PKCS8,
+ serialization.NoEncryption()
+ )
+
+ def test_private_bytes_invalid_format(self, backend):
+ key = load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "PKCS8", "ec_private_key.pem"),
+ lambda pemfile: serialization.load_pem_private_key(
+ pemfile.read().encode(), None, backend
+ )
+ )
+ _skip_if_no_serialization(key, backend)
+ with pytest.raises(TypeError):
+ key.private_bytes(
+ serialization.Encoding.PEM,
+ "invalidformat",
+ serialization.NoEncryption()
+ )
+
+ def test_private_bytes_invalid_encryption_algorithm(self, backend):
+ key = load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "PKCS8", "ec_private_key.pem"),
+ lambda pemfile: serialization.load_pem_private_key(
+ pemfile.read().encode(), None, backend
+ )
+ )
+ _skip_if_no_serialization(key, backend)
+ with pytest.raises(TypeError):
+ key.private_bytes(
+ serialization.Encoding.PEM,
+ serialization.Format.TraditionalOpenSSL,
+ "notanencalg"
+ )
+
+ def test_private_bytes_unsupported_encryption_type(self, backend):
+ key = load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "PKCS8", "ec_private_key.pem"),
+ lambda pemfile: serialization.load_pem_private_key(
+ pemfile.read().encode(), None, backend
+ )
+ )
+ _skip_if_no_serialization(key, backend)
+ with pytest.raises(ValueError):
+ key.private_bytes(
+ serialization.Encoding.PEM,
+ serialization.Format.TraditionalOpenSSL,
+ DummyKeyEncryption()
+ )