Skip to content

Commit

Permalink
Merge pull request #1610 from reaperhulk/load-der-keys
Browse files Browse the repository at this point in the history
DER key loading support for the OpenSSL backend
  • Loading branch information
alex committed Feb 23, 2015
2 parents ca32445 + d8a1d0e commit 15c7f6f
Show file tree
Hide file tree
Showing 7 changed files with 455 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ Changelog
:mod:`~cryptography.hazmat.primitives.asymmetric.rsa`.
* Added support for parsing X.509 names. See the
:doc:`X.509 documentation</x509>` for more information.
* Added
:func:`~cryptography.hazmat.primitives.serialization.load_der_private_key` to
support loading of DER encoded private keys and
:func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` to
support loading DER encoded public keys.
* Fixed building against LibreSSL, a compile-time substitute for OpenSSL.
* FreeBSD 9.2 was removed from the continuous integration system.

Expand Down
1 change: 1 addition & 0 deletions docs/hazmat/backends/openssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Red Hat Enterprise Linux 5) and greater. Earlier versions may work but are

* :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`
* :class:`~cryptography.hazmat.backends.interfaces.CMACBackend`
* :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`
* :class:`~cryptography.hazmat.backends.interfaces.DSABackend`
* :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`
* :class:`~cryptography.hazmat.backends.interfaces.HashBackend`
Expand Down
99 changes: 99 additions & 0 deletions docs/hazmat/primitives/asymmetric/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Key Serialization

.. testsetup::

import base64

pem_data = b"""
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDn09PV9KPE7Q+N5K5UtNLT1DLl8z/pKM2pP5tXqWx2OsEw00lC
Expand All @@ -32,6 +34,27 @@ Key Serialization
ex8nG0iMw4ObOtg6CwIDAQAB
-----END PUBLIC KEY-----
""".strip()
der_data = base64.b64decode(
b"MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALskegl+DrI3Msw5Z63x"
b"nj1rgoPR0KykwBi+jZgAwHv/B0TJyhy6NuEnaf+x442L7lepOqoWQzlUGXyuaSQU9mT/"
b"vHTGZ2xM8QJJaccr4eGho0MU9HePyNCFWjWVrGKpwSEAd6CLlzC0Wiy4kC9IoAUoS/IP"
b"jeyLTQNCddatgcARAgMBAAECgYAA/LlKJgeJUStTcpHgGD6mXjHvnAwWJELQKDP5+tA8"
b"VAQGwBX1G5qzJDGrPGtHQ7DSqdwF4YFZtgTpZmGq1wsAjz3lv6L4XiVsHiIPtP1B4gMx"
b"X9ogxcDzVQ7hyezXPioMAcp7Isus9Csn8HhftcL56BRabn6GvWqbIAy6zJcgEQJBAMlZ"
b"nymKW5/jKth+wkCfqEXlPhGNPO1uq87QZUbYxwdjtSM09J9+HMfH+WXR9ARCOL46DJ0I"
b"JfyjcdmuDDlh9IkCQQDt76up1Tmc7lkb/89IRBu2MudGJPMEf96VCG11nmcXulyk1OLi"
b"TXfO62YpxZbgYrvlrNxEYlSG7WQMztBgA51JAkBU2RhyJ+S+drsaaigvlVgSxCyotszi"
b"/Q0XZMgY18bfPUwanvkqsLkuEv3sw1HB7an9t3aTQdjIIpQad/acw8OJAkEAjvmnCK21"
b"KgTbjQShtQYgNNLPwImxcjG4OYvP4o6l2k9FHlNCZsQwSymOwWkXKYyK5g+CaKFBs7Zw"
b"mXWpJxjk6QJBAInqbm1w3yVfGD9I2mMQi/6oDJQP3pdWU4mU4h4sdDyRgTQLpkD4yypg"
b"jOACt4mTzxifSVT9fT+a79SkT8FFmZE="
)
public_der_data = base64.b64decode(
b"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7JHoJfg6yNzLMOWet8Z49a4KD0dCs"
b"pMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkkFPZk/7x0xmdsTPEC"
b"SWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAvSKAFKEvyD43si00DQnXW"
b"rYHAEQIDAQAB"
)
message = b""

def sign_with_rsa_key(key, message):
Expand Down Expand Up @@ -136,6 +159,82 @@ 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.

DER
~~~

DER is an ASN.1 encoding type. There are no encapsulation boundaries and the
data is binary. DER keys may be in a variety of formats, but as long as you
know whether it is a public or private key the loading functions will handle
the rest.

.. function:: load_der_private_key(data, password, backend)

.. versionadded:: 0.8

Deserialize a private key from DER encoded data to one of the supported
asymmetric private key types.

:param bytes data: The DER encoded key data.

:param bytes password: The password to use to decrypt the data. Should
be ``None`` if the private key is not encrypted.

:param backend: A
:class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`
provider.

:returns: A new instance of a private key.

:raises ValueError: If the DER data could not be decrypted or if its
structure could not be decoded successfully.

:raises TypeError: If a ``password`` was given and the private key was
not encrypted. Or if the key was encrypted but no
password was supplied.

:raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that
is not supported by the backend or if the key is encrypted with a
symmetric cipher that is not supported by the backend.

.. doctest::

>>> from cryptography.hazmat.backends import default_backend
>>> from cryptography.hazmat.primitives.asymmetric import rsa
>>> from cryptography.hazmat.primitives.serialization import load_der_private_key
>>> key = load_der_private_key(der_data, password=None, backend=default_backend())
>>> isinstance(key, rsa.RSAPrivateKey)
True

.. function:: load_der_public_key(data, backend)

.. versionadded:: 0.8

Deserialize a public key from DER encoded data to one of the supported
asymmetric public key types.

:param bytes data: The DER encoded key data.

:param backend: A
:class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`
provider.

