From 99a249df88dcddb368c0f682a6aa8fc2bb1a279f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 4 Jan 2015 15:55:22 -0600 Subject: support DER public and private key loading in the openssl backend --- .../hazmat/backends/openssl/backend.py | 74 +++++++++++++++++++++- .../hazmat/primitives/serialization.py | 8 +++ 2 files changed, 79 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 8441e891..3e95c88b 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -15,9 +15,9 @@ from cryptography.exceptions import ( 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 @@ -56,6 +56,7 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError", @utils.register_interface(CipherBackend) @utils.register_interface(CMACBackend) +@utils.register_interface(DERSerializationBackend) @utils.register_interface(DSABackend) @utils.register_interface(EllipticCurveBackend) @utils.register_interface(HashBackend) @@ -696,6 +697,73 @@ class Backend(object): 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 + key = self._evp_pkey_from_der_traditional_key(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. + key = self._evp_pkey_from_der_unencrypted_pkcs8(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, data, password): + mem_bio = self._bytes_to_bio(data) + key = self._lib.d2i_PrivateKey_bio(mem_bio.bio, self._ffi.NULL) + if key != self._ffi.NULL: + if password is not None: + raise TypeError( + "Password was given but private key is not encrypted." + ) + + key = self._ffi.gc(key, self._lib.EVP_PKEY_free) + return key + else: + self._consume_errors() + return None + + def _evp_pkey_from_der_unencrypted_pkcs8(self, data, password): + mem_bio = self._bytes_to_bio(data) + info = self._lib.d2i_PKCS8_PRIV_KEY_INFO_bio( + mem_bio.bio, self._ffi.NULL + ) + if info != self._ffi.NULL: + key = self._lib.EVP_PKCS82PKEY(info) + assert key != self._ffi.NULL + if password is not None: + raise TypeError( + "Password was given but private key is not encrypted." + ) + key = self._ffi.gc(key, self._lib.EVP_PKEY_free) + 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( diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index dad419fe..0f9506e1 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -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' ') -- cgit v1.2.3 From e81438a422cade9a512bf4457629a663ccd18666 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 13 Feb 2015 08:35:44 -0600 Subject: move gc above potential exception --- src/cryptography/hazmat/backends/openssl/backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 3e95c88b..87ee1d7a 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -725,12 +725,12 @@ class Backend(object): mem_bio = self._bytes_to_bio(data) key = self._lib.d2i_PrivateKey_bio(mem_bio.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." ) - key = self._ffi.gc(key, self._lib.EVP_PKEY_free) return key else: self._consume_errors() @@ -744,11 +744,11 @@ class Backend(object): 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." ) - key = self._ffi.gc(key, self._lib.EVP_PKEY_free) return key else: self._consume_errors() -- cgit v1.2.3 From d43a6331cc9b63c8d2ee2a6b6d7514c3f1872c62 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 22 Feb 2015 13:33:13 -0600 Subject: free PKCS8_PRIV_KEY_INFO * and reuse membio for der loading --- src/cryptography/hazmat/backends/openssl/backend.py | 19 +++++++++++-------- src/cryptography/hazmat/bindings/openssl/x509.py | 1 + 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 87ee1d7a..eb23ef00 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -702,12 +702,16 @@ class Backend(object): # 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 - key = self._evp_pkey_from_der_traditional_key(data, password) + 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. - key = self._evp_pkey_from_der_unencrypted_pkcs8(data, password) + # 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) @@ -721,9 +725,8 @@ class Backend(object): password, ) - def _evp_pkey_from_der_traditional_key(self, data, password): - mem_bio = self._bytes_to_bio(data) - key = self._lib.d2i_PrivateKey_bio(mem_bio.bio, self._ffi.NULL) + 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: @@ -736,11 +739,11 @@ class Backend(object): self._consume_errors() return None - def _evp_pkey_from_der_unencrypted_pkcs8(self, data, password): - mem_bio = self._bytes_to_bio(data) + def _evp_pkey_from_der_unencrypted_pkcs8(self, bio_data, password): info = self._lib.d2i_PKCS8_PRIV_KEY_INFO_bio( - mem_bio.bio, self._ffi.NULL + 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 diff --git a/src/cryptography/hazmat/bindings/openssl/x509.py b/src/cryptography/hazmat/bindings/openssl/x509.py index e30d23b7..585a2e90 100644 --- a/src/cryptography/hazmat/bindings/openssl/x509.py +++ b/src/cryptography/hazmat/bindings/openssl/x509.py @@ -229,6 +229,7 @@ int i2d_DSAPrivateKey_bio(BIO *, DSA *); 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 = """ -- cgit v1.2.3