aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.coveragerc1
-rwxr-xr-x.travis/install.sh2
-rw-r--r--AUTHORS.rst2
-rw-r--r--cryptography/exceptions.py8
-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
-rw-r--r--docs/changelog.rst7
-rw-r--r--docs/contributing.rst8
-rw-r--r--docs/exceptions.rst10
-rw-r--r--docs/fernet.rst17
-rw-r--r--docs/hazmat/backends/interfaces.rst49
-rw-r--r--docs/hazmat/backends/openssl.rst28
-rw-r--r--docs/hazmat/bindings/openssl.rst20
-rw-r--r--docs/hazmat/primitives/index.rst1
-rw-r--r--docs/hazmat/primitives/interfaces.rst172
-rw-r--r--docs/hazmat/primitives/key-derivation-functions.rst125
-rw-r--r--docs/index.rst11
-rw-r--r--docs/installation.rst74
-rw-r--r--pytest.ini3
-rw-r--r--tests/conftest.py3
-rw-r--r--tests/hazmat/backends/test_commoncrypto.py65
-rw-r--r--tests/hazmat/backends/test_openssl.py10
-rw-r--r--tests/hazmat/bindings/test_openssl.py73
-rw-r--r--tests/hazmat/primitives/test_pbkdf2hmac.py69
-rw-r--r--tests/hazmat/primitives/test_pbkdf2hmac_vectors.py37
-rw-r--r--tests/hazmat/primitives/utils.py32
-rw-r--r--tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt56
-rw-r--r--tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt40
-rw-r--r--tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt48
-rw-r--r--tests/test_utils.py19
-rw-r--r--tests/utils.py27
45 files changed, 1692 insertions, 86 deletions
diff --git a/.coveragerc b/.coveragerc
index 20e3224e..03fc621e 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -3,5 +3,6 @@ branch = True
[report]
exclude_lines =
+ pragma: no cover
@abc.abstractmethod
@abc.abstractproperty
diff --git a/.travis/install.sh b/.travis/install.sh
index 1e3b1702..b6dd5acc 100755
--- a/.travis/install.sh
+++ b/.travis/install.sh
@@ -29,13 +29,11 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
py26)
curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py
sudo python get-pip.py
- sudo pip install setuptools --no-use-wheel --upgrade
sudo pip install virtualenv
;;
py27)
curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py
sudo python get-pip.py
- sudo pip install setuptools --no-use-wheel --upgrade
sudo pip install virtualenv
;;
pypy)
diff --git a/AUTHORS.rst b/AUTHORS.rst
index ad27cec6..0e3979ad 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -12,3 +12,5 @@ PGP key fingerprints are enclosed in parentheses.
* Jarret Raim <jarito@gmail.com>
* Alex Stapleton <alexs@prol.etari.at> (A1C7 E50B 66DE 39ED C847 9665 8E3C 20D1 9BD9 5C4C)
* David Reid <dreid@dreid.org> (0F83 CC87 B32F 482B C726 B58A 9FBF D8F4 DA89 6D74)
+* Konstantinos Koukopoulos <koukopoulos@gmail.com> (D6BD 52B6 8C99 A91C E2C8 934D 3300 566B 3A46 726E)
+* Stephen Holsapple <sholsapp@gmail.com>
diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py
index 44363c24..e2542a1f 100644
--- a/cryptography/exceptions.py
+++ b/cryptography/exceptions.py
@@ -34,3 +34,11 @@ class InvalidTag(Exception):
class InvalidSignature(Exception):
pass
+
+
+class InternalError(Exception):
+ pass
+
+
+class InvalidKey(Exception):
+ pass
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.")
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 0a03c396..f401fe7c 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -6,10 +6,15 @@ Changelog
**In development**
-* Added :doc:`/hazmat/backends/commoncrypto` with hash and HMAC support.
+* Added :doc:`/hazmat/backends/commoncrypto`.
* Added initial :doc:`/hazmat/bindings/commoncrypto`.
* Removed ``register_cipher_adapter`` method from
:class:`~cryptography.hazmat.backends.interfaces.CipherBackend`.
+* Added support for the OpenSSL backend under Windows.
+* Improved thread-safety for the OpenSSL backend.
+* Fixed compilation on systems where OpenSSL's ``ec.h`` header is not
+ available, such as CentOS.
+* Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`.
0.1 - 2014-01-08
~~~~~~~~~~~~~~~~
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 4bb1461d..184ba214 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -60,6 +60,12 @@ always indistinguishable. As a result ``cryptography`` has, as a design
philosophy: "make it hard to do insecure things". Here are a few strategies for
API design which should be both followed, and should inspire other API choices:
+If it is necessary to compare a user provided value with a computed value (for
+example, verifying a signature), there should be an API provided which performs
+the verification in a secure way (for example, using a constant time
+comparison), rather than requiring the user to perform the comparison
+themselves.
+
If it is incorrect to ignore the result of a method, it should raise an
exception, and not return a boolean ``True``/``False`` flag. For example, a
method to verify a signature should raise ``InvalidSignature``, and not return
@@ -287,5 +293,5 @@ The HTML documentation index can now be found at
.. _`tox`: https://pypi.python.org/pypi/tox
.. _`virtualenv`: https://pypi.python.org/pypi/virtualenv
.. _`pip`: https://pypi.python.org/pypi/pip
-.. _`sphinx`: https://pypi.python.org/pypi/sphinx
+.. _`sphinx`: https://pypi.python.org/pypi/Sphinx
.. _`reStructured Text`: http://sphinx-doc.org/rest.html
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
index 1fbd3267..1e31e31c 100644
--- a/docs/exceptions.rst
+++ b/docs/exceptions.rst
@@ -10,8 +10,8 @@ Exceptions
.. class:: InvalidSignature
- This is raised when the verify method of a hash context does not
- compare equal.
+ This is raised when the verify method of a hash context's computed digest
+ does not match the expected digest.
.. class:: NotYetFinalized
@@ -30,3 +30,9 @@ Exceptions
This is raised when a backend doesn't support the requested algorithm (or
combination of algorithms).
+
+
+.. class:: InvalidKey
+
+ This is raised when the verify method of a key derivation function's
+ computed key does not match the expected key.
diff --git a/docs/fernet.rst b/docs/fernet.rst
index 13295c0c..b0215e32 100644
--- a/docs/fernet.rst
+++ b/docs/fernet.rst
@@ -72,5 +72,22 @@ symmetric (also known as "secret key") authenticated cryptography.
See :meth:`Fernet.decrypt` for more information.
+Implementation
+--------------
+
+Fernet is built on top of a number of standard cryptographic primitives.
+Specifically it uses:
+
+* :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` in
+ :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode with a
+ 128-bit key for encryption; using
+ :class:`~cryptography.hazmat.primitives.ciphers.PKCS7` padding.
+* :class:`~cryptography.hazmat.primitives.hmac.HMAC` using
+ :class:`~cryptography.hazmat.primitives.hashes.SHA256` for authentication.
+* Initialization vectors are generated using ``os.urandom()``.
+
+For complete details consult the `specification`_.
+
.. _`Fernet`: https://github.com/fernet/spec/
+.. _`specification`: https://github.com/fernet/spec/blob/master/Spec.md
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst
index 11e2f2a2..49e4c88c 100644
--- a/docs/hazmat/backends/interfaces.rst
+++ b/docs/hazmat/backends/interfaces.rst
@@ -37,7 +37,7 @@ A specific ``backend`` may provide one or more of these interfaces.
.. method:: create_symmetric_encryption_ctx(cipher, mode)
Create a
- :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that
+ :class:`~cryptography.hazmat.primitives.interfaces.CipherContext` that
can be used for encrypting data with the symmetric ``cipher`` using
the given ``mode``.
@@ -56,7 +56,7 @@ A specific ``backend`` may provide one or more of these interfaces.
.. method:: create_symmetric_decryption_ctx(cipher, mode)
Create a
- :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that
+ :class:`~cryptography.hazmat.primitives.interfaces.CipherContext` that
can be used for decrypting data with the symmetric ``cipher`` using
the given ``mode``.
@@ -91,7 +91,7 @@ A specific ``backend`` may provide one or more of these interfaces.
.. method:: create_hash_ctx(algorithm)
Create a
- :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that
+ :class:`~cryptography.hazmat.primitives.interfaces.HashContext` that
uses the specified ``algorithm`` to calculate a message digest.
:param algorithm: An instance of a
@@ -121,7 +121,7 @@ A specific ``backend`` may provide one or more of these interfaces.
.. method:: create_hmac_ctx(algorithm)
Create a
- :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that
+ :class:`~cryptography.hazmat.primitives.interfaces.HashContext` that
uses the specified ``algorithm`` to calculate a hash-based message
authentication code.
@@ -131,3 +131,44 @@ A specific ``backend`` may provide one or more of these interfaces.
:returns:
:class:`~cryptography.hazmat.primitives.interfaces.HashContext`
+
+
+.. class:: PBKDF2HMACBackend
+
+ .. versionadded:: 0.2
+
+ A backend with methods for using PBKDF2 using HMAC as a PRF.
+
+ .. method:: pbkdf2_hmac_supported(algorithm)
+
+ Check if the specified ``algorithm`` is supported by this backend.
+
+ :param algorithm: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+ provider.
+
+ :returns: ``True`` if the specified ``algorithm`` is supported for
+ PBKDF2 HMAC by this backend, otherwise ``False``.
+
+ .. method:: derive_pbkdf2_hmac(self, algorithm, length, salt, iterations,
+ key_material)
+
+ :param algorithm: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+ provider.
+
+ :param int length: The desired length of the derived key. Maximum is
+ (2\ :sup:`32` - 1) * ``algorithm.digest_size``
+
+ :param bytes salt: A salt.
+
+ :param int iterations: The number of iterations to perform of the hash
+ function. This can be used to control the length of time the
+ operation takes. Higher numbers help mitigate brute force attacks
+ against derived keys.
+
+ :param bytes key_material: The key material to use as a basis for
+ the derived key. This is typically a password.
+
+ :return bytes: Derived key.
+
diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst
index 469823f1..5ad00d03 100644
--- a/docs/hazmat/backends/openssl.rst
+++ b/docs/hazmat/backends/openssl.rst
@@ -39,32 +39,4 @@ CryptGenRandom on Windows). This engine is **active** by default when importing
the OpenSSL backend. It is added to the engine list but not activated if you
only import the binding.
-
-Using your own OpenSSL on Linux
--------------------------------
-
-Python links to OpenSSL for its own purposes and this can sometimes cause
-problems when you wish to use a different version of OpenSSL with cryptography.
-If you want to use cryptography with your own build of OpenSSL you will need to
-make sure that the build is configured correctly so that your version of
-OpenSSL doesn't conflict with Python's.
-
-The options you need to add allow the linker to identify every symbol correctly
-even when multiple versions of the library are linked into the same program. If
-you are using your distribution's source packages these will probably be
-patched in for you already, otherwise you'll need to use options something like
-this when configuring OpenSSL::
-
- ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared
-
-You'll also need to generate your own ``openssl.ld`` file. For example::
-
- OPENSSL_1.0.1F_CUSTOM {
- global:
- *;
- };
-
-You should replace the version string on the first line as appropriate for your
-build.
-
.. _`OpenSSL`: https://www.openssl.org/
diff --git a/docs/hazmat/bindings/openssl.rst b/docs/hazmat/bindings/openssl.rst
index 373fe472..557f8c4d 100644
--- a/docs/hazmat/bindings/openssl.rst
+++ b/docs/hazmat/bindings/openssl.rst
@@ -22,6 +22,26 @@ These are `CFFI`_ bindings to the `OpenSSL`_ C library.
This is a ``cffi`` library. It can be used to call OpenSSL functions,
and access constants.
+ .. classmethod:: init_static_locks
+
+ Enables the best available locking callback for OpenSSL.
+ See :ref:`openssl-threading`.
+
+.. _openssl-threading:
+
+Threading
+---------
+
+``cryptography`` enables OpenSSLs `thread safety facilities`_ in two different
+ways depending on the configuration of your system. Normally the locking
+callbacks provided by your Python implementation specifically for OpenSSL will
+be used. However if you have linked ``cryptography`` to a different version of
+OpenSSL than that used by your Python implementation we enable an alternative
+locking callback. This version is implemented in Python and so may result in
+lower performance in some situations. In particular parallelism is reduced
+because it has to acquire the GIL whenever any lock operations occur within
+OpenSSL.
.. _`CFFI`: https://cffi.readthedocs.org/
.. _`OpenSSL`: https://www.openssl.org/
+.. _`thread safety facilities`: http://www.openssl.org/docs/crypto/threads.html
diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst
index b115fdbc..bde07392 100644
--- a/docs/hazmat/primitives/index.rst
+++ b/docs/hazmat/primitives/index.rst
@@ -10,5 +10,6 @@ Primitives
hmac
symmetric-encryption
padding
+ key-derivation-functions
constant-time
interfaces
diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst
index edb24cd9..09a5a4ce 100644
--- a/docs/hazmat/primitives/interfaces.rst
+++ b/docs/hazmat/primitives/interfaces.rst
@@ -102,3 +102,175 @@ Interfaces used by the symmetric cipher modes described in
Exact requirements of the nonce are described by the documentation of
individual modes.
+
+Asymmetric Interfaces
+~~~~~~~~~~~~~~~~~~~~~
+
+.. class:: RSAPrivateKey
+
+ .. versionadded:: 0.2
+
+ An `RSA`_ private key.
+
+ .. method:: public_key()
+
+ :return: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey`
+
+ An RSA public key object corresponding to the values of the private key.
+
+ .. attribute:: modulus
+
+ :type: int
+
+ The public modulus.
+
+ .. attribute:: public_exponent
+
+ :type: int
+
+ The public exponent.
+
+ .. attribute:: key_length
+
+ :type: int
+
+ The bit length of the modulus.
+
+ .. attribute:: p
+
+ :type: int
+
+ ``p``, one of the two primes composing the :attr:`modulus`.
+
+ .. attribute:: q
+
+ :type: int
+
+ ``q``, one of the two primes composing the :attr:`modulus`.
+
+ .. attribute:: d
+
+ :type: int
+
+ The private exponent.
+
+ .. attribute:: n
+
+ :type: int
+
+ The public modulus. Alias for :attr:`modulus`.
+
+ .. attribute:: e
+
+ :type: int
+
+ The public exponent. Alias for :attr:`public_exponent`.
+
+
+.. class:: RSAPublicKey
+
+ .. versionadded:: 0.2
+
+ An `RSA`_ public key.
+
+ .. attribute:: modulus
+
+ :type: int
+
+ The public modulus.
+
+ .. attribute:: key_length
+
+ :type: int
+
+ The bit length of the modulus.
+
+ .. attribute:: public_exponent
+
+ :type: int
+
+ The public exponent.
+
+ .. attribute:: n
+
+ :type: int
+
+ The public modulus. Alias for :attr:`modulus`.
+
+ .. attribute:: e
+
+ :type: int
+
+ The public exponent. Alias for :attr:`public_exponent`.
+
+
+Hash Algorithms
+~~~~~~~~~~~~~~~
+
+.. class:: HashAlgorithm
+
+ .. attribute:: name
+
+ :type: str
+
+ The standard name for the hash algorithm, for example: ``"sha256"`` or
+ ``"whirlpool"``.
+
+ .. attribute:: digest_size
+
+ :type: int
+
+ The size of the resulting digest in bytes.
+
+ .. attribute:: block_size
+
+ :type: int
+
+ The internal block size of the hash algorithm in bytes.
+
+
+Key Derivation Functions
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. class:: KeyDerivationFunction
+
+ .. versionadded:: 0.2
+
+ .. method:: derive(key_material)
+
+ :param key_material bytes: The input key material. Depending on what
+ key derivation function you are using this
+ could be either random material, or a user
+ supplied password.
+ :return: The new key.
+ :raises cryptography.exceptions.AlreadyFinalized: This is raised when
+ :meth:`derive` or
+ :meth:`verify` is
+ called more than
+ once.
+
+ This generates and returns a new key from the supplied key material.
+
+ .. method:: verify(key_material, expected_key)
+
+ :param key_material bytes: The input key material. This is the same as
+ ``key_material`` in :meth:`derive`.
+ :param expected_key bytes: The expected result of deriving a new key,
+ this is the same as the return value of
+ :meth:`derive`.
+ :raises cryptography.exceptions.InvalidKey: This is raised when the
+ derived key does not match
+ the expected key.
+ :raises cryptography.exceptions.AlreadyFinalized: This is raised when
+ :meth:`derive` or
+ :meth:`verify` is
+ called more than
+ once.
+
+ This checks whether deriving a new key from the supplied
+ ``key_material`` generates the same key as the ``expected_key``, and
+ raises an exception if they do not match. This can be used for
+ something like checking whether a user's password attempt matches the
+ stored derived key.
+
+.. _`RSA`: http://en.wikipedia.org/wiki/RSA_(cryptosystem)
diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst
new file mode 100644
index 00000000..551dbd6d
--- /dev/null
+++ b/docs/hazmat/primitives/key-derivation-functions.rst
@@ -0,0 +1,125 @@
+.. hazmat::
+
+Key Derivation Functions
+========================
+
+.. currentmodule:: cryptography.hazmat.primitives.kdf
+
+Key derivation functions derive bytes suitable for cryptographic operations
+from passwords or other data sources using a pseudo-random function (PRF).
+Different KDFs are suitable for different tasks such as:
+
+* Cryptographic key derivation
+
+ Deriving a key suitable for use as input to an encryption algorithm.
+ Typically this means taking a password and running it through an algorithm
+ such as :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC` or HKDF.
+ This process is typically known as `key stretching`_.
+
+* Password storage
+
+ When storing passwords you want to use an algorithm that is computationally
+ intensive. Legitimate users will only need to compute it once (for example,
+ taking the user's password, running it through the KDF, then comparing it
+ to the stored value), while attackers will need to do it billions of times.
+ Ideal password storage KDFs will be demanding on both computational and
+ memory resources.
+
+.. currentmodule:: cryptography.hazmat.primitives.kdf.pbkdf2
+
+.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend)
+
+ .. versionadded:: 0.2
+
+ `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for
+ deriving a cryptographic key from a password. It may also be used for
+ key storage, but an alternate key storage KDF such as `scrypt`_ is generally
+ considered a better solution.
+
+ This class conforms to the
+ :class:`~cryptography.hazmat.primitives.interfaces.KeyDerivationFunction`
+ interface.
+
+ .. doctest::
+
+ >>> import os
+ >>> from cryptography.hazmat.primitives import hashes
+ >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> backend = default_backend()
+ >>> salt = os.urandom(16)
+ >>> # derive
+ >>> kdf = PBKDF2HMAC(
+ ... algorithm=hashes.SHA256(),
+ ... length=32,
+ ... salt=salt,
+ ... iterations=100000,
+ ... backend=backend
+ ... )
+ >>> key = kdf.derive(b"my great password")
+ >>> # verify
+ >>> kdf = PBKDF2HMAC(
+ ... algorithm=hashes.SHA256(),
+ ... length=32,
+ ... salt=salt,
+ ... iterations=100000,
+ ... backend=backend
+ ... )
+ >>> kdf.verify(b"my great password", key)
+
+ :param algorithm: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+ provider.
+ :param int length: The desired length of the derived key. Maximum is
+ (2\ :sup:`32` - 1) * ``algorithm.digest_size``.
+ :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or
+ longer.
+ :param int iterations: The number of iterations to perform of the hash
+ function. This can be used to control the length of time the operation
+ takes. Higher numbers help mitigate brute force attacks against derived
+ keys. See OWASP's `Password Storage Cheat Sheet`_ for more
+ detailed recommendations if you intend to use this for password storage.
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend`
+ provider.
+
+ .. method:: derive(key_material)
+
+ :param key_material bytes: The input key material. For PBKDF2 this
+ should be a password.
+ :return bytes: the derived key.
+ :raises cryptography.exceptions.AlreadyFinalized: This is raised when
+ :meth:`derive` or
+ :meth:`verify` is
+ called more than
+ once.
+
+ This generates and returns a new key from the supplied password.
+
+ .. method:: verify(key_material, expected_key)
+
+ :param key_material bytes: The input key material. This is the same as
+ ``key_material`` in :meth:`derive`.
+ :param expected_key bytes: The expected result of deriving a new key,
+ this is the same as the return value of
+ :meth:`derive`.
+ :raises cryptography.exceptions.InvalidKey: This is raised when the
+ derived key does not match
+ the expected key.
+ :raises cryptography.exceptions.AlreadyFinalized: This is raised when
+ :meth:`derive` or
+ :meth:`verify` is
+ called more than
+ once.
+
+ This checks whether deriving a new key from the supplied
+ ``key_material`` generates the same key as the ``expected_key``, and
+ raises an exception if they do not match. This can be used for
+ checking whether the password a user provides matches the stored derived
+ key.
+
+.. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
+.. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet
+.. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2
+.. _`scrypt`: http://en.wikipedia.org/wiki/Scrypt
+.. _`key stretching`: http://en.wikipedia.org/wiki/Key_stretching
diff --git a/docs/index.rst b/docs/index.rst
index e17b4f9f..86cd42c6 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -5,15 +5,16 @@ Welcome to ``cryptography``
primitives. We hope it'll be your one-stop-shop for all your cryptographic
needs in Python.
-Installing
-----------
-
+Installation
+------------
You can install ``cryptography`` with ``pip``:
.. code-block:: console
$ pip install cryptography
+See :doc:`Installation <installation>` for more information.
+
Why a new crypto library for Python?
------------------------------------
@@ -75,9 +76,13 @@ The ``cryptography`` open source project
.. toctree::
:maxdepth: 2
+ installation
contributing
security
api-stability
doing-a-release
changelog
community
+
+
+.. _`pre-compiled binaries`: https://www.openssl.org/related/binaries.html
diff --git a/docs/installation.rst b/docs/installation.rst
new file mode 100644
index 00000000..2206107e
--- /dev/null
+++ b/docs/installation.rst
@@ -0,0 +1,74 @@
+Installing
+==========
+
+You can install ``cryptography`` with ``pip``:
+
+.. code-block:: console
+
+ $ pip install cryptography
+
+Installation Notes
+==================
+On Windows
+----------
+If you're on Windows you'll need to make sure you have OpenSSL installed.
+There are `pre-compiled binaries`_ available. If your installation is in
+an unusual location set the ``LIB`` and ``INCLUDE`` environment variables
+to include the corresponding locations. For example:
+
+.. code-block:: console
+
+ C:\> \path\to\vcvarsall.bat x86_amd64
+ C:\> set LIB=C:\OpenSSL-1.0.1f-64bit\lib;%LIB%
+ C:\> set INCLUDE=C:\OpenSSL-1.0.1f-64bit\include;%INCLUDE%
+ C:\> pip install cryptography
+
+Using your own OpenSSL on Linux
+-------------------------------
+
+Python links to OpenSSL for its own purposes and this can sometimes cause
+problems when you wish to use a different version of OpenSSL with cryptography.
+If you want to use cryptography with your own build of OpenSSL you will need to
+make sure that the build is configured correctly so that your version of
+OpenSSL doesn't conflict with Python's.
+
+The options you need to add allow the linker to identify every symbol correctly
+even when multiple versions of the library are linked into the same program. If
+you are using your distribution's source packages these will probably be
+patched in for you already, otherwise you'll need to use options something like
+this when configuring OpenSSL:
+
+.. code-block:: console
+
+ $ ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared
+
+You'll also need to generate your own ``openssl.ld`` file. For example::
+
+ OPENSSL_1.0.1F_CUSTOM {
+ global:
+ *;
+ };
+
+You should replace the version string on the first line as appropriate for your
+build.
+
+Using your own OpenSSL on OS X
+------------------------------
+
+To link cryptography against a custom version of OpenSSL you'll need to set
+``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. OpenSSL can be installed via
+`Homebrew`_:
+
+.. code-block:: console
+
+ $ brew install openssl
+
+Then install cryptography linking against the brewed version:
+
+.. code-block:: console
+
+ $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography
+
+
+.. _`Homebrew`: http://brew.sh
+.. _`pre-compiled binaries`: https://www.openssl.org/related/binaries.html
diff --git a/pytest.ini b/pytest.ini
index 36d4edc4..2a1b6e9f 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,7 +1,8 @@
[pytest]
addopts = -r s
markers =
- hmac: this test requires a backend providing HMACBackend
cipher: this test requires a backend providing CipherBackend
hash: this test requires a backend providing HashBackend
+ hmac: this test requires a backend providing HMACBackend
+ pbkdf2hmac: this test requires a backend providing PBKDF2HMACBackend
supported: parametrized test requiring only_if and skip_message
diff --git a/tests/conftest.py b/tests/conftest.py
index a9acb54a..ecad1b23 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,7 +2,7 @@ import pytest
from cryptography.hazmat.backends import _ALL_BACKENDS
from cryptography.hazmat.backends.interfaces import (
- HMACBackend, CipherBackend, HashBackend
+ HMACBackend, CipherBackend, HashBackend, PBKDF2HMACBackend
)
from .utils import check_for_iface, check_backend_support, select_backends
@@ -21,6 +21,7 @@ def pytest_runtest_setup(item):
check_for_iface("hmac", HMACBackend, item)
check_for_iface("cipher", CipherBackend, item)
check_for_iface("hash", HashBackend, item)
+ check_for_iface("pbkdf2hmac", PBKDF2HMACBackend, item)
check_backend_support(item)
diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py
new file mode 100644
index 00000000..7cc0f72f
--- /dev/null
+++ b/tests/hazmat/backends/test_commoncrypto.py
@@ -0,0 +1,65 @@
+# 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.
+
+import pytest
+
+from cryptography import utils
+from cryptography.exceptions import UnsupportedAlgorithm, InternalError
+from cryptography.hazmat.bindings.commoncrypto.binding import Binding
+from cryptography.hazmat.primitives import interfaces
+from cryptography.hazmat.primitives.ciphers.algorithms import AES
+from cryptography.hazmat.primitives.ciphers.base import Cipher
+from cryptography.hazmat.primitives.ciphers.modes import CBC, GCM
+
+
+@utils.register_interface(interfaces.CipherAlgorithm)
+class DummyCipher(object):
+ name = "dummy-cipher"
+ block_size = 128
+
+
+@pytest.mark.skipif(not Binding.is_available(),
+ reason="CommonCrypto not available")
+class TestCommonCrypto(object):
+ def test_supports_cipher(self):
+ from cryptography.hazmat.backends.commoncrypto.backend import backend
+ assert backend.cipher_supported(None, None) is False
+
+ def test_register_duplicate_cipher_adapter(self):
+ from cryptography.hazmat.backends.commoncrypto.backend import backend
+ with pytest.raises(ValueError):
+ backend._register_cipher_adapter(
+ AES, backend._lib.kCCAlgorithmAES128,
+ CBC, backend._lib.kCCModeCBC
+ )
+
+ def test_handle_response(self):
+ from cryptography.hazmat.backends.commoncrypto.backend import backend
+
+ with pytest.raises(ValueError):
+ backend._check_response(backend._lib.kCCAlignmentError)
+
+ with pytest.raises(InternalError):
+ backend._check_response(backend._lib.kCCMemoryFailure)
+
+ with pytest.raises(InternalError):
+ backend._check_response(backend._lib.kCCDecodeError)
+
+ def test_nonexistent_aead_cipher(self):
+ from cryptography.hazmat.backends.commoncrypto.backend import Backend
+ b = Backend()
+ cipher = Cipher(
+ DummyCipher(), GCM(b"fake_iv_here"), backend=b,
+ )
+ with pytest.raises(UnsupportedAlgorithm):
+ cipher.encryptor()
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 51eb408f..e527ed19 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -16,7 +16,7 @@ import cffi
import pytest
from cryptography import utils
-from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography.exceptions import UnsupportedAlgorithm, InternalError
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.backends.openssl.backend import backend, Backend
from cryptography.hazmat.primitives import interfaces
@@ -168,20 +168,20 @@ class TestOpenSSL(object):
cipher.encryptor()
def test_handle_unknown_error(self):
- with pytest.raises(SystemError):
+ with pytest.raises(InternalError):
backend._handle_error_code(0, 0, 0)
- with pytest.raises(SystemError):
+ with pytest.raises(InternalError):
backend._handle_error_code(backend._lib.ERR_LIB_EVP, 0, 0)
- with pytest.raises(SystemError):
+ with pytest.raises(InternalError):
backend._handle_error_code(
backend._lib.ERR_LIB_EVP,
backend._lib.EVP_F_EVP_ENCRYPTFINAL_EX,
0
)
- with pytest.raises(SystemError):
+ with pytest.raises(InternalError):
backend._handle_error_code(
backend._lib.ERR_LIB_EVP,
backend._lib.EVP_F_EVP_DECRYPTFINAL_EX,
diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py
index d1e85058..35eb7e8d 100644
--- a/tests/hazmat/bindings/test_openssl.py
+++ b/tests/hazmat/bindings/test_openssl.py
@@ -11,6 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import pytest
+
from cryptography.hazmat.bindings.openssl.binding import Binding
@@ -23,3 +25,74 @@ class TestOpenSSL(object):
def test_is_available(self):
assert Binding.is_available() is True
+
+ def test_crypto_lock_init(self):
+ b = Binding()
+ b.init_static_locks()
+ lock_cb = b.lib.CRYPTO_get_locking_callback()
+ assert lock_cb != b.ffi.NULL
+
+ def _skip_if_not_fallback_lock(self, b):
+ # only run this test if we are using our locking cb
+ original_cb = b.lib.CRYPTO_get_locking_callback()
+ if original_cb != b._lock_cb_handle:
+ pytest.skip(
+ "Not using the fallback Python locking callback "
+ "implementation. Probably because import _ssl set one"
+ )
+
+ def test_fallback_crypto_lock_via_openssl_api(self):
+ b = Binding()
+ b.init_static_locks()
+
+ self._skip_if_not_fallback_lock(b)
+
+ # check that the lock state changes appropriately
+ lock = b._locks[b.lib.CRYPTO_LOCK_SSL]
+
+ # starts out unlocked
+ assert lock.acquire(False)
+ lock.release()
+
+ b.lib.CRYPTO_lock(
+ b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL, b.ffi.NULL, 0
+ )
+
+ # becomes locked
+ assert not lock.acquire(False)
+
+ b.lib.CRYPTO_lock(
+ b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL, b.ffi.NULL, 0
+ )
+
+ # then unlocked
+ assert lock.acquire(False)
+ lock.release()
+
+ def test_fallback_crypto_lock_via_binding_api(self):
+ b = Binding()
+ b.init_static_locks()
+
+ self._skip_if_not_fallback_lock(b)
+
+ lock = b._locks[b.lib.CRYPTO_LOCK_SSL]
+
+ with pytest.raises(RuntimeError):
+ b._lock_cb(0, b.lib.CRYPTO_LOCK_SSL, "<test>", 1)
+
+ # errors shouldnt cause locking
+ assert lock.acquire(False)
+ lock.release()
+
+ b._lock_cb(b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL, "<test>", 1)
+ # locked
+ assert not lock.acquire(False)
+
+ b._lock_cb(b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL, "<test>", 1)
+ # unlocked
+ assert lock.acquire(False)
+ lock.release()
diff --git a/tests/hazmat/primitives/test_pbkdf2hmac.py b/tests/hazmat/primitives/test_pbkdf2hmac.py
new file mode 100644
index 00000000..6ad225a8
--- /dev/null
+++ b/tests/hazmat/primitives/test_pbkdf2hmac.py
@@ -0,0 +1,69 @@
+# 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 pytest
+import six
+
+from cryptography import utils
+from cryptography.exceptions import (
+ InvalidKey, UnsupportedAlgorithm, AlreadyFinalized
+)
+from cryptography.hazmat.primitives import hashes, interfaces
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+from cryptography.hazmat.backends import default_backend
+
+
+@utils.register_interface(interfaces.HashAlgorithm)
+class DummyHash(object):
+ name = "dummy-hash"
+
+
+class TestPBKDF2HMAC(object):
+ def test_already_finalized(self):
+ kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend())
+ kdf.derive(b"password")
+ with pytest.raises(AlreadyFinalized):
+ kdf.derive(b"password2")
+
+ kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend())
+ key = kdf.derive(b"password")
+ with pytest.raises(AlreadyFinalized):
+ kdf.verify(b"password", key)
+
+ kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend())
+ kdf.verify(b"password", key)
+ with pytest.raises(AlreadyFinalized):
+ kdf.verify(b"password", key)
+
+ def test_unsupported_algorithm(self):
+ with pytest.raises(UnsupportedAlgorithm):
+ PBKDF2HMAC(DummyHash(), 20, b"salt", 10, default_backend())
+
+ def test_invalid_key(self):
+ kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend())
+ key = kdf.derive(b"password")
+
+ kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend())
+ with pytest.raises(InvalidKey):
+ kdf.verify(b"password2", key)
+
+ def test_unicode_error_with_salt(self):
+ with pytest.raises(TypeError):
+ PBKDF2HMAC(hashes.SHA1(), 20, six.u("salt"), 10, default_backend())
+
+ def test_unicode_error_with_key_material(self):
+ kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend())
+ with pytest.raises(TypeError):
+ kdf.derive(six.u("unicode here"))
diff --git a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py
new file mode 100644
index 00000000..cbd4cc9d
--- /dev/null
+++ b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py
@@ -0,0 +1,37 @@
+# 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 pytest
+
+from cryptography.hazmat.primitives import hashes
+
+from .utils import generate_pbkdf2_test
+from ...utils import load_nist_vectors
+
+
+@pytest.mark.supported(
+ only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()),
+ skip_message="Does not support SHA1 for PBKDF2HMAC",
+)
+@pytest.mark.pbkdf2hmac
+class TestPBKDF2HMAC_SHA1(object):
+ test_pbkdf2_sha1 = generate_pbkdf2_test(
+ load_nist_vectors,
+ "KDF",
+ [
+ "rfc-6070-PBKDF2-SHA1.txt",
+ ],
+ hashes.SHA1(),
+ )
diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py
index 6ecc70ff..6b1d055d 100644
--- a/tests/hazmat/primitives/utils.py
+++ b/tests/hazmat/primitives/utils.py
@@ -4,6 +4,7 @@ import os
import pytest
from cryptography.hazmat.primitives import hashes, hmac
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.exceptions import (
AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag,
@@ -141,8 +142,7 @@ def generate_hash_test(param_loader, path, file_names, hash_cls):
def hash_test(backend, algorithm, params):
- msg = params[0]
- md = params[1]
+ msg, md = params
m = hashes.Hash(algorithm, backend=backend)
m.update(binascii.unhexlify(msg))
expected_md = md.replace(" ", "").lower().encode("ascii")
@@ -206,14 +206,36 @@ def generate_hmac_test(param_loader, path, file_names, algorithm):
def hmac_test(backend, algorithm, params):
- msg = params[0]
- md = params[1]
- key = params[2]
+ msg, md, key = params
h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend)
h.update(binascii.unhexlify(msg))
assert h.finalize() == binascii.unhexlify(md.encode("ascii"))
+def generate_pbkdf2_test(param_loader, path, file_names, algorithm):
+ all_params = _load_all_params(path, file_names, param_loader)
+
+ @pytest.mark.parametrize("params", all_params)
+ def test_pbkdf2(self, backend, params):
+ pbkdf2_test(backend, algorithm, params)
+ return test_pbkdf2
+
+
+def pbkdf2_test(backend, algorithm, params):
+ # Password and salt can contain \0, which should be loaded as a null char.
+ # The NIST loader loads them as literal strings so we replace with the
+ # proper value.
+ kdf = PBKDF2HMAC(
+ algorithm,
+ int(params["length"]),
+ params["salt"],
+ int(params["iterations"]),
+ backend
+ )
+ derived_key = kdf.derive(params["password"])
+ assert binascii.hexlify(derived_key) == params["derived_key"]
+
+
def generate_aead_exception_test(cipher_factory, mode_factory):
def test_aead_exception(self, backend):
aead_exception_test(backend, cipher_factory, mode_factory)
diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt
new file mode 100644
index 00000000..b3fd03aa
--- /dev/null
+++ b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA1.txt
@@ -0,0 +1,56 @@
+# A.4. Test Case 4
+# Basic test case with SHA-1
+
+COUNT = 4
+
+Hash = SHA-1
+IKM = 0b0b0b0b0b0b0b0b0b0b0b
+salt = 000102030405060708090a0b0c
+info = f0f1f2f3f4f5f6f7f8f9
+L = 42
+
+PRK = 9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243
+OKM = 085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896
+
+# A.5. Test Case 5
+# Test with SHA-1 and longer inputs/outputs
+
+COUNT = 5
+
+Hash = SHA-1
+IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f
+salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
+info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
+L = 82
+
+PRK = 8adae09a2a307059478d309b26c4115a224cfaf6
+OKM = 0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4
+
+# A.6. Test Case 6
+# Test with SHA-1 and zero-length salt/info
+
+COUNT = 6
+
+Hash = SHA-1
+IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+salt =
+info =
+L = 42
+
+PRK = da8c8a73c7fa77288ec6f5e7c297786aa0d32d01
+OKM = 0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918
+
+# A.7. Test Case 7
+# Test with SHA-1, salt not provided (defaults to HashLen zero octets),
+# zero-length info
+
+COUNT = 7
+
+Hash = SHA-1
+IKM = 0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c
+salt =
+info =
+L = 42
+
+PRK = 2adccada18779e7c2077ad2eb19d3f3e731385dd
+OKM = 2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48
diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt
new file mode 100644
index 00000000..9068a739
--- /dev/null
+++ b/tests/hazmat/primitives/vectors/KDF/rfc-5869-HKDF-SHA256.txt
@@ -0,0 +1,40 @@
+# A.1. Test Case 1
+# Basic test case with SHA-256
+
+COUNT = 1
+
+Hash = SHA-256
+IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+salt = 000102030405060708090a0b0c
+info = f0f1f2f3f4f5f6f7f8f9
+L = 42
+PRK = 077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5
+OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865
+
+# A.2. Test Case 2
+# Test with SHA-256 and longer inputs/outputs
+
+COUNT = 2
+
+Hash = SHA-256
+IKM = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f
+salt = 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
+info = b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
+L = 82
+
+PRK = 06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244
+OKM = b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87
+
+# A.3. Test Case 3
+# Test with SHA-256 and zero-length salt/info
+
+COUNT = 3
+
+Hash = SHA-256
+IKM = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+salt =
+info =
+L = 42
+
+PRK = 19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04
+OKM = 8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8
diff --git a/tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt b/tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt
new file mode 100644
index 00000000..739f3f36
--- /dev/null
+++ b/tests/hazmat/primitives/vectors/KDF/rfc-6070-PBKDF2-SHA1.txt
@@ -0,0 +1,48 @@
+# PBKDF2 SHA1 vectors from http://www.ietf.org/rfc/rfc6070.txt
+
+COUNT = 0
+PASSWORD = password
+SALT = salt
+ITERATIONS = 1
+LENGTH = 20
+DERIVED_KEY = 0c60c80f961f0e71f3a9b524af6012062fe037a6
+
+
+COUNT = 1
+PASSWORD = password
+SALT = salt
+ITERATIONS = 2
+LENGTH = 20
+DERIVED_KEY = ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957
+
+
+COUNT = 2
+PASSWORD = password
+SALT = salt
+ITERATIONS = 4096
+LENGTH = 20
+DERIVED_KEY = 4b007901b765489abead49d926f721d065a429c1
+
+
+COUNT = 3
+PASSWORD = password
+SALT = salt
+ITERATIONS = 16777216
+LENGTH = 20
+DERIVED_KEY = eefe3d61cd4da4e4e9945b3d6ba2158c2634e984
+
+
+COUNT = 4
+PASSWORD = passwordPASSWORDpassword
+SALT = saltSALTsaltSALTsaltSALTsaltSALTsalt
+ITERATIONS = 4096
+LENGTH = 25
+DERIVED_KEY = 3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038
+
+
+COUNT = 5
+PASSWORD = pass\0word
+SALT = sa\0lt
+ITERATIONS = 4096
+LENGTH = 16
+DERIVED_KEY = 56fa6aa75548099dcc37d7f03425e0c3
diff --git a/tests/test_utils.py b/tests/test_utils.py
index f852f3ab..8ecb33f9 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -180,6 +180,25 @@ def test_load_nist_vectors():
]
+def test_load_nist_vectors_with_null_chars():
+ vector_data = textwrap.dedent("""
+ COUNT = 0
+ KEY = thing\\0withnulls
+
+ COUNT = 1
+ KEY = 00000000000000000000000000000000
+ """).splitlines()
+
+ assert load_nist_vectors(vector_data) == [
+ {
+ "key": b"thing\x00withnulls",
+ },
+ {
+ "key": b"00000000000000000000000000000000",
+ },
+ ]
+
+
def test_load_cryptrec_vectors():
vector_data = textwrap.dedent("""
# Vectors taken from http://info.isl.ntt.co.jp/crypt/eng/camellia/
diff --git a/tests/utils.py b/tests/utils.py
index a2432256..5c0e524f 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -11,11 +11,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import collections
import os
import pytest
+HashVector = collections.namedtuple("HashVector", ["message", "digest"])
+KeyedHashVector = collections.namedtuple(
+ "KeyedHashVector", ["message", "digest", "key"]
+)
+
+
def select_backends(names, backend_list):
if names is None:
return backend_list
@@ -82,6 +89,10 @@ def load_nist_vectors(vector_data):
# Build our data using a simple Key = Value format
name, value = [c.strip() for c in line.split("=")]
+ # Some tests (PBKDF2) contain \0, which should be interpreted as a
+ # null character rather than literal.
+ value = value.replace("\\0", "\0")
+
# COUNT is a special token that indicates a new block of data
if name.upper() == "COUNT":
test_data = {}
@@ -158,27 +169,23 @@ def load_hash_vectors(vector_data):
if line.startswith("Len"):
length = int(line.split(" = ")[1])
elif line.startswith("Key"):
- """
- HMAC vectors contain a key attribute. Hash vectors do not.
- """
+ # HMAC vectors contain a key attribute. Hash vectors do not.
key = line.split(" = ")[1].encode("ascii")
elif line.startswith("Msg"):
- """
- In the NIST vectors they have chosen to represent an empty
- string as hex 00, which is of course not actually an empty
- string. So we parse the provided length and catch this edge case.
- """
+ # In the NIST vectors they have chosen to represent an empty
+ # string as hex 00, which is of course not actually an empty
+ # string. So we parse the provided length and catch this edge case.
msg = line.split(" = ")[1].encode("ascii") if length > 0 else b""
elif line.startswith("MD"):
md = line.split(" = ")[1]
# after MD is found the Msg+MD (+ potential key) tuple is complete
if key is not None:
- vectors.append((msg, md, key))
+ vectors.append(KeyedHashVector(msg, md, key))
key = None
msg = None
md = None
else:
- vectors.append((msg, md))
+ vectors.append(HashVector(msg, md))
msg = None
md = None
else: