diff options
43 files changed, 1893 insertions, 138 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index da529f68..e45a4ae6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -78,6 +78,51 @@ Changelog support loading DER encoded public keys. * Fixed building against LibreSSL, a compile-time substitute for OpenSSL. * FreeBSD 9.2 was removed from the continuous integration system. +* Updated Windows wheels to be compiled against OpenSSL 1.0.2. +* :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key` + and :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` + now support PKCS1 RSA public keys (in addition to the previous support for + SubjectPublicKeyInfo format for RSA, EC, and DSA). +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and deprecated + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithNumbers`. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization.private_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + and deprecated + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithNumbers`. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` + and deprecated + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithNumbers`. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization.private_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + and deprecated + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithNumbers`. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization.public_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization` + and deprecated + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithNumbers`. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization.public_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. 0.7.2 - 2015-01-16 ~~~~~~~~~~~~~~~~~~ diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 2cd9faa6..68b85291 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -33,6 +33,9 @@ Asymmetric ciphers `unenc-rsa-pkcs8.pem`_, `pkcs12_s2k_pem.c`_. The contents of `enc2-rsa-pkcs8.pem`_ was re-encrypted using a stronger PKCS#8 cipher. * `Botan's ECC private keys`_. +* `asymmetric/public/PKCS1/dsa.pub.pem`_ is a PKCS1 DSA public key from the + Ruby test suite. + Custom Asymmetric Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -71,7 +74,9 @@ Custom Asymmetric Vectors `OpenSSL example key`_ for RSA. * DER conversions of `enc-rsa-pkcs8.pem`_, `enc2-rsa-pkcs8.pem`_, and `unenc-rsa-pkcs8.pem`_. - +* ``asymmetric/public/PKCS1/rsa.pub.pem`` and + ``asymmetric/public/PKCS1/rsa.pub.der`` are PKCS1 conversions of the public + key from ``asymmetric/PKCS8/unenc-rsa-pkcs8.pem`` using PEM and DER encoding. X.509 ~~~~~ @@ -104,6 +109,20 @@ Custom X.509 Vectors generated using OpenSSL that contains a UTF8String common name with the value "We heart UTF8!™". +Custom X.509 Request Vectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* ``dsa_sha1.pem`` - Contains a certificate request using 1024-bit DSA + parameters and SHA1 generated using OpenSSL. +* ``rsa_md4.pem`` - Contains a certificate request using 2048 bit RSA and MD4 + generated using OpenSSL. +* ``rsa_sha1.pem`` - Contains a certificate request using 2048 bit RSA and + SHA1 generated using OpenSSL. +* ``rsa_sha256.pem`` - Contains a certificate request using 2048 bit RSA and + SHA256 generated using OpenSSL. +* ``ec_sha256.pem`` - Contains a certificate request using EC (``secp384r1``) + and SHA256 generated using OpenSSL. + Hashes ~~~~~~ @@ -207,12 +226,12 @@ header format (substituting the correct information): .. _`draft RFC`: https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01 .. _`Specification repository`: https://github.com/fernet/spec .. _`errata`: http://www.rfc-editor.org/errata_search.php?rfc=6238 -.. _`OpenSSL example key`: http://git.openssl.org/gitweb/?p=openssl.git;a=blob;f=test/testrsa.pem;h=aad21067a8f7cb93a52a511eb9162fd83be39135;hb=66e8211c0b1347970096e04b18aa52567c325200 -.. _`GnuTLS key parsing tests`: https://gitorious.org/gnutls/gnutls/commit/f16ef39ef0303b02d7fa590a37820440c466ce8d -.. _`enc-rsa-pkcs8.pem`: https://gitorious.org/gnutls/gnutls/source/f8d943b38bf74eaaa11d396112daf43cb8aa82ae:tests/pkcs8-decode/encpkcs8.pem -.. _`enc2-rsa-pkcs8.pem`: https://gitorious.org/gnutls/gnutls/source/f8d943b38bf74eaaa11d396112daf43cb8aa82ae:tests/pkcs8-decode/enc2pkcs8.pem -.. _`unenc-rsa-pkcs8.pem`: https://gitorious.org/gnutls/gnutls/source/f8d943b38bf74eaaa11d396112daf43cb8aa82ae:tests/pkcs8-decode/unencpkcs8.pem -.. _`pkcs12_s2k_pem.c`: https://gitorious.org/gnutls/gnutls/source/f8d943b38bf74eaaa11d396112daf43cb8aa82ae:tests/pkcs12_s2k_pem.c +.. _`OpenSSL example key`: https://github.com/openssl/openssl/blob/d02b48c63a58ea4367a0e905979f140b7d090f86/test/testrsa.pem +.. _`GnuTLS key parsing tests`: https://gitlab.com/gnutls/gnutls/commit/f16ef39ef0303b02d7fa590a37820440c466ce8d +.. _`enc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/encpkcs8.pem +.. _`enc2-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/enc2pkcs8.pem +.. _`unenc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/unencpkcs8.pem +.. _`pkcs12_s2k_pem.c`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs12_s2k_pem.c .. _`Botan's ECC private keys`: https://github.com/randombit/botan/tree/4917f26a2b154e841cd27c1bcecdd41d2bdeb6ce/src/tests/data/ecc .. _`GnuTLS example keys`: https://gitorious.org/gnutls/gnutls/commit/ad2061deafdd7db78fd405f9d143b0a7c579da7b .. _`NESSIE IDEA vectors`: https://www.cosic.esat.kuleuven.be/nessie/testvectors/bc/idea/Idea-128-64.verified.test-vectors @@ -223,3 +242,4 @@ header format (substituting the correct information): .. _`testx509.pem`: https://github.com/openssl/openssl/blob/master/test/testx509.pem .. _`DigiCert Global Root G3`: http://cacerts.digicert.com/DigiCertGlobalRootG3.crt .. _`root data`: https://hg.mozilla.org/projects/nss/file/25b2922cc564/security/nss/lib/ckfw/builtins/certdata.txt#l2053 +.. _`asymmetric/public/PKCS1/dsa.pub.pem`: https://github.com/ruby/ruby/blob/4ccb387f3bc436a08fc6d72c4931994f5de95110/test/openssl/test_pkey_dsa.rb#L53 diff --git a/docs/hazmat/primitives/asymmetric/dh.rst b/docs/hazmat/primitives/asymmetric/dh.rst new file mode 100644 index 00000000..fdf113f7 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/dh.rst @@ -0,0 +1,64 @@ +.. hazmat:: + +Diffie-Hellman key exchange +=========================== + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.dh + + +.. class:: DHPrivateNumbers(x, public_numbers) + + .. versionadded:: 0.8 + + The collection of integers that make up a Diffie-Hellman private key. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers` + + The :class:`DHPublicNumbers` which makes up the DH public + key associated with this DH private key. + + .. attribute:: x + + :type: int + + The private value. + + +.. class:: DHPublicNumbers(parameters, y) + + .. versionadded:: 0.8 + + The collection of integers that make up a Diffie-Hellman public key. + + .. attribute:: parameter_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers` + + The parameters for this DH group. + + .. attribute:: y + + :type: int + + The public value. + + +.. class:: DHParameterNumbers(p, g) + + .. versionadded:: 0.8 + + The collection of integers that define a Diffie-Hellman group. + + .. attribute:: p + + :type: int + + The prime modulus value. + + .. attribute:: g + + :type: int + + The generator value. diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 3a47da45..bd02423f 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -301,6 +301,50 @@ Key interfaces instance. +.. class:: DSAPrivateKeyWithSerialization + + .. versionadded:: 0.8 + + Extends :class:`DSAPrivateKey`. + + .. method:: private_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers` + object. + + :returns: A + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers` + instance. + + .. method:: private_bytes(encoding, format, encryption_algorithm) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`), + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL` + or + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`) + and encryption algorithm (such as + :class:`~cryptography.hazmat.primitives.serialization.BestAvailableEncryption` + or :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PrivateFormat` + enum. + + :param encryption_algorithm: An instance of an object conforming to the + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` + interface. + + :return bytes: Serialized key. + + .. class:: DSAPublicKey .. versionadded:: 0.3 diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 8b9a584b..6c03d773 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -326,6 +326,45 @@ Key Interfaces :returns: An :class:`EllipticCurvePrivateNumbers` instance. +.. class:: EllipticCurvePrivateKeyWithSerialization + + .. versionadded:: 0.8 + + Extends :class:`EllipticCurvePrivateKey`. + + .. method:: private_numbers() + + Create a :class:`EllipticCurvePrivateNumbers` object. + + :returns: An :class:`EllipticCurvePrivateNumbers` instance. + + .. method:: private_bytes(encoding, format, encryption_algorithm) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`), + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL` + or + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`) + and encryption algorithm (such as + :class:`~cryptography.hazmat.primitives.serialization.BestAvailableEncryption` + or :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PrivateFormat` enum. + + :param encryption_algorithm: An instance of an object conforming to the + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` + interface. + + :return bytes: Serialized key. + + .. class:: EllipticCurvePublicKey .. versionadded:: 0.5 @@ -366,6 +405,36 @@ Key Interfaces :returns: An :class:`EllipticCurvePublicNumbers` instance. +.. class:: EllipticCurvePublicKeyWithSerialization + + .. versionadded:: 0.6 + + Extends :class:`EllipticCurvePublicKey`. + + .. method:: public_numbers() + + Create a :class:`EllipticCurvePublicNumbers` object. + + :returns: An :class:`EllipticCurvePublicNumbers` instance. + + .. method:: public_bytes(encoding, format) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PublicFormat` enum. + + :return bytes: Serialized key. + + .. _`FIPS 186-3`: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf .. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf .. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst index 59f00c5d..4242a0bd 100644 --- a/docs/hazmat/primitives/asymmetric/index.rst +++ b/docs/hazmat/primitives/asymmetric/index.rst @@ -29,6 +29,7 @@ and Elliptic Curve. dsa ec rsa + dh serialization interfaces utils diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index fd97d75b..e7033100 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -80,6 +80,56 @@ password. If the key is encrypted we can pass a ``bytes`` object as the There is also support for :func:`loading public keys in the SSH format <cryptography.hazmat.primitives.serialization.load_ssh_public_key>`. +Key serialization +~~~~~~~~~~~~~~~~~ + +If you have a private key that you've loaded or generated which implements the +:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` +interface you can use +:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes` +to serialize the key. + +.. doctest:: + + >>> from cryptography.hazmat.primitives import serialization + >>> pem = private_key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.PKCS8, + ... encryption_algorithm=serialization.BestAvailableEncryption(b'mypassword') + ... ) + >>> pem.splitlines()[0] + '-----BEGIN ENCRYPTED PRIVATE KEY-----' + +It is also possible to serialize without encryption using +:class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. + +.. doctest:: + + >>> pem = private_key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.TraditionalOpenSSL, + ... encryption_algorithm=serialization.NoEncryption() + ... ) + >>> pem.splitlines()[0] + '-----BEGIN RSA PRIVATE KEY-----' + +Similarly, if your public key implements +:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` +interface you can use +:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization.public_bytes` +to serialize the key. + +.. doctest:: + + >>> from cryptography.hazmat.primitives import serialization + >>> public_key = private_key.public_key() + >>> pem = public_key.public_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PublicFormat.SubjectPublicKeyInfo + ... ) + >>> pem.splitlines()[0] + '-----BEGIN PUBLIC KEY-----' + Signing ~~~~~~~ @@ -485,6 +535,50 @@ Key interfaces instance. +.. class:: RSAPrivateKeyWithSerialization + + .. versionadded:: 0.8 + + Extends :class:`RSAPrivateKey`. + + .. method:: private_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers` + object. + + :returns: An + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers` + instance. + + .. method:: private_bytes(encoding, format, encryption_algorithm) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`), + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL` + or + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`) + and encryption algorithm (such as + :class:`~cryptography.hazmat.primitives.serialization.BestAvailableEncryption` + or :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PrivateFormat` + enum. + + :param encryption_algorithm: An instance of an object conforming to the + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` + interface. + + :return bytes: Serialized key. + + .. class:: RSAPublicKey .. versionadded:: 0.2 @@ -549,6 +643,42 @@ Key interfaces instance. +.. class:: RSAPublicKeyWithSerialization + + .. versionadded:: 0.8 + + Extends :class:`RSAPublicKey`. + + .. method:: public_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers` + object. + + :returns: An + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers` + instance. + + .. method:: public_bytes(encoding, format) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo` + or + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.PKCS1`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PublicFormat` enum. + + :return bytes: Serialized key. + + .. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem) .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography .. _`specific mathematical properties`: https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index 87f3c0b0..ff69973a 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -3,7 +3,7 @@ Key Serialization ================= -.. currentmodule:: cryptography.hazmat.primitives.serialization +.. module:: cryptography.hazmat.primitives.serialization .. testsetup:: @@ -282,3 +282,106 @@ DSA keys look almost identical but begin with ``ssh-dss`` rather than :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that is not supported. + +Serialization Formats +~~~~~~~~~~~~~~~~~~~~~ + +.. class:: PrivateFormat + + .. versionadded:: 0.8 + + An enumeration for private key formats. Used with the ``private_bytes`` + method available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. + + .. attribute:: TraditionalOpenSSL + + Frequently known as PKCS#1 format. Still a widely used format, but + generally considered legacy. + + .. attribute:: PKCS8 + + A more modern format for serializing keys which allows for better + encryption. Choose this unless you have explicit legacy compatibility + requirements. + +.. class:: PublicFormat + + .. versionadded:: 0.8 + + An enumeration for public key formats. Used with the ``public_bytes`` + method available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. + + .. attribute:: SubjectPublicKeyInfo + + This is the typical public key format. It consists of an algorithm + identifier and the public key as a bit string. Choose this unless + you have specific needs. + + .. attribute:: PKCS1 + + Just the public key elements (without the algorithm identifier). This + format is RSA only, but is used by some older systems. + +Serialization Encodings +~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: Encoding + + .. versionadded:: 0.8 + + An enumeration for encoding types. Used with the ``private_bytes`` method + available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` + as well as ``public_bytes`` on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. + + .. attribute:: PEM + + For PEM format. This is a base64 format with delimiters. + + .. attribute:: DER + + For DER format. This is a binary format. + + +Serialization Encryption Types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: KeySerializationEncryption + + Objects with this interface are usable as encryption types with methods + like ``private_bytes`` available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. + All other classes in this section represent the available choices for + encryption and have this interface. They are used with + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`. + +.. class:: BestAvailableEncryption(password) + + Encrypt using the best available encryption for a given key's backend. + This is a curated encryption choice and the algorithm may change over + time. + + :param bytes password: The password to use for encryption. + +.. class:: NoEncryption + + Do not encrypt. diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 0f7e0377..47486895 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -6,7 +6,6 @@ Symmetric encryption .. module:: cryptography.hazmat.primitives.ciphers - Symmetric encryption is a way to `encrypt`_ or hide the contents of material where the sender and receiver both use the same secret key. Note that symmetric encryption is **not** sufficient for most applications because it only @@ -475,7 +474,7 @@ Interfaces ``AEADEncryptionContext`` provider. ``AEADCipherContext`` contains an additional method :meth:`authenticate_additional_data` for adding additional authenticated but unencrypted data (see note below). You should - call this before calls to ``update``. When you are done call `finalize`` + call this before calls to ``update``. When you are done call ``finalize`` to finish the operation. .. note:: @@ -555,7 +554,7 @@ Interfaces used by the symmetric cipher modes described in .. method:: validate_for_algorithm(algorithm) - :param CipherAlgorithm algorithm: + :param cryptography.hazmat.primitives.ciphers.CipherAlgorithm algorithm: Checks that the combination of this mode with the provided algorithm meets any necessary invariants. This should raise an exception if they diff --git a/docs/hazmat/primitives/twofactor.rst b/docs/hazmat/primitives/twofactor.rst index 0d86f7cc..89d81222 100644 --- a/docs/hazmat/primitives/twofactor.rst +++ b/docs/hazmat/primitives/twofactor.rst @@ -39,7 +39,7 @@ codes (HMAC). and be at least 128 bits. It is recommended that the key be 160 bits. :param int length: Length of generated one time password as ``int``. - :param HashAlgorithm algorithm: A + :param cryptography.hazmat.primitives.hashes.HashAlgorithm algorithm: A :class:`~cryptography.hazmat.primitives.hashes` provider. :param backend: A @@ -137,7 +137,7 @@ similar to the following code. and be at least 128 bits. It is recommended that the key be 160 bits. :param int length: Length of generated one time password as ``int``. - :param HashAlgorithm algorithm: A + :param cryptography.hazmat.primitives.hashes.HashAlgorithm algorithm: A :class:`~cryptography.hazmat.primitives.hashes` provider. :param int time_step: The time step size. The recommended size is 30. diff --git a/docs/limitations.rst b/docs/limitations.rst index ce61d893..0dfc49ca 100644 --- a/docs/limitations.rst +++ b/docs/limitations.rst @@ -16,4 +16,4 @@ Likelihood: unlikely, Remediation Cost: expensive to repair" and we do not consider this a high risk for most users. .. _`Memory wiping`: http://blogs.msdn.com/b/oldnewthing/archive/2013/05/29/10421912.aspx -.. _`CERT secure coding guidelines`: https://www.securecoding.cert.org/confluence/display/seccode/MEM03-C.+Clear+sensitive+information+stored+in+reusable+resources +.. _`CERT secure coding guidelines`: https://www.securecoding.cert.org/confluence/display/c/MEM03-C.+Clear+sensitive+information+stored+in+reusable+resources diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index ddd37897..81310d2d 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -14,6 +14,7 @@ crypto cryptographic cryptographically Debian +Diffie decrypt decrypted decrypting @@ -44,6 +45,8 @@ pseudorandom pyOpenSSL Schneier scrypt +Serializers +serializer Solaris Tanja testability diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index eb23ef00..f33aba95 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -36,7 +36,7 @@ from cryptography.hazmat.backends.openssl.rsa import ( ) from cryptography.hazmat.backends.openssl.x509 import _Certificate from cryptography.hazmat.bindings.openssl.binding import Binding -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from cryptography.hazmat.primitives.asymmetric.padding import ( MGF1, OAEP, PKCS1v15, PSS @@ -320,6 +320,7 @@ class Backend(object): ) def _bn_to_int(self, bn): + assert bn != self._ffi.NULL if six.PY3: # Python 3 has constant time from_bytes, so use that. @@ -346,6 +347,7 @@ class Backend(object): ownership of the object). Be sure to register it for GC if it will be discarded after use. """ + assert bn is None or bn != self._ffi.NULL if bn is None: bn = self._ffi.NULL @@ -690,12 +692,28 @@ class Backend(object): ) def load_pem_public_key(self, data): - return self._load_key( - self._lib.PEM_read_bio_PUBKEY, - self._evp_pkey_to_public_key, - data, - None, + mem_bio = self._bytes_to_bio(data) + evp_pkey = self._lib.PEM_read_bio_PUBKEY( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL ) + if evp_pkey != self._ffi.NULL: + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + return self._evp_pkey_to_public_key(evp_pkey) + else: + # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still + # need to check to see if it is a pure PKCS1 RSA public key (not + # embedded in a subjectPublicKeyInfo) + self._consume_errors() + res = self._lib.BIO_reset(mem_bio.bio) + assert res == 1 + rsa_cdata = self._lib.PEM_read_bio_RSAPublicKey( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if rsa_cdata != self._ffi.NULL: + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + return _RSAPublicKey(self, rsa_cdata) + else: + self._handle_key_loading_error() def load_der_private_key(self, data, password): # OpenSSL has a function called d2i_AutoPrivateKey that can simplify @@ -760,12 +778,24 @@ class Backend(object): def load_der_public_key(self, data): mem_bio = self._bytes_to_bio(data) evp_pkey = self._lib.d2i_PUBKEY_bio(mem_bio.bio, self._ffi.NULL) - if evp_pkey == self._ffi.NULL: + if evp_pkey != self._ffi.NULL: + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + return self._evp_pkey_to_public_key(evp_pkey) + else: + # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still + # need to check to see if it is a pure PKCS1 RSA public key (not + # embedded in a subjectPublicKeyInfo) self._consume_errors() - raise ValueError("Could not unserialize key data.") - - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return self._evp_pkey_to_public_key(evp_pkey) + res = self._lib.BIO_reset(mem_bio.bio) + assert res == 1 + rsa_cdata = self._lib.d2i_RSAPublicKey_bio( + mem_bio.bio, self._ffi.NULL + ) + if rsa_cdata != self._ffi.NULL: + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + return _RSAPublicKey(self, rsa_cdata) + else: + self._handle_key_loading_error() def load_pem_x509_certificate(self, data): mem_bio = self._bytes_to_bio(data) @@ -1093,6 +1123,93 @@ class Backend(object): return ctx + def _private_key_bytes(self, encoding, format, encryption_algorithm, + traditional_write_func, evp_pkey, cdata): + if not isinstance(encoding, serialization.Encoding): + raise TypeError("encoding must be an item from the Encoding enum") + + if not isinstance(format, serialization.PrivateFormat): + raise TypeError( + "format must be an item from the PrivateFormat enum" + ) + + # This is a temporary check until we land DER serialization. + if encoding is not serialization.Encoding.PEM: + raise ValueError("Only PEM encoding is supported by this backend") + + if format is serialization.PrivateFormat.PKCS8: + write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey + key = evp_pkey + elif format is serialization.PrivateFormat.TraditionalOpenSSL: + write_bio = traditional_write_func + key = cdata + + if not isinstance(encryption_algorithm, + serialization.KeySerializationEncryption): + raise TypeError( + "Encryption algorithm must be a KeySerializationEncryption " + "instance" + ) + + if isinstance(encryption_algorithm, serialization.NoEncryption): + password = b"" + passlen = 0 + evp_cipher = self._ffi.NULL + elif isinstance(encryption_algorithm, + serialization.BestAvailableEncryption): + # This is a curated value that we will update over time. + evp_cipher = self._lib.EVP_get_cipherbyname( + b"aes-256-cbc" + ) + password = encryption_algorithm.password + passlen = len(password) + if passlen > 1023: + raise ValueError( + "Passwords longer than 1023 bytes are not supported by " + "this backend" + ) + else: + raise ValueError("Unsupported encryption type") + + bio = self._create_mem_bio() + res = write_bio( + bio, + key, + evp_cipher, + password, + passlen, + self._ffi.NULL, + self._ffi.NULL + ) + assert res == 1 + return self._read_mem_bio(bio) + + def _public_key_bytes(self, encoding, format, pkcs1_write_func, evp_pkey, + cdata): + if not isinstance(encoding, serialization.Encoding): + raise TypeError("encoding must be an item from the Encoding enum") + + if not isinstance(format, serialization.PublicFormat): + raise TypeError( + "format must be an item from the PublicFormat enum" + ) + + # This is a temporary check until we land DER serialization. + if encoding is not serialization.Encoding.PEM: + raise ValueError("Only PEM encoding is supported by this backend") + + if format is serialization.PublicFormat.SubjectPublicKeyInfo: + write_bio = self._lib.PEM_write_bio_PUBKEY + key = evp_pkey + elif format is serialization.PublicFormat.PKCS1: + write_bio = pkcs1_write_func + key = cdata + + bio = self._create_mem_bio() + res = write_bio(bio, key) + assert res == 1 + return self._read_mem_bio(bio) + class GetCipherByName(object): def __init__(self, fmt): diff --git a/src/cryptography/hazmat/backends/openssl/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py index d2972e4a..8d02e492 100644 --- a/src/cryptography/hazmat/backends/openssl/dsa.py +++ b/src/cryptography/hazmat/backends/openssl/dsa.py @@ -11,9 +11,6 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( AsymmetricSignatureContext, AsymmetricVerificationContext, dsa ) -from cryptography.hazmat.primitives.interfaces import ( - DSAParametersWithNumbers, DSAPrivateKeyWithNumbers, DSAPublicKeyWithNumbers -) def _truncate_digest_for_dsa(dsa_cdata, digest, backend): @@ -94,7 +91,7 @@ class _DSASignatureContext(object): return self._backend._ffi.buffer(sig_buf)[:buflen[0]] -@utils.register_interface(DSAParametersWithNumbers) +@utils.register_interface(dsa.DSAParametersWithNumbers) class _DSAParameters(object): def __init__(self, backend, dsa_cdata): self._backend = backend @@ -111,7 +108,7 @@ class _DSAParameters(object): return self._backend.generate_dsa_private_key(self) -@utils.register_interface(DSAPrivateKeyWithNumbers) +@utils.register_interface(dsa.DSAPrivateKeyWithSerialization) class _DSAPrivateKey(object): def __init__(self, backend, dsa_cdata): self._backend = backend @@ -159,8 +156,25 @@ class _DSAPrivateKey(object): dsa_cdata.g = self._backend._lib.BN_dup(self._dsa_cdata.g) return _DSAParameters(self._backend, dsa_cdata) + def private_bytes(self, encoding, format, encryption_algorithm): + evp_pkey = self._backend._lib.EVP_PKEY_new() + assert evp_pkey != self._backend._ffi.NULL + evp_pkey = self._backend._ffi.gc( + evp_pkey, self._backend._lib.EVP_PKEY_free + ) + res = self._backend._lib.EVP_PKEY_set1_DSA(evp_pkey, self._dsa_cdata) + assert res == 1 + return self._backend._private_key_bytes( + encoding, + format, + encryption_algorithm, + self._backend._lib.PEM_write_bio_DSAPrivateKey, + evp_pkey, + self._dsa_cdata + ) + -@utils.register_interface(DSAPublicKeyWithNumbers) +@utils.register_interface(dsa.DSAPublicKeyWithNumbers) class _DSAPublicKey(object): def __init__(self, backend, dsa_cdata): self._backend = backend diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py index 52c93da9..39b0a555 100644 --- a/src/cryptography/hazmat/backends/openssl/ec.py +++ b/src/cryptography/hazmat/backends/openssl/ec.py @@ -9,7 +9,7 @@ from cryptography.exceptions import ( InvalidSignature, UnsupportedAlgorithm, _Reasons ) from cryptography.hazmat.backends.openssl.utils import _truncate_digest -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( AsymmetricSignatureContext, AsymmetricVerificationContext, ec ) @@ -148,7 +148,7 @@ class _ECDSAVerificationContext(object): return True -@utils.register_interface(ec.EllipticCurvePrivateKeyWithNumbers) +@utils.register_interface(ec.EllipticCurvePrivateKeyWithSerialization) class _EllipticCurvePrivateKey(object): def __init__(self, backend, ec_key_cdata): self._backend = backend @@ -200,6 +200,23 @@ class _EllipticCurvePrivateKey(object): public_numbers=self.public_key().public_numbers() ) + def private_bytes(self, encoding, format, encryption_algorithm): + evp_pkey = self._backend._lib.EVP_PKEY_new() + assert evp_pkey != self._backend._ffi.NULL + evp_pkey = self._backend._ffi.gc( + evp_pkey, self._backend._lib.EVP_PKEY_free + ) + res = self._backend._lib.EVP_PKEY_set1_EC_KEY(evp_pkey, self._ec_key) + assert res == 1 + return self._backend._private_key_bytes( + encoding, + format, + encryption_algorithm, + self._backend._lib.PEM_write_bio_ECPrivateKey, + evp_pkey, + self._ec_key + ) + @utils.register_interface(ec.EllipticCurvePublicKeyWithNumbers) class _EllipticCurvePublicKey(object): @@ -245,3 +262,24 @@ class _EllipticCurvePublicKey(object): y=y, curve=self._curve ) + + def public_bytes(self, encoding, format): + if format is serialization.PublicFormat.PKCS1: + raise ValueError( + "EC public keys do not support PKCS1 serialization" + ) + + evp_pkey = self._backend._lib.EVP_PKEY_new() + assert evp_pkey != self._backend._ffi.NULL + evp_pkey = self._backend._ffi.gc( + evp_pkey, self._backend._lib.EVP_PKEY_free + ) + res = self._backend._lib.EVP_PKEY_set1_EC_KEY(evp_pkey, self._ec_key) + assert res == 1 + return self._backend._public_key_bytes( + encoding, + format, + None, + evp_pkey, + None + ) diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 00ddcda3..25168c2f 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -17,8 +17,9 @@ from cryptography.hazmat.primitives.asymmetric import ( from cryptography.hazmat.primitives.asymmetric.padding import ( AsymmetricPadding, MGF1, OAEP, PKCS1v15, PSS ) -from cryptography.hazmat.primitives.interfaces import ( - RSAPrivateKeyWithNumbers, RSAPublicKeyWithNumbers +from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKeyWithNumbers, RSAPrivateKeyWithSerialization, + RSAPublicKeyWithSerialization ) @@ -507,6 +508,7 @@ class _RSAVerificationContext(object): @utils.register_interface(RSAPrivateKeyWithNumbers) +@utils.register_interface(RSAPrivateKeyWithSerialization) class _RSAPrivateKey(object): def __init__(self, backend, rsa_cdata): self._backend = backend @@ -559,8 +561,18 @@ class _RSAPrivateKey(object): ) ) + def private_bytes(self, encoding, format, encryption_algorithm): + return self._backend._private_key_bytes( + encoding, + format, + encryption_algorithm, + self._backend._lib.PEM_write_bio_RSAPrivateKey, + self._evp_pkey, + self._rsa_cdata + ) + -@utils.register_interface(RSAPublicKeyWithNumbers) +@utils.register_interface(RSAPublicKeyWithSerialization) class _RSAPublicKey(object): def __init__(self, backend, rsa_cdata): self._backend = backend @@ -592,3 +604,12 @@ class _RSAPublicKey(object): e=self._backend._bn_to_int(self._rsa_cdata.e), n=self._backend._bn_to_int(self._rsa_cdata.n), ) + + def public_bytes(self, encoding, format): + return self._backend._public_key_bytes( + encoding, + format, + self._backend._lib.PEM_write_bio_RSAPublicKey, + self._evp_pkey, + self._rsa_cdata + ) diff --git a/src/cryptography/hazmat/bindings/openssl/dh.py b/src/cryptography/hazmat/bindings/openssl/dh.py index 06ac6f41..b66e7196 100644 --- a/src/cryptography/hazmat/bindings/openssl/dh.py +++ b/src/cryptography/hazmat/bindings/openssl/dh.py @@ -18,6 +18,9 @@ typedef struct dh_st { BIGNUM *priv_key; /* Public DH value g^x */ BIGNUM *pub_key; + /* X9.42/RFC 2631 */ + BIGNUM *q; + BIGNUM *j; ...; } DH; """ @@ -28,6 +31,7 @@ void DH_free(DH *); int DH_size(const DH *); DH *DH_generate_parameters(int, int, void (*)(int, int, void *), void *); int DH_check(const DH *, int *); +int DH_check_pub_key(const DH *, const BIGNUM *, int *); int DH_generate_key(DH *); int DH_compute_key(unsigned char *, const BIGNUM *, DH *); int DH_set_ex_data(DH *, int, void *); diff --git a/src/cryptography/hazmat/bindings/openssl/err.py b/src/cryptography/hazmat/bindings/openssl/err.py index ec393c1b..0ee19c9e 100644 --- a/src/cryptography/hazmat/bindings/openssl/err.py +++ b/src/cryptography/hazmat/bindings/openssl/err.py @@ -21,6 +21,7 @@ struct ERR_string_data_st { }; typedef struct ERR_string_data_st ERR_STRING_DATA; +static const int ERR_LIB_DH; static const int ERR_LIB_EVP; static const int ERR_LIB_EC; static const int ERR_LIB_PEM; @@ -95,6 +96,10 @@ static const int ASN1_R_UNSUPPORTED_TYPE; static const int ASN1_R_WRONG_TAG; static const int ASN1_R_WRONG_TYPE; +static const int DH_F_COMPUTE_KEY; + +static const int DH_R_INVALID_PUBKEY; + static const int EVP_F_AES_INIT_KEY; static const int EVP_F_D2I_PKEY; static const int EVP_F_DSA_PKEY2PKCS8; diff --git a/src/cryptography/hazmat/bindings/openssl/pem.py b/src/cryptography/hazmat/bindings/openssl/pem.py index d0c70f5d..98c7648f 100644 --- a/src/cryptography/hazmat/bindings/openssl/pem.py +++ b/src/cryptography/hazmat/bindings/openssl/pem.py @@ -72,9 +72,23 @@ int PEM_write_bio_PUBKEY(BIO *, EVP_PKEY *); """ MACROS = """ +int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *, + unsigned char *, int, pem_password_cb *, + void *); """ CUSTOMIZATIONS = """ +// Cryptography_HAS_EC is provided by ec.py so we don't need to define it here +#ifdef OPENSSL_NO_EC +int (*PEM_write_bio_ECPrivateKey)(BIO *, EC_KEY *, const EVP_CIPHER *, + unsigned char *, int, pem_password_cb *, + void *) = NULL; +#endif + """ -CONDITIONAL_NAMES = {} +CONDITIONAL_NAMES = { + "Cryptography_HAS_EC": [ + "PEM_write_bio_ECPrivateKey" + ] +} diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py new file mode 100644 index 00000000..61556efb --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -0,0 +1,101 @@ +# 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 + + +class DHPrivateNumbers(object): + def __init__(self, x, public_numbers): + if not isinstance(x, six.integer_types): + raise TypeError("x must be an integer.") + + if not isinstance(public_numbers, DHPublicNumbers): + raise TypeError("public_numbers must be an instance of " + "DHPublicNumbers.") + + self._x = x + self._public_numbers = public_numbers + + def __eq__(self, other): + if not isinstance(other, DHPrivateNumbers): + return NotImplemented + + return ( + self._x == other._x and + self._public_numbers == other._public_numbers + ) + + def __ne__(self, other): + return not self == other + + public_numbers = utils.read_only_property("_public_numbers") + x = utils.read_only_property("_x") + + +class DHPublicNumbers(object): + def __init__(self, y, parameter_numbers): + if not isinstance(y, six.integer_types): + raise TypeError("y must be an integer.") + + if not isinstance(parameter_numbers, DHParameterNumbers): + raise TypeError( + "parameters must be an instance of DHParameterNumbers.") + + self._y = y + self._parameter_numbers = parameter_numbers + + def __eq__(self, other): + if not isinstance(other, DHPublicNumbers): + return NotImplemented + + return ( + self._y == other._y and + self._parameter_numbers == other._parameter_numbers + ) + + def __ne__(self, other): + return not self == other + + y = utils.read_only_property("_y") + parameter_numbers = utils.read_only_property("_parameter_numbers") + + +class DHParameterNumbers(object): + def __init__(self, p, g): + if ( + not isinstance(p, six.integer_types) or + not isinstance(g, six.integer_types) + ): + raise TypeError("p and g must be integers") + + self._p = p + self._g = g + + def __eq__(self, other): + if not isinstance(other, DHParameterNumbers): + return NotImplemented + + return ( + self._p == other._p and + self._g == other._g + ) + + def __ne__(self, other): + return not self == other + + p = utils.read_only_property("_p") + g = utils.read_only_property("_g") diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py index 58058df9..084686e4 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -57,13 +57,30 @@ class DSAPrivateKey(object): @six.add_metaclass(abc.ABCMeta) -class DSAPrivateKeyWithNumbers(DSAPrivateKey): +class DSAPrivateKeyWithSerialization(DSAPrivateKey): @abc.abstractmethod def private_numbers(self): """ Returns a DSAPrivateNumbers. """ + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + Returns the key serialized as bytes. + """ + + +DSAPrivateKeyWithNumbers = utils.deprecated( + DSAPrivateKeyWithSerialization, + __name__, + ( + "The DSAPrivateKeyWithNumbers interface has been renamed to " + "DSAPrivateKeyWithSerialization" + ), + utils.DeprecatedIn08 +) + @six.add_metaclass(abc.ABCMeta) class DSAPublicKey(object): diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index c7749ca5..bf1705db 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -57,13 +57,30 @@ class EllipticCurvePrivateKey(object): @six.add_metaclass(abc.ABCMeta) -class EllipticCurvePrivateKeyWithNumbers(EllipticCurvePrivateKey): +class EllipticCurvePrivateKeyWithSerialization(EllipticCurvePrivateKey): @abc.abstractmethod def private_numbers(self): """ Returns an EllipticCurvePrivateNumbers. """ + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + Returns the key serialized as bytes. + """ + + +EllipticCurvePrivateKeyWithNumbers = utils.deprecated( + EllipticCurvePrivateKeyWithSerialization, + __name__, + ( + "The EllipticCurvePrivateKeyWithNumbers interface has been renamed to " + "EllipticCurvePrivateKeyWithSerialization" + ), + utils.DeprecatedIn08 +) + @six.add_metaclass(abc.ABCMeta) class EllipticCurvePublicKey(object): @@ -81,13 +98,30 @@ class EllipticCurvePublicKey(object): @six.add_metaclass(abc.ABCMeta) -class EllipticCurvePublicKeyWithNumbers(EllipticCurvePublicKey): +class EllipticCurvePublicKeyWithSerialization(EllipticCurvePublicKey): @abc.abstractmethod def public_numbers(self): """ Returns an EllipticCurvePublicNumbers. """ + @abc.abstractmethod + def public_bytes(self, encoding, format): + """ + Returns the key serialized as bytes. + """ + + +EllipticCurvePublicKeyWithNumbers = utils.deprecated( + EllipticCurvePublicKeyWithSerialization, + __name__, + ( + "The EllipticCurvePublicKeyWithNumbers interface has been renamed to " + "EllipticCurvePublicKeyWithSerialization" + ), + utils.DeprecatedIn08 +) + @utils.register_interface(EllipticCurve) class SECT571R1(object): diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index 332ad2c3..8adc7459 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -42,13 +42,30 @@ class RSAPrivateKey(object): @six.add_metaclass(abc.ABCMeta) -class RSAPrivateKeyWithNumbers(RSAPrivateKey): +class RSAPrivateKeyWithSerialization(RSAPrivateKey): @abc.abstractmethod def private_numbers(self): """ Returns an RSAPrivateNumbers. """ + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + Returns the key serialized as bytes. + """ + + +RSAPrivateKeyWithNumbers = utils.deprecated( + RSAPrivateKeyWithSerialization, + __name__, + ( + "The RSAPrivateKeyWithNumbers interface has been renamed to " + "RSAPrivateKeyWithSerialization" + ), + utils.DeprecatedIn08 +) + @six.add_metaclass(abc.ABCMeta) class RSAPublicKey(object): @@ -72,13 +89,30 @@ class RSAPublicKey(object): @six.add_metaclass(abc.ABCMeta) -class RSAPublicKeyWithNumbers(RSAPublicKey): +class RSAPublicKeyWithSerialization(RSAPublicKey): @abc.abstractmethod def public_numbers(self): """ Returns an RSAPublicNumbers """ + @abc.abstractmethod + def public_bytes(self, encoding, format): + """ + Returns the key serialized as bytes. + """ + + +RSAPublicKeyWithNumbers = utils.deprecated( + RSAPublicKeyWithSerialization, + __name__, + ( + "The RSAPublicKeyWithNumbers interface has been renamed to " + "RSAPublicKeyWithSerialization" + ), + utils.DeprecatedIn08 +) + def generate_private_key(public_exponent, key_size, backend): if not isinstance(backend, RSABackend): diff --git a/src/cryptography/hazmat/primitives/interfaces/__init__.py b/src/cryptography/hazmat/primitives/interfaces/__init__.py index 6b4241bd..6913ace9 100644 --- a/src/cryptography/hazmat/primitives/interfaces/__init__.py +++ b/src/cryptography/hazmat/primitives/interfaces/__init__.py @@ -289,11 +289,12 @@ RSAPrivateKey = utils.deprecated( ) RSAPrivateKeyWithNumbers = utils.deprecated( - rsa.RSAPrivateKeyWithNumbers, + rsa.RSAPrivateKeyWithSerialization, __name__, ( "The RSAPrivateKeyWithNumbers interface has moved to the " - "cryptography.hazmat.primitives.asymmetric.rsa module" + "cryptography.hazmat.primitives.asymmetric.rsa module and has been " + "renamed RSAPrivateKeyWithSerialization" ), utils.DeprecatedIn08 ) @@ -386,5 +387,9 @@ class MACContext(object): signature. """ -# DeprecatedIn07 -CMACContext = MACContext +CMACContext = utils.deprecated( + MACContext, + __name__, + "The CMACContext interface has been renamed to MACContext", + utils.DeprecatedIn07 +) diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index 8ad64dec..6247f7b5 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -6,6 +6,8 @@ from __future__ import absolute_import, division, print_function import abc +import os + import six from cryptography import utils @@ -13,47 +15,11 @@ from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.bindings.utils import LazyLibrary, build_ffi -TYPES = """ -uint8_t Cryptography_check_pkcs7_padding(const uint8_t *, uint8_t); -""" - -FUNCTIONS = """ -/* Returns the value of the input with the most-significant-bit copied to all - of the bits. */ -static uint8_t Cryptography_DUPLICATE_MSB_TO_ALL(uint8_t a) { - return (1 - (a >> (sizeof(uint8_t) * 8 - 1))) - 1; -} - -/* This returns 0xFF if a < b else 0x00, but does so in a constant time - fashion */ -static uint8_t Cryptography_constant_time_lt(uint8_t a, uint8_t b) { - a -= b; - return Cryptography_DUPLICATE_MSB_TO_ALL(a); -} - -uint8_t Cryptography_check_pkcs7_padding(const uint8_t *data, - uint8_t block_len) { - uint8_t i; - uint8_t pad_size = data[block_len - 1]; - uint8_t mismatch = 0; - for (i = 0; i < block_len; i++) { - unsigned int mask = Cryptography_constant_time_lt(i, pad_size); - uint8_t b = data[block_len - 1 - i]; - mismatch |= (mask & (pad_size ^ b)); - } - - /* Check to make sure the pad_size was within the valid range. */ - mismatch |= ~Cryptography_constant_time_lt(0, pad_size); - mismatch |= Cryptography_constant_time_lt(block_len, pad_size); - - /* Make sure any bits set are copied to the lowest bit */ - mismatch |= mismatch >> 4; - mismatch |= mismatch >> 2; - mismatch |= mismatch >> 1; - /* Now check the low bit to see if it's set */ - return (mismatch & 1) == 0; -} -""" +with open(os.path.join(os.path.dirname(__file__), "src/padding.h")) as f: + TYPES = f.read() + +with open(os.path.join(os.path.dirname(__file__), "src/padding.c")) as f: + FUNCTIONS = f.read() _ffi = build_ffi(cdef_source=TYPES, verify_source=FUNCTIONS) diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 0f9506e1..8699fa91 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -4,11 +4,14 @@ from __future__ import absolute_import, division, print_function +import abc import base64 import struct +from enum import Enum import six +from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa @@ -164,3 +167,37 @@ else: data = data[4:] return result + + +class Encoding(Enum): + PEM = "PEM" + DER = "DER" + + +class PrivateFormat(Enum): + PKCS8 = "PKCS8" + TraditionalOpenSSL = "TraditionalOpenSSL" + + +class PublicFormat(Enum): + SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1" + PKCS1 = "Raw PKCS#1" + + +@six.add_metaclass(abc.ABCMeta) +class KeySerializationEncryption(object): + pass + + +@utils.register_interface(KeySerializationEncryption) +class BestAvailableEncryption(object): + def __init__(self, password): + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + self.password = password + + +@utils.register_interface(KeySerializationEncryption) +class NoEncryption(object): + pass diff --git a/src/cryptography/hazmat/primitives/src/padding.c b/src/cryptography/hazmat/primitives/src/padding.c new file mode 100644 index 00000000..570bad9f --- /dev/null +++ b/src/cryptography/hazmat/primitives/src/padding.c @@ -0,0 +1,39 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this +// repository for complete details. + +/* Returns the value of the input with the most-significant-bit copied to all + of the bits. */ +static uint8_t Cryptography_DUPLICATE_MSB_TO_ALL(uint8_t a) { + return (1 - (a >> (sizeof(uint8_t) * 8 - 1))) - 1; +} + +/* This returns 0xFF if a < b else 0x00, but does so in a constant time + fashion */ +static uint8_t Cryptography_constant_time_lt(uint8_t a, uint8_t b) { + a -= b; + return Cryptography_DUPLICATE_MSB_TO_ALL(a); +} + +uint8_t Cryptography_check_pkcs7_padding(const uint8_t *data, + uint8_t block_len) { + uint8_t i; + uint8_t pad_size = data[block_len - 1]; + uint8_t mismatch = 0; + for (i = 0; i < block_len; i++) { + unsigned int mask = Cryptography_constant_time_lt(i, pad_size); + uint8_t b = data[block_len - 1 - i]; + mismatch |= (mask & (pad_size ^ b)); + } + + /* Check to make sure the pad_size was within the valid range. */ + mismatch |= ~Cryptography_constant_time_lt(0, pad_size); + mismatch |= Cryptography_constant_time_lt(block_len, pad_size); + + /* Make sure any bits set are copied to the lowest bit */ + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + /* Now check the low bit to see if it's set */ + return (mismatch & 1) == 0; +} diff --git a/src/cryptography/hazmat/primitives/src/padding.h b/src/cryptography/hazmat/primitives/src/padding.h new file mode 100644 index 00000000..4d218b1a --- /dev/null +++ b/src/cryptography/hazmat/primitives/src/padding.h @@ -0,0 +1,5 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this +// repository for complete details. + +uint8_t Cryptography_check_pkcs7_padding(const uint8_t *, uint8_t); diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 78dcc1ca..253dea55 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -10,9 +10,7 @@ import sys import warnings -# DeprecatedIn07 objects exist. This comment exists to remind developers to -# look for them when it's time for the ninth release cycle deprecation dance. - +DeprecatedIn07 = DeprecationWarning DeprecatedIn08 = PendingDeprecationWarning diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 0e4d75ed..ba0a2ba3 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -15,11 +15,12 @@ import pytest from cryptography import utils from cryptography.exceptions import InternalError, _Reasons +from cryptography.hazmat.backends.interfaces import RSABackend from cryptography.hazmat.backends.openssl.backend import ( Backend, backend ) from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, padding from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, Cipher, CipherAlgorithm @@ -27,7 +28,7 @@ from cryptography.hazmat.primitives.ciphers import ( from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import CBC, CTR, Mode -from ..primitives.fixtures_rsa import RSA_KEY_512 +from ..primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 from ...utils import load_vectors_from_file, raises_unsupported_algorithm @@ -493,3 +494,33 @@ class TestOpenSSLEllipticCurve(object): def test_sn_to_elliptic_curve_not_supported(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): _sn_to_elliptic_curve(backend, b"fake") + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSAPEMSerialization(object): + def test_password_length_limit(self): + password = b"x" * 1024 + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(password) + ) + + def test_unsupported_private_key_encoding(self): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_unsupported_public_key_encoding(self): + key = RSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo + ) diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py new file mode 100644 index 00000000..115f3d8c --- /dev/null +++ b/tests/hazmat/primitives/test_dh.py @@ -0,0 +1,113 @@ +# 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 pytest + +from cryptography.hazmat.primitives.asymmetric import dh + + +def test_dh_parameternumbers(): + params = dh.DHParameterNumbers( + 65537, 3 + ) + + assert params.p == 65537 + assert params.g == 3 + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + None, 3 + ) + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + 65537, None + ) + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + None, None + ) + + +def test_dh_numbers(): + params = dh.DHParameterNumbers( + 65537, 3 + ) + + public = dh.DHPublicNumbers( + 1, params + ) + + assert public.parameter_numbers is params + assert public.y == 1 + + with pytest.raises(TypeError): + dh.DHPublicNumbers( + 1, None + ) + + with pytest.raises(TypeError): + dh.DHPublicNumbers( + None, params + ) + + private = dh.DHPrivateNumbers( + 1, public + ) + + assert private.public_numbers is public + assert private.x == 1 + + with pytest.raises(TypeError): + dh.DHPrivateNumbers( + 1, None + ) + + with pytest.raises(TypeError): + dh.DHPrivateNumbers( + None, public + ) + + +def test_dh_parameter_numbers_equality(): + assert dh.DHParameterNumbers(65537, 3) == dh.DHParameterNumbers(65537, 3) + assert dh.DHParameterNumbers(6, 3) != dh.DHParameterNumbers(65537, 3) + assert dh.DHParameterNumbers(65537, 0) != dh.DHParameterNumbers(65537, 3) + assert dh.DHParameterNumbers(65537, 0) != object() + + +def test_dh_private_numbers_equality(): + params = dh.DHParameterNumbers(65537, 3) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + assert private == dh.DHPrivateNumbers(2, public) + assert private != dh.DHPrivateNumbers(0, public) + assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params)) + assert private != dh.DHPrivateNumbers( + 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + ) + assert private != object() + + +def test_dh_public_numbers_equality(): + params = dh.DHParameterNumbers(65537, 3) + public = dh.DHPublicNumbers(1, params) + + assert public == dh.DHPublicNumbers(1, params) + assert public != dh.DHPublicNumbers(0, params) + assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + assert public != object() diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 8c0fb80c..19ca0794 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -4,13 +4,17 @@ from __future__ import absolute_import, division, print_function +import itertools import os import pytest +from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidSignature -from cryptography.hazmat.backends.interfaces import DSABackend -from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.backends.interfaces import ( + DSABackend, PEMSerializationBackend +) +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric.utils import ( encode_rfc6979_signature @@ -26,11 +30,28 @@ from ...utils import ( ) +def _skip_if_no_serialization(key, backend): + if not isinstance(key, dsa.DSAPrivateKeyWithSerialization): + pytest.skip( + "{0} does not support DSA key serialization".format(backend) + ) + + +def test_skip_if_no_serialization(): + with pytest.raises(pytest.skip.Exception): + _skip_if_no_serialization("notakeywithserialization", "backend") + + +@utils.register_interface(serialization.KeySerializationEncryption) +class DummyKeyEncryption(object): + pass + + @pytest.mark.requires_backend_interface(interface=DSABackend) class TestDSA(object): def test_generate_dsa_parameters(self, backend): parameters = dsa.generate_parameters(1024, backend) - assert isinstance(parameters, interfaces.DSAParameters) + assert isinstance(parameters, dsa.DSAParameters) def test_generate_invalid_dsa_parameters(self, backend): with pytest.raises(ValueError): @@ -51,7 +72,7 @@ class TestDSA(object): g=vector['g'] ).parameters(backend) skey = parameters.generate_private_key() - if isinstance(skey, interfaces.DSAPrivateKeyWithNumbers): + if isinstance(skey, dsa.DSAPrivateKeyWithNumbers): numbers = skey.private_numbers() skey_parameters = numbers.public_numbers.parameter_numbers pkey = skey.public_key() @@ -74,7 +95,7 @@ class TestDSA(object): def test_generate_dsa_private_key_and_parameters(self, backend): skey = dsa.generate_private_key(1024, backend) assert skey - if isinstance(skey, interfaces.DSAPrivateKeyWithNumbers): + if isinstance(skey, dsa.DSAPrivateKeyWithNumbers): numbers = skey.private_numbers() skey_parameters = numbers.public_numbers.parameter_numbers assert numbers.public_numbers.y == pow( @@ -769,3 +790,149 @@ class TestDSANumberEquality(object): ) ) assert priv != object() + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestDSASerialization(object): + @pytest.mark.parametrize( + ("fmt", "password"), + itertools.product( + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + [ + b"s", + b"longerpassword", + b"!*$&(@#$*&($T@%_somesymbols", + b"\x01" * 1000, + ] + ) + ) + def test_private_bytes_encrypted_pem(self, backend, fmt, password): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + _skip_if_no_serialization(key, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_pem_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + "fmt", + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + ) + def test_private_bytes_unencrypted_pem(self, backend, fmt): + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "dsa.1024.pem" + ), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + _skip_if_no_serialization(key, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.NoEncryption() + ) + loaded_key = serialization.load_pem_private_key( + serialized, None, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + def test_private_bytes_traditional_openssl_unencrypted_pem(self, backend): + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "dsa.1024.pem" + ), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + _skip_if_no_serialization(key, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + def test_private_bytes_invalid_encoding(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + DummyKeyEncryption() + ) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index ea621ad6..40b1741c 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -10,8 +10,10 @@ import os import pytest from cryptography import exceptions, utils -from cryptography.hazmat.backends.interfaces import EllipticCurveBackend -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends.interfaces import ( + EllipticCurveBackend, PEMSerializationBackend +) +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( encode_rfc6979_signature @@ -31,6 +33,18 @@ _HASH_TYPES = { } +def _skip_if_no_serialization(key, backend): + if not isinstance( + key, ( + ec.EllipticCurvePrivateKeyWithSerialization, + ec.EllipticCurvePublicKeyWithSerialization + ) + ): + pytest.skip( + "{0} does not support EC key serialization".format(backend) + ) + + def _skip_ecdsa_vector(backend, curve_type, hash_type): if not backend.elliptic_curve_signature_algorithm_supported( ec.ECDSA(hash_type()), @@ -63,12 +77,22 @@ class DummySignatureAlgorithm(object): algorithm = None +@utils.register_interface(serialization.KeySerializationEncryption) +class DummyKeyEncryption(object): + pass + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_skip_curve_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_curve_unsupported(backend, DummyCurve()) +def test_skip_no_serialization(): + with pytest.raises(pytest.skip.Exception): + _skip_if_no_serialization("fakebackend", "fakekey") + + def test_ec_numbers(): numbers = ec.EllipticCurvePrivateNumbers( 1, @@ -378,3 +402,221 @@ class TestECNumbersEquality(object): 1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP521R1()) ) assert priv != object() + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestECSerialization(object): + @pytest.mark.parametrize( + ("fmt", "password"), + itertools.product( + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + [ + b"s", + b"longerpassword", + b"!*$&(@#$*&($T@%_somesymbols", + b"\x01" * 1000, + ] + ) + ) + def test_private_bytes_encrypted_pem(self, backend, fmt, password): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + _skip_if_no_serialization(key, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_pem_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + "fmt", + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + ) + def test_private_bytes_unencrypted_pem(self, backend, fmt): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + _skip_if_no_serialization(key, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.NoEncryption() + ) + loaded_key = serialization.load_pem_private_key( + serialized, None, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + def test_private_bytes_traditional_openssl_unencrypted_pem(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + def test_private_bytes_invalid_encoding(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + DummyKeyEncryption() + ) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestEllipticCurvePEMPublicKeySerialization(object): + def test_public_bytes_unencrypted_pem(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_public_key(key_bytes, backend) + _skip_if_no_serialization(key, backend) + serialized = key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + assert serialized == key_bytes + + def test_public_bytes_invalid_encoding(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: serialization.load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.public_bytes( + "notencoding", + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def test_public_bytes_invalid_format(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: serialization.load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.public_bytes(serialization.Encoding.PEM, "invalidformat") + + def test_public_bytes_pkcs1_unsupported(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: serialization.load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + _skip_if_no_serialization(key, backend) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 + ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 6d8e6874..e6d0ac28 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -15,8 +15,10 @@ from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons ) -from cryptography.hazmat.backends.interfaces import RSABackend -from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.backends.interfaces import ( + PEMSerializationBackend, RSABackend +) +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers @@ -46,6 +48,11 @@ class DummyMGF(object): _salt_length = 0 +@utils.register_interface(serialization.KeySerializationEncryption) +class DummyKeyEncryption(object): + pass + + def _flatten_pkcs1_examples(vectors): flattened_vectors = [] for vector in vectors: @@ -78,6 +85,21 @@ def test_modular_inverse(): ) +def _skip_if_no_serialization(key, backend): + if not isinstance( + key, + (rsa.RSAPrivateKeyWithSerialization, rsa.RSAPublicKeyWithSerialization) + ): + pytest.skip( + "{0} does not support RSA key serialization".format(backend) + ) + + +def test_skip_if_no_serialization(): + with pytest.raises(pytest.skip.Exception): + _skip_if_no_serialization("notakeywithserialization", "backend") + + @pytest.mark.requires_backend_interface(interface=RSABackend) class TestRSA(object): @pytest.mark.parametrize( @@ -91,7 +113,7 @@ class TestRSA(object): skey = rsa.generate_private_key(public_exponent, key_size, backend) assert skey.key_size == key_size - if isinstance(skey, interfaces.RSAPrivateKeyWithNumbers): + if isinstance(skey, rsa.RSAPrivateKeyWithNumbers): _check_rsa_private_numbers(skey.private_numbers()) pkey = skey.public_key() assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) @@ -1725,3 +1747,158 @@ class TestRSAPrimeFactorRecovery(object): def test_invalid_recover_prime_factors(self): with pytest.raises(ValueError): rsa.rsa_recover_prime_factors(34, 3, 7) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestRSAPEMPrivateKeySerialization(object): + @pytest.mark.parametrize( + ("fmt", "password"), + itertools.product( + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + [ + b"s", + b"longerpassword", + b"!*$&(@#$*&($T@%_somesymbols", + b"\x01" * 1000, + ] + ) + ) + def test_private_bytes_encrypted_pem(self, backend, fmt, password): + key = RSA_KEY_2048.private_key(backend) + _skip_if_no_serialization(key, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_pem_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + "fmt", + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + ) + def test_private_bytes_unencrypted_pem(self, backend, fmt): + key = RSA_KEY_2048.private_key(backend) + _skip_if_no_serialization(key, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.NoEncryption() + ) + loaded_key = serialization.load_pem_private_key( + serialized, None, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + def test_private_bytes_traditional_openssl_unencrypted_pem(self, backend): + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "testrsa.pem" + ), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + def test_private_bytes_invalid_encoding(self, backend): + key = RSA_KEY_2048.private_key(backend) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + key = RSA_KEY_2048.private_key(backend) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + key = RSA_KEY_2048.private_key(backend) + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + key = RSA_KEY_2048.private_key(backend) + _skip_if_no_serialization(key, backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + DummyKeyEncryption() + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestRSAPEMPublicKeySerialization(object): + def test_public_bytes_unencrypted_pem(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-rsa-pkcs8.pub.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_public_key(key_bytes, backend) + _skip_if_no_serialization(key, backend) + serialized = key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + assert serialized == key_bytes + + def test_public_bytes_pkcs1_unencrypted_pem(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_public_key(key_bytes, backend) + _skip_if_no_serialization(key, backend) + serialized = key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1, + ) + assert serialized == key_bytes + + def test_public_bytes_invalid_encoding(self, backend): + key = RSA_KEY_2048.private_key(backend).public_key() + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.public_bytes("notencoding", serialization.PublicFormat.PKCS1) + + def test_public_bytes_invalid_format(self, backend): + key = RSA_KEY_2048.private_key(backend).public_key() + _skip_if_no_serialization(key, backend) + with pytest.raises(TypeError): + key.public_bytes(serialization.Encoding.PEM, "invalidformat") diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index a4a91430..07a8f02e 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -16,15 +16,10 @@ from cryptography.hazmat.backends.interfaces import ( DERSerializationBackend, DSABackend, EllipticCurveBackend, PEMSerializationBackend, RSABackend ) -from cryptography.hazmat.primitives import interfaces -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric.dsa import ( - DSAParameterNumbers, DSAPublicNumbers -) -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from cryptography.hazmat.primitives.serialization import ( - load_der_private_key, load_der_public_key, load_pem_private_key, - load_pem_public_key, load_ssh_public_key + BestAvailableEncryption, load_der_private_key, load_der_public_key, + load_pem_private_key, load_pem_public_key, load_ssh_public_key ) @@ -57,8 +52,8 @@ class TestDERSerialization(object): mode="rb" ) assert key - assert isinstance(key, interfaces.RSAPrivateKey) - if isinstance(key, interfaces.RSAPrivateKeyWithNumbers): + assert isinstance(key, rsa.RSAPrivateKey) + if isinstance(key, rsa.RSAPrivateKeyWithNumbers): _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.requires_backend_interface(interface=DSABackend) @@ -80,8 +75,8 @@ class TestDERSerialization(object): mode="rb" ) assert key - assert isinstance(key, interfaces.DSAPrivateKey) - if isinstance(key, interfaces.DSAPrivateKeyWithNumbers): + assert isinstance(key, dsa.DSAPrivateKey) + if isinstance(key, dsa.DSAPrivateKeyWithNumbers): _check_dsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( @@ -103,7 +98,7 @@ class TestDERSerialization(object): ) assert key - assert isinstance(key, interfaces.EllipticCurvePrivateKey) + assert isinstance(key, ec.EllipticCurvePrivateKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @@ -238,6 +233,7 @@ class TestDERSerialization(object): "asymmetric", "DER_Serialization", "unenc-rsa-pkcs8.pub.der"), os.path.join( "asymmetric", "DER_Serialization", "rsa_public_key.der"), + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.der"), ] ) @pytest.mark.requires_backend_interface(interface=RSABackend) @@ -250,8 +246,8 @@ class TestDERSerialization(object): mode="rb" ) assert key - assert isinstance(key, interfaces.RSAPublicKey) - if isinstance(key, interfaces.RSAPublicKeyWithNumbers): + assert isinstance(key, rsa.RSAPublicKey) + if isinstance(key, rsa.RSAPublicKeyWithNumbers): numbers = key.public_numbers() assert numbers.e == 65537 @@ -278,7 +274,7 @@ class TestDERSerialization(object): mode="rb" ) assert key - assert isinstance(key, interfaces.DSAPublicKey) + assert isinstance(key, dsa.DSAPublicKey) @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_load_ec_public_key(self, backend): @@ -293,7 +289,7 @@ class TestDERSerialization(object): mode="rb" ) assert key - assert isinstance(key, interfaces.EllipticCurvePublicKey) + assert isinstance(key, ec.EllipticCurvePublicKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @@ -333,8 +329,8 @@ class TestPEMSerialization(object): ) assert key - assert isinstance(key, interfaces.RSAPrivateKey) - if isinstance(key, interfaces.RSAPrivateKeyWithNumbers): + assert isinstance(key, rsa.RSAPrivateKey) + if isinstance(key, rsa.RSAPrivateKeyWithNumbers): _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( @@ -355,8 +351,8 @@ class TestPEMSerialization(object): ) ) assert key - assert isinstance(key, interfaces.DSAPrivateKey) - if isinstance(key, interfaces.DSAPrivateKeyWithNumbers): + assert isinstance(key, dsa.DSAPrivateKey) + if isinstance(key, dsa.DSAPrivateKeyWithNumbers): _check_dsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( @@ -379,7 +375,7 @@ class TestPEMSerialization(object): ) assert key - assert isinstance(key, interfaces.EllipticCurvePrivateKey) + assert isinstance(key, ec.EllipticCurvePrivateKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @@ -389,6 +385,7 @@ class TestPEMSerialization(object): os.path.join("asymmetric", "PKCS8", "unenc-rsa-pkcs8.pub.pem"), os.path.join( "asymmetric", "PEM_Serialization", "rsa_public_key.pem"), + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), ] ) def test_load_pem_rsa_public_key(self, key_file, backend): @@ -399,8 +396,8 @@ class TestPEMSerialization(object): ) ) assert key - assert isinstance(key, interfaces.RSAPublicKey) - if isinstance(key, interfaces.RSAPublicKeyWithNumbers): + assert isinstance(key, rsa.RSAPublicKey) + if isinstance(key, rsa.RSAPublicKeyWithNumbers): numbers = key.public_numbers() assert numbers.e == 65537 @@ -421,7 +418,7 @@ class TestPEMSerialization(object): ) ) assert key - assert isinstance(key, interfaces.DSAPublicKey) + assert isinstance(key, dsa.DSAPublicKey) @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_load_ec_public_key(self, backend): @@ -435,7 +432,7 @@ class TestPEMSerialization(object): ) ) assert key - assert isinstance(key, interfaces.EllipticCurvePublicKey) + assert isinstance(key, ec.EllipticCurvePublicKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @@ -542,7 +539,7 @@ class TestPEMSerialization(object): ) ) - def test_wrong_format(self, backend): + def test_wrong_private_format(self, backend): key_data = b"---- NOT A KEY ----\n" with pytest.raises(ValueError): @@ -555,6 +552,12 @@ class TestPEMSerialization(object): key_data, b"this password will not be used", backend ) + def test_wrong_public_format(self, backend): + key_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_public_key(key_data, backend) + def test_corrupt_traditional_format(self, backend): # privkey.pem with a bunch of data missing. key_data = textwrap.dedent("""\ @@ -762,12 +765,12 @@ class TestPEMSerialization(object): ) ) assert key - assert isinstance(key, interfaces.DSAPrivateKey) + assert isinstance(key, dsa.DSAPrivateKey) params = key.parameters() - assert isinstance(params, interfaces.DSAParameters) + assert isinstance(params, dsa.DSAParameters) - if isinstance(params, interfaces.DSAParametersWithNumbers): + if isinstance(params, dsa.DSAParametersWithNumbers): num = key.private_numbers() pub = num.public_numbers parameter_numbers = pub.parameter_numbers @@ -917,7 +920,7 @@ class TestRSASSHSerialization(object): key = load_ssh_public_key(ssh_key, backend) assert key is not None - assert isinstance(key, interfaces.RSAPublicKey) + assert isinstance(key, rsa.RSAPublicKey) numbers = key.public_numbers() @@ -934,7 +937,7 @@ class TestRSASSHSerialization(object): '46F8706AB88DDADBD9E8204D48B87789081E074024C8996783B31' '7076A98ABF0A2D8550EAF2097D8CCC7BE76EF', 16) - expected = RSAPublicNumbers(expected_e, expected_n) + expected = rsa.RSAPublicNumbers(expected_e, expected_n) assert numbers == expected @@ -1017,7 +1020,7 @@ class TestDSSSSHSerialization(object): key = load_ssh_public_key(ssh_key, backend) assert key is not None - assert isinstance(key, interfaces.DSAPublicKey) + assert isinstance(key, dsa.DSAPublicKey) numbers = key.public_numbers() @@ -1043,9 +1046,9 @@ class TestDSSSSHSerialization(object): "debb5982fc94d6a8c291f758feae63ad769a5621947221522a2dc31d18ede6f" "b656", 16 ) - expected = DSAPublicNumbers( + expected = dsa.DSAPublicNumbers( expected_y, - DSAParameterNumbers(expected_p, expected_q, expected_g) + dsa.DSAParameterNumbers(expected_p, expected_q, expected_g) ) assert numbers == expected @@ -1062,7 +1065,7 @@ class TestECDSASSHSerialization(object): b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" ) key = load_ssh_public_key(ssh_key, backend) - assert isinstance(key, interfaces.EllipticCurvePublicKey) + assert isinstance(key, ec.EllipticCurvePublicKey) expected_x = int( "44196257377740326295529888716212621920056478823906609851236662550" @@ -1164,3 +1167,13 @@ class TestECDSASSHSerialization(object): ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) + + +class TestKeySerializationEncryptionTypes(object): + def test_non_bytes_password(self): + with pytest.raises(ValueError): + BestAvailableEncryption(object()) + + def test_encryption_with_zero_length_password(self): + with pytest.raises(ValueError): + BestAvailableEncryption(b"") diff --git a/vectors/cryptography_vectors/asymmetric/public/PKCS1/dsa.pub.pem b/vectors/cryptography_vectors/asymmetric/public/PKCS1/dsa.pub.pem new file mode 100644 index 00000000..a2ce0bb0 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/public/PKCS1/dsa.pub.pem @@ -0,0 +1,7 @@ +-----BEGIN DSA PUBLIC KEY----- +MIHfAkEAyJSJ+g+P/knVcgDwwTzC7Pwg/pWs2EMd/r+lYlXhNfzg0biuXRul8VR4 +VUC/phySExY0PdcqItkR/xYAYNMbNwJBAOoV57X0FxKO/PrNa/MkoWzkCKV/hzhE +p0zbFdsicw+hIjJ7S6Sd/FlDlo89HQZ2FuvWJ6wGLM1j00r39+F2qbMCFQCrkhIX +SG+is37hz1IaBeEudjB2HQJAR0AloavBvtsng8obsjLb7EKnB+pSeHr/BdIQ3VH7 +fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ== +-----END DSA PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/public/PKCS1/rsa.pub.der b/vectors/cryptography_vectors/asymmetric/public/PKCS1/rsa.pub.der Binary files differnew file mode 100644 index 00000000..4bccbb26 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/public/PKCS1/rsa.pub.der diff --git a/vectors/cryptography_vectors/asymmetric/public/PKCS1/rsa.pub.pem b/vectors/cryptography_vectors/asymmetric/public/PKCS1/rsa.pub.pem new file mode 100644 index 00000000..6db528cf --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/public/PKCS1/rsa.pub.pem @@ -0,0 +1,5 @@ +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBALskegl+DrI3Msw5Z63xnj1rgoPR0KykwBi+jZgAwHv/B0TJyhy6NuEn +af+x442L7lepOqoWQzlUGXyuaSQU9mT/vHTGZ2xM8QJJaccr4eGho0MU9HePyNCF +WjWVrGKpwSEAd6CLlzC0Wiy4kC9IoAUoS/IPjeyLTQNCddatgcARAgMBAAE= +-----END RSA PUBLIC KEY----- diff --git a/vectors/cryptography_vectors/x509/requests/dsa_sha1.pem b/vectors/cryptography_vectors/x509/requests/dsa_sha1.pem new file mode 100644 index 00000000..11688dbb --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/dsa_sha1.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWDCCAhgCAQAwVzEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5LmlvMQ0wCwYDVQQK +DARQeUNBMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1 +c3RpbjCCAbYwggErBgcqhkjOOAQBMIIBHgKBgQCNf628CeKEqvppFUzqJBdwBJCe +UZ+LNdaFzeW07NyVg+dNNwoPiK2pjwJvJ3Yvs9XaeDb5ht/Ns1ieW5Jb6hFN78A+ ++B2uMMJLvG3z1YjpNCe7pkID1KWxaHsrXjtkPUxhSXb4n5WjjT5MiQZfupdRTCLF +Ctu/KJFjp0tUhZs1twIVAINd5WvQfPf4LiAy/niUmu0ReqLvAoGAH3F7Wgd4L8Lk +5o4xH+qRpU7dNrhqxjTRTwWmipfq6dLvMfse895Cw9EA35ymT1vcKux7/ftHTPgx +/qBYU7XgWfLSSYCgrEY/HoGK81I+PLeaOdRfqScxiXdShCRpz4VAsBSRAk6q+85g +GOih9GWMND9Lp8CyHlN2oh9L64SRlh4DgYQAAoGABxPwdkH2Npu1qVRSdKLUwBmY +Nn+zcbueE0NjY2cu1o+CF0wt4FyOg5vG3laN1QuijY2dhxlCOq7FVX3xDXc6si1t +Zcu4eASml7yP2WW5Uvn36FDt8TyKzbXXU7bRDlngtXMuPIK6+hQDQrxKO7oWvQaB +yKai27t+/mziuEY7FwugADAJBgcqhkjOOAQDAy8AMCwCFGHVjcAo0BEIGKfYF9dC +NXJ8Ss/fAhQJe1LhmOzpXeFyc/CpJN8jzp2BiA== +-----END CERTIFICATE REQUEST----- diff --git a/vectors/cryptography_vectors/x509/requests/ec_sha256.pem b/vectors/cryptography_vectors/x509/requests/ec_sha256.pem new file mode 100644 index 00000000..9497ae3c --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/ec_sha256.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBTzCB1gIBADBXMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8xDTALBgNVBAoM +BFB5Q0ExCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVz +dGluMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3hm1FMCzw66bOY6j4mtegWvc+RAs +rY8S/gL55MkkhySzkpftdYLgTYsypVEDjQkIaAOm0/uRoaEWfsAhWLAO+tOck5ZG +L6zP8P+vcVWBKQnTcmvVn94AHP9LubL1r4y6oAAwCgYIKoZIzj0EAwIDaAAwZQIw +LBqffejBeHMy0jB6iGtHalnxcrmw4lAmLzI4sbRe4RK7brNbD7VqEjuSlushLf/D +AjEAlM9EDJXFKCfVVq5tdlAOMAglXUfCn37ngu11WOUb/XaqRd9tmZ7VxGM0f+I4 +LRdR +-----END CERTIFICATE REQUEST----- diff --git a/vectors/cryptography_vectors/x509/requests/rsa_md4.pem b/vectors/cryptography_vectors/x509/requests/rsa_md4.pem new file mode 100644 index 00000000..f62c8ef8 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/rsa_md4.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnDCCAYQCAQAwVzEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5LmlvMQ0wCwYDVQQK +DARQeUNBMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1 +c3RpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMpZCKiob1IpjL+4 +S/J9Jgcm0ewZ/TbgJw9MB2sTQu7Ljmad9MttNeMyd9wa9fQIhenlpKjJWPSBekSX +P0uvbAKkY00eCM/mi5Deb2lZaXkfpFC2DlsOKm4uyzmyd/ob2/rSeml7+u+xnGSM +1MeYjuS5R7KrQUX+HJyw53cxkEHykXcsAvu3WxpnpZzLSFBNiE6lJZUl4vH86aUl +qrB4C87c150uT01ouEqpon8KfwCnuH1Zqw1e+XVoW6sMqvkoqx2uSJxlMzlWpXhI +rd3bMXHJ+mJyuDP5UVhQU0WuWXPO1JzEfVwVKigbpy8heHare/6tFHXp3V98fhEO +qJGaArUCAwEAAaAAMA0GCSqGSIb3DQEBAwUAA4IBAQCp9f8iBtvibyoBB5W+h9RS +IsbL/bY46MzfjkWaCr1GACC3FefO88qsQYZJNsnqrHKwl8BJUNxf5bLMyFmyLW9D +jBah8wfo3ftjos1u1F1Bt31FcL7UlDoDLZ1LA3cP/JodSbUVZ8ByVI1bj0MRdAx5 +16YNJvCtzXQtoRZxr940u+S10G6E8sSwfGjqJIgOTiF3oYmS1hSBsIGJzrJ4HOp/ +FAN5pxYW3C0sAM4Aq7qSRHQdFoIXovgRxB4XFedIveEidralOe1K0ShV675jmjwa +zoO/bTZGCIISD3KoIaCdkJjKoN0r2ckwhBTxsQsC6iO/FU5rBS4DG7369Y7FNIJ6 +-----END CERTIFICATE REQUEST----- diff --git a/vectors/cryptography_vectors/x509/requests/rsa_sha1.pem b/vectors/cryptography_vectors/x509/requests/rsa_sha1.pem new file mode 100644 index 00000000..0656c316 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/rsa_sha1.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnDCCAYQCAQAwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYD +VQQHEwZBdXN0aW4xDTALBgNVBAoTBFB5Q0ExGDAWBgNVBAMTD2NyeXB0b2dyYXBo +eS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKhAp4Rgy4YQZt+j +BFqUumzxt6udJMdhz/3cwstePx3D5L4lPnA57xT+nW0jBPUNny4VhMUVMKt1CG81 +cTi/97hU0GfR1fOE8fLyw5zDsVQV4mOFVO+EAmSK4+8IM28it+zG1DMcKyHDCRp/ +epUYGAdUpkZkC2BBnkzG9ceYEQp/Awpjn+h+M7R3bfzZk5QOx3arV6GBrYWYhXl2 +3DA/mlc8phmrP+WWMo6SgGuChoPtwXzCVrQZSKK/qNBH0hWNPY4GmqBfqFsycqux +xLRCK2Nm87cOZCN3sUXNYlnl0+fbBI1Rkh5Qdmo3sbEw7msR9QfSCoNAAejeFqks +FPLpZKMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQCDZMhv+7/gv8miH4MSVmWM +qJiXQbgFdtNvCKk0YDpDsYNyRtABZ6UYq7Hee1Gh5bfr6hSUSACBixqSPIBPEgoN +Yk9jEO956GEnVcKwHcx/Wd/bzg2z8mMPGF9QS4wXr4DL02T6X9poM3FTkwlIImzU +Y4KHoK7WUk0wBohcGQKKHuL1qR1ud9uqC0mZbuCgxgtVthvQgKCLs0qn8+B+kfN/ +ahFkW+LYZUwVcNzaFF7XzJIBf31TIl1/KD80Wexb2kHPbdddQ2dsVDSDOFImt+T6 +KchznxsOrxmWE1k5kZeYYuNhgejEwnDDVLf1LBKNsbcGOYIzJMfqJHkbe8PXAF87 +-----END CERTIFICATE REQUEST----- diff --git a/vectors/cryptography_vectors/x509/requests/rsa_sha256.pem b/vectors/cryptography_vectors/x509/requests/rsa_sha256.pem new file mode 100644 index 00000000..06393d23 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/rsa_sha256.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnDCCAYQCAQAwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYD +VQQHDAZBdXN0aW4xDTALBgNVBAoMBFB5Q0ExGDAWBgNVBAMMD2NyeXB0b2dyYXBo +eS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWsaWBzupTPnC9A +xS2dbZtOCRgERIKL+SVOxaFdXko/TsmbUCY2GQ8xhLvhD0R1kEnpzI+wQ/3OQraq +XysouWEVJp07L7AW14tkAwuo+Jy8/Or7NUrOCtOTN2fEO+hT81ihaocG5CwbEUUE +QGSzX9XJjuyVUBRpou8muv0ZRcrJG08AP9pss4MMjkvTEfX6ldht9NWXlTz2AnOL +MR4WSLS1H/Imv9OtO+nFkLUkedrV5eJasW5N2c6YkuHrEOK/Nv28WavULyBpUAJu +k5iHQ1+/5A4spmarX3pvoqxMIeJ/Snju8Xn2T6MDSI4Y+9LbqplRDhl5LKo53RZi +z4JKq8kCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCgpYRnfdC9BjlohcdiUjqz +jzzQgRs8+29YFRyQHfHHT0K0YYEeiblvdNExXVD2s3ppP/C/X2lhs5Nif8YaHI1d +wU0VDIeY55qMv1uhvnK34GkISl3U4v4VLm7bSoPHYmzNgtFQTKs3bZVSz2EtNGAJ +xRfSWcRYmcyJNvTmFStmsqa4NFvCU7PrPraxMJn5tRFJOEiN25qqgEehHwC+mEL4 +700bmos0oF3IiWa5iTByAA2rv+Z8vlpTjJe9bwVrXi7tBLQRV5Kb6EfXPn4jLd+q +Qj3uFk6RyV3DyyM93L8bUSaX2gN/LC55sOnCaGR2t5MrvcrfRDe2EbrnqJsoOSu8 +-----END CERTIFICATE REQUEST----- |