Merge pull request #673 from reaperhulk/rsa-pkcs1-signature-only

Add RSA PKCS1 signing (and structure for PSS + verification)
diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py
index f9849e2..b496259 100644
--- a/cryptography/exceptions.py
+++ b/cryptography/exceptions.py
@@ -46,3 +46,7 @@
 
 class InvalidToken(Exception):
     pass
+
+
+class UnsupportedPadding(Exception):
+    pass
diff --git a/cryptography/hazmat/backends/multibackend.py b/cryptography/hazmat/backends/multibackend.py
index 4de0202..f001719 100644
--- a/cryptography/hazmat/backends/multibackend.py
+++ b/cryptography/hazmat/backends/multibackend.py
@@ -24,6 +24,7 @@
 @utils.register_interface(HashBackend)
 @utils.register_interface(HMACBackend)
 @utils.register_interface(PBKDF2HMACBackend)
+@utils.register_interface(RSABackend)
 class MultiBackend(object):
     name = "multibackend"
 
@@ -106,3 +107,8 @@
         for b in self._filtered_backends(RSABackend):
             return b.generate_rsa_private_key(public_exponent, key_size)
         raise UnsupportedAlgorithm
+
+    def create_rsa_signature_ctx(self, private_key, padding, algorithm):
+        for b in self._filtered_backends(RSABackend):
+            return b.create_rsa_signature_ctx(private_key, padding, algorithm)
+        raise UnsupportedAlgorithm
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index de6f841..00fdc26 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -17,7 +17,8 @@
 
 from cryptography import utils
 from cryptography.exceptions import (
-    UnsupportedAlgorithm, InvalidTag, InternalError
+    UnsupportedAlgorithm, InvalidTag, InternalError, AlreadyFinalized,
+    UnsupportedPadding
 )
 from cryptography.hazmat.backends.interfaces import (
     CipherBackend, HashBackend, HMACBackend, PBKDF2HMACBackend, RSABackend
@@ -321,6 +322,23 @@
             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 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 +590,100 @@
         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 provider 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 UnsupportedPadding(
+                "{0} is not supported by this backend".format(padding.name)
+            )
+
+        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 has already been finalized")
+
+        self._hash_ctx.update(data)
+
+    def finalize(self):
+        if self._hash_ctx is None:
+            raise AlreadyFinalized("Context has already been 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.finalize()
+        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 0000000..6bafe31
--- /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 PKCS1v15(object):
+    name = "EMSA-PKCS1-v1_5"
diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py
index 23572a5..2f9e424 100644
--- a/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -135,6 +135,9 @@
     def generate(cls, 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 8ca9df2..0982426 100644
--- a/docs/exceptions.rst
+++ b/docs/exceptions.rst
@@ -42,3 +42,8 @@
 
     This is raised when the verify method of a one time password function's
     computed token does not match the expected token.
+
+
+.. class:: UnsupportedPadding
+
+    This is raised when the chosen 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 0000000..10319fa
--- /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 0000000..7aec3bd
--- /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:: PKCS1v15()
+
+    .. 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`.
+
+.. _`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
similarity index 67%
rename from docs/hazmat/primitives/rsa.rst
rename to docs/hazmat/primitives/asymmetric/rsa.rst
index 4e1f8e4..682820b 100644
--- a/docs/hazmat/primitives/rsa.rst
+++ b/docs/hazmat/primitives/asymmetric/rsa.rst
@@ -50,6 +50,46 @@
             provider.
         :return: A new instance of ``RSAPrivateKey``.
 
+    .. method:: signer(padding, algorithm, backend)
+
+        .. versionadded:: 0.3
+
+        Sign data which can be verified later by others using the public key.
+
+        .. 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(
+            ...     public_exponent=65537,
+            ...     key_size=2048,
+            ...     backend=default_backend()
+            ... )
+            >>> signer = private_key.signer(
+            ...     padding.PKCS1v15(),
+            ...     hashes.SHA256(),
+            ...     default_backend()
+            ... )
+            >>> signer.update(b"this is some data I'd like")
+            >>> signer.update(b" to sign")
+            >>> signature = signer.finalize()
+
+        :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`
+
 
 .. class:: RSAPublicKey(public_exponent, modulus)
 
diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst
index 5199d49..90deec8 100644
--- a/docs/hazmat/primitives/index.rst
+++ b/docs/hazmat/primitives/index.rst
@@ -11,7 +11,7 @@
     symmetric-encryption
     padding
     key-derivation-functions
-    rsa
+    asymmetric/index
     constant-time
     interfaces
     twofactor
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py
index ce77ce2..be1e76e 100644
--- a/tests/hazmat/backends/test_multibackend.py
+++ b/tests/hazmat/backends/test_multibackend.py
@@ -20,6 +20,7 @@
 )
 from cryptography.hazmat.backends.multibackend import MultiBackend
 from cryptography.hazmat.primitives import hashes, hmac
+from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 
 
@@ -85,6 +86,9 @@
     def generate_rsa_private_key(self, public_exponent, private_key):
         pass
 
+    def create_rsa_signature_ctx(self, private_key, padding, algorithm):
+        pass
+
 
 class TestMultiBackend(object):
     def test_ciphers(self):
@@ -158,6 +162,13 @@
             key_size=1024, public_exponent=65537
         )
 
+        backend.create_rsa_signature_ctx("private_key", padding.PKCS1v15(),
+                                         hashes.MD5())
+
         backend = MultiBackend([])
         with pytest.raises(UnsupportedAlgorithm):
             backend.generate_rsa_private_key(key_size=1024, public_exponent=3)
+
+        with pytest.raises(UnsupportedAlgorithm):
+            backend.create_rsa_signature_ctx("private_key", padding.PKCS1v15(),
+                                             hashes.MD5())
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index df3a70f..647c51b 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 DummyPadding(object):
+    name = "UNSUPPORTED-PADDING"
+
+
 def _modinv(e, m):
     """
     Modular Multiplicative Inverse.  Returns x such that: (x*e) mod m == 1
@@ -55,6 +64,17 @@
     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,63 @@
         # 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(
+            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"]
+        )
+        signer = private_key.signer(padding.PKCS1v15(), 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(
+            public_exponent=65537,
+            key_size=512,
+            backend=backend
+        )
+        signer = private_key.signer(padding.PKCS1v15(), 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(
+            public_exponent=65537,
+            key_size=512,
+            backend=backend
+        )
+        with pytest.raises(exceptions.UnsupportedPadding):
+            private_key.signer(DummyPadding(), hashes.SHA1(), backend)
+
+    def test_padding_incorrect_type(self, backend):
+        private_key = rsa.RSAPrivateKey.generate(
+            public_exponent=65537,
+            key_size=512,
+            backend=backend
+        )
+        with pytest.raises(TypeError):
+            private_key.signer("notpadding", hashes.SHA1(), backend)