aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/exceptions.py4
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py120
-rw-r--r--cryptography/hazmat/primitives/asymmetric/padding.py22
-rw-r--r--cryptography/hazmat/primitives/asymmetric/rsa.py3
-rw-r--r--docs/exceptions.rst6
-rw-r--r--docs/hazmat/primitives/asymmetric/index.rst10
-rw-r--r--docs/hazmat/primitives/asymmetric/padding.rst20
-rw-r--r--docs/hazmat/primitives/asymmetric/rsa.rst (renamed from docs/hazmat/primitives/rsa.rst)30
-rw-r--r--docs/hazmat/primitives/index.rst2
-rw-r--r--tests/hazmat/primitives/test_rsa.py60
10 files changed, 275 insertions, 2 deletions
diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py
index f9849e2f..1144cb94 100644
--- a/cryptography/exceptions.py
+++ b/cryptography/exceptions.py
@@ -46,3 +46,7 @@ class InvalidKey(Exception):
class InvalidToken(Exception):
pass
+
+
+class UnsupportedAsymmetricPadding(Exception):
+ pass
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index de6f841c..126d8f0c 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -17,7 +17,8 @@ import itertools
from cryptography import utils
from cryptography.exceptions import (
- UnsupportedAlgorithm, InvalidTag, InternalError
+ UnsupportedAlgorithm, InvalidTag, InternalError, AlreadyFinalized,
+ UnsupportedAsymmetricPadding
)
from cryptography.hazmat.backends.interfaces import (
CipherBackend, HashBackend, HMACBackend, PBKDF2HMACBackend, RSABackend
@@ -321,6 +322,30 @@ class Backend(object):
modulus=self._bn_to_int(ctx.n),
)
+ def _rsa_cdata_from_private_key(self, private_key):
+ ctx = self._lib.RSA_new()
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.RSA_free)
+ ctx.p = self._int_to_bn(private_key.p)
+ ctx.q = self._int_to_bn(private_key.q)
+ ctx.d = self._int_to_bn(private_key.d)
+ ctx.e = self._int_to_bn(private_key.e)
+ ctx.n = self._int_to_bn(private_key.n)
+ ctx.dmp1 = self._int_to_bn(private_key.dmp1)
+ ctx.dmq1 = self._int_to_bn(private_key.dmq1)
+ ctx.iqmp = self._int_to_bn(private_key.iqmp)
+ return ctx
+
+ def _rsa_cdata_from_public_key(self, public_key):
+ ctx = self._lib.RSA_new()
+ ctx = self._ffi.gc(ctx, self._lib.RSA_free)
+ ctx.e = self._int_to_bn(public_key.e)
+ ctx.n = self._int_to_bn(public_key.n)
+ return ctx
+
+ def create_rsa_signature_ctx(self, private_key, padding, algorithm):
+ return _RSASignatureContext(self, private_key, padding, algorithm)
+
class GetCipherByName(object):
def __init__(self, fmt):
@@ -572,4 +597,97 @@ class _HMACContext(object):
return self._backend._ffi.buffer(buf)[:]
+@utils.register_interface(interfaces.AsymmetricSignatureContext)
+class _RSASignatureContext(object):
+ def __init__(self, backend, private_key, padding, algorithm):
+ self._backend = backend
+ self._private_key = private_key
+ if not isinstance(padding, interfaces.AsymmetricPadding):
+ raise TypeError(
+ "Expected interface of interfaces.AsymmetricPadding")
+
+ if padding.name == "EMSA-PKCS1-v1_5":
+ if self._backend._lib.Cryptography_HAS_PKEY_CTX:
+ self._finalize_method = self._finalize_pkey_ctx
+ self._padding_enum = self._backend._lib.RSA_PKCS1_PADDING
+ else:
+ self._finalize_method = self._finalize_pkcs1
+ else:
+ raise UnsupportedAsymmetricPadding
+
+ self._padding = padding
+ self._algorithm = algorithm
+ self._hash_ctx = _HashContext(backend, self._algorithm)
+
+ def update(self, data):
+ if self._hash_ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+
+ self._hash_ctx.update(data)
+
+ def finalize(self):
+ if self._hash_ctx is None:
+ raise AlreadyFinalized("Context was already finalized")
+ evp_pkey = self._backend._lib.EVP_PKEY_new()
+ assert evp_pkey != self._backend._ffi.NULL
+ evp_pkey = backend._ffi.gc(evp_pkey, backend._lib.EVP_PKEY_free)
+ rsa_cdata = backend._rsa_cdata_from_private_key(self._private_key)
+ res = self._backend._lib.RSA_blinding_on(
+ rsa_cdata, self._backend._ffi.NULL)
+ assert res == 1
+ res = self._backend._lib.EVP_PKEY_set1_RSA(evp_pkey, rsa_cdata)
+ assert res == 1
+ evp_md = self._backend._lib.EVP_get_digestbyname(
+ self._algorithm.name.encode("ascii"))
+ assert evp_md != self._backend._ffi.NULL
+ pkey_size = self._backend._lib.EVP_PKEY_size(evp_pkey)
+ assert pkey_size > 0
+
+ return self._finalize_method(evp_pkey, pkey_size, rsa_cdata, evp_md)
+
+ def _finalize_pkey_ctx(self, evp_pkey, pkey_size, rsa_cdata, evp_md):
+ pkey_ctx = self._backend._lib.EVP_PKEY_CTX_new(
+ evp_pkey, self._backend._ffi.NULL
+ )
+ assert pkey_ctx != self._backend._ffi.NULL
+ res = self._backend._lib.EVP_PKEY_sign_init(pkey_ctx)
+ assert res == 1
+ res = self._backend._lib.EVP_PKEY_CTX_set_signature_md(
+ pkey_ctx, evp_md)
+ assert res > 0
+
+ res = self._backend._lib.EVP_PKEY_CTX_set_rsa_padding(
+ pkey_ctx, self._padding_enum)
+ assert res > 0
+ data_to_sign = self._hash_ctx.finalize()
+ self._hash_ctx = None
+ buflen = self._backend._ffi.new("size_t *")
+ res = self._backend._lib.EVP_PKEY_sign(
+ pkey_ctx,
+ self._backend._ffi.NULL,
+ buflen,
+ data_to_sign,
+ len(data_to_sign)
+ )
+ assert res == 1
+ buf = self._backend._ffi.new("unsigned char[]", buflen[0])
+ res = self._backend._lib.EVP_PKEY_sign(
+ pkey_ctx, buf, buflen, data_to_sign, len(data_to_sign))
+ assert res == 1
+ return self._backend._ffi.buffer(buf)[:]
+
+ def _finalize_pkcs1(self, evp_pkey, pkey_size, rsa_cdata, evp_md):
+ sig_buf = self._backend._ffi.new("char[]", pkey_size)
+ sig_len = self._backend._ffi.new("unsigned int *")
+ res = self._backend._lib.EVP_SignFinal(
+ self._hash_ctx._ctx,
+ sig_buf,
+ sig_len,
+ evp_pkey
+ )
+ self._hash_ctx = None
+ assert res == 1
+ return self._backend._ffi.buffer(sig_buf)[:sig_len[0]]
+
+
backend = Backend()
diff --git a/cryptography/hazmat/primitives/asymmetric/padding.py b/cryptography/hazmat/primitives/asymmetric/padding.py
new file mode 100644
index 00000000..ca00e94b
--- /dev/null
+++ b/cryptography/hazmat/primitives/asymmetric/padding.py
@@ -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 __future__ import absolute_import, division, print_function
+
+from cryptography import utils
+from cryptography.hazmat.primitives import interfaces
+
+
+@utils.register_interface(interfaces.AsymmetricPadding)
+class PKCS1(object):
+ name = "EMSA-PKCS1-v1_5"
diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py
index 01218592..a63d4308 100644
--- a/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -135,6 +135,9 @@ class RSAPrivateKey(object):
def generate(self, public_exponent, key_size, backend):
return backend.generate_rsa_private_key(public_exponent, key_size)
+ def signer(self, padding, algorithm, backend):
+ return backend.create_rsa_signature_ctx(self, padding, algorithm)
+
@property
def key_size(self):
return _bit_length(self.modulus)
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
index 8ca9df29..38bd0e47 100644
--- a/docs/exceptions.rst
+++ b/docs/exceptions.rst
@@ -42,3 +42,9 @@ Exceptions
This is raised when the verify method of a one time password function's
computed token does not match the expected token.
+
+
+.. class:: UnsupportedAsymmetricPadding
+
+ This is raised when the chosen asymmetric padding is not supported by the
+ backend.
diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst
new file mode 100644
index 00000000..10319fad
--- /dev/null
+++ b/docs/hazmat/primitives/asymmetric/index.rst
@@ -0,0 +1,10 @@
+.. hazmat::
+
+Asymmetric Algorithms
+=====================
+
+.. toctree::
+ :maxdepth: 1
+
+ rsa
+ padding
diff --git a/docs/hazmat/primitives/asymmetric/padding.rst b/docs/hazmat/primitives/asymmetric/padding.rst
new file mode 100644
index 00000000..d3f713ae
--- /dev/null
+++ b/docs/hazmat/primitives/asymmetric/padding.rst
@@ -0,0 +1,20 @@
+.. hazmat::
+
+Padding
+=======
+
+.. currentmodule:: cryptography.hazmat.primitives.asymmetric.padding
+
+.. warning::
+ `Padding is critical`_ when signing or encrypting data using RSA. Without
+ correct padding signatures can be forged, messages decrypted, and private
+ keys compromised.
+
+.. class:: PKCS1()
+
+ .. versionadded:: 0.3
+
+ PKCS1 (also known as PKCS1 v1.5) is a simple padding scheme developed for
+ use with RSA keys. It is also defined in :rfc:`3447`.
+
+.. _`Padding is critical`: http://rdist.root.org/2009/10/06/why-rsa-encryption-padding-is-critical/
diff --git a/docs/hazmat/primitives/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst
index 4e1f8e49..82cf3528 100644
--- a/docs/hazmat/primitives/rsa.rst
+++ b/docs/hazmat/primitives/asymmetric/rsa.rst
@@ -50,6 +50,36 @@ RSA
provider.
:return: A new instance of ``RSAPrivateKey``.
+ .. method:: signer(padding, algorithm, backend)
+
+ .. versionadded:: 0.3
+
+ :param padding: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :param algorithm: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+ provider.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend`
+ provider.
+
+ :returns:
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext`
+
+ .. doctest::
+
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> from cryptography.hazmat.primitives import hashes
+ >>> from cryptography.hazmat.primitives.asymmetric import rsa, padding
+ >>> private_key = rsa.RSAPrivateKey.generate(65537, 2048, default_backend())
+ >>> signer = private_key.signer(padding.PKCS1(), hashes.SHA256(), default_backend())
+ >>> signer.update(b"this is some data I'd like")
+ >>> signer.update(b" to sign")
+ >>> signature = signer.finalize()
+
.. class:: RSAPublicKey(public_exponent, modulus)
diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst
index 5199d493..90deec8b 100644
--- a/docs/hazmat/primitives/index.rst
+++ b/docs/hazmat/primitives/index.rst
@@ -11,7 +11,7 @@ Primitives
symmetric-encryption
padding
key-derivation-functions
- rsa
+ asymmetric/index
constant-time
interfaces
twofactor
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index df3a70f5..b2ea9bad 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -14,16 +14,25 @@
from __future__ import absolute_import, division, print_function
+import binascii
import itertools
import os
import pytest
+from cryptography import exceptions, utils
+from cryptography.hazmat.primitives import hashes, interfaces
from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.hazmat.primitives.asymmetric import padding
from ...utils import load_pkcs1_vectors, load_vectors_from_file
+@utils.register_interface(interfaces.AsymmetricPadding)
+class FakePadding(object):
+ name = "UNSUPPORTED-PADDING"
+
+
def _modinv(e, m):
"""
Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1
@@ -55,6 +64,17 @@ def _check_rsa_private_key(skey):
assert skey.key_size == pkey.key_size
+def _flatten_pkcs1_examples(vectors):
+ flattened_vectors = []
+ for vector in vectors:
+ examples = vector[0].pop("examples")
+ for example in examples:
+ merged_vector = (vector[0], vector[1], example)
+ flattened_vectors.append(merged_vector)
+
+ return flattened_vectors
+
+
def test_modular_inverse():
p = int(
"d1f9f6c09fd3d38987f7970247b85a6da84907753d42ec52bc23b745093f4fff5cff3"
@@ -363,3 +383,43 @@ class TestRSA(object):
# Test a public_exponent that is not odd.
with pytest.raises(ValueError):
rsa.RSAPublicKey(public_exponent=6, modulus=15)
+
+
+@pytest.mark.rsa
+class TestRSASignature(object):
+ @pytest.mark.parametrize(
+ "pkcs1_example",
+ _flatten_pkcs1_examples(load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "RSA", "pkcs1v15sign-vectors.txt"),
+ load_pkcs1_vectors
+ ))
+ )
+ def test_pkcs1v15_signing(self, pkcs1_example, backend):
+ private, public, example = pkcs1_example
+ private_key = rsa.RSAPrivateKey(**private)
+ public_key = rsa.RSAPublicKey(**public)
+ signer = private_key.signer(padding.PKCS1(), hashes.SHA1(), backend)
+ signer.update(binascii.unhexlify(example["message"]))
+ signature = signer.finalize()
+ assert binascii.hexlify(signature) == example["signature"]
+
+ def test_use_after_finalize(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(65537, 512, backend)
+ signer = private_key.signer(padding.PKCS1(), hashes.SHA1(), backend)
+ signer.update(b"sign me")
+ signer.finalize()
+ with pytest.raises(exceptions.AlreadyFinalized):
+ signer.finalize()
+ with pytest.raises(exceptions.AlreadyFinalized):
+ signer.update(b"more data")
+
+ def test_unsupported_padding(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(65537, 512, backend)
+ with pytest.raises(exceptions.UnsupportedAsymmetricPadding):
+ private_key.signer(FakePadding(), hashes.SHA1(), backend)
+
+ def test_padding_incorrect_type(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(65537, 512, backend)
+ with pytest.raises(TypeError):
+ private_key.signer("notpadding", hashes.SHA1(), backend)