diff options
Diffstat (limited to 'cryptography/hazmat')
21 files changed, 1057 insertions, 20 deletions
diff --git a/cryptography/hazmat/backends/__init__.py b/cryptography/hazmat/backends/__init__.py index 215aa4d3..cb1fee90 100644 --- a/cryptography/hazmat/backends/__init__.py +++ b/cryptography/hazmat/backends/__init__.py @@ -12,11 +12,15 @@ # limitations under the License. from cryptography.hazmat.backends import openssl +from cryptography.hazmat.bindings.commoncrypto.binding import ( + Binding as CCBinding +) +_ALL_BACKENDS = [openssl.backend] -_ALL_BACKENDS = [ - openssl.backend -] +if CCBinding.is_available(): + from cryptography.hazmat.backends import commoncrypto + _ALL_BACKENDS.append(commoncrypto.backend) def default_backend(): diff --git a/cryptography/hazmat/backends/commoncrypto/__init__.py b/cryptography/hazmat/backends/commoncrypto/__init__.py new file mode 100644 index 00000000..64a1c01c --- /dev/null +++ b/cryptography/hazmat/backends/commoncrypto/__init__.py @@ -0,0 +1,17 @@ +# 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 cryptography.hazmat.backends.commoncrypto.backend import backend + + +__all__ = ["backend"] diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py new file mode 100644 index 00000000..e5d4ee00 --- /dev/null +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -0,0 +1,482 @@ +# 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 + +from collections import namedtuple + +from cryptography import utils +from cryptography.exceptions import ( + UnsupportedAlgorithm, InvalidTag, InternalError +) +from cryptography.hazmat.backends.interfaces import ( + HashBackend, HMACBackend, CipherBackend, PBKDF2HMACBackend +) +from cryptography.hazmat.bindings.commoncrypto.binding import Binding +from cryptography.hazmat.primitives import interfaces, constant_time +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, Blowfish, TripleDES, ARC4 +) +from cryptography.hazmat.primitives.ciphers.modes import ( + CBC, CTR, ECB, OFB, CFB, GCM +) + + +HashMethods = namedtuple( + "HashMethods", ["ctx", "hash_init", "hash_update", "hash_final"] +) + + +@utils.register_interface(CipherBackend) +@utils.register_interface(HashBackend) +@utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2HMACBackend) +class Backend(object): + """ + CommonCrypto API wrapper. + """ + name = "commoncrypto" + + def __init__(self): + self._binding = Binding() + self._ffi = self._binding.ffi + self._lib = self._binding.lib + + self._cipher_registry = {} + self._register_default_ciphers() + self._hash_mapping = { + "md5": HashMethods( + "CC_MD5_CTX *", self._lib.CC_MD5_Init, + self._lib.CC_MD5_Update, self._lib.CC_MD5_Final + ), + "sha1": HashMethods( + "CC_SHA1_CTX *", self._lib.CC_SHA1_Init, + self._lib.CC_SHA1_Update, self._lib.CC_SHA1_Final + ), + "sha224": HashMethods( + "CC_SHA256_CTX *", self._lib.CC_SHA224_Init, + self._lib.CC_SHA224_Update, self._lib.CC_SHA224_Final + ), + "sha256": HashMethods( + "CC_SHA256_CTX *", self._lib.CC_SHA256_Init, + self._lib.CC_SHA256_Update, self._lib.CC_SHA256_Final + ), + "sha384": HashMethods( + "CC_SHA512_CTX *", self._lib.CC_SHA384_Init, + self._lib.CC_SHA384_Update, self._lib.CC_SHA384_Final + ), + "sha512": HashMethods( + "CC_SHA512_CTX *", self._lib.CC_SHA512_Init, + self._lib.CC_SHA512_Update, self._lib.CC_SHA512_Final + ), + } + + self._supported_hmac_algorithms = { + "md5": self._lib.kCCHmacAlgMD5, + "sha1": self._lib.kCCHmacAlgSHA1, + "sha224": self._lib.kCCHmacAlgSHA224, + "sha256": self._lib.kCCHmacAlgSHA256, + "sha384": self._lib.kCCHmacAlgSHA384, + "sha512": self._lib.kCCHmacAlgSHA512, + } + + self._supported_pbkdf2_hmac_algorithms = { + "sha1": self._lib.kCCPRFHmacAlgSHA1, + "sha224": self._lib.kCCPRFHmacAlgSHA224, + "sha256": self._lib.kCCPRFHmacAlgSHA256, + "sha384": self._lib.kCCPRFHmacAlgSHA384, + "sha512": self._lib.kCCPRFHmacAlgSHA512, + } + + def hash_supported(self, algorithm): + return algorithm.name in self._hash_mapping + + def hmac_supported(self, algorithm): + return algorithm.name in self._supported_hmac_algorithms + + def create_hash_ctx(self, algorithm): + return _HashContext(self, algorithm) + + def create_hmac_ctx(self, key, algorithm): + return _HMACContext(self, key, algorithm) + + def cipher_supported(self, cipher, mode): + return (type(cipher), type(mode)) in self._cipher_registry + + def create_symmetric_encryption_ctx(self, cipher, mode): + if isinstance(mode, GCM): + return _GCMCipherContext( + self, cipher, mode, self._lib.kCCEncrypt + ) + else: + return _CipherContext(self, cipher, mode, self._lib.kCCEncrypt) + + def create_symmetric_decryption_ctx(self, cipher, mode): + if isinstance(mode, GCM): + return _GCMCipherContext( + self, cipher, mode, self._lib.kCCDecrypt + ) + else: + return _CipherContext(self, cipher, mode, self._lib.kCCDecrypt) + + def pbkdf2_hmac_supported(self, algorithm): + return algorithm.name in self._supported_pbkdf2_hmac_algorithms + + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + alg_enum = self._supported_pbkdf2_hmac_algorithms[algorithm.name] + buf = self._ffi.new("char[]", length) + res = self._lib.CCKeyDerivationPBKDF( + self._lib.kCCPBKDF2, + key_material, + len(key_material), + salt, + len(salt), + alg_enum, + iterations, + buf, + length + ) + self._check_response(res) + + return self._ffi.buffer(buf)[:] + + def _register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, + mode_const): + if (cipher_cls, mode_cls) in self._cipher_registry: + raise ValueError("Duplicate registration for: {0} {1}".format( + cipher_cls, mode_cls) + ) + self._cipher_registry[cipher_cls, mode_cls] = (cipher_const, + mode_const) + + def _register_default_ciphers(self): + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB), + (CTR, self._lib.kCCModeCTR), + (GCM, self._lib.kCCModeGCM), + ]: + self._register_cipher_adapter( + AES, + self._lib.kCCAlgorithmAES128, + mode_cls, + mode_const + ) + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), + (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB), + ]: + self._register_cipher_adapter( + TripleDES, + self._lib.kCCAlgorithm3DES, + mode_cls, + mode_const + ) + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB) + ]: + self._register_cipher_adapter( + Blowfish, + self._lib.kCCAlgorithmBlowfish, + mode_cls, + mode_const + ) + self._register_cipher_adapter( + ARC4, + self._lib.kCCAlgorithmRC4, + type(None), + self._lib.kCCModeRC4 + ) + + def _check_response(self, response): + if response == self._lib.kCCSuccess: + return + elif response == self._lib.kCCAlignmentError: + # This error is not currently triggered due to a bug filed as + # rdar://15589470 + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length" + ) + else: + raise InternalError( + "The backend returned an unknown error, consider filing a bug." + " Code: {0}.".format(response) + ) + + +def _release_cipher_ctx(ctx): + """ + Called by the garbage collector and used to safely dereference and + release the context. + """ + if ctx[0] != backend._ffi.NULL: + res = backend._lib.CCCryptorRelease(ctx[0]) + backend._check_response(res) + ctx[0] = backend._ffi.NULL + + +@utils.register_interface(interfaces.CipherContext) +class _CipherContext(object): + def __init__(self, backend, cipher, mode, operation): + self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + # There is a bug in CommonCrypto where block ciphers do not raise + # kCCAlignmentError when finalizing if you supply non-block aligned + # data. To work around this we need to keep track of the block + # alignment ourselves, but only for alg+mode combos that require + # block alignment. OFB, CFB, and CTR make a block cipher algorithm + # into a stream cipher so we don't need to track them (and thus their + # block size is effectively 1 byte just like OpenSSL/CommonCrypto + # treat RC4 and other stream cipher block sizes). + # This bug has been filed as rdar://15589470 + self._bytes_processed = 0 + if (isinstance(cipher, interfaces.BlockCipherAlgorithm) and not + isinstance(mode, (OFB, CFB, CTR))): + self._byte_block_size = cipher.block_size // 8 + else: + self._byte_block_size = 1 + + registry = self._backend._cipher_registry + try: + cipher_enum, mode_enum = registry[type(cipher), type(mode)] + except KeyError: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend".format( + cipher.name, mode.name if mode else mode) + ) + + ctx = self._backend._ffi.new("CCCryptorRef *") + ctx = self._backend._ffi.gc(ctx, _release_cipher_ctx) + + if isinstance(mode, interfaces.ModeWithInitializationVector): + iv_nonce = mode.initialization_vector + elif isinstance(mode, interfaces.ModeWithNonce): + iv_nonce = mode.nonce + else: + iv_nonce = self._backend._ffi.NULL + + if isinstance(mode, CTR): + mode_option = self._backend._lib.kCCModeOptionCTR_BE + else: + mode_option = 0 + + res = self._backend._lib.CCCryptorCreateWithMode( + operation, + mode_enum, cipher_enum, + self._backend._lib.ccNoPadding, iv_nonce, + cipher.key, len(cipher.key), + self._backend._ffi.NULL, 0, 0, mode_option, ctx) + self._backend._check_response(res) + + self._ctx = ctx + + def update(self, data): + # Count bytes processed to handle block alignment. + self._bytes_processed += len(data) + buf = self._backend._ffi.new( + "unsigned char[]", len(data) + self._byte_block_size - 1) + outlen = self._backend._ffi.new("size_t *") + res = self._backend._lib.CCCryptorUpdate( + self._ctx[0], data, len(data), buf, + len(data) + self._byte_block_size - 1, outlen) + self._backend._check_response(res) + return self._backend._ffi.buffer(buf)[:outlen[0]] + + def finalize(self): + # Raise error if block alignment is wrong. + if self._bytes_processed % self._byte_block_size: + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length" + ) + buf = self._backend._ffi.new("unsigned char[]", self._byte_block_size) + outlen = self._backend._ffi.new("size_t *") + res = self._backend._lib.CCCryptorFinal( + self._ctx[0], buf, len(buf), outlen) + self._backend._check_response(res) + _release_cipher_ctx(self._ctx) + return self._backend._ffi.buffer(buf)[:outlen[0]] + + +@utils.register_interface(interfaces.AEADCipherContext) +@utils.register_interface(interfaces.AEADEncryptionContext) +class _GCMCipherContext(object): + def __init__(self, backend, cipher, mode, operation): + self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + self._tag = None + + registry = self._backend._cipher_registry + try: + cipher_enum, mode_enum = registry[type(cipher), type(mode)] + except KeyError: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend".format( + cipher.name, mode.name if mode else mode) + ) + + ctx = self._backend._ffi.new("CCCryptorRef *") + ctx = self._backend._ffi.gc(ctx, _release_cipher_ctx) + + self._ctx = ctx + + res = self._backend._lib.CCCryptorCreateWithMode( + operation, + mode_enum, cipher_enum, + self._backend._lib.ccNoPadding, + self._backend._ffi.NULL, + cipher.key, len(cipher.key), + self._backend._ffi.NULL, 0, 0, 0, self._ctx) + self._backend._check_response(res) + + res = self._backend._lib.CCCryptorGCMAddIV( + self._ctx[0], + mode.initialization_vector, + len(mode.initialization_vector) + ) + self._backend._check_response(res) + + def update(self, data): + buf = self._backend._ffi.new("unsigned char[]", len(data)) + args = (self._ctx[0], data, len(data), buf) + if self._operation == self._backend._lib.kCCEncrypt: + res = self._backend._lib.CCCryptorGCMEncrypt(*args) + else: + res = self._backend._lib.CCCryptorGCMDecrypt(*args) + + self._backend._check_response(res) + return self._backend._ffi.buffer(buf)[:] + + def finalize(self): + tag_size = self._cipher.block_size // 8 + tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) + tag_len = self._backend._ffi.new("size_t *", tag_size) + res = backend._lib.CCCryptorGCMFinal(self._ctx[0], tag_buf, tag_len) + self._backend._check_response(res) + _release_cipher_ctx(self._ctx) + self._tag = self._backend._ffi.buffer(tag_buf)[:] + if (self._operation == self._backend._lib.kCCDecrypt and + not constant_time.bytes_eq( + self._tag[:len(self._mode.tag)], self._mode.tag + )): + raise InvalidTag + return b"" + + def authenticate_additional_data(self, data): + res = self._backend._lib.CCCryptorGCMAddAAD( + self._ctx[0], data, len(data) + ) + self._backend._check_response(res) + + @property + def tag(self): + return self._tag + + +@utils.register_interface(interfaces.HashContext) +class _HashContext(object): + def __init__(self, backend, algorithm, ctx=None): + self.algorithm = algorithm + self._backend = backend + + if ctx is None: + try: + methods = self._backend._hash_mapping[self.algorithm.name] + except KeyError: + raise UnsupportedAlgorithm( + "{0} is not a supported hash on this backend".format( + algorithm.name) + ) + ctx = self._backend._ffi.new(methods.ctx) + res = methods.hash_init(ctx) + assert res == 1 + + self._ctx = ctx + + def copy(self): + methods = self._backend._hash_mapping[self.algorithm.name] + new_ctx = self._backend._ffi.new(methods.ctx) + # CommonCrypto has no APIs for copying hashes, so we have to copy the + # underlying struct. + new_ctx[0] = self._ctx[0] + + return _HashContext(self._backend, self.algorithm, ctx=new_ctx) + + def update(self, data): + methods = self._backend._hash_mapping[self.algorithm.name] + res = methods.hash_update(self._ctx, data, len(data)) + assert res == 1 + + def finalize(self): + methods = self._backend._hash_mapping[self.algorithm.name] + buf = self._backend._ffi.new("unsigned char[]", + self.algorithm.digest_size) + res = methods.hash_final(buf, self._ctx) + assert res == 1 + return self._backend._ffi.buffer(buf)[:] + + +@utils.register_interface(interfaces.HashContext) +class _HMACContext(object): + def __init__(self, backend, key, algorithm, ctx=None): + self.algorithm = algorithm + self._backend = backend + if ctx is None: + ctx = self._backend._ffi.new("CCHmacContext *") + try: + alg = self._backend._supported_hmac_algorithms[algorithm.name] + except KeyError: + raise UnsupportedAlgorithm( + "{0} is not a supported HMAC hash on this backend".format( + algorithm.name) + ) + + self._backend._lib.CCHmacInit(ctx, alg, key, len(key)) + + self._ctx = ctx + self._key = key + + def copy(self): + copied_ctx = self._backend._ffi.new("CCHmacContext *") + # CommonCrypto has no APIs for copying HMACs, so we have to copy the + # underlying struct. + copied_ctx[0] = self._ctx[0] + return _HMACContext( + self._backend, self._key, self.algorithm, ctx=copied_ctx + ) + + def update(self, data): + self._backend._lib.CCHmacUpdate(self._ctx, data, len(data)) + + def finalize(self): + buf = self._backend._ffi.new("unsigned char[]", + self.algorithm.digest_size) + self._backend._lib.CCHmacFinal(self._ctx, buf) + return self._backend._ffi.buffer(buf)[:] + + +backend = Backend() diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 9a570968..53c75181 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -26,12 +26,6 @@ class CipherBackend(six.with_metaclass(abc.ABCMeta)): """ @abc.abstractmethod - def register_cipher_adapter(self, cipher, mode, adapter): - """ - Register an adapter for a cipher and mode to a backend specific object. - """ - - @abc.abstractmethod def create_symmetric_encryption_ctx(self, cipher, mode): """ Get a CipherContext that can be used for encryption. @@ -71,3 +65,19 @@ class HMACBackend(six.with_metaclass(abc.ABCMeta)): """ Create a HashContext for calculating a message authentication code. """ + + +class PBKDF2HMACBackend(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def pbkdf2_hmac_supported(self, algorithm): + """ + Return True if the hash algorithm is supported for PBKDF2 by this + backend. + """ + + @abc.abstractmethod + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + """ + Return length bytes derived from provided PBKDF2 parameters. + """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 07ee58c1..cf931dab 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -16,11 +16,13 @@ from __future__ import absolute_import, division, print_function import itertools from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag +from cryptography.exceptions import ( + UnsupportedAlgorithm, InvalidTag, InternalError +) from cryptography.hazmat.backends.interfaces import ( - CipherBackend, HashBackend, HMACBackend + CipherBackend, HashBackend, HMACBackend, PBKDF2HMACBackend ) -from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives import interfaces, hashes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, Blowfish, Camellia, TripleDES, ARC4, ) @@ -33,16 +35,20 @@ from cryptography.hazmat.bindings.openssl.binding import Binding @utils.register_interface(CipherBackend) @utils.register_interface(HashBackend) @utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2HMACBackend) class Backend(object): """ OpenSSL API binding interfaces. """ + name = "openssl" def __init__(self): self._binding = Binding() self._ffi = self._binding.ffi self._lib = self._binding.lib + self._binding.init_static_locks() + # adds all ciphers/digests for EVP self._lib.OpenSSL_add_all_algorithms() # registers available SSL/TLS ciphers and digests @@ -128,6 +134,49 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + def pbkdf2_hmac_supported(self, algorithm): + if self._lib.Cryptography_HAS_PBKDF2_HMAC: + return self.hmac_supported(algorithm) + else: + # OpenSSL < 1.0.0 has an explicit PBKDF2-HMAC-SHA1 function, + # so if the PBKDF2_HMAC function is missing we only support + # SHA1 via PBKDF2_HMAC_SHA1. + return isinstance(algorithm, hashes.SHA1) + + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + buf = self._ffi.new("char[]", length) + if self._lib.Cryptography_HAS_PBKDF2_HMAC: + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + assert evp_md != self._ffi.NULL + res = self._lib.PKCS5_PBKDF2_HMAC( + key_material, + len(key_material), + salt, + len(salt), + iterations, + evp_md, + length, + buf + ) + assert res == 1 + else: + # OpenSSL < 1.0.0 + assert isinstance(algorithm, hashes.SHA1) + res = self._lib.PKCS5_PBKDF2_HMAC_SHA1( + key_material, + len(key_material), + salt, + len(salt), + iterations, + length, + buf + ) + assert res == 1 + + return self._ffi.buffer(buf)[:] + def _handle_error(self, mode): code = self._lib.ERR_get_error() if not code and isinstance(mode, GCM): @@ -153,7 +202,7 @@ class Backend(object): "the block length" ) - raise SystemError( + raise InternalError( "Unknown error code from OpenSSL, you should probably file a bug." ) diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py index 9c1af40a..45c0eaad 100644 --- a/cryptography/hazmat/bindings/commoncrypto/binding.py +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -26,6 +26,8 @@ class Binding(object): _modules = [ "common_digest", "common_hmac", + "common_key_derivation", + "common_cryptor", ] ffi = None diff --git a/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py new file mode 100644 index 00000000..8f03bc3f --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py @@ -0,0 +1,108 @@ +# 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. + +INCLUDES = """ +#include <CommonCrypto/CommonCryptor.h> +""" + +TYPES = """ +enum { + kCCAlgorithmAES128 = 0, + kCCAlgorithmDES, + kCCAlgorithm3DES, + kCCAlgorithmCAST, + kCCAlgorithmRC4, + kCCAlgorithmRC2, + kCCAlgorithmBlowfish +}; +typedef uint32_t CCAlgorithm; +enum { + kCCSuccess = 0, + kCCParamError = -4300, + kCCBufferTooSmall = -4301, + kCCMemoryFailure = -4302, + kCCAlignmentError = -4303, + kCCDecodeError = -4304, + kCCUnimplemented = -4305 +}; +typedef int32_t CCCryptorStatus; +typedef uint32_t CCOptions; +enum { + kCCEncrypt = 0, + kCCDecrypt, +}; +typedef uint32_t CCOperation; +typedef ... *CCCryptorRef; + +enum { + kCCModeOptionCTR_LE = 0x0001, + kCCModeOptionCTR_BE = 0x0002 +}; + +typedef uint32_t CCModeOptions; + +enum { + kCCModeECB = 1, + kCCModeCBC = 2, + kCCModeCFB = 3, + kCCModeCTR = 4, + kCCModeF8 = 5, + kCCModeLRW = 6, + kCCModeOFB = 7, + kCCModeXTS = 8, + kCCModeRC4 = 9, + kCCModeCFB8 = 10, + kCCModeGCM = 11 +}; +typedef uint32_t CCMode; +enum { + ccNoPadding = 0, + ccPKCS7Padding = 1, +}; +typedef uint32_t CCPadding; +""" + +FUNCTIONS = """ +CCCryptorStatus CCCryptorCreateWithMode(CCOperation, CCMode, CCAlgorithm, + CCPadding, const void *, const void *, + size_t, const void *, size_t, int, + CCModeOptions, CCCryptorRef *); +CCCryptorStatus CCCryptorCreate(CCOperation, CCAlgorithm, CCOptions, + const void *, size_t, const void *, + CCCryptorRef *); +CCCryptorStatus CCCryptorUpdate(CCCryptorRef, const void *, size_t, void *, + size_t, size_t *); +CCCryptorStatus CCCryptorFinal(CCCryptorRef, void *, size_t, size_t *); +CCCryptorStatus CCCryptorRelease(CCCryptorRef); + +CCCryptorStatus CCCryptorGCMAddIV(CCCryptorRef, const void *, size_t); +CCCryptorStatus CCCryptorGCMAddAAD(CCCryptorRef, const void *, size_t); +CCCryptorStatus CCCryptorGCMEncrypt(CCCryptorRef, const void *, size_t, + void *); +CCCryptorStatus CCCryptorGCMDecrypt(CCCryptorRef, const void *, size_t, + void *); +CCCryptorStatus CCCryptorGCMFinal(CCCryptorRef, const void *, size_t *); +CCCryptorStatus CCCryptorGCMReset(CCCryptorRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +// Not defined in the public header +enum { + kCCModeGCM = 11 +}; +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py b/cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py new file mode 100644 index 00000000..85def1e9 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py @@ -0,0 +1,48 @@ +# 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. + +INCLUDES = """ +#include <CommonCrypto/CommonKeyDerivation.h> +""" + +TYPES = """ +enum { + kCCPBKDF2 = 2, +}; +typedef uint32_t CCPBKDFAlgorithm; +enum { + kCCPRFHmacAlgSHA1 = 1, + kCCPRFHmacAlgSHA224 = 2, + kCCPRFHmacAlgSHA256 = 3, + kCCPRFHmacAlgSHA384 = 4, + kCCPRFHmacAlgSHA512 = 5, +}; +typedef uint32_t CCPseudoRandomAlgorithm; +typedef unsigned int uint; +""" + +FUNCTIONS = """ +int CCKeyDerivationPBKDF(CCPBKDFAlgorithm, const char *, size_t, + const uint8_t *, size_t, CCPseudoRandomAlgorithm, + uint, uint8_t *, size_t); +uint CCCalibratePBKDF(CCPBKDFAlgorithm, size_t, size_t, + CCPseudoRandomAlgorithm, size_t, uint32_t); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/openssl/aes.py b/cryptography/hazmat/bindings/openssl/aes.py new file mode 100644 index 00000000..6cbcd577 --- /dev/null +++ b/cryptography/hazmat/bindings/openssl/aes.py @@ -0,0 +1,40 @@ +# 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. + +INCLUDES = """ +#include <openssl/aes.h> +""" + +TYPES = """ +struct aes_key_st { + ...; +}; +typedef struct aes_key_st AES_KEY; +""" + +FUNCTIONS = """ +int AES_set_encrypt_key(const unsigned char *, const int, AES_KEY *); +int AES_set_decrypt_key(const unsigned char *, const int, AES_KEY *); +int AES_wrap_key(AES_KEY *, const unsigned char *, unsigned char *, + const unsigned char *, unsigned int); +int AES_unwrap_key(AES_KEY *, const unsigned char *, unsigned char *, + const unsigned char *, unsigned int); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/openssl/bignum.py b/cryptography/hazmat/bindings/openssl/bignum.py index 59efd171..6545f329 100644 --- a/cryptography/hazmat/bindings/openssl/bignum.py +++ b/cryptography/hazmat/bindings/openssl/bignum.py @@ -47,6 +47,9 @@ char *BN_bn2hex(const BIGNUM *); int BN_hex2bn(BIGNUM **, const char *); int BN_dec2bn(BIGNUM **, const char *); +int BN_bn2bin(const BIGNUM *, unsigned char *); +BIGNUM *BN_bin2bn(const unsigned char *, int, BIGNUM *); + int BN_num_bits(const BIGNUM *); """ diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 88299d14..cde3bdbd 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,6 +13,9 @@ from __future__ import absolute_import, division, print_function +import sys +import threading + from cryptography.hazmat.bindings.utils import build_ffi @@ -41,6 +44,7 @@ class Binding(object): """ _module_prefix = "cryptography.hazmat.bindings.openssl." _modules = [ + "aes", "asn1", "bignum", "bio", @@ -67,6 +71,10 @@ class Binding(object): "x509v3", ] + _locks = None + _lock_cb_handle = None + _lock_init_lock = threading.Lock() + ffi = None lib = None @@ -78,11 +86,57 @@ class Binding(object): if cls.ffi is not None and cls.lib is not None: return + # platform check to set the right library names + if sys.platform != "win32": + libraries = ["crypto", "ssl"] + else: # pragma: no cover + libraries = ["libeay32", "ssleay32"] + cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, - ["crypto", "ssl"]) + libraries) @classmethod def is_available(cls): # OpenSSL is the only binding so for now it must always be available return True + + @classmethod + def init_static_locks(cls): + with cls._lock_init_lock: + cls._ensure_ffi_initialized() + + if not cls._lock_cb_handle: + cls._lock_cb_handle = cls.ffi.callback( + "void(int, int, const char *, int)", + cls._lock_cb + ) + + # use Python's implementation if available + + __import__("_ssl") + + if cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL: + return + + # otherwise setup our version + + num_locks = cls.lib.CRYPTO_num_locks() + cls._locks = [threading.Lock() for n in range(num_locks)] + + cls.lib.CRYPTO_set_locking_callback(cls._lock_cb_handle) + + @classmethod + def _lock_cb(cls, mode, n, file, line): + lock = cls._locks[n] + + if mode & cls.lib.CRYPTO_LOCK: + lock.acquire() + elif mode & cls.lib.CRYPTO_UNLOCK: + lock.release() + else: + raise RuntimeError( + "Unknown lock mode {0}: lock={1}, file={2}, line={3}".format( + mode, n, file, line + ) + ) diff --git a/cryptography/hazmat/bindings/openssl/crypto.py b/cryptography/hazmat/bindings/openssl/crypto.py index 40d91bf2..81d13b73 100644 --- a/cryptography/hazmat/bindings/openssl/crypto.py +++ b/cryptography/hazmat/bindings/openssl/crypto.py @@ -27,6 +27,11 @@ static const int CRYPTO_MEM_CHECK_ON; static const int CRYPTO_MEM_CHECK_OFF; static const int CRYPTO_MEM_CHECK_ENABLE; static const int CRYPTO_MEM_CHECK_DISABLE; +static const int CRYPTO_LOCK; +static const int CRYPTO_UNLOCK; +static const int CRYPTO_READ; +static const int CRYPTO_WRITE; +static const int CRYPTO_LOCK_SSL; """ FUNCTIONS = """ @@ -43,6 +48,7 @@ void CRYPTO_set_locking_callback(void(*)(int, int, const char *, int)); void CRYPTO_set_id_callback(unsigned long (*)(void)); unsigned long (*CRYPTO_get_id_callback(void))(void); void (*CRYPTO_get_locking_callback(void))(int, int, const char *, int); +void CRYPTO_lock(int, int, const char *, int); void OPENSSL_free(void *); """ @@ -51,7 +57,6 @@ MACROS = """ void CRYPTO_add(int *, int, int); void CRYPTO_malloc_init(void); void CRYPTO_malloc_debug_init(void); - """ CUSTOMIZATIONS = """ diff --git a/cryptography/hazmat/bindings/openssl/dh.py b/cryptography/hazmat/bindings/openssl/dh.py index 3c12fbc6..ecc62e98 100644 --- a/cryptography/hazmat/bindings/openssl/dh.py +++ b/cryptography/hazmat/bindings/openssl/dh.py @@ -16,7 +16,17 @@ INCLUDES = """ """ TYPES = """ -typedef ... DH; +typedef struct dh_st { + // prime number (shared) + BIGNUM *p; + // generator of Z_p (shared) + BIGNUM *g; + // private DH value x + BIGNUM *priv_key; + // public DH value g^x + BIGNUM *pub_key; + ...; +} DH; """ FUNCTIONS = """ diff --git a/cryptography/hazmat/bindings/openssl/dsa.py b/cryptography/hazmat/bindings/openssl/dsa.py index 3b77d7ae..609a33bf 100644 --- a/cryptography/hazmat/bindings/openssl/dsa.py +++ b/cryptography/hazmat/bindings/openssl/dsa.py @@ -16,7 +16,19 @@ INCLUDES = """ """ TYPES = """ -typedef ... DSA; +typedef struct dsa_st { + // prime number (public) + BIGNUM *p; + // 160-bit subprime, q | p-1 (public) + BIGNUM *q; + // generator of subgroup (public) + BIGNUM *g; + // private key x + BIGNUM *priv_key; + // public key y = g^x + BIGNUM *pub_key; + ...; +} DSA; """ FUNCTIONS = """ diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 9f10365a..39403ff2 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -12,7 +12,10 @@ # limitations under the License. INCLUDES = """ +#ifndef OPENSSL_NO_EC #include <openssl/ec.h> +#endif + #include <openssl/obj_mac.h> """ @@ -31,16 +34,17 @@ static const int NID_X9_62_prime256v1; """ FUNCTIONS = """ -EC_KEY *EC_KEY_new_by_curve_name(int); -void EC_KEY_free(EC_KEY *); """ MACROS = """ +EC_KEY *EC_KEY_new_by_curve_name(int); +void EC_KEY_free(EC_KEY *); """ CUSTOMIZATIONS = """ #ifdef OPENSSL_NO_EC static const long Cryptography_HAS_EC = 0; +typedef void EC_KEY; EC_KEY* (*EC_KEY_new_by_curve_name)(int) = NULL; void (*EC_KEY_free)(EC_KEY *) = NULL; #else diff --git a/cryptography/hazmat/bindings/openssl/evp.py b/cryptography/hazmat/bindings/openssl/evp.py index c426e52e..c7cc154f 100644 --- a/cryptography/hazmat/bindings/openssl/evp.py +++ b/cryptography/hazmat/bindings/openssl/evp.py @@ -40,6 +40,7 @@ static const int EVP_CTRL_GCM_GET_TAG; static const int EVP_CTRL_GCM_SET_TAG; static const int Cryptography_HAS_GCM; +static const int Cryptography_HAS_PBKDF2_HMAC; """ FUNCTIONS = """ @@ -95,6 +96,9 @@ int EVP_VerifyFinal(EVP_MD_CTX *, const unsigned char *, unsigned int, EVP_PKEY *); const EVP_MD *EVP_md5(void); + +int PKCS5_PBKDF2_HMAC_SHA1(const char *, int, const unsigned char *, int, int, + int, unsigned char *); """ MACROS = """ @@ -103,6 +107,9 @@ int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); int EVP_PKEY_assign_DSA(EVP_PKEY *, DSA *); int EVP_CIPHER_CTX_block_size(const EVP_CIPHER_CTX *); int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *, int, int, void *); + +int PKCS5_PBKDF2_HMAC(const char *, int, const unsigned char *, int, int, + const EVP_MD *, int, unsigned char *); """ CUSTOMIZATIONS = """ @@ -114,6 +121,13 @@ const long EVP_CTRL_GCM_GET_TAG = -1; const long EVP_CTRL_GCM_SET_TAG = -1; const long EVP_CTRL_GCM_SET_IVLEN = -1; #endif +#if OPENSSL_VERSION_NUMBER >= 0x10000000 +const long Cryptography_HAS_PBKDF2_HMAC = 1; +#else +const long Cryptography_HAS_PBKDF2_HMAC = 0; +int (*PKCS5_PBKDF2_HMAC)(const char *, int, const unsigned char *, int, int, + const EVP_MD *, int, unsigned char *) = NULL; +#endif """ CONDITIONAL_NAMES = { @@ -121,5 +135,8 @@ CONDITIONAL_NAMES = { "EVP_CTRL_GCM_GET_TAG", "EVP_CTRL_GCM_SET_TAG", "EVP_CTRL_GCM_SET_IVLEN", + ], + "Cryptography_HAS_PBKDF2_HMAC": [ + "PKCS5_PBKDF2_HMAC" ] } diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py index cd872d18..2b4e54f1 100644 --- a/cryptography/hazmat/bindings/openssl/ssl.py +++ b/cryptography/hazmat/bindings/openssl/ssl.py @@ -393,6 +393,6 @@ CONDITIONAL_NAMES = { ], "Cryptography_HAS_EC": [ - "EC_KEY_new_by_curve_name", + "SSL_CTX_set_tmp_ecdh", ] } diff --git a/cryptography/hazmat/bindings/openssl/x509.py b/cryptography/hazmat/bindings/openssl/x509.py index 840254a2..e4021a12 100644 --- a/cryptography/hazmat/bindings/openssl/x509.py +++ b/cryptography/hazmat/bindings/openssl/x509.py @@ -119,6 +119,7 @@ int X509_REQ_sign(X509_REQ *, EVP_PKEY *, const EVP_MD *); int X509_REQ_verify(X509_REQ *, EVP_PKEY *); EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *); int X509_REQ_add_extensions(X509_REQ *, X509_EXTENSIONS *); +X509_EXTENSIONS *X509_REQ_get_extensions(X509_REQ *); int X509_REQ_print_ex(BIO *, X509_REQ *, unsigned long, unsigned long); int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int); @@ -165,6 +166,7 @@ int X509_set_serialNumber(X509 *, ASN1_INTEGER *); X509_STORE *X509_STORE_new(void); void X509_STORE_free(X509_STORE *); int X509_STORE_add_cert(X509_STORE *, X509 *); +int X509_verify_cert(X509_STORE_CTX *); """ MACROS = """ diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 7a6bf3e2..1a27644f 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -169,3 +169,107 @@ class HashContext(six.with_metaclass(abc.ABCMeta)): """ Return a HashContext that is a copy of the current context. """ + + +class RSAPrivateKey(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def modulus(self): + """ + The public modulus of the RSA key. + """ + + @abc.abstractproperty + def public_exponent(self): + """ + The public exponent of the RSA key. + """ + + @abc.abstractproperty + def key_length(self): + """ + The bit length of the public modulus. + """ + + @abc.abstractmethod + def public_key(self): + """ + The RSAPublicKey associated with this private key. + """ + + @abc.abstractproperty + def n(self): + """ + The public modulus of the RSA key. Alias for modulus. + """ + + @abc.abstractproperty + def p(self): + """ + One of the two primes used to generate d. + """ + + @abc.abstractproperty + def q(self): + """ + One of the two primes used to generate d. + """ + + @abc.abstractproperty + def d(self): + """ + The private exponent. This can be calculated using p and q. + """ + + @abc.abstractproperty + def e(self): + """ + The public exponent of the RSA key. Alias for public_exponent. + """ + + +class RSAPublicKey(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def modulus(self): + """ + The public modulus of the RSA key. + """ + + @abc.abstractproperty + def public_exponent(self): + """ + The public exponent of the RSA key. + """ + + @abc.abstractproperty + def key_length(self): + """ + The bit length of the public modulus. + """ + + @abc.abstractproperty + def n(self): + """ + The public modulus of the RSA key. Alias for modulus. + """ + + @abc.abstractproperty + def e(self): + """ + The public exponent of the RSA key. Alias for public_exponent. + """ + + +class KeyDerivationFunction(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def derive(self, key_material): + """ + Deterministically generates and returns a new key based on the existing + key material. + """ + + @abc.abstractmethod + def verify(self, key_material, expected_key): + """ + Checks whether the key generated by the key material matches the + expected derived key. Raises an exception if they do not match. + """ diff --git a/cryptography/hazmat/primitives/kdf/__init__.py b/cryptography/hazmat/primitives/kdf/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/cryptography/hazmat/primitives/kdf/__init__.py diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py new file mode 100644 index 00000000..71b88211 --- /dev/null +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -0,0 +1,66 @@ +# 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 + +import six + +from cryptography import utils +from cryptography.exceptions import ( + InvalidKey, UnsupportedAlgorithm, AlreadyFinalized +) +from cryptography.hazmat.primitives import constant_time, interfaces + + +@utils.register_interface(interfaces.KeyDerivationFunction) +class PBKDF2HMAC(object): + def __init__(self, algorithm, length, salt, iterations, backend): + if not backend.pbkdf2_hmac_supported(algorithm): + raise UnsupportedAlgorithm( + "{0} is not supported for PBKDF2 by this backend".format( + algorithm.name) + ) + self._used = False + self._algorithm = algorithm + self._length = length + if isinstance(salt, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) + self._salt = salt + self._iterations = iterations + self._backend = backend + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized("PBKDF2 instances can only be used once") + self._used = True + + if isinstance(key_material, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before using them as key " + "material." + ) + return self._backend.derive_pbkdf2_hmac( + self._algorithm, + self._length, + self._salt, + self._iterations, + key_material + ) + + def verify(self, key_material, expected_key): + derived_key = self.derive(key_material) + if not constant_time.bytes_eq(derived_key, expected_key): + raise InvalidKey("Keys do not match.") |