diff options
-rw-r--r-- | cryptography/hazmat/primitives/asymmetric/__init__.py | 0 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/asymmetric/rsa.py | 149 | ||||
-rw-r--r-- | docs/hazmat/primitives/index.rst | 1 | ||||
-rw-r--r-- | docs/hazmat/primitives/rsa.rst | 58 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_rsa.py | 173 |
5 files changed, 381 insertions, 0 deletions
diff --git a/cryptography/hazmat/primitives/asymmetric/__init__.py b/cryptography/hazmat/primitives/asymmetric/__init__.py new file mode 100644 index 00000000..e69de29b --- /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 00000000..1b33eaab --- /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 bde07392..38ed24c9 100644 --- a/docs/hazmat/primitives/index.rst +++ b/docs/hazmat/primitives/index.rst @@ -11,5 +11,6 @@ Primitives 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 00000000..7c6356c1 --- /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 00000000..e2aca028 --- /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) |