aboutsummaryrefslogtreecommitdiffstats
path: root/cryptography/hazmat
diff options
context:
space:
mode:
Diffstat (limited to 'cryptography/hazmat')
-rw-r--r--cryptography/hazmat/backends/__init__.py10
-rw-r--r--cryptography/hazmat/backends/commoncrypto/__init__.py17
-rw-r--r--cryptography/hazmat/backends/commoncrypto/backend.py482
-rw-r--r--cryptography/hazmat/backends/interfaces.py22
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py57
-rw-r--r--cryptography/hazmat/bindings/commoncrypto/binding.py2
-rw-r--r--cryptography/hazmat/bindings/commoncrypto/common_cryptor.py108
-rw-r--r--cryptography/hazmat/bindings/commoncrypto/common_key_derivation.py48
-rw-r--r--cryptography/hazmat/bindings/openssl/aes.py40
-rw-r--r--cryptography/hazmat/bindings/openssl/bignum.py3
-rw-r--r--cryptography/hazmat/bindings/openssl/binding.py56
-rw-r--r--cryptography/hazmat/bindings/openssl/crypto.py7
-rw-r--r--cryptography/hazmat/bindings/openssl/dh.py12
-rw-r--r--cryptography/hazmat/bindings/openssl/dsa.py14
-rw-r--r--cryptography/hazmat/bindings/openssl/ec.py8
-rw-r--r--cryptography/hazmat/bindings/openssl/evp.py17
-rw-r--r--cryptography/hazmat/bindings/openssl/ssl.py2
-rw-r--r--cryptography/hazmat/bindings/openssl/x509.py2
-rw-r--r--cryptography/hazmat/primitives/interfaces.py104
-rw-r--r--cryptography/hazmat/primitives/kdf/__init__.py0
-rw-r--r--cryptography/hazmat/primitives/kdf/pbkdf2.py66
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.")