Merge branch 'master' into fernet

Conflicts:
	docs/index.rst
This commit is contained in:
Alex Gaynor 2014-01-02 10:55:36 -08:00
commit 5bae063ff2
41 changed files with 434 additions and 275 deletions

View file

@ -5,8 +5,24 @@ set -x
if [[ "${OPENSSL}" == "0.9.8" ]]; then
sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ lucid main"
sudo apt-get -y update
fi
if [[ "${TOX_ENV}" == "pypy" ]]; then
sudo add-apt-repository -y ppa:pypy/ppa
fi
sudo apt-get -y update
if [[ "${OPENSSL}" == "0.9.8" ]]; then
sudo apt-get install -y --force-yes libssl-dev/lucid
fi
if [[ "${TOX_ENV}" == "pypy" ]]; then
sudo apt-get install -y pypy
# This is required because we need to get rid of the Travis installed PyPy
# or it'll take precedence over the PPA installed one.
sudo rm -rf /usr/local/pypy/bin
fi
pip install tox coveralls

View file

@ -30,4 +30,4 @@ __author__ = ("Alex Gaynor, Hynek Schlawack, Donald Stufft, "
__email__ = "cryptography-dev@python.org"
__license__ = "Apache License, Version 2.0"
__copyright__ = "Copyright 2013 %s" % __author__
__copyright__ = "Copyright 2013-2014 %s" % __author__

View file

@ -14,9 +14,6 @@
from __future__ import absolute_import, division, print_function
import itertools
import sys
import cffi
from cryptography import utils
from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag
@ -30,24 +27,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import (
from cryptography.hazmat.primitives.ciphers.modes import (
CBC, CTR, ECB, OFB, CFB, GCM,
)
_OSX_PRE_INCLUDE = """
#ifdef __APPLE__
#include <AvailabilityMacros.h>
#define __ORIG_DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER \
DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#undef DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
"""
_OSX_POST_INCLUDE = """
#ifdef __APPLE__
#undef DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER \
__ORIG_DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
"""
from cryptography.hazmat.bindings.openssl.binding import Binding
@utils.register_interface(CipherBackend)
@ -55,135 +35,34 @@ _OSX_POST_INCLUDE = """
@utils.register_interface(HMACBackend)
class Backend(object):
"""
OpenSSL API wrapper.
Modules listed in the ``_modules`` listed should have the following
attributes:
* ``INCLUDES``: A string containg C includes.
* ``TYPES``: A string containing C declarations for types.
* ``FUNCTIONS``: A string containing C declarations for functions.
* ``MACROS``: A string containing C declarations for any macros.
* ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this
can be used to do things like test for a define and provide an
alternate implementation based on that.
* ``CONDITIONAL_NAMES``: A dict mapping strings of condition names from the
library to a list of names which will not be present without the
condition.
OpenSSL API binding interfaces.
"""
_module_prefix = "cryptography.hazmat.backends.openssl."
_modules = [
"asn1",
"bignum",
"bio",
"conf",
"crypto",
"dh",
"dsa",
"engine",
"err",
"evp",
"hmac",
"nid",
"objects",
"opensslv",
"pem",
"pkcs7",
"pkcs12",
"rand",
"rsa",
"ssl",
"x509",
"x509name",
"x509v3",
]
ffi = None
lib = None
def __init__(self):
self._ensure_ffi_initialized()
self._binding = Binding()
self._ffi = self._binding.ffi
self._lib = self._binding.lib
self._lib.OpenSSL_add_all_algorithms()
self._lib.SSL_load_error_strings()
self._cipher_registry = {}
self._register_default_ciphers()
@classmethod
def _ensure_ffi_initialized(cls):
if cls.ffi is not None and cls.lib is not None:
return
ffi = cffi.FFI()
includes = []
functions = []
macros = []
customizations = []
for name in cls._modules:
module_name = cls._module_prefix + name
__import__(module_name)
module = sys.modules[module_name]
ffi.cdef(module.TYPES)
macros.append(module.MACROS)
functions.append(module.FUNCTIONS)
includes.append(module.INCLUDES)
customizations.append(module.CUSTOMIZATIONS)
# loop over the functions & macros after declaring all the types
# so we can set interdependent types in different files and still
# have them all defined before we parse the funcs & macros
for func in functions:
ffi.cdef(func)
for macro in macros:
ffi.cdef(macro)
# We include functions here so that if we got any of their definitions
# wrong, the underlying C compiler will explode. In C you are allowed
# to re-declare a function if it has the same signature. That is:
# int foo(int);
# int foo(int);
# is legal, but the following will fail to compile:
# int foo(int);
# int foo(short);
lib = ffi.verify(
source="\n".join(
[_OSX_PRE_INCLUDE] +
includes +
[_OSX_POST_INCLUDE] +
functions +
customizations
),
libraries=["crypto", "ssl"],
)
for name in cls._modules:
module_name = cls._module_prefix + name
module = sys.modules[module_name]
for condition, names in module.CONDITIONAL_NAMES.items():
if not getattr(lib, condition):
for name in names:
delattr(lib, name)
cls.ffi = ffi
cls.lib = lib
cls.lib.OpenSSL_add_all_algorithms()
cls.lib.SSL_load_error_strings()
def openssl_version_text(self):
"""
Friendly string name of linked OpenSSL.
Example: OpenSSL 1.0.1e 11 Feb 2013
"""
return self.ffi.string(self.lib.OPENSSL_VERSION_TEXT).decode("ascii")
return self._ffi.string(self._lib.OPENSSL_VERSION_TEXT).decode("ascii")
def create_hmac_ctx(self, key, algorithm):
return _HMACContext(self, key, algorithm)
def hash_supported(self, algorithm):
digest = self.lib.EVP_get_digestbyname(algorithm.name.encode("ascii"))
return digest != self.ffi.NULL
digest = self._lib.EVP_get_digestbyname(algorithm.name.encode("ascii"))
return digest != self._ffi.NULL
def hmac_supported(self, algorithm):
return self.hash_supported(algorithm)
@ -197,7 +76,7 @@ class Backend(object):
except KeyError:
return False
evp_cipher = adapter(self, cipher, mode)
return self.ffi.NULL != evp_cipher
return self._ffi.NULL != evp_cipher
def register_cipher_adapter(self, cipher_cls, mode_cls, adapter):
if (cipher_cls, mode_cls) in self._cipher_registry:
@ -251,25 +130,25 @@ class Backend(object):
return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT)
def _handle_error(self, mode):
code = self.lib.ERR_get_error()
code = self._lib.ERR_get_error()
if not code and isinstance(mode, GCM):
raise InvalidTag
assert code != 0
lib = self.lib.ERR_GET_LIB(code)
func = self.lib.ERR_GET_FUNC(code)
reason = self.lib.ERR_GET_REASON(code)
lib = self._lib.ERR_GET_LIB(code)
func = self._lib.ERR_GET_FUNC(code)
reason = self._lib.ERR_GET_REASON(code)
return self._handle_error_code(lib, func, reason)
def _handle_error_code(self, lib, func, reason):
if lib == self.lib.ERR_LIB_EVP:
if func == self.lib.EVP_F_EVP_ENCRYPTFINAL_EX:
if reason == self.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
if lib == self._lib.ERR_LIB_EVP:
if func == self._lib.EVP_F_EVP_ENCRYPTFINAL_EX:
if reason == self._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
raise ValueError(
"The length of the provided data is not a multiple of "
"the block length"
)
elif func == self.lib.EVP_F_EVP_DECRYPTFINAL_EX:
if reason == self.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
elif func == self._lib.EVP_F_EVP_DECRYPTFINAL_EX:
if reason == self._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
raise ValueError(
"The length of the provided data is not a multiple of "
"the block length"
@ -286,7 +165,7 @@ class GetCipherByName(object):
def __call__(self, backend, cipher, mode):
cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower()
return backend.lib.EVP_get_cipherbyname(cipher_name.encode("ascii"))
return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii"))
@utils.register_interface(interfaces.CipherContext)
@ -308,8 +187,10 @@ class _CipherContext(object):
else:
self._block_size = 1
ctx = self._backend.lib.EVP_CIPHER_CTX_new()
ctx = self._backend.ffi.gc(ctx, self._backend.lib.EVP_CIPHER_CTX_free)
ctx = self._backend._lib.EVP_CIPHER_CTX_new()
ctx = self._backend._ffi.gc(
ctx, self._backend._lib.EVP_CIPHER_CTX_free
)
registry = self._backend._cipher_registry
try:
@ -322,7 +203,7 @@ class _CipherContext(object):
)
evp_cipher = adapter(self._backend, cipher, mode)
if evp_cipher == self._backend.ffi.NULL:
if evp_cipher == self._backend._ffi.NULL:
raise UnsupportedAlgorithm(
"cipher {0} in {1} mode is not supported "
"by this backend".format(
@ -334,86 +215,84 @@ class _CipherContext(object):
elif isinstance(mode, interfaces.ModeWithNonce):
iv_nonce = mode.nonce
else:
iv_nonce = self._backend.ffi.NULL
iv_nonce = self._backend._ffi.NULL
# begin init with cipher and operation type
res = self._backend.lib.EVP_CipherInit_ex(ctx, evp_cipher,
self._backend.ffi.NULL,
self._backend.ffi.NULL,
self._backend.ffi.NULL,
operation)
res = self._backend._lib.EVP_CipherInit_ex(ctx, evp_cipher,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
operation)
assert res != 0
# set the key length to handle variable key ciphers
res = self._backend.lib.EVP_CIPHER_CTX_set_key_length(
res = self._backend._lib.EVP_CIPHER_CTX_set_key_length(
ctx, len(cipher.key)
)
assert res != 0
if isinstance(mode, GCM):
res = self._backend.lib.EVP_CIPHER_CTX_ctrl(
ctx, self._backend.lib.EVP_CTRL_GCM_SET_IVLEN,
len(iv_nonce), self._backend.ffi.NULL
res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
ctx, self._backend._lib.EVP_CTRL_GCM_SET_IVLEN,
len(iv_nonce), self._backend._ffi.NULL
)
assert res != 0
if operation == self._DECRYPT:
if not mode.tag or len(mode.tag) < 4:
raise ValueError("Authentication tag must be provided and "
"be 4 bytes or longer when decrypting")
res = self._backend.lib.EVP_CIPHER_CTX_ctrl(
ctx, self._backend.lib.EVP_CTRL_GCM_SET_TAG,
res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
ctx, self._backend._lib.EVP_CTRL_GCM_SET_TAG,
len(mode.tag), mode.tag
)
assert res != 0
else:
if mode.tag:
raise ValueError("Authentication tag must be None when "
"encrypting")
# pass key/iv
res = self._backend.lib.EVP_CipherInit_ex(ctx, self._backend.ffi.NULL,
self._backend.ffi.NULL,
cipher.key,
iv_nonce,
operation)
res = self._backend._lib.EVP_CipherInit_ex(
ctx,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
cipher.key,
iv_nonce,
operation
)
assert res != 0
# We purposely disable padding here as it's handled higher up in the
# API.
self._backend.lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
self._backend._lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
self._ctx = ctx
def update(self, data):
buf = self._backend.ffi.new("unsigned char[]",
len(data) + self._block_size - 1)
outlen = self._backend.ffi.new("int *")
res = self._backend.lib.EVP_CipherUpdate(self._ctx, buf, outlen, data,
len(data))
buf = self._backend._ffi.new("unsigned char[]",
len(data) + self._block_size - 1)
outlen = self._backend._ffi.new("int *")
res = self._backend._lib.EVP_CipherUpdate(self._ctx, buf, outlen, data,
len(data))
assert res != 0
return self._backend.ffi.buffer(buf)[:outlen[0]]
return self._backend._ffi.buffer(buf)[:outlen[0]]
def finalize(self):
buf = self._backend.ffi.new("unsigned char[]", self._block_size)
outlen = self._backend.ffi.new("int *")
res = self._backend.lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)
buf = self._backend._ffi.new("unsigned char[]", self._block_size)
outlen = self._backend._ffi.new("int *")
res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)
if res == 0:
self._backend._handle_error(self._mode)
if (isinstance(self._mode, GCM) and
self._operation == self._ENCRYPT):
block_byte_size = self._block_size // 8
tag_buf = self._backend.ffi.new("unsigned char[]", block_byte_size)
res = self._backend.lib.EVP_CIPHER_CTX_ctrl(
self._ctx, self._backend.lib.EVP_CTRL_GCM_GET_TAG,
tag_buf = self._backend._ffi.new(
"unsigned char[]", block_byte_size
)
res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
self._ctx, self._backend._lib.EVP_CTRL_GCM_GET_TAG,
block_byte_size, tag_buf
)
assert res != 0
self._tag = self._backend.ffi.buffer(tag_buf)[:]
self._tag = self._backend._ffi.buffer(tag_buf)[:]
res = self._backend.lib.EVP_CIPHER_CTX_cleanup(self._ctx)
res = self._backend._lib.EVP_CIPHER_CTX_cleanup(self._ctx)
assert res == 1
return self._backend.ffi.buffer(buf)[:outlen[0]]
return self._backend._ffi.buffer(buf)[:outlen[0]]
def authenticate_additional_data(self, data):
outlen = self._backend.ffi.new("int *")
res = self._backend.lib.EVP_CipherUpdate(
self._ctx, self._backend.ffi.NULL, outlen, data, len(data)
outlen = self._backend._ffi.new("int *")
res = self._backend._lib.EVP_CipherUpdate(
self._ctx, self._backend._ffi.NULL, outlen, data, len(data)
)
assert res != 0
@ -430,43 +309,44 @@ class _HashContext(object):
self._backend = backend
if ctx is None:
ctx = self._backend.lib.EVP_MD_CTX_create()
ctx = self._backend.ffi.gc(ctx,
self._backend.lib.EVP_MD_CTX_destroy)
evp_md = self._backend.lib.EVP_get_digestbyname(
ctx = self._backend._lib.EVP_MD_CTX_create()
ctx = self._backend._ffi.gc(ctx,
self._backend._lib.EVP_MD_CTX_destroy)
evp_md = self._backend._lib.EVP_get_digestbyname(
algorithm.name.encode("ascii"))
if evp_md == self._backend.ffi.NULL:
if evp_md == self._backend._ffi.NULL:
raise UnsupportedAlgorithm(
"{0} is not a supported hash on this backend".format(
algorithm.name)
)
res = self._backend.lib.EVP_DigestInit_ex(ctx, evp_md,
self._backend.ffi.NULL)
res = self._backend._lib.EVP_DigestInit_ex(ctx, evp_md,
self._backend._ffi.NULL)
assert res != 0
self._ctx = ctx
def copy(self):
copied_ctx = self._backend.lib.EVP_MD_CTX_create()
copied_ctx = self._backend.ffi.gc(copied_ctx,
self._backend.lib.EVP_MD_CTX_destroy)
res = self._backend.lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx)
copied_ctx = self._backend._lib.EVP_MD_CTX_create()
copied_ctx = self._backend._ffi.gc(
copied_ctx, self._backend._lib.EVP_MD_CTX_destroy
)
res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx)
assert res != 0
return _HashContext(self._backend, self.algorithm, ctx=copied_ctx)
def update(self, data):
res = self._backend.lib.EVP_DigestUpdate(self._ctx, data, len(data))
res = self._backend._lib.EVP_DigestUpdate(self._ctx, data, len(data))
assert res != 0
def finalize(self):
buf = self._backend.ffi.new("unsigned char[]",
self.algorithm.digest_size)
res = self._backend.lib.EVP_DigestFinal_ex(self._ctx, buf,
self._backend.ffi.NULL)
buf = self._backend._ffi.new("unsigned char[]",
self.algorithm.digest_size)
res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf,
self._backend._ffi.NULL)
assert res != 0
res = self._backend.lib.EVP_MD_CTX_cleanup(self._ctx)
res = self._backend._lib.EVP_MD_CTX_cleanup(self._ctx)
assert res == 1
return self._backend.ffi.buffer(buf)[:]
return self._backend._ffi.buffer(buf)[:]
@utils.register_interface(interfaces.HashContext)
@ -476,18 +356,20 @@ class _HMACContext(object):
self._backend = backend
if ctx is None:
ctx = self._backend.ffi.new("HMAC_CTX *")
self._backend.lib.HMAC_CTX_init(ctx)
ctx = self._backend.ffi.gc(ctx, self._backend.lib.HMAC_CTX_cleanup)
evp_md = self._backend.lib.EVP_get_digestbyname(
ctx = self._backend._ffi.new("HMAC_CTX *")
self._backend._lib.HMAC_CTX_init(ctx)
ctx = self._backend._ffi.gc(
ctx, self._backend._lib.HMAC_CTX_cleanup
)
evp_md = self._backend._lib.EVP_get_digestbyname(
algorithm.name.encode('ascii'))
if evp_md == self._backend.ffi.NULL:
if evp_md == self._backend._ffi.NULL:
raise UnsupportedAlgorithm(
"{0} is not a supported hash on this backend".format(
algorithm.name)
)
res = self._backend.lib.Cryptography_HMAC_Init_ex(
ctx, key, len(key), evp_md, self._backend.ffi.NULL
res = self._backend._lib.Cryptography_HMAC_Init_ex(
ctx, key, len(key), evp_md, self._backend._ffi.NULL
)
assert res != 0
@ -495,12 +377,12 @@ class _HMACContext(object):
self._key = key
def copy(self):
copied_ctx = self._backend.ffi.new("HMAC_CTX *")
self._backend.lib.HMAC_CTX_init(copied_ctx)
copied_ctx = self._backend.ffi.gc(
copied_ctx, self._backend.lib.HMAC_CTX_cleanup
copied_ctx = self._backend._ffi.new("HMAC_CTX *")
self._backend._lib.HMAC_CTX_init(copied_ctx)
copied_ctx = self._backend._ffi.gc(
copied_ctx, self._backend._lib.HMAC_CTX_cleanup
)
res = self._backend.lib.Cryptography_HMAC_CTX_copy(
res = self._backend._lib.Cryptography_HMAC_CTX_copy(
copied_ctx, self._ctx
)
assert res != 0
@ -509,20 +391,22 @@ class _HMACContext(object):
)
def update(self, data):
res = self._backend.lib.Cryptography_HMAC_Update(
res = self._backend._lib.Cryptography_HMAC_Update(
self._ctx, data, len(data)
)
assert res != 0
def finalize(self):
buf = self._backend.ffi.new("unsigned char[]",
self.algorithm.digest_size)
buflen = self._backend.ffi.new("unsigned int *",
self.algorithm.digest_size)
res = self._backend.lib.Cryptography_HMAC_Final(self._ctx, buf, buflen)
buf = self._backend._ffi.new("unsigned char[]",
self.algorithm.digest_size)
buflen = self._backend._ffi.new("unsigned int *",
self.algorithm.digest_size)
res = self._backend._lib.Cryptography_HMAC_Final(
self._ctx, buf, buflen
)
assert res != 0
self._backend.lib.HMAC_CTX_cleanup(self._ctx)
return self._backend.ffi.buffer(buf)[:]
self._backend._lib.HMAC_CTX_cleanup(self._ctx)
return self._backend._ffi.buffer(buf)[:]
backend = Backend()

View file

@ -0,0 +1,12 @@
# 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.

View file

@ -0,0 +1,12 @@
# 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.

View file

@ -0,0 +1,149 @@
# 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 sys
import cffi
_OSX_PRE_INCLUDE = """
#ifdef __APPLE__
#include <AvailabilityMacros.h>
#define __ORIG_DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER \
DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#undef DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
"""
_OSX_POST_INCLUDE = """
#ifdef __APPLE__
#undef DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER \
__ORIG_DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
"""
class Binding(object):
"""
OpenSSL API wrapper.
Modules listed in the ``_modules`` listed should have the following
attributes:
* ``INCLUDES``: A string containg C includes.
* ``TYPES``: A string containing C declarations for types.
* ``FUNCTIONS``: A string containing C declarations for functions.
* ``MACROS``: A string containing C declarations for any macros.
* ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this
can be used to do things like test for a define and provide an
alternate implementation based on that.
* ``CONDITIONAL_NAMES``: A dict mapping strings of condition names from the
library to a list of names which will not be present without the
condition.
"""
_module_prefix = "cryptography.hazmat.bindings.openssl."
_modules = [
"asn1",
"bignum",
"bio",
"conf",
"crypto",
"dh",
"dsa",
"engine",
"err",
"evp",
"hmac",
"nid",
"objects",
"opensslv",
"pem",
"pkcs7",
"pkcs12",
"rand",
"rsa",
"ssl",
"x509",
"x509name",
"x509v3",
]
ffi = None
lib = None
def __init__(self):
self._ensure_ffi_initialized()
@classmethod
def _ensure_ffi_initialized(cls):
if cls.ffi is not None and cls.lib is not None:
return
ffi = cffi.FFI()
includes = []
functions = []
macros = []
customizations = []
for name in cls._modules:
module_name = cls._module_prefix + name
__import__(module_name)
module = sys.modules[module_name]
ffi.cdef(module.TYPES)
macros.append(module.MACROS)
functions.append(module.FUNCTIONS)
includes.append(module.INCLUDES)
customizations.append(module.CUSTOMIZATIONS)
# loop over the functions & macros after declaring all the types
# so we can set interdependent types in different files and still
# have them all defined before we parse the funcs & macros
for func in functions:
ffi.cdef(func)
for macro in macros:
ffi.cdef(macro)
# We include functions here so that if we got any of their definitions
# wrong, the underlying C compiler will explode. In C you are allowed
# to re-declare a function if it has the same signature. That is:
# int foo(int);
# int foo(int);
# is legal, but the following will fail to compile:
# int foo(int);
# int foo(short);
lib = ffi.verify(
source="\n".join(
[_OSX_PRE_INCLUDE] +
includes +
[_OSX_POST_INCLUDE] +
functions +
customizations
),
libraries=["crypto", "ssl"],
)
for name in cls._modules:
module_name = cls._module_prefix + name
module = sys.modules[module_name]
for condition, names in module.CONDITIONAL_NAMES.items():
if not getattr(lib, condition):
for name in names:
delattr(lib, name)
cls.ffi = ffi
cls.lib = lib

View file

@ -36,6 +36,11 @@ int CRYPTO_mem_ctrl(int);
int CRYPTO_is_mem_check_on(void);
void CRYPTO_mem_leaks(struct bio_st *);
void CRYPTO_cleanup_all_ex_data(void);
int CRYPTO_num_locks(void);
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 OPENSSL_free(void *);
"""

View file

@ -30,16 +30,26 @@ class Cipher(object):
self._backend = backend
def encryptor(self):
if isinstance(self.mode, interfaces.ModeWithAuthenticationTag):
if self.mode.tag is not None:
raise ValueError(
"Authentication tag must be None when encrypting"
)
ctx = self._backend.create_symmetric_encryption_ctx(
self.algorithm, self.mode
)
return self._wrap_ctx(ctx, True)
return self._wrap_ctx(ctx, encrypt=True)
def decryptor(self):
if isinstance(self.mode, interfaces.ModeWithAuthenticationTag):
if self.mode.tag is None:
raise ValueError(
"Authentication tag must be provided when decrypting"
)
ctx = self._backend.create_symmetric_decryption_ctx(
self.algorithm, self.mode
)
return self._wrap_ctx(ctx, False)
return self._wrap_ctx(ctx, encrypt=False)
def _wrap_ctx(self, ctx, encrypt):
if isinstance(self.mode, interfaces.ModeWithAuthenticationTag):

View file

@ -65,5 +65,10 @@ class GCM(object):
name = "GCM"
def __init__(self, initialization_vector, tag=None):
if tag is not None and len(tag) < 4:
raise ValueError(
"Authentication tag must be 4 bytes or longer"
)
self.initialization_vector = initialization_vector
self.tag = tag

View file

@ -1,13 +0,0 @@
Architecture
============
``cryptography`` has three different layers:
* ``cryptography``: This package contains higher level recipes, for example
"encrypt and then MAC". This is implemented on top of
``cryptography.hazmat.primitives``.
* ``cryptography.hazmat.primitives``: This packages contains low level
algorithms, things like ``AES`` or ``SHA1``. This is implemented on top of
``cryptography.hazmat.backends``.
* ``cryptography.hazmat.backends``: This package contains bindings to low level
cryptographic libraries. Our initial target is OpenSSL.

View file

@ -54,7 +54,7 @@ master_doc = 'index'
# General information about the project.
project = 'Cryptography'
copyright = '2013, Individual Contributors'
copyright = '2013-2014, Individual Contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View file

@ -3,23 +3,37 @@
OpenSSL Backend
===============
These are `CFFI`_ bindings to the `OpenSSL`_ C library.
The `OpenSSL`_ C library.
.. data:: cryptography.hazmat.backends.openssl.backend
This is the exposed API for the OpenSSL bindings. It has two public
attributes:
This is the exposed API for the OpenSSL backend. It has no public attributes.
.. attribute:: ffi
Using your own OpenSSL on Linux
-------------------------------
This is a :class:`cffi.FFI` instance. It can be used to allocate and
otherwise manipulate OpenSSL structures.
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.
.. attribute:: lib
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::
This is a ``cffi`` library. It can be used to call OpenSSL functions,
and access constants.
./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.
.. _`CFFI`: https://cffi.readthedocs.org/
.. _`OpenSSL`: https://www.openssl.org/

View file

@ -0,0 +1,22 @@
.. hazmat::
Bindings
========
.. currentmodule:: cryptography.hazmat.bindings
``cryptography`` aims to provide low-level CFFI based bindings to multiple
native C libraries. These provide no automatic initialisation of the library
and may not provide complete wrappers for its API.
Using these functions directly is likely to require you to be careful in
managing memory allocation, locking and other resources.
Individual Bindings
-------------------
.. toctree::
:maxdepth: 1
openssl

View file

@ -0,0 +1,27 @@
.. hazmat::
OpenSSL Binding
===============
.. currentmodule:: cryptography.hazmat.bindings.openssl.binding
These are `CFFI`_ bindings to the `OpenSSL`_ C library.
.. class:: cryptography.hazmat.bindings.openssl.binding.Binding()
This is the exposed API for the OpenSSL bindings. It has two public
attributes:
.. attribute:: ffi
This is a :class:`cffi.FFI` instance. It can be used to allocate and
otherwise manipulate OpenSSL structures.
.. attribute:: lib
This is a ``cffi`` library. It can be used to call OpenSSL functions,
and access constants.
.. _`CFFI`: https://cffi.readthedocs.org/
.. _`OpenSSL`: https://www.openssl.org/

View file

@ -43,7 +43,9 @@ The other level is low-level cryptographic primitives. These are often
dangerous and can be used incorrectly. They require making decisions and having
an in-depth knowledge of the cryptographic concepts at work. Because of the
potential danger in working at this level, this is referred to as the
"hazardous materials" or "hazmat" layer.
"hazardous materials" or "hazmat" layer. These live in the
``cryptography.hazmat`` package, and their documentation will always contain an
admonition at the top.
We recommend using the recipes layer whenever possible, and falling back to the
hazmat layer only when necessary.
@ -55,7 +57,6 @@ The recipes layer
:maxdepth: 2
fernet
architecture
exceptions
glossary
@ -67,6 +68,7 @@ The hazardous materials layer
hazmat/primitives/index
hazmat/backends/index
hazmat/bindings/index
The ``cryptography`` open source project
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -58,18 +58,13 @@ class TestOpenSSL(object):
with pytest.raises(ValueError):
backend.register_cipher_adapter(AES, CBC, None)
def test_instances_share_ffi(self):
b = Backend()
assert b.ffi is backend.ffi
assert b.lib is backend.lib
@pytest.mark.parametrize("mode", [DummyMode(), None])
def test_nonexistent_cipher(self, mode):
b = Backend()
b.register_cipher_adapter(
DummyCipher,
type(mode),
lambda backend, cipher, mode: backend.ffi.NULL
lambda backend, cipher, mode: backend._ffi.NULL
)
cipher = Cipher(
DummyCipher(), mode, backend=b,
@ -82,18 +77,18 @@ class TestOpenSSL(object):
backend._handle_error_code(0, 0, 0)
with pytest.raises(SystemError):
backend._handle_error_code(backend.lib.ERR_LIB_EVP, 0, 0)
backend._handle_error_code(backend._lib.ERR_LIB_EVP, 0, 0)
with pytest.raises(SystemError):
backend._handle_error_code(
backend.lib.ERR_LIB_EVP,
backend.lib.EVP_F_EVP_ENCRYPTFINAL_EX,
backend._lib.ERR_LIB_EVP,
backend._lib.EVP_F_EVP_ENCRYPTFINAL_EX,
0
)
with pytest.raises(SystemError):
backend._handle_error_code(
backend.lib.ERR_LIB_EVP,
backend.lib.EVP_F_EVP_DECRYPTFINAL_EX,
backend._lib.ERR_LIB_EVP,
backend._lib.EVP_F_EVP_DECRYPTFINAL_EX,
0
)

View file

@ -0,0 +1,22 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from cryptography.hazmat.bindings.openssl.binding import Binding
class TestOpenSSL(object):
def test_binding_loads(self):
binding = Binding()
assert binding
assert binding.lib
assert binding.ffi

View file

@ -264,13 +264,10 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory):
)
with pytest.raises(ValueError):
cipher.decryptor()
cipher = Cipher(
cipher_factory(binascii.unhexlify(b"0" * 32)),
mode_factory(binascii.unhexlify(b"0" * 24), b"000"),
backend
)
with pytest.raises(ValueError):
cipher.decryptor()
mode_factory(binascii.unhexlify(b"0" * 24), b"000")
cipher = Cipher(
cipher_factory(binascii.unhexlify(b"0" * 32)),
mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16),

View file

@ -25,7 +25,7 @@ commands =
# Temporarily disable coverage on pypy because of performance problems with
# coverage.py on pypy.
[testenv:pypy]
commands = py.test --capture=no
commands = py.test --capture=no --strict
[testenv:pep8]
deps = flake8