aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/hazmat/backends/interfaces.py6
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py166
-rw-r--r--cryptography/hazmat/bindings/openssl/dsa.py8
-rw-r--r--cryptography/hazmat/bindings/openssl/err.py2
-rw-r--r--cryptography/hazmat/bindings/openssl/ssl.py28
-rw-r--r--cryptography/hazmat/primitives/asymmetric/rsa.py9
-rw-r--r--cryptography/hazmat/primitives/cmac.py75
-rw-r--r--docs/hazmat/backends/interfaces.rst14
-rw-r--r--docs/hazmat/primitives/asymmetric/padding.rst6
-rw-r--r--docs/hazmat/primitives/asymmetric/rsa.rst34
-rw-r--r--docs/hazmat/primitives/index.rst2
-rw-r--r--docs/hazmat/primitives/interfaces.rst18
-rw-r--r--docs/hazmat/primitives/mac/cmac.rst105
-rw-r--r--docs/hazmat/primitives/mac/hmac.rst (renamed from docs/hazmat/primitives/hmac.rst)0
-rw-r--r--docs/hazmat/primitives/mac/index.rst10
-rw-r--r--docs/hazmat/primitives/symmetric-encryption.rst4
-rw-r--r--docs/installation.rst17
-rw-r--r--pytest.ini1
-rw-r--r--tests/conftest.py6
-rw-r--r--tests/hazmat/backends/test_openssl.py4
-rw-r--r--tests/hazmat/bindings/test_openssl.py34
-rw-r--r--tests/hazmat/primitives/test_cmac.py217
-rw-r--r--tests/hazmat/primitives/test_rsa.py95
23 files changed, 830 insertions, 31 deletions
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py
index 92413d8c..677f4c67 100644
--- a/cryptography/hazmat/backends/interfaces.py
+++ b/cryptography/hazmat/backends/interfaces.py
@@ -117,6 +117,12 @@ class RSABackend(object):
Return True if the hash algorithm is supported for MGF1 in PSS.
"""
+ @abc.abstractmethod
+ def decrypt_rsa(self, private_key, ciphertext, padding):
+ """
+ Returns decrypted bytes.
+ """
+
@six.add_metaclass(abc.ABCMeta)
class DSABackend(object):
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 86fa704b..7d73c413 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -25,8 +25,8 @@ from cryptography.exceptions import (
UnsupportedAlgorithm, _Reasons
)
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
- RSABackend
+ CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
+ PBKDF2HMACBackend, RSABackend
)
from cryptography.hazmat.bindings.openssl.binding import Binding
from cryptography.hazmat.primitives import hashes, interfaces
@@ -47,6 +47,7 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError",
@utils.register_interface(CipherBackend)
+@utils.register_interface(CMACBackend)
@utils.register_interface(DSABackend)
@utils.register_interface(HashBackend)
@utils.register_interface(HMACBackend)
@@ -473,6 +474,97 @@ class Backend(object):
y=self._bn_to_int(ctx.pub_key)
)
+ def decrypt_rsa(self, private_key, ciphertext, padding):
+ if isinstance(padding, PKCS1v15):
+ padding_enum = self._lib.RSA_PKCS1_PADDING
+ else:
+ raise UnsupportedAlgorithm(
+ "{0} is not supported by this backend".format(
+ padding.name
+ ),
+ _Reasons.UNSUPPORTED_PADDING
+ )
+
+ key_size_bytes = int(math.ceil(private_key.key_size / 8.0))
+ if key_size_bytes < len(ciphertext):
+ raise ValueError("Ciphertext too large for key size")
+
+ if self._lib.Cryptography_HAS_PKEY_CTX:
+ return self._decrypt_rsa_pkey_ctx(private_key, ciphertext,
+ padding_enum)
+ else:
+ return self._decrypt_rsa_098(private_key, ciphertext, padding_enum)
+
+ def _decrypt_rsa_pkey_ctx(self, private_key, ciphertext, padding_enum):
+ evp_pkey = self._rsa_private_key_to_evp_pkey(private_key)
+ pkey_ctx = self._lib.EVP_PKEY_CTX_new(
+ evp_pkey, self._ffi.NULL
+ )
+ assert pkey_ctx != self._ffi.NULL
+ pkey_ctx = self._ffi.gc(pkey_ctx, self._lib.EVP_PKEY_CTX_free)
+ res = self._lib.EVP_PKEY_decrypt_init(pkey_ctx)
+ assert res == 1
+ res = self._lib.EVP_PKEY_CTX_set_rsa_padding(
+ pkey_ctx, padding_enum)
+ assert res > 0
+ buf_size = self._lib.EVP_PKEY_size(evp_pkey)
+ assert buf_size > 0
+ outlen = self._ffi.new("size_t *", buf_size)
+ buf = self._ffi.new("char[]", buf_size)
+ res = self._lib.Cryptography_EVP_PKEY_decrypt(
+ pkey_ctx,
+ buf,
+ outlen,
+ ciphertext,
+ len(ciphertext)
+ )
+ if res <= 0:
+ errors = self._consume_errors()
+ assert errors
+ assert errors[0].lib == self._lib.ERR_LIB_RSA
+ assert (
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02
+ )
+ raise ValueError("Decryption failed")
+
+ return self._ffi.buffer(buf)[:outlen[0]]
+
+ def _decrypt_rsa_098(self, private_key, ciphertext, padding_enum):
+ rsa_cdata = self._rsa_cdata_from_private_key(private_key)
+ rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
+ key_size = self._lib.RSA_size(rsa_cdata)
+ assert key_size > 0
+ buf = self._ffi.new("unsigned char[]", key_size)
+ res = self._lib.RSA_private_decrypt(
+ len(ciphertext),
+ ciphertext,
+ buf,
+ rsa_cdata,
+ padding_enum
+ )
+ if res < 0:
+ errors = self._consume_errors()
+ assert errors
+ assert errors[0].lib == self._lib.ERR_LIB_RSA
+ assert (
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or
+ errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02
+ )
+ raise ValueError("Decryption failed")
+
+ return self._ffi.buffer(buf)[:res]
+
+ def cmac_algorithm_supported(self, algorithm):
+ return (
+ backend._lib.Cryptography_HAS_CMAC == 1
+ and backend.cipher_supported(algorithm, CBC(
+ b"\x00" * algorithm.block_size))
+ )
+
+ def create_cmac_ctx(self, algorithm):
+ return _CMACContext(self, algorithm)
+
class GetCipherByName(object):
def __init__(self, fmt):
@@ -1159,4 +1251,74 @@ class _RSAVerificationContext(object):
raise InvalidSignature
+@utils.register_interface(interfaces.CMACContext)
+class _CMACContext(object):
+ def __init__(self, backend, algorithm, ctx=None):
+ if not backend.cmac_algorithm_supported(algorithm):
+ raise UnsupportedAlgorithm("This backend does not support CMAC")
+
+ self._backend = backend
+ self._key = algorithm.key
+ self._algorithm = algorithm
+ self._output_length = algorithm.block_size // 8
+
+ if ctx is None:
+ registry = self._backend._cipher_registry
+ try:
+ adapter = registry[type(algorithm), CBC]
+ except KeyError:
+ raise UnsupportedAlgorithm(
+ "cipher {0} is not supported by this backend".format(
+ algorithm.name), _Reasons.UNSUPPORTED_CIPHER
+ )
+
+ evp_cipher = adapter(self._backend, algorithm, CBC)
+ if evp_cipher == self._backend._ffi.NULL:
+ raise UnsupportedAlgorithm(
+ "cipher {0} is not supported by this backend".format(
+ algorithm.name), _Reasons.UNSUPPORTED_CIPHER
+ )
+
+ ctx = self._backend._lib.CMAC_CTX_new()
+
+ assert ctx != self._backend._ffi.NULL
+ ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free)
+
+ self._backend._lib.CMAC_Init(
+ ctx, self._key, len(self._key),
+ evp_cipher, self._backend._ffi.NULL
+ )
+
+ self._ctx = ctx
+
+ def update(self, data):
+ res = self._backend._lib.CMAC_Update(self._ctx, data, len(data))
+ assert res == 1
+
+ def finalize(self):
+ buf = self._backend._ffi.new("unsigned char[]", self._output_length)
+ length = self._backend._ffi.new("size_t *", self._output_length)
+ res = self._backend._lib.CMAC_Final(
+ self._ctx, buf, length
+ )
+ assert res == 1
+
+ self._ctx = None
+
+ return self._backend._ffi.buffer(buf)[:]
+
+ def copy(self):
+ copied_ctx = self._backend._lib.CMAC_CTX_new()
+ copied_ctx = self._backend._ffi.gc(
+ copied_ctx, self._backend._lib.CMAC_CTX_free
+ )
+ res = self._backend._lib.CMAC_CTX_copy(
+ copied_ctx, self._ctx
+ )
+ assert res == 1
+ return _CMACContext(
+ self._backend, self._algorithm, ctx=copied_ctx
+ )
+
+
backend = Backend()
diff --git a/cryptography/hazmat/bindings/openssl/dsa.py b/cryptography/hazmat/bindings/openssl/dsa.py
index 40d3b8ee..51a02666 100644
--- a/cryptography/hazmat/bindings/openssl/dsa.py
+++ b/cryptography/hazmat/bindings/openssl/dsa.py
@@ -31,6 +31,10 @@ typedef struct dsa_st {
BIGNUM *pub_key;
...;
} DSA;
+typedef struct {
+ BIGNUM *r;
+ BIGNUM *s;
+} DSA_SIG;
"""
FUNCTIONS = """
@@ -39,6 +43,10 @@ DSA *DSA_generate_parameters(int, unsigned char *, int, int *, unsigned long *,
int DSA_generate_key(DSA *);
DSA *DSA_new(void);
void DSA_free(DSA *);
+DSA_SIG *DSA_SIG_new(void);
+void DSA_SIG_free(DSA_SIG *);
+int i2d_DSA_SIG(const DSA_SIG *, unsigned char **);
+DSA_SIG *d2i_DSA_SIG(DSA_SIG **, const unsigned char **, long);
"""
MACROS = """
diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py
index f51393aa..c08c880c 100644
--- a/cryptography/hazmat/bindings/openssl/err.py
+++ b/cryptography/hazmat/bindings/openssl/err.py
@@ -216,6 +216,8 @@ static const int PEM_R_UNSUPPORTED_ENCRYPTION;
static const int RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE;
static const int RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY;
+static const int RSA_R_BLOCK_TYPE_IS_NOT_01;
+static const int RSA_R_BLOCK_TYPE_IS_NOT_02;
"""
FUNCTIONS = """
diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py
index 094310f3..7ed42f9f 100644
--- a/cryptography/hazmat/bindings/openssl/ssl.py
+++ b/cryptography/hazmat/bindings/openssl/ssl.py
@@ -237,26 +237,28 @@ size_t SSL_get_peer_finished(const SSL *, void *, size_t);
"""
MACROS = """
-long SSL_set_mode(SSL *, long);
-long SSL_get_mode(SSL *);
+unsigned long SSL_set_mode(SSL *, unsigned long);
+unsigned long SSL_get_mode(SSL *);
-long SSL_set_options(SSL *, long);
-long SSL_get_options(SSL *);
+unsigned long SSL_set_options(SSL *, unsigned long);
+unsigned long SSL_get_options(SSL *);
int SSL_want_read(const SSL *);
int SSL_want_write(const SSL *);
long SSL_total_renegotiations(SSL *);
-long SSL_CTX_set_options(SSL_CTX *, long);
-long SSL_CTX_get_options(SSL_CTX *);
-long SSL_CTX_set_mode(SSL_CTX *, long);
-long SSL_CTX_get_mode(SSL_CTX *);
-long SSL_CTX_set_session_cache_mode(SSL_CTX *, long);
-long SSL_CTX_get_session_cache_mode(SSL_CTX *);
-long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *);
-long SSL_CTX_set_tmp_ecdh(SSL_CTX *, EC_KEY *);
-long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *);
+/* Defined as unsigned long because SSL_OP_ALL is greater than signed 32-bit
+ and Windows defines long as 32-bit. */
+unsigned long SSL_CTX_set_options(SSL_CTX *, unsigned long);
+unsigned long SSL_CTX_get_options(SSL_CTX *);
+unsigned long SSL_CTX_set_mode(SSL_CTX *, unsigned long);
+unsigned long SSL_CTX_get_mode(SSL_CTX *);
+unsigned long SSL_CTX_set_session_cache_mode(SSL_CTX *, unsigned long);
+unsigned long SSL_CTX_get_session_cache_mode(SSL_CTX *);
+unsigned long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *);
+unsigned long SSL_CTX_set_tmp_ecdh(SSL_CTX *, EC_KEY *);
+unsigned long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *);
/*- These aren't macros these functions are all const X on openssl > 1.0.x -*/
diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py
index 5b15350a..cffd4e98 100644
--- a/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -189,6 +189,15 @@ class RSAPrivateKey(object):
return backend.create_rsa_signature_ctx(self, padding, algorithm)
+ def decrypt(self, ciphertext, padding, backend):
+ if not isinstance(backend, RSABackend):
+ raise UnsupportedAlgorithm(
+ "Backend object does not implement RSABackend",
+ _Reasons.BACKEND_MISSING_INTERFACE
+ )
+
+ return backend.decrypt_rsa(self, ciphertext, padding)
+
@property
def key_size(self):
return utils.bit_length(self.modulus)
diff --git a/cryptography/hazmat/primitives/cmac.py b/cryptography/hazmat/primitives/cmac.py
new file mode 100644
index 00000000..7e7f65ab
--- /dev/null
+++ b/cryptography/hazmat/primitives/cmac.py
@@ -0,0 +1,75 @@
+# 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 (
+ AlreadyFinalized, InvalidSignature, UnsupportedAlgorithm, _Reasons
+)
+from cryptography.hazmat.backends.interfaces import CMACBackend
+from cryptography.hazmat.primitives import constant_time, interfaces
+
+
+@utils.register_interface(interfaces.CMACContext)
+class CMAC(object):
+ def __init__(self, algorithm, backend, ctx=None):
+ if not isinstance(backend, CMACBackend):
+ raise UnsupportedAlgorithm(
+ "Backend object does not implement CMACBackend",
+ _Reasons.BACKEND_MISSING_INTERFACE
+ )
+
+ if not isinstance(algorithm, interfaces.BlockCipherAlgorithm):
+ raise TypeError(
+ "Expected instance of interfaces.BlockCipherAlgorithm"
+ )
+ self._algorithm = algorithm
+
+ self._backend = backend
+ if ctx is None:
+ self._ctx = self._backend.create_cmac_ctx(self._algorithm)
+ else:
+ self._ctx = ctx
+
+ def update(self, data):
+ if self._ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+ if isinstance(data, six.text_type):
+ raise TypeError("Unicode-objects must be encoded before hashing")
+ self._ctx.update(data)
+
+ def finalize(self):
+ if self._ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+ digest = self._ctx.finalize()
+ self._ctx = None
+ return digest
+
+ def verify(self, signature):
+ if isinstance(signature, six.text_type):
+ raise TypeError("Unicode-objects must be encoded before verifying")
+ digest = self.finalize()
+ if not constant_time.bytes_eq(digest, signature):
+ raise InvalidSignature("Signature did not match digest.")
+
+ def copy(self):
+ if self._ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+ return CMAC(
+ self._algorithm,
+ backend=self._backend,
+ ctx=self._ctx.copy()
+ )
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst
index 394d060b..3ed4cfb6 100644
--- a/docs/hazmat/backends/interfaces.rst
+++ b/docs/hazmat/backends/interfaces.rst
@@ -9,7 +9,7 @@ Backend interfaces
Backend implementations may provide a number of interfaces to support operations
such as :doc:`/hazmat/primitives/symmetric-encryption`,
:doc:`/hazmat/primitives/cryptographic-hashes`, and
-:doc:`/hazmat/primitives/hmac`.
+:doc:`/hazmat/primitives/mac/hmac`.
A specific ``backend`` may provide one or more of these interfaces.
@@ -263,6 +263,18 @@ A specific ``backend`` may provide one or more of these interfaces.
:returns: ``True`` if the specified ``algorithm`` is supported by this
backend, otherwise ``False``.
+ .. method:: decrypt_rsa(private_key, ciphertext, padding)
+
+ :param private_key: An instance of an
+ :class:`~cryptography.hazmat.primitives.interfaces.RSAPrivateKey`
+ provider.
+
+ :param bytes ciphertext: The ciphertext to decrypt.
+
+ :param padding: An instance of an
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
.. class:: OpenSSLSerializationBackend
diff --git a/docs/hazmat/primitives/asymmetric/padding.rst b/docs/hazmat/primitives/asymmetric/padding.rst
index 89af7eaa..f33ca4e2 100644
--- a/docs/hazmat/primitives/asymmetric/padding.rst
+++ b/docs/hazmat/primitives/asymmetric/padding.rst
@@ -19,7 +19,8 @@ Padding
PSS (Probabilistic Signature Scheme) is a signature scheme defined in
:rfc:`3447`. It is more complex than PKCS1 but possesses a `security proof`_.
- This is the `recommended padding algorithm`_ for RSA signatures.
+ This is the `recommended padding algorithm`_ for RSA signatures. It cannot
+ be used with RSA encryption.
:param mgf: A mask generation function object. At this time the only
supported MGF is :class:`MGF1`.
@@ -37,7 +38,8 @@ Padding
.. versionadded:: 0.3
PKCS1 v1.5 (also known as simply PKCS1) is a simple padding scheme
- developed for use with RSA keys. It is defined in :rfc:`3447`.
+ developed for use with RSA keys. It is defined in :rfc:`3447`. This padding
+ can be used for signing and encryption.
Mask generation functions
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst
index c9de2831..c282d9ef 100644
--- a/docs/hazmat/primitives/asymmetric/rsa.rst
+++ b/docs/hazmat/primitives/asymmetric/rsa.rst
@@ -116,6 +116,36 @@ RSA
:raises ValueError: This is raised when the chosen hash algorithm is
too large for the key size.
+ .. method:: decrypt(ciphertext, padding, backend)
+
+ .. versionadded:: 0.4
+
+ Decrypt data that was encrypted with the public key.
+
+ :param bytes ciphertext: The ciphertext to decrypt.
+
+ :param padding: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend`
+ provider.
+
+ :return bytes: Decrypted data.
+
+ :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if
+ the provided ``backend`` does not implement
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend` or if
+ the backend does not support the chosen hash or padding algorithm.
+
+ :raises TypeError: This is raised when the padding is not an
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :raises ValueError: This is raised when decryption fails or the chosen
+ hash algorithm is too large for the key size.
+
.. class:: RSAPublicKey(public_exponent, modulus)
@@ -221,7 +251,7 @@ If you are trying to load RSA private keys yourself you may find that not all
parameters required by ``RSAPrivateKey`` are available. In particular the
`Chinese Remainder Theorem`_ (CRT) values ``dmp1``, ``dmq1``, ``iqmp`` may be
missing or present in a different form. For example `OpenPGP`_ does not include
-the ``iqmp``, ``dmp1`` or ``dmq1`` parameters.
+the ``iqmp``, ``dmp1`` or ``dmq1`` parameters.
The following functions are provided for users who want to work with keys like
this without having to do the math themselves.
@@ -241,7 +271,7 @@ this without having to do the math themselves.
``p``.
.. function:: rsa_crt_dmq1(private_exponent, q)
-
+
.. versionadded:: 0.4
Generates the ``dmq1`` parameter from the RSA private exponent and prime
diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst
index 90deec8b..a9ab38a0 100644
--- a/docs/hazmat/primitives/index.rst
+++ b/docs/hazmat/primitives/index.rst
@@ -7,7 +7,7 @@ Primitives
:maxdepth: 1
cryptographic-hashes
- hmac
+ mac/index
symmetric-encryption
padding
key-derivation-functions
diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst
index 95fd6f9f..3b837a0d 100644
--- a/docs/hazmat/primitives/interfaces.rst
+++ b/docs/hazmat/primitives/interfaces.rst
@@ -133,6 +133,24 @@ Asymmetric interfaces
:returns:
:class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext`
+ .. method:: decrypt(ciphertext, padding, backend)
+
+ .. versionadded:: 0.4
+
+ Decrypt data that was encrypted via the public key.
+
+ :param bytes ciphertext: The ciphertext to decrypt.
+
+ :param padding: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend`
+ provider.
+
+ :return bytes: Decrypted data.
+
.. method:: public_key()
:return: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey`
diff --git a/docs/hazmat/primitives/mac/cmac.rst b/docs/hazmat/primitives/mac/cmac.rst
new file mode 100644
index 00000000..8b88a3ce
--- /dev/null
+++ b/docs/hazmat/primitives/mac/cmac.rst
@@ -0,0 +1,105 @@
+.. hazmat::
+
+Cipher-based message authentication code
+========================================
+
+.. currentmodule:: cryptography.hazmat.primitives.cmac
+
+.. testsetup::
+
+ import binascii
+ key = binascii.unhexlify(b"0" * 32)
+
+`Cipher-based message authentication codes`_ (or CMACs) are a tool for calculating
+message authentication codes using a block cipher coupled with a
+secret key. You can use an CMAC to verify both the integrity and authenticity
+of a message.
+
+A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`.
+
+.. class:: CMAC(algorithm, backend)
+
+ CMAC objects take a
+ :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm` provider.
+
+ .. code-block:: pycon
+
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> from cryptography.hazmat.primitives import cmac
+ >>> from cryptography.hazmat.primitives.ciphers import algorithms
+ >>> c = cmac.CMAC(algorithms.AES(key), backend=default_backend())
+ >>> c.update(b"message to authenticate")
+ >>> c.finalize()
+ 'CT\x1d\xc8\x0e\x15\xbe4e\xdb\xb6\x84\xca\xd9Xk'
+
+ If the backend doesn't support the requested ``algorithm`` an
+ :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be
+ raised.
+
+ If the `algorithm`` isn't a
+ :class:`~cryptography.primitives.interfaces.BlockCipherAlgorithm` provider,
+ ``TypeError`` will be raised.
+
+ To check that a given signature is correct use the :meth:`verify` method.
+ You will receive an exception if the signature is wrong:
+
+ .. code-block:: pycon
+
+ >>> c.verify(b"an incorrect signature")
+ Traceback (most recent call last):
+ ...
+ cryptography.exceptions.InvalidSignature: Signature did not match digest.
+
+ :param algorithm: An
+ :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm`
+ provider.
+ :param backend: An
+ :class:`~cryptography.hazmat.backends.interfaces.CMACBackend`
+ provider.
+ :raises TypeError: This is raised if the provided ``algorithm`` is not an instance of
+ :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm`
+ :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the
+ provided ``backend`` does not implement
+ :class:`~cryptography.hazmat.backends.interfaces.CMACBackend`
+
+ .. method:: update(data)
+
+ :param bytes data: The bytes to hash and authenticate.
+ :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
+
+ .. method:: copy()
+
+ Copy this :class:`CMAC` instance, usually so that we may call
+ :meth:`finalize` to get an intermediate value while we continue
+ to call :meth:`update` on the original instance.
+
+ :return: A new instance of :class:`CMAC` that can be updated
+ and finalized independently of the original instance.
+ :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
+
+ .. method:: verify(signature)
+
+ Finalize the current context and securely compare the MAC to
+ ``signature``.
+
+ :param bytes signature: The bytes to compare the current CMAC
+ against.
+ :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
+ :raises cryptography.exceptions.InvalidSignature: If signature does not
+ match digest
+
+ .. method:: finalize()
+
+ Finalize the current context and return the message authentication code
+ as bytes.
+
+ After ``finalize`` has been called this object can no longer be used
+ and :meth:`update`, :meth:`copy`, :meth:`verify` and :meth:`finalize`
+ will raise an :class:`~cryptography.exceptions.AlreadyFinalized`
+ exception.
+
+ :return bytes: The message authentication code as bytes.
+ :raises cryptography.exceptions.AlreadyFinalized:
+
+
+.. _`Cipher-based message authentication codes`: https://en.wikipedia.org/wiki/CMAC
diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/mac/hmac.rst
index 11b10735..11b10735 100644
--- a/docs/hazmat/primitives/hmac.rst
+++ b/docs/hazmat/primitives/mac/hmac.rst
diff --git a/docs/hazmat/primitives/mac/index.rst b/docs/hazmat/primitives/mac/index.rst
new file mode 100644
index 00000000..59fb8da2
--- /dev/null
+++ b/docs/hazmat/primitives/mac/index.rst
@@ -0,0 +1,10 @@
+.. hazmat::
+
+Message Authentication Codes
+============================
+
+.. toctree::
+ :maxdepth: 1
+
+ cmac
+ hmac
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index 1a4df222..c2692ae2 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -21,7 +21,7 @@ message but an attacker can create bogus messages and force the application to
decrypt them.
For this reason it is *strongly* recommended to combine encryption with a
-message authentication code, such as :doc:`HMAC </hazmat/primitives/hmac>`, in
+message authentication code, such as :doc:`HMAC </hazmat/primitives/mac/hmac>`, in
an "encrypt-then-MAC" formulation as `described by Colin Percival`_.
.. class:: Cipher(algorithm, mode, backend)
@@ -289,7 +289,7 @@ Modes
block cipher mode that simultaneously encrypts the message as well as
authenticating it. Additional unencrypted data may also be authenticated.
Additional means of verifying integrity such as
- :doc:`HMAC </hazmat/primitives/hmac>` are not necessary.
+ :doc:`HMAC </hazmat/primitives/mac/hmac>` are not necessary.
**This mode does not require padding.**
diff --git a/docs/installation.rst b/docs/installation.rst
index a0dd5f22..3ebbecfd 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -10,16 +10,27 @@ You can install ``cryptography`` with ``pip``:
Supported platforms
-------------------
-Currently we test ``cryptography`` on Python 2.6, 2.7, 3.2, 3.3 and PyPy on
-these operating systems.
+Currently we test ``cryptography`` on Python 2.6, 2.7, 3.2, 3.3, 3.4 and PyPy
+on these operating systems.
-* x86-64 CentOS 6.4 and CentOS 5
+* x86-64 CentOS 6.4 and CentOS 5.x
* x86-64 FreeBSD 9.2 and FreeBSD 10
* OS X 10.9 Mavericks, 10.8 Mountain Lion, and 10.7 Lion
* x86-64 Ubuntu 12.04 LTS
* 32-bit Python on 64-bit Windows Server 2008
* 64-bit Python on 64-bit Windows Server 2012
+We test compiling with ``clang`` as well as ``gcc`` and use the following
+OpenSSL releases:
+
+* ``OpenSSL 0.9.8e-fips-rhel5`` (``RHEL/CentOS 5``)
+* ``OpenSSL 0.9.8y``
+* ``OpenSSL 1.0.0-fips`` (``RHEL/CentOS 6.4``)
+* ``OpenSSL 1.0.1``
+* ``OpenSSL 1.0.1e-freebsd``
+* ``OpenSSL 1.0.1g``
+* ``OpenSSL 1.0.2 beta``
+
On Windows
----------
diff --git a/pytest.ini b/pytest.ini
index b590d0be..cb6a80a8 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -2,6 +2,7 @@
addopts = -r s
markers =
cipher: this test requires a backend providing CipherBackend
+ cmac: this test requires a backend providing CMACBackend
dsa: this test requires a backend providing DSABackend
hash: this test requires a backend providing HashBackend
hmac: this test requires a backend providing HMACBackend
diff --git a/tests/conftest.py b/tests/conftest.py
index 1ee2a993..d55e6cf6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -17,10 +17,9 @@ import pytest
from cryptography.hazmat.backends import _available_backends
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
- RSABackend
+ CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
+ PBKDF2HMACBackend, RSABackend
)
-
from .utils import check_backend_support, check_for_iface, select_backends
@@ -36,6 +35,7 @@ def pytest_generate_tests(metafunc):
def pytest_runtest_setup(item):
check_for_iface("hmac", HMACBackend, item)
check_for_iface("cipher", CipherBackend, item)
+ check_for_iface("cmac", CMACBackend, item)
check_for_iface("hash", HashBackend, item)
check_for_iface("pbkdf2hmac", PBKDF2HMACBackend, item)
check_for_iface("dsa", DSABackend, item)
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 43d28c33..c589506f 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -143,8 +143,8 @@ class TestOpenSSL(object):
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
backend.derive_pbkdf2_hmac(hashes.SHA256(), 10, b"", 1000, b"")
- # This test is not in the next class because to check if it's really
- # default we don't want to run the setup_method before it
+ # This test is not in the TestOpenSSLRandomEngine class because to check
+ # if it's really default we don't want to run the setup_method before it
def test_osrandom_engine_is_default(self):
e = backend._lib.ENGINE_get_default_RAND()
name = backend._lib.ENGINE_get_name(e)
diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py
index acab22b1..1dbd23b4 100644
--- a/tests/hazmat/bindings/test_openssl.py
+++ b/tests/hazmat/bindings/test_openssl.py
@@ -103,3 +103,37 @@ class TestOpenSSL(object):
b = Binding()
res = b.lib.Cryptography_add_osrandom_engine()
assert res == 2
+
+ def test_ssl_ctx_options(self):
+ # Test that we're properly handling 32-bit unsigned on all platforms.
+ b = Binding()
+ assert b.lib.SSL_OP_ALL > 0
+ ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method())
+ ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free)
+ resp = b.lib.SSL_CTX_set_options(ctx, b.lib.SSL_OP_ALL)
+ assert resp == b.lib.SSL_OP_ALL
+ assert b.lib.SSL_OP_ALL == b.lib.SSL_CTX_get_options(ctx)
+
+ def test_ssl_options(self):
+ # Test that we're properly handling 32-bit unsigned on all platforms.
+ b = Binding()
+ assert b.lib.SSL_OP_ALL > 0
+ ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method())
+ ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free)
+ ssl = b.lib.SSL_new(ctx)
+ ssl = b.ffi.gc(ssl, b.lib.SSL_free)
+ resp = b.lib.SSL_set_options(ssl, b.lib.SSL_OP_ALL)
+ assert resp == b.lib.SSL_OP_ALL
+ assert b.lib.SSL_OP_ALL == b.lib.SSL_get_options(ssl)
+
+ def test_ssl_mode(self):
+ # Test that we're properly handling 32-bit unsigned on all platforms.
+ b = Binding()
+ assert b.lib.SSL_OP_ALL > 0
+ ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method())
+ ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free)
+ ssl = b.lib.SSL_new(ctx)
+ ssl = b.ffi.gc(ssl, b.lib.SSL_free)
+ resp = b.lib.SSL_set_mode(ssl, b.lib.SSL_OP_ALL)
+ assert resp == b.lib.SSL_OP_ALL
+ assert b.lib.SSL_OP_ALL == b.lib.SSL_get_mode(ssl)
diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py
new file mode 100644
index 00000000..7ec4af68
--- /dev/null
+++ b/tests/hazmat/primitives/test_cmac.py
@@ -0,0 +1,217 @@
+# 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 binascii
+
+import pretend
+
+import pytest
+
+import six
+
+from cryptography import utils
+from cryptography.exceptions import (
+ AlreadyFinalized, InvalidSignature, _Reasons
+)
+from cryptography.hazmat.backends.interfaces import CMACBackend
+from cryptography.hazmat.primitives.ciphers.algorithms import (
+ AES, ARC4, TripleDES
+)
+from cryptography.hazmat.primitives.cmac import CMAC
+
+from tests.utils import (
+ load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm
+)
+
+vectors_aes128 = load_vectors_from_file(
+ "CMAC/nist-800-38b-aes128.txt", load_nist_vectors)
+
+vectors_aes192 = load_vectors_from_file(
+ "CMAC/nist-800-38b-aes192.txt", load_nist_vectors)
+
+vectors_aes256 = load_vectors_from_file(
+ "CMAC/nist-800-38b-aes256.txt", load_nist_vectors)
+
+vectors_aes = vectors_aes128 + vectors_aes192 + vectors_aes256
+
+vectors_3des = load_vectors_from_file(
+ "CMAC/nist-800-38b-3des.txt", load_nist_vectors)
+
+fake_key = b"\x00" * 16
+
+
+@pytest.mark.cmac
+class TestCMAC(object):
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_aes)
+ def test_aes_generate(self, backend, params):
+ key = params["key"]
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(AES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert binascii.hexlify(cmac.finalize()) == output
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_aes)
+ def test_aes_verify(self, backend, params):
+ key = params["key"]
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(AES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert cmac.verify(binascii.unhexlify(output)) is None
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ TripleDES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_3des)
+ def test_3des_generate(self, backend, params):
+ key1 = params["key1"]
+ key2 = params["key2"]
+ key3 = params["key3"]
+
+ key = key1 + key2 + key3
+
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(TripleDES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert binascii.hexlify(cmac.finalize()) == output
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ TripleDES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ @pytest.mark.parametrize("params", vectors_3des)
+ def test_3des_verify(self, backend, params):
+ key1 = params["key1"]
+ key2 = params["key2"]
+ key3 = params["key3"]
+
+ key = key1 + key2 + key3
+
+ message = params["message"]
+ output = params["output"]
+
+ cmac = CMAC(TripleDES(binascii.unhexlify(key)), backend)
+ cmac.update(binascii.unhexlify(message))
+ assert cmac.verify(binascii.unhexlify(output)) is None
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_invalid_verify(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+ cmac.update(b"6bc1bee22e409f96e93d7e117393172a")
+
+ with pytest.raises(InvalidSignature):
+ cmac.verify(b"foobar")
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cipher_supported(
+ ARC4(fake_key), None),
+ skip_message="Does not support CMAC."
+ )
+ def test_invalid_algorithm(self, backend):
+ key = b"0102030405"
+ with pytest.raises(TypeError):
+ CMAC(ARC4(key), backend)
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_raises_after_finalize(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+ cmac.finalize()
+
+ with pytest.raises(AlreadyFinalized):
+ cmac.update(b"foo")
+
+ with pytest.raises(AlreadyFinalized):
+ cmac.copy()
+
+ with pytest.raises(AlreadyFinalized):
+ cmac.finalize()
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_verify_reject_unicode(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+
+ with pytest.raises(TypeError):
+ cmac.update(six.u(''))
+
+ with pytest.raises(TypeError):
+ cmac.verify(six.u(''))
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.cmac_algorithm_supported(
+ AES(fake_key)),
+ skip_message="Does not support CMAC."
+ )
+ def test_copy_with_backend(self, backend):
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend)
+ cmac.update(b"6bc1bee22e409f96e93d7e117393172a")
+ copy_cmac = cmac.copy()
+ assert cmac.finalize() == copy_cmac.finalize()
+
+
+def test_copy():
+ @utils.register_interface(CMACBackend)
+ class PretendBackend(object):
+ pass
+
+ pretend_backend = PretendBackend()
+ copied_ctx = pretend.stub()
+ pretend_ctx = pretend.stub(copy=lambda: copied_ctx)
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ cmac = CMAC(AES(key), backend=pretend_backend, ctx=pretend_ctx)
+
+ assert cmac._backend is pretend_backend
+ assert cmac.copy()._backend is pretend_backend
+
+
+def test_invalid_backend():
+ key = b"2b7e151628aed2a6abf7158809cf4f3c"
+ pretend_backend = object()
+
+ with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE):
+ CMAC(AES(key), pretend_backend)
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index 84d0f805..a5266d57 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -1225,3 +1225,98 @@ class TestMGF1(object):
mgf = padding.MGF1(algorithm, padding.MGF1.MAX_LENGTH)
assert mgf._algorithm == algorithm
assert mgf._salt_length == padding.MGF1.MAX_LENGTH
+
+
+@pytest.mark.rsa
+class TestRSADecryption(object):
+ @pytest.mark.parametrize(
+ "vector",
+ _flatten_pkcs1_examples(load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"),
+ load_pkcs1_vectors
+ ))
+ )
+ def test_decrypt_pkcs1v15_vectors(self, vector, backend):
+ private, public, example = vector
+ skey = rsa.RSAPrivateKey(
+ p=private["p"],
+ q=private["q"],
+ private_exponent=private["private_exponent"],
+ dmp1=private["dmp1"],
+ dmq1=private["dmq1"],
+ iqmp=private["iqmp"],
+ public_exponent=private["public_exponent"],
+ modulus=private["modulus"]
+ )
+ ciphertext = binascii.unhexlify(example["encryption"])
+ assert len(ciphertext) == math.ceil(skey.key_size / 8.0)
+ message = skey.decrypt(
+ ciphertext,
+ padding.PKCS1v15(),
+ backend
+ )
+ assert message == binascii.unhexlify(example["message"])
+
+ def test_unsupported_padding(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING):
+ private_key.decrypt(b"somedata", DummyPadding(), backend)
+
+ def test_decrypt_invalid_decrypt(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with pytest.raises(ValueError):
+ private_key.decrypt(
+ b"\x00" * 64,
+ padding.PKCS1v15(),
+ backend
+ )
+
+ def test_decrypt_ciphertext_too_large(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ with pytest.raises(ValueError):
+ private_key.decrypt(
+ b"\x00" * 65,
+ padding.PKCS1v15(),
+ backend
+ )
+
+ def test_decrypt_ciphertext_too_small(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ ct = binascii.unhexlify(
+ b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1"
+ b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0"
+ )
+ with pytest.raises(ValueError):
+ private_key.decrypt(
+ ct,
+ padding.PKCS1v15(),
+ backend
+ )
+
+ def test_rsa_decrypt_invalid_backend(self, backend):
+ pretend_backend = object()
+ private_key = rsa.RSAPrivateKey.generate(65537, 2048, backend)
+
+ with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE):
+ private_key.decrypt(
+ b"irrelevant",
+ padding.PKCS1v15(),
+ pretend_backend
+ )