aboutsummaryrefslogtreecommitdiffstats
path: root/cryptography/hazmat
diff options
context:
space:
mode:
Diffstat (limited to 'cryptography/hazmat')
-rw-r--r--cryptography/hazmat/backends/commoncrypto/backend.py324
-rw-r--r--cryptography/hazmat/backends/interfaces.py16
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py56
-rw-r--r--cryptography/hazmat/bindings/commoncrypto/binding.py1
-rw-r--r--cryptography/hazmat/bindings/commoncrypto/common_cryptor.py14
-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/binding.py56
-rw-r--r--cryptography/hazmat/bindings/openssl/crypto.py7
-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
16 files changed, 738 insertions, 23 deletions
diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py
index 603edc40..e5d4ee00 100644
--- a/cryptography/hazmat/backends/commoncrypto/backend.py
+++ b/cryptography/hazmat/backends/commoncrypto/backend.py
@@ -16,12 +16,20 @@ from __future__ import absolute_import, division, print_function
from collections import namedtuple
from cryptography import utils
-from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography.exceptions import (
+ UnsupportedAlgorithm, InvalidTag, InternalError
+)
from cryptography.hazmat.backends.interfaces import (
- HashBackend, HMACBackend,
+ HashBackend, HMACBackend, CipherBackend, PBKDF2HMACBackend
)
from cryptography.hazmat.bindings.commoncrypto.binding import Binding
-from cryptography.hazmat.primitives import interfaces
+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(
@@ -29,8 +37,10 @@ HashMethods = namedtuple(
)
+@utils.register_interface(CipherBackend)
@utils.register_interface(HashBackend)
@utils.register_interface(HMACBackend)
+@utils.register_interface(PBKDF2HMACBackend)
class Backend(object):
"""
CommonCrypto API wrapper.
@@ -42,6 +52,8 @@ class Backend(object):
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,
@@ -78,27 +90,311 @@ class Backend(object):
"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:
- self._hash_mapping[algorithm.name]
+ cipher_enum, mode_enum = registry[type(cipher), type(mode)]
except KeyError:
- return False
+ 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:
- return True
+ iv_nonce = self._backend._ffi.NULL
- def hmac_supported(self, algorithm):
+ 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:
- self._supported_hmac_algorithms[algorithm.name]
+ cipher_enum, mode_enum = registry[type(cipher), type(mode)]
except KeyError:
- return False
+ 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:
- return True
+ res = self._backend._lib.CCCryptorGCMDecrypt(*args)
- def create_hash_ctx(self, algorithm):
- return _HashContext(self, algorithm)
+ self._backend._check_response(res)
+ return self._backend._ffi.buffer(buf)[:]
- def create_hmac_ctx(self, key, algorithm):
- return _HMACContext(self, key, algorithm)
+ 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)
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py
index 4fbb3488..53c75181 100644
--- a/cryptography/hazmat/backends/interfaces.py
+++ b/cryptography/hazmat/backends/interfaces.py
@@ -65,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 88afe997..99d97b7f 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,6 +35,7 @@ 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.
@@ -44,6 +47,8 @@ class Backend(object):
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
@@ -170,6 +175,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):
@@ -195,7 +243,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 a5a0dca8..45c0eaad 100644
--- a/cryptography/hazmat/bindings/commoncrypto/binding.py
+++ b/cryptography/hazmat/bindings/commoncrypto/binding.py
@@ -26,6 +26,7 @@ class Binding(object):
_modules = [
"common_digest",
"common_hmac",
+ "common_key_derivation",
"common_cryptor",
]
diff --git a/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py
index ef0e7e10..8f03bc3f 100644
--- a/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py
+++ b/cryptography/hazmat/bindings/commoncrypto/common_cryptor.py
@@ -62,6 +62,7 @@ enum {
kCCModeXTS = 8,
kCCModeRC4 = 9,
kCCModeCFB8 = 10,
+ kCCModeGCM = 11
};
typedef uint32_t CCMode;
enum {
@@ -83,12 +84,25 @@ 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/binding.py b/cryptography/hazmat/bindings/openssl/binding.py
index 261bbb8d..9df26cde 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",
@@ -68,6 +72,10 @@ class Binding(object):
"x509v3",
]
+ _locks = None
+ _lock_cb_handle = None
+ _lock_init_lock = threading.Lock()
+
ffi = None
lib = None
@@ -79,9 +87,15 @@ 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)
res = cls.lib.Cryptography_add_osrandom_engine()
assert res == 1
@@ -89,3 +103,43 @@ class Binding(object):
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/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.")