:returns: A new instance of a public key.

: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 rsa
>>> from cryptography.hazmat.primitives.serialization import load_der_public_key
>>> key = load_der_public_key(public_der_data, backend=default_backend())
>>> isinstance(key, rsa.RSAPublicKey)
True


OpenSSH Public Key
~~~~~~~~~~~~~~~~~~
Expand Down
77 changes: 74 additions & 3 deletions src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
InternalError, UnsupportedAlgorithm, _Reasons
)
from cryptography.hazmat.backends.interfaces import (
CMACBackend, CipherBackend, DSABackend, EllipticCurveBackend, HMACBackend,
HashBackend, PBKDF2HMACBackend, PEMSerializationBackend, RSABackend,
X509Backend
CMACBackend, CipherBackend, DERSerializationBackend, DSABackend,
EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
PEMSerializationBackend, RSABackend, X509Backend
)
from cryptography.hazmat.backends.openssl.ciphers import (
_AESCTRCipherContext, _CipherContext
Expand Down Expand Up @@ -56,6 +56,7 @@

@utils.register_interface(CipherBackend)
@utils.register_interface(CMACBackend)
@utils.register_interface(DERSerializationBackend)
@utils.register_interface(DSABackend)
@utils.register_interface(EllipticCurveBackend)
@utils.register_interface(HashBackend)
Expand Down Expand Up @@ -696,6 +697,76 @@ def load_pem_public_key(self, data):
None,
)

def load_der_private_key(self, data, password):
# OpenSSL has a function called d2i_AutoPrivateKey that can simplify
# this. Unfortunately it doesn't properly support PKCS8 on OpenSSL
# 0.9.8 so we can't use it. Instead we sequentially try to load it 3
# different ways. First we'll try to load it as a traditional key
bio_data = self._bytes_to_bio(data)
key = self._evp_pkey_from_der_traditional_key(bio_data, password)
if not key:
# Okay so it's not a traditional key. Let's try
# PKCS8 unencrypted. OpenSSL 0.9.8 can't load unencrypted
# PKCS8 keys using d2i_PKCS8PrivateKey_bio so we do this instead.
# Reset the memory BIO so we can read the data again.
res = self._lib.BIO_reset(bio_data.bio)
assert res == 1
key = self._evp_pkey_from_der_unencrypted_pkcs8(bio_data, password)

if key:
return self._evp_pkey_to_private_key(key)
else:
# Finally we try to load it with the method that handles encrypted
# PKCS8 properly.
return self._load_key(
self._lib.d2i_PKCS8PrivateKey_bio,
self._evp_pkey_to_private_key,
data,
password,
)

def _evp_pkey_from_der_traditional_key(self, bio_data, password):
key = self._lib.d2i_PrivateKey_bio(bio_data.bio, self._ffi.NULL)
if key != self._ffi.NULL:
key = self._ffi.gc(key, self._lib.EVP_PKEY_free)
if password is not None:
raise TypeError(
"Password was given but private key is not encrypted."
)

return key
else:
self._consume_errors()
return None

def _evp_pkey_from_der_unencrypted_pkcs8(self, bio_data, password):
info = self._lib.d2i_PKCS8_PRIV_KEY_INFO_bio(
bio_data.bio, self._ffi.NULL
)
info = self._ffi.gc(info, self._lib.PKCS8_PRIV_KEY_INFO_free)
if info != self._ffi.NULL:
key = self._lib.EVP_PKCS82PKEY(info)
assert key != self._ffi.NULL
key = self._ffi.gc(key, self._lib.EVP_PKEY_free)
if password is not None:
raise TypeError(
"Password was given but private key is not encrypted."
)
return key
else:
self._consume_errors()
return None

def load_der_public_key(self, data):
mem_bio = self._bytes_to_bio(data)
evp_pkey = self._lib.d2i_PUBKEY_bio(mem_bio.bio, self._ffi.NULL)
if evp_pkey == self._ffi.NULL:
self._consume_errors()
raise ValueError("Could not unserialize key data.")

evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
return self._evp_pkey_to_public_key(evp_pkey)

def load_pem_x509_certificate(self, data):
mem_bio = self._bytes_to_bio(data)
x509 = self._lib.PEM_read_bio_X509(
Expand Down
1 change: 1 addition & 0 deletions src/cryptography/hazmat/bindings/openssl/x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
PKCS8_PRIV_KEY_INFO *d2i_PKCS8_PRIV_KEY_INFO_bio(BIO *,
PKCS8_PRIV_KEY_INFO **);
void PKCS8_PRIV_KEY_INFO_free(PKCS8_PRIV_KEY_INFO *);
"""

MACROS = """
Expand Down
8 changes: 8 additions & 0 deletions src/cryptography/hazmat/primitives/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def load_pem_public_key(data, backend):
return backend.load_pem_public_key(data)


def load_der_private_key(data, password, backend):
return backend.load_der_private_key(data, password)


def load_der_public_key(data, backend):
return backend.load_der_public_key(data)


def load_ssh_public_key(data, backend):
key_parts = data.split(b' ')

Expand Down

0 comments on commit 15c7f6f

Please sign in to comment.