diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2014-05-22 14:40:42 -0500 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2014-05-22 14:40:42 -0500 |
commit | ebf1235e7ebbd84d2f1e05a060c6df25adb58353 (patch) | |
tree | 9285520022c4fb7d626e10991edc9e3a66c44fc5 /cryptography | |
parent | 22f7dfbb3d916ff916b0c1db4692754d79afa066 (diff) | |
parent | 307b6cbe1fb712e36307d615668ebd18b1e3739c (diff) | |
download | cryptography-ebf1235e7ebbd84d2f1e05a060c6df25adb58353.tar.gz cryptography-ebf1235e7ebbd84d2f1e05a060c6df25adb58353.tar.bz2 cryptography-ebf1235e7ebbd84d2f1e05a060c6df25adb58353.zip |
Merge pull request #959 from public/openssl-loading-backend
OpenSSL loading backend
Diffstat (limited to 'cryptography')
-rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 144 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/serialization.py | 20 |
2 files changed, 163 insertions, 1 deletions
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index cd2a614e..5d9626d0 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -26,7 +26,7 @@ from cryptography.exceptions import ( ) from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend, - PBKDF2HMACBackend, RSABackend + PBKDF2HMACBackend, RSABackend, TraditionalOpenSSLSerializationBackend ) from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, interfaces @@ -42,6 +42,7 @@ from cryptography.hazmat.primitives.ciphers.modes import ( ) +_MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) _OpenSSLError = collections.namedtuple("_OpenSSLError", ["code", "lib", "func", "reason"]) @@ -53,6 +54,7 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError", @utils.register_interface(HMACBackend) @utils.register_interface(PBKDF2HMACBackend) @utils.register_interface(RSABackend) +@utils.register_interface(TraditionalOpenSSLSerializationBackend) class Backend(object): """ OpenSSL API binding interfaces. @@ -399,6 +401,51 @@ class Backend(object): return evp_pkey + def _bytes_to_bio(self, data): + """ + Return a _MemoryBIO namedtuple of (BIO, char*). + + The char* is the storage for the BIO and it must stay alive until the + BIO is finished with. + """ + data_char_p = backend._ffi.new("char[]", data) + bio = backend._lib.BIO_new_mem_buf( + data_char_p, len(data) + ) + assert bio != self._ffi.NULL + + return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_char_p) + + def _evp_pkey_to_private_key(self, evp_pkey): + """ + Return the appropriate type of PrivateKey given an evp_pkey cdata + pointer. + """ + + type = evp_pkey.type + + if type == self._lib.EVP_PKEY_RSA: + rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) + assert rsa_cdata != self._ffi.NULL + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + return self._rsa_cdata_to_private_key(rsa_cdata) + elif type == self._lib.EVP_PKEY_DSA: + dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) + assert dsa_cdata != self._ffi.NULL + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + return self._dsa_cdata_to_private_key(dsa_cdata) + else: + raise UnsupportedAlgorithm("Unsupported key type.") + + def _dsa_cdata_to_private_key(self, cdata): + return dsa.DSAPrivateKey( + modulus=self._bn_to_int(cdata.p), + subgroup_order=self._bn_to_int(cdata.q), + generator=self._bn_to_int(cdata.g), + x=self._bn_to_int(cdata.priv_key), + y=self._bn_to_int(cdata.pub_key) + ) + def _rsa_cdata_to_private_key(self, cdata): return rsa.RSAPrivateKey( p=self._bn_to_int(cdata.p), @@ -411,6 +458,37 @@ class Backend(object): modulus=self._bn_to_int(cdata.n), ) + def _pem_password_cb(self, password): + """ + Generate a pem_password_cb function pointer that copied the password to + OpenSSL as required and returns the number of bytes copied. + + typedef int pem_password_cb(char *buf, int size, + int rwflag, void *userdata); + + Useful for decrypting PKCS8 files and so on. + + Returns a tuple of (cdata function pointer, callback function). + """ + + def pem_password_cb(buf, size, writing, userdata): + pem_password_cb.called += 1 + + if not password or len(password) >= size: + return 0 + else: + pw_buf = self._ffi.buffer(buf, size) + pw_buf[:len(password)] = password + return len(password) + + pem_password_cb.called = 0 + + return ( + self._ffi.callback("int (char *, int, int, void *)", + pem_password_cb), + pem_password_cb + ) + def _rsa_cdata_from_private_key(self, private_key): # Does not GC the RSA cdata. You *must* make sure it's freed # correctly yourself! @@ -684,6 +762,70 @@ class Backend(object): def create_cmac_ctx(self, algorithm): return _CMACContext(self, algorithm) + def load_traditional_openssl_pem_private_key(self, data, password): + mem_bio = self._bytes_to_bio(data) + + password_callback, password_func = self._pem_password_cb(password) + + evp_pkey = self._lib.PEM_read_bio_PrivateKey( + mem_bio.bio, + self._ffi.NULL, + password_callback, + self._ffi.NULL + ) + + if evp_pkey == self._ffi.NULL: + errors = self._consume_errors() + if not errors: + raise ValueError("Could not unserialize key data.") + + if errors[0][1:] == ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_DO_HEADER, + self._lib.PEM_R_BAD_PASSWORD_READ + ): + assert not password + raise TypeError( + "Password was not given but private key is encrypted.") + + elif errors[0][1:] == ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_DECRYPTFINAL_EX, + self._lib.EVP_R_BAD_DECRYPT + ): + raise ValueError( + "Bad decrypt. Incorrect password?" + ) + + elif errors[0][1:] == ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_GET_EVP_CIPHER_INFO, + self._lib.PEM_R_UNSUPPORTED_ENCRYPTION + ): + raise UnsupportedAlgorithm( + "PEM data is encrypted with an unsupported cipher") + + else: + assert errors[0][1] in ( + self._lib.ERR_LIB_EVP, + self._lib.ERR_LIB_PEM, + self._lib.ERR_LIB_ASN1, + ) + raise ValueError("Could not unserialize key data.") + + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + + if password is not None and password_func.called == 0: + raise TypeError( + "Password was given but private key is not encrypted.") + + assert ( + (password is not None and password_func.called == 1) or + password is None + ) + + return self._evp_pkey_to_private_key(evp_pkey) + class GetCipherByName(object): def __init__(self, fmt): diff --git a/cryptography/hazmat/primitives/serialization.py b/cryptography/hazmat/primitives/serialization.py new file mode 100644 index 00000000..38937508 --- /dev/null +++ b/cryptography/hazmat/primitives/serialization.py @@ -0,0 +1,20 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + + +def load_pem_traditional_openssl_private_key(data, password, backend): + return backend.load_traditional_openssl_pem_private_key( + data, password + ) |