aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/hazmat/backends/interfaces.rst19
-rw-r--r--docs/hazmat/primitives/asymmetric/dh.rst19
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst91
-rw-r--r--src/cryptography/hazmat/backends/interfaces.py12
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py63
-rw-r--r--src/cryptography/hazmat/backends/openssl/dh.py22
-rw-r--r--src/cryptography/hazmat/primitives/serialization.py12
-rw-r--r--tests/hazmat/backends/test_openssl.py4
-rw-r--r--tests/hazmat/primitives/test_dh.py136
-rw-r--r--tests/hazmat/primitives/test_serialization.py19
-rw-r--r--vectors/cryptography_vectors/asymmetric/DH/dhp_rfc5114_2.derbin526 -> 557 bytes
11 files changed, 395 insertions, 2 deletions
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst
index 4d0520fa..8ea1cf99 100644
--- a/docs/hazmat/backends/interfaces.rst
+++ b/docs/hazmat/backends/interfaces.rst
@@ -452,6 +452,15 @@ A specific ``backend`` may provide one or more of these interfaces.
serialized data contains.
:raises ValueError: If the data could not be deserialized.
+ .. method:: load_pem_parameters(data)
+
+ .. versionadded:: 2.0
+
+ :param bytes data: PEM data to load.
+ :return: A new instance of the appropriate type of encryption
+ parameters the serialized data contains.
+ :raises ValueError: If the data could not be deserialized.
+
.. class:: DERSerializationBackend
.. versionadded:: 0.8
@@ -476,6 +485,16 @@ A specific ``backend`` may provide one or more of these interfaces.
serialized data contains.
:raises ValueError: If the data could not be deserialized.
+ .. method:: load_der_parameters(data)
+
+ .. versionadded:: 2.0
+
+ :param bytes data: DER data to load.
+ :return: A new instance of the appropriate type of encryption
+ parameters the serialized data contains.
+ :raises ValueError: If the data could not be deserialized.
+
+
.. class:: X509Backend
.. versionadded:: 0.7
diff --git a/docs/hazmat/primitives/asymmetric/dh.rst b/docs/hazmat/primitives/asymmetric/dh.rst
index f4cae1c3..971d3452 100644
--- a/docs/hazmat/primitives/asymmetric/dh.rst
+++ b/docs/hazmat/primitives/asymmetric/dh.rst
@@ -115,6 +115,25 @@ Group parameters
:return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers`.
+ .. method:: parameter_bytes(encoding, format)
+
+ .. versionadded:: 2.0
+
+ Allows serialization of the parameters to bytes. Encoding (
+ :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or
+ :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and
+ format (
+ :attr:`~cryptography.hazmat.primitives.serialization.ParameterFormat.PKCS3`)
+ 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.ParameterFormat` enum.
+
+ :return bytes: Serialized parameters.
+
Key interfaces
~~~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index b745332e..b0cfbd0e 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -63,6 +63,20 @@ Key Serialization
def sign_with_dsa_key(key, message):
return b""
+ parameters_pem_data = b"""
+ -----BEGIN DH PARAMETERS-----
+ MIGHAoGBALsrWt44U1ojqTy88o0wfjysBE51V6Vtarjm2+5BslQK/RtlndHde3gx
+ +ccNs+InANszcuJFI8AHt4743kGRzy5XSlul4q4dDJENOHoyqYxueFuFVJELEwLQ
+ XrX/McKw+hS6GPVQnw6tZhgGo9apdNdYgeLQeQded8Bum8jqzP3rAgEC
+ -----END DH PARAMETERS-----
+ """.strip()
+
+ parameters_der_data = base64.b64decode(
+ b"MIGHAoGBALsrWt44U1ojqTy88o0wfjysBE51V6Vtarjm2+5BslQK/RtlndHde3gx+ccNs+In"
+ b"ANsz\ncuJFI8AHt4743kGRzy5XSlul4q4dDJENOHoyqYxueFuFVJELEwLQXrX/McKw+hS6GP"
+ b"VQnw6tZhgG\no9apdNdYgeLQeQded8Bum8jqzP3rAgEC"
+ )
+
There are several common schemes for serializing asymmetric private and public
keys to bytes. They generally support encryption of private keys and additional
key metadata.
@@ -181,6 +195,37 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END
:raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key
is of a type that is not supported by the backend.
+.. function:: load_pem_parameters(data, backend)
+
+ .. versionadded:: 2.0
+
+ Deserialize encryption parameters from PEM encoded data to one of the supported
+ asymmetric encryption parameters types.
+
+ .. doctest::
+
+ >>> from cryptography.hazmat.primitives.serialization import load_pem_parameters
+ >>> from cryptography.hazmat.primitives.asymmetric import dh
+ >>> parameters = load_pem_parameters(parameters_pem_data, backend=default_backend())
+ >>> isinstance(parameters, dh.DHParameters)
+ True
+
+ :param bytes data: The PEM encoded parameters data.
+
+ :param backend: An instance of
+ :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`.
+
+
+ :returns: Currently only
+ :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`
+ supported.
+
+ :raises ValueError: If the PEM data's structure could not be decoded
+ successfully.
+
+ :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized parameters
+ is of a type that is not supported by the backend.
+
DER
~~~
@@ -268,6 +313,37 @@ the rest.
>>> isinstance(key, rsa.RSAPublicKey)
True
+.. function:: load_der_parameters(data, backend)
+
+ .. versionadded:: 2.0
+
+ Deserialize encryption parameters from DER encoded data to one of the supported
+ asymmetric encryption parameters types.
+
+ :param bytes data: The DER encoded parameters data.
+
+ :param backend: An instance of
+ :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`.
+
+ :returns: Currently only
+ :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`
+ supported.
+
+ :raises ValueError: If the DER data's structure could not be decoded
+ successfully.
+
+ :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that
+ is not supported by the backend.
+
+ .. doctest::
+
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> from cryptography.hazmat.primitives.asymmetric import dh
+ >>> from cryptography.hazmat.primitives.serialization import load_der_parameters
+ >>> parameters = load_der_parameters(parameters_der_data, backend=default_backend())
+ >>> isinstance(parameters, dh.DHParameters)
+ True
+
OpenSSH Public Key
~~~~~~~~~~~~~~~~~~
@@ -379,6 +455,18 @@ Serialization Formats
The public key format used by OpenSSH (e.g. as found in
``~/.ssh/id_rsa.pub`` or ``~/.ssh/authorized_keys``).
+.. class:: ParameterFormat
+
+ .. versionadded:: 2.0
+
+ An enumeration for parameters formats. Used with the ``parameter_bytes``
+ method available on
+ :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParametersWithSerialization`.
+
+ .. attribute:: PKCS3
+
+ ASN1 DH parameters sequence as defined in `PKCS3`_.
+
Serialization Encodings
~~~~~~~~~~~~~~~~~~~~~~~
@@ -445,3 +533,6 @@ Serialization Encryption Types
.. class:: NoEncryption
Do not encrypt.
+
+
+.. _`PKCS3`: https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-3-diffie-hellman-key-agreement-standar.htm
diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py
index 9ed50cc4..0a476b99 100644
--- a/src/cryptography/hazmat/backends/interfaces.py
+++ b/src/cryptography/hazmat/backends/interfaces.py
@@ -243,6 +243,12 @@ class PEMSerializationBackend(object):
Loads a public key from PEM encoded data.
"""
+ @abc.abstractmethod
+ def load_pem_parameters(self, data):
+ """
+ Load encryption parameters from PEM encoded data.
+ """
+
@six.add_metaclass(abc.ABCMeta)
class DERSerializationBackend(object):
@@ -259,6 +265,12 @@ class DERSerializationBackend(object):
Loads a public key from DER encoded data.
"""
+ @abc.abstractmethod
+ def load_der_parameters(self, data):
+ """
+ Load encryption parameters from DER encoded data.
+ """
+
@six.add_metaclass(abc.ABCMeta)
class X509Backend(object):
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index d17b38ca..5458a0f8 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -1007,6 +1007,17 @@ class Backend(object):
else:
self._handle_key_loading_error()
+ def load_pem_parameters(self, data):
+ mem_bio = self._bytes_to_bio(data)
+ # only DH is supported currently
+ dh_cdata = self._lib.PEM_read_bio_DHparams(
+ mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL)
+ if dh_cdata != self._ffi.NULL:
+ dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free)
+ return _DHParameters(self, dh_cdata)
+ else:
+ self._handle_key_loading_error()
+
def load_der_private_key(self, data, password):
# OpenSSL has a function called d2i_AutoPrivateKey that in theory
# handles this automatically, however it doesn't handle encrypted
@@ -1063,6 +1074,28 @@ class Backend(object):
else:
self._handle_key_loading_error()
+ def load_der_parameters(self, data):
+ mem_bio = self._bytes_to_bio(data)
+ dh_cdata = self._lib.d2i_DHparams_bio(
+ mem_bio.bio, self._ffi.NULL
+ )
+ if dh_cdata != self._ffi.NULL:
+ dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free)
+ return _DHParameters(self, dh_cdata)
+ elif self._lib.Cryptography_HAS_EVP_PKEY_DHX:
+ # We check to see if the is dhx.
+ self._consume_errors()
+ res = self._lib.BIO_reset(mem_bio.bio)
+ self.openssl_assert(res == 1)
+ dh_cdata = self._lib.Cryptography_d2i_DHxparams_bio(
+ mem_bio.bio, self._ffi.NULL
+ )
+ if dh_cdata != self._ffi.NULL:
+ dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free)
+ return _DHParameters(self, dh_cdata)
+
+ self._handle_key_loading_error()
+
def load_pem_x509_certificate(self, data):
mem_bio = self._bytes_to_bio(data)
x509 = self._lib.PEM_read_bio_X509(
@@ -1618,6 +1651,36 @@ class Backend(object):
serialization._ssh_write_string(public_numbers.encode_point())
)
+ def _parameter_bytes(self, encoding, format, cdata):
+ if encoding is serialization.Encoding.OpenSSH:
+ raise TypeError(
+ "OpenSSH encoding is not supported"
+ )
+
+ # Only DH is supported here currently.
+ q = self._ffi.new("BIGNUM **")
+ self._lib.DH_get0_pqg(cdata,
+ self._ffi.NULL,
+ q,
+ self._ffi.NULL)
+ if encoding is serialization.Encoding.PEM:
+ if q[0] != self._ffi.NULL:
+ write_bio = self._lib.PEM_write_bio_DHxparams
+ else:
+ write_bio = self._lib.PEM_write_bio_DHparams
+ elif encoding is serialization.Encoding.DER:
+ if q[0] != self._ffi.NULL:
+ write_bio = self._lib.Cryptography_i2d_DHxparams_bio
+ else:
+ write_bio = self._lib.i2d_DHparams_bio
+ else:
+ raise TypeError("encoding must be an item from the Encoding enum")
+
+ bio = self._create_mem_bio_gc()
+ res = write_bio(bio, cdata)
+ self.openssl_assert(res == 1)
+ return self._read_mem_bio(bio)
+
def generate_dh_parameters(self, generator, key_size):
if key_size < 512:
raise ValueError("DH key_size must be at least 512 bits")
diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py
index 456e9bea..e5f76447 100644
--- a/src/cryptography/hazmat/backends/openssl/dh.py
+++ b/src/cryptography/hazmat/backends/openssl/dh.py
@@ -59,6 +59,28 @@ class _DHParameters(object):
def generate_private_key(self):
return self._backend.generate_dh_private_key(self)
+ def parameter_bytes(self, encoding, format):
+ if format is not serialization.ParameterFormat.PKCS3:
+ raise ValueError(
+ "Only PKCS3 serialization is supported"
+ )
+ if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX:
+ q = self._backend._ffi.new("BIGNUM **")
+ self._backend._lib.DH_get0_pqg(self._dh_cdata,
+ self._backend._ffi.NULL,
+ q,
+ self._backend._ffi.NULL)
+ if q[0] != self._backend._ffi.NULL:
+ raise UnsupportedAlgorithm(
+ "DH X9.42 serialization is not supported",
+ _Reasons.UNSUPPORTED_SERIALIZATION)
+
+ return self._backend._parameter_bytes(
+ encoding,
+ format,
+ self._dh_cdata
+ )
+
def _handle_dh_compute_key_error(errors, backend):
lib = backend._lib
diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py
index 992fd42f..bd09e6e3 100644
--- a/src/cryptography/hazmat/primitives/serialization.py
+++ b/src/cryptography/hazmat/primitives/serialization.py
@@ -24,6 +24,10 @@ def load_pem_public_key(data, backend):
return backend.load_pem_public_key(data)
+def load_pem_parameters(data, backend):
+ return backend.load_pem_parameters(data)
+
+
def load_der_private_key(data, password, backend):
return backend.load_der_private_key(data, password)
@@ -32,6 +36,10 @@ def load_der_public_key(data, backend):
return backend.load_der_public_key(data)
+def load_der_parameters(data, backend):
+ return backend.load_der_parameters(data)
+
+
def load_ssh_public_key(data, backend):
key_parts = data.split(b' ', 2)
@@ -178,6 +186,10 @@ class PublicFormat(Enum):
OpenSSH = "OpenSSH"
+class ParameterFormat(Enum):
+ PKCS3 = "PKCS3"
+
+
@six.add_metaclass(abc.ABCMeta)
class KeySerializationEncryption(object):
pass
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index e857ff61..d8e7fe47 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -614,6 +614,10 @@ class TestOpenSSLDHSerialization(object):
public_key.public_bytes(
serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo)
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
+ parameters.parameters(backend).parameter_bytes(
+ serialization.Encoding.PEM,
+ serialization.ParameterFormat.PKCS3)
@pytest.mark.parametrize(
("key_path", "loader_func"),
diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py
index c351e5df..1fdabe57 100644
--- a/tests/hazmat/primitives/test_dh.py
+++ b/tests/hazmat/primitives/test_dh.py
@@ -669,3 +669,139 @@ class TestDHPublicKeySerialization(object):
key.public_bytes(
serialization.Encoding.PEM, serialization.PublicFormat.PKCS1
)
+
+
+@pytest.mark.requires_backend_interface(interface=DHBackend)
+@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
+@pytest.mark.requires_backend_interface(interface=DERSerializationBackend)
+class TestDHParameterSerialization(object):
+
+ @pytest.mark.parametrize(
+ ("encoding", "loader_func"),
+ [
+ [
+ serialization.Encoding.PEM,
+ serialization.load_pem_parameters
+ ],
+ [
+ serialization.Encoding.DER,
+ serialization.load_der_parameters
+ ],
+ ]
+ )
+ def test_parameter_bytes(self, backend, encoding,
+ loader_func):
+ parameters = dh.generate_parameters(2, 512, backend)
+ serialized = parameters.parameter_bytes(
+ encoding, serialization.ParameterFormat.PKCS3
+ )
+ loaded_key = loader_func(serialized, backend)
+ loaded_param_num = loaded_key.parameter_numbers()
+ assert loaded_param_num == parameters.parameter_numbers()
+
+ @pytest.mark.parametrize(
+ ("param_path", "loader_func", "encoding", "is_dhx"),
+ [
+ (
+ os.path.join("asymmetric", "DH", "dhp.pem"),
+ serialization.load_pem_parameters,
+ serialization.Encoding.PEM,
+ False,
+ ), (
+ os.path.join("asymmetric", "DH", "dhp.der"),
+ serialization.load_der_parameters,
+ serialization.Encoding.DER,
+ False,
+ ), (
+ os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"),
+ serialization.load_pem_parameters,
+ serialization.Encoding.PEM,
+ True,
+ ), (
+ os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"),
+ serialization.load_der_parameters,
+ serialization.Encoding.DER,
+ True,
+ )
+ ]
+ )
+ def test_parameter_bytes_match(self, param_path, loader_func,
+ encoding, backend, is_dhx):
+ _skip_dhx_unsupported(backend, is_dhx)
+ param_bytes = load_vectors_from_file(
+ param_path,
+ lambda pemfile: pemfile.read(), mode="rb"
+ )
+ parameters = loader_func(param_bytes, backend)
+ serialized = parameters.parameter_bytes(
+ encoding,
+ serialization.ParameterFormat.PKCS3,
+ )
+ assert serialized == param_bytes
+
+ @pytest.mark.parametrize(
+ ("param_path", "loader_func", "vec_path", "is_dhx"),
+ [
+ (
+ os.path.join("asymmetric", "DH", "dhp.pem"),
+ serialization.load_pem_parameters,
+ os.path.join("asymmetric", "DH", "dhkey.txt"),
+ False,
+ ), (
+ os.path.join("asymmetric", "DH", "dhp.der"),
+ serialization.load_der_parameters,
+ os.path.join("asymmetric", "DH", "dhkey.txt"),
+ False,
+ ), (
+ os.path.join("asymmetric", "DH", "dhp_rfc5114_2.pem"),
+ serialization.load_pem_parameters,
+ os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
+ True,
+ ), (
+ os.path.join("asymmetric", "DH", "dhp_rfc5114_2.der"),
+ serialization.load_der_parameters,
+ os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.txt"),
+ True,
+ )
+ ]
+ )
+ def test_public_bytes_values(self, param_path, loader_func,
+ vec_path, backend, is_dhx):
+ _skip_dhx_unsupported(backend, is_dhx)
+ key_bytes = load_vectors_from_file(
+ param_path,
+ lambda pemfile: pemfile.read(), mode="rb"
+ )
+ vec = load_vectors_from_file(vec_path, load_nist_vectors)[0]
+ parameters = loader_func(key_bytes, backend)
+ parameter_numbers = parameters.parameter_numbers()
+ assert parameter_numbers.g == int(vec["g"], 16)
+ assert parameter_numbers.p == int(vec["p"], 16)
+ if "q" in vec:
+ assert parameter_numbers.q == int(vec["q"], 16)
+ else:
+ assert parameter_numbers.q is None
+
+ def test_parameter_bytes_invalid_encoding(self, backend):
+ parameters = dh.generate_parameters(2, 512, backend)
+ with pytest.raises(TypeError):
+ parameters.parameter_bytes(
+ "notencoding",
+ serialization.ParameterFormat.PKCS3
+ )
+
+ def test_parameter_bytes_invalid_format(self, backend):
+ parameters = dh.generate_parameters(2, 512, backend)
+ with pytest.raises(ValueError):
+ parameters.parameter_bytes(
+ serialization.Encoding.PEM,
+ "notformat"
+ )
+
+ def test_parameter_bytes_openssh_unsupported(self, backend):
+ parameters = dh.generate_parameters(2, 512, backend)
+ with pytest.raises(TypeError):
+ parameters.parameter_bytes(
+ serialization.Encoding.OpenSSH,
+ serialization.ParameterFormat.PKCS3
+ )
diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py
index f4b953e6..a7355221 100644
--- a/tests/hazmat/primitives/test_serialization.py
+++ b/tests/hazmat/primitives/test_serialization.py
@@ -18,8 +18,9 @@ from cryptography.hazmat.backends.interfaces import (
)
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
from cryptography.hazmat.primitives.serialization import (
- BestAvailableEncryption, load_der_private_key, load_der_public_key,
- load_pem_private_key, load_pem_public_key, load_ssh_public_key
+ BestAvailableEncryption, load_der_parameters, load_der_private_key,
+ load_der_public_key, load_pem_parameters, load_pem_private_key,
+ load_pem_public_key, load_ssh_public_key
)
@@ -310,6 +311,14 @@ class TestDERSerialization(object):
assert key.curve.name == "secp256r1"
assert key.curve.key_size == 256
+ def test_wrong_parameters_format(self, backend):
+ param_data = b"---- NOT A KEY ----\n"
+
+ with pytest.raises(ValueError):
+ load_der_parameters(
+ param_data, backend
+ )
+
@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
class TestPEMSerialization(object):
@@ -591,6 +600,12 @@ class TestPEMSerialization(object):
with pytest.raises(ValueError):
load_pem_public_key(key_data, backend)
+ def test_wrong_parameters_format(self, backend):
+ param_data = b"---- NOT A KEY ----\n"
+
+ with pytest.raises(ValueError):
+ load_pem_parameters(param_data, backend)
+
def test_corrupt_traditional_format(self, backend):
# privkey.pem with a bunch of data missing.
key_data = textwrap.dedent("""\
diff --git a/vectors/cryptography_vectors/asymmetric/DH/dhp_rfc5114_2.der b/vectors/cryptography_vectors/asymmetric/DH/dhp_rfc5114_2.der
index 666eb9a0..f00c443a 100644
--- a/vectors/cryptography_vectors/asymmetric/DH/dhp_rfc5114_2.der
+++ b/vectors/cryptography_vectors/asymmetric/DH/dhp_rfc5114_2.der
Binary files differ