aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2015-03-01 16:53:58 -0600
committerPaul Kehrer <paul.l.kehrer@gmail.com>2015-03-02 12:24:09 -0600
commitec3426383c58098a326b3568a42f298046f1b9c5 (patch)
tree161b4076cec0d3d18fa3304a0bf166f2cf7b2d2d
parent7385d5d5eeb52b4559fc106c1dd4137976a64da9 (diff)
downloadcryptography-ec3426383c58098a326b3568a42f298046f1b9c5.tar.gz
cryptography-ec3426383c58098a326b3568a42f298046f1b9c5.tar.bz2
cryptography-ec3426383c58098a326b3568a42f298046f1b9c5.zip
serialize DSA private keys
-rw-r--r--CHANGELOG.rst8
-rw-r--r--docs/hazmat/primitives/asymmetric/dsa.rst44
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst12
-rw-r--r--src/cryptography/hazmat/backends/openssl/dsa.py26
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/dsa.py19
-rw-r--r--tests/hazmat/primitives/test_dsa.py171
6 files changed, 268 insertions, 12 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1841091b..053e7552 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -94,6 +94,14 @@ Changelog
:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`
to
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`.
+* Added
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`
+ and deprecated
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithNumbers`.
+* Added
+ :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization.private_bytes`
+ to
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`.
0.7.2 - 2015-01-16
~~~~~~~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst
index 3a47da45..bd02423f 100644
--- a/docs/hazmat/primitives/asymmetric/dsa.rst
+++ b/docs/hazmat/primitives/asymmetric/dsa.rst
@@ -301,6 +301,50 @@ Key interfaces
instance.
+.. class:: DSAPrivateKeyWithSerialization
+
+ .. versionadded:: 0.8
+
+ Extends :class:`DSAPrivateKey`.
+
+ .. method:: private_numbers()
+
+ Create a
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers`
+ object.
+
+ :returns: A
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers`
+ 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.PrivateFormat.TraditionalOpenSSL`
+ or
+ :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.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.PrivateFormat`
+ enum.
+
+ :param encryption_algorithm: An instance of an object conforming to the
+ :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption`
+ interface.
+
+ :return bytes: Serialized key.
+
+
.. class:: DSAPublicKey
.. versionadded:: 0.3
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index 49a0e36e..4a2aedc9 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -293,8 +293,10 @@ Serialization Formats
An enumeration for private key formats. Used with the ``private_bytes``
method available on
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`
+ ,
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`
and
- :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`.
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`.
.. attribute:: TraditionalOpenSSL
@@ -317,8 +319,10 @@ Serialization Encodings
An enumeration for encoding types. Used with the ``private_bytes`` method
available on
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`
+ ,
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`
and
- :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`.
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`.
.. attribute:: PEM
@@ -337,8 +341,10 @@ Serialization Encryption Types
Objects with this interface are usable as encryption types with methods
like ``private_bytes`` available on
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`
+ ,
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`
and
- :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`.
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`.
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/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py
index d2972e4a..8d02e492 100644
--- a/src/cryptography/hazmat/backends/openssl/dsa.py
+++ b/src/cryptography/hazmat/backends/openssl/dsa.py
@@ -11,9 +11,6 @@ from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import (
AsymmetricSignatureContext, AsymmetricVerificationContext, dsa
)
-from cryptography.hazmat.primitives.interfaces import (
- DSAParametersWithNumbers, DSAPrivateKeyWithNumbers, DSAPublicKeyWithNumbers
-)
def _truncate_digest_for_dsa(dsa_cdata, digest, backend):
@@ -94,7 +91,7 @@ class _DSASignatureContext(object):
return self._backend._ffi.buffer(sig_buf)[:buflen[0]]
-@utils.register_interface(DSAParametersWithNumbers)
+@utils.register_interface(dsa.DSAParametersWithNumbers)
class _DSAParameters(object):
def __init__(self, backend, dsa_cdata):
self._backend = backend
@@ -111,7 +108,7 @@ class _DSAParameters(object):
return self._backend.generate_dsa_private_key(self)
-@utils.register_interface(DSAPrivateKeyWithNumbers)
+@utils.register_interface(dsa.DSAPrivateKeyWithSerialization)
class _DSAPrivateKey(object):
def __init__(self, backend, dsa_cdata):
self._backend = backend
@@ -159,8 +156,25 @@ class _DSAPrivateKey(object):
dsa_cdata.g = self._backend._lib.BN_dup(self._dsa_cdata.g)
return _DSAParameters(self._backend, dsa_cdata)
+ 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_DSA(evp_pkey, self._dsa_cdata)
+ assert res == 1
+ return self._backend._private_key_bytes(
+ encoding,
+ format,
+ encryption_algorithm,
+ self._backend._lib.PEM_write_bio_DSAPrivateKey,
+ evp_pkey,
+ self._dsa_cdata
+ )
+
-@utils.register_interface(DSAPublicKeyWithNumbers)
+@utils.register_interface(dsa.DSAPublicKeyWithNumbers)
class _DSAPublicKey(object):
def __init__(self, backend, dsa_cdata):
self._backend = backend
diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py
index 58058df9..084686e4 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py
@@ -57,13 +57,30 @@ class DSAPrivateKey(object):
@six.add_metaclass(abc.ABCMeta)
-class DSAPrivateKeyWithNumbers(DSAPrivateKey):
+class DSAPrivateKeyWithSerialization(DSAPrivateKey):
@abc.abstractmethod
def private_numbers(self):
"""
Returns a DSAPrivateNumbers.
"""
+ @abc.abstractmethod
+ def private_bytes(self, encoding, format, encryption_algorithm):
+ """
+ Returns the key serialized as bytes.
+ """
+
+
+DSAPrivateKeyWithNumbers = utils.deprecated(
+ DSAPrivateKeyWithSerialization,
+ __name__,
+ (
+ "The DSAPrivateKeyWithNumbers interface has been renamed to "
+ "DSAPrivateKeyWithSerialization"
+ ),
+ utils.DeprecatedIn08
+)
+
@six.add_metaclass(abc.ABCMeta)
class DSAPublicKey(object):
diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py
index 95164923..19ca0794 100644
--- a/tests/hazmat/primitives/test_dsa.py
+++ b/tests/hazmat/primitives/test_dsa.py
@@ -4,13 +4,17 @@
from __future__ import absolute_import, division, print_function
+import itertools
import os
import pytest
+from cryptography import utils
from cryptography.exceptions import AlreadyFinalized, InvalidSignature
-from cryptography.hazmat.backends.interfaces import DSABackend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.backends.interfaces import (
+ DSABackend, PEMSerializationBackend
+)
+from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography.hazmat.primitives.asymmetric.utils import (
encode_rfc6979_signature
@@ -26,6 +30,23 @@ from ...utils import (
)
+def _skip_if_no_serialization(key, backend):
+ if not isinstance(key, dsa.DSAPrivateKeyWithSerialization):
+ pytest.skip(
+ "{0} does not support DSA key serialization".format(backend)
+ )
+
+
+def test_skip_if_no_serialization():
+ with pytest.raises(pytest.skip.Exception):
+ _skip_if_no_serialization("notakeywithserialization", "backend")
+
+
+@utils.register_interface(serialization.KeySerializationEncryption)
+class DummyKeyEncryption(object):
+ pass
+
+
@pytest.mark.requires_backend_interface(interface=DSABackend)
class TestDSA(object):
def test_generate_dsa_parameters(self, backend):
@@ -769,3 +790,149 @@ class TestDSANumberEquality(object):
)
)
assert priv != object()
+
+
+@pytest.mark.requires_backend_interface(interface=DSABackend)
+@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
+class TestDSASerialization(object):
+ @pytest.mark.parametrize(
+ ("fmt", "password"),
+ itertools.product(
+ [
+ serialization.PrivateFormat.TraditionalOpenSSL,
+ serialization.PrivateFormat.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", "unenc-dsa-pkcs8.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.PrivateFormat.TraditionalOpenSSL,
+ serialization.PrivateFormat.PKCS8
+ ],
+ )
+ def test_private_bytes_unencrypted_pem(self, backend, fmt):
+ key_bytes = load_vectors_from_file(
+ os.path.join(
+ "asymmetric",
+ "Traditional_OpenSSL_Serialization",
+ "dsa.1024.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",
+ "Traditional_OpenSSL_Serialization",
+ "dsa.1024.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,
+ serialization.PrivateFormat.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", "unenc-dsa-pkcs8.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.PrivateFormat.PKCS8,
+ serialization.NoEncryption()
+ )
+
+ def test_private_bytes_invalid_format(self, backend):
+ key = load_vectors_from_file(
+ os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.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", "unenc-dsa-pkcs8.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.PrivateFormat.TraditionalOpenSSL,
+ "notanencalg"
+ )
+
+ def test_private_bytes_unsupported_encryption_type(self, backend):
+ key = load_vectors_from_file(
+ os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.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.PrivateFormat.TraditionalOpenSSL,
+ DummyKeyEncryption()
+ )