Merge pull request #559 from public/rsa-keys

RSA keys
diff --git a/cryptography/hazmat/primitives/asymmetric/__init__.py b/cryptography/hazmat/primitives/asymmetric/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cryptography/hazmat/primitives/asymmetric/__init__.py
diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py
new file mode 100644
index 0000000..1b33eaa
--- /dev/null
+++ b/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -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 six
+
+from cryptography import utils
+from cryptography.hazmat.primitives import interfaces
+
+
+def _bit_length(x):
+    if sys.version_info >= (2, 7):
+        return x.bit_length()
+    else:
+        return len(bin(x)) - (2 + (x <= 0))
+
+
+@utils.register_interface(interfaces.RSAPublicKey)
+class RSAPublicKey(object):
+    def __init__(self, public_exponent, modulus):
+        if (
+            not isinstance(public_exponent, six.integer_types) or
+            not isinstance(modulus, six.integer_types)
+        ):
+            raise TypeError("RSAPublicKey arguments must be integers")
+
+        if modulus < 3:
+            raise ValueError("modulus must be >= 3")
+
+        if public_exponent < 3 or public_exponent >= modulus:
+            raise ValueError("public_exponent must be >= 3 and < modulus")
+
+        if public_exponent & 1 == 0:
+            raise ValueError("public_exponent must be odd")
+
+        self._public_exponent = public_exponent
+        self._modulus = modulus
+
+    @property
+    def key_size(self):
+        return _bit_length(self.modulus)
+
+    @property
+    def public_exponent(self):
+        return self._public_exponent
+
+    @property
+    def modulus(self):
+        return self._modulus
+
+    @property
+    def e(self):
+        return self.public_exponent
+
+    @property
+    def n(self):
+        return self.modulus
+
+
+@utils.register_interface(interfaces.RSAPrivateKey)
+class RSAPrivateKey(object):
+    def __init__(self, p, q, private_exponent, public_exponent, modulus):
+        if (
+            not isinstance(p, six.integer_types) or
+            not isinstance(q, six.integer_types) or
+            not isinstance(private_exponent, six.integer_types) or
+            not isinstance(public_exponent, six.integer_types) or
+            not isinstance(modulus, six.integer_types)
+        ):
+            raise TypeError("RSAPrivateKey arguments must be integers")
+
+        if modulus < 3:
+            raise ValueError("modulus must be >= 3")
+
+        if p >= modulus:
+            raise ValueError("p must be < modulus")
+
+        if q >= modulus:
+            raise ValueError("q must be < modulus")
+
+        if private_exponent >= modulus:
+            raise ValueError("private_exponent must be < modulus")
+
+        if public_exponent < 3 or public_exponent >= modulus:
+            raise ValueError("public_exponent must be >= 3 and < modulus")
+
+        if public_exponent & 1 == 0:
+            raise ValueError("public_exponent must be odd")
+
+        if p * q != modulus:
+            raise ValueError("p*q must equal modulus")
+
+        self._p = p
+        self._q = q
+        self._private_exponent = private_exponent
+        self._public_exponent = public_exponent
+        self._modulus = modulus
+
+    @property
+    def key_size(self):
+        return _bit_length(self.modulus)
+
+    def public_key(self):
+        return RSAPublicKey(self.public_exponent, self.modulus)
+
+    @property
+    def p(self):
+        return self._p
+
+    @property
+    def q(self):
+        return self._q
+
+    @property
+    def private_exponent(self):
+        return self._private_exponent
+
+    @property
+    def public_exponent(self):
+        return self._public_exponent
+
+    @property
+    def modulus(self):
+        return self._modulus
+
+    @property
+    def d(self):
+        return self.private_exponent
+
+    @property
+    def e(self):
+        return self.public_exponent
+
+    @property
+    def n(self):
+        return self.modulus
diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst
index bde0739..38ed24c 100644
--- a/docs/hazmat/primitives/index.rst
+++ b/docs/hazmat/primitives/index.rst
@@ -11,5 +11,6 @@
     symmetric-encryption
     padding
     key-derivation-functions
+    rsa
     constant-time
     interfaces
diff --git a/docs/hazmat/primitives/rsa.rst b/docs/hazmat/primitives/rsa.rst
new file mode 100644
index 0000000..7c6356c
--- /dev/null
+++ b/docs/hazmat/primitives/rsa.rst
@@ -0,0 +1,58 @@
+.. hazmat::
+
+RSA
+===
+
+.. currentmodule:: cryptography.hazmat.primitives.asymmetric.rsa
+
+`RSA`_ is a `public-key`_ algorithm for encrypting and signing messages.
+
+.. class:: RSAPrivateKey(p, q, private_exponent, public_exponent, modulus)
+    
+    .. versionadded:: 0.2
+
+    An RSA private key is required for decryption and signing of messages.
+
+    Normally you do not need to directly construct private keys because you'll
+    be loading them from a file or generating them automatically.
+
+    .. warning::
+        This method only checks a limited set of properties of its arguments.
+        Using an RSA that you do not trust or with incorrect parameters may
+        lead to insecure operation, crashes, and other undefined behavior. We
+        recommend that you only ever load private keys that were generated with
+        software you trust.
+
+    This class conforms to the
+    :class:`~cryptography.hazmat.primitives.interfaces.RSAPrivateKey`
+    interface.
+
+    :raises TypeError: This is raised when the arguments are not all integers. 
+
+    :raises ValueError: This is raised when the values of `p`, `q`,
+                        `private_exponent`, `public_exponent` or `modulus` do 
+                        not match the bounds specified in `RFC 3447`_.
+
+.. class:: RSAPublicKey(public_exponent, modulus)
+    
+    .. versionadded:: 0.2
+
+    An RSA public key is required for encryption and verification of messages.
+
+    Normally you do not need to directly construct public keys because you'll
+    be loading them from a file, generating them automatically or receiving
+    them from a 3rd party.
+
+    This class conforms to the
+    :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey`
+    interface.
+
+    :raises TypeError: This is raised when the arguments are not all integers. 
+
+    :raises ValueError: This is raised when the values of `public_exponent` or
+                        `modulus` do not match the bounds specified in
+                        `RFC 3447`_.
+
+.. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem)
+.. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography
+.. _`RFC 3447`: https://tools.ietf.org/html/rfc3447
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
new file mode 100644
index 0000000..e2aca02
--- /dev/null
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -0,0 +1,173 @@
+# 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 os
+
+import pytest
+
+from cryptography.hazmat.primitives.asymmetric import rsa
+
+from ...utils import load_pkcs1_vectors, load_vectors_from_file
+
+
+class TestRSA(object):
+    @pytest.mark.parametrize(
+        "pkcs1_example",
+        load_vectors_from_file(
+            os.path.join(
+                "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt"),
+            load_pkcs1_vectors
+        )
+    )
+    def test_load_pss_vect_example_keys(self, pkcs1_example):
+        secret, public = pkcs1_example
+
+        skey = rsa.RSAPrivateKey(**secret)
+        pkey = rsa.RSAPublicKey(**public)
+        pkey2 = skey.public_key()
+
+        assert skey and pkey and pkey2
+
+        assert skey.modulus
+        assert skey.modulus == pkey.modulus
+        assert skey.modulus == skey.n
+        assert skey.public_exponent == pkey.public_exponent
+        assert skey.public_exponent == skey.e
+        assert skey.private_exponent == skey.d
+
+        assert pkey.modulus
+        assert pkey.modulus == pkey2.modulus
+        assert pkey.modulus == pkey.n
+        assert pkey.public_exponent == pkey2.public_exponent
+        assert pkey.public_exponent == pkey.e
+
+        assert skey.key_size
+        assert skey.key_size == pkey.key_size
+        assert skey.key_size == pkey2.key_size
+
+        assert skey.p * skey.q == skey.modulus
+
+    def test_invalid_private_key_argument_types(self):
+        with pytest.raises(TypeError):
+            rsa.RSAPrivateKey(None, None, None, None, None)
+
+    def test_invalid_public_key_argument_types(self):
+        with pytest.raises(TypeError):
+            rsa.RSAPublicKey(None, None)
+
+    def test_invalid_private_key_argument_values(self):
+        # Start with p=3, q=5, private_exponent=14, public_exponent=7,
+        # modulus=15. Then change one value at a time to test the bounds.
+
+        # Test a modulus < 3.
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=3,
+                q=5,
+                private_exponent=14,
+                public_exponent=7,
+                modulus=2
+            )
+
+        # Test a modulus != p * q.
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=3,
+                q=5,
+                private_exponent=14,
+                public_exponent=7,
+                modulus=16
+            )
+
+        # Test a p > modulus.
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=16,
+                q=5,
+                private_exponent=14,
+                public_exponent=7,
+                modulus=15
+            )
+
+        # Test a q > modulus.
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=3,
+                q=16,
+                private_exponent=14,
+                public_exponent=7,
+                modulus=15
+            )
+
+        # Test a private_exponent > modulus
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=3,
+                q=5,
+                private_exponent=16,
+                public_exponent=7,
+                modulus=15
+            )
+
+        # Test a public_exponent < 3
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=3,
+                q=5,
+                private_exponent=14,
+                public_exponent=1,
+                modulus=15
+            )
+
+        # Test a public_exponent > modulus
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=3,
+                q=5,
+                private_exponent=14,
+                public_exponent=17,
+                modulus=15
+            )
+
+        # Test a public_exponent that is not odd.
+        with pytest.raises(ValueError):
+            rsa.RSAPrivateKey(
+                p=3,
+                q=5,
+                private_exponent=14,
+                public_exponent=6,
+                modulus=15
+            )
+
+    def test_invalid_public_key_argument_values(self):
+        # Start with public_exponent=7, modulus=15. Then change one value at a
+        # time to test the bounds.
+
+        # Test a modulus < 3.
+        with pytest.raises(ValueError):
+            rsa.RSAPublicKey(public_exponent=7, modulus=2)
+
+        # Test a public_exponent < 3
+        with pytest.raises(ValueError):
+            rsa.RSAPublicKey(public_exponent=1, modulus=15)
+
+        # Test a public_exponent > modulus
+        with pytest.raises(ValueError):
+            rsa.RSAPublicKey(public_exponent=17, modulus=15)
+
+        # Test a public_exponent that is not odd.
+        with pytest.raises(ValueError):
+            rsa.RSAPublicKey(public_exponent=6, modulus=15)