cryptography-43.0.0/.gitattributes010064400017510000177000000000221464676315000154160ustar 00000000000000*.pem text eol=lf cryptography-43.0.0/.gitignore010064400017510000177000000002351464676315000145210ustar 00000000000000__pycache__/ _build/ build/ dist/ htmlcov/ *.so .tox/ .cache/ .coverage *.egg-info/ *.egg .eggs/ *.py[cdo] .hypothesis/ target/ .rust-cov/ *.lcov *.profdata cryptography-43.0.0/CHANGELOG.rst010064400017510000177000003112731464676315000145610ustar 00000000000000Changelog ========= .. _v43-0-0: 43.0.0 - 2024-07-20 ~~~~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for OpenSSL less than 1.1.1e has been removed. Users on older version of OpenSSL will need to upgrade. * **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.8. * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.3.1. * Updated the minimum supported Rust version (MSRV) to 1.65.0, from 1.63.0. * :func:`~cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key` now enforces a minimum RSA key size of 1024-bit. Note that 1024-bit is still considered insecure, users should generally use a key size of 2048-bits. * :func:`~cryptography.hazmat.primitives.serialization.pkcs7.serialize_certificates` now emits ASN.1 that more closely follows the recommendations in :rfc:`2315`. * Added new :doc:`/hazmat/decrepit/index` module which contains outdated and insecure cryptographic primitives. :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5`, :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SEED`, :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA`, and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.Blowfish`, which were deprecated in 37.0.0, have been added to this module. They will be removed from the ``cipher`` module in 45.0.0. * Moved :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ARC4` into :doc:`/hazmat/decrepit/index` and deprecated them in the ``cipher`` module. They will be removed from the ``cipher`` module in 48.0.0. * Added support for deterministic :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` (:rfc:`6979`) * Added support for client certificate verification to the :mod:`X.509 path validation ` APIs in the form of :class:`~cryptography.x509.verification.ClientVerifier`, :class:`~cryptography.x509.verification.VerifiedClient`, and ``PolicyBuilder`` :meth:`~cryptography.x509.verification.PolicyBuilder.build_client_verifier`. * Added Certificate :attr:`~cryptography.x509.Certificate.public_key_algorithm_oid` and Certificate Signing Request :attr:`~cryptography.x509.CertificateSigningRequest.public_key_algorithm_oid` to determine the :class:`~cryptography.hazmat._oid.PublicKeyAlgorithmOID` Object Identifier of the public key found inside the certificate. * Added :attr:`~cryptography.x509.InvalidityDate.invalidity_date_utc`, a timezone-aware alternative to the naïve ``datetime`` attribute :attr:`~cryptography.x509.InvalidityDate.invalidity_date`. * Added support for parsing empty DN string in :meth:`~cryptography.x509.Name.from_rfc4514_string`. * Added the following properties that return timezone-aware ``datetime`` objects: :meth:`~cryptography.x509.ocsp.OCSPResponse.produced_at_utc`, :meth:`~cryptography.x509.ocsp.OCSPResponse.revocation_time_utc`, :meth:`~cryptography.x509.ocsp.OCSPResponse.this_update_utc`, :meth:`~cryptography.x509.ocsp.OCSPResponse.next_update_utc`, :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.revocation_time_utc`, :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.this_update_utc`, :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.next_update_utc`, These are timezone-aware variants of existing properties that return naïve ``datetime`` objects. * Added :func:`~cryptography.hazmat.primitives.asymmetric.rsa.rsa_recover_private_exponent` * Added :meth:`~cryptography.hazmat.primitives.ciphers.CipherContext.reset_nonce` for altering the ``nonce`` of a cipher context without initializing a new instance. See the docs for additional restrictions. * :class:`~cryptography.x509.NameAttribute` now raises an exception when attempting to create a common name whose length is shorter or longer than :rfc:`5280` permits. * Added basic support for PKCS7 encryption (including SMIME) via :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7EnvelopeBuilder`. .. _v42-0-8: 42.0.8 - 2024-06-04 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.2.2. .. _v42-0-7: 42.0.7 - 2024-05-06 ~~~~~~~~~~~~~~~~~~~ * Restored Windows 7 compatibility for our pre-built wheels. Note that we do not test on Windows 7 and wheels for our next release will not support it. Microsoft no longer provides support for Windows 7 and users are encouraged to upgrade. .. _v42-0-6: 42.0.6 - 2024-05-04 ~~~~~~~~~~~~~~~~~~~ * Fixed compilation when using LibreSSL 3.9.1. .. _v42-0-5: 42.0.5 - 2024-02-23 ~~~~~~~~~~~~~~~~~~~ * Limit the number of name constraint checks that will be performed in :mod:`X.509 path validation ` to protect against denial of service attacks. * Upgrade ``pyo3`` version, which fixes building on PowerPC. .. _v42-0-4: 42.0.4 - 2024-02-20 ~~~~~~~~~~~~~~~~~~~ * Fixed a null-pointer-dereference and segfault that could occur when creating a PKCS#12 bundle. Credit to **Alexander-Programming** for reporting the issue. **CVE-2024-26130** * Fixed ASN.1 encoding for PKCS7/SMIME signed messages. The fields ``SMIMECapabilities`` and ``SignatureAlgorithmIdentifier`` should now be correctly encoded according to the definitions in :rfc:`2633` :rfc:`3370`. .. _v42-0-3: 42.0.3 - 2024-02-15 ~~~~~~~~~~~~~~~~~~~ * Fixed an initialization issue that caused key loading failures for some users. .. _v42-0-2: 42.0.2 - 2024-01-30 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.2.1. * Fixed an issue that prevented the use of Python buffer protocol objects in ``sign`` and ``verify`` methods on asymmetric keys. * Fixed an issue with incorrect keyword-argument naming with ``EllipticCurvePrivateKey`` :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.exchange`, ``X25519PrivateKey`` :meth:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.exchange`, ``X448PrivateKey`` :meth:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.exchange`, and ``DHPrivateKey`` :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey.exchange`. .. _v42-0-1: 42.0.1 - 2024-01-24 ~~~~~~~~~~~~~~~~~~~ * Fixed an issue with incorrect keyword-argument naming with ``EllipticCurvePrivateKey`` :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign`. * Resolved compatibility issue with loading certain RSA public keys in :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key`. .. _v42-0-0: 42.0.0 - 2024-01-22 ~~~~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.7. * **BACKWARDS INCOMPATIBLE:** Loading a PKCS7 with no content field using :func:`~cryptography.hazmat.primitives.serialization.pkcs7.load_pem_pkcs7_certificates` or :func:`~cryptography.hazmat.primitives.serialization.pkcs7.load_der_pkcs7_certificates` will now raise a ``ValueError`` rather than return an empty list. * Parsing SSH certificates no longer permits malformed critical options with values, as documented in the 41.0.2 release notes. * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.2.0. * Updated the minimum supported Rust version (MSRV) to 1.63.0, from 1.56.0. * We now publish both ``py37`` and ``py39`` ``abi3`` wheels. This should resolve some errors relating to initializing a module multiple times per process. * Support :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` for X.509 certificate signing requests and certificate revocation lists with the keyword-only argument ``rsa_padding`` on the ``sign`` methods for :class:`~cryptography.x509.CertificateSigningRequestBuilder` and :class:`~cryptography.x509.CertificateRevocationListBuilder`. * Added support for obtaining X.509 certificate signing request signature algorithm parameters (including PSS) via :meth:`~cryptography.x509.CertificateSigningRequest.signature_algorithm_parameters`. * Added support for obtaining X.509 certificate revocation list signature algorithm parameters (including PSS) via :meth:`~cryptography.x509.CertificateRevocationList.signature_algorithm_parameters`. * Added ``mgf`` property to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. * Added ``algorithm`` and ``mgf`` properties to :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP`. * Added the following properties that return timezone-aware ``datetime`` objects: :meth:`~cryptography.x509.Certificate.not_valid_before_utc`, :meth:`~cryptography.x509.Certificate.not_valid_after_utc`, :meth:`~cryptography.x509.RevokedCertificate.revocation_date_utc`, :meth:`~cryptography.x509.CertificateRevocationList.next_update_utc`, :meth:`~cryptography.x509.CertificateRevocationList.last_update_utc`. These are timezone-aware variants of existing properties that return naïve ``datetime`` objects. * Deprecated the following properties that return naïve ``datetime`` objects: :meth:`~cryptography.x509.Certificate.not_valid_before`, :meth:`~cryptography.x509.Certificate.not_valid_after`, :meth:`~cryptography.x509.RevokedCertificate.revocation_date`, :meth:`~cryptography.x509.CertificateRevocationList.next_update`, :meth:`~cryptography.x509.CertificateRevocationList.last_update` in favor of the new timezone-aware variants mentioned above. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20` on LibreSSL. * Added support for RSA PSS signatures in PKCS7 with :meth:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7SignatureBuilder.add_signer`. * In the next release (43.0.0) of cryptography, loading an X.509 certificate with a negative serial number will raise an exception. This has been deprecated since 36.0.0. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.aead.AESGCMSIV` when using OpenSSL 3.2.0+. * Added the :mod:`X.509 path validation ` APIs for :class:`~cryptography.x509.Certificate` chains. These APIs should be considered unstable and not subject to our stability guarantees until documented as such in a future release. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SM4` :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` when using OpenSSL 3.0 or greater. .. _v41-0-7: 41.0.7 - 2023-11-27 ~~~~~~~~~~~~~~~~~~~ * Fixed compilation when using LibreSSL 3.8.2. .. _v41-0-6: 41.0.6 - 2023-11-27 ~~~~~~~~~~~~~~~~~~~ * Fixed a null-pointer-dereference and segfault that could occur when loading certificates from a PKCS#7 bundle. Credit to **pkuzco** for reporting the issue. **CVE-2023-49083** .. _v41-0-5: 41.0.5 - 2023-10-24 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.4. * Added a function to support an upcoming ``pyOpenSSL`` release. .. _v41-0-4: 41.0.4 - 2023-09-19 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.3. .. _v41-0-3: 41.0.3 - 2023-08-01 ~~~~~~~~~~~~~~~~~~~ * Fixed performance regression loading DH public keys. * Fixed a memory leak when using :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`. * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.2. .. _v41-0-2: 41.0.2 - 2023-07-10 ~~~~~~~~~~~~~~~~~~~ * Fixed bugs in creating and parsing SSH certificates where critical options with values were handled incorrectly. Certificates are now created correctly and parsing accepts correct values as well as the previously generated invalid forms with a warning. In the next release, support for parsing these invalid forms will be removed. .. _v41-0-1: 41.0.1 - 2023-06-01 ~~~~~~~~~~~~~~~~~~~ * Temporarily allow invalid ECDSA signature algorithm parameters in X.509 certificates, which are generated by older versions of Java. * Allow null bytes in pass phrases when serializing private keys. .. _v41-0-0: 41.0.0 - 2023-05-30 ~~~~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for OpenSSL less than 1.1.1d has been removed. Users on older version of OpenSSL will need to upgrade. * **BACKWARDS INCOMPATIBLE:** Support for Python 3.6 has been removed. * **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.6. * Updated the minimum supported Rust version (MSRV) to 1.56.0, from 1.48.0. * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.1. * Added support for the :class:`~cryptography.x509.OCSPAcceptableResponses` OCSP extension. * Added support for the :class:`~cryptography.x509.MSCertificateTemplate` proprietary Microsoft certificate extension. * Implemented support for equality checks on all asymmetric public key types. * Added support for ``aes256-gcm@openssh.com`` encrypted keys in :func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key`. * Added support for obtaining X.509 certificate signature algorithm parameters (including PSS) via :meth:`~cryptography.x509.Certificate.signature_algorithm_parameters`. * Support signing :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` X.509 certificates via the new keyword-only argument ``rsa_padding`` on :meth:`~cryptography.x509.CertificateBuilder.sign`. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` on BoringSSL. .. _v40-0-2: 40.0.2 - 2023-04-14 ~~~~~~~~~~~~~~~~~~~ * Fixed compilation when using LibreSSL 3.7.2. * Added some functions to support an upcoming ``pyOpenSSL`` release. .. _v40-0-1: 40.0.1 - 2023-03-24 ~~~~~~~~~~~~~~~~~~~ * Fixed a bug where certain operations would fail if an object happened to be in the top-half of the memory-space. This only impacted 32-bit systems. .. _v40-0-0: 40.0.0 - 2023-03-24 ~~~~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** As announced in the 39.0.0 changelog, the way ``cryptography`` links OpenSSL has changed. This only impacts users who build ``cryptography`` from source (i.e., not from a ``wheel``), and specify their own version of OpenSSL. For those users, the ``CFLAGS``, ``LDFLAGS``, ``INCLUDE``, ``LIB``, and ``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS`` environment variables are no longer valid. Instead, users need to configure their builds `as documented here`_. * Support for Python 3.6 is deprecated and will be removed in the next release. * Deprecated the current minimum supported Rust version (MSRV) of 1.48.0. In the next release we will raise MSRV to 1.56.0. Users with the latest ``pip`` will typically get a wheel and not need Rust installed, but check :doc:`/installation` for documentation on installing a newer ``rustc`` if required. * Deprecated support for OpenSSL less than 1.1.1d. The next release of ``cryptography`` will drop support for older versions. * Deprecated support for DSA keys in :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` and :func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key`. * Deprecated support for OpenSSH serialization in :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` and :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. * The minimum supported version of PyPy3 is now 7.3.10. * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.1.0. * Added support for parsing SSH certificates in addition to public keys with :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_identity`. :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` continues to support only public keys. * Added support for generating SSH certificates with :class:`~cryptography.hazmat.primitives.serialization.SSHCertificateBuilder`. * Added :meth:`~cryptography.x509.Certificate.verify_directly_issued_by` to :class:`~cryptography.x509.Certificate`. * Added a check to :class:`~cryptography.x509.NameConstraints` to ensure that :class:`~cryptography.x509.DNSName` constraints do not contain any ``*`` wildcards. * Removed many unused CFFI OpenSSL bindings. This will not impact you unless you are using ``cryptography`` to directly invoke OpenSSL's C API. Note that these have never been considered a stable, supported, public API by ``cryptography``, this note is included as a courtesy. * The X.509 builder classes now raise ``UnsupportedAlgorithm`` instead of ``ValueError`` if an unsupported hash algorithm is passed. * Added public union type aliases for type hinting: * Asymmetric types: :const:`~cryptography.hazmat.primitives.asymmetric.types.PublicKeyTypes`, :const:`~cryptography.hazmat.primitives.asymmetric.types.PrivateKeyTypes`, :const:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`, :const:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPublicKeyTypes`, :const:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. * SSH keys: :const:`~cryptography.hazmat.primitives.serialization.SSHPublicKeyTypes`, :const:`~cryptography.hazmat.primitives.serialization.SSHPrivateKeyTypes`, :const:`~cryptography.hazmat.primitives.serialization.SSHCertPublicKeyTypes`, :const:`~cryptography.hazmat.primitives.serialization.SSHCertPrivateKeyTypes`. * PKCS12: :const:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12PrivateKeyTypes` * PKCS7: :const:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7HashTypes`, :const:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7PrivateKeyTypes`. * Two-factor: :const:`~cryptography.hazmat.primitives.twofactor.hotp.HOTPHashTypes` * Deprecated previously undocumented but not private type aliases in the ``cryptography.hazmat.primitives.asymmetric.types`` module in favor of new ones above. .. _v39-0-2: 39.0.2 - 2023-03-02 ~~~~~~~~~~~~~~~~~~~ * Fixed a bug where the content type header was not properly encoded for PKCS7 signatures when using the ``Text`` option and ``SMIME`` encoding. .. _v39-0-1: 39.0.1 - 2023-02-07 ~~~~~~~~~~~~~~~~~~~ * **SECURITY ISSUE** - Fixed a bug where ``Cipher.update_into`` accepted Python buffer protocol objects, but allowed immutable buffers. **CVE-2023-23931** * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.8. .. _v39-0-0: 39.0.0 - 2023-01-01 ~~~~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for OpenSSL 1.1.0 has been removed. Users on older version of OpenSSL will need to upgrade. * **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.5. The new minimum LibreSSL version is 3.5.0. Going forward our policy is to support versions of LibreSSL that are available in versions of OpenBSD that are still receiving security support. * **BACKWARDS INCOMPATIBLE:** Removed the ``encode_point`` and ``from_encoded_point`` methods on :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers`, which had been deprecated for several years. :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes` and :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point` should be used instead. * **BACKWARDS INCOMPATIBLE:** Support for using MD5 or SHA1 in :class:`~cryptography.x509.CertificateBuilder`, other X.509 builders, and PKCS7 has been removed. * **BACKWARDS INCOMPATIBLE:** Dropped support for macOS 10.10 and 10.11, macOS users must upgrade to 10.12 or newer. * **ANNOUNCEMENT:** The next version of ``cryptography`` (40.0) will change the way we link OpenSSL. This will only impact users who build ``cryptography`` from source (i.e., not from a ``wheel``), and specify their own version of OpenSSL. For those users, the ``CFLAGS``, ``LDFLAGS``, ``INCLUDE``, ``LIB``, and ``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS`` environment variables will no longer be respected. Instead, users will need to configure their builds `as documented here`_. * Added support for :ref:`disabling the legacy provider in OpenSSL 3.0.x`. * Added support for disabling RSA key validation checks when loading RSA keys via :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`, :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`, and :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers.private_key`. This speeds up key loading but is :term:`unsafe` if you are loading potentially attacker supplied keys. * Significantly improved performance for :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` when repeatedly calling ``encrypt`` or ``decrypt`` with the same key. * Added support for creating OCSP requests with precomputed hashes using :meth:`~cryptography.x509.ocsp.OCSPRequestBuilder.add_certificate_by_hash`. * Added support for loading multiple PEM-encoded X.509 certificates from a single input via :func:`~cryptography.x509.load_pem_x509_certificates`. .. _v38-0-4: 38.0.4 - 2022-11-27 ~~~~~~~~~~~~~~~~~~~ * Fixed compilation when using LibreSSL 3.6.0. * Fixed error when using ``py2app`` to build an application with a ``cryptography`` dependency. .. _v38-0-3: 38.0.3 - 2022-11-01 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.7, which resolves *CVE-2022-3602* and *CVE-2022-3786*. .. _v38-0-2: 38.0.2 - 2022-10-11 (YANKED) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. attention:: This release was subsequently yanked from PyPI due to a regression in OpenSSL. * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.6. .. _v38-0-1: 38.0.1 - 2022-09-07 ~~~~~~~~~~~~~~~~~~~ * Fixed parsing TLVs in ASN.1 with length greater than 65535 bytes (typically seen in large CRLs). .. _v38-0-0: 38.0.0 - 2022-09-06 ~~~~~~~~~~~~~~~~~~~ * Final deprecation of OpenSSL 1.1.0. The next release of ``cryptography`` will drop support. * We no longer ship ``manylinux2010`` wheels. Users should upgrade to the latest ``pip`` to ensure this doesn't cause issues downloading wheels on their platform. We now ship ``manylinux_2_28`` wheels for users on new enough platforms. * Updated the minimum supported Rust version (MSRV) to 1.48.0, from 1.41.0. Users with the latest ``pip`` will typically get a wheel and not need Rust installed, but check :doc:`/installation` for documentation on installing a newer ``rustc`` if required. * :meth:`~cryptography.fernet.Fernet.decrypt` and related methods now accept both ``str`` and ``bytes`` tokens. * Parsing ``CertificateSigningRequest`` restores the behavior of enforcing that the ``Extension`` ``critical`` field must be correctly encoded DER. See `the issue `_ for complete details. * Added two new OpenSSL functions to the bindings to support an upcoming ``pyOpenSSL`` release. * When parsing :class:`~cryptography.x509.CertificateRevocationList` and :class:`~cryptography.x509.CertificateSigningRequest` values, it is now enforced that the ``version`` value in the input must be valid according to the rules of :rfc:`2986` and :rfc:`5280`. * Using MD5 or SHA1 in :class:`~cryptography.x509.CertificateBuilder` and other X.509 builders is deprecated and support will be removed in the next version. * Added additional APIs to :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp`, including :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.signature_hash_algorithm`, :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.signature_algorithm`, :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.signature`, and :attr:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp.extension_bytes`. * Added :attr:`~cryptography.x509.Certificate.tbs_precertificate_bytes`, allowing users to access the to-be-signed pre-certificate data needed for signed certificate timestamp verification. * :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFHMAC` and :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFCMAC` now support :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed` counter location. * Fixed :rfc:`4514` name parsing to reverse the order of the RDNs according to the section 2.1 of the RFC, affecting method :meth:`~cryptography.x509.Name.from_rfc4514_string`. * It is now possible to customize some aspects of encryption when serializing private keys, using :meth:`~cryptography.hazmat.primitives.serialization.PrivateFormat.encryption_builder`. * Removed several legacy symbols from our OpenSSL bindings. Users of pyOpenSSL versions older than 22.0 will need to upgrade. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES128` and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES256` classes. These classes do not replace :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` (which allows all AES key lengths), but are intended for applications where developers want to be explicit about key length. .. _v37-0-4: 37.0.4 - 2022-07-05 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.5. .. _v37-0-3: 37.0.3 - 2022-06-21 (YANKED) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. attention:: This release was subsequently yanked from PyPI due to a regression in OpenSSL. * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.4. .. _v37-0-2: 37.0.2 - 2022-05-03 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.3. * Added a constant needed for an upcoming pyOpenSSL release. .. _v37-0-1: 37.0.1 - 2022-04-27 ~~~~~~~~~~~~~~~~~~~ * Fixed an issue where parsing an encrypted private key with the public loader functions would hang waiting for console input on OpenSSL 3.0.x rather than raising an error. * Restored some legacy symbols for older ``pyOpenSSL`` users. These will be removed again in the future, so ``pyOpenSSL`` users should still upgrade to the latest version of that package when they upgrade ``cryptography``. .. _v37-0-0: 37.0.0 - 2022-04-26 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.2. * **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL 2.9.x and 3.0.x. The new minimum LibreSSL version is 3.1+. * **BACKWARDS INCOMPATIBLE:** Removed ``signer`` and ``verifier`` methods from the public key and private key classes. These methods were originally deprecated in version 2.0, but had an extended deprecation timeline due to usage. Any remaining users should transition to ``sign`` and ``verify``. * Deprecated OpenSSL 1.1.0 support. OpenSSL 1.1.0 is no longer supported by the OpenSSL project. The next release of ``cryptography`` will be the last to support compiling with OpenSSL 1.1.0. * Deprecated Python 3.6 support. Python 3.6 is no longer supported by the Python core team. Support for Python 3.6 will be removed in a future ``cryptography`` release. * Deprecated the current minimum supported Rust version (MSRV) of 1.41.0. In the next release we will raise MSRV to 1.48.0. Users with the latest ``pip`` will typically get a wheel and not need Rust installed, but check :doc:`/installation` for documentation on installing a newer ``rustc`` if required. * Deprecated :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5`, :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SEED`, :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA`, and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.Blowfish` because they are legacy algorithms with extremely low usage. These will be removed in a future version of ``cryptography``. * Added limited support for distinguished names containing a bit string. * We now ship ``universal2`` wheels on macOS, which contain both ``arm64`` and ``x86_64`` architectures. Users on macOS should upgrade to the latest ``pip`` to ensure they can use this wheel, although we will continue to ship ``x86_64`` specific wheels for now to ease the transition. * This will be the final release for which we ship ``manylinux2010`` wheels. Going forward the minimum supported ``manylinux`` ABI for our wheels will be ``manylinux2014``. The vast majority of users will continue to receive ``manylinux`` wheels provided they have an up to date ``pip``. For PyPy wheels this release already requires ``manylinux2014`` for compatibility with binaries distributed by upstream. * Added support for multiple :class:`~cryptography.x509.ocsp.OCSPSingleResponse` in a :class:`~cryptography.x509.ocsp.OCSPResponse`. * Restored support for signing certificates and other structures in :doc:`/x509/index` with SHA3 hash algorithms. * :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` is disabled in FIPS mode. * Added support for serialization of PKCS#12 CA friendly names/aliases in :func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates` * Added support for 12-15 byte (96 to 120 bit) nonces to :class:`~cryptography.hazmat.primitives.ciphers.aead.AESOCB3`. This class previously supported only 12 byte (96 bit). * Added support for :class:`~cryptography.hazmat.primitives.ciphers.aead.AESSIV` when using OpenSSL 3.0.0+. * Added support for serializing PKCS7 structures from a list of certificates with :class:`~cryptography.hazmat.primitives.serialization.pkcs7.serialize_certificates`. * Added support for parsing :rfc:`4514` strings with :meth:`~cryptography.x509.Name.from_rfc4514_string`. * Added :attr:`~cryptography.hazmat.primitives.asymmetric.padding.PSS.AUTO` to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. This can be used to verify a signature where the salt length is not already known. * Added :attr:`~cryptography.hazmat.primitives.asymmetric.padding.PSS.DIGEST_LENGTH` to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. This constant will set the salt length to the same length as the ``PSS`` hash algorithm. * Added support for loading RSA-PSS key types with :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key` and :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`. This functionality is limited to OpenSSL 1.1.1e+ and loads the key as a normal RSA private key, discarding the PSS constraint information. .. _v36-0-2: 36.0.2 - 2022-03-15 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 1.1.1n. .. _v36-0-1: 36.0.1 - 2021-12-14 ~~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 1.1.1m. .. _v36-0-0: 36.0.0 - 2021-11-21 ~~~~~~~~~~~~~~~~~~~ * **FINAL DEPRECATION** Support for ``verifier`` and ``signer`` on our asymmetric key classes was deprecated in version 2.0. These functions had an extended deprecation due to usage, however the next version of ``cryptography`` will drop support. Users should migrate to ``sign`` and ``verify``. * The entire :doc:`/x509/index` layer is now written in Rust. This allows alternate asymmetric key implementations that can support cloud key management services or hardware security modules provided they implement the necessary interface (for example: :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`). * :ref:`Deprecated the backend argument` for all functions. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.aead.AESOCB3`. * Added support for iterating over arbitrary request :attr:`~cryptography.x509.CertificateSigningRequest.attributes`. * Deprecated the ``get_attribute_for_oid`` method on :class:`~cryptography.x509.CertificateSigningRequest` in favor of :meth:`~cryptography.x509.Attributes.get_attribute_for_oid` on the new :class:`~cryptography.x509.Attributes` object. * Fixed handling of PEM files to allow loading when certificate and key are in the same file. * Fixed parsing of :class:`~cryptography.x509.CertificatePolicies` extensions containing legacy ``BMPString`` values in their ``explicitText``. * Allow parsing of negative serial numbers in certificates. Negative serial numbers are prohibited by :rfc:`5280` so a deprecation warning will be raised whenever they are encountered. A future version of ``cryptography`` will drop support for parsing them. * Added support for parsing PKCS12 files with friendly names for all certificates with :func:`~cryptography.hazmat.primitives.serialization.pkcs12.load_pkcs12`, which will return an object of type :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates`. * :meth:`~cryptography.x509.Name.rfc4514_string` and related methods now have an optional ``attr_name_overrides`` parameter to supply custom OID to name mappings, which can be used to match vendor-specific extensions. * **BACKWARDS INCOMPATIBLE:** Reverted the nonstandard formatting of email address fields as ``E`` in :meth:`~cryptography.x509.Name.rfc4514_string` methods from version 35.0. The previous behavior can be restored with: ``name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"})`` * Allow :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey` and :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey` to be used as public keys when parsing certificates or creating them with :class:`~cryptography.x509.CertificateBuilder`. These key types must be signed with a different signing algorithm as ``X25519`` and ``X448`` do not support signing. * Extension values can now be serialized to a DER byte string by calling :func:`~cryptography.x509.ExtensionType.public_bytes`. * Added experimental support for compiling against BoringSSL. As BoringSSL does not commit to a stable API, ``cryptography`` tests against the latest commit only. Please note that several features are not available when building against BoringSSL. * Parsing ``CertificateSigningRequest`` from DER and PEM now, for a limited time period, allows the ``Extension`` ``critical`` field to be incorrectly encoded. See `the issue `_ for complete details. This will be reverted in a future ``cryptography`` release. * When :class:`~cryptography.x509.OCSPNonce` are parsed and generated their value is now correctly wrapped in an ASN.1 ``OCTET STRING``. This conforms to :rfc:`6960` but conflicts with the original behavior specified in :rfc:`2560`. For a temporary period for backwards compatibility, we will also parse values that are encoded as specified in :rfc:`2560` but this behavior will be removed in a future release. .. _v35-0-0: 35.0.0 - 2021-09-29 ~~~~~~~~~~~~~~~~~~~ * Changed the :ref:`version scheme `. This will result in us incrementing the major version more frequently, but does not change our existing backwards compatibility policy. * **BACKWARDS INCOMPATIBLE:** The :doc:`/x509/index` PEM parsers now require that the PEM string passed have PEM delimiters of the correct type. For example, parsing a private key PEM concatenated with a certificate PEM will no longer be accepted by the PEM certificate parser. * **BACKWARDS INCOMPATIBLE:** The X.509 certificate parser no longer allows negative serial numbers. :rfc:`5280` has always prohibited these. * **BACKWARDS INCOMPATIBLE:** Additional forms of invalid ASN.1 found during :doc:`/x509/index` parsing will raise an error on initial parse rather than when the malformed field is accessed. * Rust is now required for building ``cryptography``, the ``CRYPTOGRAPHY_DONT_BUILD_RUST`` environment variable is no longer respected. * Parsers for :doc:`/x509/index` no longer use OpenSSL and have been rewritten in Rust. This should be backwards compatible (modulo the items listed above) and improve both security and performance. * Added support for OpenSSL 3.0.0 as a compilation target. * Added support for :class:`~cryptography.hazmat.primitives.hashes.SM3` and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SM4`, when using OpenSSL 1.1.1. These algorithms are provided for compatibility in regions where they may be required, and are not generally recommended. * We now ship ``manylinux_2_24`` and ``musllinux_1_1`` wheels, in addition to our ``manylinux2010`` and ``manylinux2014`` wheels. Users on distributions like Alpine Linux should ensure they upgrade to the latest ``pip`` to correctly receive wheels. * Added ``rfc4514_attribute_name`` attribute to :attr:`x509.NameAttribute `. * Added :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFCMAC`. .. _v3-4-8: 3.4.8 - 2021-08-24 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1l. .. _v3-4-7: 3.4.7 - 2021-03-25 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1k. .. _v3-4-6: 3.4.6 - 2021-02-16 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1j. .. _v3-4-5: 3.4.5 - 2021-02-13 ~~~~~~~~~~~~~~~~~~ * Various improvements to type hints. * Lower the minimum supported Rust version (MSRV) to >=1.41.0. This change improves compatibility with system-provided Rust on several Linux distributions. * ``cryptography`` will be switching to a new versioning scheme with its next feature release. More information is available in our :doc:`/api-stability` documentation. .. _v3-4-4: 3.4.4 - 2021-02-09 ~~~~~~~~~~~~~~~~~~ * Added a ``py.typed`` file so that ``mypy`` will know to use our type annotations. * Fixed an import cycle that could be triggered by certain import sequences. .. _v3-4-3: 3.4.3 - 2021-02-08 ~~~~~~~~~~~~~~~~~~ * Specify our supported Rust version (>=1.45.0) in our ``setup.py`` so users on older versions will get a clear error message. .. _v3-4-2: 3.4.2 - 2021-02-08 ~~~~~~~~~~~~~~~~~~ * Improvements to make the rust transition a bit easier. This includes some better error messages and small dependency fixes. If you experience installation problems **Be sure to update pip** first, then check the :doc:`FAQ `. .. _v3-4-1: 3.4.1 - 2021-02-07 ~~~~~~~~~~~~~~~~~~ * Fixed a circular import issue. * Added additional debug output to assist users seeing installation errors due to outdated ``pip`` or missing ``rustc``. .. _v3-4: 3.4 - 2021-02-07 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for Python 2 has been removed. * We now ship ``manylinux2014`` wheels and no longer ship ``manylinux1`` wheels. Users should upgrade to the latest ``pip`` to ensure this doesn't cause issues downloading wheels on their platform. * ``cryptography`` now incorporates Rust code. Users building ``cryptography`` themselves will need to have the Rust toolchain installed. Users who use an officially produced wheel will not need to make any changes. The minimum supported Rust version is 1.45.0. * ``cryptography`` now has :pep:`484` type hints on nearly all of of its public APIs. Users can begin using them to type check their code with ``mypy``. .. _v3-3-2: 3.3.2 - 2021-02-07 ~~~~~~~~~~~~~~~~~~ * **SECURITY ISSUE:** Fixed a bug where certain sequences of ``update()`` calls when symmetrically encrypting very large payloads (>2GB) could result in an integer overflow, leading to buffer overflows. *CVE-2020-36242* **Update:** This fix is a workaround for *CVE-2021-23840* in OpenSSL, fixed in OpenSSL 1.1.1j. .. _v3-3-1: 3.3.1 - 2020-12-09 ~~~~~~~~~~~~~~~~~~ * Re-added a legacy symbol causing problems for older ``pyOpenSSL`` users. .. _v3-3: 3.3 - 2020-12-08 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for Python 3.5 has been removed due to low usage and maintenance burden. * **BACKWARDS INCOMPATIBLE:** The :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` and :class:`~cryptography.hazmat.primitives.ciphers.aead.AESGCM` now require 64-bit to 1024-bit (8 byte to 128 byte) initialization vectors. This change is to conform with an upcoming OpenSSL release that will no longer support sizes outside this window. * **BACKWARDS INCOMPATIBLE:** When deserializing asymmetric keys we now raise ``ValueError`` rather than ``UnsupportedAlgorithm`` when an unsupported cipher is used. This change is to conform with an upcoming OpenSSL release that will no longer distinguish between error types. * **BACKWARDS INCOMPATIBLE:** We no longer allow loading of finite field Diffie-Hellman parameters of less than 512 bits in length. This change is to conform with an upcoming OpenSSL release that no longer supports smaller sizes. These keys were already wildly insecure and should not have been used in any application outside of testing. * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1i. * Python 2 support is deprecated in ``cryptography``. This is the last release that will support Python 2. * Added the :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.recover_data_from_signature` function to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` for recovering the signed data from an RSA signature. .. _v3-2-1: 3.2.1 - 2020-10-27 ~~~~~~~~~~~~~~~~~~ * Disable blinding on RSA public keys to address an error with some versions of OpenSSL. .. _v3-2: 3.2 - 2020-10-25 ~~~~~~~~~~~~~~~~ * **SECURITY ISSUE:** Attempted to make RSA PKCS#1v1.5 decryption more constant time, to protect against Bleichenbacher vulnerabilities. Due to limitations imposed by our API, we cannot completely mitigate this vulnerability and a future release will contain a new API which is designed to be resilient to these for contexts where it is required. Credit to **Hubert Kario** for reporting the issue. *CVE-2020-25659* * Support for OpenSSL 1.0.2 has been removed. Users on older version of OpenSSL will need to upgrade. * Added basic support for PKCS7 signing (including SMIME) via :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7SignatureBuilder`. .. _v3-1-1: 3.1.1 - 2020-09-22 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1h. .. _v3-1: 3.1 - 2020-08-26 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Removed support for ``idna`` based :term:`U-label` parsing in various X.509 classes. This support was originally deprecated in version 2.1 and moved to an extra in 2.5. * Deprecated OpenSSL 1.0.2 support. OpenSSL 1.0.2 is no longer supported by the OpenSSL project. The next version of ``cryptography`` will drop support for it. * Deprecated support for Python 3.5. This version sees very little use and will be removed in the next release. * ``backend`` arguments to functions are no longer required and the default backend will automatically be selected if no ``backend`` is provided. * Added initial support for parsing certificates from PKCS7 files with :func:`~cryptography.hazmat.primitives.serialization.pkcs7.load_pem_pkcs7_certificates` and :func:`~cryptography.hazmat.primitives.serialization.pkcs7.load_der_pkcs7_certificates` . * Calling ``update`` or ``update_into`` on :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` with ``data`` longer than 2\ :sup:`31` bytes no longer raises an ``OverflowError``. This also resolves the same issue in :doc:`/fernet`. .. _v3-0: 3.0 - 2020-07-20 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Removed support for passing an :class:`~cryptography.x509.Extension` instance to :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier`, as per our deprecation policy. * **BACKWARDS INCOMPATIBLE:** Support for LibreSSL 2.7.x, 2.8.x, and 2.9.0 has been removed (2.9.1+ is still supported). * **BACKWARDS INCOMPATIBLE:** Dropped support for macOS 10.9, macOS users must upgrade to 10.10 or newer. * **BACKWARDS INCOMPATIBLE:** RSA :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key` no longer accepts ``public_exponent`` values except 65537 and 3 (the latter for legacy purposes). * **BACKWARDS INCOMPATIBLE:** X.509 certificate parsing now enforces that the ``version`` field contains a valid value, rather than deferring this check until :attr:`~cryptography.x509.Certificate.version` is accessed. * Deprecated support for Python 2. At the time there is no time table for actually dropping support, however we strongly encourage all users to upgrade their Python, as Python 2 no longer receives support from the Python core team. If you have trouble suppressing this warning in tests view the :ref:`FAQ entry addressing this issue `. * Added support for ``OpenSSH`` serialization format for ``ec``, ``ed25519``, ``rsa`` and ``dsa`` private keys: :func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key` for loading and :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.OpenSSH` for writing. * Added support for ``OpenSSH`` certificates to :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key`. * Added :meth:`~cryptography.fernet.Fernet.encrypt_at_time` and :meth:`~cryptography.fernet.Fernet.decrypt_at_time` to :class:`~cryptography.fernet.Fernet`. * Added support for the :class:`~cryptography.x509.SubjectInformationAccess` X.509 extension. * Added support for parsing :class:`~cryptography.x509.SignedCertificateTimestamps` in OCSP responses. * Added support for parsing attributes in certificate signing requests via ``CertificateSigningRequest.get_attribute_for_oid``. * Added support for encoding attributes in certificate signing requests via :meth:`~cryptography.x509.CertificateSigningRequestBuilder.add_attribute`. * On OpenSSL 1.1.1d and higher ``cryptography`` now uses OpenSSL's built-in CSPRNG instead of its own OS random engine because these versions of OpenSSL properly reseed on fork. * Added initial support for creating PKCS12 files with :func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates`. .. _v2-9-2: 2.9.2 - 2020-04-22 ~~~~~~~~~~~~~~~~~~ * Updated the macOS wheel to fix an issue where it would not run on macOS versions older than 10.15. .. _v2-9-1: 2.9.1 - 2020-04-21 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1g. .. _v2-9: 2.9 - 2020-04-02 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for Python 3.4 has been removed due to low usage and maintenance burden. * **BACKWARDS INCOMPATIBLE:** Support for OpenSSL 1.0.1 has been removed. Users on older version of OpenSSL will need to upgrade. * **BACKWARDS INCOMPATIBLE:** Support for LibreSSL 2.6.x has been removed. * Removed support for calling :meth:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey.public_bytes` with no arguments, as per our deprecation policy. You must now pass ``encoding`` and ``format``. * **BACKWARDS INCOMPATIBLE:** Reversed the order in which :meth:`~cryptography.x509.Name.rfc4514_string` returns the RDNs as required by :rfc:`4514`. * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1f. * Added support for parsing :attr:`~cryptography.x509.ocsp.OCSPResponse.single_extensions` in an OCSP response. * :class:`~cryptography.x509.NameAttribute` values can now be empty strings. .. _v2-8: 2.8 - 2019-10-16 ~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux`` wheels to be compiled with OpenSSL 1.1.1d. * Added support for Python 3.8. * Added class methods :meth:`Poly1305.generate_tag ` and :meth:`Poly1305.verify_tag ` for Poly1305 sign and verify operations. * Deprecated support for OpenSSL 1.0.1. Support will be removed in ``cryptography`` 2.9. * We now ship ``manylinux2010`` wheels in addition to our ``manylinux1`` wheels. * Added support for ``ed25519`` and ``ed448`` keys in the :class:`~cryptography.x509.CertificateBuilder`, :class:`~cryptography.x509.CertificateSigningRequestBuilder`, :class:`~cryptography.x509.CertificateRevocationListBuilder` and :class:`~cryptography.x509.ocsp.OCSPResponseBuilder`. * ``cryptography`` no longer depends on ``asn1crypto``. * :class:`~cryptography.x509.FreshestCRL` is now allowed as a :class:`~cryptography.x509.CertificateRevocationList` extension. .. _v2-7: 2.7 - 2019-05-30 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** We no longer distribute 32-bit ``manylinux1`` wheels. Continuing to produce them was a maintenance burden. * **BACKWARDS INCOMPATIBLE:** Removed the ``cryptography.hazmat.primitives.mac.MACContext`` interface. The ``CMAC`` and ``HMAC`` APIs have not changed, but they are no longer registered as ``MACContext`` instances. * Updated Windows, macOS, and ``manylinux1`` wheels to be compiled with OpenSSL 1.1.1c. * Removed support for running our tests with ``setup.py test``. Users interested in running our tests can continue to follow the directions in our :doc:`development documentation`. * Add support for :class:`~cryptography.hazmat.primitives.poly1305.Poly1305` when using OpenSSL 1.1.1 or newer. * Support serialization with ``Encoding.OpenSSH`` and ``PublicFormat.OpenSSH`` in :meth:`Ed25519PublicKey.public_bytes ` . * Correctly allow passing a ``SubjectKeyIdentifier`` to :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier` and deprecate passing an ``Extension`` object. The documentation always required ``SubjectKeyIdentifier`` but the implementation previously required an ``Extension``. .. _v2-6-1: 2.6.1 - 2019-02-27 ~~~~~~~~~~~~~~~~~~ * Resolved an error in our build infrastructure that broke our Python3 wheels for macOS and Linux. .. _v2-6: 2.6 - 2019-02-27 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Removed ``cryptography.hazmat.primitives.asymmetric.utils.encode_rfc6979_signature`` and ``cryptography.hazmat.primitives.asymmetric.utils.decode_rfc6979_signature``, which had been deprecated for nearly 4 years. Use :func:`~cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature` and :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature` instead. * **BACKWARDS INCOMPATIBLE**: Removed ``cryptography.x509.Certificate.serial``, which had been deprecated for nearly 3 years. Use :attr:`~cryptography.x509.Certificate.serial_number` instead. * Updated Windows, macOS, and ``manylinux1`` wheels to be compiled with OpenSSL 1.1.1b. * Added support for :doc:`/hazmat/primitives/asymmetric/ed448` when using OpenSSL 1.1.1b or newer. * Added support for :doc:`/hazmat/primitives/asymmetric/ed25519` when using OpenSSL 1.1.1b or newer. * :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` can now load ``ed25519`` public keys. * Add support for easily mapping an object identifier to its elliptic curve class via :func:`~cryptography.hazmat.primitives.asymmetric.ec.get_curve_for_oid`. * Add support for OpenSSL when compiled with the ``no-engine`` (``OPENSSL_NO_ENGINE``) flag. .. _v2-5: 2.5 - 2019-01-22 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** :term:`U-label` strings were deprecated in version 2.1, but this version removes the default ``idna`` dependency as well. If you still need this deprecated path please install cryptography with the ``idna`` extra: ``pip install cryptography[idna]``. * **BACKWARDS INCOMPATIBLE:** The minimum supported PyPy version is now 5.4. * Numerous classes and functions have been updated to allow :term:`bytes-like` types for keying material and passwords, including symmetric algorithms, AEAD ciphers, KDFs, loading asymmetric keys, and one time password classes. * Updated Windows, macOS, and ``manylinux1`` wheels to be compiled with OpenSSL 1.1.1a. * Added support for :class:`~cryptography.hazmat.primitives.hashes.SHA512_224` and :class:`~cryptography.hazmat.primitives.hashes.SHA512_256` when using OpenSSL 1.1.1. * Added support for :class:`~cryptography.hazmat.primitives.hashes.SHA3_224`, :class:`~cryptography.hazmat.primitives.hashes.SHA3_256`, :class:`~cryptography.hazmat.primitives.hashes.SHA3_384`, and :class:`~cryptography.hazmat.primitives.hashes.SHA3_512` when using OpenSSL 1.1.1. * Added support for :doc:`/hazmat/primitives/asymmetric/x448` when using OpenSSL 1.1.1. * Added support for :class:`~cryptography.hazmat.primitives.hashes.SHAKE128` and :class:`~cryptography.hazmat.primitives.hashes.SHAKE256` when using OpenSSL 1.1.1. * Added initial support for parsing PKCS12 files with :func:`~cryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates`. * Added support for :class:`~cryptography.x509.IssuingDistributionPoint`. * Added ``rfc4514_string()`` method to :meth:`x509.Name `, :meth:`x509.RelativeDistinguishedName `, and :meth:`x509.NameAttribute ` to format the name or component an :rfc:`4514` Distinguished Name string. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point`, which immediately checks if the point is on the curve and supports compressed points. Deprecated the previous method ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point``. * Added :attr:`~cryptography.x509.ocsp.OCSPResponse.signature_hash_algorithm` to ``OCSPResponse``. * Updated :doc:`/hazmat/primitives/asymmetric/x25519` support to allow additional serialization methods. Calling :meth:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey.public_bytes` with no arguments has been deprecated. * Added support for encoding compressed and uncompressed points via :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes`. Deprecated the previous method ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point``. .. _v2-4-2: 2.4.2 - 2018-11-21 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux1`` wheels to be compiled with OpenSSL 1.1.0j. .. _v2-4-1: 2.4.1 - 2018-11-11 ~~~~~~~~~~~~~~~~~~ * Fixed a build breakage in our ``manylinux1`` wheels. .. _v2-4: 2.4 - 2018-11-11 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL 2.4.x. * Deprecated OpenSSL 1.0.1 support. OpenSSL 1.0.1 is no longer supported by the OpenSSL project. At this time there is no time table for dropping support, however we strongly encourage all users to upgrade or install ``cryptography`` from a wheel. * Added initial :doc:`OCSP ` support. * Added support for :class:`~cryptography.x509.PrecertPoison`. .. _v2-3-1: 2.3.1 - 2018-08-14 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux1`` wheels to be compiled with OpenSSL 1.1.0i. .. _v2-3: 2.3 - 2018-07-18 ~~~~~~~~~~~~~~~~ * **SECURITY ISSUE:** :meth:`~cryptography.hazmat.primitives.ciphers.AEADDecryptionContext.finalize_with_tag` allowed tag truncation by default which can allow tag forgery in some cases. The method now enforces the ``min_tag_length`` provided to the :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` constructor. *CVE-2018-10903* * Added support for Python 3.7. * Added :meth:`~cryptography.fernet.Fernet.extract_timestamp` to get the authenticated timestamp of a :doc:`Fernet ` token. * Support for Python 2.7.x without ``hmac.compare_digest`` has been deprecated. We will require Python 2.7.7 or higher (or 2.7.6 on Ubuntu) in the next ``cryptography`` release. * Fixed multiple issues preventing ``cryptography`` from compiling against LibreSSL 2.7.x. * Added :class:`~cryptography.x509.CertificateRevocationList.get_revoked_certificate_by_serial_number` for quick serial number searches in CRLs. * The :class:`~cryptography.x509.RelativeDistinguishedName` class now preserves the order of attributes. Duplicate attributes now raise an error instead of silently discarding duplicates. * :func:`~cryptography.hazmat.primitives.keywrap.aes_key_unwrap` and :func:`~cryptography.hazmat.primitives.keywrap.aes_key_unwrap_with_padding` now raise :class:`~cryptography.hazmat.primitives.keywrap.InvalidUnwrap` if the wrapped key is an invalid length, instead of ``ValueError``. .. _v2-2-2: 2.2.2 - 2018-03-27 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux1`` wheels to be compiled with OpenSSL 1.1.0h. .. _v2-2-1: 2.2.1 - 2018-03-20 ~~~~~~~~~~~~~~~~~~ * Reverted a change to ``GeneralNames`` which prohibited having zero elements, due to breakages. * Fixed a bug in :func:`~cryptography.hazmat.primitives.keywrap.aes_key_unwrap_with_padding` that caused it to raise ``InvalidUnwrap`` when key length modulo 8 was zero. .. _v2-2: 2.2 - 2018-03-19 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for Python 2.6 has been dropped. * Resolved a bug in ``HKDF`` that incorrectly constrained output size. * Added :class:`~cryptography.hazmat.primitives.asymmetric.ec.BrainpoolP256R1`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.BrainpoolP384R1`, and :class:`~cryptography.hazmat.primitives.asymmetric.ec.BrainpoolP512R1` to support inter-operating with systems like German smart meters. * Added token rotation support to :doc:`Fernet ` with :meth:`~cryptography.fernet.MultiFernet.rotate`. * Fixed a memory leak in :func:`~cryptography.hazmat.primitives.asymmetric.ec.derive_private_key`. * Added support for AES key wrapping with padding via :func:`~cryptography.hazmat.primitives.keywrap.aes_key_wrap_with_padding` and :func:`~cryptography.hazmat.primitives.keywrap.aes_key_unwrap_with_padding` . * Allow loading DSA keys with 224 bit ``q``. .. _v2-1-4: 2.1.4 - 2017-11-29 ~~~~~~~~~~~~~~~~~~ * Added ``X509_up_ref`` for an upcoming ``pyOpenSSL`` release. .. _v2-1-3: 2.1.3 - 2017-11-02 ~~~~~~~~~~~~~~~~~~ * Updated Windows, macOS, and ``manylinux1`` wheels to be compiled with OpenSSL 1.1.0g. .. _v2-1-2: 2.1.2 - 2017-10-24 ~~~~~~~~~~~~~~~~~~ * Corrected a bug with the ``manylinux1`` wheels where OpenSSL's stack was marked executable. .. _v2-1-1: 2.1.1 - 2017-10-12 ~~~~~~~~~~~~~~~~~~ * Fixed support for install with the system ``pip`` on Ubuntu 16.04. .. _v2-1: 2.1 - 2017-10-11 ~~~~~~~~~~~~~~~~ * **FINAL DEPRECATION** Python 2.6 support is deprecated, and will be removed in the next release of ``cryptography``. * **BACKWARDS INCOMPATIBLE:** ``Whirlpool``, ``RIPEMD160``, and ``UnsupportedExtension`` have been removed in accordance with our :doc:`/api-stability` policy. * **BACKWARDS INCOMPATIBLE:** :attr:`DNSName.value `, :attr:`RFC822Name.value `, and :attr:`UniformResourceIdentifier.value ` will now return an :term:`A-label` string when parsing a certificate containing an internationalized domain name (IDN) or if the caller passed a :term:`U-label` to the constructor. See below for additional deprecations related to this change. * Installing ``cryptography`` now requires ``pip`` 6 or newer. * Deprecated passing :term:`U-label` strings to the :class:`~cryptography.x509.DNSName`, :class:`~cryptography.x509.UniformResourceIdentifier`, and :class:`~cryptography.x509.RFC822Name` constructors. Instead, users should pass values as :term:`A-label` strings with ``idna`` encoding if necessary. This change will not affect anyone who is not processing internationalized domains. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20`. In most cases users should choose :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` rather than using this unauthenticated form. * Added :meth:`~cryptography.x509.CertificateRevocationList.is_signature_valid` to :class:`~cryptography.x509.CertificateRevocationList`. * Support :class:`~cryptography.hazmat.primitives.hashes.BLAKE2b` and :class:`~cryptography.hazmat.primitives.hashes.BLAKE2s` with :class:`~cryptography.hazmat.primitives.hmac.HMAC`. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.modes.XTS` mode for AES. * Added support for using labels with :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP` when using OpenSSL 1.0.2 or greater. * Improved compatibility with NSS when issuing certificates from an issuer that has a subject with non-``UTF8String`` string types. * Add support for the :class:`~cryptography.x509.DeltaCRLIndicator` extension. * Add support for the :class:`~cryptography.x509.TLSFeature` extension. This is commonly used for enabling ``OCSP Must-Staple`` in certificates. * Add support for the :class:`~cryptography.x509.FreshestCRL` extension. .. _v2-0-3: 2.0.3 - 2017-08-03 ~~~~~~~~~~~~~~~~~~ * Fixed an issue with weak linking symbols when compiling on macOS versions older than 10.12. .. _v2-0-2: 2.0.2 - 2017-07-27 ~~~~~~~~~~~~~~~~~~ * Marked all symbols as hidden in the ``manylinux1`` wheel to avoid a bug with symbol resolution in certain scenarios. .. _v2-0-1: 2.0.1 - 2017-07-26 ~~~~~~~~~~~~~~~~~~ * Fixed a compilation bug affecting OpenBSD. * Altered the ``manylinux1`` wheels to statically link OpenSSL instead of dynamically linking and bundling the shared object. This should resolve crashes seen when using ``uwsgi`` or other binaries that link against OpenSSL independently. * Fixed the stack level for the ``signer`` and ``verifier`` warnings. .. _v2-0: 2.0 - 2017-07-17 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Support for Python 3.3 has been dropped. * We now ship ``manylinux1`` wheels linked against OpenSSL 1.1.0f. These wheels will be automatically used with most Linux distributions if you are running the latest pip. * Deprecated the use of ``signer`` on :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, and :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` in favor of ``sign``. * Deprecated the use of ``verifier`` on :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, and :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` in favor of ``verify``. * Added support for parsing :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp` objects from X.509 certificate extensions. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`. * Added support for :class:`~cryptography.hazmat.primitives.ciphers.aead.AESCCM`. * Added :class:`~cryptography.hazmat.primitives.ciphers.aead.AESGCM`, a "one shot" API for AES GCM encryption. * Added support for :doc:`/hazmat/primitives/asymmetric/x25519`. * Added support for serializing and deserializing Diffie-Hellman parameters with :func:`~cryptography.hazmat.primitives.serialization.load_pem_parameters`, :func:`~cryptography.hazmat.primitives.serialization.load_der_parameters`, and :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters.parameter_bytes` . * The ``extensions`` attribute on :class:`~cryptography.x509.Certificate`, :class:`~cryptography.x509.CertificateSigningRequest`, :class:`~cryptography.x509.CertificateRevocationList`, and :class:`~cryptography.x509.RevokedCertificate` now caches the computed ``Extensions`` object. There should be no performance change, just a performance improvement for programs accessing the ``extensions`` attribute multiple times. .. _v1-9: 1.9 - 2017-05-29 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** Elliptic Curve signature verification no longer returns ``True`` on success. This brings it in line with the interface's documentation, and our intent. The correct way to use :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.verify` has always been to check whether or not :class:`~cryptography.exceptions.InvalidSignature` was raised. * **BACKWARDS INCOMPATIBLE:** Dropped support for macOS 10.7 and 10.8. * **BACKWARDS INCOMPATIBLE:** The minimum supported PyPy version is now 5.3. * Python 3.3 support has been deprecated, and will be removed in the next ``cryptography`` release. * Add support for providing ``tag`` during :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` finalization via :meth:`~cryptography.hazmat.primitives.ciphers.AEADDecryptionContext.finalize_with_tag`. * Fixed an issue preventing ``cryptography`` from compiling against LibreSSL 2.5.x. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.key_size` and :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.key_size` as convenience methods for determining the bit size of a secret scalar for the curve. * Accessing an unrecognized extension marked critical on an X.509 object will no longer raise an ``UnsupportedExtension`` exception, instead an :class:`~cryptography.x509.UnrecognizedExtension` object will be returned. This behavior was based on a poor reading of the RFC, unknown critical extensions only need to be rejected on certificate verification. * The CommonCrypto backend has been removed. * MultiBackend has been removed. * ``Whirlpool`` and ``RIPEMD160`` have been deprecated. .. _v1-8-2: 1.8.2 - 2017-05-26 ~~~~~~~~~~~~~~~~~~ * Fixed a compilation bug affecting OpenSSL 1.1.0f. * Updated Windows and macOS wheels to be compiled against OpenSSL 1.1.0f. .. _v1-8-1: 1.8.1 - 2017-03-10 ~~~~~~~~~~~~~~~~~~ * Fixed macOS wheels to properly link against 1.1.0 rather than 1.0.2. .. _v1-8: 1.8 - 2017-03-09 ~~~~~~~~~~~~~~~~ * Added support for Python 3.6. * Windows and macOS wheels now link against OpenSSL 1.1.0. * macOS wheels are no longer universal. This change significantly shrinks the size of the wheels. Users on macOS 32-bit Python (if there are any) should migrate to 64-bit or build their own packages. * Changed ASN.1 dependency from ``pyasn1`` to ``asn1crypto`` resulting in a general performance increase when encoding/decoding ASN.1 structures. Also, the ``pyasn1_modules`` test dependency is no longer required. * Added support for :meth:`~cryptography.hazmat.primitives.ciphers.CipherContext.update_into` on :class:`~cryptography.hazmat.primitives.ciphers.CipherContext`. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey.private_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey.public_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`. * :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key` and :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key` now require that ``password`` must be bytes if provided. Previously this was documented but not enforced. * Added support for subgroup order in :doc:`/hazmat/primitives/asymmetric/dh`. .. _v1-7-2: 1.7.2 - 2017-01-27 ~~~~~~~~~~~~~~~~~~ * Updated Windows and macOS wheels to be compiled against OpenSSL 1.0.2k. .. _v1-7-1: 1.7.1 - 2016-12-13 ~~~~~~~~~~~~~~~~~~ * Fixed a regression in ``int_from_bytes`` where it failed to accept ``bytearray``. .. _v1-7: 1.7 - 2016-12-12 ~~~~~~~~~~~~~~~~ * Support for OpenSSL 1.0.0 has been removed. Users on older version of OpenSSL will need to upgrade. * Added support for Diffie-Hellman key exchange using :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey.exchange`. * The OS random engine for OpenSSL has been rewritten to improve compatibility with embedded Python and other edge cases. More information about this change can be found in the `pull request `_. .. _v1-6: 1.6 - 2016-11-22 ~~~~~~~~~~~~~~~~ * Deprecated support for OpenSSL 1.0.0. Support will be removed in ``cryptography`` 1.7. * Replaced the Python-based OpenSSL locking callbacks with a C version to fix a potential deadlock that could occur if a garbage collection cycle occurred while inside the lock. * Added support for :class:`~cryptography.hazmat.primitives.hashes.BLAKE2b` and :class:`~cryptography.hazmat.primitives.hashes.BLAKE2s` when using OpenSSL 1.1.0. * Added :attr:`~cryptography.x509.Certificate.signature_algorithm_oid` support to :class:`~cryptography.x509.Certificate`. * Added :attr:`~cryptography.x509.CertificateSigningRequest.signature_algorithm_oid` support to :class:`~cryptography.x509.CertificateSigningRequest`. * Added :attr:`~cryptography.x509.CertificateRevocationList.signature_algorithm_oid` support to :class:`~cryptography.x509.CertificateRevocationList`. * Added support for :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt` when using OpenSSL 1.1.0. * Added a workaround to improve compatibility with Python application bundling tools like ``PyInstaller`` and ``cx_freeze``. * Added support for generating a :meth:`~cryptography.x509.random_serial_number`. * Added support for encoding ``IPv4Network`` and ``IPv6Network`` in X.509 certificates for use with :class:`~cryptography.x509.NameConstraints`. * Added :meth:`~cryptography.x509.Name.public_bytes` to :class:`~cryptography.x509.Name`. * Added :class:`~cryptography.x509.RelativeDistinguishedName` * :class:`~cryptography.x509.DistributionPoint` now accepts :class:`~cryptography.x509.RelativeDistinguishedName` for :attr:`~cryptography.x509.DistributionPoint.relative_name`. Deprecated use of :class:`~cryptography.x509.Name` as :attr:`~cryptography.x509.DistributionPoint.relative_name`. * :class:`~cryptography.x509.Name` now accepts an iterable of :class:`~cryptography.x509.RelativeDistinguishedName`. RDNs can be accessed via the :attr:`~cryptography.x509.Name.rdns` attribute. When constructed with an iterable of :class:`~cryptography.x509.NameAttribute`, each attribute becomes a single-valued RDN. * Added :func:`~cryptography.hazmat.primitives.asymmetric.ec.derive_private_key`. * Added support for signing and verifying RSA, DSA, and ECDSA signatures with :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` digests. .. _v1-5-3: 1.5.3 - 2016-11-05 ~~~~~~~~~~~~~~~~~~ * **SECURITY ISSUE**: Fixed a bug where ``HKDF`` would return an empty byte-string if used with a ``length`` less than ``algorithm.digest_size``. Credit to **Markus Döring** for reporting the issue. *CVE-2016-9243* .. _v1-5-2: 1.5.2 - 2016-09-26 ~~~~~~~~~~~~~~~~~~ * Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2j. .. _v1-5-1: 1.5.1 - 2016-09-22 ~~~~~~~~~~~~~~~~~~ * Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2i. * Resolved a ``UserWarning`` when used with cffi 1.8.3. * Fixed a memory leak in name creation with X.509. * Added a workaround for old versions of setuptools. * Fixed an issue preventing ``cryptography`` from compiling against OpenSSL 1.0.2i. .. _v1-5: 1.5 - 2016-08-26 ~~~~~~~~~~~~~~~~ * Added :func:`~cryptography.hazmat.primitives.asymmetric.padding.calculate_max_pss_salt_length`. * Added "one shot" :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign` and :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey.verify` methods to DSA keys. * Added "one shot" :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign` and :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.verify` methods to ECDSA keys. * Switched back to the older callback model on Python 3.5 in order to mitigate the locking callback problem with OpenSSL <1.1.0. * :class:`~cryptography.x509.CertificateBuilder`, :class:`~cryptography.x509.CertificateRevocationListBuilder`, and :class:`~cryptography.x509.RevokedCertificateBuilder` now accept timezone aware ``datetime`` objects as method arguments * ``cryptography`` now supports OpenSSL 1.1.0 as a compilation target. .. _v1-4: 1.4 - 2016-06-04 ~~~~~~~~~~~~~~~~ * Support for OpenSSL 0.9.8 has been removed. Users on older versions of OpenSSL will need to upgrade. * Added :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFHMAC`. * Added support for ``OpenSSH`` public key serialization. * Added support for SHA-2 in RSA :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP` when using OpenSSL 1.0.2 or greater. * Added "one shot" :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign` and :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.verify` methods to RSA keys. * Deprecated the ``serial`` attribute on :class:`~cryptography.x509.Certificate`, in favor of :attr:`~cryptography.x509.Certificate.serial_number`. .. _v1-3-4: 1.3.4 - 2016-06-03 ~~~~~~~~~~~~~~~~~~ * Added another OpenSSL function to the bindings to support an upcoming ``pyOpenSSL`` release. .. _v1-3-3: 1.3.3 - 2016-06-02 ~~~~~~~~~~~~~~~~~~ * Added two new OpenSSL functions to the bindings to support an upcoming ``pyOpenSSL`` release. .. _v1-3-2: 1.3.2 - 2016-05-04 ~~~~~~~~~~~~~~~~~~ * Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2h. * Fixed an issue preventing ``cryptography`` from compiling against LibreSSL 2.3.x. .. _v1-3-1: 1.3.1 - 2016-03-21 ~~~~~~~~~~~~~~~~~~ * Fixed a bug that caused an ``AttributeError`` when using ``mock`` to patch some ``cryptography`` modules. .. _v1-3: 1.3 - 2016-03-18 ~~~~~~~~~~~~~~~~ * Added support for padding ANSI X.923 with :class:`~cryptography.hazmat.primitives.padding.ANSIX923`. * Deprecated support for OpenSSL 0.9.8. Support will be removed in ``cryptography`` 1.4. * Added support for the :class:`~cryptography.x509.PolicyConstraints` X.509 extension including both parsing and generation using :class:`~cryptography.x509.CertificateBuilder` and :class:`~cryptography.x509.CertificateSigningRequestBuilder`. * Added :attr:`~cryptography.x509.CertificateSigningRequest.is_signature_valid` to :class:`~cryptography.x509.CertificateSigningRequest`. * Fixed an intermittent ``AssertionError`` when performing an RSA decryption on an invalid ciphertext, ``ValueError`` is now correctly raised in all cases. * Added :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier`. .. _v1-2-3: 1.2.3 - 2016-03-01 ~~~~~~~~~~~~~~~~~~ * Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2g. .. _v1-2-2: 1.2.2 - 2016-01-29 ~~~~~~~~~~~~~~~~~~ * Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2f. .. _v1-2-1: 1.2.1 - 2016-01-08 ~~~~~~~~~~~~~~~~~~ * Reverts a change to an OpenSSL ``EVP_PKEY`` object that caused errors with ``pyOpenSSL``. .. _v1-2: 1.2 - 2016-01-08 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** :class:`~cryptography.x509.RevokedCertificate` :attr:`~cryptography.x509.RevokedCertificate.extensions` now uses extension classes rather than returning raw values inside the :class:`~cryptography.x509.Extension` :attr:`~cryptography.x509.Extension.value`. The new classes are: * :class:`~cryptography.x509.CertificateIssuer` * :class:`~cryptography.x509.CRLReason` * :class:`~cryptography.x509.InvalidityDate` * Deprecated support for OpenSSL 0.9.8 and 1.0.0. At this time there is no time table for actually dropping support, however we strongly encourage all users to upgrade, as those versions no longer receive support from the OpenSSL project. * The :class:`~cryptography.x509.Certificate` class now has :attr:`~cryptography.x509.Certificate.signature` and :attr:`~cryptography.x509.Certificate.tbs_certificate_bytes` attributes. * The :class:`~cryptography.x509.CertificateSigningRequest` class now has :attr:`~cryptography.x509.CertificateSigningRequest.signature` and :attr:`~cryptography.x509.CertificateSigningRequest.tbs_certrequest_bytes` attributes. * The :class:`~cryptography.x509.CertificateRevocationList` class now has :attr:`~cryptography.x509.CertificateRevocationList.signature` and :attr:`~cryptography.x509.CertificateRevocationList.tbs_certlist_bytes` attributes. * :class:`~cryptography.x509.NameConstraints` are now supported in the :class:`~cryptography.x509.CertificateBuilder` and :class:`~cryptography.x509.CertificateSigningRequestBuilder`. * Support serialization of certificate revocation lists using the :meth:`~cryptography.x509.CertificateRevocationList.public_bytes` method of :class:`~cryptography.x509.CertificateRevocationList`. * Add support for parsing :class:`~cryptography.x509.CertificateRevocationList` :meth:`~cryptography.x509.CertificateRevocationList.extensions` in the OpenSSL backend. The following extensions are currently supported: * :class:`~cryptography.x509.AuthorityInformationAccess` * :class:`~cryptography.x509.AuthorityKeyIdentifier` * :class:`~cryptography.x509.CRLNumber` * :class:`~cryptography.x509.IssuerAlternativeName` * Added :class:`~cryptography.x509.CertificateRevocationListBuilder` and :class:`~cryptography.x509.RevokedCertificateBuilder` to allow creation of CRLs. * Unrecognized non-critical X.509 extensions are now parsed into an :class:`~cryptography.x509.UnrecognizedExtension` object. .. _v1-1-2: 1.1.2 - 2015-12-10 ~~~~~~~~~~~~~~~~~~ * Fixed a SIGBUS crash with the OS X wheels caused by redefinition of a method. * Fixed a runtime error ``undefined symbol EC_GFp_nistp224_method`` that occurred with some OpenSSL installations. * Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2e. .. _v1-1-1: 1.1.1 - 2015-11-19 ~~~~~~~~~~~~~~~~~~ * Fixed several small bugs related to compiling the OpenSSL bindings with unusual OpenSSL configurations. * Resolved an issue where, depending on the method of installation and which Python interpreter they were using, users on El Capitan (OS X 10.11) may have seen an ``InternalError`` on import. .. _v1-1: 1.1 - 2015-10-28 ~~~~~~~~~~~~~~~~ * Added support for Elliptic Curve Diffie-Hellman with :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDH`. * Added :class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF`. * Added support for parsing certificate revocation lists (CRLs) using :func:`~cryptography.x509.load_pem_x509_crl` and :func:`~cryptography.x509.load_der_x509_crl`. * Add support for AES key wrapping with :func:`~cryptography.hazmat.primitives.keywrap.aes_key_wrap` and :func:`~cryptography.hazmat.primitives.keywrap.aes_key_unwrap`. * Added a ``__hash__`` method to :class:`~cryptography.x509.Name`. * Add support for encoding and decoding elliptic curve points to a byte string form using ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point`` and ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point``. * Added :meth:`~cryptography.x509.Extensions.get_extension_for_class`. * :class:`~cryptography.x509.CertificatePolicies` are now supported in the :class:`~cryptography.x509.CertificateBuilder`. * ``countryName`` is now encoded as a ``PrintableString`` when creating subject and issuer distinguished names with the Certificate and CSR builder classes. .. _v1-0-2: 1.0.2 - 2015-09-27 ~~~~~~~~~~~~~~~~~~ * **SECURITY ISSUE**: The OpenSSL backend prior to 1.0.2 made extensive use of assertions to check response codes where our tests could not trigger a failure. However, when Python is run with ``-O`` these asserts are optimized away. If a user ran Python with this flag and got an invalid response code this could result in undefined behavior or worse. Accordingly, all response checks from the OpenSSL backend have been converted from ``assert`` to a true function call. Credit **Emilia Käsper (Google Security Team)** for the report. .. _v1-0-1: 1.0.1 - 2015-09-05 ~~~~~~~~~~~~~~~~~~ * We now ship OS X wheels that statically link OpenSSL by default. When installing a wheel on OS X 10.10+ (and using a Python compiled against the 10.10 SDK) users will no longer need to compile. See :doc:`/installation` for alternate installation methods if required. * Set the default string mask to UTF-8 in the OpenSSL backend to resolve character encoding issues with older versions of OpenSSL. * Several new OpenSSL bindings have been added to support a future pyOpenSSL release. * Raise an error during install on PyPy < 2.6. 1.0+ requires PyPy 2.6+. .. _v1-0: 1.0 - 2015-08-12 ~~~~~~~~~~~~~~~~ * Switched to the new `cffi`_ ``set_source`` out-of-line API mode for compilation. This results in significantly faster imports and lowered memory consumption. Due to this change we no longer support PyPy releases older than 2.6 nor do we support any released version of PyPy3 (until a version supporting cffi 1.0 comes out). * Fix parsing of OpenSSH public keys that have spaces in comments. * Support serialization of certificate signing requests using the ``public_bytes`` method of :class:`~cryptography.x509.CertificateSigningRequest`. * Support serialization of certificates using the ``public_bytes`` method of :class:`~cryptography.x509.Certificate`. * Add ``get_provisioning_uri`` method to :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP` and :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP` for generating provisioning URIs. * Add :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHash` and :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHMAC`. * Raise a ``TypeError`` when passing objects that are not text as the value to :class:`~cryptography.x509.NameAttribute`. * Add support for :class:`~cryptography.x509.OtherName` as a general name type. * Added new X.509 extension support in :class:`~cryptography.x509.Certificate` The following new extensions are now supported: * :class:`~cryptography.x509.OCSPNoCheck` * :class:`~cryptography.x509.InhibitAnyPolicy` * :class:`~cryptography.x509.IssuerAlternativeName` * :class:`~cryptography.x509.NameConstraints` * Extension support was added to :class:`~cryptography.x509.CertificateSigningRequest`. * Add support for creating signed certificates with :class:`~cryptography.x509.CertificateBuilder`. This includes support for the following extensions: * :class:`~cryptography.x509.BasicConstraints` * :class:`~cryptography.x509.SubjectAlternativeName` * :class:`~cryptography.x509.KeyUsage` * :class:`~cryptography.x509.ExtendedKeyUsage` * :class:`~cryptography.x509.SubjectKeyIdentifier` * :class:`~cryptography.x509.AuthorityKeyIdentifier` * :class:`~cryptography.x509.AuthorityInformationAccess` * :class:`~cryptography.x509.CRLDistributionPoints` * :class:`~cryptography.x509.InhibitAnyPolicy` * :class:`~cryptography.x509.IssuerAlternativeName` * :class:`~cryptography.x509.OCSPNoCheck` * Add support for creating certificate signing requests with :class:`~cryptography.x509.CertificateSigningRequestBuilder`. This includes support for the same extensions supported in the ``CertificateBuilder``. * Deprecate ``encode_rfc6979_signature`` and ``decode_rfc6979_signature`` in favor of :func:`~cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature` and :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature`. .. _v0-9-3: 0.9.3 - 2015-07-09 ~~~~~~~~~~~~~~~~~~ * Updated Windows wheels to be compiled against OpenSSL 1.0.2d. .. _v0-9-2: 0.9.2 - 2015-07-04 ~~~~~~~~~~~~~~~~~~ * Updated Windows wheels to be compiled against OpenSSL 1.0.2c. .. _v0-9-1: 0.9.1 - 2015-06-06 ~~~~~~~~~~~~~~~~~~ * **SECURITY ISSUE**: Fixed a double free in the OpenSSL backend when using DSA to verify signatures. Note that this only affects PyPy 2.6.0 and (presently unreleased) CFFI versions greater than 1.1.0. .. _v0-9: 0.9 - 2015-05-13 ~~~~~~~~~~~~~~~~ * Removed support for Python 3.2. This version of Python is rarely used and caused support headaches. Users affected by this should upgrade to 3.3+. * Deprecated support for Python 2.6. At the time there is no time table for actually dropping support, however we strongly encourage all users to upgrade their Python, as Python 2.6 no longer receives support from the Python core team. * Add support for the :class:`~cryptography.hazmat.primitives.asymmetric.ec.SECP256K1` elliptic curve. * Fixed compilation when using an OpenSSL which was compiled with the ``no-comp`` (``OPENSSL_NO_COMP``) option. * Support :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` serialization of public keys using the ``public_bytes`` method of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, and :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. * Support :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` serialization of private keys using the ``private_bytes`` method of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, and :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. * Add support for parsing X.509 certificate signing requests (CSRs) with :func:`~cryptography.x509.load_pem_x509_csr` and :func:`~cryptography.x509.load_der_x509_csr`. * Moved ``cryptography.exceptions.InvalidToken`` to :class:`cryptography.hazmat.primitives.twofactor.InvalidToken` and deprecated the old location. This was moved to minimize confusion between this exception and :class:`cryptography.fernet.InvalidToken`. * Added support for X.509 extensions in :class:`~cryptography.x509.Certificate` objects. The following extensions are supported as of this release: * :class:`~cryptography.x509.BasicConstraints` * :class:`~cryptography.x509.AuthorityKeyIdentifier` * :class:`~cryptography.x509.SubjectKeyIdentifier` * :class:`~cryptography.x509.KeyUsage` * :class:`~cryptography.x509.SubjectAlternativeName` * :class:`~cryptography.x509.ExtendedKeyUsage` * :class:`~cryptography.x509.CRLDistributionPoints` * :class:`~cryptography.x509.AuthorityInformationAccess` * :class:`~cryptography.x509.CertificatePolicies` Note that unsupported extensions with the critical flag raise ``UnsupportedExtension`` while unsupported extensions set to non-critical are silently ignored. Read the :doc:`X.509 documentation` for more information. .. _v0-8-2: 0.8.2 - 2015-04-10 ~~~~~~~~~~~~~~~~~~ * Fixed a race condition when initializing the OpenSSL or CommonCrypto backends in a multi-threaded scenario. .. _v0-8-1: 0.8.1 - 2015-03-20 ~~~~~~~~~~~~~~~~~~ * Updated Windows wheels to be compiled against OpenSSL 1.0.2a. .. _v0-8: 0.8 - 2015-03-08 ~~~~~~~~~~~~~~~~ * :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` can now load elliptic curve public keys. * Added :attr:`~cryptography.x509.Certificate.signature_hash_algorithm` support to :class:`~cryptography.x509.Certificate`. * Added :func:`~cryptography.hazmat.primitives.asymmetric.rsa.rsa_recover_prime_factors` * :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` was moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.kdf`. * Added support for parsing X.509 names. See the :doc:`X.509 documentation` for more information. * Added :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key` to support loading of DER encoded private keys and :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` to 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 ``EllipticCurvePrivateKeyWithSerialization`` and deprecated ``EllipticCurvePrivateKeyWithNumbers``. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.private_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. * Added ``RSAPrivateKeyWithSerialization`` and deprecated ``RSAPrivateKeyWithNumbers``. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.private_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. * Added ``DSAPrivateKeyWithSerialization`` and deprecated ``DSAPrivateKeyWithNumbers``. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.private_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. * Added ``RSAPublicKeyWithSerialization`` and deprecated ``RSAPublicKeyWithNumbers``. * Added ``public_bytes`` to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. * Added ``EllipticCurvePublicKeyWithSerialization`` and deprecated ``EllipticCurvePublicKeyWithNumbers``. * Added ``public_bytes`` to :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. * Added ``DSAPublicKeyWithSerialization`` and deprecated ``DSAPublicKeyWithNumbers``. * Added ``public_bytes`` to :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. * :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` and :class:`~cryptography.hazmat.primitives.hashes.HashContext` were moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.hashes`. * :class:`~cryptography.hazmat.primitives.ciphers.CipherContext`, :class:`~cryptography.hazmat.primitives.ciphers.AEADCipherContext`, :class:`~cryptography.hazmat.primitives.ciphers.AEADEncryptionContext`, :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`, and :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` were moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.ciphers`. * :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`, :class:`~cryptography.hazmat.primitives.ciphers.modes.ModeWithInitializationVector`, :class:`~cryptography.hazmat.primitives.ciphers.modes.ModeWithNonce`, and :class:`~cryptography.hazmat.primitives.ciphers.modes.ModeWithAuthenticationTag` were moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.ciphers.modes`. * :class:`~cryptography.hazmat.primitives.padding.PaddingContext` was moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.padding`. * :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding` was moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.asymmetric.padding`. * ``AsymmetricSignatureContext`` and ``AsymmetricVerificationContext`` were moved from ``cryptography.hazmat.primitives.interfaces`` to ``cryptography.hazmat.primitives.asymmetric``. * :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`, ``DSAParametersWithNumbers``, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, ``DSAPrivateKeyWithNumbers``, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` and ``DSAPublicKeyWithNumbers`` were moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.asymmetric.dsa` * :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurveSignatureAlgorithm`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, ``EllipticCurvePrivateKeyWithNumbers``, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, and ``EllipticCurvePublicKeyWithNumbers`` were moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.asymmetric.ec`. * :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, ``RSAPrivateKeyWithNumbers``, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` and ``RSAPublicKeyWithNumbers`` were moved from ``cryptography.hazmat.primitives.interfaces`` to :mod:`~cryptography.hazmat.primitives.asymmetric.rsa`. .. _v0-7-2: 0.7.2 - 2015-01-16 ~~~~~~~~~~~~~~~~~~ * Updated Windows wheels to be compiled against OpenSSL 1.0.1l. * ``enum34`` is no longer installed on Python 3.4, where it is included in the standard library. * Added a new function to the OpenSSL bindings to support additional functionality in pyOpenSSL. .. _v0-7-1: 0.7.1 - 2014-12-28 ~~~~~~~~~~~~~~~~~~ * Fixed an issue preventing compilation on platforms where ``OPENSSL_NO_SSL3`` was defined. .. _v0-7: 0.7 - 2014-12-17 ~~~~~~~~~~~~~~~~ * Cryptography has been relicensed from the Apache Software License, Version 2.0, to being available under *either* the Apache Software License, Version 2.0, or the BSD license. * Added key-rotation support to :doc:`Fernet ` with :class:`~cryptography.fernet.MultiFernet`. * More bit-lengths are now supported for ``p`` and ``q`` when loading DSA keys from numbers. * Added ``MACContext`` as a common interface for CMAC and HMAC and deprecated ``CMACContext``. * Added support for encoding and decoding :rfc:`6979` signatures in :doc:`/hazmat/primitives/asymmetric/utils`. * Added :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` to support the loading of OpenSSH public keys (:rfc:`4253`). Only RSA and DSA keys are currently supported. * Added initial support for X.509 certificate parsing. See the :doc:`X.509 documentation` for more information. .. _v0-6-1: 0.6.1 - 2014-10-15 ~~~~~~~~~~~~~~~~~~ * Updated Windows wheels to be compiled against OpenSSL 1.0.1j. * Fixed an issue where OpenSSL 1.0.1j changed the errors returned by some functions. * Added our license file to the ``cryptography-vectors`` package. * Implemented DSA hash truncation support (per FIPS 186-3) in the OpenSSL backend. This works around an issue in 1.0.0, 1.0.0a, and 1.0.0b where truncation was not implemented. .. _v0-6: 0.6 - 2014-09-29 ~~~~~~~~~~~~~~~~ * Added :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key` to ease loading private keys, and :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key` to support loading public keys. * Removed the, deprecated in 0.4, support for the ``salt_length`` argument to the :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF1` constructor. The ``salt_length`` should be passed to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` instead. * Fix compilation on OS X Yosemite. * Deprecated ``elliptic_curve_private_key_from_numbers`` and ``elliptic_curve_public_key_from_numbers`` in favor of ``load_elliptic_curve_private_numbers`` and ``load_elliptic_curve_public_numbers`` on ``EllipticCurveBackend``. * Added ``EllipticCurvePrivateKeyWithNumbers`` and ``EllipticCurvePublicKeyWithNumbers`` support. * Work around three GCM related bugs in CommonCrypto and OpenSSL. * On the CommonCrypto backend adding AAD but not subsequently calling update would return null tag bytes. * One the CommonCrypto backend a call to update without an empty add AAD call would return null ciphertext bytes. * On the OpenSSL backend with certain versions adding AAD only would give invalid tag bytes. * Support loading EC private keys from PEM. .. _v0-5-4: 0.5.4 - 2014-08-20 ~~~~~~~~~~~~~~~~~~ * Added several functions to the OpenSSL bindings to support new functionality in pyOpenSSL. * Fixed a redefined constant causing compilation failure with Solaris 11.2. .. _v0-5-3: 0.5.3 - 2014-08-06 ~~~~~~~~~~~~~~~~~~ * Updated Windows wheels to be compiled against OpenSSL 1.0.1i. .. _v0-5-2: 0.5.2 - 2014-07-09 ~~~~~~~~~~~~~~~~~~ * Add ``TraditionalOpenSSLSerializationBackend`` support to ``multibackend``. * Fix compilation error on OS X 10.8 (Mountain Lion). .. _v0-5-1: 0.5.1 - 2014-07-07 ~~~~~~~~~~~~~~~~~~ * Add ``PKCS8SerializationBackend`` support to ``multibackend``. .. _v0-5: 0.5 - 2014-07-07 ~~~~~~~~~~~~~~~~ * **BACKWARDS INCOMPATIBLE:** :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` no longer allows truncation of tags by default. Previous versions of ``cryptography`` allowed tags to be truncated by default, applications wishing to preserve this behavior (not recommended) can pass the ``min_tag_length`` argument. * Windows builds now statically link OpenSSL by default. When installing a wheel on Windows you no longer need to install OpenSSL separately. Windows users can switch between static and dynamic linking with an environment variable. See :doc:`/installation` for more details. * Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDFExpand`. * Added :class:`~cryptography.hazmat.primitives.ciphers.modes.CFB8` support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on ``commoncrypto`` and ``openssl``. * Added ``AES`` :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR` support to the OpenSSL backend when linked against 0.9.8. * Added ``PKCS8SerializationBackend`` and ``TraditionalOpenSSLSerializationBackend`` support to ``openssl``. * Added :doc:`/hazmat/primitives/asymmetric/ec` and ``EllipticCurveBackend``. * Added :class:`~cryptography.hazmat.primitives.ciphers.modes.ECB` support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on ``commoncrypto`` and ``openssl``. * Deprecated the concrete ``RSAPrivateKey`` class in favor of backend specific providers of the :class:`cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` interface. * Deprecated the concrete ``RSAPublicKey`` in favor of backend specific providers of the :class:`cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` interface. * Deprecated the concrete ``DSAPrivateKey`` class in favor of backend specific providers of the :class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` interface. * Deprecated the concrete ``DSAPublicKey`` class in favor of backend specific providers of the :class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` interface. * Deprecated the concrete ``DSAParameters`` class in favor of backend specific providers of the :class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters` interface. * Deprecated ``encrypt_rsa``, ``decrypt_rsa``, ``create_rsa_signature_ctx`` and ``create_rsa_verification_ctx`` on ``RSABackend``. * Deprecated ``create_dsa_signature_ctx`` and ``create_dsa_verification_ctx`` on ``DSABackend``. .. _v0-4: 0.4 - 2014-05-03 ~~~~~~~~~~~~~~~~ * Deprecated ``salt_length`` on :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF1` and added it to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. It will be removed from ``MGF1`` in two releases per our :doc:`/api-stability` policy. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SEED` support. * Added :class:`~cryptography.hazmat.primitives.cmac.CMAC`. * Added decryption support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` and encryption support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. * Added signature support to :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` and verification support to :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. .. _v0-3: 0.3 - 2014-03-27 ~~~~~~~~~~~~~~~~ * Added :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP`. * Added :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP`. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA` support. * Added signature support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` and verification support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. * Moved test vectors to the new ``cryptography_vectors`` package. .. _v0-2-2: 0.2.2 - 2014-03-03 ~~~~~~~~~~~~~~~~~~ * Removed a constant definition that was causing compilation problems with specific versions of OpenSSL. .. _v0-2-1: 0.2.1 - 2014-02-22 ~~~~~~~~~~~~~~~~~~ * Fix a bug where importing cryptography from multiple paths could cause initialization to fail. .. _v0-2: 0.2 - 2014-02-20 ~~~~~~~~~~~~~~~~ * Added ``commoncrypto``. * Added initial ``commoncrypto``. * Removed ``register_cipher_adapter`` method from ``CipherBackend``. * Added support for the OpenSSL backend under Windows. * Improved thread-safety for the OpenSSL backend. * Fixed compilation on systems where OpenSSL's ``ec.h`` header is not available, such as CentOS. * Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`. * Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. * Added ``multibackend``. * Set default random for ``openssl`` to the OS random engine. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5` (CAST-128) support. .. _v0-1: 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ * Initial release. .. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic .. _`main`: https://github.com/pyca/cryptography/ .. _`cffi`: https://cffi.readthedocs.io/ cryptography-43.0.0/CONTRIBUTING.rst010064400017510000177000000012321464676315000151700ustar 00000000000000Contributing to cryptography ============================ As an open source project, cryptography welcomes contributions of many forms. Examples of contributions include: * Code patches * Documentation improvements * Bug reports and patch reviews Extensive contribution guidelines are available in the repository at ``docs/development/index.rst``, or online at: https://cryptography.io/en/latest/development/ Security issues --------------- To report a security issue, please follow the special `security reporting guidelines`_, do not report them in the public issue tracker. .. _`security reporting guidelines`: https://cryptography.io/en/latest/security/ cryptography-43.0.0/LICENSE010064400017510000177000000003051464676315000135340ustar 00000000000000This software is made available under the terms of *either* of the licenses found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made under the terms of *both* these licenses. cryptography-43.0.0/LICENSE.APACHE010064400017510000177000000261401464676315000144610ustar 00000000000000 Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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 https://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. cryptography-43.0.0/LICENSE.BSD010064400017510000177000000027741464676315000141570ustar 00000000000000Copyright (c) Individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of PyCA Cryptography nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cryptography-43.0.0/README.rst010064400017510000177000000043111464676315000142170ustar 00000000000000pyca/cryptography ================= .. image:: https://img.shields.io/pypi/v/cryptography.svg :target: https://pypi.org/project/cryptography/ :alt: Latest Version .. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest :target: https://cryptography.io :alt: Latest Docs .. image:: https://github.com/pyca/cryptography/workflows/CI/badge.svg?branch=main :target: https://github.com/pyca/cryptography/actions?query=workflow%3ACI+branch%3Amain ``cryptography`` is a package which provides cryptographic recipes and primitives to Python developers. Our goal is for it to be your "cryptographic standard library". It supports Python 3.7+ and PyPy3 7.3.11+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and key derivation functions. For example, to encrypt something with ``cryptography``'s high level symmetric encryption recipe: .. code-block:: pycon >>> from cryptography.fernet import Fernet >>> # Put this somewhere safe! >>> key = Fernet.generate_key() >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token b'...' >>> f.decrypt(token) b'A really secret message. Not for prying eyes.' You can find more information in the `documentation`_. You can install ``cryptography`` with: .. code-block:: console $ pip install cryptography For full details see `the installation documentation`_. Discussion ~~~~~~~~~~ If you run into bugs, you can file them in our `issue tracker`_. We maintain a `cryptography-dev`_ mailing list for development discussion. You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get involved. Security ~~~~~~~~ Need to report a security issue? Please consult our `security reporting`_ documentation. .. _`documentation`: https://cryptography.io/ .. _`the installation documentation`: https://cryptography.io/en/latest/installation/ .. _`issue tracker`: https://github.com/pyca/cryptography/issues .. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev .. _`security reporting`: https://cryptography.io/en/latest/security/ cryptography-43.0.0/docs/Makefile010064400017510000177000000127241464676315000151270ustar 00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Cryptography.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Cryptography.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Cryptography" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Cryptography" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." cryptography-43.0.0/docs/_ext/cryptography-docs.py010064400017510000177000000031371464676315000204570ustar 00000000000000# 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. from docutils import nodes from docutils.parsers.rst import Directive DANGER_MESSAGE = """ This is a "Hazardous Materials" module. You should **ONLY** use it if you're 100% absolutely sure that you know what you're doing because this module is full of land mines, dragons, and dinosaurs with laser guns. """ DANGER_ALTERNATE = """ You may instead be interested in :doc:`{alternate}`. """ class HazmatDirective(Directive): has_content = True def run(self): message = DANGER_MESSAGE if self.content: message += DANGER_ALTERNATE.format(alternate=self.content[0]) content = nodes.paragraph("", message) admonition_node = Hazmat("\n".join(content)) self.state.nested_parse(content, self.content_offset, admonition_node) admonition_node.line = self.lineno return [admonition_node] class Hazmat(nodes.Admonition, nodes.Element): pass def html_visit_hazmat_node(self, node): return self.visit_admonition(node, "danger") def latex_visit_hazmat_node(self, node): return self.visit_admonition(node) def depart_hazmat_node(self, node): return self.depart_admonition(node) def setup(app): app.add_node( Hazmat, html=(html_visit_hazmat_node, depart_hazmat_node), latex=(latex_visit_hazmat_node, depart_hazmat_node), ) app.add_directive("hazmat", HazmatDirective) return { "parallel_read_safe": True, } cryptography-43.0.0/docs/_ext/linkcode_res.py010064400017510000177000000065611464676315000174430ustar 00000000000000import importlib import inspect import os import sys import cryptography # -- Linkcode resolver ----------------------------------------------------- # This is HEAVILY inspired by numpy's # https://github.com/numpy/numpy/blob/73fe877ff967f279d470b81ad447b9f3056c1335/doc/source/conf.py#L390 # Copyright (c) 2005-2020, NumPy Developers. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # * Neither the name of the NumPy Developers nor the names of any # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. def linkcode_resolve(domain, info): """ Determine the url corresponding to Python object """ if domain != "py": return None modname = info["module"] fullname = info["fullname"] try: importlib.import_module(modname) except Exception: return None submod = sys.modules.get(modname) if submod is None: return None obj = submod for part in fullname.split("."): try: obj = getattr(obj, part) except Exception: return None # strip decorators, which would resolve to the source of the decorator # possibly an upstream bug in getsourcefile, bpo-1764286 try: unwrap = inspect.unwrap except AttributeError: pass else: obj = unwrap(obj) fn = None lineno = None try: fn = inspect.getsourcefile(obj) except Exception: fn = None if not fn: return None try: source, lineno = inspect.getsourcelines(obj) except Exception: lineno = None fn = os.path.relpath(fn, start=os.path.dirname(cryptography.__file__)) if lineno: linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) else: linespec = "" url = "https://github.com/pyca/cryptography/blob/%s/src/cryptography/%s%s" if "dev" in cryptography.__version__: return url % ("main", fn, linespec) else: version = ".".join(cryptography.__version__.split(".")[:2] + ["x"]) return url % (version, fn, linespec) cryptography-43.0.0/docs/_static/.keep010064400017510000177000000000001464676315000160220ustar 00000000000000cryptography-43.0.0/docs/api-stability.rst010064400017510000177000000062601464676315000167720ustar 00000000000000API stability ============= From its first release, ``cryptography`` has had a strong API stability policy. What does this policy cover? ---------------------------- This policy includes any API or behavior that is documented in this documentation. What does "stable" mean? ------------------------ * Public APIs will not be removed or renamed without providing a compatibility alias. * The behavior of existing APIs will not change. What doesn't this policy cover? ------------------------------- * We may add new features, things like the result of ``dir(obj))`` or the contents of ``obj.__dict__`` may change. * Objects are not guaranteed to be pickleable, and pickled objects from one version of ``cryptography`` may not be loadable in future versions. * Unless otherwise documented, types in ``cryptography`` are not intended to be sub-classed, and we do not guarantee that behavior with respect to sub-classes will be stable. * Development versions of ``cryptography``. Before a feature is in a release, it is not covered by this policy and may change. Security ~~~~~~~~ One exception to our API stability policy is for security. We will violate this policy as necessary in order to resolve a security issue or harden ``cryptography`` against a possible attack. Versioning ---------- Version 35.0.0+ ~~~~~~~~~~~~~~~ Beginning with release 35.0.0 ``cryptography`` uses a Firefox-inspired version scheme. Given a version ``cryptography X.Y.Z``, * ``X`` indicates the major version number. This is incremented on any feature release. * ``Y`` is always ``0``. * ``Z`` is an integer that is incremented for minor backward-compatible releases (such as fixing security issues or correcting regressions in a major release). This scheme is compatible with `SemVer`_, though many major releases will **not** include any backwards-incompatible changes. Deprecation ~~~~~~~~~~~ From time to time we will want to change the behavior of an API or remove it entirely. In that case, here's how the process will work: * In ``cryptography X.0.0`` the feature exists. * In ``cryptography (X + 1).0.0`` using that feature will emit a ``CryptographyDeprecationWarning`` (base class ``UserWarning``). * In ``cryptography (X + 2).0.0`` using that feature will emit a ``CryptographyDeprecationWarning``. * In ``cryptography (X + 3).0.0`` the feature will be removed or changed. In short, code that runs without warnings will always continue to work for a period of two major releases. From time to time, we may decide to deprecate an API that is particularly widely used. In these cases, we may decide to provide an extended deprecation period, at our discretion. Previous Scheme ~~~~~~~~~~~~~~~ Before version 35.0.0 this project uses a custom versioning scheme as described below. Given a version ``cryptography X.Y.Z``, * ``X.Y`` is a decimal number that is incremented for potentially-backwards-incompatible releases. * This increases like a standard decimal. In other words, 0.9 is the ninth release, and 1.0 is the tenth (not 0.10). The dividing decimal point can effectively be ignored. * ``Z`` is an integer that is incremented for backward-compatible releases. .. _`SemVer`: https://semver.org/ cryptography-43.0.0/docs/changelog.rst010064400017510000177000000000361464676315000161410ustar 00000000000000.. include:: ../CHANGELOG.rst cryptography-43.0.0/docs/community.rst010064400017510000177000000011151464676315000162350ustar 00000000000000Community ========= You can find ``cryptography`` all over the web: * `Mailing list`_ * `Source code`_ * `Issue tracker`_ * `Documentation`_ * IRC: ``#pyca`` on ``irc.libera.chat`` Wherever we interact, we adhere to the `Python Community Code of Conduct`_. .. _`Mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev .. _`Source code`: https://github.com/pyca/cryptography .. _`Issue tracker`: https://github.com/pyca/cryptography/issues .. _`Documentation`: https://cryptography.io/ .. _`Python Community Code of Conduct`: https://www.python.org/psf/codeofconduct/ cryptography-43.0.0/docs/conf.py010064400017510000177000000142541464676315000147660ustar 00000000000000# 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. # Cryptography documentation build configuration file, created by # sphinx-quickstart on Tue Aug 6 19:19:14 2013. # # This file is execfile()d with the current directory set to its containing dir # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys try: import sphinx_rtd_theme except ImportError: sphinx_rtd_theme = None try: from sphinxcontrib import spelling except ImportError: spelling = None # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("_ext")) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosectionlabel", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.linkcode", "cryptography-docs", "sphinx_rtd_theme", ] if spelling is not None: extensions.append("sphinxcontrib.spelling") # Linkcode resolver from linkcode_res import linkcode_resolve # noqa: E402, F401 # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] nitpicky = True # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "Cryptography" copyright = "2013-2024, Individual Contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # base_dir = os.path.join(os.path.dirname(__file__), os.pardir) about = {} with open(os.path.join(base_dir, "src", "cryptography", "__about__.py")) as f: exec(f.read(), about) version = release = about["__version__"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. if sphinx_rtd_theme: html_theme = "sphinx_rtd_theme" else: html_theme = "default" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Output file base name for HTML help builder. htmlhelp_basename = "Cryptographydoc" # -- Options for LaTeX output ------------------------------------------------- latex_elements = {} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [ ( "index", "Cryptography.tex", "Cryptography Documentation", "Individual Contributors", "manual", ), ] # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( "index", "cryptography", "Cryptography Documentation", ["Individual Contributors"], 1, ) ] # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "Cryptography", "Cryptography Documentation", "Individual Contributors", "Cryptography", "One line description of project.", "Miscellaneous", ), ] intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} epub_theme = "epub" # Retry requests in the linkcheck builder so that we're resillient against # transient network errors. linkcheck_retries = 10 linkcheck_timeout = 5 linkcheck_ignore = [ # Insecure renegotiation settings r"https://info.isl.ntt.co.jp/crypt/eng/camellia/", # Cloudflare returns 403s for all non-browser requests r"https://speakerdeck.com", r"https://\w+.stackexchange.com", r"https://stackoverflow.com", # GitHub changed how they do page renders so anchor detection # no longer works in source view r"https://github.com/.*/blob/.*#L\d+", # Kuleuven struggles with the endless forward march of time r"https://www.cosic.esat.kuleuven.be", ] autosectionlabel_prefix_document = True cryptography-43.0.0/docs/development/c-bindings.rst010064400017510000177000000134541464676315000205610ustar 00000000000000C bindings ========== C bindings are bindings to C libraries, using cffi_ whenever possible. .. _cffi: https://cffi.readthedocs.io Bindings live in ``cryptography.hazmat.bindings``. When modifying the bindings you will need to recompile the C extensions to test the changes. This can be accomplished with ``pip install -e .`` in the project root. If you do not do this a ``RuntimeError`` will be raised. Style guide ----------- Don't name parameters: .. code-block:: c /* Good */ long f(long); /* Bad */ long f(long x); ...unless they're inside a struct: .. code-block:: c struct my_struct { char *name; int number; ...; }; Include ``void`` if the function takes no arguments: .. code-block:: c /* Good */ long f(void); /* Bad */ long f(); Wrap lines at 80 characters like so: .. code-block:: c /* Pretend this went to 80 characters */ long f(long, long, int *) Include a space after commas between parameters: .. code-block:: c /* Good */ long f(int, char *) /* Bad */ long f(int,char *) Use C-style ``/* */`` comments instead of C++-style ``//``: .. code-block:: c // Bad /* Good */ Values set by ``#define`` should be assigned the appropriate type. If you see this: .. code-block:: c #define SOME_INTEGER_LITERAL 0x0; #define SOME_UNSIGNED_INTEGER_LITERAL 0x0001U; #define SOME_STRING_LITERAL "hello"; ...it should be added to the bindings like so: .. code-block:: c static const int SOME_INTEGER_LITERAL; static const unsigned int SOME_UNSIGNED_INTEGER_LITERAL; static const char *const SOME_STRING_LITERAL; Adding constant, types, functions... ------------------------------------ You can create bindings for any name that exists in some version of the library you're binding against. However, the project also has to keep supporting older versions of the library. In order to achieve this, binding modules have a ``CUSTOMIZATIONS`` constant, and there is a ``CONDITIONAL_NAMES`` constants in ``src/cryptography/hazmat/bindings/openssl/_conditional.py``. Let's say you want to enable quantum transmogrification. The upstream library implements this as the following API:: static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT; static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT; typedef ... QM_TRANSMOGRIFICATION_CTX; int QM_transmogrify(QM_TRANSMOGRIFICATION_CTX *, int); To start, create a new constant that defines if the *actual* library has the feature you want, and add it to ``TYPES``:: static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION; This should start with ``Cryptography_``, since we're adding it in this library. This prevents namespace collisions. Then, define the actual features (constants, types, functions...) you want to expose. If it's a constant, just add it to ``TYPES``:: static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT; static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT; If it's a struct, add it to ``TYPES`` as well. The following is an opaque struct:: typedef ... QM_TRANSMOGRIFICATION_CTX; ... but you can also make some or all items in the struct accessible:: typedef struct { /* Fundamental constant k for your particular universe */ BIGNUM *k; ...; } QM_TRANSMOGRIFICATION_CTX; For functions just add the signature to ``FUNCTIONS``:: int QM_transmogrify(QM_TRANSMOGRIFICATION_CTX *, int); Then, we define the ``CUSTOMIZATIONS`` entry. To do that, we have to come up with a C preprocessor expression that decides whether or not a feature exists in the library. For example:: #ifdef QM_transmogrify Then, we set the flag that signifies the feature exists:: static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION = 1; Otherwise, we set that flag to 0:: #else static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION = 0; Then, in that ``#else`` block, we define the names that aren't available as dummy values. For an integer constant, use 0:: static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT = 0; static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT = 0; For a function, it's a bit trickier. You have to define a function pointer of the appropriate type to be NULL:: int (*QM_transmogrify)(QM_TRANSMOGRIFICATION_CTX *, int) = NULL; (To do that, copy the signature, put a ``*`` in front of the function name and wrap it in parentheses, and then put ``= NULL`` at the end). Note how types don't need to be conditionally defined, as long as all the necessarily type definitions are in place. Finally, add an entry to ``CONDITIONAL_NAMES`` with all of the things you want to conditionally export:: def cryptography_has_quantum_transmogrification(): return [ "QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT", "QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT", "QM_transmogrify", ] CONDITIONAL_NAMES = { ... "Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION": ( cryptography_has_quantum_transmogrification ), } Caveats ~~~~~~~ Sometimes, a set of loosely related features are added in the same version, and it's impractical to create ``#ifdef`` statements for each one. In that case, it may make sense to either check for a particular version. For example, to check for OpenSSL 1.1.1 or newer:: #if CRYPTOGRAPHY_OPENSSL_111_OR_GREATER Sometimes, the version of a library on a particular platform will have features that you thought it wouldn't, based on its version. Occasionally, packagers appear to ship arbitrary VCS checkouts. As a result, sometimes you may have to add separate ``#ifdef`` statements for particular features. This kind of issue is typically only caught by running the tests on a wide variety of systems, which is the job of our continuous integration infrastructure. cryptography-43.0.0/docs/development/custom-vectors/aes-192-gcm-siv.rst010064400017510000177000000016721464676315000241640ustar 00000000000000AES-GCM-SIV vector creation =========================== This page documents the code that was used to generate the AES-GCM-SIV test vectors for key lengths not available in the OpenSSL test vectors. All the vectors were generated using OpenSSL and verified with Rust. Creation -------- The following Python script was run to generate the vector files. The OpenSSL test vectors were used as a base and modified to have 192-bit key length. .. literalinclude:: /development/custom-vectors/aes-192-gcm-siv/generate_aes192gcmsiv.py Download link: :download:`generate_aes192gcmsiv.py ` Verification ------------ The following Rust program was used to verify the vectors. .. literalinclude:: /development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/src/main.rs Download link: :download:`main.rs ` cryptography-43.0.0/docs/development/custom-vectors/aes-192-gcm-siv/generate_aes192gcmsiv.py010064400017510000177000000054141464676315000300710ustar 00000000000000# 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. import binascii from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV def convert_key_to_192_bits(key: str) -> str: """ This takes existing 128 and 256-bit keys from test vectors from OpenSSL and makes them 192-bit by either appending 0 or truncating the key. """ new_key = binascii.unhexlify(key) if len(new_key) == 16: new_key += b"\x00" * 8 elif len(new_key) == 32: new_key = new_key[0:24] else: raise RuntimeError( "Unexpected key length. OpenSSL AES-GCM-SIV test vectors only " "contain 128-bit and 256-bit keys" ) return binascii.hexlify(new_key).decode("ascii") def encrypt(key: str, iv: str, plaintext: str, aad: str) -> (str, str): aesgcmsiv = AESGCMSIV(binascii.unhexlify(key)) encrypted_output = aesgcmsiv.encrypt( binascii.unhexlify(iv), binascii.unhexlify(plaintext), binascii.unhexlify(aad) if aad else None, ) ciphertext, tag = encrypted_output[:-16], encrypted_output[-16:] return ( binascii.hexlify(ciphertext).decode("ascii"), binascii.hexlify(tag).decode("ascii"), ) def build_vectors(filename): count = 0 output = [] key = None iv = None aad = None plaintext = None with open(filename) as vector_file: for line in vector_file: line = line.strip() if line.startswith("Key"): if count != 0: ciphertext, tag = encrypt(key, iv, plaintext, aad) output.append(f"Tag = {tag}\nCiphertext = {ciphertext}\n") output.append(f"\nCOUNT = {count}") count += 1 aad = None _, key = line.split(" = ") key = convert_key_to_192_bits(key) output.append(f"Key = {key}") elif line.startswith("IV"): _, iv = line.split(" = ") output.append(f"IV = {iv}") elif line.startswith("AAD"): _, aad = line.split(" = ") output.append(f"AAD = {aad}") elif line.startswith("Plaintext"): _, plaintext = line.split(" = ") output.append(f"Plaintext = {plaintext}") ciphertext, tag = encrypt(key, iv, plaintext, aad) output.append(f"Tag = {tag}\nCiphertext = {ciphertext}\n") return "\n".join(output) def write_file(data, filename): with open(filename, "w") as f: f.write(data) path = "vectors/cryptography_vectors/ciphers/AES/GCM-SIV/openssl.txt" write_file(build_vectors(path), "aes-192-gcm-siv.txt") cryptography-43.0.0/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/Cargo.toml010064400017510000177000000002151464676315000310710ustar 00000000000000[package] name = "verify-aes192gcmsiv" version = "0.1.0" edition = "2021" [dependencies] aes-gcm-siv = "0.11.1" aes = "0.8.1" hex = "0.4.3" cryptography-43.0.0/docs/development/custom-vectors/aes-192-gcm-siv/verify-aes192gcmsiv/src/main.rs010064400017510000177000000066761464676315000312430ustar 00000000000000use aes_gcm_siv::{ aead::{Aead, KeyInit}, AesGcmSiv, Nonce, }; use aes::Aes192; use aes_gcm_siv::aead::generic_array::GenericArray; use aes_gcm_siv::aead::Payload; use std::fs::File; use std::io; use std::io::BufRead; use std::path::Path; pub type Aes192GcmSiv = AesGcmSiv; struct VectorArgs { nonce: String, key: String, aad: String, tag: String, plaintext: String, ciphertext: String, } fn validate(v: &VectorArgs) { let key_bytes = hex::decode(&v.key).unwrap(); let nonce_bytes = hex::decode(&v.nonce).unwrap(); let aad_bytes = hex::decode(&v.aad).unwrap(); let plaintext_bytes = hex::decode(&v.plaintext).unwrap(); let expected_ciphertext_bytes = hex::decode(&v.ciphertext).unwrap(); let expected_tag_bytes = hex::decode(&v.tag).unwrap(); let key_array: [u8; 24] = key_bytes.try_into().unwrap(); let cipher = Aes192GcmSiv::new(&GenericArray::from(key_array)); let payload = Payload { msg: plaintext_bytes.as_slice(), aad: aad_bytes.as_slice(), }; let encrypted_bytes = cipher .encrypt(Nonce::from_slice(nonce_bytes.as_slice()), payload) .unwrap(); let (ciphertext_bytes, tag_bytes) = encrypted_bytes.split_at(plaintext_bytes.len()); assert_eq!(ciphertext_bytes, expected_ciphertext_bytes); assert_eq!(tag_bytes, expected_tag_bytes); } fn validate_vectors(filename: &Path) { let file = File::open(filename).expect("Failed to open file"); let reader = io::BufReader::new(file); let mut vector: Option = None; for line in reader.lines() { let line = line.expect("Failed to read line"); let segments: Vec<&str> = line.splitn(2, " = ").collect(); match segments.first() { Some(&"COUNT") => { if let Some(v) = vector.take() { validate(&v); } vector = Some(VectorArgs { nonce: String::new(), key: String::new(), aad: String::new(), tag: String::new(), plaintext: String::new(), ciphertext: String::new(), }); } Some(&"IV") => { if let Some(v) = &mut vector { v.nonce = segments[1].parse().expect("Failed to parse IV"); } } Some(&"Key") => { if let Some(v) = &mut vector { v.key = segments[1].to_string(); } } Some(&"AAD") => { if let Some(v) = &mut vector { v.aad = segments[1].to_string(); } } Some(&"Tag") => { if let Some(v) = &mut vector { v.tag = segments[1].to_string(); } } Some(&"Plaintext") => { if let Some(v) = &mut vector { v.plaintext = segments[1].to_string(); } } Some(&"Ciphertext") => { if let Some(v) = &mut vector { v.ciphertext = segments[1].to_string(); } } _ => {} } } if let Some(v) = vector { validate(&v); } } fn main() { validate_vectors(Path::new( "vectors/cryptography_vectors/ciphers/AES/GCM-SIV/aes-192-gcm-siv.txt", )); println!("AES-192-GCM-SIV OK.") } cryptography-43.0.0/docs/development/custom-vectors/arc4.rst010064400017510000177000000015101464676315000223600ustar 00000000000000ARC4 vector creation ==================== This page documents the code that was used to generate the ARC4 test vectors for key lengths not available in :rfc:`6229`. All the vectors were generated using OpenSSL and verified with Go. Creation -------- ``cryptography`` was modified to support ARC4 key lengths not listed in :rfc:`6229`. Then the following Python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/arc4/generate_arc4.py Download link: :download:`generate_arc4.py ` Verification ------------ The following Go code was used to verify the vectors. .. literalinclude:: /development/custom-vectors/arc4/verify_arc4.go :language: go Download link: :download:`verify_arc4.go ` cryptography-43.0.0/docs/development/custom-vectors/arc4/generate_arc4.py010064400017510000177000000046031464676315000247110ustar 00000000000000# 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. import binascii from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import algorithms _RFC6229_KEY_MATERIALS = [ ( True, 8 * "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", ), ( False, 8 * "1ada31d5cf688221c109163908ebe51debb46227c6cc8b37641910833222772a", ), ] _RFC6229_OFFSETS = [ 0, 16, 240, 256, 496, 512, 752, 768, 1008, 1024, 1520, 1536, 2032, 2048, 3056, 3072, 4080, 4096, ] _SIZES_TO_GENERATE = [160] def _key_for_size(size, keyinfo): msb, key = keyinfo if msb: return key[: size // 4] else: return key[-size // 4 :] def _build_vectors(): count = 0 output = [] key = None plaintext = binascii.unhexlify(32 * "0") for size in _SIZES_TO_GENERATE: for keyinfo in _RFC6229_KEY_MATERIALS: key = _key_for_size(size, keyinfo) cipher = ciphers.Cipher( algorithms.ARC4(binascii.unhexlify(key)), None, ) encryptor = cipher.encryptor() current_offset = 0 for offset in _RFC6229_OFFSETS: if offset % 16 != 0: raise ValueError( f"Offset {offset} is not evenly divisible by 16" ) while current_offset < offset: encryptor.update(plaintext) current_offset += len(plaintext) output.append(f"\nCOUNT = {count}") count += 1 output.append(f"KEY = {key}") output.append(f"OFFSET = {offset}") output.append(f"PLAINTEXT = {binascii.hexlify(plaintext)}") output.append( f"CIPHERTEXT = " f"{binascii.hexlify(encryptor.update(plaintext))}" ) current_offset += len(plaintext) assert not encryptor.finalize() return "\n".join(output) def _write_file(data, filename): with open(filename, "w") as f: f.write(data) if __name__ == "__main__": _write_file(_build_vectors(), "arc4.txt") cryptography-43.0.0/docs/development/custom-vectors/arc4/verify_arc4.go010064400017510000177000000051121464676315000243740ustar 00000000000000package main import ( "bufio" "bytes" "crypto/rc4" "encoding/hex" "fmt" "os" "strconv" "strings" ) func unhexlify(s string) []byte { bytes, err := hex.DecodeString(s) if err != nil { panic(err) } return bytes } type vectorArgs struct { count string offset uint64 key string plaintext string ciphertext string } type vectorVerifier interface { validate(count string, offset uint64, key, plaintext, expectedCiphertext []byte) } type arc4Verifier struct{} func (o arc4Verifier) validate(count string, offset uint64, key, plaintext, expectedCiphertext []byte) { if offset%16 != 0 || len(plaintext) != 16 || len(expectedCiphertext) != 16 { panic(fmt.Errorf("Unexpected input value encountered: offset=%v; len(plaintext)=%v; len(expectedCiphertext)=%v", offset, len(plaintext), len(expectedCiphertext))) } stream, err := rc4.NewCipher(key) if err != nil { panic(err) } var currentOffset uint64 = 0 ciphertext := make([]byte, len(plaintext)) for currentOffset <= offset { stream.XORKeyStream(ciphertext, plaintext) currentOffset += uint64(len(plaintext)) } if !bytes.Equal(ciphertext, expectedCiphertext) { panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", count, hex.EncodeToString(expectedCiphertext), hex.EncodeToString(ciphertext))) } } func validateVectors(verifier vectorVerifier, filename string) { vectors, err := os.Open(filename) if err != nil { panic(err) } defer vectors.Close() var segments []string var vector *vectorArgs scanner := bufio.NewScanner(vectors) for scanner.Scan() { segments = strings.Split(scanner.Text(), " = ") switch { case strings.ToUpper(segments[0]) == "COUNT": if vector != nil { verifier.validate(vector.count, vector.offset, unhexlify(vector.key), unhexlify(vector.plaintext), unhexlify(vector.ciphertext)) } vector = &vectorArgs{count: segments[1]} case strings.ToUpper(segments[0]) == "OFFSET": vector.offset, err = strconv.ParseUint(segments[1], 10, 64) if err != nil { panic(err) } case strings.ToUpper(segments[0]) == "KEY": vector.key = segments[1] case strings.ToUpper(segments[0]) == "PLAINTEXT": vector.plaintext = segments[1] case strings.ToUpper(segments[0]) == "CIPHERTEXT": vector.ciphertext = segments[1] } } if vector != nil { verifier.validate(vector.count, vector.offset, unhexlify(vector.key), unhexlify(vector.plaintext), unhexlify(vector.ciphertext)) } } func main() { validateVectors(arc4Verifier{}, "vectors/cryptography_vectors/ciphers/ARC4/arc4.txt") fmt.Println("ARC4 OK.") } cryptography-43.0.0/docs/development/custom-vectors/cast5.rst010064400017510000177000000017441464676315000225570ustar 00000000000000CAST5 vector creation ===================== This page documents the code that was used to generate the CAST5 CBC, CFB, OFB, and CTR test vectors as well as the code used to verify them against another implementation. The CBC, CFB, and OFB vectors were generated using OpenSSL and the CTR vectors were generated using Apple's CommonCrypto. All the generated vectors were verified with Go. Creation -------- ``cryptography`` was modified to support CAST5 in CBC, CFB, and OFB modes. Then the following Python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/cast5/generate_cast5.py Download link: :download:`generate_cast5.py ` Verification ------------ The following Go code was used to verify the vectors. .. literalinclude:: /development/custom-vectors/cast5/verify_cast5.go :language: go Download link: :download:`verify_cast5.go ` cryptography-43.0.0/docs/development/custom-vectors/cast5/generate_cast5.py010064400017510000177000000043141464676315000252640ustar 00000000000000# 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. import binascii from cryptography.hazmat.primitives.ciphers import algorithms, base, modes def encrypt(mode, key, iv, plaintext): cipher = base.Cipher( algorithms.CAST5(binascii.unhexlify(key)), mode(binascii.unhexlify(iv)), ) encryptor = cipher.encryptor() ct = encryptor.update(binascii.unhexlify(plaintext)) ct += encryptor.finalize() return binascii.hexlify(ct) def build_vectors(mode, filename): count = 0 output = [] key = None iv = None plaintext = None with open(filename) as vector_file: for line in vector_file: line = line.strip() if line.startswith("KEY"): if count != 0: output.append( f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) output.append(f"\nCOUNT = {count}") count += 1 _, key = line.split(" = ") output.append(f"KEY = {key}") elif line.startswith("IV"): _, iv = line.split(" = ") iv = iv[0:16] output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): _, plaintext = line.split(" = ") output.append(f"PLAINTEXT = {plaintext}") output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) def write_file(data, filename): with open(filename, "w") as f: f.write(data) cbc_path = "tests/hazmat/primitives/vectors/ciphers/AES/CBC/CBCMMT128.rsp" write_file(build_vectors(modes.CBC, cbc_path), "cast5-cbc.txt") ofb_path = "tests/hazmat/primitives/vectors/ciphers/AES/OFB/OFBMMT128.rsp" write_file(build_vectors(modes.OFB, ofb_path), "cast5-ofb.txt") cfb_path = "tests/hazmat/primitives/vectors/ciphers/AES/CFB/CFB128MMT128.rsp" write_file(build_vectors(modes.CFB, cfb_path), "cast5-cfb.txt") ctr_path = "tests/hazmat/primitives/vectors/ciphers/AES/CTR/aes-128-ctr.txt" write_file(build_vectors(modes.CTR, ctr_path), "cast5-ctr.txt") cryptography-43.0.0/docs/development/custom-vectors/cast5/verify_cast5.go010064400017510000177000000076751464676315000247700ustar 00000000000000package main import ( "bufio" "bytes" "golang.org/x/crypto/cast5" "crypto/cipher" "encoding/hex" "fmt" "os" "strings" ) func unhexlify(s string) []byte { bytes, err := hex.DecodeString(s) if err != nil { panic(err) } return bytes } type vectorArgs struct { count string key string iv string plaintext string ciphertext string } type vectorVerifier interface { validate(count string, key, iv, plaintext, expectedCiphertext []byte) } type ofbVerifier struct{} func (o ofbVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { block, err := cast5.NewCipher(key) if err != nil { panic(err) } ciphertext := make([]byte, len(plaintext)) stream := cipher.NewOFB(block, iv) stream.XORKeyStream(ciphertext, plaintext) if !bytes.Equal(ciphertext, expectedCiphertext) { panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", count, hex.EncodeToString(expectedCiphertext), hex.EncodeToString(ciphertext))) } } type cbcVerifier struct{} func (o cbcVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { block, err := cast5.NewCipher(key) if err != nil { panic(err) } ciphertext := make([]byte, len(plaintext)) mode := cipher.NewCBCEncrypter(block, iv) mode.CryptBlocks(ciphertext, plaintext) if !bytes.Equal(ciphertext, expectedCiphertext) { panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", count, hex.EncodeToString(expectedCiphertext), hex.EncodeToString(ciphertext))) } } type cfbVerifier struct{} func (o cfbVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { block, err := cast5.NewCipher(key) if err != nil { panic(err) } ciphertext := make([]byte, len(plaintext)) stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(ciphertext, plaintext) if !bytes.Equal(ciphertext, expectedCiphertext) { panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", count, hex.EncodeToString(expectedCiphertext), hex.EncodeToString(ciphertext))) } } type ctrVerifier struct{} func (o ctrVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { block, err := cast5.NewCipher(key) if err != nil { panic(err) } ciphertext := make([]byte, len(plaintext)) stream := cipher.NewCTR(block, iv) stream.XORKeyStream(ciphertext, plaintext) if !bytes.Equal(ciphertext, expectedCiphertext) { panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", count, hex.EncodeToString(expectedCiphertext), hex.EncodeToString(ciphertext))) } } func validateVectors(verifier vectorVerifier, filename string) { vectors, err := os.Open(filename) if err != nil { panic(err) } defer vectors.Close() var segments []string var vector *vectorArgs scanner := bufio.NewScanner(vectors) for scanner.Scan() { segments = strings.Split(scanner.Text(), " = ") switch { case strings.ToUpper(segments[0]) == "COUNT": if vector != nil { verifier.validate(vector.count, unhexlify(vector.key), unhexlify(vector.iv), unhexlify(vector.plaintext), unhexlify(vector.ciphertext)) } vector = &vectorArgs{count: segments[1]} case strings.ToUpper(segments[0]) == "IV": vector.iv = segments[1][:16] case strings.ToUpper(segments[0]) == "KEY": vector.key = segments[1] case strings.ToUpper(segments[0]) == "PLAINTEXT": vector.plaintext = segments[1] case strings.ToUpper(segments[0]) == "CIPHERTEXT": vector.ciphertext = segments[1] } } } func main() { validateVectors(ofbVerifier{}, "vectors/cryptography_vectors/ciphers/CAST5/cast5-ofb.txt") fmt.Println("OFB OK.") validateVectors(cfbVerifier{}, "vectors/cryptography_vectors/ciphers/CAST5/cast5-cfb.txt") fmt.Println("CFB OK.") validateVectors(cbcVerifier{}, "vectors/cryptography_vectors/ciphers/CAST5/cast5-cbc.txt") fmt.Println("CBC OK.") validateVectors(ctrVerifier{}, "vectors/cryptography_vectors/ciphers/CAST5/cast5-ctr.txt") fmt.Println("CTR OK.") } cryptography-43.0.0/docs/development/custom-vectors/chacha20.rst010064400017510000177000000016521464676315000231070ustar 00000000000000ChaCha20 vector creation ======================== This page documents the code that was used to generate the vectors to test the counter overflow behavior in ChaCha20 as well as code used to verify them against another implementation. Creation -------- The following Python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/chacha20/generate_chacha20_overflow.py Download link: :download:`generate_chacha20_overflow.py ` Verification ------------ The following Python script was used to verify the vectors. The counter overflow is handled manually to avoid relying on the same code that generated the vectors. .. literalinclude:: /development/custom-vectors/chacha20/verify_chacha20_overflow.py Download link: :download:`verify_chacha20_overflow.py ` cryptography-43.0.0/docs/development/custom-vectors/chacha20/generate_chacha20_overflow.py010064400017510000177000000027641464676315000301020ustar 00000000000000# 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. import binascii import struct from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import algorithms _N_BLOCKS = [1, 1.5, 2, 2.5, 3] _INITIAL_COUNTERS = [2**32 - 1, 2**64 - 1] def _build_vectors(): count = 0 output = [] key = "0" * 64 nonce = "0" * 16 for blocks in _N_BLOCKS: plaintext = binascii.unhexlify("0" * int(128 * blocks)) for counter in _INITIAL_COUNTERS: full_nonce = struct.pack(" bytes: full_nonce = struct.pack("` Verification ------------ The following Go code was used to verify the vectors. .. literalinclude:: /development/custom-vectors/hkdf/verify_hkdf.go :language: go Download link: :download:`verify_hkdf.go ` cryptography-43.0.0/docs/development/custom-vectors/hkdf/generate_hkdf.py010064400017510000177000000016701464676315000250600ustar 00000000000000# 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. import binascii from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF IKM = binascii.unhexlify(b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") L = 1200 OKM = HKDF( algorithm=hashes.SHA256(), length=L, salt=None, info=None, ).derive(IKM) def _build_vectors(): output = [ "COUNT = 0", "Hash = SHA-256", "IKM = " + binascii.hexlify(IKM).decode("ascii"), "salt = ", "info = ", f"L = {L}", "OKM = " + binascii.hexlify(OKM).decode("ascii"), ] return "\n".join(output) def _write_file(data, filename): with open(filename, "w") as f: f.write(data) if __name__ == "__main__": _write_file(_build_vectors(), "hkdf.txt") cryptography-43.0.0/docs/development/custom-vectors/hkdf/verify_hkdf.go010064400017510000177000000024241464676315000245450ustar 00000000000000package main import ( "bufio" "bytes" "crypto/sha256" "encoding/hex" "fmt" "golang.org/x/crypto/hkdf" "io" "os" "strconv" "strings" ) func unhexlify(s string) []byte { bytes, err := hex.DecodeString(s) if err != nil { panic(err) } return bytes } func verifier(l uint64, ikm, okm []byte) bool { hash := sha256.New hkdf := hkdf.New(hash, ikm, nil, nil) okmComputed := make([]byte, l) io.ReadFull(hkdf, okmComputed) return bytes.Equal(okmComputed, okm) } func validateVectors(filename string) bool { vectors, err := os.Open(filename) if err != nil { panic(err) } defer vectors.Close() var segments []string var l uint64 var ikm, okm string scanner := bufio.NewScanner(vectors) for scanner.Scan() { segments = strings.Split(scanner.Text(), " = ") switch { case strings.ToUpper(segments[0]) == "L": l, err = strconv.ParseUint(segments[1], 10, 64) if err != nil { panic(err) } case strings.ToUpper(segments[0]) == "IKM": ikm = segments[1] case strings.ToUpper(segments[0]) == "OKM": okm = segments[1] } } return verifier(l, unhexlify(ikm), unhexlify(okm)) } func main() { if validateVectors("vectors/cryptography_vectors/KDF/hkdf-generated.txt") { fmt.Println("HKDF OK.") } else { fmt.Println("HKDF failed.") os.Exit(1) } } cryptography-43.0.0/docs/development/custom-vectors/idea.rst010064400017510000177000000016651464676315000224440ustar 00000000000000IDEA vector creation ===================== This page documents the code that was used to generate the IDEA CBC, CFB, and OFB test vectors as well as the code used to verify them against another implementation. The vectors were generated using OpenSSL and verified with `Botan`_. Creation -------- ``cryptography`` was modified to support IDEA in CBC, CFB, and OFB modes. Then the following python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/idea/generate_idea.py Download link: :download:`generate_idea.py ` Verification ------------ The following Python code was used to verify the vectors using the `Botan`_ project's Python bindings. .. literalinclude:: /development/custom-vectors/idea/verify_idea.py Download link: :download:`verify_idea.py ` .. _`Botan`: https://botan.randombit.net cryptography-43.0.0/docs/development/custom-vectors/idea/generate_idea.py010064400017510000177000000035351464676315000250360ustar 00000000000000import binascii from cryptography.hazmat.primitives.ciphers import algorithms, base, modes def encrypt(mode, key, iv, plaintext): cipher = base.Cipher( algorithms.IDEA(binascii.unhexlify(key)), mode(binascii.unhexlify(iv)), ) encryptor = cipher.encryptor() ct = encryptor.update(binascii.unhexlify(plaintext)) ct += encryptor.finalize() return binascii.hexlify(ct) def build_vectors(mode, filename): with open(filename) as f: vector_file = f.read().splitlines() count = 0 output = [] key = None iv = None plaintext = None for line in vector_file: line = line.strip() if line.startswith("KEY"): if count != 0: output.append( f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) output.append(f"\nCOUNT = {count}") count += 1 _, key = line.split(" = ") output.append(f"KEY = {key}") elif line.startswith("IV"): _, iv = line.split(" = ") iv = iv[0:16] output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): _, plaintext = line.split(" = ") output.append(f"PLAINTEXT = {plaintext}") output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) def write_file(data, filename): with open(filename, "w") as f: f.write(data) CBC_PATH = "tests/hazmat/primitives/vectors/ciphers/AES/CBC/CBCMMT128.rsp" write_file(build_vectors(modes.CBC, CBC_PATH), "idea-cbc.txt") OFB_PATH = "tests/hazmat/primitives/vectors/ciphers/AES/OFB/OFBMMT128.rsp" write_file(build_vectors(modes.OFB, OFB_PATH), "idea-ofb.txt") CFB_PATH = "tests/hazmat/primitives/vectors/ciphers/AES/CFB/CFB128MMT128.rsp" write_file(build_vectors(modes.CFB, CFB_PATH), "idea-cfb.txt") cryptography-43.0.0/docs/development/custom-vectors/idea/verify_idea.py010064400017510000177000000017451464676315000245510ustar 00000000000000import binascii import botan from tests.utils import load_nist_vectors BLOCK_SIZE = 64 def encrypt(mode, key, iv, plaintext): encryptor = botan.Cipher( f"IDEA/{mode}/NoPadding", "encrypt", binascii.unhexlify(key) ) cipher_text = encryptor.cipher( binascii.unhexlify(plaintext), binascii.unhexlify(iv) ) return binascii.hexlify(cipher_text) def verify_vectors(mode, filename): with open(filename) as f: vector_file = f.read().splitlines() vectors = load_nist_vectors(vector_file) for vector in vectors: ct = encrypt(mode, vector["key"], vector["iv"], vector["plaintext"]) assert ct == vector["ciphertext"] cbc_path = "tests/hazmat/primitives/vectors/ciphers/IDEA/idea-cbc.txt" verify_vectors("CBC", cbc_path) ofb_path = "tests/hazmat/primitives/vectors/ciphers/IDEA/idea-ofb.txt" verify_vectors("OFB", ofb_path) cfb_path = "tests/hazmat/primitives/vectors/ciphers/IDEA/idea-cfb.txt" verify_vectors("CFB", cfb_path) cryptography-43.0.0/docs/development/custom-vectors/rc2.rst010064400017510000177000000015471464676315000222270ustar 00000000000000RC2 vector creation =================== This page documents the code that was used to generate the RC2 CBC test vector. The CBC vector was generated using Go's internal RC2 implementation and verified using Go and OpenSSL. Creation/Verification --------------------- The program below outputs a test vector in the standard format we use and also verifies that the encrypted value round trips as expected. The output was also checked against OpenSSL by modifying ``cryptography`` to support the algorithm. If you wish to run this program we recommend cloning the repository, which also contains the requisite ``go.mod`` file. .. literalinclude:: /development/custom-vectors/rc2/genrc2.go :language: go Download link: :download:`genrc2.go ` Download link: :download:`rc2.go ` cryptography-43.0.0/docs/development/custom-vectors/rc2/genrc2.go010064400017510000177000000016461464676315000232040ustar 00000000000000package main import ( "bytes" "crypto/cipher" "encoding/hex" "fmt" "rc2sucks/rc2" ) func main() { // Generate count := 1 key := []byte("0000000000000000") iv := []byte("00000000") plaintext := []byte("the quick brown fox jumped over the lazy dog!!!!") ciphertext := make([]byte, len(plaintext)) block, _ := rc2.New(key, 128) mode := cipher.NewCBCEncrypter(block, iv) mode.CryptBlocks(ciphertext, plaintext) fmt.Printf("COUNT = %v\n", count) fmt.Printf("Key = %s\n", hex.EncodeToString(key)) fmt.Printf("IV = %s\n", hex.EncodeToString(iv)) fmt.Printf("Plaintext = %s\n", hex.EncodeToString(plaintext)) fmt.Printf("Ciphertext = %s\n", hex.EncodeToString(ciphertext)) // Verify decrypted := make([]byte, len(plaintext)) decmode := cipher.NewCBCDecrypter(block, iv) decmode.CryptBlocks(decrypted, ciphertext) if bytes.Equal(decrypted, plaintext) { fmt.Println("Success") } else { fmt.Println("Failed") } } cryptography-43.0.0/docs/development/custom-vectors/rc2/go.mod010064400017510000177000000000331464676315000225700ustar 00000000000000module rc2sucks go 1.21.7 cryptography-43.0.0/docs/development/custom-vectors/rc2/rc2/rc2.go010064400017510000177000000147421464676315000232010ustar 00000000000000// From https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.19.0:pkcs12/internal/rc2/rc2.go // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package rc2 implements the RC2 cipher /* https://www.ietf.org/rfc/rfc2268.txt http://people.csail.mit.edu/rivest/pubs/KRRR98.pdf This code is licensed under the MIT license. */ package rc2 import ( "crypto/cipher" "encoding/binary" "math/bits" ) // The rc2 block size in bytes const BlockSize = 8 type rc2Cipher struct { k [64]uint16 } // New returns a new rc2 cipher with the given key and effective key length t1 func New(key []byte, t1 int) (cipher.Block, error) { // TODO(dgryski): error checking for key length return &rc2Cipher{ k: expandKey(key, t1), }, nil } func (*rc2Cipher) BlockSize() int { return BlockSize } var piTable = [256]byte{ 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad, } func expandKey(key []byte, t1 int) [64]uint16 { l := make([]byte, 128) copy(l, key) var t = len(key) var t8 = (t1 + 7) / 8 var tm = byte(255 % uint(1<<(8+uint(t1)-8*uint(t8)))) for i := len(key); i < 128; i++ { l[i] = piTable[l[i-1]+l[uint8(i-t)]] } l[128-t8] = piTable[l[128-t8]&tm] for i := 127 - t8; i >= 0; i-- { l[i] = piTable[l[i+1]^l[i+t8]] } var k [64]uint16 for i := range k { k[i] = uint16(l[2*i]) + uint16(l[2*i+1])*256 } return k } func (c *rc2Cipher) Encrypt(dst, src []byte) { r0 := binary.LittleEndian.Uint16(src[0:]) r1 := binary.LittleEndian.Uint16(src[2:]) r2 := binary.LittleEndian.Uint16(src[4:]) r3 := binary.LittleEndian.Uint16(src[6:]) var j int for j <= 16 { // mix r0 r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) r0 = bits.RotateLeft16(r0, 1) j++ // mix r1 r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) r1 = bits.RotateLeft16(r1, 2) j++ // mix r2 r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) r2 = bits.RotateLeft16(r2, 3) j++ // mix r3 r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) r3 = bits.RotateLeft16(r3, 5) j++ } r0 = r0 + c.k[r3&63] r1 = r1 + c.k[r0&63] r2 = r2 + c.k[r1&63] r3 = r3 + c.k[r2&63] for j <= 40 { // mix r0 r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) r0 = bits.RotateLeft16(r0, 1) j++ // mix r1 r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) r1 = bits.RotateLeft16(r1, 2) j++ // mix r2 r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) r2 = bits.RotateLeft16(r2, 3) j++ // mix r3 r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) r3 = bits.RotateLeft16(r3, 5) j++ } r0 = r0 + c.k[r3&63] r1 = r1 + c.k[r0&63] r2 = r2 + c.k[r1&63] r3 = r3 + c.k[r2&63] for j <= 60 { // mix r0 r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) r0 = bits.RotateLeft16(r0, 1) j++ // mix r1 r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) r1 = bits.RotateLeft16(r1, 2) j++ // mix r2 r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) r2 = bits.RotateLeft16(r2, 3) j++ // mix r3 r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) r3 = bits.RotateLeft16(r3, 5) j++ } binary.LittleEndian.PutUint16(dst[0:], r0) binary.LittleEndian.PutUint16(dst[2:], r1) binary.LittleEndian.PutUint16(dst[4:], r2) binary.LittleEndian.PutUint16(dst[6:], r3) } func (c *rc2Cipher) Decrypt(dst, src []byte) { r0 := binary.LittleEndian.Uint16(src[0:]) r1 := binary.LittleEndian.Uint16(src[2:]) r2 := binary.LittleEndian.Uint16(src[4:]) r3 := binary.LittleEndian.Uint16(src[6:]) j := 63 for j >= 44 { // unmix r3 r3 = bits.RotateLeft16(r3, 16-5) r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) j-- // unmix r2 r2 = bits.RotateLeft16(r2, 16-3) r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) j-- // unmix r1 r1 = bits.RotateLeft16(r1, 16-2) r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) j-- // unmix r0 r0 = bits.RotateLeft16(r0, 16-1) r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) j-- } r3 = r3 - c.k[r2&63] r2 = r2 - c.k[r1&63] r1 = r1 - c.k[r0&63] r0 = r0 - c.k[r3&63] for j >= 20 { // unmix r3 r3 = bits.RotateLeft16(r3, 16-5) r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) j-- // unmix r2 r2 = bits.RotateLeft16(r2, 16-3) r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) j-- // unmix r1 r1 = bits.RotateLeft16(r1, 16-2) r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) j-- // unmix r0 r0 = bits.RotateLeft16(r0, 16-1) r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) j-- } r3 = r3 - c.k[r2&63] r2 = r2 - c.k[r1&63] r1 = r1 - c.k[r0&63] r0 = r0 - c.k[r3&63] for j >= 0 { // unmix r3 r3 = bits.RotateLeft16(r3, 16-5) r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) j-- // unmix r2 r2 = bits.RotateLeft16(r2, 16-3) r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) j-- // unmix r1 r1 = bits.RotateLeft16(r1, 16-2) r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) j-- // unmix r0 r0 = bits.RotateLeft16(r0, 16-1) r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) j-- } binary.LittleEndian.PutUint16(dst[0:], r0) binary.LittleEndian.PutUint16(dst[2:], r1) binary.LittleEndian.PutUint16(dst[4:], r2) binary.LittleEndian.PutUint16(dst[6:], r3) } cryptography-43.0.0/docs/development/custom-vectors/rsa-oaep-sha2.rst010064400017510000177000000032571464676315000241030ustar 00000000000000RSA OAEP SHA2 vector creation ============================= This page documents the code that was used to generate the RSA OAEP SHA2 test vectors as well as code used to verify them against another implementation. Creation -------- ``cryptography`` was modified to allow the use of SHA2 in OAEP encryption. Then the following python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py Download link: :download:`generate_rsa_oaep_sha2.py ` Verification ------------ A Java 8 program was written using `Bouncy Castle`_ to load and verify the test vectors. .. literalinclude:: /development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java Download link: :download:`VerifyRSAOAEPSHA2.java ` Using the Verifier ------------------ Download and install the `Java SDK`_. Initial verification was performed using ``jdk-8u77-macosx-x64.dmg``. Download the latest `Bouncy Castle`_ JAR. Initial verification was performed using ``bcprov-jdk15on-154.jar``. Set the ``-classpath`` to include the Bouncy Castle jar and the path to ``VerifyRSAOAEPSHA2.java`` and compile the program. .. code-block:: console $ javac -classpath ~/Downloads/bcprov-jdk15on-154.jar:./ VerifyRSAOAEPSHA2.java Finally, run the program with the path to the SHA-2 vectors: .. code-block:: console $ java -classpath ~/Downloads/bcprov-jdk15on-154.jar:./ VerifyRSAOAEPSHA2 .. _`Bouncy Castle`: https://www.bouncycastle.org/ .. _`Java SDK`: https://www.oracle.com/java/technologies/javase-downloads.html cryptography-43.0.0/docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java010064400017510000177000000341701464676315000271070ustar 00000000000000import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.Security; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.MGF1ParameterSpec; import java.security.spec.RSAPrivateKeySpec; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.PSource; import javax.xml.bind.DatatypeConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; class TestVectorData { public BigInteger pub_key_modulus; public BigInteger pub_key_exponent; public BigInteger priv_key_public_exponent; public BigInteger priv_key_modulus; public BigInteger priv_key_exponent; public BigInteger priv_key_prime_1; public BigInteger priv_key_prime_2; public BigInteger priv_key_prime_exponent_1; public BigInteger priv_key_prime_exponent_2; public BigInteger priv_key_coefficient; public byte[] plaintext; public byte[] ciphertext; } class TestVectorLoader { private static final String FILE_HEADER = "# RSA OAEP SHA2 vectors built"; private static final String EXAMPLE_HEADER = "# ====="; private static final String EXAMPLE = "# Example"; private static final String PUBLIC_KEY = "# Public key"; private static final String PUB_MODULUS = "# Modulus:"; private static final String PUB_EXPONENT = "# Exponent:"; private static final String PRIVATE_KEY = "# Private key"; private static final String PRIV_MODULUS = "# Modulus:"; private static final String PRIV_PUBLIC_EXPONENT = "# Public exponent:"; private static final String PRIV_EXPONENT = "# Exponent:"; private static final String PRIV_PRIME_1 = "# Prime 1:"; private static final String PRIV_PRIME_2 = "# Prime 2:"; private static final String PRIV_PRIME_EXPONENT_1 = "# Prime exponent 1:"; private static final String PRIV_PRIME_EXPONENT_2 = "# Prime exponent 2:"; private static final String PRIV_COEFFICIENT = "# Coefficient:"; private static final String OAEP_EXAMPLE_HEADER = "# OAEP Example"; private static final String MESSAGE = "# Message:"; private static final String ENCRYPTION = "# Encryption:"; private BufferedReader m_reader = null; private FileReader m_file_reader = null; private TestVectorData m_data = null; TestVectorLoader() { } protected void finalize() { close(); } public void open(String path) throws IOException { close(); m_file_reader = new FileReader(path); m_reader = new BufferedReader(m_file_reader); m_data = new TestVectorData(); } public void close() { try { if (m_reader != null) { m_reader.close(); m_reader = null; } if (m_file_reader != null) { m_file_reader.close(); m_file_reader = null; } m_data = null; } catch (IOException e) { System.out.println("Exception closing files"); e.printStackTrace(); } } public TestVectorData loadNextTest() throws IOException { if (m_file_reader == null || m_reader == null || m_data == null) { throw new IOException("A test vector file must be opened first"); } String line = m_reader.readLine(); if (line == null) { // end of file return null; } if (line.startsWith(FILE_HEADER)) { // start of file skipFileHeader(m_reader); line = m_reader.readLine(); } if (line.startsWith(OAEP_EXAMPLE_HEADER)) { // Next example, keep existing keys and load next message loadMessage(m_reader, m_data); return m_data; } // otherwise it's a new example if (!line.startsWith(EXAMPLE_HEADER)) { throw new IOException("Test Header Missing"); } startNewTest(m_reader); m_data = new TestVectorData(); line = m_reader.readLine(); if (!line.startsWith(PUBLIC_KEY)) throw new IOException("Public Key Missing"); loadPublicKey(m_reader, m_data); line = m_reader.readLine(); if (!line.startsWith(PRIVATE_KEY)) throw new IOException("Private Key Missing"); loadPrivateKey(m_reader, m_data); line = m_reader.readLine(); if (!line.startsWith(OAEP_EXAMPLE_HEADER)) throw new IOException("Message Missing"); loadMessage(m_reader, m_data); return m_data; } private byte[] unhexlify(String line) { byte[] bytes = DatatypeConverter.parseHexBinary(line); return bytes; } private BigInteger readBigInteger(BufferedReader br) throws IOException { return new BigInteger(br.readLine(), 16); } private void skipFileHeader(BufferedReader br) throws IOException { br.readLine(); // # # Derived from the NIST OAEP SHA1 vectors br.readLine(); // # # Verified against the Bouncy Castle OAEP SHA2 implementation br.readLine(); // # } private void startNewTest(BufferedReader br) throws IOException { String line = br.readLine(); if (!line.startsWith(EXAMPLE)) throw new IOException("Example Header Missing"); } private void loadPublicKey(BufferedReader br, TestVectorData data) throws IOException { String line = br.readLine(); if (!line.startsWith(PUB_MODULUS)) throw new IOException("Public Key Modulus Missing"); data.pub_key_modulus = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PUB_EXPONENT)) throw new IOException("Public Key Exponent Missing"); data.pub_key_exponent = readBigInteger(br); } private void loadPrivateKey(BufferedReader br, TestVectorData data) throws IOException { String line = br.readLine(); if (!line.startsWith(PRIV_MODULUS)) throw new IOException("Private Key Modulus Missing"); data.priv_key_modulus = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PRIV_PUBLIC_EXPONENT)) throw new IOException("Private Key Public Exponent Missing"); data.priv_key_public_exponent = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PRIV_EXPONENT)) throw new IOException("Private Key Exponent Missing"); data.priv_key_exponent = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PRIV_PRIME_1)) throw new IOException("Private Key Prime 1 Missing"); data.priv_key_prime_1 = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PRIV_PRIME_2)) throw new IOException("Private Key Prime 2 Missing"); data.priv_key_prime_2 = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PRIV_PRIME_EXPONENT_1)) throw new IOException("Private Key Prime Exponent 1 Missing"); data.priv_key_prime_exponent_1 = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PRIV_PRIME_EXPONENT_2)) throw new IOException("Private Key Prime Exponent 2 Missing"); data.priv_key_prime_exponent_2 = readBigInteger(br); line = br.readLine(); if (!line.startsWith(PRIV_COEFFICIENT)) throw new IOException("Private Key Coefficient Missing"); data.priv_key_coefficient = readBigInteger(br); } private void loadMessage(BufferedReader br, TestVectorData data) throws IOException { String line = br.readLine(); if (!line.startsWith(MESSAGE)) throw new IOException("Plaintext Missing"); data.plaintext = unhexlify(br.readLine()); line = br.readLine(); if (!line.startsWith(ENCRYPTION)) throw new IOException("Ciphertext Missing"); data.ciphertext = unhexlify(br.readLine()); } } public class VerifyRSAOAEPSHA2 { public enum SHAHash { SHA1, SHA224, SHA256, SHA384, SHA512 } private SHAHash m_mgf1_hash; private SHAHash m_alg_hash; private Cipher m_cipher; private PrivateKey m_private_key; private AlgorithmParameters m_algo_param; VerifyRSAOAEPSHA2(SHAHash mgf1_hash, SHAHash alg_hash, TestVectorData test_data) throws Exception { m_mgf1_hash = mgf1_hash; m_alg_hash = alg_hash; MGF1ParameterSpec mgf1_spec = getMGF1ParameterSpec(m_mgf1_hash); AlgorithmParameterSpec algo_param_spec = getAlgorithmParameterSpec(m_alg_hash, mgf1_spec); m_algo_param = AlgorithmParameters.getInstance("OAEP"); m_algo_param.init(algo_param_spec); m_private_key = loadPrivateKey(test_data); m_cipher = getCipher(m_alg_hash); } private Cipher getCipher(SHAHash alg_hash) throws GeneralSecurityException { Cipher cipher = null; switch (alg_hash) { case SHA1: cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA1andMGF1Padding", "BC"); break; case SHA224: cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-224andMGF1Padding", "BC"); break; case SHA256: cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding", "BC"); break; case SHA384: cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-384andMGF1Padding", "BC"); break; case SHA512: cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-512andMGF1Padding", "BC"); break; } return cipher; } private MGF1ParameterSpec getMGF1ParameterSpec(SHAHash mgf1_hash) { MGF1ParameterSpec mgf1 = null; switch (mgf1_hash) { case SHA1: mgf1 = MGF1ParameterSpec.SHA1; break; case SHA224: mgf1 = MGF1ParameterSpec.SHA224; break; case SHA256: mgf1 = MGF1ParameterSpec.SHA256; break; case SHA384: mgf1 = MGF1ParameterSpec.SHA384; break; case SHA512: mgf1 = MGF1ParameterSpec.SHA512; break; } return mgf1; } private AlgorithmParameterSpec getAlgorithmParameterSpec(SHAHash alg_hash, MGF1ParameterSpec mgf1_spec) { OAEPParameterSpec oaep_spec = null; switch (alg_hash) { case SHA1: oaep_spec = new OAEPParameterSpec("SHA1", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); break; case SHA224: oaep_spec = new OAEPParameterSpec("SHA-224", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); break; case SHA256: oaep_spec = new OAEPParameterSpec("SHA-256", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); break; case SHA384: oaep_spec = new OAEPParameterSpec("SHA-384", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); break; case SHA512: oaep_spec = new OAEPParameterSpec("SHA-512", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); break; } return oaep_spec; } private PrivateKey loadPrivateKey(TestVectorData test_data) throws Exception { KeyFactory kf = KeyFactory.getInstance("RSA"); RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(test_data.priv_key_modulus, test_data.priv_key_exponent); return kf.generatePrivate(keySpec); } public void testDecrypt(byte[] plaintext, byte[] ciphertext) throws Exception { System.out.println("Verifying OAEP with mgf1_hash: " + m_mgf1_hash + " alg_hash: " + m_alg_hash + " - " + ciphertext.length + " bytes ciphertext - " + plaintext.length + " bytes plaintext"); m_cipher.init(Cipher.DECRYPT_MODE, m_private_key, m_algo_param); byte[] java_plaintext = m_cipher.doFinal(ciphertext); if (Arrays.equals(java_plaintext, plaintext) == false) { throw new Exception("Verification failure - plaintext does not match after decryption."); } } public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); // assume current directory if no path given on command line String vector_path = "./vectors/cryptography_vectors/asymmetric/RSA/oaep-custom"; if (args.length > 0) { vector_path = args[0]; } System.out.println("Vector file path: " + vector_path); try { // loop over each combination of hash loading the vector file // to verify for each for (SHAHash mgf1_hash : SHAHash.values()) { for (SHAHash alg_hash : SHAHash.values()) { if (mgf1_hash.name().toLowerCase().equals("sha1") && alg_hash.name().toLowerCase().equals("sha1")) { continue; } String filename = "oaep-" + mgf1_hash.name().toLowerCase() + "-" + alg_hash.name().toLowerCase() + ".txt"; System.out.println("Loading " + filename + "..."); TestVectorLoader loader = new TestVectorLoader(); loader.open(vector_path + filename); TestVectorData test_data; // load each test in the file and verify while ((test_data = loader.loadNextTest()) != null) { VerifyRSAOAEPSHA2 verify = new VerifyRSAOAEPSHA2(mgf1_hash, alg_hash, test_data); verify.testDecrypt(test_data.plaintext, test_data.ciphertext); } System.out.println("Verifying " + filename + " completed successfully."); } } System.out.println("All verification completed successfully"); } catch (Exception e) { // if any exception is thrown the verification has failed e.printStackTrace(); System.out.println("Verification Failed!"); } } } cryptography-43.0.0/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py010064400017510000177000000102511464676315000302730ustar 00000000000000# 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. import binascii import itertools import os from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding, rsa from tests.utils import load_pkcs1_vectors, load_vectors_from_file def build_vectors(mgf1alg, hashalg, filename): vectors = load_vectors_from_file(filename, load_pkcs1_vectors) output = [] for vector in vectors: # RSA keys for this must be long enough to accommodate the length of # the underlying hash function. This means we can't use the keys from # the sha1 test vectors for sha512 tests because 1024-bit keys are too # small. Instead we parse the vectors for the test cases, then # generate our own 2048-bit keys for each. private, _ = vector skey = rsa.generate_private_key(65537, 2048) pn = skey.private_numbers() examples = private["examples"] output.append("# =============================================") output.append("# Example") output.append("# Public key") output.append("# Modulus:") output.append(format(pn.public_numbers.n, "x")) output.append("# Exponent:") output.append(format(pn.public_numbers.e, "x")) output.append("# Private key") output.append("# Modulus:") output.append(format(pn.public_numbers.n, "x")) output.append("# Public exponent:") output.append(format(pn.public_numbers.e, "x")) output.append("# Exponent:") output.append(format(pn.d, "x")) output.append("# Prime 1:") output.append(format(pn.p, "x")) output.append("# Prime 2:") output.append(format(pn.q, "x")) output.append("# Prime exponent 1:") output.append(format(pn.dmp1, "x")) output.append("# Prime exponent 2:") output.append(format(pn.dmq1, "x")) output.append("# Coefficient:") output.append(format(pn.iqmp, "x")) pkey = skey.public_key() vectorkey = rsa.RSAPrivateNumbers( p=private["p"], q=private["q"], d=private["private_exponent"], dmp1=private["dmp1"], dmq1=private["dmq1"], iqmp=private["iqmp"], public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), ).private_key() count = 1 for example in examples: message = vectorkey.decrypt( binascii.unhexlify(example["encryption"]), padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None, ), ) assert message == binascii.unhexlify(example["message"]) ct = pkey.encrypt( message, padding.OAEP( mgf=padding.MGF1(algorithm=mgf1alg), algorithm=hashalg, label=None, ), ) output.append( f"# OAEP Example {count} alg={hashalg.name} " f"mgf1={mgf1alg.name}" ) count += 1 output.append("# Message:") output.append(example["message"].decode("utf-8")) output.append("# Encryption:") output.append(binascii.hexlify(ct).decode("utf-8")) return "\n".join(output) def write_file(data, filename): with open(filename, "w") as f: f.write(data) oaep_path = os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt" ) hashalgs = [ hashes.SHA1(), hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512(), ] for hashtuple in itertools.product(hashalgs, hashalgs): if isinstance(hashtuple[0], hashes.SHA1) and isinstance( hashtuple[1], hashes.SHA1 ): continue write_file( build_vectors(hashtuple[0], hashtuple[1], oaep_path), f"oaep-{hashtuple[0].name}-{hashtuple[1].name}.txt", ) cryptography-43.0.0/docs/development/custom-vectors/secp256k1.rst010064400017510000177000000017461464676315000231650ustar 00000000000000SECP256K1 vector creation ========================= This page documents the code that was used to generate the SECP256K1 elliptic curve test vectors as well as code used to verify them against another implementation. Creation -------- The vectors are generated using a `pure Python ecdsa`_ implementation. The test messages and combinations of algorithms are derived from the NIST vector data. .. literalinclude:: /development/custom-vectors/secp256k1/generate_secp256k1.py Download link: :download:`generate_secp256k1.py ` Verification ------------ ``cryptography`` was modified to support the SECP256K1 curve. Then the following python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/secp256k1/verify_secp256k1.py Download link: :download:`verify_secp256k1.py ` .. _`pure Python ecdsa`: https://pypi.org/project/ecdsa/ cryptography-43.0.0/docs/development/custom-vectors/secp256k1/generate_secp256k1.py010064400017510000177000000045031464676315000262740ustar 00000000000000import hashlib import os from binascii import hexlify from collections import defaultdict from ecdsa import SECP256k1, SigningKey from ecdsa.util import sigdecode_der, sigencode_der from cryptography_vectors import open_vector_file from tests.utils import load_fips_ecdsa_signing_vectors, load_vectors_from_file HASHLIB_HASH_TYPES = { "SHA-1": hashlib.sha1, "SHA-224": hashlib.sha224, "SHA-256": hashlib.sha256, "SHA-384": hashlib.sha384, "SHA-512": hashlib.sha512, } class TruncatedHash: def __init__(self, hasher): self.hasher = hasher def __call__(self, data): self.hasher.update(data) return self def digest(self): return self.hasher.digest()[: 256 // 8] def build_vectors(fips_vectors): vectors = defaultdict(list) for vector in fips_vectors: vectors[vector["digest_algorithm"]].append(vector["message"]) for digest_algorithm, messages in vectors.items(): if digest_algorithm not in HASHLIB_HASH_TYPES: continue yield "" yield f"[K-256,{digest_algorithm}]" yield "" for message in messages: # Make a hash context hash_func = TruncatedHash(HASHLIB_HASH_TYPES[digest_algorithm]()) # Sign the message using warner/ecdsa secret_key = SigningKey.generate(curve=SECP256k1) public_key = secret_key.get_verifying_key() signature = secret_key.sign( message, hashfunc=hash_func, sigencode=sigencode_der ) r, s = sigdecode_der(signature, None) yield f"Msg = {hexlify(message)}" yield f"d = {secret_key.privkey.secret_multiplier:x}" yield f"Qx = {public_key.pubkey.point.x():x}" yield f"Qy = {public_key.pubkey.point.y():x}" yield f"R = {r:x}" yield f"S = {s:x}" yield "" def write_file(lines, dest): for line in lines: print(line) print(line, file=dest) source_path = os.path.join("asymmetric", "ECDSA", "FIPS_186-3", "SigGen.txt") dest_path = os.path.join("asymmetric", "ECDSA", "SECP256K1", "SigGen.txt") fips_vectors = load_vectors_from_file( source_path, load_fips_ecdsa_signing_vectors ) with open_vector_file(dest_path, "w") as dest_file: write_file(build_vectors(fips_vectors), dest_file) cryptography-43.0.0/docs/development/custom-vectors/secp256k1/verify_secp256k1.py010064400017510000177000000023341464676315000260060ustar 00000000000000import os from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( encode_dss_signature, ) from tests.utils import load_fips_ecdsa_signing_vectors, load_vectors_from_file CRYPTOGRAPHY_HASH_TYPES = { "SHA-1": hashes.SHA1, "SHA-224": hashes.SHA224, "SHA-256": hashes.SHA256, "SHA-384": hashes.SHA384, "SHA-512": hashes.SHA512, } def verify_one_vector(vector): digest_algorithm = vector["digest_algorithm"] message = vector["message"] x = vector["x"] y = vector["y"] signature = encode_dss_signature(vector["r"], vector["s"]) numbers = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256K1()) key = numbers.public_key() verifier = key.verifier( signature, ec.ECDSA(CRYPTOGRAPHY_HASH_TYPES[digest_algorithm]()) ) verifier.update(message) verifier.verify() def verify_vectors(vectors): for vector in vectors: verify_one_vector(vector) vector_path = os.path.join("asymmetric", "ECDSA", "SECP256K1", "SigGen.txt") secp256k1_vectors = load_vectors_from_file( vector_path, load_fips_ecdsa_signing_vectors ) verify_vectors(secp256k1_vectors) cryptography-43.0.0/docs/development/custom-vectors/seed.rst010064400017510000177000000016511464676315000224550ustar 00000000000000SEED vector creation ===================== This page documents the code that was used to generate the SEED CFB and OFB test vectors as well as the code used to verify them against another implementation. The vectors were generated using OpenSSL and verified with `Botan`_. Creation -------- ``cryptography`` was modified to support SEED in CFB and OFB modes. Then the following python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/seed/generate_seed.py Download link: :download:`generate_seed.py ` Verification ------------ The following Python code was used to verify the vectors using the `Botan`_ project's Python bindings. .. literalinclude:: /development/custom-vectors/seed/verify_seed.py Download link: :download:`verify_seed.py ` .. _`Botan`: https://botan.randombit.net cryptography-43.0.0/docs/development/custom-vectors/seed/generate_seed.py010064400017510000177000000032631464676315000250700ustar 00000000000000import binascii from cryptography.hazmat.primitives.ciphers import algorithms, base, modes def encrypt(mode, key, iv, plaintext): cipher = base.Cipher( algorithms.SEED(binascii.unhexlify(key)), mode(binascii.unhexlify(iv)), ) encryptor = cipher.encryptor() ct = encryptor.update(binascii.unhexlify(plaintext)) ct += encryptor.finalize() return binascii.hexlify(ct) def build_vectors(mode, filename): with open(filename) as f: vector_file = f.read().splitlines() count = 0 output = [] key = None iv = None plaintext = None for line in vector_file: line = line.strip() if line.startswith("KEY"): if count != 0: output.append( f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) output.append(f"\nCOUNT = {count}") count += 1 _, key = line.split(" = ") output.append(f"KEY = {key}") elif line.startswith("IV"): _, iv = line.split(" = ") output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): _, plaintext = line.split(" = ") output.append(f"PLAINTEXT = {plaintext}") output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) def write_file(data, filename): with open(filename, "w") as f: f.write(data) OFB_PATH = "vectors/cryptography_vectors/ciphers/AES/OFB/OFBMMT128.rsp" write_file(build_vectors(modes.OFB, OFB_PATH), "seed-ofb.txt") CFB_PATH = "vectors/cryptography_vectors/ciphers/AES/CFB/CFB128MMT128.rsp" write_file(build_vectors(modes.CFB, CFB_PATH), "seed-cfb.txt") cryptography-43.0.0/docs/development/custom-vectors/seed/verify_seed.py010064400017510000177000000015471464676315000246050ustar 00000000000000import binascii import botan from tests.utils import load_nist_vectors def encrypt(mode, key, iv, plaintext): encryptor = botan.Cipher( f"SEED/{mode}/NoPadding", "encrypt", binascii.unhexlify(key) ) cipher_text = encryptor.cipher( binascii.unhexlify(plaintext), binascii.unhexlify(iv) ) return binascii.hexlify(cipher_text) def verify_vectors(mode, filename): with open(filename) as f: vector_file = f.read().splitlines() vectors = load_nist_vectors(vector_file) for vector in vectors: ct = encrypt(mode, vector["key"], vector["iv"], vector["plaintext"]) assert ct == vector["ciphertext"] ofb_path = "vectors/cryptography_vectors/ciphers/SEED/seed-ofb.txt" verify_vectors("OFB", ofb_path) cfb_path = "vectors/cryptography_vectors/ciphers/SEED/seed-cfb.txt" verify_vectors("CFB", cfb_path) cryptography-43.0.0/docs/development/getting-started.rst010064400017510000177000000035741464676315000216530ustar 00000000000000Getting started =============== Development dependencies ------------------------ Working on ``cryptography`` requires the installation of a small number of development dependencies in addition to the dependencies for :doc:`/installation` (including :ref:`Rust`). These are handled by the use of ``nox``, which can be installed with ``pip``. .. code-block:: console $ # Create a virtualenv and activate it $ # Set up your cryptography build environment $ pip install nox $ nox -e local OpenSSL on macOS ~~~~~~~~~~~~~~~~ You must have installed `OpenSSL`_ (via `Homebrew`_ , `MacPorts`_) before invoking ``nox`` or else pip will fail to compile. Running tests ------------- ``cryptography`` unit tests are found in the ``tests/`` directory and are designed to be run using `pytest`_. ``nox`` automatically invokes ``pytest`` and other required checks for ``cryptography``: .. code-block:: console $ nox -e local You can also specify a subset of tests to run as positional arguments: .. code-block:: console $ # run the whole x509 testsuite, plus the fernet tests $ nox -e local -- tests/x509/ tests/test_fernet.py Building the docs ----------------- Building the docs on non-Windows platforms requires manually installing the C library ``libenchant`` (`installation instructions`_). The docs can be built using ``nox``: .. code-block:: console $ nox -e docs .. _`Homebrew`: https://brew.sh .. _`MacPorts`: https://www.macports.org .. _`OpenSSL`: https://www.openssl.org .. _`pytest`: https://pypi.org/project/pytest/ .. _`nox`: https://pypi.org/project/nox/ .. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _`pip`: https://pypi.org/project/pip/ .. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic .. _`installation instructions`: https://pyenchant.github.io/pyenchant/install.html#installing-the-enchant-c-librarycryptography-43.0.0/docs/development/index.rst010064400017510000177000000010651464676315000176460ustar 00000000000000Development =========== As an open source project, ``cryptography`` welcomes contributions of all forms. The sections below will help you get started. File bugs and feature requests on our issue tracker on `GitHub`_. If it is a bug check out `what to put in your bug report`_. .. toctree:: :maxdepth: 2 getting-started submitting-patches reviewing-patches test-vectors c-bindings .. _`GitHub`: https://github.com/pyca/cryptography .. _`what to put in your bug report`: https://www.contribution-guide.org/#what-to-put-in-your-bug-report cryptography-43.0.0/docs/development/reviewing-patches.rst010064400017510000177000000041111464676315000221560ustar 00000000000000Reviewing and merging patches ============================= Everyone is encouraged to review open pull requests. We only ask that you try and think carefully, ask questions and are `excellent to one another`_. Code review is our opportunity to share knowledge, design ideas and make friends. When reviewing a patch try to keep each of these concepts in mind: Intent ------ * What is the change being proposed? * Do we want this feature or is the bug they're fixing really a bug? Architecture ------------ * Is the proposed change being made in the correct place? Is it a fix in the backend when it should be in the primitives? Implementation -------------- * Does the change do what the author claims? * Are there sufficient tests? * Has it been documented? * Will this change introduce new bugs? Grammar and style ----------------- These are small things that are not caught by the automated style checkers. * Does a variable need a better name? * Should this be a keyword argument? Merge requirements ------------------ Because cryptography is so complex, and the implications of getting it wrong so devastating, ``cryptography`` has a strict merge policy for committers: * Patches must *never* be pushed directly to ``main``, all changes (even the most trivial typo fixes!) must be submitted as a pull request. * A committer may *never* merge their own pull request, a second party must merge their changes. If multiple people work on a pull request, it must be merged by someone who did not work on it. * A patch that breaks tests, or introduces regressions by changing or removing existing tests should not be merged. Tests must always be passing on ``main``. * If somehow the tests get into a failing state on ``main`` (such as by a backwards incompatible release of a dependency) no pull requests may be merged until this is rectified. * All merged patches must have 100% test coverage. The purpose of these policies is to minimize the chances we merge a change that jeopardizes our users' security. .. _`excellent to one another`: https://speakerdeck.com/ohrite/better-code-review cryptography-43.0.0/docs/development/submitting-patches.rst010064400017510000177000000131071464676315000223510ustar 00000000000000Submitting patches ================== * Always make a new branch for your work. * Patches should be small to facilitate easier review. `Studies have shown`_ that review quality falls off as patch size grows. Sometimes this will result in many small PRs to land a single large feature. * Larger changes should be discussed on `our mailing list`_ before submission. * New features and significant bug fixes should be documented in the :doc:`/changelog`. * You must have legal permission to distribute any code you contribute to ``cryptography``, and it must be available under both the BSD and Apache Software License Version 2.0 licenses. If you believe you've identified a security issue in ``cryptography``, please follow the directions on the :doc:`security page `. Code ---- When in doubt, refer to :pep:`8` for Python code. You can check if your code meets our automated requirements by formatting it with ``ruff format`` and running ``ruff`` against it. If you've installed the development requirements this will automatically use our configuration. You can also run the ``nox`` job with ``nox -e flake``. `Write comments as complete sentences.`_ Class names which contains acronyms or initialisms should always be capitalized. A class should be named ``HTTPClient``, not ``HttpClient``. Every code file must start with the boilerplate licensing notice: .. code-block:: python # 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. API considerations ~~~~~~~~~~~~~~~~~~ Most projects' APIs are designed with a philosophy of "make easy things easy, and make hard things possible". One of the perils of writing cryptographic code is that secure code looks just like insecure code, and its results are almost always indistinguishable. As a result, ``cryptography`` has, as a design philosophy: "make it hard to do insecure things". Here are a few strategies for API design that should be both followed, and should inspire other API choices: If it is necessary to compare a user provided value with a computed value (for example, verifying a signature), there should be an API provided that performs the verification in a secure way (for example, using a constant time comparison), rather than requiring the user to perform the comparison themselves. If it is incorrect to ignore the result of a method, it should raise an exception, and not return a boolean ``True``/``False`` flag. For example, a method to verify a signature should raise ``InvalidSignature``, and not return whether the signature was valid. .. code-block:: python # This is bad. def verify(sig: bytes) -> bool: # ... return is_valid # Good! def verify(sig: bytes) -> None: # ... if not is_valid: raise InvalidSignature Every recipe should include a version or algorithmic marker of some sort in its output in order to allow transparent upgrading of the algorithms in use, as the algorithms or parameters needed to achieve a given security margin evolve. C bindings ~~~~~~~~~~ More information on C bindings can be found in :doc:`the dedicated section of the documentation `. Tests ----- All code changes must be accompanied by unit tests with 100% code coverage (as measured by the combined metrics across our build matrix). When implementing a new primitive or recipe ``cryptography`` requires that you provide a set of test vectors. See :doc:`/development/test-vectors` for more details. Documentation ------------- All features should be documented with prose in the ``docs`` section. To ensure it builds you can run ``nox -e docs``. Because of the inherent challenges in implementing correct cryptographic systems, we want to make our documentation point people in the right directions as much as possible. To that end: * When documenting a generic interface, use a strong algorithm in examples. (e.g. when showing a hashing example, don't use :class:`~cryptography.hazmat.primitives.hashes.MD5`) * When giving prescriptive advice, always provide references and supporting material. * When there is real disagreement between cryptographic experts, represent both sides of the argument and describe the trade-offs clearly. When documenting a new module in the ``hazmat`` package, its documentation should begin with the "Hazardous Materials" warning: .. code-block:: rest .. hazmat:: Always prefer terminology that is most broadly accepted. For example: * When referring to class instances use "an instance of ``Foo``" instead of "a ``Foo`` provider". When referring to a hypothetical individual (such as "a person receiving an encrypted message") use gender neutral pronouns (they/them/their). Docstrings are typically only used when writing abstract classes, but should be written like this if required: .. code-block:: python def some_function(some_arg): """ Does some things. :param some_arg: Some argument. """ So, specifically: * Always use three double quotes. * Put the three double quotes on their own line. * No blank line at the end. * Use Sphinx parameter/attribute documentation `syntax`_. .. _`Write comments as complete sentences.`: https://nedbatchelder.com/blog/201401/comments_should_be_sentences.html .. _`syntax`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists .. _`Studies have shown`: https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/ .. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev cryptography-43.0.0/docs/development/test-vectors.rst010064400017510000177000001711341464676315000212060ustar 00000000000000Test vectors ============ Testing the correctness of the primitives implemented in ``cryptography`` requires trusted test vectors. Where possible these vectors are obtained from official sources such as `NIST`_ or `IETF`_ RFCs. When this is not possible ``cryptography`` has chosen to create a set of custom vectors using an official vector file as input. Vectors are kept in the ``cryptography_vectors`` package rather than within our main test suite. Sources ------- Project Wycheproof ~~~~~~~~~~~~~~~~~~ We run vectors from `Project Wycheproof`_ -- a collection of known edge-cases for various cryptographic algorithms. These are not included in the repository (or ``cryptography_vectors`` package), but rather cloned from Git in our continuous integration environments. Asymmetric ciphers ~~~~~~~~~~~~~~~~~~ * RSA PKCS #1 from the RSA FTP site (ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/ and ftp://ftp.rsa.com/pub/rsalabs/tmp/). * RSA FIPS 186-2 and PKCS1 v1.5 vulnerability test vectors from `NIST CAVP`_. * FIPS 186-2 and FIPS 186-3 DSA test vectors from `NIST CAVP`_. * FIPS 186-2 and FIPS 186-3 ECDSA test vectors from `NIST CAVP`_. * DH and ECDH and ECDH+KDF(17.4) test vectors from `NIST CAVP`_. * Ed25519 test vectors from the `Ed25519 website`_. * OpenSSL PEM RSA serialization vectors from the `OpenSSL example key`_ and `GnuTLS key parsing tests`_. * ``asymmetric/PEM_Serialization/rsa-bad-1025-q-is-2.pem`` from `badkeys`_. * OpenSSL PEM DSA serialization vectors from the `GnuTLS example keys`_. * PKCS #8 PEM serialization vectors from * GnuTLS: `enc-rsa-pkcs8.pem`_, `enc2-rsa-pkcs8.pem`_, `unenc-rsa-pkcs8.pem`_, `pkcs12_s2k_pem.c`_. The encoding error in `unenc-rsa-pkcs8.pem`_ was fixed, and the contents of `enc-rsa-pkcs8.pem`_ was re-encrypted to include it. 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. * X25519 and X448 test vectors from :rfc:`7748`. * RSA OAEP with custom label from the `BoringSSL evp tests`_. * Ed448 test vectors from :rfc:`8032`. * Deterministic ECDSA (:rfc:`6979`) from `OpenSSL's RFC 6979 test vectors`_. Custom asymmetric vectors ~~~~~~~~~~~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 1 custom-vectors/secp256k1 custom-vectors/rsa-oaep-sha2 * ``asymmetric/PEM_Serialization/ec_private_key.pem`` and ``asymmetric/DER_Serialization/ec_private_key.der`` - Contains an Elliptic Curve key generated by OpenSSL from the curve ``secp256r1``. * ``asymmetric/PEM_Serialization/ec_private_key_encrypted.pem`` and ``asymmetric/DER_Serialization/ec_private_key_encrypted.der``- Contains the same Elliptic Curve key as ``ec_private_key.pem``, except that it is encrypted with AES-128 with the password "123456". * ``asymmetric/PEM_Serialization/ec_public_key.pem`` and ``asymmetric/DER_Serialization/ec_public_key.der``- Contains the public key corresponding to ``ec_private_key.pem``, generated using OpenSSL. * ``asymmetric/PEM_Serialization/ec_public_key_rsa_delimiter.pem`` - Contains the public key corresponding to ``ec_private_key.pem``, but with the wrong PEM delimiter (``RSA PUBLIC KEY`` when it should be ``PUBLIC KEY``). * ``asymmetric/PEM_Serialization/rsa_private_key.pem`` - Contains an RSA 2048 bit key generated using OpenSSL, protected by the secret "123456" with DES3 encryption. * ``asymmetric/PEM_Serialization/rsa_public_key.pem`` and ``asymmetric/DER_Serialization/rsa_public_key.der``- Contains an RSA 2048 bit public generated using OpenSSL from ``rsa_private_key.pem``. * ``asymmetric/PEM_Serialization/rsa_wrong_delimiter_public_key.pem`` - Contains an RSA 2048 bit public key generated from ``rsa_private_key.pem``, but with the wrong PEM delimiter (``RSA PUBLIC KEY`` when it should be ``PUBLIC KEY``). * ``asymmetric/PEM_Serialization/dsa_4096.pem`` - Contains a 4096-bit DSA private key generated using OpenSSL. * ``asymmetric/PEM_Serialization/dsaparam.pem`` - Contains 2048-bit DSA parameters generated using OpenSSL; contains no keys. * ``asymmetric/PEM_Serialization/dsa_private_key.pem`` - Contains a DSA 2048 bit key generated using OpenSSL from the parameters in ``dsaparam.pem``, protected by the secret "123456" with DES3 encryption. * ``asymmetric/PEM_Serialization/dsa_public_key.pem`` and ``asymmetric/DER_Serialization/dsa_public_key.der`` - Contains a DSA 2048 bit key generated using OpenSSL from ``dsa_private_key.pem``. * ``asymmetric/DER_Serialization/dsa_public_key_no_params.der`` - Contains a DSA public key with the optional parameters removed. * ``asymmetric/DER_Serialization/dsa_public_key_invalid_bit_string.der`` - Contains a DSA public key with the bit string padding value set to 2 rather than the required 0. * ``asymmetric/PKCS8/unenc-dsa-pkcs8.pem`` and ``asymmetric/DER_Serialization/unenc-dsa-pkcs8.der`` - Contains a DSA 1024 bit key generated using OpenSSL. * ``asymmetric/PKCS8/unenc-dsa-pkcs8.pub.pem`` and ``asymmetric/DER_Serialization/unenc-dsa-pkcs8.pub.der`` - Contains a DSA 2048 bit public key generated using OpenSSL from ``unenc-dsa-pkcs8.pem``. * DER conversions of the `GnuTLS example keys`_ for DSA as well as the `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. * ``x509/custom/ca/ca_key.pem`` - An unencrypted PCKS8 ``secp256r1`` key. It is the private key for the certificate ``x509/custom/ca/ca.pem``. * ``pkcs12/ca/ca_key.pem`` - An unencrypted PCKS8 ``secp256r1`` key. It is the private key for the certificate ``pkcs12/ca/ca.pem``. This key is encoded in several of the PKCS12 custom vectors. * ``x509/custom/ca/rsa_key.pem`` - An unencrypted PCKS8 4096 bit RSA key. It is the private key for the certificate ``x509/custom/ca/rsa_ca.pem``. * ``asymmetric/EC/compressed_points.txt`` - Contains compressed public points generated using OpenSSL. * ``asymmetric/EC/explicit_parameters_private_key.pem`` - Contains an EC private key with an curve defined by explicit parameters. * ``asymmetric/EC/explicit_parameters_wap_wsg_idm_ecid_wtls11_private_key.pem`` - Contains an EC private key with over the ``wap-wsg-idm-ecid-wtls11`` curve, encoded with explicit parameters. * ``asymmetric/EC/secp128r1_private_key.pem`` - Contains an EC private key on the curve ``secp128r1``. * ``asymmetric/EC/sect163k1-spki.pem`` - Contains an EC SPKI on the curve ``sect163k1``. * ``asymmetric/EC/sect163r2-spki.pem`` - Contains an EC SPKI on the curve ``sect163r2``. * ``asymmetric/EC/sect233k1-spki.pem`` - Contains an EC SPKI on the curve ``sect233k1``. * ``asymmetric/EC/sect233r1-spki.pem`` - Contains an EC SPKI on the curve ``sect233r1``. * ``asymmetric/X448/x448-pkcs8-enc.pem`` and ``asymmetric/X448/x448-pkcs8-enc.der`` contain an X448 key encrypted with AES 256 CBC with the password ``password``. * ``asymmetric/X448/x448-pkcs8.pem`` and ``asymmetric/X448/x448-pkcs8.der`` contain an unencrypted X448 key. * ``asymmetric/X448/x448-pub.pem`` and ``asymmetric/X448/x448-pub.der`` contain an X448 public key. * ``asymmetric/Ed25519/ed25519-pkcs8-enc.pem`` and ``asymmetric/Ed25519/ed25519-pkcs8-enc.der`` contain an Ed25519 key encrypted with AES 256 CBC with the password ``password``. * ``asymmetric/Ed25519/ed25519-pkcs8.pem`` and ``asymmetric/Ed25519/ed25519-pkcs8.der`` contain an unencrypted Ed25519 key. * ``asymmetric/Ed25519/ed25519-pub.pem`` and ``asymmetric/Ed25519/ed25519-pub.der`` contain an Ed25519 public key. * ``asymmetric/X25519/x25519-pkcs8-enc.pem`` and ``asymmetric/X25519/x25519-pkcs8-enc.der`` contain an X25519 key encrypted with AES 256 CBC with the password ``password``. * ``asymmetric/X25519/x25519-pkcs8.pem`` and ``asymmetric/X25519/x25519-pkcs8.der`` contain an unencrypted X25519 key. * ``asymmetric/X25519/x25519-pub.pem`` and ``asymmetric/X25519/x25519-pub.der`` contain an X25519 public key. * ``asymmetric/Ed448/ed448-pkcs8-enc.pem`` and ``asymmetric/Ed448/ed448-pkcs8-enc.der`` contain an Ed448 key encrypted with AES 256 CBC with the password ``password``. * ``asymmetric/Ed448/ed448-pkcs8.pem`` and ``asymmetric/Ed448/ed448-pkcs8.der`` contain an unencrypted Ed448 key. * ``asymmetric/Ed448/ed448-pub.pem`` and ``asymmetric/Ed448/ed448-pub.der`` contain an Ed448 public key. * ``asymmetric/PKCS8/rsa_pss_2048.pem`` - A 2048-bit RSA PSS key with no explicit parameters set. * ``asymmetric/PKCS8/rsa_pss_2048_pub.der`` - The public key corresponding to ``asymmetric/PKCS8/rsa_pss_2048.pem``. * ``asymmetric/PKCS8/rsa_pss_2048_hash.pem`` - A 2048-bit RSA PSS key with the hash algorithm PSS parameter set to SHA256. * ``asymmetric/PKCS8/rsa_pss_2048_hash_mask.pem`` - A 2048-bit RSA PSS key with with the hash (SHA256) and mask algorithm (SHA256) PSS parameters set. * ``asymmetric/PKCS8/rsa_pss_2048_hash_mask_diff.pem`` - A 2048-bit RSA PSS key with the hash (SHA256) and mask algorithm (SHA512) PSS parameters set. * ``asymmetric/PKCS8/rsa_pss_2048_hash_mask_salt.pem`` - A 2048-bit RSA PSS key with the hash (SHA256), mask algorithm (SHA256), and salt length (32) PSS parameters set. Key exchange ~~~~~~~~~~~~ * ``vectors/cryptography_vectors/asymmetric/DH/rfc3526.txt`` contains several standardized Diffie-Hellman groups from :rfc:`3526`. * ``vectors/cryptography_vectors/asymmetric/DH/RFC5114.txt`` contains Diffie-Hellman examples from appendix A.1, A.2 and A.3 of :rfc:`5114`. * ``vectors/cryptography_vectors/asymmetric/DH/vec.txt`` contains Diffie-Hellman examples from `botan`_. * ``vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt`` contains Diffie-Hellman vector pairs that were generated using OpenSSL ``DH_generate_parameters_ex`` and ``DH_generate_key``. * ``vectors/cryptography_vectors/asymmetric/DH/dhp.pem``, ``vectors/cryptography_vectors/asymmetric/DH/dhkey.pem`` and ``vectors/cryptography_vectors/asymmetric/DH/dhpub.pem`` contains Diffie-Hellman parameters and key respectively. The keys were generated using OpenSSL following `DHKE`_ guide. ``vectors/cryptography_vectors/asymmetric/DH/dhkey.txt`` contains all parameter in text. ``vectors/cryptography_vectors/asymmetric/DH/dhp.der``, ``vectors/cryptography_vectors/asymmetric/DH/dhkey.der`` and ``vectors/cryptography_vectors/asymmetric/DH/dhpub.der`` contains are the above parameters and keys in DER format. * ``vectors/cryptography_vectors/asymmetric/DH/dhp_rfc5114_2.pem``, ``vectors/cryptography_vectors/asymmetric/DH/dhkey_rfc5114_2.pem`` and ``vectors/cryptography_vectors/asymmetric/DH/dhpub_rfc5114_2.pem`` contains Diffie-Hellman parameters and key respectively. The keys were generated using OpenSSL following `DHKE`_ guide. When creating the parameters we added the `-pkeyopt dh_rfc5114:2` option to use :rfc:`5114` 2048 bit DH parameters with 224 bit subgroup. ``vectors/cryptography_vectors/asymmetric/DH/dhkey_rfc5114_2.txt`` contains all parameter in text. ``vectors/cryptography_vectors/asymmetric/DH/dhp_rfc5114_2.der``, ``vectors/cryptography_vectors/asymmetric/DH/dhkey_rfc5114_2.der`` and ``vectors/cryptography_vectors/asymmetric/DH/dhpub_rfc5114_2.der`` contains are the above parameters and keys in DER format. * ``vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem`` contains a PEM PKCS8 encoded DH key with a 256-bit key size. * ``vectors/cryptoraphy_vectors/asymmetric/ECDH/brainpool.txt`` contains Brainpool vectors from :rfc:`7027`. * ``vectors/cryptography_vectors/asymmetric/DH/dhpub_cryptography_old.pem`` contains a Diffie-Hellman public key generated with a previous version of ``cryptography``. X.509 ~~~~~ * PKITS test suite from `NIST PKI Testing`_. * ``v1_cert.pem`` from the OpenSSL source tree (`testx509.pem`_). * ``ecdsa_root.pem`` - `DigiCert Global Root G3`_, a ``secp384r1`` ECDSA root certificate. * ``verisign-md2-root.pem`` - A legacy Verisign public root signed using the MD2 algorithm. This is a PEM conversion of the `root data`_ in the NSS source tree. * ``cryptography.io.pem`` - A leaf certificate issued by RapidSSL for the cryptography website. * ``cryptography.io.old_header.pem`` - A leaf certificate issued by RapidSSL for the cryptography website. This certificate uses the ``X509 CERTIFICATE`` legacy PEM header format. * ``cryptography.io.chain.pem`` - The same as ``cryptography.io.pem``, but ``rapidssl_sha256_ca_g3.pem`` is concatenated to the end. * ``cryptography.io.with_headers.pem`` - The same as ``cryptography.io.pem``, but with an unrelated (encrypted) private key concatenated to the end. * ``cryptography.io.chain_with_garbage.pem`` - The same as ``cryptography.io.chain.pem``, but with other sections and text around it. * ``cryptography.io.with_garbage.pem`` - The same as ``cryptography.io.pem``, but with other sections and text around it. * ``rapidssl_sha256_ca_g3.pem`` - The intermediate CA that issued the ``cryptography.io.pem`` certificate. * ``cryptography.io.precert.pem`` - A pre-certificate with the CT poison extension for the cryptography website. * ``cryptography-scts.pem`` - A leaf certificate issued by Let's Encrypt for the cryptography website which contains signed certificate timestamps. * ``wildcard_san.pem`` - A leaf certificate issued by a public CA for ``langui.sh`` that contains wildcard entries in the SAN extension. * ``san_edipartyname.der`` - A DSA certificate from a `Mozilla bug`_ containing a SAN extension with an ``ediPartyName`` general name. * ``san_x400address.der`` - A DSA certificate from a `Mozilla bug`_ containing a SAN extension with an ``x400Address`` general name. * ``department-of-state-root.pem`` - The intermediary CA for the Department of State, issued by the United States Federal Government's Common Policy CA. Notably has a ``critical`` policy constraints extensions. * ``e-trust.ru.der`` - A certificate from a `Russian CA`_ signed using the GOST cipher and containing numerous unusual encodings such as NUMERICSTRING in the subject DN. * ``alternate-rsa-sha1-oid.der`` - A certificate that uses an alternate signature OID for RSA with SHA1. This certificate has an invalid signature. * ``badssl-sct.pem`` - A certificate with the certificate transparency signed certificate timestamp extension. * ``badssl-sct-none-hash.der`` - The same as ``badssl-sct.pem``, but DER-encoded and with the SCT's signature hash manually changed to "none" (``0x00``). * ``badssl-sct-anonymous-sig.der`` - The same as ``badssl-sct.pem``, but DER-encoded and with the SCT's signature algorithm manually changed to "anonymous" (``0x00``). * ``bigoid.pem`` - A certificate with a rather long OID in the Certificate Policies extension. We need to make sure we can parse long OIDs. * ``wosign-bc-invalid.pem`` - A certificate issued by WoSign that contains a basic constraints extension with CA set to false and a path length of zero in violation of :rfc:`5280`. * ``tls-feature-ocsp-staple.pem`` - A certificate issued by Let's Encrypt that contains a TLS Feature extension with the ``status_request`` feature (commonly known as OCSP Must-Staple). * ``unique-identifier.pem`` - A certificate containing a distinguished name with an ``x500UniqueIdentifier``. * ``utf8-dnsname.pem`` - A certificate containing non-ASCII characters in the DNS name entries of the SAN extension. * ``badasn1time.pem`` - A certificate containing an incorrectly specified UTCTime in its validity->not_after. * ``letsencryptx3.pem`` - A subordinate certificate used by Let's Encrypt to issue end entity certificates. * ``ed25519-rfc8410.pem`` - A certificate containing an X25519 public key with an ``ed25519`` signature taken from :rfc:`8410`. * ``root-ed25519.pem`` - An ``ed25519`` root certificate (``ed25519`` signature with ``ed25519`` public key) from the OpenSSL test suite. (`root-ed25519.pem`_) * ``server-ed25519-cert.pem`` - An ``ed25519`` server certificate (RSA signature with ``ed25519`` public key) from the OpenSSL test suite. (`server-ed25519-cert.pem`_) * ``server-ed448-cert.pem`` - An ``ed448`` server certificate (RSA signature with ``ed448`` public key) from the OpenSSL test suite. (`server-ed448-cert.pem`_) * ``accvraiz1.pem`` - An RSA root certificate that contains an ``explicitText`` entry with a ``BMPString`` type. * ``scottishpower-bitstring-dn.pem`` - An ECDSA certificate that contains a subject DN with a bit string type. * ``cryptography-scts-tbs-precert.der`` - The "to-be-signed" pre-certificate bytes from ``cryptography-scts.pem``, with the SCT list extension removed. * ``belgian-eid-invalid-visiblestring.pem`` - A certificate with UTF-8 bytes in a ``VisibleString`` type. * ``ee-pss-sha1-cert.pem`` - An RSA PSS certificate using a SHA1 signature and SHA1 for MGF1 from the OpenSSL test suite. Custom X.509 Vectors ~~~~~~~~~~~~~~~~~~~~ * ``invalid_version.pem`` - Contains an RSA 2048 bit certificate with the X.509 version field set to ``0x7``. * ``post2000utctime.pem`` - Contains an RSA 2048 bit certificate with the ``notBefore`` and ``notAfter`` fields encoded as post-2000 ``UTCTime``. * ``dsa_selfsigned_ca.pem`` - Contains a DSA self-signed CA certificate generated using OpenSSL. * ``ec_no_named_curve.pem`` - Contains an ECDSA certificate that does not have an embedded OID defining the curve. * ``all_supported_names.pem`` - An RSA 2048 bit certificate generated using OpenSSL that contains a subject and issuer that have two of each supported attribute type from :rfc:`5280`. * ``unsupported_subject_name.pem`` - An RSA 2048 bit self-signed CA certificate generated using OpenSSL that contains the unsupported "initials" name. * ``utf8_common_name.pem`` - An RSA 2048 bit self-signed CA certificate generated using OpenSSL that contains a UTF8String common name with the value "We heart UTF8!™". * ``invalid_utf8_common_name.pem`` - A certificate that contains a ``UTF8String`` common name with an invalid UTF-8 byte sequence. * ``two_basic_constraints.pem`` - An RSA 2048 bit self-signed certificate containing two basic constraints extensions. * ``basic_constraints_not_critical.pem`` - An RSA 2048 bit self-signed certificate containing a basic constraints extension that is not marked as critical. * ``bc_path_length_zero.pem`` - An RSA 2048 bit self-signed certificate containing a basic constraints extension with a path length of zero. * ``unsupported_extension.pem`` - An RSA 2048 bit self-signed certificate containing an unsupported extension type. The OID was encoded as "1.2.3.4" with an ``extnValue`` of "value". * ``unsupported_extension_2.pem`` - A ``secp256r1`` certificate containing two unsupported extensions. The OIDs are ``1.3.6.1.4.1.41482.2`` with an ``extnValue`` of ``1.3.6.1.4.1.41482.1.2`` and ``1.3.6.1.4.1.45724.2.1.1`` with an ``extnValue`` of ``\x03\x02\x040`` * ``unsupported_extension_critical.pem`` - An RSA 2048 bit self-signed certificate containing an unsupported extension type marked critical. The OID was encoded as "1.2.3.4" with an ``extnValue`` of "value". * ``san_email_dns_ip_dirname_uri.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with the following general names: ``rfc822Name``, ``dNSName``, ``iPAddress``, ``directoryName``, and ``uniformResourceIdentifier``. * ``san_empty_hostname.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative extension with an empty ``dNSName`` general name. * ``san_other_name.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with the ``otherName`` general name. * ``san_registered_id.pem`` - An RSA 1024 bit certificate containing a subject alternative name extension with the ``registeredID`` general name. * ``all_key_usages.pem`` - An RSA 2048 bit self-signed certificate containing a key usage extension with all nine purposes set to true. * ``extended_key_usage.pem`` - An RSA 2048 bit self-signed certificate containing an extended key usage extension with eight usages. * ``san_idna_names.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with ``rfc822Name``, ``dNSName``, and ``uniformResourceIdentifier`` general names with IDNA (:rfc:`5895`) encoding. * ``san_wildcard_idna.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with a ``dNSName`` general name with a wildcard IDNA (:rfc:`5895`) domain. * ``san_idna2003_dnsname.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with an IDNA 2003 (:rfc:`3490`) ``dNSName``. * ``san_rfc822_names.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with various ``rfc822Name`` values. * ``san_rfc822_idna.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with an IDNA ``rfc822Name``. * ``san_uri_with_port.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with various ``uniformResourceIdentifier`` values. * ``san_ipaddr.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with an ``iPAddress`` value. * ``san_dirname.pem`` - An RSA 2048 bit self-signed certificate containing a subject alternative name extension with a ``directoryName`` value. * ``inhibit_any_policy_5.pem`` - An RSA 2048 bit self-signed certificate containing an inhibit any policy extension with the value 5. * ``inhibit_any_policy_negative.pem`` - An RSA 2048 bit self-signed certificate containing an inhibit any policy extension with the value -1. * ``authority_key_identifier.pem`` - An RSA 2048 bit self-signed certificate containing an authority key identifier extension with key identifier, authority certificate issuer, and authority certificate serial number fields. * ``authority_key_identifier_no_keyid.pem`` - An RSA 2048 bit self-signed certificate containing an authority key identifier extension with authority certificate issuer and authority certificate serial number fields. * ``aia_ocsp_ca_issuers.pem`` - An RSA 2048 bit self-signed certificate containing an authority information access extension with two OCSP and one CA issuers entry. * ``aia_ocsp.pem`` - An RSA 2048 bit self-signed certificate containing an authority information access extension with an OCSP entry. * ``aia_ca_issuers.pem`` - An RSA 2048 bit self-signed certificate containing an authority information access extension with a CA issuers entry. * ``cdp_empty_hostname.pem`` - An RSA 2048 bit self-signed certificate containing a CRL distribution point extension with ``fullName`` URI without a hostname. * ``cdp_fullname_reasons_crl_issuer.pem`` - An RSA 1024 bit certificate containing a CRL distribution points extension with ``fullName``, ``cRLIssuer``, and ``reasons`` data. * ``cdp_crl_issuer.pem`` - An RSA 1024 bit certificate containing a CRL distribution points extension with ``cRLIssuer`` data. * ``cdp_all_reasons.pem`` - An RSA 1024 bit certificate containing a CRL distribution points extension with all ``reasons`` bits set. * ``cdp_reason_aa_compromise.pem`` - An RSA 1024 bit certificate containing a CRL distribution points extension with the ``AACompromise`` ``reasons`` bit set. * ``nc_permitted_excluded.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with both permitted and excluded elements. Contains ``IPv4`` and ``IPv6`` addresses with network mask as well as ``dNSName`` with a leading period. * ``nc_permitted_excluded_2.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with both permitted and excluded elements. Unlike ``nc_permitted_excluded.pem``, the general names do not contain any name constraints specific values. * ``nc_permitted.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with permitted elements. * ``nc_permitted_2.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with permitted elements that do not contain any name constraints specific values. * ``nc_excluded.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with excluded elements. * ``nc_invalid_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with a permitted element that has an ``IPv6`` IP and an invalid network mask. * ``nc_invalid_ip4_netmask.der`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with a permitted element that has an ``IPv4`` IP and an invalid network mask. The signature on this certificate is invalid. * ``nc_single_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with a permitted element that has two IPs with ``/32`` and ``/128`` network masks. * ``nc_ip_invalid_length.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with a permitted element that has an invalid length (33 bytes instead of 32) for an ``IPv6`` address with network mask. The signature on this certificate is invalid. * ``cp_user_notice_with_notice_reference.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with a notice reference in the user notice. * ``cp_user_notice_with_explicit_text.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with explicit text and no notice reference. * ``cp_cps_uri.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with a CPS URI and no user notice. * ``cp_user_notice_no_explicit_text.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with a user notice with no explicit text. * ``cp_invalid.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with invalid data. The ``policyQualifierId`` is for ``id-qt-unotice`` but the value is an ``id-qt-cps`` ASN.1 structure. * ``cp_invalid2.der`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with invalid data. The ``policyQualifierId`` is for ``id-qt-cps`` but the value is an ``id-qt-unotice`` ASN.1 structure. The signature on this certificate is invalid. * ``ian_uri.pem`` - An RSA 2048 bit certificate containing an issuer alternative name extension with a ``URI`` general name. * ``ocsp_nocheck.pem`` - An RSA 2048 bit self-signed certificate containing an ``OCSPNoCheck`` extension. * ``pc_inhibit_require.pem`` - An RSA 2048 bit self-signed certificate containing a policy constraints extension with both inhibit policy mapping and require explicit policy elements. * ``pc_inhibit.pem`` - An RSA 2048 bit self-signed certificate containing a policy constraints extension with an inhibit policy mapping element. * ``pc_require.pem`` - An RSA 2048 bit self-signed certificate containing a policy constraints extension with a require explicit policy element. * ``unsupported_subject_public_key_info.pem`` - A certificate whose public key is an unknown OID (``1.3.6.1.4.1.8432.1.1.2``). * ``policy_constraints_explicit.pem`` - A self-signed certificate containing a ``policyConstraints`` extension with a ``requireExplicitPolicy`` value. * ``freshestcrl.pem`` - A self-signed certificate containing a ``freshestCRL`` extension. * ``sia.pem`` - An RSA 2048 bit self-signed certificate containing a subject information access extension with both a CA repository entry and a custom OID entry. * ``ca/ca.pem`` - A self-signed certificate with ``basicConstraints`` set to true. Its private key is ``ca/ca_key.pem``. * ``pkcs12/ca/ca.pem`` - A self-signed certificate with ``basicConstraints`` set to true. Its private key is ``pkcs12/ca/ca_key.pem``. This key is encoded in several of the PKCS12 custom vectors. * ``negative_serial.pem`` - A certificate with a serial number that is a negative number. * ``rsa_pss.pem`` - A certificate with an RSA PSS signature. * ``root-ed448.pem`` - An ``ed448`` self-signed CA certificate using ``ed448-pkcs8.pem`` as key. * ``ca/rsa_ca.pem`` - A self-signed RSA certificate with ``basicConstraints`` set to true. Its private key is ``ca/rsa_key.pem``. * ``ca/rsae_ca.pem`` - A self-signed RSA certificate using a (non-PSS) RSA public key and a RSA PSS signature. Its private key is ``ca/rsa_key.pem``. * ``invalid-sct-version.der`` - A certificate with an SCT with an unknown version. * ``invalid-sct-length.der`` - A certificate with an SCT with an internal length greater than the amount of data. * ``bad_country.pem`` - A certificate with country name and jurisdiction country name values in its subject and issuer distinguished names which are longer than 2 characters. * ``rsa_pss_cert.pem`` - A self-signed certificate with an RSA PSS signature with ``asymmetric/PKCS8/rsa_pss_2048.pem`` as its key. * ``rsa_pss_cert_invalid_mgf.der`` - A self-signed certificate with an invalid RSA PSS signature that has a non-MGF1 OID for its mask generation function in the signature algorithm. * ``rsa_pss_cert_no_sig_params.der`` - A self-signed certificate with an invalid RSA PSS signature algorithm that is missing signature parameters for PSS. * ``rsa_pss_cert_unsupported_mgf_hash.der`` - A self-signed certificate with an unsupported MGF1 hash algorithm in the signature algorithm. * ``long-form-name-attribute.pem`` - A certificate with ``subject`` and ``issuer`` names containing attributes whose value's tag is encoded in long-form. * ``mismatch_inner_outer_sig_algorithm.der`` - A leaf certificate derived from ``x509/cryptography.io.pem`` but modifying the ``tbs_cert.signature_algorithm`` OID to not match the outer signature algorithm OID. * ``ms-certificate-template.pem`` - A certificate with a ``msCertificateTemplate`` extension. * ``rsa_pss_sha256_no_null.pem`` - A certificate with an RSA PSS signature with no encoded ``NULL`` for the PSS hash algorithm parameters. This certificate was generated by LibreSSL. * ``ecdsa_null_alg.pem`` - A certificate with an ECDSA signature with ``NULL`` algorithm parameters. This encoding is invalid, but was generated by Java 11. * ``dsa_null_alg_params.pem`` - A certificate with a DSA signature with ``NULL`` algorithm parameters. This encoding is invalid, but was generated by Java 20. * ``ekucrit-testuser-cert.pem`` - A leaf certificate containing a critical EKU. This is an invalid certificate per CA/B 7.1.2.7.6. Custom X.509 Request Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``dsa_sha1.pem`` and ``dsa_sha1.der`` - Contain a certificate request using 1024-bit DSA parameters and SHA1 generated using OpenSSL. * ``rsa_md4.pem`` and ``rsa_md4.der`` - Contain a certificate request using 2048 bit RSA and MD4 generated using OpenSSL. * ``rsa_sha1.pem`` and ``rsa_sha1.der`` - Contain a certificate request using 2048 bit RSA and SHA1 generated using OpenSSL. * ``rsa_sha256.pem`` and ``rsa_sha256.der`` - Contain a certificate request using 2048 bit RSA and SHA256 generated using OpenSSL. * ``ec_sha256.pem`` and ``ec_sha256.der`` - Contain a certificate request using EC (``secp384r1``) and SHA256 generated using OpenSSL. * ``ec_sha256_old_header.pem`` - Identical to ``ec_sha256.pem``, but uses the ``-----BEGIN NEW CERTIFICATE REQUEST-----`` legacy PEM header format. * ``san_rsa_sha1.pem`` and ``san_rsa_sha1.der`` - Contain a certificate request using RSA and SHA1 with a subject alternative name extension generated using OpenSSL. * ``two_basic_constraints.pem`` - A certificate signing request for an RSA 2048 bit key containing two basic constraints extensions. The signature on this CSR is invalid. * ``unsupported_extension.pem`` - A certificate signing request for an RSA 2048 bit key containing containing an unsupported extension type. The OID was encoded as "1.2.3.4" with an ``extnValue`` of "value". The signature on this CSR is invalid. * ``unsupported_extension_critical.pem`` - A certificate signing request for an RSA 2048 bit key containing containing an unsupported extension type marked critical. The OID was encoded as "1.2.3.4" with an ``extnValue`` of "value". The signature on this CSR is invalid. * ``basic_constraints.pem`` - A certificate signing request for an RSA 2048 bit key containing a basic constraints extension marked as critical. The signature on this CSR is invalid. * ``invalid_signature.pem`` - A certificate signing request for an RSA 1024 bit key containing an invalid signature with correct padding. * ``challenge.pem`` - A certificate signing request for an RSA 2048 bit key containing a challenge password. * ``challenge-invalid.der`` - A certificate signing request for an RSA 2048 bit key containing a challenge password attribute that has been encoded as an ASN.1 integer rather than a string. * ``challenge-unstructured.pem`` - A certificate signing request for an RSA 2048 bit key containing a challenge password attribute and an unstructured name attribute. * ``challenge-multi-valued.der`` - A certificate signing request for an RSA 2048 bit key containing a challenge password attribute with two values inside the ASN.1 set. The signature on this request is invalid. * ``freeipa-bad-critical.pem`` - A certificate signing request where the extensions value has a ``critical`` value of ``False`` explicitly encoded. * ``bad-version.pem`` - A certificate signing request where the version is invalid. * ``long-form-attribute.pem`` - A certificate signing request containing an attribute whose value's tag is encoded in the long form. Custom X.509 Certificate Revocation List Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``crl_all_reasons.pem`` - Contains a CRL with 12 revoked certificates, whose serials match their list position. It includes one revocation without any entry extensions, 10 revocations with every supported reason code and one revocation with an unsupported, non-critical entry extension with the OID value set to "1.2.3.4". The signature on this CRL is invalid. * ``crl_dup_entry_ext.pem`` - Contains a CRL with one revocation which has a duplicate entry extension. The signature on this CRL is invalid. * ``crl_md2_unknown_crit_entry_ext.pem`` - Contains a CRL with one revocation which contains an unsupported critical entry extension with the OID value set to "1.2.3.4". The CRL uses an unsupported MD2 signature algorithm, and the signature on this CRL is invalid. * ``crl_unsupported_reason.pem`` - Contains a CRL with one revocation which has an unsupported reason code. The signature on this CRL is invalid. * ``crl_inval_cert_issuer_entry_ext.pem`` - Contains a CRL with one revocation which has one entry extension for certificate issuer with an empty value. The signature on this CRL is invalid. * ``crl_empty.pem`` - Contains a CRL with no revoked certificates. * ``crl_empty_no_sequence.der`` - Contains a CRL with no revoked certificates and the optional ASN.1 sequence for revoked certificates is omitted. * ``crl_ian_aia_aki.pem`` - Contains a CRL with ``IssuerAlternativeName``, ``AuthorityInformationAccess``, ``AuthorityKeyIdentifier`` and ``CRLNumber`` extensions. * ``valid_signature_crl.pem`` - Contains a CRL with a valid signature. * ``valid_signature_cert.pem`` - Contains a cert whose public key corresponds to the private key that produced the signature for ``valid_signature_crl.pem``. * ``invalid_signature_crl.pem`` - Contains a CRL with the last signature byte incremented by 1 to produce an invalid signature. * ``invalid_signature_cert.pem`` - Contains a cert whose public key corresponds to the private key that produced the signature for ``invalid_signature_crl.pem``. * ``crl_delta_crl_indicator.pem`` - Contains a CRL with the ``DeltaCRLIndicator`` extension. * ``crl_idp_fullname_only.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension with only a ``fullname`` for the distribution point. * ``crl_idp_only_ca.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension that is only valid for CA certificate revocation. * ``crl_idp_fullname_only_aa.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension that sets a ``fullname`` and is only valid for attribute certificate revocation. * ``crl_idp_fullname_only_user.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension that sets a ``fullname`` and is only valid for user certificate revocation. * ``crl_idp_fullname_indirect_crl.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension that sets a ``fullname`` and the indirect CRL flag. * ``crl_idp_reasons_only.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension that is only valid for revocations with the ``keyCompromise`` reason. * ``crl_idp_relative_user_all_reasons.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension that sets all revocation reasons as allowed. * ``crl_idp_relativename_only.pem`` - Contains a CRL with an ``IssuingDistributionPoints`` extension with only a ``relativename`` for the distribution point. * ``crl_unrecognized_extension.der`` - Contains a CRL containing an unsupported extension type. The OID was encoded as "1.2.3.4.5" with an ``extnValue`` of ``abcdef``. * ``crl_invalid_time.der`` - Contains a CRL with an invalid ``UTCTime`` value in ``thisUpdate``. The signature on this CRL is invalid. * ``crl_no_next_time.pem`` - Contains a CRL with no ``nextUpdate`` value. The signature on this CRL is invalid. * ``crl_bad_version.pem`` - Contains a CRL with an invalid version. * ``crl_almost_10k.pem`` - Contains a CRL with 9,999 entries. * ``crl_inner_outer_mismatch.der`` - A CRL created from ``valid_signature_crl.pem`` but with a mismatched inner and outer signature algorithm. The signature on this CRL is invalid. X.509 OCSP Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~ * ``x509/ocsp/resp-sha256.der`` - An OCSP response for ``cryptography.io`` with a SHA256 signature. * ``x509/ocsp/resp-unauthorized.der`` - An OCSP response with an unauthorized status. * ``x509/ocsp/resp-revoked.der`` - An OCSP response for ``revoked.badssl.com`` with a revoked status. * ``x509/ocsp/resp-delegate-unknown-cert.der`` - An OCSP response for an unknown cert from ``AC Camerafirma``. This response also contains a delegate certificate. * ``x509/ocsp/resp-responder-key-hash.der`` - An OCSP response from the ``DigiCert`` OCSP responder that uses a key hash for the responder ID. * ``x509/ocsp/resp-revoked-reason.der`` - An OCSP response from the ``QuoVadis`` OCSP responder that contains a revoked certificate with a revocation reason. * ``x509/ocsp/resp-revoked-no-next-update.der`` - An OCSP response that contains a revoked certificate and no ``nextUpdate`` value. * ``x509/ocsp/resp-invalid-signature-oid.der`` - An OCSP response that was modified to contain an MD2 signature algorithm object identifier. * ``x509/ocsp/resp-single-extension-reason.der`` - An OCSP response that contains a ``CRLReason`` single extension. * ``x509/ocsp/resp-sct-extension.der`` - An OCSP response containing a ``CT Certificate SCTs`` single extension, from the SwissSign OCSP responder. * ``x509/ocsp/ocsp-army.deps.mil-resp.der`` - An OCSP response containing multiple ``SINGLERESP`` values. * ``x509/ocsp/resp-response-type-unknown-oid.der`` - An OCSP response with an unknown OID for response type. The signature on this response is invalid. * ``x509/ocsp/resp-successful-no-response-bytes.der`` - An OCSP request with a successful response type but the response bytes are missing. * ``x509/ocsp/resp-unknown-response-status.der`` - An OCSP response with an unknown response status. Custom X.509 OCSP Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``x509/ocsp/req-sha1.der`` - An OCSP request containing a single request and using SHA1 as the hash algorithm. * ``x509/ocsp/req-multi-sha1.der`` - An OCSP request containing multiple requests. * ``x509/ocsp/req-invalid-hash-alg.der`` - An OCSP request containing an invalid hash algorithm OID. * ``x509/ocsp/req-ext-nonce.der`` - An OCSP request containing a nonce extension. * ``x509/ocsp/req-ext-unknown-oid.der`` - An OCSP request containing an extension with an unknown OID. * ``x509/ocsp/req-duplicate-ext.der`` - An OCSP request with duplicate extensions. * ``x509/ocsp/resp-unknown-extension.der`` - An OCSP response containing an extension with an unknown OID. * ``x509/ocsp/resp-unknown-hash-alg.der`` - An OCSP response containing an invalid hash algorithm OID. * ``x509/ocsp/req-acceptable-responses.der`` - An OCSP request containing an acceptable responses extension. Custom PKCS12 Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``pkcs12/cert-key-aes256cbc.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) both encrypted with AES 256 CBC with the password ``cryptography``. * ``pkcs12/cert-none-key-none.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with no encryption. The password (used for integrity checking only) is ``cryptography``. * ``pkcs12/cert-rc2-key-3des.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) encrypted with RC2 and key (``pkcs12/ca/ca_key.pem``) encrypted via 3DES with the password ``cryptography``. * ``pkcs12/no-password.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with no encryption and no password. * ``pkcs12/no-cert-key-aes256cbc.p12`` - A PKCS12 file containing a key (``pkcs12/ca/ca_key.pem``) encrypted via AES 256 CBC with the password ``cryptography`` and no certificate. * ``pkcs12/cert-aes256cbc-no-key.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) encrypted via AES 256 CBC with the password ``cryptography`` and no private key. * ``pkcs12/no-name-no-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``). * ``pkcs12/name-all-no-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3``, respectively. * ``pkcs12/name-1-no-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``). * ``pkcs12/name-2-3-no-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3``, respectively. * ``pkcs12/name-2-no-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the first having friendly name ``name2``. * ``pkcs12/name-3-no-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the latter having friendly name ``name3``. * ``pkcs12/name-unicode-no-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``☺``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``ä`` and ``ç``, respectively. * ``pkcs12/no-name-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-all-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3`` respectively, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-1-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``name``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-2-3-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3`` respectively, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-2-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the first having friendly name ``name2``, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-3-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``), as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the latter having friendly name ``name2``, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/name-unicode-pwd.p12`` - A PKCS12 file containing a cert (``pkcs12/ca/ca.pem``) and key (``pkcs12/ca/ca_key.pem``) with friendly name ``☺``, as well as two additional certificates (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``ä`` and ``ç`` respectively, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/no-cert-no-name-no-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``). * ``pkcs12/no-cert-name-all-no-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3``, respectively. * ``pkcs12/no-cert-name-2-no-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the first having friendly name ``name2``. * ``pkcs12/no-cert-name-3-no-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the second having friendly name ``name3``. * ``pkcs12/no-cert-name-unicode-no-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``☹`` and ``ï``, respectively. * ``pkcs12/no-cert-no-name-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/no-cert-name-all-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``name2`` and ``name3``, respectively, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/no-cert-name-2-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the first with friendly name ``name2``, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/no-cert-name-3-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``), the second with friendly name ``name3``, encrypted via AES 256 CBC with the password ``cryptography``. * ``pkcs12/no-cert-name-unicode-pwd.p12`` - A PKCS12 file containing two certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``) with friendly names ``☹`` and ``ï``, respectively, encrypted via AES 256 CBC with the password ``cryptography``. Custom PKCS7 Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~ * ``pkcs7/isrg.pem`` - A PEM encoded PKCS7 file containing the ISRG X1 root CA. * ``pkcs7/amazon-roots.p7b`` - A BER encoded PCKS7 file containing Amazon Root CA 2 and 3 generated by Apple Keychain. * ``pkcs7/amazon-roots.der`` - A DER encoded PCKS7 file containing Amazon Root CA 2 and 3 generated by OpenSSL. * ``pkcs7/enveloped.pem`` - A PEM encoded PKCS7 file with enveloped data. Custom OpenSSH Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``ed25519-aesgcm-psw.key`` and ``ed25519-aesgcm-psw.key.pub`` generated by exporting an Ed25519 key from ``1password 8`` with the password "password". This key is encrypted using the ``aes256-gcm@openssh.com`` algorithm. Generated by ``asymmetric/OpenSSH/gen.sh`` using command-line tools from OpenSSH_7.6p1 package. * ``dsa-nopsw.key``, ``dsa-nopsw.key.pub``, ``dsa-nopsw.key-cert.pub`` - DSA-1024 private key; and corresponding public key in plain format and with self-signed certificate. * ``dsa-psw.key``, ``dsa-psw.key.pub`` - Password-protected DSA-1024 private key and corresponding public key. Password is "password". * ``ecdsa-nopsw.key``, ``ecdsa-nopsw.key.pub``, ``ecdsa-nopsw.key-cert.pub`` - SECP256R1 private key; and corresponding public key in plain format and with self-signed certificate. * ``ecdsa-psw.key``, ``ecdsa-psw.key.pub`` - Password-protected SECP384R1 private key and corresponding public key. Password is "password". * ``ed25519-nopsw.key``, ``ed25519-nopsw.key.pub``, ``ed25519-nopsw.key-cert.pub`` - Ed25519 private key; and corresponding public key in plain format and with self-signed certificate. * ``ed25519-psw.key``, ``ed25519-psw.key.pub`` - Password-protected Ed25519 private key and corresponding public key. Password is "password". * ``rsa-nopsw.key``, ``rsa-nopsw.key.pub``, ``rsa-nopsw.key-cert.pub`` - RSA-2048 private key; and corresponding public key in plain format and with self-signed certificate. * ``rsa-psw.key``, ``rsa-psw.key.pub`` - Password-protected RSA-2048 private key and corresponding public key. Password is "password". Custom OpenSSH Certificate Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``p256-p256-duplicate-extension.pub`` - A certificate with a duplicate extension. * ``p256-p256-non-lexical-extensions.pub`` - A certificate with extensions in non-lexical order. * ``p256-p256-duplicate-crit-opts.pub`` - A certificate with a duplicate critical option. * ``p256-p256-non-lexical-crit-opts.pub`` - A certificate with critical options in non-lexical order. * ``p256-ed25519-non-singular-crit-opt-val.pub`` - A certificate with a critical option that contains more than one value. * ``p256-ed25519-non-singular-ext-val.pub`` - A certificate with an extension that contains more than one value. * ``dsa-p256.pub`` - A certificate with a DSA public key signed by a P256 CA. * ``p256-dsa.pub`` - A certificate with a P256 public key signed by a DSA CA. * ``p256-p256-broken-signature-key-type.pub`` - A certificate with a P256 public key signed by a P256 CA, but the signature key type is set to ``rsa-sha2-512``. * ``p256-p256-empty-principals.pub`` - A certificate with a P256 public key signed by a P256 CA with an empty valid principals list. * ``p256-p256-invalid-cert-type.pub`` - A certificate with a P256 public key signed by a P256 CA with an invalid certificate type. * ``p256-p384.pub`` - A certificate with a P256 public key signed by a P384 CA. * ``p256-p521.pub`` - A certificate with a P256 public key signed by a P521 CA. * ``p256-rsa-sha1.pub`` - A certificate with a P256 public key signed by a RSA CA using SHA1. * ``p256-rsa-sha256.pub`` - A certificate with a P256 public key signed by a RSA CA using SHA256. * ``p256-rsa-sha512.pub`` - A certificate with a P256 public key signed by a RSA CA using SHA512. Hashes ~~~~~~ * MD5 from :rfc:`1321`. * RIPEMD160 from the `RIPEMD website`_. * SHA1 from `NIST CAVP`_. * SHA2 (224, 256, 384, 512, 512/224, 512/256) from `NIST CAVP`_. * SHA3 (224, 256, 384, 512) from `NIST CAVP`_. * SHAKE (128, 256) from `NIST CAVP`_. * Blake2s and Blake2b from OpenSSL `test/evptests.txt`_. HMAC ~~~~ * HMAC-MD5 from :rfc:`2202`. * HMAC-SHA1 from :rfc:`2202`. * HMAC-RIPEMD160 from :rfc:`2286`. * HMAC-SHA2 (224, 256, 384, 512) from :rfc:`4231`. Key derivation functions ~~~~~~~~~~~~~~~~~~~~~~~~ * HKDF (SHA1, SHA256) from :rfc:`5869`. * PBKDF2 (HMAC-SHA1) from :rfc:`6070`. * scrypt from the `draft RFC`_. * X9.63 KDF from `NIST CAVP`_. * SP 800-108 Counter Mode KDF (HMAC-SHA1, HMAC-SHA224, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512) from `NIST CAVP`_. Key wrapping ~~~~~~~~~~~~ * AES key wrap (AESKW) and 3DES key wrap test vectors from `NIST CAVP`_. * AES key wrap with padding vectors from `Botan's key wrap vectors`_. Recipes ~~~~~~~ * Fernet from its `specification repository`_. Symmetric ciphers ~~~~~~~~~~~~~~~~~ * AES (CBC, CFB, ECB, GCM, OFB, CCM) from `NIST CAVP`_. * AES CTR from :rfc:`3686`. * AES-GCM-SIV (KEY-LENGTH: 128, 256) from OpenSSL's `evpciph_aes_gcm_siv.txt`_. * AES-GCM-SIV (KEY-LENGTH: 192) generated by this project. See :doc:`/development/custom-vectors/aes-192-gcm-siv` * AES OCB3 from :rfc:`7253`, `dkg's additional OCB3 vectors`_, and `OpenSSL's OCB vectors`_. * AES SIV from OpenSSL's `evpciph_aes_siv.txt`_. * 3DES (CBC, CFB, ECB, OFB) from `NIST CAVP`_. * ARC4 (KEY-LENGTH: 40, 56, 64, 80, 128, 192, 256) from :rfc:`6229`. * ARC4 (KEY-LENGTH: 160) generated by this project. See: :doc:`/development/custom-vectors/arc4` * Blowfish (CBC, CFB, ECB, OFB) from `Bruce Schneier's vectors`_. * Camellia (ECB) from NTT's `Camellia page`_ as linked by `CRYPTREC`_. * Camellia (CBC, CFB, OFB) from `OpenSSL's test vectors`_. * CAST5 (ECB) from :rfc:`2144`. * CAST5 (CBC, CFB, OFB) generated by this project. See: :doc:`/development/custom-vectors/cast5` * ChaCha20 from :rfc:`7539` and generated by this project. See: :doc:`/development/custom-vectors/chacha20` * ChaCha20Poly1305 from :rfc:`7539`, `OpenSSL's evpciph.txt`_, and the `BoringSSL ChaCha20Poly1305 tests`_. * IDEA (ECB) from the `NESSIE IDEA vectors`_ created by `NESSIE`_. * IDEA (CBC, CFB, OFB) generated by this project. See: :doc:`/development/custom-vectors/idea` * RC2-128-CBC generated by this project. See: :doc:`/development/custom-vectors/rc2` * SEED (ECB) from :rfc:`4269`. * SEED (CBC) from :rfc:`4196`. * SEED (CFB, OFB) generated by this project. See: :doc:`/development/custom-vectors/seed` * SM4 (CBC, CFB, CTR, ECB, OFB) from `draft-ribose-cfrg-sm4-10`_. * SM4 (GCM) from :rfc:`8998`. Two factor authentication ~~~~~~~~~~~~~~~~~~~~~~~~~ * HOTP from :rfc:`4226` * TOTP from :rfc:`6238` (Note that an `errata`_ for the test vectors in RFC 6238 exists) CMAC ~~~~ * AES-128, AES-192, AES-256, 3DES from `NIST SP-800-38B`_ Poly1305 ~~~~~~~~ * Test vectors from :rfc:`7539`. Creating test vectors --------------------- When official vectors are unavailable ``cryptography`` may choose to build its own using existing vectors as source material. Created Vectors ~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 1 custom-vectors/aes-192-gcm-siv custom-vectors/arc4 custom-vectors/cast5 custom-vectors/chacha20 custom-vectors/idea custom-vectors/seed custom-vectors/hkdf custom-vectors/rc2 If official test vectors appear in the future the custom generated vectors should be discarded. Any vectors generated by this method must also be prefixed with the following header format (substituting the correct information): .. code-block:: python # CAST5 CBC vectors built for https://github.com/pyca/cryptography # Derived from the AESVS MMT test data for CBC # Verified against the CommonCrypto and Go crypto packages # Key Length : 128 .. _`NIST`: https://www.nist.gov/ .. _`IETF`: https://www.ietf.org/ .. _`Project Wycheproof`: https://github.com/C2SP/wycheproof .. _`NIST CAVP`: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program .. _`Bruce Schneier's vectors`: https://www.schneier.com/wp-content/uploads/2015/12/vectors-2.txt .. _`Camellia page`: https://info.isl.ntt.co.jp/crypt/eng/camellia/ .. _`CRYPTREC`: https://www.cryptrec.go.jp .. _`OpenSSL's test vectors`: https://github.com/openssl/openssl/blob/97cf1f6c2854a3a955fd7dd3a1f113deba00c9ef/crypto/evp/evptests.txt#L232 .. _`OpenSSL's evpciph.txt`: https://github.com/openssl/openssl/blob/5a7bc0be97dee9ac715897fe8180a08e211bc6ea/test/evpciph.txt#L2362 .. _`BoringSSL ChaCha20Poly1305 tests`: https://boringssl.googlesource.com/boringssl/+/2e2a226ac9201ac411a84b5e79ac3a7333d8e1c9/crypto/cipher_extra/test/chacha20_poly1305_tests.txt .. _`BoringSSL evp tests`: https://boringssl.googlesource.com/boringssl/+/ce3773f9fe25c3b54390bc51d72572f251c7d7e6/crypto/evp/evp_tests.txt .. _`RIPEMD website`: https://homes.esat.kuleuven.be/~bosselae/ripemd160.html .. _`draft RFC`: https://datatracker.ietf.org/doc/html/draft-josefsson-scrypt-kdf-01 .. _`Specification repository`: https://github.com/fernet/spec .. _`errata`: https://www.rfc-editor.org/errata_search.php?rfc=6238 .. _`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://gitlab.com/gnutls/gnutls/-/commit/ad2061deafdd7db78fd405f9d143b0a7c579da7b .. _`NESSIE IDEA vectors`: https://www.cosic.esat.kuleuven.be/nessie/testvectors/bc/idea/Idea-128-64.verified.test-vectors .. _`NESSIE`: https://en.wikipedia.org/wiki/NESSIE .. _`draft-ribose-cfrg-sm4-10`: https://datatracker.ietf.org/doc/html/draft-ribose-cfrg-sm4-10 .. _`Ed25519 website`: https://ed25519.cr.yp.to/software.html .. _`NIST SP-800-38B`: https://csrc.nist.gov/pubs/sp/800/38/b/final .. _`NIST PKI Testing`: https://csrc.nist.gov/Projects/PKI-Testing .. _`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 .. _`Mozilla bug`: https://bugzilla.mozilla.org/show_bug.cgi?id=233586 .. _`Russian CA`: https://e-trust.gosuslugi.ru/ .. _`test/evptests.txt`: https://github.com/openssl/openssl/blob/2d0b44126763f989a4cbffbffe9d0c7518158bb7/test/evptests.txt .. _`unknown signature OID`: https://bugzilla.mozilla.org/show_bug.cgi?id=405966 .. _`botan`: https://github.com/randombit/botan/blob/57789bdfc55061002b2727d0b32587612829a37c/src/tests/data/pubkey/dh.vec .. _`DHKE`: https://sandilands.info/sgordon/diffie-hellman-secret-key-exchange-with-openssl .. _`Botan's key wrap vectors`: https://github.com/randombit/botan/blob/737f33c09a18500e044dca3e2ae13bd2c08bafdd/src/tests/data/keywrap/nist_key_wrap.vec .. _`root-ed25519.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/root-ed25519.pem .. _`server-ed25519-cert.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/server-ed25519-cert.pem .. _`server-ed448-cert.pem`: https://github.com/openssl/openssl/blob/2a1e2fe145c6eb8e75aa2e1b3a8c3a49384b2852/test/certs/server-ed448-cert.pem .. _`evpciph_aes_gcm_siv.txt`: https://github.com/openssl/openssl/blob/a2b1ab6100d5f0fb50b61d241471eea087415632/test/recipes/30-test_evp_data/evpciph_aes_gcm_siv.txt .. _`evpciph_aes_siv.txt`: https://github.com/openssl/openssl/blob/d830526c711074fdcd82c70c24c31444366a1ed8/test/recipes/30-test_evp_data/evpciph_aes_siv.txt .. _`dkg's additional OCB3 vectors`: https://gitlab.com/dkg/ocb-test-vectors .. _`OpenSSL's OCB vectors`: https://github.com/openssl/openssl/commit/2f19ab18a29cf9c82cdd68bc8c7e5be5061b19be .. _`badkeys`: https://github.com/vcsjones/badkeys/tree/50f1cc5f8d13bf3a2046d689f6452decb15d9c3c .. _`OpenSSL's RFC 6979 test vectors`: https://github.com/openssl/openssl/blob/01690a7ff36c4d18c48b301cdf375c954105a1d9/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt cryptography-43.0.0/docs/doing-a-release.rst010064400017510000177000000073441464676315000171570ustar 00000000000000Doing a release =============== Doing a release of ``cryptography`` requires a few steps. Security Releases ----------------- In addition to the other steps described below, for a release which fixes a security vulnerability, you should also include the following steps: * Request a `CVE from MITRE`_. Once you have received the CVE, it should be included in the :doc:`changelog`. Ideally you should request the CVE before starting the release process so that the CVE is available at the time of the release. * Document the CVE in the git commit that fixes the issue. * Ensure that the :doc:`changelog` entry credits whoever reported the issue and contains the assigned CVE. * Publish a GitHub Security Advisory on the repository with all relevant information. * The release should be announced on the `oss-security`_ mailing list, in addition to the regular announcement lists. Verifying OpenSSL version ------------------------- The release process creates wheels bundling OpenSSL for Windows, macOS, and Linux. Check that the Windows, macOS, and Linux builders (the ``manylinux`` containers) have the latest OpenSSL. If anything is out of date follow the instructions for upgrading OpenSSL. Upgrading OpenSSL ----------------- Use the `upgrading OpenSSL issue template`_. Bumping the version number -------------------------- The next step in doing a release is bumping the version number in the software. * Run ``python release.py bump-version {new_version}`` * Set the release date in the :doc:`/changelog`. * Do a commit indicating this. * Send a pull request with this. * Wait for it to be merged. Performing the release ---------------------- The commit that merged the version number bump is now the official release commit for this release. You will need to have ``git`` configured to perform signed tags. Once this has happened: * Run ``python release.py release``. The release should now be available on PyPI and a tag should be available in the repository. Verifying the release --------------------- You should verify that ``pip install cryptography`` works correctly: .. code-block:: pycon >>> import cryptography >>> cryptography.__version__ '...' >>> import cryptography_vectors >>> cryptography_vectors.__version__ '...' Verify that this is the version you just released. For the Windows wheels check the builds for the ``cryptography-wheel-builder`` job and verify that the final output for each build shows it loaded and linked the expected OpenSSL version. Post-release tasks ------------------ * Send an email to the `mailing list`_ and `python-announce`_ announcing the release. * Close the `milestone`_ for the previous release on GitHub. * For major version releases, send a pull request to pyOpenSSL increasing the maximum ``cryptography`` version pin and perform a pyOpenSSL release. * Update the version number to the next major (e.g. ``0.5.dev1``) with ``python release.py bump-version {new_version}``. * Add new :doc:`/changelog` entry with next version and note that it is under active development * Send a pull request with these items * Check for any outstanding code undergoing a deprecation cycle by looking in ``cryptography.utils`` for ``DeprecatedIn**`` definitions. If any exist open a ticket to increment them for the next release. .. _`CVE from MITRE`: https://cveform.mitre.org/ .. _`oss-security`: https://www.openwall.com/lists/oss-security/ .. _`upgrading OpenSSL issue template`: https://github.com/pyca/cryptography/issues/new?template=openssl-release.md .. _`milestone`: https://github.com/pyca/cryptography/milestones .. _`mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev .. _`python-announce`: https://mail.python.org/mailman3/lists/python-announce-list.python.org/ cryptography-43.0.0/docs/exceptions.rst010064400017510000177000000015251464676315000163770ustar 00000000000000Exceptions ========== .. currentmodule:: cryptography.exceptions .. class:: UnsupportedAlgorithm Raised when the requested algorithm, or combination of algorithms is not supported. .. class:: AlreadyFinalized This is raised when a context is used after being finalized. .. class:: InvalidSignature This is raised when signature verification fails. This can occur with HMAC or asymmetric key signature validation. .. class:: NotYetFinalized This is raised when the AEAD tag property is accessed on a context before it is finalized. .. class:: AlreadyUpdated This is raised when additional data is added to a context after update has already been called. .. class:: InvalidKey This is raised when the verify method of a key derivation function's computed key does not match the expected key. cryptography-43.0.0/docs/faq.rst010064400017510000177000000237241464676315000147720ustar 00000000000000Frequently asked questions ========================== What issues can you help with in your issue tracker? ---------------------------------------------------- The primary purpose of our issue tracker is to enable us to identify and resolve bugs and feature requests in ``cryptography``, so any time a user files a bug, we start by asking: Is this a ``cryptography`` bug, or is it a bug somewhere else? That said, we do our best to help users to debug issues that are in their code or environments. Please note, however, that there's a limit to our ability to assist users in resolving problems that are specific to their environments, particularly when we have no way to reproduce the issue. Lastly, we're not able to provide support for general Python or Python packaging issues. .. _faq-howto-handle-deprecation-warning: I cannot suppress the deprecation warning that ``cryptography`` emits on import ------------------------------------------------------------------------------- .. hint:: The deprecation warning emitted on import does not inherit :py:exc:`DeprecationWarning` but inherits :py:exc:`UserWarning` instead. If your pytest setup follows the best practices of failing on emitted warnings (``filterwarnings = error``), you may ignore it by adding the following line at the end of the list:: ignore:Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in a future release.:UserWarning **Note:** Using ``cryptography.utils.CryptographyDeprecationWarning`` is not possible here because specifying it triggers ``import cryptography`` internally that emits the warning before the ignore rule even kicks in. Ref: https://github.com/pytest-dev/pytest/issues/7524 The same applies when you use :py:func:`~warnings.filterwarnings` in your code or invoke CPython with :std:option:`-W` command line option. ``cryptography`` failed to install! ----------------------------------- If you are having issues installing ``cryptography`` the first troubleshooting step is to upgrade ``pip`` and then try to install again. For most users this will take the form of ``pip install -U pip``, but on Windows you should do ``python -m pip install -U pip``. If you are still seeing errors after upgrading and trying ``pip install cryptography`` again, please see the :doc:`/installation` documentation. How does ``cryptography`` compare to NaCl (Networking and Cryptography Library)? -------------------------------------------------------------------------------- While ``cryptography`` and `NaCl`_ both share the goal of making cryptography easier, and safer, to use for developers, ``cryptography`` is designed to be a general purpose library, interoperable with existing systems, while NaCl features a collection of hand selected algorithms. ``cryptography``'s :ref:`recipes ` layer has similar goals to NaCl. If you prefer NaCl's design, we highly recommend `PyNaCl`_, which is also maintained by the PyCA team. Why use ``cryptography``? ------------------------- If you've done cryptographic work in Python before you have likely encountered other libraries in Python such as *M2Crypto*, *PyCrypto*, or *PyOpenSSL*. In building ``cryptography`` we wanted to address a few issues we observed in the legacy libraries: * Extremely error prone APIs and insecure defaults. * Use of poor implementations of algorithms (i.e. ones with known side-channel attacks). * Lack of maintenance. * Lack of high level APIs. * Lack of PyPy and Python 3 support. * Absence of algorithms such as :class:`AES-GCM ` and :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. Why does ``cryptography`` require Rust? --------------------------------------- ``cryptography`` uses OpenSSL (see: :doc:`/openssl`) for its cryptographic operations. OpenSSL is the de facto standard for cryptographic libraries and provides high performance along with various certifications that may be relevant to developers. However, it is written in C and lacks `memory safety`_. We want ``cryptography`` to be as secure as possible while retaining the advantages of OpenSSL, so we've chosen to rewrite non-cryptographic operations (such as ASN.1 parsing) in a high performance memory safe language: Rust. ``cryptography`` raised an ``InternalError`` and I'm not sure what to do? ------------------------------------------------------------------------- Frequently ``InternalError`` is raised when there are errors on the OpenSSL error stack that were placed there by other libraries that are also using OpenSSL. Try removing the other libraries and see if the problem persists. If you have no other libraries using OpenSSL in your process, or they do not appear to be at fault, it's possible that this is a bug in ``cryptography``. Please file an `issue`_ with instructions on how to reproduce it. Installing cryptography with OpenSSL 0.9.8, 1.0.0, 1.0.1, 1.0.2, 1.1.0 fails ---------------------------------------------------------------------------- The OpenSSL project has dropped support for the 0.9.8, 1.0.0, 1.0.1, 1.0.2, and 1.1.0 release series. Since they are no longer receiving security patches from upstream, ``cryptography`` is also dropping support for them. To fix this issue you should upgrade to a newer version of OpenSSL (1.1.1 or later). This may require you to upgrade to a newer operating system. Installing ``cryptography`` fails with ``error: Can not find Rust compiler`` ---------------------------------------------------------------------------- Building ``cryptography`` from source requires you have :ref:`Rust installed and available` on your ``PATH``. You may be able to fix this by upgrading to a newer version of ``pip`` which will install a pre-compiled ``cryptography`` wheel. If not, you'll need to install Rust. Follow the :ref:`instructions` to ensure you install a recent Rust version. Rust is only required during the build phase of ``cryptography``, you do not need to have Rust installed after you've built ``cryptography``. This is the same as the C compiler toolchain which is also required to build ``cryptography``, but not afterwards. I'm getting errors installing or importing ``cryptography`` on AWS Lambda ------------------------------------------------------------------------- Make sure you're following AWS's documentation either for `building .zip archives for Lambda`_ or `building container images for Lambda`_. Why are there no wheels for my Python3.x version? ------------------------------------------------- Our Python3 wheels are ``abi3`` wheels. This means they support multiple versions of Python. The ``abi3`` wheel can be used with any version of Python greater than or equal to the version it specifies. Recent versions of ``pip`` will automatically install ``abi3`` wheels. Why can't I import my PEM file? ------------------------------- PEM is a format (defined by several RFCs, but originally :rfc:`1421`) for encoding keys, certificates, and others cryptographic data into a regular form. The data is encoded as base64 and wrapped with a header and footer. If you are having trouble importing PEM files, make sure your file fits the following rules: * has a one-line header like this: ``-----BEGIN [FILE TYPE]-----`` (where ``[FILE TYPE]`` is ``CERTIFICATE``, ``PUBLIC KEY``, ``PRIVATE KEY``, etc.) * has a one-line footer like this: ``-----END [FILE TYPE]-----`` * all lines, except for the final one, must consist of exactly 64 characters. For example, this is a PEM file for a RSA Public Key: :: -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7CsKFSzq20NLb2VQDXma 9DsDXtKADv0ziI5hT1KG6Bex5seE9pUoEcUxNv4uXo2jzAUgyRweRl/DLU8SoN8+ WWd6YWik4GZvNv7j0z28h9Q5jRySxy4dmElFtIRHGiKhqd1Z06z4AzrmKEzgxkOk LJjY9cvwD+iXjpK2oJwNNyavvjb5YZq6V60RhpyNtKpMh2+zRLgIk9sROEPQeYfK 22zj2CnGBMg5Gm2uPOsGDltl/I/Fdh1aO3X4i1GXwCuPf1kSAg6lPJD0batftkSG v0X0heUaV0j1HSNlBWamT4IR9+iJfKJHekOqvHQBcaCu7Ja4kXzx6GZ3M2j/Ja3A 2QIDAQAB -----END PUBLIC KEY----- .. _faq-missing-backend: What happened to the backend argument? -------------------------------------- ``cryptography`` stopped requiring the use of ``backend`` arguments in version 3.1 and deprecated their use in version 36.0. If you are on an older version that requires these arguments please view the appropriate documentation version or upgrade to the latest release. Note that for forward compatibility ``backend`` is still silently accepted by functions that previously required it, but it is ignored and no longer documented. Will you upload wheels for my non-x86 non-ARM64 CPU architecture? ----------------------------------------------------------------- Maybe! But there's some pre-requisites. For us to build wheels and upload them to PyPI, we consider it necessary to run our tests for that architecture as a part of our CI (i.e. for every commit). If we don't run the tests, it's hard to have confidence that everything works -- particularly with cryptography, which frequently employs per-architecture assembly code. For us to add something to CI we need a provider which offers builds on that architecture, which integrate into our workflows, has sufficient capacity, and performs well enough not to regress the contributor experience. We don't think this is an insurmountable bar, but it's also not one that can be cleared lightly. If you are interested in helping support a new CPU architecture, we encourage you to reach out, discuss, and contribute that support. We will attempt to be supportive, but we cannot commit to doing the work ourselves. .. _`NaCl`: https://nacl.cr.yp.to/ .. _`PyNaCl`: https://pynacl.readthedocs.io .. _`issue`: https://github.com/pyca/cryptography/issues .. _`memory safety`: https://alexgaynor.net/2019/aug/12/introduction-to-memory-unsafety-for-vps-of-engineering/ .. _`building .zip archives for Lambda`: https://docs.aws.amazon.com/lambda/latest/dg/python-package.html .. _`building container images for Lambda`: https://docs.aws.amazon.com/lambda/latest/dg/python-image.html cryptography-43.0.0/docs/fernet.rst010064400017510000177000000266211464676315000155050ustar 00000000000000Fernet (symmetric encryption) ============================= .. currentmodule:: cryptography.fernet Fernet guarantees that a message encrypted using it cannot be manipulated or read without the key. `Fernet`_ is an implementation of symmetric (also known as "secret key") authenticated cryptography. Fernet also has support for implementing key rotation via :class:`MultiFernet`. .. class:: Fernet(key) This class provides both encryption and decryption facilities. .. doctest:: >>> from cryptography.fernet import Fernet >>> key = Fernet.generate_key() >>> f = Fernet(key) >>> token = f.encrypt(b"my deep dark secret") >>> token b'...' >>> f.decrypt(token) b'my deep dark secret' :param key: A URL-safe base64-encoded 32-byte key. This **must** be kept secret. Anyone with this key is able to create and read messages. :type key: bytes or str .. classmethod:: generate_key() Generates a fresh fernet key. Keep this some place safe! If you lose it you'll no longer be able to decrypt messages; if anyone else gains access to it, they'll be able to decrypt all of your messages, and they'll also be able forge arbitrary messages that will be authenticated and decrypted. .. method:: encrypt(data) Encrypts data passed. The result of this encryption is known as a "Fernet token" and has strong privacy and authenticity guarantees. :param bytes data: The message you would like to encrypt. :returns bytes: A secure message that cannot be read or altered without the key. It is URL-safe base64-encoded. This is referred to as a "Fernet token". :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. note:: The encrypted message contains the current time when it was generated in *plaintext*, the time a message was created will therefore be visible to a possible attacker. .. method:: encrypt_at_time(data, current_time) .. versionadded:: 3.0 Encrypts data passed using explicitly passed current time. See :meth:`encrypt` for the documentation of the ``data`` parameter, the return type and the exceptions raised. The motivation behind this method is for the client code to be able to test token expiration. Since this method can be used in an insecure manner one should make sure the correct time (``int(time.time())``) is passed as ``current_time`` outside testing. :param int current_time: The current time. .. note:: Similarly to :meth:`encrypt` the encrypted message contains the timestamp in *plaintext*, in this case the timestamp is the value of the ``current_time`` parameter. .. method:: decrypt(token, ttl=None) Decrypts a Fernet token. If successfully decrypted you will receive the original plaintext as the result, otherwise an exception will be raised. It is safe to use this data immediately as Fernet verifies that the data has not been tampered with prior to returning it. :param bytes or str token: The Fernet token. This is the result of calling :meth:`encrypt`. :param int ttl: Optionally, the number of seconds old a message may be for it to be valid. If the message is older than ``ttl`` seconds (from the time it was originally created) an exception will be raised. If ``ttl`` is not provided (or is ``None``), the age of the message is not considered. :returns bytes: The original plaintext. :raises cryptography.fernet.InvalidToken: If the ``token`` is in any way invalid, this exception is raised. A token may be invalid for a number of reasons: it is older than the ``ttl``, it is malformed, or it does not have a valid signature. :raises TypeError: This exception is raised if ``token`` is not ``bytes`` or ``str``. .. method:: decrypt_at_time(token, ttl, current_time) .. versionadded:: 3.0 Decrypts a token using explicitly passed current time. See :meth:`decrypt` for the documentation of the ``token`` and ``ttl`` parameters (``ttl`` is required here), the return type and the exceptions raised. The motivation behind this method is for the client code to be able to test token expiration. Since this method can be used in an insecure manner one should make sure the correct time (``int(time.time())``) is passed as ``current_time`` outside testing. :param int current_time: The current time. .. method:: extract_timestamp(token) .. versionadded:: 2.3 Returns the timestamp for the token. The caller can then decide if the token is about to expire and, for example, issue a new token. :param bytes or str token: The Fernet token. This is the result of calling :meth:`encrypt`. :returns int: The Unix timestamp of the token. :raises cryptography.fernet.InvalidToken: If the ``token``'s signature is invalid this exception is raised. :raises TypeError: This exception is raised if ``token`` is not ``bytes`` or ``str``. .. class:: MultiFernet(fernets) .. versionadded:: 0.7 This class implements key rotation for Fernet. It takes a ``list`` of :class:`Fernet` instances and implements the same API with the exception of one additional method: :meth:`MultiFernet.rotate`: .. doctest:: >>> from cryptography.fernet import Fernet, MultiFernet >>> key1 = Fernet(Fernet.generate_key()) >>> key2 = Fernet(Fernet.generate_key()) >>> f = MultiFernet([key1, key2]) >>> token = f.encrypt(b"Secret message!") >>> token b'...' >>> f.decrypt(token) b'Secret message!' MultiFernet performs all encryption options using the *first* key in the ``list`` provided. MultiFernet attempts to decrypt tokens with each key in turn. A :class:`cryptography.fernet.InvalidToken` exception is raised if the correct key is not found in the ``list`` provided. Key rotation makes it easy to replace old keys. You can add your new key at the front of the list to start encrypting new messages, and remove old keys as they are no longer needed. Token rotation as offered by :meth:`MultiFernet.rotate` is a best practice and manner of cryptographic hygiene designed to limit damage in the event of an undetected event and to increase the difficulty of attacks. For example, if an employee who had access to your company's fernet keys leaves, you'll want to generate new fernet key, rotate all of the tokens currently deployed using that new key, and then retire the old fernet key(s) to which the employee had access. .. method:: rotate(msg) .. versionadded:: 2.2 Rotates a token by re-encrypting it under the :class:`MultiFernet` instance's primary key. This preserves the timestamp that was originally saved with the token. If a token has successfully been rotated then the rotated token will be returned. If rotation fails this will raise an exception. .. doctest:: >>> from cryptography.fernet import Fernet, MultiFernet >>> key1 = Fernet(Fernet.generate_key()) >>> key2 = Fernet(Fernet.generate_key()) >>> f = MultiFernet([key1, key2]) >>> token = f.encrypt(b"Secret message!") >>> token b'...' >>> f.decrypt(token) b'Secret message!' >>> key3 = Fernet(Fernet.generate_key()) >>> f2 = MultiFernet([key3, key1, key2]) >>> rotated = f2.rotate(token) >>> f2.decrypt(rotated) b'Secret message!' :param bytes or str msg: The token to re-encrypt. :returns bytes: A secure message that cannot be read or altered without the key. This is URL-safe base64-encoded. This is referred to as a "Fernet token". :raises cryptography.fernet.InvalidToken: If a ``token`` is in any way invalid this exception is raised. :raises TypeError: This exception is raised if the ``msg`` is not ``bytes`` or ``str``. .. class:: InvalidToken See :meth:`Fernet.decrypt` for more information. Using passwords with Fernet --------------------------- It is possible to use passwords with Fernet. To do this, you need to run the password through a key derivation function such as :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`, bcrypt or :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt`. .. doctest:: >>> import base64 >>> import os >>> from cryptography.fernet import Fernet >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC >>> password = b"password" >>> salt = os.urandom(16) >>> kdf = PBKDF2HMAC( ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, ... iterations=480000, ... ) >>> key = base64.urlsafe_b64encode(kdf.derive(password)) >>> f = Fernet(key) >>> token = f.encrypt(b"Secret message!") >>> token b'...' >>> f.decrypt(token) b'Secret message!' In this scheme, the salt has to be stored in a retrievable location in order to derive the same key from the password in the future. The iteration count used should be adjusted to be as high as your server can tolerate. A good default is at least 480,000 iterations, which is what `Django recommends as of December 2022`_. Implementation -------------- Fernet is built on top of a number of standard cryptographic primitives. Specifically it uses: * :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` in :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode with a 128-bit key for encryption; using :class:`~cryptography.hazmat.primitives.padding.PKCS7` padding. * :class:`~cryptography.hazmat.primitives.hmac.HMAC` using :class:`~cryptography.hazmat.primitives.hashes.SHA256` for authentication. * Initialization vectors are generated using ``os.urandom()``. For complete details consult the `specification`_. Limitations ----------- Fernet is ideal for encrypting data that easily fits in memory. As a design feature it does not expose unauthenticated bytes. This means that the complete message contents must be available in memory, making Fernet generally unsuitable for very large files at this time. .. _`Fernet`: https://github.com/fernet/spec/ .. _`Django recommends as of December 2022`: https://github.com/django/django/blob/main/django/contrib/auth/hashers.py .. _`specification`: https://github.com/fernet/spec/blob/master/Spec.md cryptography-43.0.0/docs/glossary.rst010064400017510000177000000121661464676315000160640ustar 00000000000000Glossary ======== .. glossary:: :sorted: plaintext User-readable data you care about. ciphertext The encoded data, it's not user readable. Potential attackers are able to see this. encryption The process of converting plaintext to ciphertext. decryption The process of converting ciphertext to plaintext. key Secret data is encoded with a function using this key. Sometimes multiple keys are used. These **must** be kept secret, if a key is exposed to an attacker, any data encrypted with it will be exposed. symmetric cryptography Cryptographic operations where encryption and decryption use the same key. public-key cryptography asymmetric cryptography Cryptographic operations where encryption and decryption use different keys. There are separate encryption and decryption keys. Typically encryption is performed using a :term:`public key`, and it can then be decrypted using a :term:`private key`. Asymmetric cryptography can also be used to create signatures, which can be generated with a :term:`private key` and verified with a :term:`public key`. public key This is one of two keys involved in :term:`public-key cryptography`. It can be used to encrypt messages for someone possessing the corresponding :term:`private key` and to verify signatures created with the corresponding :term:`private key`. This can be distributed publicly, hence the name. private key This is one of two keys involved in :term:`public-key cryptography`. It can be used to decrypt messages which were encrypted with the corresponding :term:`public key`, as well as to create signatures, which can be verified with the corresponding :term:`public key`. These **must** be kept secret, if they are exposed, all encrypted messages are compromised, and an attacker will be able to forge signatures. authentication The process of verifying that a message was created by a specific individual (or program). Like encryption, authentication can be either symmetric or asymmetric. Authentication is necessary for effective encryption. ciphertext indistinguishability This is a property of encryption systems whereby two encrypted messages aren't distinguishable without knowing the encryption key. This is considered a basic, necessary property for a working encryption system. nonce A nonce is a **n**\ umber used **once**. Nonces are used in many cryptographic protocols. Generally, a nonce does not have to be secret or unpredictable, but it must be unique. A nonce is often a random or pseudo-random number (see :doc:`Random number generation `). Since a nonce does not have to be unpredictable, it can also take a form of a counter. opaque key An opaque key is a type of key that allows you to perform cryptographic operations such as encryption, decryption, signing, and verification, but does not allow access to the key itself. Typically an opaque key is loaded from a `hardware security module`_ (HSM). A-label The ASCII compatible encoded (ACE) representation of an internationalized (unicode) domain name. A-labels begin with the prefix ``xn--``. To create an A-label from a unicode domain string use a library like `idna`_. bits A bit is binary value -- a value that has only two possible states. Typically binary values are represented visually as 0 or 1, but remember that their actual value is not a printable character. A byte on modern computers is 8 bits and represents 256 possible values. In cryptographic applications when you see something say it requires a 128 bit key, you can calculate the number of bytes by dividing by 8. 128 divided by 8 is 16, so a 128 bit key is a 16 byte key. bytes-like A bytes-like object contains binary data and supports the `buffer protocol`_. This includes ``bytes``, ``bytearray``, and ``memoryview`` objects. It is :term:`unsafe` to pass a mutable object (e.g., a ``bytearray`` or other implementer of the buffer protocol) and to `mutate it concurrently`_ with the operation it has been provided for. U-label The presentational unicode form of an internationalized domain name. U-labels use unicode characters outside the ASCII range and are encoded as A-labels when stored in certificates. unsafe This is a term used to describe an operation where the user must ensure that the input is correct. Failure to do so can result in crashes, hangs, and other security issues. .. _`hardware security module`: https://en.wikipedia.org/wiki/Hardware_security_module .. _`idna`: https://pypi.org/project/idna/ .. _`buffer protocol`: https://docs.python.org/3/c-api/buffer.html .. _`mutate it concurrently`: https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ cryptography-43.0.0/docs/hazmat/decrepit/ciphers.rst010064400017510000177000000114531464676315000207370ustar 00000000000000.. hazmat:: Decrepit Symmetric algorithms ============================= .. module:: cryptography.hazmat.decrepit.ciphers This module contains decrepit symmetric encryption algorithms. These are algorithms that should not be used unless necessary for backwards compatibility or interoperability with legacy systems. Their use is **strongly discouraged**. These algorithms require you to use a :class:`~cryptography.hazmat.primitives.ciphers.Cipher` object along with the appropriate :mod:`~cryptography.hazmat.primitives.ciphers.modes`. .. class:: ARC4(key) .. versionadded:: 43.0.0 ARC4 (Alleged RC4) is a stream cipher with serious weaknesses in its initial stream output. Its use is strongly discouraged. ARC4 does not use mode constructions. :param key: The secret key. This must be kept secret. Either ``40``, ``56``, ``64``, ``80``, ``128``, ``192``, or ``256`` :term:`bits` in length. :type key: :term:`bytes-like` .. doctest:: >>> import os >>> from cryptography.hazmat.decrepit.ciphers.algorithms import ARC4 >>> from cryptography.hazmat.primitives.ciphers import Cipher, modes >>> key = os.urandom(16) >>> algorithm = ARC4(key) >>> cipher = Cipher(algorithm, mode=None) >>> encryptor = cipher.encryptor() >>> ct = encryptor.update(b"a secret message") >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) b'a secret message' .. class:: TripleDES(key) .. versionadded:: 43.0.0 Triple DES (Data Encryption Standard), sometimes referred to as 3DES, is a block cipher standardized by NIST. Triple DES has known crypto-analytic flaws, however none of them currently enable a practical attack. Nonetheless, Triple DES is not recommended for new applications because it is incredibly slow; old applications should consider moving away from it. :param key: The secret key. This must be kept secret. Either ``64``, ``128``, or ``192`` :term:`bits` long. DES only uses ``56``, ``112``, or ``168`` bits of the key as there is a parity byte in each component of the key. Some writing refers to there being up to three separate keys that are each ``56`` bits long, they can simply be concatenated to produce the full key. :type key: :term:`bytes-like` .. class:: CAST5(key) .. versionadded:: 43.0.0 CAST5 (also known as CAST-128) is a block cipher approved for use in the Canadian government by the `Communications Security Establishment`_. It is a variable key length cipher and supports keys from 40-128 :term:`bits` in length. :param key: The secret key, This must be kept secret. 40 to 128 :term:`bits` in length in increments of 8 bits. :type key: :term:`bytes-like` .. doctest:: >>> import os >>> from cryptography.hazmat.decrepit.ciphers.algorithms import CAST5 >>> from cryptography.hazmat.primitives.ciphers import Cipher, modes >>> key = os.urandom(16) >>> iv = os.urandom(8) >>> algorithm = CAST5(key) >>> cipher = Cipher(algorithm, modes.CBC(iv)) >>> encryptor = cipher.encryptor() >>> ct = encryptor.update(b"a secret message") >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) b'a secret message' .. class:: SEED(key) .. versionadded:: 43.0.0 SEED is a block cipher developed by the Korea Information Security Agency (KISA). It is defined in :rfc:`4269` and is used broadly throughout South Korean industry, but rarely found elsewhere. :param key: The secret key. This must be kept secret. ``128`` :term:`bits` in length. :type key: :term:`bytes-like` .. class:: Blowfish(key) .. versionadded:: 43.0.0 Blowfish is a block cipher developed by Bruce Schneier. It is known to be susceptible to attacks when using weak keys. The author has recommended that users of Blowfish move to newer algorithms. :param key: The secret key. This must be kept secret. 32 to 448 :term:`bits` in length in increments of 8 bits. :type key: :term:`bytes-like` .. class:: IDEA(key) .. versionadded:: 43.0.0 IDEA (`International Data Encryption Algorithm`_) is a block cipher created in 1991. It is an optional component of the `OpenPGP`_ standard. This cipher is susceptible to attacks when using weak keys. It is recommended that you do not use this cipher for new applications. :param key: The secret key. This must be kept secret. ``128`` :term:`bits` in length. :type key: :term:`bytes-like` .. _`Communications Security Establishment`: https://www.cse-cst.gc.ca .. _`International Data Encryption Algorithm`: https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm .. _`OpenPGP`: https://www.openpgp.org/ cryptography-43.0.0/docs/hazmat/decrepit/index.rst010064400017510000177000000005131464676315000204040ustar 00000000000000.. hazmat:: Decrepit cryptography ===================== This module holds old, deprecated, and/or insecure cryptographic algorithms that may be needed in exceptional cases for backwards compatibility or interoperability reasons. Unless necessary their use is **strongly discouraged**. .. toctree:: :maxdepth: 2 ciphers cryptography-43.0.0/docs/hazmat/primitives/aead.rst010064400017510000177000000506171464676315000205750ustar 00000000000000.. hazmat:: Authenticated encryption ======================== .. module:: cryptography.hazmat.primitives.ciphers.aead Authenticated encryption with associated data (AEAD) are encryption schemes which provide both confidentiality and integrity for their ciphertext. They also support providing integrity for associated data which is not encrypted. .. class:: ChaCha20Poly1305(key) .. versionadded:: 2.0 The ChaCha20Poly1305 construction is defined in :rfc:`7539` section 2.8. It is a stream cipher combined with a MAC that offers strong integrity guarantees. :param key: A 32-byte key. This **must** be kept secret. :type key: :term:`bytes-like` :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of OpenSSL does not support ChaCha20Poly1305. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 >>> data = b"a secret message" >>> aad = b"authenticated but unencrypted data" >>> key = ChaCha20Poly1305.generate_key() >>> chacha = ChaCha20Poly1305(key) >>> nonce = os.urandom(12) >>> ct = chacha.encrypt(nonce, data, aad) >>> chacha.decrypt(nonce, ct, aad) b'a secret message' .. classmethod:: generate_key() Securely generates a random ChaCha20Poly1305 key. :returns bytes: A 32 byte key. .. method:: encrypt(nonce, data, associated_data) .. warning:: Reuse of a ``nonce`` with a given ``key`` compromises the security of any message with that ``nonce`` and ``key`` pair. Encrypts the ``data`` provided and authenticates the ``associated_data``. The output of this can be passed directly to the ``decrypt`` method. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to encrypt. :type data: :term:`bytes-like` :param associated_data: Additional data that should be authenticated with the key, but does not need to be encrypted. Can be ``None``. :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the 16 byte tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) Decrypts the ``data`` and authenticates the ``associated_data``. If you called encrypt with ``associated_data`` you must pass the same ``associated_data`` in decrypt or the integrity check will fail. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to decrypt (with tag appended). :type data: :term:`bytes-like` :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. .. class:: AESGCM(key) .. versionadded:: 2.0 The AES-GCM construction is composed of the :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` block cipher utilizing Galois Counter Mode (GCM). :param key: A 128, 192, or 256-bit key. This **must** be kept secret. :type key: :term:`bytes-like` .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers.aead import AESGCM >>> data = b"a secret message" >>> aad = b"authenticated but unencrypted data" >>> key = AESGCM.generate_key(bit_length=128) >>> aesgcm = AESGCM(key) >>> nonce = os.urandom(12) >>> ct = aesgcm.encrypt(nonce, data, aad) >>> aesgcm.decrypt(nonce, ct, aad) b'a secret message' .. classmethod:: generate_key(bit_length) Securely generates a random AES-GCM key. :param bit_length: The bit length of the key to generate. Must be 128, 192, or 256. :returns bytes: The generated key. .. method:: encrypt(nonce, data, associated_data) .. warning:: Reuse of a ``nonce`` with a given ``key`` compromises the security of any message with that ``nonce`` and ``key`` pair. Encrypts and authenticates the ``data`` provided as well as authenticating the ``associated_data``. The output of this can be passed directly to the ``decrypt`` method. :param nonce: NIST `recommends a 96-bit IV length`_ for best performance but it can be up to 2\ :sup:`64` - 1 :term:`bits`. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to encrypt. :type data: :term:`bytes-like` :param associated_data: Additional data that should be authenticated with the key, but is not encrypted. Can be ``None``. :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the 16 byte tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) Decrypts the ``data`` and authenticates the ``associated_data``. If you called encrypt with ``associated_data`` you must pass the same ``associated_data`` in decrypt or the integrity check will fail. :param nonce: NIST `recommends a 96-bit IV length`_ for best performance but it can be up to 2\ :sup:`64` - 1 :term:`bits`. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to decrypt (with tag appended). :type data: :term:`bytes-like` :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. .. class:: AESGCMSIV(key) .. versionadded:: 42.0.0 The AES-GCM-SIV construction is defined in :rfc:`8452` and is composed of the :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` block cipher utilizing Galois Counter Mode (GCM) and a synthetic initialization vector (SIV). :param key: A 128, 192, or 256-bit key. This **must** be kept secret. :type key: :term:`bytes-like` :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of OpenSSL does not support AES-GCM-SIV. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV >>> data = b"a secret message" >>> aad = b"authenticated but unencrypted data" >>> key = AESGCMSIV.generate_key(bit_length=128) >>> aesgcmsiv = AESGCMSIV(key) >>> nonce = os.urandom(12) >>> ct = aesgcmsiv.encrypt(nonce, data, aad) >>> aesgcmsiv.decrypt(nonce, ct, aad) b'a secret message' .. classmethod:: generate_key(bit_length) Securely generates a random AES-GCM-SIV key. :param bit_length: The bit length of the key to generate. Must be 128, 192, or 256. :returns bytes: The generated key. .. method:: encrypt(nonce, data, associated_data) Encrypts and authenticates the ``data`` provided as well as authenticating the ``associated_data``. The output of this can be passed directly to the ``decrypt`` method. :param nonce: A 12-byte value. :type nonce: :term:`bytes-like` :param data: The data to encrypt. :type data: :term:`bytes-like` :param associated_data: Additional data that should be authenticated with the key, but is not encrypted. Can be ``None``. :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the 16 byte tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger than 2\ :sup:`32` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) Decrypts the ``data`` and authenticates the ``associated_data``. If you called encrypt with ``associated_data`` you must pass the same ``associated_data`` in decrypt or the integrity check will fail. :param nonce: A 12-byte value. :type nonce: :term:`bytes-like` :param data: The data to decrypt (with tag appended). :type data: :term:`bytes-like` :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. .. class:: AESOCB3(key) .. versionadded:: 36.0.0 The OCB3 construction is defined in :rfc:`7253`. It is an AEAD mode that offers strong integrity guarantees and good performance. :param key: A 128, 192, or 256-bit key. This **must** be kept secret. :type key: :term:`bytes-like` :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of OpenSSL does not support AES-OCB3. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers.aead import AESOCB3 >>> data = b"a secret message" >>> aad = b"authenticated but unencrypted data" >>> key = AESOCB3.generate_key(bit_length=128) >>> aesocb = AESOCB3(key) >>> nonce = os.urandom(12) >>> ct = aesocb.encrypt(nonce, data, aad) >>> aesocb.decrypt(nonce, ct, aad) b'a secret message' .. classmethod:: generate_key(bit_length) Securely generates a random AES-OCB3 key. :param bit_length: The bit length of the key to generate. Must be 128, 192, or 256. :returns bytes: The generated key. .. method:: encrypt(nonce, data, associated_data) .. warning:: Reuse of a ``nonce`` with a given ``key`` compromises the security of any message with that ``nonce`` and ``key`` pair. Encrypts and authenticates the ``data`` provided as well as authenticating the ``associated_data``. The output of this can be passed directly to the ``decrypt`` method. :param nonce: A 12-15 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to encrypt. :type data: :term:`bytes-like` :param associated_data: Additional data that should be authenticated with the key, but is not encrypted. Can be ``None``. :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the 16 byte tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) Decrypts the ``data`` and authenticates the ``associated_data``. If you called encrypt with ``associated_data`` you must pass the same ``associated_data`` in decrypt or the integrity check will fail. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to decrypt (with tag appended). :type data: :term:`bytes-like` :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. .. class:: AESSIV(key) .. versionadded:: 37.0.0 The SIV (synthetic initialization vector) construction is defined in :rfc:`5297`. Depending on how it is used, SIV allows either deterministic authenticated encryption or nonce-based, misuse-resistant authenticated encryption. :param key: A 256, 384, or 512-bit key (double sized from typical AES). This **must** be kept secret. :type key: :term:`bytes-like` :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of OpenSSL does not support AES-SIV. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers.aead import AESSIV >>> data = b"a secret message" >>> nonce = os.urandom(16) >>> aad = [b"authenticated but unencrypted data", nonce] >>> key = AESSIV.generate_key(bit_length=512) # AES256 requires 512-bit keys for SIV >>> aessiv = AESSIV(key) >>> ct = aessiv.encrypt(data, aad) >>> aessiv.decrypt(ct, aad) b'a secret message' .. classmethod:: generate_key(bit_length) Securely generates a random AES-SIV key. :param bit_length: The bit length of the key to generate. Must be 256, 384, or 512. AES-SIV splits the key into an encryption and MAC key, so these lengths correspond to AES 128, 192, and 256. :returns bytes: The generated key. .. method:: encrypt(data, associated_data) .. note:: SIV performs nonce-based authenticated encryption when a component of the associated data is a nonce. The final associated data in the list is used for the nonce. Random nonces should have at least 128-bits of entropy. If a nonce is reused with SIV authenticity is retained and confidentiality is only compromised to the extent that an attacker can determine that the same plaintext (and same associated data) was protected with the same nonce and key. If you do not supply a nonce encryption is deterministic and the same (plaintext, key) pair will always produce the same ciphertext. Encrypts and authenticates the ``data`` provided as well as authenticating the ``associated_data``. The output of this can be passed directly to the ``decrypt`` method. :param data: The data to encrypt. :type data: :term:`bytes-like` :param list associated_data: An optional ``list`` of ``bytes-like objects``. This is additional data that should be authenticated with the key, but is not encrypted. Can be ``None``. In SIV mode the final element of this list is treated as a ``nonce``. :returns bytes: The ciphertext bytes with the 16 byte tag **prepended**. :raises OverflowError: If ``data`` or an ``associated_data`` element is larger than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(data, associated_data) Decrypts the ``data`` and authenticates the ``associated_data``. If you called encrypt with ``associated_data`` you must pass the same ``associated_data`` in decrypt or the integrity check will fail. :param bytes data: The data to decrypt (with tag **prepended**). :param list associated_data: An optional ``list`` of ``bytes-like objects``. This is additional data that should be authenticated with the key, but is not encrypted. Can be ``None`` if none was used during encryption. :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key or associated data are wrong. .. class:: AESCCM(key, tag_length=16) .. versionadded:: 2.0 .. note: AES-CCM is provided largely for compatibility with existing protocols. Due to its construction it is not as computationally efficient as other AEAD ciphers. The AES-CCM construction is composed of the :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` block cipher utilizing Counter with CBC-MAC (CCM) (specified in :rfc:`3610`). :param key: A 128, 192, or 256-bit key. This **must** be kept secret. :type key: :term:`bytes-like` :param int tag_length: The length of the authentication tag. This defaults to 16 bytes and it is **strongly** recommended that you do not make it shorter unless absolutely necessary. Valid tag lengths are 4, 6, 8, 10, 12, 14, and 16. :raises cryptography.exceptions.UnsupportedAlgorithm: If the version of OpenSSL does not support AES-CCM. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers.aead import AESCCM >>> data = b"a secret message" >>> aad = b"authenticated but unencrypted data" >>> key = AESCCM.generate_key(bit_length=128) >>> aesccm = AESCCM(key) >>> nonce = os.urandom(13) >>> ct = aesccm.encrypt(nonce, data, aad) >>> aesccm.decrypt(nonce, ct, aad) b'a secret message' .. classmethod:: generate_key(bit_length) Securely generates a random AES-CCM key. :param bit_length: The bit length of the key to generate. Must be 128, 192, or 256. :returns bytes: The generated key. .. method:: encrypt(nonce, data, associated_data) .. warning:: Reuse of a ``nonce`` with a given ``key`` compromises the security of any message with that ``nonce`` and ``key`` pair. Encrypts and authenticates the ``data`` provided as well as authenticating the ``associated_data``. The output of this can be passed directly to the ``decrypt`` method. :param nonce: A value of between 7 and 13 bytes. The maximum length is determined by the length of the ciphertext you are encrypting and must satisfy the condition: ``len(data) < 2 ** (8 * (15 - len(nonce)))`` **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to encrypt. :type data: :term:`bytes-like` :param associated_data: Additional data that should be authenticated with the key, but is not encrypted. Can be ``None``. :type associated_data: :term:`bytes-like` :returns bytes: The ciphertext bytes with the tag appended. :raises OverflowError: If ``data`` or ``associated_data`` is larger than 2\ :sup:`31` - 1 bytes. .. method:: decrypt(nonce, data, associated_data) Decrypts the ``data`` and authenticates the ``associated_data``. If you called encrypt with ``associated_data`` you must pass the same ``associated_data`` in decrypt or the integrity check will fail. :param nonce: A value of between 7 and 13 bytes. This is the same value used when you originally called encrypt. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` :param data: The data to decrypt (with tag appended). :type data: :term:`bytes-like` :param associated_data: Additional data to authenticate. Can be ``None`` if none was passed during encryption. :type associated_data: :term:`bytes-like` :returns bytes: The original plaintext. :raises cryptography.exceptions.InvalidTag: If the authentication tag doesn't validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong. .. _`recommends a 96-bit IV length`: https://csrc.nist.gov/pubs/sp/800/38/d/final cryptography-43.0.0/docs/hazmat/primitives/asymmetric/dh.rst010064400017510000177000000271641464676315000224540ustar 00000000000000.. hazmat:: Diffie-Hellman key exchange =========================== .. currentmodule:: cryptography.hazmat.primitives.asymmetric.dh .. note:: For security and performance reasons we suggest using :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDH` instead of DH where possible. `Diffie-Hellman key exchange`_ (D–H) is a method that allows two parties to jointly agree on a shared secret using an insecure channel. Exchange Algorithm ~~~~~~~~~~~~~~~~~~ For most applications the ``shared_key`` should be passed to a key derivation function. This allows mixing of additional information into the key, derivation of multiple keys, and destroys any structure that may be present. .. warning:: This example does not give `forward secrecy`_ and is only provided as a demonstration of the basic Diffie-Hellman construction. For real world applications always use the ephemeral form described after this example. .. code-block:: pycon >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import dh >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF >>> # Generate some parameters. These can be reused. >>> parameters = dh.generate_parameters(generator=2, key_size=2048) >>> # Generate a private key for use in the exchange. >>> server_private_key = parameters.generate_private_key() >>> # In a real handshake the peer is a remote client. For this >>> # example we'll generate another local private key though. Note that in >>> # a DH handshake both peers must agree on a common set of parameters. >>> peer_private_key = parameters.generate_private_key() >>> shared_key = server_private_key.exchange(peer_private_key.public_key()) >>> # Perform key derivation. >>> derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key) >>> # And now we can demonstrate that the handshake performed in the >>> # opposite direction gives the same final value >>> same_shared_key = peer_private_key.exchange( ... server_private_key.public_key() ... ) >>> same_derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(same_shared_key) >>> derived_key == same_derived_key DHE (or EDH), the ephemeral form of this exchange, is **strongly preferred** over simple DH and provides `forward secrecy`_ when used. You must generate a new private key using :func:`~DHParameters.generate_private_key` for each :meth:`~DHPrivateKey.exchange` when performing an DHE key exchange. An example of the ephemeral form: .. code-block:: pycon >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import dh >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF >>> # Generate some parameters. These can be reused. >>> parameters = dh.generate_parameters(generator=2, key_size=2048) >>> # Generate a private key for use in the exchange. >>> private_key = parameters.generate_private_key() >>> # In a real handshake the peer_public_key will be received from the >>> # other party. For this example we'll generate another private key and >>> # get a public key from that. Note that in a DH handshake both peers >>> # must agree on a common set of parameters. >>> peer_public_key = parameters.generate_private_key().public_key() >>> shared_key = private_key.exchange(peer_public_key) >>> # Perform key derivation. >>> derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key) >>> # For the next handshake we MUST generate another private key, but >>> # we can reuse the parameters. >>> private_key_2 = parameters.generate_private_key() >>> peer_public_key_2 = parameters.generate_private_key().public_key() >>> shared_key_2 = private_key_2.exchange(peer_public_key_2) >>> derived_key_2 = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key_2) To assemble a :class:`~DHParameters` and a :class:`~DHPublicKey` from primitive integers, you must first create the :class:`~DHParameterNumbers` and :class:`~DHPublicNumbers` objects. For example, if **p**, **g**, and **y** are :class:`int` objects received from a peer:: pn = dh.DHParameterNumbers(p, g) parameters = pn.parameters() peer_public_numbers = dh.DHPublicNumbers(y, pn) peer_public_key = peer_public_numbers.public_key() Group parameters ~~~~~~~~~~~~~~~~ .. function:: generate_parameters(generator, key_size) .. versionadded:: 1.7 Generate a new DH parameter group. :param generator: The :class:`int` to use as a generator. Must be 2 or 5. :param key_size: The bit length of the prime modulus to generate. :returns: DH parameters as a new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. :raises ValueError: If ``key_size`` is not at least 512. .. class:: DHParameters .. versionadded:: 1.7 .. method:: generate_private_key() Generate a DH private key. This method can be used to generate many new private keys from a single set of parameters. :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. .. method:: parameter_numbers() Return the numbers that make up this set of parameters. :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers`. .. method:: parameter_bytes(encoding, format) .. versionadded:: 2.0 Allows serialization of the parameters 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.ParameterFormat.PKCS3`) 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.ParameterFormat` enum. At the moment only ``PKCS3`` is supported. :return bytes: Serialized parameters. Key interfaces ~~~~~~~~~~~~~~ .. class:: DHPrivateKey .. versionadded:: 1.7 .. attribute:: key_size The bit length of the prime modulus. .. method:: public_key() Return the public key associated with this private key. :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`. .. method:: parameters() Return the parameters associated with this private key. :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. .. method:: exchange(peer_public_key) .. versionadded:: 1.7 :param DHPublicKey peer_public_key: The public key for the peer. :return bytes: The agreed key. The bytes are ordered in 'big' endian. .. method:: private_numbers() Return the numbers that make up this private key. :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateNumbers`. .. method:: private_bytes(encoding, format, encryption_algorithm) .. versionadded:: 1.8 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.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:: DHPublicKey .. versionadded:: 1.7 .. attribute:: key_size The bit length of the prime modulus. .. method:: parameters() Return the parameters associated with this private key. :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. .. method:: public_numbers() Return the numbers that make up this public key. :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers`. .. method:: public_bytes(encoding, format) .. versionadded:: 1.8 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. Numbers ~~~~~~~ .. class:: DHParameterNumbers(p, g, q=None) .. 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. Must be 2 or greater. .. attribute:: q .. versionadded:: 1.8 :type: int p subgroup order value. .. method:: parameters() .. versionadded:: 1.7 :returns: A new instance of :class:`DHParameters`. .. 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. .. method:: private_key() .. versionadded:: 1.7 :returns: A new instance of :class:`DHPrivateKey`. .. class:: DHPublicNumbers(y, parameter_numbers) .. 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. .. method:: public_key() .. versionadded:: 1.7 :returns: A new instance of :class:`DHPublicKey`. .. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange .. _`forward secrecy`: https://en.wikipedia.org/wiki/Forward_secrecy cryptography-43.0.0/docs/hazmat/primitives/asymmetric/dsa.rst010064400017510000177000000303261464676315000226220ustar 00000000000000.. hazmat:: DSA === .. module:: cryptography.hazmat.primitives.asymmetric.dsa .. note:: DSA is a **legacy algorithm** and should generally be avoided in favor of choices like :doc:`EdDSA using curve25519` or :doc:`ECDSA`. `DSA`_ is a `public-key`_ algorithm for signing messages. Generation ~~~~~~~~~~ .. function:: generate_private_key(key_size) .. versionadded:: 0.5 .. versionchanged:: 3.0 Added support for 4096-bit keys for some legacy applications that continue to use DSA despite the wider cryptographic community's `ongoing protestations`_. Generate a DSA private key from the given key size. This function will generate a new set of parameters and key in one step. :param int key_size: The length of the modulus in :term:`bits`. It should be either 1024, 2048, 3072, or 4096. For keys generated in 2015 this should be `at least 2048`_ (See page 41). :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. .. function:: generate_parameters(key_size) .. versionadded:: 0.5 .. versionchanged:: 3.0 Added support for 4096-bit keys for some legacy applications that continue to use DSA despite the wider cryptographic community's `ongoing protestations`_. Generate DSA parameters. :param int key_size: The length of :attr:`~DSAParameterNumbers.p`. It should be either 1024, 2048, 3072, or 4096. For keys generated in 2015 this should be `at least 2048`_ (See page 41). :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. Signing ~~~~~~~ Using a :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` instance. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import dsa >>> private_key = dsa.generate_private_key( ... key_size=1024, ... ) >>> data = b"this is some data I'd like to sign" >>> signature = private_key.sign( ... data, ... hashes.SHA256() ... ) The ``signature`` is a ``bytes`` object, whose contents is DER encoded as described in :rfc:`3279`. This can be decoded using :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature`. If your data is too large to be passed in a single call, you can hash it separately and pass that value using :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import utils >>> chosen_hash = hashes.SHA256() >>> hasher = hashes.Hash(chosen_hash) >>> hasher.update(b"data & ") >>> hasher.update(b"more data") >>> digest = hasher.finalize() >>> sig = private_key.sign( ... digest, ... utils.Prehashed(chosen_hash) ... ) Verification ~~~~~~~~~~~~ Verification is performed using a :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` instance. You can get a public key object with :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key`, :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key`, :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers.public_key` , or :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.public_key`. .. doctest:: >>> public_key = private_key.public_key() >>> public_key.verify( ... signature, ... data, ... hashes.SHA256() ... ) ``verify()`` takes the signature in the same format as is returned by ``sign()``. ``verify()`` will raise an :class:`~cryptography.exceptions.InvalidSignature` exception if the signature isn't valid. If your data is too large to be passed in a single call, you can hash it separately and pass that value using :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`. .. doctest:: >>> chosen_hash = hashes.SHA256() >>> hasher = hashes.Hash(chosen_hash) >>> hasher.update(b"data & ") >>> hasher.update(b"more data") >>> digest = hasher.finalize() >>> public_key.verify( ... sig, ... digest, ... utils.Prehashed(chosen_hash) ... ) Numbers ~~~~~~~ .. class:: DSAParameterNumbers(p, q, g) .. versionadded:: 0.5 The collection of integers that make up a set of DSA parameters. .. attribute:: p :type: int The public modulus. .. attribute:: q :type: int The sub-group order. .. attribute:: g :type: int The generator. .. method:: parameters() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. .. class:: DSAPublicNumbers(y, parameter_numbers) .. versionadded:: 0.5 The collection of integers that make up a DSA public key. .. attribute:: y :type: int The public value ``y``. .. attribute:: parameter_numbers :type: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` The :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` associated with the public key. .. method:: public_key() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. .. class:: DSAPrivateNumbers(x, public_numbers) .. versionadded:: 0.5 The collection of integers that make up a DSA private key. .. warning:: Revealing the value of ``x`` will compromise the security of any cryptographic operations performed. .. attribute:: x :type: int The private value ``x``. .. attribute:: public_numbers :type: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` The :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` associated with the private key. .. method:: private_key() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. Key interfaces ~~~~~~~~~~~~~~ .. class:: DSAParameters .. versionadded:: 0.3 `DSA`_ parameters. .. method:: generate_private_key() .. versionadded:: 0.5 Generate a DSA private key. This method can be used to generate many new private keys from a single set of parameters. :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. .. method:: parameter_numbers() Create a :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` object. :returns: A :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` instance. .. class:: DSAPrivateKey .. versionadded:: 0.3 A `DSA`_ private key. .. method:: public_key() :return: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` An DSA public key object corresponding to the values of the private key. .. method:: parameters() :return: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters` The DSAParameters object associated with this private key. .. attribute:: key_size :type: int The bit length of :attr:`~DSAParameterNumbers.q`. .. method:: sign(data, algorithm) .. versionadded:: 1.5 .. versionchanged:: 1.6 :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` can now be used as an ``algorithm``. Sign one block of data which can be verified later by others using the public key. :param data: The message string to sign. :type data: :term:`bytes-like` :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` if the ``data`` you want to sign has already been hashed. :return bytes: Signature. .. 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 A `DSA`_ public key. .. attribute:: key_size :type: int The bit length of :attr:`~DSAParameterNumbers.q`. .. method:: parameters() :return: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters` The DSAParameters object associated with this public key. .. method:: public_numbers() Create a :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` object. :returns: A :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` 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. .. method:: verify(signature, data, algorithm) .. versionadded:: 1.5 .. versionchanged:: 1.6 :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` can now be used as an ``algorithm``. Verify one block of data was signed by the private key associated with this public key. :param signature: The signature to verify. :type signature: :term:`bytes-like` :param data: The message string that was signed. :type data: :term:`bytes-like` :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` if the ``data`` you want to sign has already been hashed. :returns: None :raises cryptography.exceptions.InvalidSignature: If the signature does not validate. .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography .. _`FIPS 186-4`: https://csrc.nist.gov/publications/detail/fips/186/4/final .. _`at least 2048`: https://www.cosic.esat.kuleuven.be/ecrypt/ecrypt2/documents/D.SPA.20.pdf .. _`ongoing protestations`: https://words.filippo.io/dispatches/dsa/ cryptography-43.0.0/docs/hazmat/primitives/asymmetric/ec.rst010064400017510000177000000667651464676315000224620ustar 00000000000000.. hazmat:: Elliptic curve cryptography =========================== .. module:: cryptography.hazmat.primitives.asymmetric.ec .. function:: generate_private_key(curve) .. versionadded:: 0.5 Generate a new private key on ``curve``. :param curve: An instance of :class:`EllipticCurve`. :returns: A new instance of :class:`EllipticCurvePrivateKey`. .. function:: derive_private_key(private_value, curve) .. versionadded:: 1.6 Derive a private key from ``private_value`` on ``curve``. :param int private_value: The secret scalar value. :param curve: An instance of :class:`EllipticCurve`. :returns: A new instance of :class:`EllipticCurvePrivateKey`. Elliptic Curve Signature Algorithms ----------------------------------- .. class:: ECDSA(algorithm) .. versionadded:: 0.5 The ECDSA signature algorithm first standardized in NIST publication `FIPS 186-3`_, and later in `FIPS 186-4`_. Note that while elliptic curve keys can be used for both signing and key exchange, this is `bad cryptographic practice`_. Instead, users should generate separate signing and ECDH keys. :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param bool deterministic_signing: A boolean flag defaulting to ``False`` that specifies whether the signing procedure should be deterministic or not, as defined in :rfc:`6979`. This only impacts the signing process, verification is not affected (the verification process is the same for both deterministic and non-deterministic signed messages). .. versionadded:: 43.0.0 :raises cryptography.exceptions.UnsupportedAlgorithm: If ``deterministic_signing`` is set to ``True`` and the version of OpenSSL does not support ECDSA with deterministic signing. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import ec >>> private_key = ec.generate_private_key( ... ec.SECP384R1() ... ) >>> data = b"this is some data I'd like to sign" >>> signature = private_key.sign( ... data, ... ec.ECDSA(hashes.SHA256()) ... ) The ``signature`` is a ``bytes`` object, whose contents are DER encoded as described in :rfc:`3279`. This can be decoded using :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature`. If your data is too large to be passed in a single call, you can hash it separately and pass that value using :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import utils >>> chosen_hash = hashes.SHA256() >>> hasher = hashes.Hash(chosen_hash) >>> hasher.update(b"data & ") >>> hasher.update(b"more data") >>> digest = hasher.finalize() >>> sig = private_key.sign( ... digest, ... ec.ECDSA(utils.Prehashed(chosen_hash)) ... ) Verification requires the public key, the DER-encoded signature itself, the signed data, and knowledge of the hashing algorithm that was used when producing the signature: >>> public_key = private_key.public_key() >>> public_key.verify(signature, data, ec.ECDSA(hashes.SHA256())) As above, the ``signature`` is a ``bytes`` object whose contents are DER encoded as described in :rfc:`3279`. It can be created from a raw ``(r,s)`` pair by using :func:`~cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature`. If the signature is not valid, an :class:`~cryptography.exceptions.InvalidSignature` exception will be raised. If your data is too large to be passed in a single call, you can hash it separately and pass that value using :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`. .. doctest:: >>> chosen_hash = hashes.SHA256() >>> hasher = hashes.Hash(chosen_hash) >>> hasher.update(b"data & ") >>> hasher.update(b"more data") >>> digest = hasher.finalize() >>> public_key.verify( ... sig, ... digest, ... ec.ECDSA(utils.Prehashed(chosen_hash)) ... ) .. note:: Although in this case the public key was derived from the private one, in a typical setting you will not possess the private key. The `Key loading`_ section explains how to load the public key from other sources. .. class:: EllipticCurvePrivateNumbers(private_value, public_numbers) .. versionadded:: 0.5 The collection of integers that make up an EC private key. .. attribute:: public_numbers :type: :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers` The :class:`EllipticCurvePublicNumbers` which makes up the EC public key associated with this EC private key. .. attribute:: private_value :type: int The private value. .. method:: private_key() Convert a collection of numbers into a private key suitable for doing actual cryptographic operations. :returns: A new instance of :class:`EllipticCurvePrivateKey`. .. class:: EllipticCurvePublicNumbers(x, y, curve) .. warning:: The point represented by this object is not validated in any way until :meth:`EllipticCurvePublicNumbers.public_key` is called and may not represent a valid point on the curve. You should not attempt to perform any computations using the values from this class until you have either validated it yourself or called ``public_key()`` successfully. .. versionadded:: 0.5 The collection of integers that make up an EC public key. .. attribute:: curve :type: :class:`EllipticCurve` The elliptic curve for this key. .. attribute:: x :type: int The affine x component of the public point used for verifying. .. attribute:: y :type: int The affine y component of the public point used for verifying. .. method:: public_key() Convert a collection of numbers into a public key suitable for doing actual cryptographic operations. :raises ValueError: Raised if the point is invalid for the curve. :returns: A new instance of :class:`EllipticCurvePublicKey`. Elliptic Curve Key Exchange algorithm ------------------------------------- .. class:: ECDH() .. versionadded:: 1.1 The Elliptic Curve Diffie-Hellman Key Exchange algorithm standardized in NIST publication `800-56A`_. For most applications the ``shared_key`` should be passed to a key derivation function. This allows mixing of additional information into the key, derivation of multiple keys, and destroys any structure that may be present. Note that while elliptic curve keys can be used for both signing and key exchange, this is `bad cryptographic practice`_. Instead, users should generate separate signing and ECDH keys. .. warning:: This example does not give `forward secrecy`_ and is only provided as a demonstration of the basic Diffie-Hellman construction. For real world applications always use the ephemeral form described after this example. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import ec >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF >>> # Generate a private key for use in the exchange. >>> server_private_key = ec.generate_private_key( ... ec.SECP384R1() ... ) >>> # In a real handshake the peer is a remote client. For this >>> # example we'll generate another local private key though. >>> peer_private_key = ec.generate_private_key( ... ec.SECP384R1() ... ) >>> shared_key = server_private_key.exchange( ... ec.ECDH(), peer_private_key.public_key()) >>> # Perform key derivation. >>> derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key) >>> # And now we can demonstrate that the handshake performed in the >>> # opposite direction gives the same final value >>> same_shared_key = peer_private_key.exchange( ... ec.ECDH(), server_private_key.public_key()) >>> # Perform key derivation. >>> same_derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(same_shared_key) >>> derived_key == same_derived_key True ECDHE (or EECDH), the ephemeral form of this exchange, is **strongly preferred** over simple ECDH and provides `forward secrecy`_ when used. You must generate a new private key using :func:`generate_private_key` for each :meth:`~EllipticCurvePrivateKey.exchange` when performing an ECDHE key exchange. An example of the ephemeral form: .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import ec >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF >>> # Generate a private key for use in the exchange. >>> private_key = ec.generate_private_key( ... ec.SECP384R1() ... ) >>> # In a real handshake the peer_public_key will be received from the >>> # other party. For this example we'll generate another private key >>> # and get a public key from that. >>> peer_public_key = ec.generate_private_key( ... ec.SECP384R1() ... ).public_key() >>> shared_key = private_key.exchange(ec.ECDH(), peer_public_key) >>> # Perform key derivation. >>> derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key) >>> # For the next handshake we MUST generate another private key. >>> private_key_2 = ec.generate_private_key( ... ec.SECP384R1() ... ) >>> peer_public_key_2 = ec.generate_private_key( ... ec.SECP384R1() ... ).public_key() >>> shared_key_2 = private_key_2.exchange(ec.ECDH(), peer_public_key_2) >>> derived_key_2 = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key_2) Elliptic Curves --------------- Elliptic curves provide equivalent security at much smaller key sizes than other asymmetric cryptography systems such as RSA or DSA. For many operations elliptic curves are also significantly faster; `elliptic curve diffie-hellman is faster than diffie-hellman`_. .. note:: Curves with a size of `less than 224 bits`_ should not be used. You should strongly consider using curves of at least 224 :term:`bits`. Generally the NIST prime field ("P") curves are significantly faster than the other types suggested by NIST at both signing and verifying with ECDSA. Prime fields also `minimize the number of security concerns for elliptic-curve cryptography`_. However, there is `some concern`_ that both the prime field and binary field ("B") NIST curves may have been weakened during their generation. Currently `cryptography` only supports NIST curves, none of which are considered "safe" by the `SafeCurves`_ project run by Daniel J. Bernstein and Tanja Lange. All named curves are instances of :class:`EllipticCurve`. .. class:: SECP256R1 .. versionadded:: 0.5 SECG curve ``secp256r1``. Also called NIST P-256. .. class:: SECP384R1 .. versionadded:: 0.5 SECG curve ``secp384r1``. Also called NIST P-384. .. class:: SECP521R1 .. versionadded:: 0.5 SECG curve ``secp521r1``. Also called NIST P-521. .. class:: SECP224R1 .. versionadded:: 0.5 SECG curve ``secp224r1``. Also called NIST P-224. .. class:: SECP192R1 .. versionadded:: 0.5 SECG curve ``secp192r1``. Also called NIST P-192. .. class:: SECP256K1 .. versionadded:: 0.9 SECG curve ``secp256k1``. .. class:: BrainpoolP256R1 .. versionadded:: 2.2 Brainpool curve specified in :rfc:`5639`. These curves are discouraged for new systems. .. class:: BrainpoolP384R1 .. versionadded:: 2.2 Brainpool curve specified in :rfc:`5639`. These curves are discouraged for new systems. .. class:: BrainpoolP512R1 .. versionadded:: 2.2 Brainpool curve specified in :rfc:`5639`. These curves are discouraged for new systems. .. class:: SECT571K1 .. versionadded:: 0.5 SECG curve ``sect571k1``. Also called NIST K-571. These binary curves are discouraged for new systems. .. class:: SECT409K1 .. versionadded:: 0.5 SECG curve ``sect409k1``. Also called NIST K-409. These binary curves are discouraged for new systems. .. class:: SECT283K1 .. versionadded:: 0.5 SECG curve ``sect283k1``. Also called NIST K-283. These binary curves are discouraged for new systems. .. class:: SECT233K1 .. versionadded:: 0.5 SECG curve ``sect233k1``. Also called NIST K-233. These binary curves are discouraged for new systems. .. class:: SECT163K1 .. versionadded:: 0.5 SECG curve ``sect163k1``. Also called NIST K-163. These binary curves are discouraged for new systems. .. class:: SECT571R1 .. versionadded:: 0.5 SECG curve ``sect571r1``. Also called NIST B-571. These binary curves are discouraged for new systems. .. class:: SECT409R1 .. versionadded:: 0.5 SECG curve ``sect409r1``. Also called NIST B-409. These binary curves are discouraged for new systems. .. class:: SECT283R1 .. versionadded:: 0.5 SECG curve ``sect283r1``. Also called NIST B-283. These binary curves are discouraged for new systems. .. class:: SECT233R1 .. versionadded:: 0.5 SECG curve ``sect233r1``. Also called NIST B-233. These binary curves are discouraged for new systems. .. class:: SECT163R2 .. versionadded:: 0.5 SECG curve ``sect163r2``. Also called NIST B-163. These binary curves are discouraged for new systems. Key Interfaces ~~~~~~~~~~~~~~ .. class:: EllipticCurve .. versionadded:: 0.5 A named elliptic curve. .. attribute:: name :type: str The name of the curve. Usually the name used for the ASN.1 OID such as ``secp256k1``. .. attribute:: key_size :type: int Size (in :term:`bits`) of a secret scalar for the curve (as generated by :func:`generate_private_key`). .. class:: EllipticCurveSignatureAlgorithm .. versionadded:: 0.5 .. versionchanged:: 1.6 :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` can now be used as an ``algorithm``. A signature algorithm for use with elliptic curve keys. .. attribute:: algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` The digest algorithm to be used with the signature scheme. .. class:: EllipticCurvePrivateKey .. versionadded:: 0.5 An elliptic curve private key for use with an algorithm such as `ECDSA`_. .. method:: exchange(algorithm, peer_public_key) .. versionadded:: 1.1 Performs a key exchange operation using the provided algorithm with the peer's public key. For most applications the ``shared_key`` should be passed to a key derivation function. This allows mixing of additional information into the key, derivation of multiple keys, and destroys any structure that may be present. :param algorithm: The key exchange algorithm, currently only :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDH` is supported. :param EllipticCurvePublicKey peer_public_key: The public key for the peer. :returns bytes: A shared key. .. method:: public_key() :return: :class:`EllipticCurvePublicKey` The EllipticCurvePublicKey object for this private key. .. method:: sign(data, signature_algorithm) .. versionadded:: 1.5 Sign one block of data which can be verified later by others using the public key. :param data: The message string to sign. :type data: :term:`bytes-like` :param signature_algorithm: An instance of :class:`EllipticCurveSignatureAlgorithm`, such as :class:`ECDSA`. :return bytes: The signature as a ``bytes`` object, whose contents are DER encoded as described in :rfc:`3279`. This can be decoded using :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature`, which returns the decoded tuple ``(r, s)``. .. attribute:: curve :type: :class:`EllipticCurve` The EllipticCurve that this key is on. .. attribute:: key_size .. versionadded:: 1.9 :type: int Size (in :term:`bits`) of a secret scalar for the curve (as generated by :func:`generate_private_key`). .. 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`, :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.OpenSSH` 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 An elliptic curve public key. .. attribute:: curve :type: :class:`EllipticCurve` The elliptic curve for this key. .. method:: public_numbers() Create a :class:`EllipticCurvePublicNumbers` object. :returns: An :class:`EllipticCurvePublicNumbers` instance. .. method:: public_bytes(encoding, format) Allows serialization of the key data to bytes. When encoding the public key the encodings ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`) are chosen to define the exact serialization. When encoding the point the encoding :attr:`~cryptography.hazmat.primitives.serialization.Encoding.X962` should be used with the formats ( :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.UncompressedPoint` or :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.CompressedPoint` ). :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 data. .. method:: verify(signature, data, signature_algorithm) .. versionadded:: 1.5 Verify one block of data was signed by the private key associated with this public key. :param signature: The DER-encoded signature to verify. A raw signature may be DER-encoded by splitting it into the ``r`` and ``s`` components and passing them into :func:`~cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature`. :type signature: :term:`bytes-like` :param data: The message string that was signed. :type data: :term:`bytes-like` :param signature_algorithm: An instance of :class:`EllipticCurveSignatureAlgorithm`. :returns: None :raises cryptography.exceptions.InvalidSignature: If the signature does not validate. .. attribute:: key_size .. versionadded:: 1.9 :type: int Size (in :term:`bits`) of a secret scalar for the curve (as generated by :func:`generate_private_key`). .. classmethod:: from_encoded_point(curve, data) .. versionadded:: 2.5 Decodes a byte string as described in `SEC 1 v2.0`_ section 2.3.3 and returns an :class:`EllipticCurvePublicKey`. This class method supports compressed points. :param curve: An :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve` instance. :param bytes data: The serialized point byte string. :returns: An :class:`EllipticCurvePublicKey` instance. :raises ValueError: Raised when an invalid point is supplied. :raises TypeError: Raised when curve is not an :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. Serialization ~~~~~~~~~~~~~ This sample demonstrates how to generate a private key and serialize it. .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import ec >>> private_key = ec.generate_private_key(ec.SECP384R1()) >>> serialized_private = private_key.private_bytes( ... encoding=serialization.Encoding.PEM, ... format=serialization.PrivateFormat.PKCS8, ... encryption_algorithm=serialization.BestAvailableEncryption(b'testpassword') ... ) >>> serialized_private.splitlines()[0] b'-----BEGIN ENCRYPTED PRIVATE KEY-----' You can also serialize the key without a password, by relying on :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. The public key is serialized as follows: .. doctest:: >>> public_key = private_key.public_key() >>> serialized_public = public_key.public_bytes( ... encoding=serialization.Encoding.PEM, ... format=serialization.PublicFormat.SubjectPublicKeyInfo ... ) >>> serialized_public.splitlines()[0] b'-----BEGIN PUBLIC KEY-----' This is the part that you would normally share with the rest of the world. Key loading ~~~~~~~~~~~ This extends the sample in the previous section, assuming that the variables ``serialized_private`` and ``serialized_public`` contain the respective keys in PEM format. .. doctest:: >>> loaded_public_key = serialization.load_pem_public_key( ... serialized_public, ... ) >>> loaded_private_key = serialization.load_pem_private_key( ... serialized_private, ... # or password=None, if in plain text ... password=b'testpassword', ... ) Elliptic Curve Object Identifiers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: EllipticCurveOID .. versionadded:: 2.4 .. attribute:: SECP192R1 Corresponds to the dotted string ``"1.2.840.10045.3.1.1"``. .. attribute:: SECP224R1 Corresponds to the dotted string ``"1.3.132.0.33"``. .. attribute:: SECP256K1 Corresponds to the dotted string ``"1.3.132.0.10"``. .. attribute:: SECP256R1 Corresponds to the dotted string ``"1.2.840.10045.3.1.7"``. .. attribute:: SECP384R1 Corresponds to the dotted string ``"1.3.132.0.34"``. .. attribute:: SECP521R1 Corresponds to the dotted string ``"1.3.132.0.35"``. .. attribute:: BRAINPOOLP256R1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.36.3.3.2.8.1.1.7"``. .. attribute:: BRAINPOOLP384R1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.36.3.3.2.8.1.1.11"``. .. attribute:: BRAINPOOLP512R1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.36.3.3.2.8.1.1.13"``. .. attribute:: SECT163K1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.1"``. .. attribute:: SECT163R2 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.15"``. .. attribute:: SECT233K1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.26"``. .. attribute:: SECT233R1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.27"``. .. attribute:: SECT283K1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.16"``. .. attribute:: SECT283R1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.17"``. .. attribute:: SECT409K1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.36"``. .. attribute:: SECT409R1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.37"``. .. attribute:: SECT571K1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.38"``. .. attribute:: SECT571R1 .. versionadded:: 2.5 Corresponds to the dotted string ``"1.3.132.0.39"``. .. function:: get_curve_for_oid(oid) .. versionadded:: 2.6 A function that takes an :class:`~cryptography.x509.ObjectIdentifier` and returns the associated elliptic curve class. :param oid: An instance of :class:`~cryptography.x509.ObjectIdentifier`. :returns: The matching elliptic curve class. The returned class conforms to the :class:`EllipticCurve` interface. :raises LookupError: Raised if no elliptic curve is found that matches the provided object identifier. .. _`FIPS 186-3`: https://csrc.nist.gov/files/pubs/fips/186-3/final/docs/fips_186-3.pdf .. _`FIPS 186-4`: https://csrc.nist.gov/pubs/fips/186-4/final .. _`800-56A`: https://csrc.nist.gov/pubs/sp/800/56/a/r3/final .. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters .. _`less than 224 bits`: https://www.cosic.esat.kuleuven.be/ecrypt/ecrypt2/documents/D.SPA.20.pdf .. _`elliptic curve diffie-hellman is faster than diffie-hellman`: https://digitalcommons.unl.edu/cgi/viewcontent.cgi?article=1100&context=cseconfwork .. _`minimize the number of security concerns for elliptic-curve cryptography`: https://cr.yp.to/ecdh/curve25519-20060209.pdf .. _`SafeCurves`: https://safecurves.cr.yp.to/ .. _`ECDSA`: https://en.wikipedia.org/wiki/ECDSA .. _`forward secrecy`: https://en.wikipedia.org/wiki/Forward_secrecy .. _`SEC 1 v2.0`: https://www.secg.org/sec1-v2.pdf .. _`bad cryptographic practice`: https://crypto.stackexchange.com/a/3313 cryptography-43.0.0/docs/hazmat/primitives/asymmetric/ed25519.rst010064400017510000177000000174311464676315000230530ustar 00000000000000.. hazmat:: Ed25519 signing =============== .. currentmodule:: cryptography.hazmat.primitives.asymmetric.ed25519 Ed25519 is an elliptic curve signing algorithm using `EdDSA`_ and `Curve25519`_. If you do not have legacy interoperability concerns then you should strongly consider using this signature algorithm. Signing & Verification ~~~~~~~~~~~~~~~~~~~~~~ .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey >>> private_key = Ed25519PrivateKey.generate() >>> signature = private_key.sign(b"my authenticated message") >>> public_key = private_key.public_key() >>> # Raises InvalidSignature if verification fails >>> public_key.verify(signature, b"my authenticated message") Key interfaces ~~~~~~~~~~~~~~ .. class:: Ed25519PrivateKey .. versionadded:: 2.6 .. classmethod:: generate() Generate an Ed25519 private key. :returns: :class:`Ed25519PrivateKey` .. classmethod:: from_private_bytes(data) :param data: 32 byte private key. :type data: :term:`bytes-like` :returns: :class:`Ed25519PrivateKey` :raises ValueError: This is raised if the private key is not 32 bytes long. :raises cryptography.exceptions.UnsupportedAlgorithm: If Ed25519 is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import ed25519 >>> private_key = ed25519.Ed25519PrivateKey.generate() >>> private_bytes = private_key.private_bytes( ... encoding=serialization.Encoding.Raw, ... format=serialization.PrivateFormat.Raw, ... encryption_algorithm=serialization.NoEncryption() ... ) >>> loaded_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_bytes) .. method:: public_key() :returns: :class:`Ed25519PublicKey` .. method:: sign(data) :param data: The data to sign. :type data: :term:`bytes-like` :returns bytes: The 64 byte signature. .. method:: private_bytes(encoding, format, encryption_algorithm) Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`, :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.OpenSSH` or :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` , otherwise it must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8` or :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.OpenSSH`. :param encryption_algorithm: An instance of an object conforming to the :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` interface. :return bytes: Serialized key. .. method:: private_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`private_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding, :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` format, and :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. :return bytes: Raw key. .. class:: Ed25519PublicKey .. versionadded:: 2.6 .. classmethod:: from_public_bytes(data) :param bytes data: 32 byte public key. :returns: :class:`Ed25519PublicKey` :raises ValueError: This is raised if the public key is not 32 bytes long. :raises cryptography.exceptions.UnsupportedAlgorithm: If Ed25519 is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import ed25519 >>> private_key = ed25519.Ed25519PrivateKey.generate() >>> public_key = private_key.public_key() >>> public_bytes = public_key.public_bytes( ... encoding=serialization.Encoding.Raw, ... format=serialization.PublicFormat.Raw ... ) >>> loaded_public_key = ed25519.Ed25519PublicKey.from_public_bytes(public_bytes) .. method:: public_bytes(encoding, format) Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.OpenSSH`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`, :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.OpenSSH` , or :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw`. If ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.OpenSSH` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.OpenSSH`. In all other cases ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`. :returns bytes: The public key bytes. .. method:: public_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`public_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding and :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` format. :return bytes: Raw key. .. method:: verify(signature, data) :param signature: The signature to verify. :type signature: :term:`bytes-like` :param data: The data to verify. :type data: :term:`bytes-like` :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the signature cannot be verified. .. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA .. _`Curve25519`: https://en.wikipedia.org/wiki/Curve25519 cryptography-43.0.0/docs/hazmat/primitives/asymmetric/ed448.rst010064400017510000177000000125501464676315000227020ustar 00000000000000.. hazmat:: Ed448 signing ============= .. currentmodule:: cryptography.hazmat.primitives.asymmetric.ed448 Ed448 is an elliptic curve signing algorithm using `EdDSA`_. Signing & Verification ~~~~~~~~~~~~~~~~~~~~~~ .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey >>> private_key = Ed448PrivateKey.generate() >>> signature = private_key.sign(b"my authenticated message") >>> public_key = private_key.public_key() >>> # Raises InvalidSignature if verification fails >>> public_key.verify(signature, b"my authenticated message") Key interfaces ~~~~~~~~~~~~~~ .. class:: Ed448PrivateKey .. versionadded:: 2.6 .. classmethod:: generate() Generate an Ed448 private key. :returns: :class:`Ed448PrivateKey` .. classmethod:: from_private_bytes(data) :param data: 57 byte private key. :type data: :term:`bytes-like` :returns: :class:`Ed448PrivateKey` .. method:: public_key() :returns: :class:`Ed448PublicKey` .. method:: sign(data) :param data: The data to sign. :type data: :term:`bytes-like` :returns bytes: The 114 byte signature. .. method:: private_bytes(encoding, format, encryption_algorithm) Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8` or :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` , otherwise it must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`. :param encryption_algorithm: An instance of an object conforming to the :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` interface. :return bytes: Serialized key. .. method:: private_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`private_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding, :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` format, and :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. :return bytes: Raw key. .. class:: Ed448PublicKey .. versionadded:: 2.6 .. classmethod:: from_public_bytes(data) :param bytes data: 57 byte public key. :returns: :class:`Ed448PublicKey` .. method:: public_bytes(encoding, format) Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo` or :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` , otherwise it must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`. :returns bytes: The public key bytes. .. method:: public_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`public_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding and :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` format. :return bytes: Raw key. .. method:: verify(signature, data) :param signature: The signature to verify. :type signature: :term:`bytes-like` :param data: The data to verify. :type data: :term:`bytes-like` :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the signature cannot be verified. .. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA cryptography-43.0.0/docs/hazmat/primitives/asymmetric/index.rst010064400017510000177000000117711464676315000231650ustar 00000000000000.. hazmat:: Asymmetric algorithms ===================== Asymmetric cryptography is a branch of cryptography where a secret key can be divided into two parts, a :term:`public key` and a :term:`private key`. The public key can be given to anyone, trusted or not, while the private key must be kept secret (just like the key in symmetric cryptography). Asymmetric cryptography has two primary use cases: authentication and confidentiality. Using asymmetric cryptography, messages can be signed with a private key, and then anyone with the public key is able to verify that the message was created by someone possessing the corresponding private key. This can be combined with a `proof of identity`_ system to know what entity (person or group) actually owns that private key, providing authentication. Encryption with asymmetric cryptography works in a slightly different way from symmetric encryption. Someone with the public key is able to encrypt a message, providing confidentiality, and then only the person in possession of the private key is able to decrypt it. .. toctree:: :maxdepth: 1 ed25519 x25519 ed448 x448 ec rsa dh dsa serialization utils .. _`proof of identity`: https://en.wikipedia.org/wiki/Public-key_infrastructure Common types ~~~~~~~~~~~~ Asymmetric key types do not inherit from a common base class. The following union type aliases can be used instead to reference a multitude of key types. .. currentmodule:: cryptography.hazmat.primitives.asymmetric.types .. data:: PublicKeyTypes .. versionadded:: 40.0.0 Type alias: A union of all public key types supported: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. .. data:: PrivateKeyTypes .. versionadded:: 40.0.0 Type alias: A union of all private key types supported: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`. .. data:: CertificatePublicKeyTypes .. versionadded:: 40.0.0 Type alias: A union of all public key types supported for X.509 certificates: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. .. data:: CertificateIssuerPublicKeyTypes .. versionadded:: 40.0.0 Type alias: A union of all public key types that can sign other X.509 certificates as an issuer. x448/x25519 can be a public key, but cannot be used in signing, so they are not allowed in these contexts. Allowed: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. .. data:: CertificateIssuerPrivateKeyTypes .. versionadded:: 40.0.0 Type alias: A union of all private key types that can sign other X.509 certificates as an issuer. x448/x25519 can be a public key, but cannot be used in signing, so they are not allowed in these contexts. Allowed: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`. cryptography-43.0.0/docs/hazmat/primitives/asymmetric/rsa.rst010064400017510000177000000666141464676315000226510ustar 00000000000000.. hazmat:: RSA === .. module:: cryptography.hazmat.primitives.asymmetric.rsa `RSA`_ is a `public-key`_ algorithm for encrypting and signing messages. Generation ~~~~~~~~~~ Unlike symmetric cryptography, where the key is typically just a random series of bytes, RSA keys have a complex internal structure with `specific mathematical properties`_. .. function:: generate_private_key(public_exponent, key_size) .. versionadded:: 0.5 .. versionchanged:: 3.0 Tightened restrictions on ``public_exponent``. Generates a new RSA private key. ``key_size`` describes how many :term:`bits` long the key should be. Larger keys provide more security; currently ``1024`` and below are considered breakable while ``2048`` or ``4096`` are reasonable default key sizes for new keys. The ``public_exponent`` indicates what one mathematical property of the key generation will be. Unless you have a specific reason to do otherwise, you should always `use 65537`_. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> private_key = rsa.generate_private_key( ... public_exponent=65537, ... key_size=2048, ... ) :param int public_exponent: The public exponent of the new key. Either 65537 or 3 (for legacy purposes). Almost everyone should `use 65537`_. :param int key_size: The length of the modulus in :term:`bits`. For keys generated in 2015 it is strongly recommended to be `at least 2048`_ (See page 41). It must not be less than 512. :return: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. Key loading ~~~~~~~~~~~ If you already have an on-disk key in the PEM format (which are recognizable by the distinctive ``-----BEGIN {format}-----`` and ``-----END {format}-----`` markers), you can load it: .. code-block:: pycon >>> from cryptography.hazmat.primitives import serialization >>> with open("path/to/key.pem", "rb") as key_file: ... private_key = serialization.load_pem_private_key( ... key_file.read(), ... password=None, ... ) Serialized keys may optionally be encrypted on disk using a password. In this example we loaded an unencrypted key, and therefore we did not provide a password. If the key is encrypted we can pass a ``bytes`` object as the ``password`` argument. There is also support for :func:`loading public keys in the SSH format `. Key serialization ~~~~~~~~~~~~~~~~~ If you have a private key that you've loaded you can use :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.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] b'-----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] b'-----BEGIN RSA PRIVATE KEY-----' For public keys you can use :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.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] b'-----BEGIN PUBLIC KEY-----' Signing ~~~~~~~ A private key can be used to sign a message. This allows anyone with the public key to verify that the message was created by someone who possesses the corresponding private key. RSA signatures require a specific hash function, and padding to be used. Here is an example of signing ``message`` using RSA, with a secure hash function and padding: .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import padding >>> message = b"A message I want to sign" >>> signature = private_key.sign( ... message, ... padding.PSS( ... mgf=padding.MGF1(hashes.SHA256()), ... salt_length=padding.PSS.MAX_LENGTH ... ), ... hashes.SHA256() ... ) Valid paddings for signatures are :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` and :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`. ``PSS`` is the recommended choice for any new protocols or applications, ``PKCS1v15`` should only be used to support legacy protocols. If your data is too large to be passed in a single call, you can hash it separately and pass that value using :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import utils >>> chosen_hash = hashes.SHA256() >>> hasher = hashes.Hash(chosen_hash) >>> hasher.update(b"data & ") >>> hasher.update(b"more data") >>> digest = hasher.finalize() >>> sig = private_key.sign( ... digest, ... padding.PSS( ... mgf=padding.MGF1(hashes.SHA256()), ... salt_length=padding.PSS.MAX_LENGTH ... ), ... utils.Prehashed(chosen_hash) ... ) Verification ~~~~~~~~~~~~ The previous section describes what to do if you have a private key and want to sign something. If you have a public key, a message, a signature, and the signing algorithm that was used you can check that the private key associated with a given public key was used to sign that specific message. You can obtain a public key to use in verification using :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key`, :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key`, :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers.public_key` , or :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.public_key`. .. doctest:: >>> public_key = private_key.public_key() >>> public_key.verify( ... signature, ... message, ... padding.PSS( ... mgf=padding.MGF1(hashes.SHA256()), ... salt_length=padding.PSS.MAX_LENGTH ... ), ... hashes.SHA256() ... ) If the signature does not match, ``verify()`` will raise an :class:`~cryptography.exceptions.InvalidSignature` exception. If your data is too large to be passed in a single call, you can hash it separately and pass that value using :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`. .. doctest:: >>> chosen_hash = hashes.SHA256() >>> hasher = hashes.Hash(chosen_hash) >>> hasher.update(b"data & ") >>> hasher.update(b"more data") >>> digest = hasher.finalize() >>> public_key.verify( ... sig, ... digest, ... padding.PSS( ... mgf=padding.MGF1(hashes.SHA256()), ... salt_length=padding.PSS.MAX_LENGTH ... ), ... utils.Prehashed(chosen_hash) ... ) Encryption ~~~~~~~~~~ RSA encryption is interesting because encryption is performed using the **public** key, meaning anyone can encrypt data. The data is then decrypted using the **private** key. Like signatures, RSA supports encryption with several different padding options. Here's an example using a secure padding and hash function: .. doctest:: >>> message = b"encrypted data" >>> ciphertext = public_key.encrypt( ... message, ... padding.OAEP( ... mgf=padding.MGF1(algorithm=hashes.SHA256()), ... algorithm=hashes.SHA256(), ... label=None ... ) ... ) Valid paddings for encryption are :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP` and :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`. ``OAEP`` is the recommended choice for any new protocols or applications, ``PKCS1v15`` should only be used to support legacy protocols. Decryption ~~~~~~~~~~ Once you have an encrypted message, it can be decrypted using the private key: .. doctest:: >>> plaintext = private_key.decrypt( ... ciphertext, ... padding.OAEP( ... mgf=padding.MGF1(algorithm=hashes.SHA256()), ... algorithm=hashes.SHA256(), ... label=None ... ) ... ) >>> plaintext == message True Padding ~~~~~~~ .. module:: cryptography.hazmat.primitives.asymmetric.padding .. class:: AsymmetricPadding .. versionadded:: 0.2 .. attribute:: name .. class:: PSS(mgf, salt_length) .. versionadded:: 0.3 .. versionchanged:: 0.4 Added ``salt_length`` parameter. PSS (Probabilistic Signature Scheme) is a signature scheme defined in :rfc:`3447`. It is more complex than PKCS1 but possesses a `security proof`_. This is the `recommended padding algorithm`_ for RSA signatures. It cannot be used with RSA encryption. :param mgf: A mask generation function object. At this time the only supported MGF is :class:`MGF1`. :param int salt_length: The length of the salt. It is recommended that this be set to ``PSS.DIGEST_LENGTH`` or ``PSS.MAX_LENGTH``. .. attribute:: MAX_LENGTH Pass this attribute to ``salt_length`` to get the maximum salt length available. .. attribute:: DIGEST_LENGTH .. versionadded:: 37.0.0 Pass this attribute to ``salt_length`` to set the salt length to the byte length of the digest passed when calling ``sign``. Note that this is **not** the length of the digest passed to ``MGF1``. .. attribute:: AUTO .. versionadded:: 37.0.0 Pass this attribute to ``salt_length`` to automatically determine the salt length when verifying. Raises ``ValueError`` if used when signing. .. attribute:: mgf :type: :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF` .. versionadded:: 42.0.0 The padding's mask generation function (MGF). .. class:: OAEP(mgf, algorithm, label) .. versionadded:: 0.4 OAEP (Optimal Asymmetric Encryption Padding) is a padding scheme defined in :rfc:`3447`. It provides probabilistic encryption and is `proven secure`_ against several attack types. This is the `recommended padding algorithm`_ for RSA encryption. It cannot be used with RSA signing. :param mgf: A mask generation function object. At this time the only supported MGF is :class:`MGF1`. :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param bytes label: A label to apply. This is a rarely used field and should typically be set to ``None`` or ``b""``, which are equivalent. .. attribute:: algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` .. versionadded:: 42.0.0 The padding's hash algorithm. .. attribute:: mgf :type: :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF` .. versionadded:: 42.0.0 The padding's mask generation function (MGF). .. class:: PKCS1v15() .. versionadded:: 0.3 PKCS1 v1.5 (also known as simply PKCS1) is a simple padding scheme developed for use with RSA keys. It is defined in :rfc:`3447`. This padding can be used for signing and encryption. It is not recommended that ``PKCS1v15`` be used for new applications, :class:`OAEP` should be preferred for encryption and :class:`PSS` should be preferred for signatures. .. warning:: Our implementation of PKCS1 v1.5 decryption is not constant time. See :doc:`/limitations` for details. .. function:: calculate_max_pss_salt_length(key, hash_algorithm) .. versionadded:: 1.5 :param key: An RSA public or private key. :param hash_algorithm: A :class:`cryptography.hazmat.primitives.hashes.HashAlgorithm`. :returns int: The computed salt length. Computes the length of the salt that :class:`PSS` will use if :data:`PSS.MAX_LENGTH` is used. Mask generation functions ------------------------- .. class:: MGF .. versionadded:: 37.0.0 .. class:: MGF1(algorithm) .. versionadded:: 0.3 .. versionchanged:: 0.6 Removed the deprecated ``salt_length`` parameter. MGF1 (Mask Generation Function 1) is used as the mask generation function in :class:`PSS` and :class:`OAEP` padding. It takes a hash algorithm. :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. Numbers ~~~~~~~ .. currentmodule:: cryptography.hazmat.primitives.asymmetric.rsa These classes hold the constituent components of an RSA key. They are useful only when more traditional :doc:`/hazmat/primitives/asymmetric/serialization` is unavailable. .. class:: RSAPublicNumbers(e, n) .. versionadded:: 0.5 The collection of integers that make up an RSA public key. .. attribute:: n :type: int The public modulus. .. attribute:: e :type: int The public exponent. .. method:: public_key() :returns: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. .. class:: RSAPrivateNumbers(p, q, d, dmp1, dmq1, iqmp, public_numbers) .. versionadded:: 0.5 The collection of integers that make up an RSA private key. .. warning:: With the exception of the integers contained in the :class:`RSAPublicNumbers` all attributes of this class must be kept secret. Revealing them will compromise the security of any cryptographic operations performed with a key loaded from them. .. attribute:: public_numbers :type: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers` The :class:`RSAPublicNumbers` which makes up the RSA public key associated with this RSA private key. .. attribute:: p :type: int ``p``, one of the two primes composing ``n``. .. attribute:: q :type: int ``q``, one of the two primes composing ``n``. .. attribute:: d :type: int The private exponent. .. attribute:: dmp1 :type: int A `Chinese remainder theorem`_ coefficient used to speed up RSA operations. Calculated as: d mod (p-1) .. attribute:: dmq1 :type: int A `Chinese remainder theorem`_ coefficient used to speed up RSA operations. Calculated as: d mod (q-1) .. attribute:: iqmp :type: int A `Chinese remainder theorem`_ coefficient used to speed up RSA operations. Calculated as: q\ :sup:`-1` mod p .. method:: private_key(*, unsafe_skip_rsa_key_validation=False) :param unsafe_skip_rsa_key_validation: .. versionadded:: 39.0.0 A keyword-only argument that defaults to ``False``. If ``True`` RSA private keys will not be validated. This significantly speeds up loading the keys, but is :term:`unsafe` unless you are certain the key is valid. User supplied keys should never be loaded with this parameter set to ``True``. If you do load an invalid key this way and attempt to use it OpenSSL may hang, crash, or otherwise misbehave. :type unsafe_skip_rsa_key_validation: bool :returns: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. Handling partial RSA private keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you are trying to load RSA private keys yourself you may find that not all parameters required by ``RSAPrivateNumbers`` are available. In particular the `Chinese Remainder Theorem`_ (CRT) values ``dmp1``, ``dmq1``, ``iqmp`` may be missing or present in a different form. For example, `OpenPGP`_ does not include the ``iqmp``, ``dmp1`` or ``dmq1`` parameters. The following functions are provided for users who want to work with keys like this without having to do the math themselves. .. function:: rsa_crt_iqmp(p, q) .. versionadded:: 0.4 Computes the ``iqmp`` (also known as ``qInv``) parameter from the RSA primes ``p`` and ``q``. .. function:: rsa_crt_dmp1(private_exponent, p) .. versionadded:: 0.4 Computes the ``dmp1`` parameter from the RSA private exponent (``d``) and prime ``p``. .. function:: rsa_crt_dmq1(private_exponent, q) .. versionadded:: 0.4 Computes the ``dmq1`` parameter from the RSA private exponent (``d``) and prime ``q``. .. function:: rsa_recover_private_exponent(e, p, q) .. versionadded:: 43.0.0 Computes the RSA private_exponent (``d``) given the public exponent (``e``) and the RSA primes ``p`` and ``q``. .. note:: This implementation uses the Carmichael totient function to return the smallest working value of ``d``. Older RSA implementations, including the original RSA paper, often used the Euler totient function, which results in larger but equally functional private exponents. The private exponents resulting from the Carmichael totient function, as returned here, are slightly more computationally efficient to use, and some modern standards require them. .. function:: rsa_recover_prime_factors(n, e, d) .. versionadded:: 0.8 Computes the prime factors ``(p, q)`` given the modulus, public exponent, and private exponent. .. note:: When recovering prime factors this algorithm will always return ``p`` and ``q`` such that ``p > q``. Note: before 1.5, this function always returned ``p`` and ``q`` such that ``p < q``. It was changed because libraries commonly require ``p > q``. :return: A tuple ``(p, q)`` Key interfaces ~~~~~~~~~~~~~~ .. class:: RSAPrivateKey .. versionadded:: 0.2 An `RSA`_ private key. .. method:: decrypt(ciphertext, padding) .. versionadded:: 0.4 .. warning:: Our implementation of PKCS1 v1.5 decryption is not constant time. See :doc:`/limitations` for details. Decrypt data that was encrypted with the public key. :param bytes ciphertext: The ciphertext to decrypt. :param padding: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. :return bytes: Decrypted data. .. method:: public_key() :return: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` An RSA public key object corresponding to the values of the private key. .. attribute:: key_size :type: int The bit length of the modulus. .. method:: sign(data, padding, algorithm) .. versionadded:: 1.4 .. versionchanged:: 1.6 :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` can now be used as an ``algorithm``. Sign one block of data which can be verified later by others using the public key. :param data: The message string to sign. :type data: :term:`bytes-like` :param padding: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` if the ``data`` you want to sign has already been hashed. :return bytes: Signature. .. 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`, :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.OpenSSH` 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 An `RSA`_ public key. .. method:: encrypt(plaintext, padding) .. versionadded:: 0.4 Encrypt data with the public key. :param bytes plaintext: The plaintext to encrypt. :param padding: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. :return bytes: Encrypted data. :raises ValueError: The data could not be encrypted. One possible cause is if ``data`` is too large; RSA keys can only encrypt data that is smaller than the key size. .. attribute:: key_size :type: int The bit length of the modulus. .. 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. .. method:: verify(signature, data, padding, algorithm) .. versionadded:: 1.4 .. versionchanged:: 1.6 :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` can now be used as an ``algorithm``. Verify one block of data was signed by the private key associated with this public key. :param signature: The signature to verify. :type signature: :term:`bytes-like` :param data: The message string that was signed. :type data: :term:`bytes-like` :param padding: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` if the ``data`` you want to verify has already been hashed. :returns: None :raises cryptography.exceptions.InvalidSignature: If the signature does not validate. .. method:: recover_data_from_signature(signature, padding, algorithm) .. versionadded:: 3.3 Recovers the signed data from the signature. The data typically contains the digest of the original message string. The ``padding`` and ``algorithm`` parameters must match the ones used when the signature was created for the recovery to succeed. The ``algorithm`` parameter can also be set to ``None`` to recover all the data present in the signature, without regard to its format or the hash algorithm used for its creation. For :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` padding, this method returns the data after removing the padding layer. For standard signatures the data contains the full ``DigestInfo`` structure. For non-standard signatures, any data can be returned, including zero-length data. Normally you should use the :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.verify` function to validate the signature. But for some non-standard signature formats you may need to explicitly recover and validate the signed data. The following are some examples: - Some old Thawte and Verisign timestamp certificates without ``DigestInfo``. - Signed MD5/SHA1 hashes in TLS 1.1 or earlier (:rfc:`4346`, section 4.7). - IKE version 1 signatures without ``DigestInfo`` (:rfc:`2409`, section 5.1). :param bytes signature: The signature. :param padding: An instance of :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. Recovery is only supported with some of the padding types. (Currently only with :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`). :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. Can be ``None`` to return the all the data present in the signature. :return bytes: The signed data. :raises cryptography.exceptions.InvalidSignature: If the signature is invalid. :raises cryptography.exceptions.UnsupportedAlgorithm: If signature data recovery is not supported with the provided ``padding`` type. .. _`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 .. _`use 65537`: https://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html .. _`at least 2048`: https://www.cosic.esat.kuleuven.be/ecrypt/ecrypt2/documents/D.SPA.20.pdf .. _`OpenPGP`: https://en.wikipedia.org/wiki/Pretty_Good_Privacy .. _`Chinese Remainder Theorem`: https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29#Using_the_Chinese_remainder_algorithm .. _`security proof`: https://eprint.iacr.org/2001/062.pdf .. _`recommended padding algorithm`: https://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html .. _`proven secure`: https://cseweb.ucsd.edu/~mihir/papers/oaep.pdf cryptography-43.0.0/docs/hazmat/primitives/asymmetric/serialization.rst010064400017510000177000001673701464676315000247420ustar 00000000000000.. hazmat:: Key Serialization ================= .. module:: cryptography.hazmat.primitives.serialization .. testsetup:: import base64 pem_data = b""" -----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDn09PV9KPE7Q+N5K5UtNLT1DLl8z/pKM2pP5tXqWx2OsEw00lC kDHdHESwzS050s/8rtkERKKyusCzCm9+vC1pQzUlmtibfF4PQAQc1pJL6KHqlidg Hw49atYmnC25CaeXt65pAYXoIacOZ8k5X7FW3Eagex8nG0iMw4ObOtg6CwIDAQAB AoGBAL31l/4YYN1rNrSZLrQgGyUSGsbLxJHEKolFon95R3O1fzoH117gkstQb4TE Cwv3jw/JIfBaYUq8tku/AE9D2Jx51x7kYaCuQIMTavKIgkXKfxTQCQDjSEfkvXMW 4WOIj5sYdSCNbzLbaeFsWG32bSsBTy/sSheDIlCEFnqDuqwBAkEA+wYfJEMDf5nS VCQd9VKGM4HVeTWBioaWBFCflFdhc1Vb65dsNDp8iIMZgAHC2LEX5dMUmgqXk7AT lwFlIeW4CwJBAOxsSfuIVMuPKyx1xQ6ebpC7zeVxIOdswcM8ain91MSGDdKZw6pF ioFh3kUbKHw4yqqHbdRmUDAJ1mcgGJQOxgECQQCmQaGylKfmhWymyd0FtIip6J4I z4ViyEznwrZOu6kRiEF/QiUqWmpMx/fFrmTsvC5Fy43jkIxgBsiSxRvEXa+NAkB+ 5m0bhwTEslchKSGZhC6inzuYAQ4BSh4C1mXBnk5bIf0/Ymtk9KiwY8CzZS1o5+7Y c5LfI/+8mTss5UxsBDYBAkEA6NqhcsNWndIJZiWUU4u+RjFUQXqH8WCyJmEDCNxs 7SGRS1DTUGX4Y70m9dQpguy6Zg+gpHC+o+ERZR06uEQr+w== -----END RSA PRIVATE KEY----- """.strip() public_pem_data = b""" -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDn09PV9KPE7Q+N5K5UtNLT1DLl 8z/pKM2pP5tXqWx2OsEw00lCkDHdHESwzS050s/8rtkERKKyusCzCm9+vC1pQzUl mtibfF4PQAQc1pJL6KHqlidgHw49atYmnC25CaeXt65pAYXoIacOZ8k5X7FW3Eag ex8nG0iMw4ObOtg6CwIDAQAB -----END PUBLIC KEY----- """.strip() der_data = base64.b64decode( b"MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALskegl+DrI3Msw5Z63x" b"nj1rgoPR0KykwBi+jZgAwHv/B0TJyhy6NuEnaf+x442L7lepOqoWQzlUGXyuaSQU9mT/" b"vHTGZ2xM8QJJaccr4eGho0MU9HePyNCFWjWVrGKpwSEAd6CLlzC0Wiy4kC9IoAUoS/IP" b"jeyLTQNCddatgcARAgMBAAECgYAA/LlKJgeJUStTcpHgGD6mXjHvnAwWJELQKDP5+tA8" b"VAQGwBX1G5qzJDGrPGtHQ7DSqdwF4YFZtgTpZmGq1wsAjz3lv6L4XiVsHiIPtP1B4gMx" b"X9ogxcDzVQ7hyezXPioMAcp7Isus9Csn8HhftcL56BRabn6GvWqbIAy6zJcgEQJBAMlZ" b"nymKW5/jKth+wkCfqEXlPhGNPO1uq87QZUbYxwdjtSM09J9+HMfH+WXR9ARCOL46DJ0I" b"JfyjcdmuDDlh9IkCQQDt76up1Tmc7lkb/89IRBu2MudGJPMEf96VCG11nmcXulyk1OLi" b"TXfO62YpxZbgYrvlrNxEYlSG7WQMztBgA51JAkBU2RhyJ+S+drsaaigvlVgSxCyotszi" b"/Q0XZMgY18bfPUwanvkqsLkuEv3sw1HB7an9t3aTQdjIIpQad/acw8OJAkEAjvmnCK21" b"KgTbjQShtQYgNNLPwImxcjG4OYvP4o6l2k9FHlNCZsQwSymOwWkXKYyK5g+CaKFBs7Zw" b"mXWpJxjk6QJBAInqbm1w3yVfGD9I2mMQi/6oDJQP3pdWU4mU4h4sdDyRgTQLpkD4yypg" b"jOACt4mTzxifSVT9fT+a79SkT8FFmZE=" ) public_der_data = base64.b64decode( b"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7JHoJfg6yNzLMOWet8Z49a4KD0dCs" b"pMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkkFPZk/7x0xmdsTPEC" b"SWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAvSKAFKEvyD43si00DQnXW" b"rYHAEQIDAQAB" ) message = b"" def sign_with_rsa_key(key, message): return b"" def sign_with_dsa_key(key, message): return b"" parameters_pem_data = b""" -----BEGIN DH PARAMETERS----- MIGHAoGBALsrWt44U1ojqTy88o0wfjysBE51V6Vtarjm2+5BslQK/RtlndHde3gx +ccNs+InANszcuJFI8AHt4743kGRzy5XSlul4q4dDJENOHoyqYxueFuFVJELEwLQ XrX/McKw+hS6GPVQnw6tZhgGo9apdNdYgeLQeQded8Bum8jqzP3rAgEC -----END DH PARAMETERS----- """.strip() parameters_der_data = base64.b64decode( b"MIGHAoGBALsrWt44U1ojqTy88o0wfjysBE51V6Vtarjm2+5BslQK/RtlndHde3gx+ccNs+In" b"ANsz\ncuJFI8AHt4743kGRzy5XSlul4q4dDJENOHoyqYxueFuFVJELEwLQXrX/McKw+hS6GP" b"VQnw6tZhgG\no9apdNdYgeLQeQded8Bum8jqzP3rAgEC" ) There are several common schemes for serializing asymmetric private and public keys to bytes. They generally support encryption of private keys and additional key metadata. Many serialization formats support multiple different types of asymmetric keys and will return an instance of the appropriate type. You should check that the returned key matches the type your application expects when using these methods. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import dsa, rsa >>> from cryptography.hazmat.primitives.serialization import load_pem_private_key >>> key = load_pem_private_key(pem_data, password=None) >>> if isinstance(key, rsa.RSAPrivateKey): ... signature = sign_with_rsa_key(key, message) ... elif isinstance(key, dsa.DSAPrivateKey): ... signature = sign_with_dsa_key(key, message) ... else: ... raise TypeError Key dumping ~~~~~~~~~~~ The ``serialization`` module contains functions for loading keys from ``bytes``. To dump a ``key`` object to ``bytes``, you must call the appropriate method on the key object. Documentation for these methods in found in the :mod:`~cryptography.hazmat.primitives.asymmetric.rsa`, :mod:`~cryptography.hazmat.primitives.asymmetric.dsa`, and :mod:`~cryptography.hazmat.primitives.asymmetric.ec` module documentation. PEM ~~~ PEM is an encapsulation format, meaning keys in it can actually be any of several different key types. However these are all self-identifying, so you don't need to worry about this detail. PEM keys are recognizable because they all begin with ``-----BEGIN {format}-----`` and end with ``-----END {format}-----``. .. note:: A PEM block which starts with ``-----BEGIN CERTIFICATE-----`` is not a public or private key, it's an :doc:`X.509 Certificate `. You can load it using :func:`~cryptography.x509.load_pem_x509_certificate` and extract the public key with :meth:`Certificate.public_key `. .. function:: load_pem_private_key(data, password, *, unsafe_skip_rsa_key_validation=False) .. versionadded:: 0.6 .. note:: SSH private keys are a different format and must be loaded with :func:`load_ssh_private_key`. Deserialize a private key from PEM encoded data to one of the supported asymmetric private key types. :param data: The PEM encoded key data. :type data: :term:`bytes-like` :param password: The password to use to decrypt the data. Should be ``None`` if the private key is not encrypted. :type password: :term:`bytes-like` :param unsafe_skip_rsa_key_validation: .. versionadded:: 39.0.0 A keyword-only argument that defaults to ``False``. If ``True`` RSA private keys will not be validated. This significantly speeds up loading the keys, but is :term:`unsafe` unless you are certain the key is valid. User supplied keys should never be loaded with this parameter set to ``True``. If you do load an invalid key this way and attempt to use it OpenSSL may hang, crash, or otherwise misbehave. :type unsafe_skip_rsa_key_validation: bool :returns: One of :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` depending on the contents of ``data``. :raises ValueError: If the PEM data could not be decrypted or if its structure could not be decoded successfully. :raises TypeError: If a ``password`` was given and the private key was not encrypted. Or if the key was encrypted but no password was supplied. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key type is not supported by the OpenSSL version ``cryptography`` is using. .. function:: load_pem_public_key(data) .. versionadded:: 0.6 Deserialize a public key from PEM encoded data to one of the supported asymmetric public key types. The PEM encoded data is typically a ``subjectPublicKeyInfo`` payload as specified in :rfc:`5280`. .. doctest:: >>> from cryptography.hazmat.primitives.serialization import load_pem_public_key >>> key = load_pem_public_key(public_pem_data) >>> isinstance(key, rsa.RSAPublicKey) True :param bytes data: The PEM encoded key data. :returns: One of :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` depending on the contents of ``data``. :raises ValueError: If the PEM data's structure could not be decoded successfully. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key type is not supported by the OpenSSL version ``cryptography`` is using. .. function:: load_pem_parameters(data) .. versionadded:: 2.0 Deserialize parameters from PEM encoded data to one of the supported asymmetric parameters types. .. doctest:: >>> from cryptography.hazmat.primitives.serialization import load_pem_parameters >>> from cryptography.hazmat.primitives.asymmetric import dh >>> parameters = load_pem_parameters(parameters_pem_data) >>> isinstance(parameters, dh.DHParameters) True :param bytes data: The PEM encoded parameters data. :returns: Currently only :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters` supported. :raises ValueError: If the PEM data's structure could not be decoded successfully. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key type is not supported by the OpenSSL version ``cryptography`` is using. DER ~~~ DER is an ASN.1 encoding type. There are no encapsulation boundaries and the data is binary. DER keys may be in a variety of formats, but as long as you know whether it is a public or private key the loading functions will handle the rest. .. function:: load_der_private_key(data, password, *, unsafe_skip_rsa_key_validation=False) .. versionadded:: 0.8 Deserialize a private key from DER encoded data to one of the supported asymmetric private key types. :param data: The DER encoded key data. :type data: :term:`bytes-like` :param password: The password to use to decrypt the data. Should be ``None`` if the private key is not encrypted. :type password: :term:`bytes-like` :param unsafe_skip_rsa_key_validation: .. versionadded:: 39.0.0 A keyword-only argument that defaults to ``False``. If ``True`` RSA private keys will not be validated. This significantly speeds up loading the keys, but is :term:`unsafe` unless you are certain the key is valid. User supplied keys should never be loaded with this parameter set to ``True``. If you do load an invalid key this way and attempt to use it OpenSSL may hang, crash, or otherwise misbehave. :type unsafe_skip_rsa_key_validation: bool :returns: One of :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` depending on the contents of ``data``. :raises ValueError: If the DER data could not be decrypted or if its structure could not be decoded successfully. :raises TypeError: If a ``password`` was given and the private key was not encrypted. Or if the key was encrypted but no password was supplied. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key type is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> from cryptography.hazmat.primitives.serialization import load_der_private_key >>> key = load_der_private_key(der_data, password=None) >>> isinstance(key, rsa.RSAPrivateKey) True .. function:: load_der_public_key(data) .. versionadded:: 0.8 Deserialize a public key from DER encoded data to one of the supported asymmetric public key types. The DER encoded data is typically a ``subjectPublicKeyInfo`` payload as specified in :rfc:`5280`. :param bytes data: The DER encoded key data. :returns: One of :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` depending on the contents of ``data``. :raises ValueError: If the DER data's structure could not be decoded successfully. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key type is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> from cryptography.hazmat.primitives.serialization import load_der_public_key >>> key = load_der_public_key(public_der_data) >>> isinstance(key, rsa.RSAPublicKey) True .. function:: load_der_parameters(data) .. versionadded:: 2.0 Deserialize parameters from DER encoded data to one of the supported asymmetric parameters types. :param bytes data: The DER encoded parameters data. :returns: Currently only :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters` supported. :raises ValueError: If the DER data's structure could not be decoded successfully. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key type is not supported by the OpenSSL version ``cryptography`` is using. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import dh >>> from cryptography.hazmat.primitives.serialization import load_der_parameters >>> parameters = load_der_parameters(parameters_der_data) >>> isinstance(parameters, dh.DHParameters) True OpenSSH Public Key ~~~~~~~~~~~~~~~~~~ The format used by OpenSSH to store public keys, as specified in :rfc:`4253`. An example RSA key in OpenSSH format (line breaks added for formatting purposes):: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy ///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX 2MzHvnbv testkey@localhost DSA keys look almost identical but begin with ``ssh-dss`` rather than ``ssh-rsa``. ECDSA keys have a slightly different format, they begin with ``ecdsa-sha2-{curve}``. .. data:: SSHPublicKeyTypes .. versionadded:: 40.0.0 Type alias: A union of public key types accepted for SSH: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` , or :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`. .. function:: load_ssh_public_key(data) .. versionadded:: 0.7 .. note:: SSH DSA key support is deprecated and will be removed in a future release. Deserialize a public key from OpenSSH (:rfc:`4253` and `PROTOCOL.certkeys`_) encoded data to an instance of the public key type. :param data: The OpenSSH encoded key data. :type data: :term:`bytes-like` :returns: One of :data:`SSHPublicKeyTypes` depending on the contents of ``data``. :raises ValueError: If the OpenSSH data could not be properly decoded or if the key is not in the proper format. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that is not supported. OpenSSH Private Key ~~~~~~~~~~~~~~~~~~~ The format used by OpenSSH to store private keys, as approximately specified in `PROTOCOL.key`_. An example ECDSA key in OpenSSH format:: -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRI0fWnI1CxX7qYqp0ih6bxjhGmUrZK /Axf8vhM8Db3oH7CFR+JdL715lUdu4XCWvQZKVf60/h3kBFhuxQC23XjAAAAqKPzVaOj81 WjAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEjR9acjULFfupiq nSKHpvGOEaZStkr8DF/y+EzwNvegfsIVH4l0vvXmVR27hcJa9BkpV/rT+HeQEWG7FALbde MAAAAga/VGV2asRlL3kXXao0aochQ59nXHA2xEGeAoQd952r0AAAAJbWFya29AdmZmAQID BAUGBw== -----END OPENSSH PRIVATE KEY----- .. data:: SSHPrivateKeyTypes .. versionadded:: 40.0.0 Type alias: A union of private key types accepted for SSH: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`. .. function:: load_ssh_private_key(data, password) .. versionadded:: 3.0 .. note:: SSH DSA key support is deprecated and will be removed in a future release. Deserialize a private key from OpenSSH encoded data to an instance of the private key type. :param data: The PEM encoded OpenSSH private key data. :type data: :term:`bytes-like` :param bytes password: Password bytes to use to decrypt password-protected key. Or ``None`` if not needed. :returns: One of :data:`SSHPrivateKeyTypes` depending on the contents of ``data``. :raises ValueError: If the OpenSSH data could not be properly decoded, if the key is not in the proper format or the incorrect password was provided. :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that is not supported. OpenSSH Certificate ~~~~~~~~~~~~~~~~~~~ The format used by OpenSSH for certificates, as specified in `PROTOCOL.certkeys`_. .. data:: SSHCertPublicKeyTypes .. versionadded:: 40.0.0 Type alias: A union of public key types supported for SSH certificates: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` .. data:: SSHCertPrivateKeyTypes .. versionadded:: 40.0.0 Type alias: A union of private key types supported for SSH certificates: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` .. function:: load_ssh_public_identity(data) .. versionadded:: 40.0.0 .. note:: This function does not support parsing certificates with DSA public keys or signatures from DSA certificate authorities. DSA is a deprecated algorithm and should not be used. Deserialize an OpenSSH encoded identity to an instance of :class:`SSHCertificate` or the appropriate public key type. Parsing a certificate does not verify anything. It is up to the caller to perform any necessary verification. :param data: The OpenSSH encoded data. :type data: bytes :returns: :class:`SSHCertificate` or one of :data:`SSHCertPublicKeyTypes`. :raises ValueError: If the OpenSSH data could not be properly decoded. :raises cryptography.exceptions.UnsupportedAlgorithm: If the data contains a public key type that is not supported. .. class:: SSHCertificate .. versionadded:: 40.0.0 .. attribute:: nonce :type: bytes The nonce field is a CA-provided random value of arbitrary length (but typically 16 or 32 bytes) included to make attacks that depend on inducing collisions in the signature hash infeasible. .. method:: public_key() The public key contained in the certificate, one of :data:`SSHCertPublicKeyTypes`. .. attribute:: serial :type: int Serial is an optional certificate serial number set by the CA to provide an abbreviated way to refer to certificates from that CA. If a CA does not wish to number its certificates, it must set this field to zero. .. attribute:: type :type: :class:`SSHCertificateType` Type specifies whether this certificate is for identification of a user or a host. .. attribute:: key_id :type: bytes This is a free-form text field that is filled in by the CA at the time of signing; the intention is that the contents of this field are used to identify the identity principal in log messages. .. attribute:: valid_principals :type: list[bytes] "valid principals" is a list containing one or more principals as byte strings. These principals list the names for which this certificate is valid; hostnames for host certificates and usernames for user certificates. As a special case, an empty list means the certificate is valid for any principal of the specified type. .. attribute:: valid_after :type: int An integer representing the Unix timestamp (in UTC) after which the certificate is valid. **This time is inclusive.** .. attribute:: valid_before :type: int An integer representing the Unix timestamp (in UTC) before which the certificate is valid. **This time is not inclusive.** .. attribute:: critical_options :type: dict[bytes, bytes] Critical options is a dict of zero or more options that are critical for the certificate to be considered valid. If any of these options are not supported by the implementation, the certificate must be rejected. .. attribute:: extensions :type: dict[bytes, bytes] Extensions is a dict of zero or more options that are non-critical for the certificate to be considered valid. If any of these options are not supported by the implementation, the implementation may safely ignore them. .. method:: signature_key() The public key used to sign the certificate, one of :data:`SSHCertPublicKeyTypes`. .. method:: verify_cert_signature() .. warning:: This method does not validate anything about whether the signing key is trusted! Callers are responsible for validating trust in the signer. Validates that the signature on the certificate was created by the private key associated with the certificate's signature key and that the certificate has not been changed since signing. :return: None :raises: :class:`~cryptography.exceptions.InvalidSignature` if the signature is invalid. .. method:: public_bytes() :return: The serialized certificate in OpenSSH format. :rtype: bytes .. class:: SSHCertificateType .. versionadded:: 40.0.0 An enumeration of the types of SSH certificates. .. attribute:: USER The cert is intended for identification of a user. Corresponds to the value ``1``. .. attribute:: HOST The cert is intended for identification of a host. Corresponds to the value ``2``. SSH Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~ .. class:: SSHCertificateBuilder .. versionadded:: 40.0.0 .. note:: This builder does not support generating certificates with DSA public keys or creating signatures with DSA certificate authorities. DSA is a deprecated algorithm and should not be used. .. doctest:: >>> import datetime >>> from cryptography.hazmat.primitives.asymmetric import ec >>> from cryptography.hazmat.primitives.serialization import ( ... SSHCertificateType, SSHCertificateBuilder ... ) >>> signing_key = ec.generate_private_key(ec.SECP256R1()) >>> public_key = ec.generate_private_key(ec.SECP256R1()).public_key() >>> valid_after = datetime.datetime( ... 2023, 1, 1, 1, tzinfo=datetime.timezone.utc ... ).timestamp() >>> valid_before = datetime.datetime( ... 2023, 7, 1, 1, tzinfo=datetime.timezone.utc ... ).timestamp() >>> key_id = b"a_key_id" >>> valid_principals = [b"eve", b"alice"] >>> builder = ( ... SSHCertificateBuilder() ... .public_key(public_key) ... .type(SSHCertificateType.USER) ... .valid_before(valid_before) ... .valid_after(valid_after) ... .key_id(b"a_key_id") ... .valid_principals(valid_principals) ... .add_extension(b"no-touch-required", b"") ... ) >>> builder.sign(signing_key).public_bytes() b'...' .. method:: public_key(public_key) :param public_key: The public key to be included in the certificate. This value is required. :type public_key: :data:`SSHCertPublicKeyTypes` .. method:: serial(serial) :param int serial: The serial number to be included in the certificate. This is not a required value and will be set to zero if not provided. Value must be between 0 and 2:sup:`64` - 1, inclusive. .. method:: type(type) :param type: The type of the certificate. There are two options, user or host. :type type: :class:`SSHCertificateType` .. method:: key_id(key_id) :param key_id: The key ID to be included in the certificate. This is not a required value. :type key_id: bytes .. method:: valid_principals(valid_principals) :param valid_principals: A list of principals that the certificate is valid for. This is a required value unless :meth:`valid_for_all_principals` has been called. :type valid_principals: list[bytes] .. method:: valid_for_all_principals() Marks the certificate as valid for all principals. This cannot be set if principals have been added via :meth:`valid_principals`. .. method:: valid_after(valid_after) :param int valid_after: The Unix timestamp (in UTC) that marks the activation time for the certificate. This is a required value. .. method:: valid_before(valid_before) :param int valid_before: The Unix timestamp (in UTC) that marks the expiration time for the certificate. This is a required value. .. method:: add_critical_option(name, value) :param name: The name of the critical option to add. No duplicates are allowed. :type name: bytes :param value: The value of the critical option to add. This is commonly an empty byte string. :type value: bytes .. method:: add_extension(name, value) :param name: The name of the extension to add. No duplicates are allowed. :type name: bytes :param value: The value of the extension to add. :type value: bytes .. method:: sign(private_key) :param private_key: The private key that will be used to sign the certificate. :type private_key: :data:`SSHCertPrivateKeyTypes` :return: The signed certificate. :rtype: :class:`SSHCertificate` PKCS12 ~~~~~~ .. currentmodule:: cryptography.hazmat.primitives.serialization.pkcs12 PKCS12 is a binary format described in :rfc:`7292`. It can contain certificates, keys, and more. PKCS12 files commonly have a ``pfx`` or ``p12`` file suffix. .. note:: ``cryptography`` only supports a single private key and associated certificates when parsing PKCS12 files at this time. .. data:: PKCS12PrivateKeyTypes .. versionadded:: 40.0.0 Type alias: A union of private key types supported for PKCS12 serialization: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. .. function:: load_key_and_certificates(data, password) .. versionadded:: 2.5 Deserialize a PKCS12 blob. :param data: The binary data. :type data: :term:`bytes-like` :param password: The password to use to decrypt the data. ``None`` if the PKCS12 is not encrypted. :type password: :term:`bytes-like` :returns: A tuple of ``(private_key, certificate, additional_certificates)``. ``private_key`` is a private key type or ``None``, ``certificate`` is either the :class:`~cryptography.x509.Certificate` whose public key matches the private key in the PKCS 12 object or ``None``, and ``additional_certificates`` is a list of all other :class:`~cryptography.x509.Certificate` instances in the PKCS12 object. .. function:: load_pkcs12(data, password) .. versionadded:: 36.0.0 Deserialize a PKCS12 blob, and return a :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates` instance. :param data: The binary data. :type data: :term:`bytes-like` :param password: The password to use to decrypt the data. ``None`` if the PKCS12 is not encrypted. :type password: :term:`bytes-like` :returns: A :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates` instance. .. function:: serialize_key_and_certificates(name, key, cert, cas, encryption_algorithm) .. versionadded:: 3.0 .. note:: With OpenSSL 3.0.0+ the defaults for encryption when serializing PKCS12 have changed and some versions of Windows and macOS will not be able to read the new format. Maximum compatibility can be achieved by using ``SHA1`` for MAC algorithm and :attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC` for encryption algorithm as seen in the example below. However, users should avoid this unless required for compatibility. .. warning:: PKCS12 encryption is typically not secure and should not be used as a security mechanism. Wrap a PKCS12 blob in a more secure envelope if you need to store or send it safely. Serialize a PKCS12 blob. .. note:: Due to `a bug in Firefox`_ it's not possible to load unencrypted PKCS12 blobs in Firefox. :param name: The friendly name to use for the supplied certificate and key. :type name: bytes :param key: The private key to include in the structure. :type key: :data:`PKCS12PrivateKeyTypes` :param cert: The certificate associated with the private key. :type cert: :class:`~cryptography.x509.Certificate` or ``None`` :param cas: An optional set of certificates to also include in the structure. If a :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` is given, its friendly name will be serialized. :type cas: ``None``, or list of :class:`~cryptography.x509.Certificate` or :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` :param encryption_algorithm: The encryption algorithm that should be used for the key and certificate. An instance of an object conforming to the :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` interface. PKCS12 encryption is typically **very weak** and should not be used as a security boundary. :return bytes: Serialized PKCS12. .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, load_pem_private_key, pkcs12 >>> cert = x509.load_pem_x509_certificate(ca_cert) >>> key = load_pem_private_key(ca_key, None) >>> p12 = pkcs12.serialize_key_and_certificates( ... b"friendlyname", key, cert, None, BestAvailableEncryption(b"password") ... ) This example uses an ``encryption_builder()`` to create a PKCS12 with more compatible, but substantially less secure, encryption. .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.serialization import PrivateFormat, load_pem_private_key, pkcs12 >>> encryption = ( ... PrivateFormat.PKCS12.encryption_builder(). ... kdf_rounds(50000). ... key_cert_algorithm(pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC). ... hmac_hash(hashes.SHA1()).build(b"my password") ... ) >>> cert = x509.load_pem_x509_certificate(ca_cert) >>> key = load_pem_private_key(ca_key, None) >>> p12 = pkcs12.serialize_key_and_certificates( ... b"friendlyname", key, cert, None, encryption ... ) .. class:: PKCS12Certificate .. versionadded:: 36.0.0 Represents additional data provided for a certificate in a PKCS12 file. .. attribute:: certificate A :class:`~cryptography.x509.Certificate` instance. .. attribute:: friendly_name :type: bytes or None An optional byte string containing the friendly name of the certificate. .. class:: PKCS12KeyAndCertificates .. versionadded:: 36.0.0 A simplified representation of a PKCS12 file. .. attribute:: key An optional private key belonging to :attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates.cert` (see :data:`PKCS12PrivateKeyTypes`). .. attribute:: cert An optional :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` instance belonging to the private key :attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates.key`. .. attribute:: additional_certs A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate` instances. .. class:: PBES :canonical: cryptography.hazmat.primitives._serialization.PBES .. versionadded:: 38.0.0 An enumeration of password-based encryption schemes used in PKCS12. These values are used with :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryptionBuilder`. .. attribute:: PBESv1SHA1And3KeyTripleDESCBC PBESv1 using SHA1 as the KDF PRF and 3-key triple DES-CBC as the cipher. .. attribute:: PBESv2SHA256AndAES256CBC PBESv2 using SHA256 as the KDF PRF and AES256-CBC as the cipher. This is only supported on OpenSSL 3.0.0 or newer. PKCS7 ~~~~~ .. currentmodule:: cryptography.hazmat.primitives.serialization.pkcs7 PKCS7 is a format described in :rfc:`2315`, among other specifications. It can contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, ``p7m``, or ``p7s`` file suffix but other suffixes are also seen in the wild. .. note:: ``cryptography`` only supports parsing certificates from PKCS7 files at this time. .. data:: PKCS7HashTypes .. versionadded:: 40.0.0 Type alias: A union of hash types supported for PKCS7 serialization: :class:`~cryptography.hazmat.primitives.hashes.SHA1`, :class:`~cryptography.hazmat.primitives.hashes.SHA224`, :class:`~cryptography.hazmat.primitives.hashes.SHA256`, :class:`~cryptography.hazmat.primitives.hashes.SHA384`, or :class:`~cryptography.hazmat.primitives.hashes.SHA512`. .. data:: PKCS7PrivateKeyTypes .. versionadded:: 40.0.0 Type alias: A union of private key types supported for PKCS7 serialization: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` .. function:: load_pem_pkcs7_certificates(data) .. versionadded:: 3.1 Deserialize a PEM encoded PKCS7 blob to a list of certificates. PKCS7 can contain many other types of data, including CRLs, but this function will ignore everything except certificates. :param data: The data. :type data: bytes :returns: A list of :class:`~cryptography.x509.Certificate`. :raises ValueError: If the PKCS7 data could not be loaded. :raises cryptography.exceptions.UnsupportedAlgorithm: If the PKCS7 data is of a type that is not supported. .. function:: load_der_pkcs7_certificates(data) .. versionadded:: 3.1 Deserialize a DER encoded PKCS7 blob to a list of certificates. PKCS7 can contain many other types of data, including CRLs, but this function will ignore everything except certificates. :param data: The data. :type data: bytes :returns: A list of :class:`~cryptography.x509.Certificate`. :raises ValueError: If the PKCS7 data could not be loaded. :raises cryptography.exceptions.UnsupportedAlgorithm: If the PKCS7 data is of a type that is not supported. .. function:: serialize_certificates(certs, encoding) .. versionadded:: 37.0.0 Serialize a list of certificates to a PKCS7 structure. :param certs: A list of :class:`~cryptography.x509.Certificate`. :param encoding: :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`. :returns bytes: The serialized PKCS7 data. .. testsetup:: ca_key = b""" -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgA8Zqz5vLeR0ePZUe jBfdyMmnnI4U5uAJApWTsMn/RuWhRANCAAQY/8+7+Tm49d3D7sBAiwZ1BqtPzdgs UiROH+AQRme1XxW5Yr07zwxvvhr3tKEPtLnLboazUPlsUb/Bgte+xfkF -----END PRIVATE KEY----- """.strip() ca_cert = b""" -----BEGIN CERTIFICATE----- MIIBUTCB96ADAgECAgIDCTAKBggqhkjOPQQDAjAnMQswCQYDVQQGEwJVUzEYMBYG A1UEAwwPY3J5cHRvZ3JhcGh5IENBMB4XDTE3MDEwMTEyMDEwMFoXDTM4MTIzMTA4 MzAwMFowJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTBZ MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBj/z7v5Obj13cPuwECLBnUGq0/N2CxS JE4f4BBGZ7VfFblivTvPDG++Gve0oQ+0uctuhrNQ+WxRv8GC177F+QWjEzARMA8G A1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhANES742XWm64tkGnz8Dn pG6u2lHkZFQr3oaVvPcemvlbAiEA0WGGzmYx5C9UvfXIK7NEziT4pQtyESE0uRVK Xw4nMqk= -----END CERTIFICATE----- """.strip() ca_cert_rsa = b""" -----BEGIN CERTIFICATE----- MIIExzCCAq+gAwIBAgIJAOcS06ClbtbJMA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNV BAMMD2NyeXB0b2dyYXBoeSBDQTAeFw0yMDA5MTQyMTQwNDJaFw00ODAxMzEyMTQw NDJaMBoxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTCCAiIwDQYJKoZIhvcNAQEB BQADggIPADCCAgoCggIBANBIheRc1HT4MzV5GvUbDk9CFU6DTomRApNqRmizriRq m6OY4Ht3d71BXog6/IBkqAnZ4/XJQ40G4sVDb52k11oPvfJ/F5pc+6UqPBL+QGzY GkJoubAqXFpI6ow0qayFNQLv0T9o4yh0QQOoGvgCmv91qmitLrZNXu4U9S76G+Di GST+QyMkMxj+VsGRsRRBufV1urcnvFWjU6Q2+cr2cp0mMAG96NTyIskYiJ8vL03W z4DX4klO4X47fPmDnU/OMn4SbvMZ896j1L0J04S+uVThTkxQWcFcqXhX5qM8kzcj JUmybFlbf150j3WiucW48K/j7fJ0x9q3iUo4Gva0coScglJWcgo/BBCwFDw8NVba 7npxSRMiaS3qTv0dEFcRnvByc+7hyGxxlWdTE9tHisUI1eZVk9P9ziqNOZKscY8Z X1+/C4M9X69Y7A8I74F5dO27IRycEgOrSo2z1NhfSwbqJr9a2TBtRsFinn8rjKBI zNn0E5p9jO1WjxtkcjHfXXpLN8FFMvoYI9l/K+ZWDm9sboaF8jrgozSc004AFemA H79mmCGVRKXn1vDAo4DLC6p3NiBFYQcYbW9V+beGD6srsF6xJtuY/UwtPROLWSzu CCrZ/4BlmpNsR0ehIFFvzEKjX6rR2yp3YKlguDbMBMKMpfSGxAFwcZ7OiaxR20UH AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADSveDS4 y2V/N6Li2n9ChGNdCMr/45M0cl+GpL55aA36AWYMRLv0wip7MWV3yOj4mkjGBlTE awKHH1FtetsE6B4a7M2hHhOXyXE60uUdptEx6ckGrJ1iyqu5cQUX1P+VnXbmOxfF bl+Ugzjbgirx239rA4ezkDRuOvKcCbDOFV/gw3ZHfJ/IQeRXIQRl/y51wcnFUvFM JEESYiijeDbEcY8r1/phmVQL0CO7WLMmTxlFj4X/TR3MTZWJQIap9GiLs5+n3QiO jsZ3GuFOomB8oTebYkXniwbNu5hgLP/seRQzGA7B9VDZryAhCtvGgjtQh0eW2Qxt sgmDJGOPKnKT3O5U0v3+IPLEYpe8JSzgAhhh6H1rAJRUNwP2gRcO4eOUJSkdl218 fRNT0ILzosuWxwprER9ciMQF8q0JJKMhcfHRMH0S5mWVJAIkj68KY05oCy2zNyYa oruopKSWXe0Bzr40znm40P7xIkui2BGQMlDPpbCaEfLsLqyctfbdmMlxac/QgIfY TltrbqmI3MNy5uqGViGFpWPCB+kD8EsJF9nlKJXlu/i55qgUr/2/2CdeWlZDBP8A 1fdzmpYpWnwhE0KobzLS2z3AwDxiY/RSWUfypLZA0K/lpaEtYB6UHMDZ0/8WqgZV gNucCuty0cA4Kf7eX1TlAKVwH8hTkVmJc2rX -----END CERTIFICATE----- """.strip() .. class:: PKCS7SignatureBuilder The PKCS7 signature builder can create both basic PKCS7 signed messages as well as S/MIME messages, which are commonly used in email. S/MIME has multiple versions, but this implements a subset of :rfc:`2632`, also known as S/MIME Version 3. .. versionadded:: 3.2 .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes, serialization >>> from cryptography.hazmat.primitives.serialization import pkcs7 >>> cert = x509.load_pem_x509_certificate(ca_cert) >>> key = serialization.load_pem_private_key(ca_key, None) >>> options = [pkcs7.PKCS7Options.DetachedSignature] >>> pkcs7.PKCS7SignatureBuilder().set_data( ... b"data to sign" ... ).add_signer( ... cert, key, hashes.SHA256() ... ).sign( ... serialization.Encoding.SMIME, options ... ) b'...' .. method:: set_data(data) :param data: The data to be hashed and signed. :type data: :term:`bytes-like` .. method:: add_signer(certificate, private_key, hash_algorithm, *, rsa_padding=None) :param certificate: The :class:`~cryptography.x509.Certificate`. :param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` associated with the certificate provided (matches :data:`PKCS7PrivateKeyTypes`). :param hash_algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the signature. This must be one of the types in :data:`PKCS7HashTypes`. :param rsa_padding: .. versionadded:: 42.0.0 This is a keyword-only argument. If ``private_key`` is an ``RSAPrivateKey`` then this can be set to either :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign with those respective paddings. If this is ``None`` then RSA keys will default to ``PKCS1v15`` padding. All other key types **must** not pass a value other than ``None``. .. method:: add_certificate(certificate) Add an additional certificate (typically used to help build a verification chain) to the PKCS7 structure. This method may be called multiple times to add as many certificates as desired. :param certificate: The :class:`~cryptography.x509.Certificate` to add. .. method:: sign(encoding, options) :param encoding: :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME`. :param options: A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. :returns bytes: The signed PKCS7 message. .. class:: PKCS7EnvelopeBuilder The PKCS7 envelope builder can create encrypted S/MIME messages, which are commonly used in email. S/MIME has multiple versions, but this implements a subset of :rfc:`5751`, also known as S/MIME Version 3.2. .. versionadded:: 43.0.0 .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.serialization import pkcs7 >>> cert = x509.load_pem_x509_certificate(ca_cert_rsa) >>> options = [pkcs7.PKCS7Options.Text] >>> pkcs7.PKCS7EnvelopeBuilder().set_data( ... b"data to encrypt" ... ).add_recipient( ... cert ... ).encrypt( ... serialization.Encoding.SMIME, options ... ) b'...' .. method:: set_data(data) :param data: The data to be encrypted. :type data: :term:`bytes-like` .. method:: add_recipient(certificate) Add a recipient for the message. Recipients will be able to use their private keys to decrypt the message. This method may be called multiple times to add as many recipients as desired. :param certificate: A :class:`~cryptography.x509.Certificate` for an intended recipient of the encrypted message. Only certificates with public RSA keys are currently supported. .. method:: encrypt(encoding, options) The message is encrypted using AES-128-CBC. The encryption key used is included in the envelope, encrypted using the recipient's public RSA key. If multiple recipients are specified, the key is encrypted once with each recipient's public key, and all encrypted keys are included in the envelope (one per recipient). :param encoding: :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME`. :param options: A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For this operation only :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` and :attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Binary` are supported. :returns bytes: The enveloped PKCS7 message. .. class:: PKCS7Options .. versionadded:: 3.2 An enumeration of options for PKCS7 signature and envelope creation. .. attribute:: Text The text option adds ``text/plain`` headers to an S/MIME message when serializing to :attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME`. This option is disallowed with ``DER`` serialization. .. attribute:: Binary Signing normally converts line endings (LF to CRLF). When passing this option the data will not be converted. .. attribute:: DetachedSignature Don't embed the signed data within the ASN.1. When signing with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME` this also results in the data being added as clear text before the PEM encoded structure. .. attribute:: NoCapabilities PKCS7 structures contain a ``MIMECapabilities`` section inside the ``authenticatedAttributes``. Passing this as an option removes ``MIMECapabilities``. .. attribute:: NoAttributes PKCS7 structures contain an ``authenticatedAttributes`` section. Passing this as an option removes that section. Note that if you pass ``NoAttributes`` you can't pass ``NoCapabilities`` since ``NoAttributes`` removes ``MIMECapabilities`` and more. .. attribute:: NoCerts Don't include the signer's certificate in the PKCS7 structure. This can reduce the size of the signature but requires that the recipient can obtain the signer's certificate by other means (for example from a previously signed message). Serialization Formats ~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: cryptography.hazmat.primitives.serialization .. class:: PrivateFormat :canonical: cryptography.hazmat.primitives._serialization.PrivateFormat .. versionadded:: 0.8 An enumeration for private key formats. Used with the ``private_bytes`` method available on :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey` and :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. .. attribute:: TraditionalOpenSSL Frequently known as PKCS#1 format. Still a widely used format, but generally considered legacy. A PEM encoded RSA key will look like:: -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY----- .. attribute:: PKCS8 A more modern format for serializing keys which allows for better encryption. Choose this unless you have explicit legacy compatibility requirements. A PEM encoded key will look like:: -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- .. attribute:: Raw .. versionadded:: 2.5 A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. .. attribute:: OpenSSH .. versionadded:: 3.0 Custom private key format for OpenSSH, internals are based on SSH protocol and not ASN1. Requires :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` encoding. A PEM encoded OpenSSH key will look like:: -----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY----- .. attribute:: PKCS12 .. versionadded:: 38.0.0 The PKCS#12 format is a binary format used to store private keys and certificates. This attribute is used in conjunction with ``encryption_builder()`` to allow control of the encryption algorithm and parameters. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.serialization import PrivateFormat, pkcs12 >>> encryption = ( ... PrivateFormat.PKCS12.encryption_builder(). ... kdf_rounds(50000). ... key_cert_algorithm(pkcs12.PBES.PBESv2SHA256AndAES256CBC). ... hmac_hash(hashes.SHA256()).build(b"my password") ... ) >>> p12 = pkcs12.serialize_key_and_certificates( ... b"friendlyname", key, None, None, encryption ... ) .. method:: encryption_builder() .. versionadded:: 38.0.0 Returns a builder for configuring how values are encrypted with this format. You must call this method on an element of the enumeration. For example, ``PrivateFormat.OpenSSH.encryption_builder()``. For most use cases, :class:`BestAvailableEncryption` is preferred. :returns: A new instance of :class:`KeySerializationEncryptionBuilder` .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> encryption = ( ... serialization.PrivateFormat.OpenSSH.encryption_builder().kdf_rounds(30).build(b"my password") ... ) >>> key.private_bytes( ... encoding=serialization.Encoding.PEM, ... format=serialization.PrivateFormat.OpenSSH, ... encryption_algorithm=encryption ... ) b'-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n' .. 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.RSAPublicKey` , :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey` , and :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. .. 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. A PEM encoded key will look like:: -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- .. attribute:: PKCS1 Just the public key elements (without the algorithm identifier). This format is RSA only, but is used by some older systems. A PEM encoded key will look like:: -----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY----- .. attribute:: OpenSSH .. versionadded:: 1.4 The public key format used by OpenSSH (e.g. as found in ``~/.ssh/id_rsa.pub`` or ``~/.ssh/authorized_keys``). .. attribute:: Raw .. versionadded:: 2.5 A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. .. attribute:: CompressedPoint .. versionadded:: 2.5 A compressed elliptic curve public key as defined in ANSI X9.62 section 4.3.6 (as well as `SEC 1 v2.0`_). .. attribute:: UncompressedPoint .. versionadded:: 2.5 An uncompressed elliptic curve public key as defined in ANSI X9.62 section 4.3.6 (as well as `SEC 1 v2.0`_). .. class:: ParameterFormat .. versionadded:: 2.0 An enumeration for parameters formats. Used with the ``parameter_bytes`` method available on :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. .. attribute:: PKCS3 ASN1 DH parameters sequence as defined in `PKCS3`_. Serialization Encodings ~~~~~~~~~~~~~~~~~~~~~~~ .. class:: Encoding :canonical: cryptography.hazmat.primitives._serialization.Encoding An enumeration for encoding types. Used with the ``private_bytes`` method available on :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, and :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey` as well as ``public_bytes`` on :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, and :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. .. attribute:: PEM .. versionadded:: 0.8 For PEM format. This is a base64 format with delimiters. .. attribute:: DER .. versionadded:: 0.9 For DER format. This is a binary format. .. attribute:: OpenSSH .. versionadded:: 1.4 The format used by OpenSSH public keys. This is a text format. .. attribute:: Raw .. versionadded:: 2.5 A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a binary format and is invalid for other key types. .. attribute:: X962 .. versionadded:: 2.5 The format used by elliptic curve point encodings. This is a binary format. .. attribute:: SMIME .. versionadded:: 3.2 An output format used for PKCS7. This is a text format. Serialization Encryption Types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: KeySerializationEncryption :canonical: cryptography.hazmat.primitives._serialization.KeySerializationEncryption Objects with this interface are usable as encryption types with methods like ``private_bytes`` available on :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey` and :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. All other classes in this section represent the available choices for encryption and have this interface. .. class:: BestAvailableEncryption(password) :canonical: cryptography.hazmat.primitives._serialization.BestAvailableEncryption Encrypt using the best available encryption for a given key. This is a curated encryption choice and the algorithm may change over time. The encryption algorithm may vary based on which version of OpenSSL the library is compiled against. :param bytes password: The password to use for encryption. .. class:: NoEncryption :canonical: cryptography.hazmat.primitives._serialization.NoEncryption Do not encrypt. .. class:: KeySerializationEncryptionBuilder .. versionadded:: 38.0.0 A builder that can be used to configure how data is encrypted. To create one, call :meth:`PrivateFormat.encryption_builder`. Different serialization types will support different options on this builder. .. method:: kdf_rounds(rounds) Set the number of rounds the Key Derivation Function should use. The meaning of the number of rounds varies on the KDF being used. :param int rounds: Number of rounds. .. method:: key_cert_algorithm(algorithm) Set the encryption algorithm to use when encrypting the key and certificate in a PKCS12 structure. :param algorithm: A value from the :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PBES` enumeration. .. method:: hmac_hash(algorithm) Set the hash algorithm to use within the MAC for a PKCS12 structure. :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` .. method:: build(password) Turns the builder into an instance of :class:`KeySerializationEncryption` with a given password. :param bytes password: The password. :returns: A :class:`KeySerializationEncryption` encryption object that can be passed to methods like ``private_bytes`` or :func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates`. .. _`a bug in Firefox`: https://bugzilla.mozilla.org/show_bug.cgi?id=773111 .. _`PKCS3`: https://www.teletrust.de/fileadmin/files/oid/oid_pkcs-3v1-4.pdf .. _`SEC 1 v2.0`: https://www.secg.org/sec1-v2.pdf .. _`PROTOCOL.key`: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key .. _`PROTOCOL.certkeys`: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys cryptography-43.0.0/docs/hazmat/primitives/asymmetric/utils.rst010064400017510000177000000054511464676315000232140ustar 00000000000000.. hazmat:: Asymmetric Utilities ==================== .. currentmodule:: cryptography.hazmat.primitives.asymmetric.utils .. function:: decode_dss_signature(signature) Takes in signatures generated by the DSA/ECDSA signers and returns a tuple ``(r, s)``. These signatures are ASN.1 encoded ``Dss-Sig-Value`` sequences (as defined in :rfc:`3279`) :param bytes signature: The signature to decode. :returns: The decoded tuple ``(r, s)``. :raises ValueError: Raised if the signature is malformed. .. function:: encode_dss_signature(r, s) Creates an ASN.1 encoded ``Dss-Sig-Value`` (as defined in :rfc:`3279`) from raw ``r`` and ``s`` values. :param int r: The raw signature value ``r``. :param int s: The raw signature value ``s``. :return bytes: The encoded signature. .. class:: Prehashed(algorithm) .. versionadded:: 1.6 ``Prehashed`` can be passed as the ``algorithm`` in the RSA :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign` and :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.verify` as well as DSA :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign` and :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey.verify` methods. For elliptic curves it can be passed as the ``algorithm`` in :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` and then used with :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign` and :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.verify` . :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. .. doctest:: >>> import hashlib >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import ( ... padding, rsa, utils ... ) >>> private_key = rsa.generate_private_key( ... public_exponent=65537, ... key_size=2048, ... ) >>> prehashed_msg = hashlib.sha256(b"A message I want to sign").digest() >>> signature = private_key.sign( ... prehashed_msg, ... padding.PSS( ... mgf=padding.MGF1(hashes.SHA256()), ... salt_length=padding.PSS.MAX_LENGTH ... ), ... utils.Prehashed(hashes.SHA256()) ... ) >>> public_key = private_key.public_key() >>> public_key.verify( ... signature, ... prehashed_msg, ... padding.PSS( ... mgf=padding.MGF1(hashes.SHA256()), ... salt_length=padding.PSS.MAX_LENGTH ... ), ... utils.Prehashed(hashes.SHA256()) ... ) cryptography-43.0.0/docs/hazmat/primitives/asymmetric/x25519.rst010064400017510000177000000172661464676315000227400ustar 00000000000000.. hazmat:: X25519 key exchange =================== .. currentmodule:: cryptography.hazmat.primitives.asymmetric.x25519 X25519 is an elliptic curve `Diffie-Hellman key exchange`_ using `Curve25519`_. It allows two parties to jointly agree on a shared secret using an insecure channel. Exchange Algorithm ~~~~~~~~~~~~~~~~~~ For most applications the ``shared_key`` should be passed to a key derivation function. This allows mixing of additional information into the key, derivation of multiple keys, and destroys any structure that may be present. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF >>> # Generate a private key for use in the exchange. >>> private_key = X25519PrivateKey.generate() >>> # In a real handshake the peer_public_key will be received from the >>> # other party. For this example we'll generate another private key and >>> # get a public key from that. Note that in a DH handshake both peers >>> # must agree on a common set of parameters. >>> peer_public_key = X25519PrivateKey.generate().public_key() >>> shared_key = private_key.exchange(peer_public_key) >>> # Perform key derivation. >>> derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key) >>> # For the next handshake we MUST generate another private key. >>> private_key_2 = X25519PrivateKey.generate() >>> peer_public_key_2 = X25519PrivateKey.generate().public_key() >>> shared_key_2 = private_key_2.exchange(peer_public_key_2) >>> derived_key_2 = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key_2) Key interfaces ~~~~~~~~~~~~~~ .. class:: X25519PrivateKey .. versionadded:: 2.0 .. classmethod:: generate() Generate an X25519 private key. :returns: :class:`X25519PrivateKey` .. classmethod:: from_private_bytes(data) .. versionadded:: 2.5 A class method for loading an X25519 key encoded as :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`. :param bytes data: 32 byte private key. :returns: :class:`X25519PrivateKey` .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import x25519 >>> private_key = x25519.X25519PrivateKey.generate() >>> private_bytes = private_key.private_bytes( ... encoding=serialization.Encoding.Raw, ... format=serialization.PrivateFormat.Raw, ... encryption_algorithm=serialization.NoEncryption() ... ) >>> loaded_private_key = x25519.X25519PrivateKey.from_private_bytes(private_bytes) .. method:: public_key() :returns: :class:`X25519PublicKey` .. method:: exchange(peer_public_key) :param X25519PublicKey peer_public_key: The public key for the peer. :returns bytes: A shared key. .. method:: private_bytes(encoding, format, encryption_algorithm) .. versionadded:: 2.5 Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8` or :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` , otherwise it must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`. :param encryption_algorithm: An instance of an object conforming to the :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` interface. :return bytes: Serialized key. .. method:: private_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`private_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding, :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` format, and :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. :return bytes: Raw key. .. class:: X25519PublicKey .. versionadded:: 2.0 .. classmethod:: from_public_bytes(data) :param bytes data: 32 byte public key. :returns: :class:`X25519PublicKey` .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import x25519 >>> private_key = x25519.X25519PrivateKey.generate() >>> public_key = private_key.public_key() >>> public_bytes = public_key.public_bytes( ... encoding=serialization.Encoding.Raw, ... format=serialization.PublicFormat.Raw ... ) >>> loaded_public_key = x25519.X25519PublicKey.from_public_bytes(public_bytes) .. method:: public_bytes(encoding, format) Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo` or :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` , otherwise it must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`. :returns bytes: The public key bytes. .. method:: public_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`public_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding and :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` format. :return bytes: Raw key. .. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange .. _`Curve25519`: https://en.wikipedia.org/wiki/Curve25519 cryptography-43.0.0/docs/hazmat/primitives/asymmetric/x448.rst010064400017510000177000000170351464676315000225640ustar 00000000000000.. hazmat:: X448 key exchange =================== .. currentmodule:: cryptography.hazmat.primitives.asymmetric.x448 X448 is an elliptic curve `Diffie-Hellman key exchange`_ using `Curve448`_. It allows two parties to jointly agree on a shared secret using an insecure channel. Exchange Algorithm ~~~~~~~~~~~~~~~~~~ For most applications the ``shared_key`` should be passed to a key derivation function. This allows mixing of additional information into the key, derivation of multiple keys, and destroys any structure that may be present. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF >>> # Generate a private key for use in the exchange. >>> private_key = X448PrivateKey.generate() >>> # In a real handshake the peer_public_key will be received from the >>> # other party. For this example we'll generate another private key and >>> # get a public key from that. Note that in a DH handshake both peers >>> # must agree on a common set of parameters. >>> peer_public_key = X448PrivateKey.generate().public_key() >>> shared_key = private_key.exchange(peer_public_key) >>> # Perform key derivation. >>> derived_key = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key) >>> # For the next handshake we MUST generate another private key. >>> private_key_2 = X448PrivateKey.generate() >>> peer_public_key_2 = X448PrivateKey.generate().public_key() >>> shared_key_2 = private_key_2.exchange(peer_public_key_2) >>> derived_key_2 = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=None, ... info=b'handshake data', ... ).derive(shared_key_2) Key interfaces ~~~~~~~~~~~~~~ .. class:: X448PrivateKey .. versionadded:: 2.5 .. classmethod:: generate() Generate an X448 private key. :returns: :class:`X448PrivateKey` .. classmethod:: from_private_bytes(data) :param data: 56 byte private key. :type data: :term:`bytes-like` :returns: :class:`X448PrivateKey` .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import x448 >>> private_key = x448.X448PrivateKey.generate() >>> private_bytes = private_key.private_bytes( ... encoding=serialization.Encoding.Raw, ... format=serialization.PrivateFormat.Raw, ... encryption_algorithm=serialization.NoEncryption() ... ) >>> loaded_private_key = x448.X448PrivateKey.from_private_bytes(private_bytes) .. method:: public_key() :returns: :class:`X448PublicKey` .. method:: exchange(peer_public_key) :param X448PublicKey peer_public_key: The public key for the peer. :returns bytes: A shared key. .. method:: private_bytes(encoding, format, encryption_algorithm) Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8` or :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` , otherwise it must be :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`. :param encryption_algorithm: An instance of an object conforming to the :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` interface. :return bytes: Serialized key. .. method:: private_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`private_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding, :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` format, and :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. :return bytes: Raw key. .. class:: X448PublicKey .. versionadded:: 2.5 .. classmethod:: from_public_bytes(data) :param bytes data: 56 byte public key. :returns: :class:`X448PublicKey` .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import x448 >>> private_key = x448.X448PrivateKey.generate() >>> public_key = private_key.public_key() >>> public_bytes = public_key.public_bytes( ... encoding=serialization.Encoding.Raw, ... format=serialization.PublicFormat.Raw ... ) >>> loaded_public_key = x448.X448PublicKey.from_public_bytes(public_bytes) .. method:: public_bytes(encoding, format) Allows serialization of the key to bytes. Encoding ( :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and format ( :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo` or :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` ) 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. If the ``encoding`` is :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` then ``format`` must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` , otherwise it must be :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`. :returns bytes: The public key bytes. .. method:: public_bytes_raw() .. versionadded:: 40 Allows serialization of the key to raw bytes. This method is a convenience shortcut for calling :meth:`public_bytes` with :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` encoding and :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` format. :return bytes: Raw key. .. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange .. _`Curve448`: https://en.wikipedia.org/wiki/Curve448 cryptography-43.0.0/docs/hazmat/primitives/constant-time.rst010064400017510000177000000030701464676315000224570ustar 00000000000000.. hazmat:: Constant time functions ======================= .. currentmodule:: cryptography.hazmat.primitives.constant_time This module contains functions for operating with secret data in a way that does not leak information about that data through how long it takes to perform the operation. These functions should be used whenever operating on secret data along with data that is user supplied. An example would be comparing a HMAC signature received from a client to the one generated by the server code for authentication purposes. For more information about this sort of issue, see `Coda Hale's blog post`_ about the timing attacks on KeyCzar and Java's ``MessageDigest.isEqual()``. .. function:: bytes_eq(a, b) Compares ``a`` and ``b`` with one another. If ``a`` and ``b`` have different lengths, this returns ``False`` immediately. Otherwise it compares them in a way that takes the same amount of time, regardless of how many characters are the same between the two. .. doctest:: >>> from cryptography.hazmat.primitives import constant_time >>> constant_time.bytes_eq(b"foo", b"foo") True >>> constant_time.bytes_eq(b"foo", b"bar") False :param bytes a: The left-hand side. :param bytes b: The right-hand side. :returns bool: ``True`` if ``a`` has the same bytes as ``b``, otherwise ``False``. :raises TypeError: This exception is raised if ``a`` or ``b`` is not ``bytes``. .. _`Coda Hale's blog post`: https://codahale.com/a-lesson-in-timing-attacks/ cryptography-43.0.0/docs/hazmat/primitives/cryptographic-hashes.rst010064400017510000177000000222751464676315000240310ustar 00000000000000.. hazmat:: Message digests (Hashing) ========================= .. module:: cryptography.hazmat.primitives.hashes .. class:: Hash(algorithm) A cryptographic hash function takes an arbitrary block of data and calculates a fixed-size bit string (a digest), such that different data results (with a high probability) in different digests. This is an implementation of :class:`~cryptography.hazmat.primitives.hashes.HashContext` meant to be used with :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` implementations to provide an incremental interface to calculating various message digests. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> digest = hashes.Hash(hashes.SHA256()) >>> digest.update(b"abc") >>> digest.update(b"123") >>> digest.finalize() b'l\xa1=R\xcap\xc8\x83\xe0\xf0\xbb\x10\x1eBZ\x89\xe8bM\xe5\x1d\xb2\xd29%\x93\xafj\x84\x11\x80\x90' Keep in mind that attacks against cryptographic hashes only get stronger with time, and that often algorithms that were once thought to be strong, become broken. Because of this it's important to include a plan for upgrading the hash algorithm you use over time. For more information, see `Lifetimes of cryptographic hash functions`_. :param algorithm: A :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance such as those described in :ref:`below `. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the provided ``algorithm`` is unsupported. .. method:: update(data) :param bytes data: The bytes to be hashed. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: copy() Copy this :class:`Hash` instance, usually so that you may call :meth:`finalize` to get an intermediate digest value while we continue to call :meth:`update` on the original instance. :return: A new instance of :class:`Hash` that can be updated and finalized independently of the original instance. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. .. method:: finalize() Finalize the current context and return the message digest as bytes. After ``finalize`` has been called this object can no longer be used and :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise an :class:`~cryptography.exceptions.AlreadyFinalized` exception. :return bytes: The message digest as bytes. .. _cryptographic-hash-algorithms: SHA-2 family ~~~~~~~~~~~~ .. class:: SHA224() SHA-224 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a 224-bit message digest. .. class:: SHA256() SHA-256 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a 256-bit message digest. .. class:: SHA384() SHA-384 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a 384-bit message digest. .. class:: SHA512() SHA-512 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a 512-bit message digest. .. class:: SHA512_224() .. versionadded:: 2.5 SHA-512/224 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a 224-bit message digest. .. class:: SHA512_256() .. versionadded:: 2.5 SHA-512/256 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a 256-bit message digest. BLAKE2 ~~~~~~ `BLAKE2`_ is a cryptographic hash function specified in :rfc:`7693`. BLAKE2's design makes it immune to `length-extension attacks`_, an advantage over the SHA-family of hashes. .. note:: While the RFC specifies keying, personalization, and salting features, these are not supported at this time due to limitations in OpenSSL. .. class:: BLAKE2b(digest_size) BLAKE2b is optimized for 64-bit platforms and produces an 1 to 64-byte message digest. :param int digest_size: The desired size of the hash output in bytes. Only ``64`` is supported at this time. :raises ValueError: If the ``digest_size`` is invalid. .. class:: BLAKE2s(digest_size) BLAKE2s is optimized for 8 to 32-bit platforms and produces a 1 to 32-byte message digest. :param int digest_size: The desired size of the hash output in bytes. Only ``32`` is supported at this time. :raises ValueError: If the ``digest_size`` is invalid. SHA-3 family ~~~~~~~~~~~~ SHA-3 is the most recent NIST secure hash algorithm standard. Despite the larger number SHA-3 is not considered to be better than SHA-2. Instead, it uses a significantly different internal structure so that **if** an attack appears against SHA-2 it is unlikely to apply to SHA-3. SHA-3 is significantly slower than SHA-2 so at this time most users should choose SHA-2. .. class:: SHA3_224() .. versionadded:: 2.5 SHA3/224 is a cryptographic hash function from the SHA-3 family and is standardized by NIST. It produces a 224-bit message digest. .. class:: SHA3_256() .. versionadded:: 2.5 SHA3/256 is a cryptographic hash function from the SHA-3 family and is standardized by NIST. It produces a 256-bit message digest. .. class:: SHA3_384() .. versionadded:: 2.5 SHA3/384 is a cryptographic hash function from the SHA-3 family and is standardized by NIST. It produces a 384-bit message digest. .. class:: SHA3_512() .. versionadded:: 2.5 SHA3/512 is a cryptographic hash function from the SHA-3 family and is standardized by NIST. It produces a 512-bit message digest. .. class:: SHAKE128(digest_size) .. versionadded:: 2.5 SHAKE128 is an extendable output function (XOF) based on the same core permutations as SHA3. It allows the caller to obtain an arbitrarily long digest length. Longer lengths, however, do not increase security or collision resistance and lengths shorter than 128 bit (16 bytes) will decrease it. :param int digest_size: The length of output desired. Must be greater than zero. :raises ValueError: If the ``digest_size`` is invalid. .. class:: SHAKE256(digest_size) .. versionadded:: 2.5 SHAKE256 is an extendable output function (XOF) based on the same core permutations as SHA3. It allows the caller to obtain an arbitrarily long digest length. Longer lengths, however, do not increase security or collision resistance and lengths shorter than 256 bit (32 bytes) will decrease it. :param int digest_size: The length of output desired. Must be greater than zero. :raises ValueError: If the ``digest_size`` is invalid. SHA-1 ~~~~~ .. warning:: SHA-1 is a deprecated hash algorithm that has practical known collision attacks. You are strongly discouraged from using it. Existing applications should strongly consider moving away. .. class:: SHA1() SHA-1 is a cryptographic hash function standardized by NIST. It produces an 160-bit message digest. Cryptanalysis of SHA-1 has demonstrated that it is vulnerable to practical collision attacks, and collisions have been demonstrated. MD5 ~~~ .. warning:: MD5 is a deprecated hash algorithm that has practical known collision attacks. You are strongly discouraged from using it. Existing applications should strongly consider moving away. .. class:: MD5() MD5 is a deprecated cryptographic hash function. It produces a 128-bit message digest and has practical known collision attacks. SM3 ~~~ .. class:: SM3() .. versionadded:: 35.0.0 SM3 is a cryptographic hash function standardized by the Chinese National Cryptography Administration in `GM/T 0004-2012`_. It produces 256-bit message digests. (An English description is available at `draft-sca-cfrg-sm3`_.) This hash should be used for compatibility purposes where required and is not otherwise recommended for use. Interfaces ~~~~~~~~~~ .. class:: HashAlgorithm .. attribute:: name :type: str The standard name for the hash algorithm, for example: ``"sha256"`` or ``"blake2b"``. .. attribute:: digest_size :type: int The size of the resulting digest in bytes. .. class:: HashContext .. attribute:: algorithm A :class:`HashAlgorithm` that will be used by this context. .. method:: update(data) :param bytes data: The data you want to hash. .. method:: finalize() :return: The final digest as bytes. .. method:: copy() :return: A :class:`HashContext` that is a copy of the current context. .. _`Lifetimes of cryptographic hash functions`: https://valerieaurora.org/hash.html .. _`BLAKE2`: https://www.blake2.net/ .. _`length-extension attacks`: https://en.wikipedia.org/wiki/Length_extension_attack .. _`GM/T 0004-2012`: https://www.oscca.gov.cn/sca/xxgk/2010-12/17/1002389/files/302a3ada057c4a73830536d03e683110.pdf .. _`draft-sca-cfrg-sm3`: https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3 cryptography-43.0.0/docs/hazmat/primitives/index.rst010064400017510000177000000003661464676315000210060ustar 00000000000000.. hazmat:: Primitives ========== .. toctree:: :maxdepth: 2 aead asymmetric/index constant-time key-derivation-functions keywrap mac/index cryptographic-hashes symmetric-encryption padding twofactor cryptography-43.0.0/docs/hazmat/primitives/key-derivation-functions.rst010064400017510000177000001263671464676315000246510ustar 00000000000000.. hazmat:: Key derivation functions ======================== .. module:: cryptography.hazmat.primitives.kdf Key derivation functions derive bytes suitable for cryptographic operations from passwords or other data sources using a pseudo-random function (PRF). Different KDFs are suitable for different tasks such as: * Cryptographic key derivation Deriving a key suitable for use as input to an encryption algorithm. Typically this means taking a password and running it through an algorithm such as :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC` or :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. This process is typically known as `key stretching`_. * Password storage When storing passwords you want to use an algorithm that is computationally intensive. Legitimate users will only need to compute it once (for example, taking the user's password, running it through the KDF, then comparing it to the stored value), while attackers will need to do it billions of times. Ideal password storage KDFs will be demanding on both computational and memory resources. Variable cost algorithms ~~~~~~~~~~~~~~~~~~~~~~~~ PBKDF2 ------ .. currentmodule:: cryptography.hazmat.primitives.kdf.pbkdf2 .. class:: PBKDF2HMAC(algorithm, length, salt, iterations) .. versionadded:: 0.2 `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for deriving a cryptographic key from a password. It may also be used for key storage, but an alternate key storage KDF such as :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt` is generally considered a better solution. This class conforms to the :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` interface. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC >>> # Salts should be randomly generated >>> salt = os.urandom(16) >>> # derive >>> kdf = PBKDF2HMAC( ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, ... iterations=480000, ... ) >>> key = kdf.derive(b"my great password") >>> # verify >>> kdf = PBKDF2HMAC( ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, ... iterations=480000, ... ) >>> kdf.verify(b"my great password", key) :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param int length: The desired length of the derived key in bytes. Maximum is (2\ :sup:`32` - 1) * ``algorithm.digest_size``. :param bytes salt: A salt. Secure values [#nist]_ are 128-bits (16 bytes) or longer and randomly generated. :param int iterations: The number of iterations to perform of the hash function. This can be used to control the length of time the operation takes. Higher numbers help mitigate brute force attacks against derived keys. A `more detailed description`_ can be consulted for additional information. :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. .. method:: derive(key_material) :param key_material: The input key material. For PBKDF2 this should be a password. :type key_material: :term:`bytes-like` :return bytes: the derived key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. This generates and returns a new key from the supplied password. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. This can be used for checking whether the password a user provides matches the stored derived key. Scrypt ------ .. currentmodule:: cryptography.hazmat.primitives.kdf.scrypt .. class:: Scrypt(salt, length, n, r, p) .. versionadded:: 1.6 Scrypt is a KDF designed for password storage by Colin Percival to be resistant against hardware-assisted attackers by having a tunable memory cost. It is described in :rfc:`7914`. This class conforms to the :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` interface. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.kdf.scrypt import Scrypt >>> salt = os.urandom(16) >>> # derive >>> kdf = Scrypt( ... salt=salt, ... length=32, ... n=2**14, ... r=8, ... p=1, ... ) >>> key = kdf.derive(b"my great password") >>> # verify >>> kdf = Scrypt( ... salt=salt, ... length=32, ... n=2**14, ... r=8, ... p=1, ... ) >>> kdf.verify(b"my great password", key) :param bytes salt: A salt. :param int length: The desired length of the derived key in bytes. :param int n: CPU/Memory cost parameter. It must be larger than 1 and be a power of 2. :param int r: Block size parameter. :param int p: Parallelization parameter. The computational and memory cost of Scrypt can be adjusted by manipulating the 3 parameters: ``n``, ``r``, and ``p``. In general, the memory cost of Scrypt is affected by the values of both ``n`` and ``r``, while ``n`` also determines the number of iterations performed. ``p`` increases the computational cost without affecting memory usage. A more in-depth explanation of the 3 parameters can be found `here`_. :rfc:`7914` `recommends`_ values of ``r=8`` and ``p=1`` while scaling ``n`` to a number appropriate for your system. `The scrypt paper`_ suggests a minimum value of ``n=2**14`` for interactive logins (t < 100ms), or ``n=2**20`` for more sensitive files (t < 5s). :raises cryptography.exceptions.UnsupportedAlgorithm: If Scrypt is not supported by the OpenSSL version ``cryptography`` is using. :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. :raises ValueError: This exception is raised if ``n`` is less than 2, if ``n`` is not a power of 2, if ``r`` is less than 1 or if ``p`` is less than 1. .. method:: derive(key_material) :param key_material: The input key material. :type key_material: :term:`bytes-like` :return bytes: the derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This generates and returns a new key from the supplied password. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. This can be used for checking whether the password a user provides matches the stored derived key. Fixed cost algorithms ~~~~~~~~~~~~~~~~~~~~~ ConcatKDF --------- .. currentmodule:: cryptography.hazmat.primitives.kdf.concatkdf .. class:: ConcatKDFHash(algorithm, length, otherinfo) .. versionadded:: 1.0 ConcatKDFHash (Concatenation Key Derivation Function) is defined by the NIST Special Publication `NIST SP 800-56Ar3`_ document, to be used to derive keys for use after a Key Exchange negotiation operation. .. warning:: ConcatKDFHash should not be used for password storage. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash >>> otherinfo = b"concatkdf-example" >>> ckdf = ConcatKDFHash( ... algorithm=hashes.SHA256(), ... length=32, ... otherinfo=otherinfo, ... ) >>> key = ckdf.derive(b"input key") >>> ckdf = ConcatKDFHash( ... algorithm=hashes.SHA256(), ... length=32, ... otherinfo=otherinfo, ... ) >>> ckdf.verify(b"input key", key) :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param int length: The desired length of the derived key in bytes. Maximum is ``hashlen * (2^32 -1)``. :param bytes otherinfo: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. :raises TypeError: This exception is raised if ``otherinfo`` is not ``bytes``. .. method:: derive(key_material) :param key_material: The input key material. :type key_material: :term:`bytes-like` :return bytes: The derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. Derives a new key from the input key material. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. .. class:: ConcatKDFHMAC(algorithm, length, salt, otherinfo) .. versionadded:: 1.0 Similar to ConcatKFDHash but uses an HMAC function instead. .. warning:: ConcatKDFHMAC should not be used for password storage. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHMAC >>> salt = os.urandom(16) >>> otherinfo = b"concatkdf-example" >>> ckdf = ConcatKDFHMAC( ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, ... otherinfo=otherinfo, ... ) >>> key = ckdf.derive(b"input key") >>> ckdf = ConcatKDFHMAC( ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, ... otherinfo=otherinfo, ... ) >>> ckdf.verify(b"input key", key) :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param int length: The desired length of the derived key in bytes. Maximum is ``hashlen * (2^32 -1)``. :param bytes salt: A salt. Randomizes the KDF's output. Optional, but highly recommended. Ideally as many bits of entropy as the security level of the hash: often that means cryptographically random and as long as the hash output. Does not have to be secret, but may cause stronger security guarantees if secret; If ``None`` is explicitly passed a default salt of ``algorithm.block_size`` null bytes will be used. :param bytes otherinfo: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. :raises TypeError: This exception is raised if ``salt`` or ``otherinfo`` is not ``bytes``. .. method:: derive(key_material) :param bytes key_material: The input key material. :return bytes: The derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. Derives a new key from the input key material. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. HKDF ---- .. currentmodule:: cryptography.hazmat.primitives.kdf.hkdf .. class:: HKDF(algorithm, length, salt, info) .. versionadded:: 0.2 `HKDF`_ (HMAC-based Extract-and-Expand Key Derivation Function) is suitable for deriving keys of a fixed size used for other cryptographic operations. .. warning:: HKDF should not be used for password storage. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF >>> salt = os.urandom(16) >>> info = b"hkdf-example" >>> hkdf = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, ... info=info, ... ) >>> key = hkdf.derive(b"input key") >>> hkdf = HKDF( ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, ... info=info, ... ) >>> hkdf.verify(b"input key", key) :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param int length: The desired length of the derived key in bytes. Maximum is ``255 * (algorithm.digest_size // 8)``. :param bytes salt: A salt. Randomizes the KDF's output. Optional, but highly recommended. Ideally as many bits of entropy as the security level of the hash: often that means cryptographically random and as long as the hash output. Worse (shorter, less entropy) salt values can still meaningfully contribute to security. May be reused. Does not have to be secret, but may cause stronger security guarantees if secret; see :rfc:`5869` and the `HKDF paper`_ for more details. If ``None`` is explicitly passed a default salt of ``algorithm.digest_size // 8`` null bytes will be used. See `understanding HKDF`_ for additional detail about the salt and info parameters. :param bytes info: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. :raises TypeError: This exception is raised if ``salt`` or ``info`` is not ``bytes``. .. method:: derive(key_material) :param key_material: The input key material. :type key_material: :term:`bytes-like` :return bytes: The derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. Derives a new key from the input key material by performing both the extract and expand operations. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. .. class:: HKDFExpand(algorithm, length, info) .. versionadded:: 0.5 HKDF consists of two stages, extract and expand. This class exposes an expand only version of HKDF that is suitable when the key material is already cryptographically strong. .. warning:: HKDFExpand should only be used if the key material is cryptographically strong. You should use :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF` if you are unsure. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand >>> info = b"hkdf-example" >>> key_material = os.urandom(16) >>> hkdf = HKDFExpand( ... algorithm=hashes.SHA256(), ... length=32, ... info=info, ... ) >>> key = hkdf.derive(key_material) >>> hkdf = HKDFExpand( ... algorithm=hashes.SHA256(), ... length=32, ... info=info, ... ) >>> hkdf.verify(key_material, key) :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param int length: The desired length of the derived key in bytes. Maximum is ``255 * (algorithm.digest_size // 8)``. :param bytes info: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. :raises TypeError: This exception is raised if ``info`` is not ``bytes``. .. method:: derive(key_material) :param bytes key_material: The input key material. :return bytes: The derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. Derives a new key from the input key material by performing both the extract and expand operations. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. :raises TypeError: This is raised if the provided ``key_material`` is a ``unicode`` object This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. KBKDF ----- .. currentmodule:: cryptography.hazmat.primitives.kdf.kbkdf .. class:: KBKDFHMAC(algorithm, mode, length, rlen, llen, location,\ label, context, fixed) .. versionadded:: 1.4 KBKDF (Key Based Key Derivation Function) is defined by the `NIST SP 800-108`_ document, to be used to derive additional keys from a key that has been established through an automated key-establishment scheme. .. warning:: KBKDFHMAC should not be used for password storage. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.kbkdf import ( ... CounterLocation, KBKDFHMAC, Mode ... ) >>> label = b"KBKDF HMAC Label" >>> context = b"KBKDF HMAC Context" >>> kdf = KBKDFHMAC( ... algorithm=hashes.SHA256(), ... mode=Mode.CounterMode, ... length=32, ... rlen=4, ... llen=4, ... location=CounterLocation.BeforeFixed, ... label=label, ... context=context, ... fixed=None, ... ) >>> key = kdf.derive(b"input key") >>> kdf = KBKDFHMAC( ... algorithm=hashes.SHA256(), ... mode=Mode.CounterMode, ... length=32, ... rlen=4, ... llen=4, ... location=CounterLocation.BeforeFixed, ... label=label, ... context=context, ... fixed=None, ... ) >>> kdf.verify(b"input key", key) :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param mode: The desired mode of the PRF. A value from the :class:`~cryptography.hazmat.primitives.kdf.kbkdf.Mode` enum. :param int length: The desired length of the derived key in bytes. :param int rlen: An integer that indicates the length of the binary representation of the counter in bytes. :param int llen: An integer that indicates the binary representation of the ``length`` in bytes. :param location: The desired location of the counter. A value from the :class:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation` enum. :param bytes label: Application specific label information. If ``None`` is explicitly passed an empty byte string will be used. :param bytes context: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. :param bytes fixed: Instead of specifying ``label`` and ``context`` you may supply your own fixed data. If ``fixed`` is specified, ``label`` and ``context`` is ignored. :param int break_location: A keyword-only argument. An integer that indicates the bytes offset where counter bytes are to be located. Required when ``location`` is :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. :raises TypeError: This exception is raised if ``label`` or ``context`` is not ``bytes``. Also raised if ``rlen``, ``llen``, or ``break_location`` is not ``int``. :raises ValueError: This exception is raised if ``rlen`` or ``llen`` is greater than 4 or less than 1. This exception is also raised if you specify a ``label`` or ``context`` and ``fixed``. This exception is also raised if you specify ``break_location`` and ``location`` is not :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. .. method:: derive(key_material) :param key_material: The input key material. :type key_material: :term:`bytes-like` :return bytes: The derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. Derives a new key from the input key material. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. .. class:: KBKDFCMAC(algorithm, mode, length, rlen, llen, location,\ label, context, fixed) .. versionadded:: 35.0.0 KBKDF (Key Based Key Derivation Function) is defined by the `NIST SP 800-108`_ document, to be used to derive additional keys from a key that has been established through an automated key-establishment scheme. .. warning:: KBKDFCMAC should not be used for password storage. .. doctest:: >>> from cryptography.hazmat.primitives.ciphers import algorithms >>> from cryptography.hazmat.primitives.kdf.kbkdf import ( ... CounterLocation, KBKDFCMAC, Mode ... ) >>> label = b"KBKDF CMAC Label" >>> context = b"KBKDF CMAC Context" >>> kdf = KBKDFCMAC( ... algorithm=algorithms.AES, ... mode=Mode.CounterMode, ... length=32, ... rlen=4, ... llen=4, ... location=CounterLocation.BeforeFixed, ... label=label, ... context=context, ... fixed=None, ... ) >>> key = kdf.derive(b"32 bytes long input key material") >>> kdf = KBKDFCMAC( ... algorithm=algorithms.AES, ... mode=Mode.CounterMode, ... length=32, ... rlen=4, ... llen=4, ... location=CounterLocation.BeforeFixed, ... label=label, ... context=context, ... fixed=None, ... ) >>> kdf.verify(b"32 bytes long input key material", key) :param algorithm: A class implementing a block cipher algorithm being a subclass of :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm` and :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. :param mode: The desired mode of the PRF. A value from the :class:`~cryptography.hazmat.primitives.kdf.kbkdf.Mode` enum. :param int length: The desired length of the derived key in bytes. :param int rlen: An integer that indicates the length of the binary representation of the counter in bytes. :param int llen: An integer that indicates the binary representation of the ``length`` in bytes. :param location: The desired location of the counter. A value from the :class:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation` enum. :param bytes label: Application specific label information. If ``None`` is explicitly passed an empty byte string will be used. :param bytes context: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. :param bytes fixed: Instead of specifying ``label`` and ``context`` you may supply your own fixed data. If ``fixed`` is specified, ``label`` and ``context`` is ignored. :param int break_location: A keyword-only argument. An integer that indicates the bytes offset where counter bytes are to be located. Required when ``location`` is :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if ``algorithm`` is not a subclass of :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm` and :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. :raises TypeError: This exception is raised if ``label`` or ``context`` is not ``bytes``, ``rlen``, ``llen``, or ``break_location`` is not ``int``, ``mode`` is not :class:`~cryptography.hazmat.primitives.kdf.kbkdf.Mode` or ``location`` is not :class:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation`. :raises ValueError: This exception is raised if ``rlen`` or ``llen`` is greater than 4 or less than 1. This exception is also raised if you specify a ``label`` or ``context`` and ``fixed``. This exception is also raised if you specify ``break_location`` and ``location`` is not :attr:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation.MiddleFixed`. .. method:: derive(key_material) :param key_material: The input key material. :type key_material: :term:`bytes-like` :return bytes: The derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises ValueError: This exception is raised if ``key_material`` is not a valid key for ``algorithm`` passed to :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFCMAC` constructor. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. Derives a new key from the input key material. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises: Exceptions raised by :meth:`derive`. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. .. class:: Mode An enumeration for the key based key derivative modes. .. attribute:: CounterMode The output of the PRF is computed with a counter as the iteration variable. .. class:: CounterLocation An enumeration for the key based key derivative counter location. .. attribute:: BeforeFixed The counter iteration variable will be concatenated before the fixed input data. .. attribute:: AfterFixed The counter iteration variable will be concatenated after the fixed input data. .. attribute:: MiddleFixed .. versionadded:: 38.0.0 The counter iteration variable will be concatenated in the middle of the fixed input data. X963KDF ------- .. currentmodule:: cryptography.hazmat.primitives.kdf.x963kdf .. class:: X963KDF(algorithm, length, otherinfo) .. versionadded:: 1.1 X963KDF (ANSI X9.63 Key Derivation Function) is defined by ANSI in the `ANSI X9.63:2001`_ document, to be used to derive keys for use after a Key Exchange negotiation operation. SECG in `SEC 1 v2.0`_ recommends that :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHash` be used for new projects. This KDF should only be used for backwards compatibility with pre-existing protocols. .. warning:: X963KDF should not be used for password storage. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF >>> sharedinfo = b"ANSI X9.63 Example" >>> xkdf = X963KDF( ... algorithm=hashes.SHA256(), ... length=32, ... sharedinfo=sharedinfo, ... ) >>> key = xkdf.derive(b"input key") >>> xkdf = X963KDF( ... algorithm=hashes.SHA256(), ... length=32, ... sharedinfo=sharedinfo, ... ) >>> xkdf.verify(b"input key", key) :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. :param int length: The desired length of the derived key in bytes. Maximum is ``hashlen * (2^32 -1)``. :param bytes sharedinfo: Application specific context information. If ``None`` is explicitly passed an empty byte string will be used. :raises TypeError: This exception is raised if ``sharedinfo`` is not ``bytes``. .. method:: derive(key_material) :param key_material: The input key material. :type key_material: :term:`bytes-like` :return bytes: The derived key. :raises TypeError: This exception is raised if ``key_material`` is not ``bytes``. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. Derives a new key from the input key material. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. Interface ~~~~~~~~~ .. currentmodule:: cryptography.hazmat.primitives.kdf .. class:: KeyDerivationFunction .. versionadded:: 0.2 .. method:: derive(key_material) :param bytes key_material: The input key material. Depending on what key derivation function you are using this could be either random bytes, or a user supplied password. :return: The new key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This generates and returns a new key from the supplied key material. .. method:: verify(key_material, expected_key) :param bytes key_material: The input key material. This is the same as ``key_material`` in :meth:`derive`. :param bytes expected_key: The expected result of deriving a new key, this is the same as the return value of :meth:`derive`. :raises cryptography.exceptions.InvalidKey: This is raised when the derived key does not match the expected key. :raises cryptography.exceptions.AlreadyFinalized: This is raised when :meth:`derive` or :meth:`verify` is called more than once. This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and raises an exception if they do not match. This can be used for something like checking whether a user's password attempt matches the stored derived key. .. [#nist] See `NIST SP 800-132`_. .. _`NIST SP 800-132`: https://csrc.nist.gov/pubs/sp/800/132/final .. _`NIST SP 800-108`: https://csrc.nist.gov/pubs/sp/800/108/r1/final .. _`NIST SP 800-56Ar3`: https://csrc.nist.gov/pubs/sp/800/56/a/r3/final .. _`ANSI X9.63:2001`: https://webstore.ansi.org .. _`SEC 1 v2.0`: https://www.secg.org/sec1-v2.pdf .. _`more detailed description`: https://security.stackexchange.com/a/3993/43116 .. _`PBKDF2`: https://en.wikipedia.org/wiki/PBKDF2 .. _`key stretching`: https://en.wikipedia.org/wiki/Key_stretching .. _`HKDF`: https://en.wikipedia.org/wiki/HKDF .. _`HKDF paper`: https://eprint.iacr.org/2010/264 .. _`here`: https://stackoverflow.com/a/30308723/1170681 .. _`recommends`: https://datatracker.ietf.org/doc/html/rfc7914#section-2 .. _`The scrypt paper`: https://www.tarsnap.com/scrypt/scrypt.pdf .. _`understanding HKDF`: https://soatok.blog/2021/11/17/understanding-hkdf/ cryptography-43.0.0/docs/hazmat/primitives/keywrap.rst010064400017510000177000000042601464676315000213560ustar 00000000000000.. hazmat:: .. module:: cryptography.hazmat.primitives.keywrap Key wrapping ============ Key wrapping is a cryptographic construct that uses symmetric encryption to encapsulate key material. Key wrapping algorithms are occasionally utilized to protect keys at rest or transmit them over insecure networks. Many of the protections offered by key wrapping are also offered by using authenticated :doc:`symmetric encryption `. .. function:: aes_key_wrap(wrapping_key, key_to_wrap) .. versionadded:: 1.1 This function performs AES key wrap (without padding) as specified in :rfc:`3394`. :param bytes wrapping_key: The wrapping key. :param bytes key_to_wrap: The key to wrap. :return bytes: The wrapped key as bytes. .. function:: aes_key_unwrap(wrapping_key, wrapped_key) .. versionadded:: 1.1 This function performs AES key unwrap (without padding) as specified in :rfc:`3394`. :param bytes wrapping_key: The wrapping key. :param bytes wrapped_key: The wrapped key. :return bytes: The unwrapped key as bytes. :raises cryptography.hazmat.primitives.keywrap.InvalidUnwrap: This is raised if the key is not successfully unwrapped. .. function:: aes_key_wrap_with_padding(wrapping_key, key_to_wrap) .. versionadded:: 2.2 This function performs AES key wrap with padding as specified in :rfc:`5649`. :param bytes wrapping_key: The wrapping key. :param bytes key_to_wrap: The key to wrap. :return bytes: The wrapped key as bytes. .. function:: aes_key_unwrap_with_padding(wrapping_key, wrapped_key) .. versionadded:: 2.2 This function performs AES key unwrap with padding as specified in :rfc:`5649`. :param bytes wrapping_key: The wrapping key. :param bytes wrapped_key: The wrapped key. :return bytes: The unwrapped key as bytes. :raises cryptography.hazmat.primitives.keywrap.InvalidUnwrap: This is raised if the key is not successfully unwrapped. Exceptions ~~~~~~~~~~ .. class:: InvalidUnwrap This is raised when a wrapped key fails to unwrap. It can be caused by a corrupted or invalid wrapped key or an invalid wrapping key. cryptography-43.0.0/docs/hazmat/primitives/mac/cmac.rst010064400017510000177000000075141464676315000213440ustar 00000000000000.. hazmat:: Cipher-based message authentication code (CMAC) =============================================== .. currentmodule:: cryptography.hazmat.primitives.cmac .. testsetup:: import binascii key = binascii.unhexlify(b"0" * 32) `Cipher-based message authentication codes`_ (or CMACs) are a tool for calculating message authentication codes using a block cipher coupled with a secret key. You can use an CMAC to verify both the integrity and authenticity of a message. A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. .. class:: CMAC(algorithm) .. versionadded:: 0.4 CMAC objects take a :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` instance. .. doctest:: >>> from cryptography.hazmat.primitives import cmac >>> from cryptography.hazmat.primitives.ciphers import algorithms >>> c = cmac.CMAC(algorithms.AES(key)) >>> c.update(b"message to authenticate") >>> c.finalize() b'CT\x1d\xc8\x0e\x15\xbe4e\xdb\xb6\x84\xca\xd9Xk' If ``algorithm`` isn't a :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` instance then ``TypeError`` will be raised. To check that a given signature is correct use the :meth:`verify` method. You will receive an exception if the signature is wrong: .. doctest:: >>> c = cmac.CMAC(algorithms.AES(key)) >>> c.update(b"message to authenticate") >>> c.verify(b"an incorrect signature") Traceback (most recent call last): ... cryptography.exceptions.InvalidSignature: Signature did not match digest. :param algorithm: An instance of :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. :raises TypeError: This is raised if the provided ``algorithm`` is not an instance of :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the provided ``algorithm`` is unsupported. .. method:: update(data) :param bytes data: The bytes to hash and authenticate. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: copy() Copy this :class:`CMAC` instance, usually so that we may call :meth:`finalize` to get an intermediate value while we continue to call :meth:`update` on the original instance. :return: A new instance of :class:`CMAC` that can be updated and finalized independently of the original instance. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` .. method:: verify(signature) Finalize the current context and securely compare the MAC to ``signature``. :param bytes signature: The bytes to compare the current CMAC against. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises cryptography.exceptions.InvalidSignature: If signature does not match digest :raises TypeError: This exception is raised if ``signature`` is not ``bytes``. .. method:: finalize() Finalize the current context and return the message authentication code as bytes. After ``finalize`` has been called this object can no longer be used and :meth:`update`, :meth:`copy`, :meth:`verify` and :meth:`finalize` will raise an :class:`~cryptography.exceptions.AlreadyFinalized` exception. :return bytes: The message authentication code as bytes. :raises cryptography.exceptions.AlreadyFinalized: .. _`Cipher-based message authentication codes`: https://en.wikipedia.org/wiki/CMAC cryptography-43.0.0/docs/hazmat/primitives/mac/hmac.rst010064400017510000177000000101431464676315000213410ustar 00000000000000.. hazmat:: Hash-based message authentication codes (HMAC) ============================================== .. currentmodule:: cryptography.hazmat.primitives.hmac .. testsetup:: import binascii key = binascii.unhexlify(b"0" * 32) Hash-based message authentication codes (or HMACs) are a tool for calculating message authentication codes using a cryptographic hash function coupled with a secret key. You can use an HMAC to verify both the integrity and authenticity of a message. .. class:: HMAC(key, algorithm) HMAC objects take a ``key`` and a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance. The ``key`` should be :doc:`randomly generated bytes ` and is recommended to be equal in length to the ``digest_size`` of the hash function chosen. You must keep the ``key`` secret. This is an implementation of :rfc:`2104`. .. doctest:: >>> from cryptography.hazmat.primitives import hashes, hmac >>> key = b'test key. Beware! A real key should use os.urandom or TRNG to generate' >>> h = hmac.HMAC(key, hashes.SHA256()) >>> h.update(b"message to hash") >>> signature = h.finalize() >>> signature b'k\xd9\xb29\xefS\xf8\xcf\xec\xed\xbf\x95\xe6\x97X\x18\x9e%\x11DU1\x9fq}\x9a\x9c\xe0)y`=' If ``algorithm`` isn't a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance then ``TypeError`` will be raised. To check that a given signature is correct use the :meth:`verify` method. You will receive an exception if the signature is wrong: .. doctest:: >>> h = hmac.HMAC(key, hashes.SHA256()) >>> h.update(b"message to hash") >>> h_copy = h.copy() # get a copy of `h' to be reused >>> h.verify(signature) >>> >>> h_copy.verify(b"an incorrect signature") Traceback (most recent call last): ... cryptography.exceptions.InvalidSignature: Signature did not match digest. :param key: The secret key. :type key: :term:`bytes-like` :param algorithm: An :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance such as those described in :ref:`Cryptographic Hashes `. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the provided ``algorithm`` isn't supported. .. method:: update(msg) :param msg: The bytes to hash and authenticate. :type msg: :term:`bytes-like` :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises TypeError: This exception is raised if ``msg`` is not ``bytes``. .. method:: copy() Copy this :class:`HMAC` instance, usually so that we may call :meth:`finalize` to get an intermediate digest value while we continue to call :meth:`update` on the original instance. :return: A new instance of :class:`HMAC` that can be updated and finalized independently of the original instance. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` .. method:: verify(signature) Finalize the current context and securely compare digest to ``signature``. :param bytes signature: The bytes to compare the current digest against. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises cryptography.exceptions.InvalidSignature: If signature does not match digest :raises TypeError: This exception is raised if ``signature`` is not ``bytes``. .. method:: finalize() Finalize the current context and return the message digest as bytes. After ``finalize`` has been called this object can no longer be used and :meth:`update`, :meth:`copy`, :meth:`verify` and :meth:`finalize` will raise an :class:`~cryptography.exceptions.AlreadyFinalized` exception. :return bytes: The message digest as bytes. :raises cryptography.exceptions.AlreadyFinalized: cryptography-43.0.0/docs/hazmat/primitives/mac/index.rst010064400017510000177000000007261464676315000215460ustar 00000000000000.. hazmat:: Message authentication codes ============================ While cryptography supports multiple MAC algorithms, we strongly recommend that HMAC should be used unless you have a very specific need. For more information on why HMAC is preferred, see `Use cases for CMAC vs. HMAC?`_ .. toctree:: :maxdepth: 1 cmac hmac poly1305 .. _`Use cases for CMAC vs. HMAC?`: https://crypto.stackexchange.com/questions/15721/use-cases-for-cmac-vs-hmac cryptography-43.0.0/docs/hazmat/primitives/mac/poly1305.rst010064400017510000177000000117121464676315000217300ustar 00000000000000.. hazmat:: Poly1305 ======== .. currentmodule:: cryptography.hazmat.primitives.poly1305 .. testsetup:: key = b"\x01" * 32 Poly1305 is an authenticator that takes a 32-byte key and a message and produces a 16-byte tag. This tag is used to authenticate the message. Each key **must** only be used once. Using the same key to generate tags for multiple messages allows an attacker to forge tags. Poly1305 is described in :rfc:`7539`. .. class:: Poly1305(key) .. versionadded:: 2.7 .. warning:: Using the same key to generate tags for multiple messages allows an attacker to forge tags. Always generate a new key per message you want to authenticate. If you are using this as a MAC for symmetric encryption please use :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` instead. .. doctest:: >>> from cryptography.hazmat.primitives import poly1305 >>> p = poly1305.Poly1305(key) >>> p.update(b"message to authenticate") >>> p.finalize() b'T\xae\xff3\xbdW\xef\xd5r\x01\xe2n=\xb7\xd2h' To check that a given tag is correct use the :meth:`verify` method. You will receive an exception if the tag is wrong: .. doctest:: >>> p = poly1305.Poly1305(key) >>> p.update(b"message to authenticate") >>> p.verify(b"an incorrect tag") Traceback (most recent call last): ... cryptography.exceptions.InvalidSignature: Value did not match computed tag. :param key: The secret key. :type key: :term:`bytes-like` :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the version of OpenSSL ``cryptography`` is compiled against does not support this algorithm. .. method:: update(data) :param data: The bytes to hash and authenticate. :type data: :term:`bytes-like` :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: verify(tag) Finalize the current context and securely compare the MAC to ``tag``. :param bytes tag: The bytes to compare against. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises cryptography.exceptions.InvalidSignature: If tag does not match. :raises TypeError: This exception is raised if ``tag`` is not ``bytes``. .. method:: finalize() Finalize the current context and return the message authentication code as bytes. After ``finalize`` has been called this object can no longer be used and :meth:`update`, :meth:`verify`, and :meth:`finalize` will raise an :class:`~cryptography.exceptions.AlreadyFinalized` exception. :return bytes: The message authentication code as bytes. :raises cryptography.exceptions.AlreadyFinalized: .. classmethod:: generate_tag(key, data) A single step alternative to do sign operations. Returns the message authentication code as ``bytes`` for the given ``key`` and ``data``. :param key: Secret key as ``bytes``. :type key: :term:`bytes-like` :param data: The bytes to hash and authenticate. :type data: :term:`bytes-like` :return bytes: The message authentication code as bytes. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the version of OpenSSL ``cryptography`` is compiled against does not support this algorithm. :raises TypeError: This exception is raised if ``key`` or ``data`` are not ``bytes``. .. doctest:: >>> poly1305.Poly1305.generate_tag(key, b"message to authenticate") b'T\xae\xff3\xbdW\xef\xd5r\x01\xe2n=\xb7\xd2h' .. classmethod:: verify_tag(key, data, tag) A single step alternative to do verify operations. Securely compares the MAC to ``tag``, using the given ``key`` and ``data``. :param key: Secret key as ``bytes``. :type key: :term:`bytes-like` :param data: The bytes to hash and authenticate. :type data: :term:`bytes-like` :param bytes tag: The bytes to compare against. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the version of OpenSSL ``cryptography`` is compiled against does not support this algorithm. :raises TypeError: This exception is raised if ``key``, ``data`` or ``tag`` are not ``bytes``. :raises cryptography.exceptions.InvalidSignature: If tag does not match. .. doctest:: >>> poly1305.Poly1305.verify_tag(key, b"message to authenticate", b"an incorrect tag") Traceback (most recent call last): ... cryptography.exceptions.InvalidSignature: Value did not match computed tag. cryptography-43.0.0/docs/hazmat/primitives/padding.rst010064400017510000177000000106401464676315000213010ustar 00000000000000.. hazmat:: Symmetric Padding ================= .. module:: cryptography.hazmat.primitives.padding Padding is a way to take data that may or may not be a multiple of the block size for a cipher and extend it out so that it is. This is required for many block cipher modes as they require the data to be encrypted to be an exact multiple of the block size. .. class:: PKCS7(block_size) PKCS7 padding is a generalization of PKCS5 padding (also known as standard padding). PKCS7 padding works by appending ``N`` bytes with the value of ``chr(N)``, where ``N`` is the number of bytes required to make the final block of data the same size as the block size. A simple example of padding is: .. doctest:: >>> from cryptography.hazmat.primitives import padding >>> padder = padding.PKCS7(128).padder() >>> padded_data = padder.update(b"11111111111111112222222222") >>> padded_data += padder.finalize() >>> padded_data b'11111111111111112222222222\x06\x06\x06\x06\x06\x06' >>> unpadder = padding.PKCS7(128).unpadder() >>> data = unpadder.update(padded_data) >>> data += unpadder.finalize() >>> data b'11111111111111112222222222' :param block_size: The size of the block in :term:`bits` that the data is being padded to. :raises ValueError: Raised if block size is not a multiple of 8 or is not between 0 and 2040 inclusive. .. method:: padder() :returns: A padding :class:`~cryptography.hazmat.primitives.padding.PaddingContext` instance. .. method:: unpadder() :returns: An unpadding :class:`~cryptography.hazmat.primitives.padding.PaddingContext` instance. .. class:: ANSIX923(block_size) .. versionadded:: 1.3 `ANSI X9.23`_ padding works by appending ``N-1`` bytes with the value of ``0`` and a last byte with the value of ``chr(N)``, where ``N`` is the number of bytes required to make the final block of data the same size as the block size. A simple example of padding is: .. doctest:: >>> padder = padding.ANSIX923(128).padder() >>> padded_data = padder.update(b"11111111111111112222222222") >>> padded_data += padder.finalize() >>> padded_data b'11111111111111112222222222\x00\x00\x00\x00\x00\x06' >>> unpadder = padding.ANSIX923(128).unpadder() >>> data = unpadder.update(padded_data) >>> data += unpadder.finalize() >>> data b'11111111111111112222222222' :param block_size: The size of the block in :term:`bits` that the data is being padded to. :raises ValueError: Raised if block size is not a multiple of 8 or is not between 0 and 2040 inclusive. .. method:: padder() :returns: A padding :class:`~cryptography.hazmat.primitives.padding.PaddingContext` instance. .. method:: unpadder() :returns: An unpadding :class:`~cryptography.hazmat.primitives.padding.PaddingContext` instance. .. class:: PaddingContext When calling ``padder()`` or ``unpadder()`` the result will conform to the ``PaddingContext`` interface. You can then call ``update(data)`` with data until you have fed everything into the context. Once that is done call ``finalize()`` to finish the operation and obtain the remainder of the data. .. method:: update(data) :param data: The data you wish to pass into the context. :type data: :term:`bytes-like` :return bytes: Returns the data that was padded or unpadded. :raises TypeError: Raised if data is not bytes. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: finalize() Finalize the current context and return the rest of the data. After ``finalize`` has been called this object can no longer be used; :meth:`update` and :meth:`finalize` will raise an :class:`~cryptography.exceptions.AlreadyFinalized` exception. :return bytes: Returns the remainder of the data. :raises TypeError: Raised if data is not bytes. :raises ValueError: When trying to remove padding from incorrectly padded data. .. _`ANSI X9.23`: https://en.wikipedia.org/wiki/Padding_%28cryptography%29#ANSI_X9.23 cryptography-43.0.0/docs/hazmat/primitives/symmetric-encryption.rst010064400017510000177000001055211464676315000241020ustar 00000000000000.. hazmat:: /fernet 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 provides secrecy but not authenticity. That means an attacker can't see the message but an attacker can create bogus messages and force the application to decrypt them. In many contexts, a lack of authentication on encrypted messages can result in a loss of secrecy as well. For this reason in nearly all contexts it is necessary to combine encryption with a message authentication code, such as :doc:`HMAC `, in an "encrypt-then-MAC" formulation as `described by Colin Percival`_. ``cryptography`` includes a recipe named :doc:`/fernet` that does this for you. **To minimize the risk of security issues you should evaluate Fernet to see if it fits your needs before implementing anything using this module.** If :doc:`/fernet` is not appropriate for your use-case then you may still benefit from :doc:`/hazmat/primitives/aead` which combines encryption and authentication securely. .. class:: Cipher(algorithm, mode) Cipher objects combine an algorithm such as :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` with a mode like :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` or :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`. A simple example of encrypting and then decrypting content with AES is: .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes >>> key = os.urandom(32) >>> iv = os.urandom(16) >>> cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) >>> encryptor = cipher.encryptor() >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) + decryptor.finalize() b'a secret message' :param algorithm: A :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm` instance such as those described :ref:`below `. :param mode: A :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode` instance such as those described :ref:`below `. :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the provided ``algorithm`` is unsupported. .. method:: encryptor() :return: An encrypting :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` instance. If the requested combination of ``algorithm`` and ``mode`` is unsupported an :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be raised. .. method:: decryptor() :return: A decrypting :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` instance. If the requested combination of ``algorithm`` and ``mode`` is unsupported an :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be raised. .. _symmetric-encryption-algorithms: Algorithms ~~~~~~~~~~ .. currentmodule:: cryptography.hazmat.primitives.ciphers.algorithms .. class:: AES(key) AES (Advanced Encryption Standard) is a block cipher standardized by NIST. AES is both fast, and cryptographically strong. It is a good default choice for encryption. :param key: The secret key. This must be kept secret. Either ``128``, ``192``, or ``256`` :term:`bits` long. :type key: :term:`bytes-like` .. class:: AES128(key) .. versionadded:: 38.0.0 An AES class that only accepts 128 bit keys. This is identical to the standard ``AES`` class except that it will only accept a single key length. :param key: The secret key. This must be kept secret. ``128`` :term:`bits` long. :type key: :term:`bytes-like` .. class:: AES256(key) .. versionadded:: 38.0.0 An AES class that only accepts 256 bit keys. This is identical to the standard ``AES`` class except that it will only accept a single key length. :param key: The secret key. This must be kept secret. ``256`` :term:`bits` long. :type key: :term:`bytes-like` .. class:: Camellia(key) Camellia is a block cipher approved for use by `CRYPTREC`_ and ISO/IEC. It is considered to have comparable security and performance to AES but is not as widely studied or deployed. :param key: The secret key. This must be kept secret. Either ``128``, ``192``, or ``256`` :term:`bits` long. :type key: :term:`bytes-like` .. class:: ChaCha20(key, nonce) .. versionadded:: 2.1 .. note:: In most cases users should use :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` instead of this class. `ChaCha20` alone does not provide integrity so it must be combined with a MAC to be secure. :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305` does this for you. ChaCha20 is a stream cipher used in several IETF protocols. While it is standardized in :rfc:`7539`, **this implementation is not RFC-compliant**. This implementation uses a ``64`` :term:`bits` counter and a ``64`` :term:`bits` nonce as defined in the `original version`_ of the algorithm, rather than the ``32/96`` counter/nonce split defined in :rfc:`7539`. :param key: The secret key. This must be kept secret. ``256`` :term:`bits` (32 bytes) in length. :type key: :term:`bytes-like` :param nonce: Should be unique, a :term:`nonce`. It is critical to never reuse a ``nonce`` with a given key. Any reuse of a nonce with the same key compromises the security of every message encrypted with that key. The nonce does not need to be kept secret and may be included with the ciphertext. This must be ``128`` :term:`bits` in length. The 128-bit value is a concatenation of the 8-byte little-endian counter and the 8-byte nonce. :type nonce: :term:`bytes-like` .. note:: In the `original version`_ of the algorithm the nonce is defined as a 64-bit value that is later concatenated with a block counter (encoded as a 64-bit little-endian). If you have a separate nonce and block counter you will need to concatenate it yourself before passing it. For example, if you have an initial block counter of 2 and a 64-bit nonce the concatenated nonce would be ``struct.pack(">> import struct, os >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes >>> nonce = os.urandom(8) >>> counter = 0 >>> full_nonce = struct.pack(">> algorithm = algorithms.ChaCha20(key, full_nonce) >>> cipher = Cipher(algorithm, mode=None) >>> encryptor = cipher.encryptor() >>> ct = encryptor.update(b"a secret message") >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) b'a secret message' .. class:: TripleDES(key) .. warning:: This algorithm has been deprecated and moved to the :doc:`/hazmat/decrepit/index` module. If you need to continue using it then update your code to use the new module path. It will be removed from this namespace in 48.0.0. Triple DES (Data Encryption Standard), sometimes referred to as 3DES, is a block cipher standardized by NIST. Triple DES has known crypto-analytic flaws, however none of them currently enable a practical attack. Nonetheless, Triple DES is not recommended for new applications because it is incredibly slow; old applications should consider moving away from it. :param key: The secret key. This must be kept secret. Either ``64``, ``128``, or ``192`` :term:`bits` long. DES only uses ``56``, ``112``, or ``168`` bits of the key as there is a parity byte in each component of the key. Some writing refers to there being up to three separate keys that are each ``56`` bits long, they can simply be concatenated to produce the full key. :type key: :term:`bytes-like` .. class:: CAST5(key) .. versionadded:: 0.2 .. warning:: This algorithm has been deprecated and moved to the :doc:`/hazmat/decrepit/index` module. If you need to continue using it then update your code to use the new module path. It will be removed from this namespace in 45.0.0. CAST5 (also known as CAST-128) is a block cipher approved for use in the Canadian government by the `Communications Security Establishment`_. It is a variable key length cipher and supports keys from 40-128 :term:`bits` in length. :param key: The secret key, This must be kept secret. 40 to 128 :term:`bits` in length in increments of 8 bits. :type key: :term:`bytes-like` .. class:: SEED(key) .. versionadded:: 0.4 .. warning:: This algorithm has been deprecated and moved to the :doc:`/hazmat/decrepit/index` module. If you need to continue using it then update your code to use the new module path. It will be removed from this namespace in 45.0.0. SEED is a block cipher developed by the Korea Information Security Agency (KISA). It is defined in :rfc:`4269` and is used broadly throughout South Korean industry, but rarely found elsewhere. :param key: The secret key. This must be kept secret. ``128`` :term:`bits` in length. :type key: :term:`bytes-like` .. class:: SM4(key) .. versionadded:: 35.0.0 SM4 is a block cipher developed by the Chinese Government and standardized in the GB/T 32907-2016. It is used in the Chinese WAPI (Wired Authentication and Privacy Infrastructure) standard. (An English description is available at `draft-ribose-cfrg-sm4-10`_.) This block cipher should be used for compatibility purposes where required and is not otherwise recommended for use. :param key: The secret key. This must be kept secret. ``128`` :term:`bits` in length. :type key: :term:`bytes-like` Weak ciphers ------------ .. warning:: These ciphers are considered weak for a variety of reasons. New applications should avoid their use and existing applications should strongly consider migrating away. .. class:: Blowfish(key) .. warning:: This algorithm has been deprecated and moved to the :doc:`/hazmat/decrepit/index` module. If you need to continue using it then update your code to use the new module path. It will be removed from this namespace in 45.0.0. Blowfish is a block cipher developed by Bruce Schneier. It is known to be susceptible to attacks when using weak keys. The author has recommended that users of Blowfish move to newer algorithms such as :class:`AES`. :param key: The secret key. This must be kept secret. 32 to 448 :term:`bits` in length in increments of 8 bits. :type key: :term:`bytes-like` .. class:: ARC4(key) .. warning:: This algorithm has been deprecated and moved to the :doc:`/hazmat/decrepit/index` module. If you need to continue using it then update your code to use the new module path. It will be removed from this namespace in 48.0.0. ARC4 (Alleged RC4) is a stream cipher with serious weaknesses in its initial stream output. Its use is strongly discouraged. ARC4 does not use mode constructions. :param key: The secret key. This must be kept secret. Either ``40``, ``56``, ``64``, ``80``, ``128``, ``192``, or ``256`` :term:`bits` in length. :type key: :term:`bytes-like` .. doctest:: >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes >>> algorithm = algorithms.ARC4(key) >>> cipher = Cipher(algorithm, mode=None) >>> encryptor = cipher.encryptor() >>> ct = encryptor.update(b"a secret message") >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) b'a secret message' .. class:: IDEA(key) .. warning:: This algorithm has been deprecated and moved to the :doc:`/hazmat/decrepit/index` module. If you need to continue using it then update your code to use the new module path. It will be removed from this namespace in 45.0.0. IDEA (`International Data Encryption Algorithm`_) is a block cipher created in 1991. It is an optional component of the `OpenPGP`_ standard. This cipher is susceptible to attacks when using weak keys. It is recommended that you do not use this cipher for new applications. :param key: The secret key. This must be kept secret. ``128`` :term:`bits` in length. :type key: :term:`bytes-like` .. _symmetric-encryption-modes: Modes ~~~~~ .. module:: cryptography.hazmat.primitives.ciphers.modes .. class:: CBC(initialization_vector) CBC (Cipher Block Chaining) is a mode of operation for block ciphers. It is considered cryptographically strong. **Padding is required when using this mode.** :param initialization_vector: Must be :doc:`random bytes `. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Each time something is encrypted a new ``initialization_vector`` should be generated. Do not reuse an ``initialization_vector`` with a given ``key``, and particularly do not use a constant ``initialization_vector``. :type initialization_vector: :term:`bytes-like` A good construction looks like: .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers.modes import CBC >>> iv = os.urandom(16) >>> mode = CBC(iv) While the following is bad and will leak information: .. doctest:: >>> from cryptography.hazmat.primitives.ciphers.modes import CBC >>> iv = b"a" * 16 >>> mode = CBC(iv) .. class:: CTR(nonce) .. warning:: Counter mode is not recommended for use with block ciphers that have a block size of less than 128-:term:`bits`. CTR (Counter) is a mode of operation for block ciphers. It is considered cryptographically strong. It transforms a block cipher into a stream cipher. **This mode does not require padding.** :param nonce: Should be unique, a :term:`nonce`. It is critical to never reuse a ``nonce`` with a given key. Any reuse of a nonce with the same key compromises the security of every message encrypted with that key. Must be the same number of bytes as the ``block_size`` of the cipher with a given key. The nonce does not need to be kept secret and may be included with the ciphertext. :type nonce: :term:`bytes-like` .. class:: OFB(initialization_vector) OFB (Output Feedback) is a mode of operation for block ciphers. It transforms a block cipher into a stream cipher. **This mode does not require padding.** :param initialization_vector: Must be :doc:`random bytes `. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. :type initialization_vector: :term:`bytes-like` .. class:: CFB(initialization_vector) CFB (Cipher Feedback) is a mode of operation for block ciphers. It transforms a block cipher into a stream cipher. **This mode does not require padding.** :param initialization_vector: Must be :doc:`random bytes `. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. :type initialization_vector: :term:`bytes-like` .. class:: CFB8(initialization_vector) CFB (Cipher Feedback) is a mode of operation for block ciphers. It transforms a block cipher into a stream cipher. The CFB8 variant uses an 8-bit shift register. **This mode does not require padding.** :param initialization_vector: Must be :doc:`random bytes `. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. :type initialization_vector: :term:`bytes-like` .. class:: GCM(initialization_vector, tag=None, min_tag_length=16) .. danger:: If you are encrypting data that can fit into memory you should strongly consider using :class:`~cryptography.hazmat.primitives.ciphers.aead.AESGCM` instead of this. When using this mode you **must** not use the decrypted data until the appropriate finalization method (:meth:`~cryptography.hazmat.primitives.ciphers.CipherContext.finalize` or :meth:`~cryptography.hazmat.primitives.ciphers.AEADDecryptionContext.finalize_with_tag`) has been called. GCM provides **no** guarantees of ciphertext integrity until decryption is complete. GCM (Galois Counter Mode) is a mode of operation for block ciphers. An AEAD (authenticated encryption with additional data) mode is a type of block cipher mode that simultaneously encrypts the message as well as authenticating it. Additional unencrypted data may also be authenticated. Additional means of verifying integrity such as :doc:`HMAC ` are not necessary. **This mode does not require padding.** :param initialization_vector: Must be unique, a :term:`nonce`. They do not need to be kept secret and they can be included in a transmitted message. NIST `recommends a 96-bit IV length`_ for performance critical situations but it can be up to 2\ :sup:`64` - 1 :term:`bits`. Do not reuse an ``initialization_vector`` with a given ``key``. :type initialization_vector: :term:`bytes-like` .. note:: Cryptography will generate a 128-bit tag when finalizing encryption. You can shorten a tag by truncating it to the desired length but this is **not recommended** as it makes it easier to forge messages, and also potentially leaks the key (`NIST SP-800-38D`_ recommends 96-:term:`bits` or greater). Applications wishing to allow truncation can pass the ``min_tag_length`` parameter. .. versionchanged:: 0.5 The ``min_tag_length`` parameter was added in ``0.5``, previously truncation down to ``4`` bytes was always allowed. :param bytes tag: The tag bytes to verify during decryption. When encrypting this must be ``None``. When decrypting, it may be ``None`` if the tag is supplied on finalization using :meth:`~cryptography.hazmat.primitives.ciphers.AEADDecryptionContext.finalize_with_tag`. Otherwise, the tag is mandatory. :param int min_tag_length: The minimum length ``tag`` must be. By default this is ``16``, meaning tag truncation is not allowed. Allowing tag truncation is strongly discouraged for most applications. :raises ValueError: This is raised if ``len(tag) < min_tag_length`` or the ``initialization_vector`` is too short. An example of securely encrypting and decrypting data with ``AES`` in the ``GCM`` mode looks like: .. testcode:: import os from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, modes ) def encrypt(key, plaintext, associated_data): # Generate a random 96-bit IV. iv = os.urandom(12) # Construct an AES-GCM Cipher object with the given key and a # randomly generated IV. encryptor = Cipher( algorithms.AES(key), modes.GCM(iv), ).encryptor() # associated_data will be authenticated but not encrypted, # it must also be passed in on decryption. encryptor.authenticate_additional_data(associated_data) # Encrypt the plaintext and get the associated ciphertext. # GCM does not require padding. ciphertext = encryptor.update(plaintext) + encryptor.finalize() return (iv, ciphertext, encryptor.tag) def decrypt(key, associated_data, iv, ciphertext, tag): # Construct a Cipher object, with the key, iv, and additionally the # GCM tag used for authenticating the message. decryptor = Cipher( algorithms.AES(key), modes.GCM(iv, tag), ).decryptor() # We put associated_data back in or the tag will fail to verify # when we finalize the decryptor. decryptor.authenticate_additional_data(associated_data) # Decryption gets us the authenticated plaintext. # If the tag does not match an InvalidTag exception will be raised. return decryptor.update(ciphertext) + decryptor.finalize() iv, ciphertext, tag = encrypt( key, b"a secret message!", b"authenticated but not encrypted payload" ) print(decrypt( key, b"authenticated but not encrypted payload", iv, ciphertext, tag )) .. testoutput:: b'a secret message!' .. class:: XTS(tweak) .. versionadded:: 2.1 .. warning:: XTS mode is meant for disk encryption and should not be used in other contexts. ``cryptography`` only supports XTS mode with :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`. .. note:: AES XTS keys are double length. This means that to do AES-128 encryption in XTS mode you need a 256-bit key. Similarly, AES-256 requires passing a 512-bit key. AES 192 is not supported in XTS mode. XTS (XEX-based tweaked-codebook mode with ciphertext stealing) is a mode of operation for the AES block cipher that is used for `disk encryption`_. **This mode does not require padding.** :param tweak: The tweak is a 16 byte value typically derived from something like the disk sector number. A given ``(tweak, key)`` pair should not be reused, although doing so is less catastrophic than in CTR mode. :type tweak: :term:`bytes-like` Insecure modes -------------- .. warning:: These modes are insecure. New applications should never make use of them, and existing applications should strongly consider migrating away. .. class:: ECB() ECB (Electronic Code Book) is the simplest mode of operation for block ciphers. Each block of data is encrypted in the same way. This means identical plaintext blocks will always result in identical ciphertext blocks, which can leave `significant patterns in the output`_. **Padding is required when using this mode.** Interfaces ~~~~~~~~~~ .. currentmodule:: cryptography.hazmat.primitives.ciphers .. class:: CipherContext When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object the result will conform to the ``CipherContext`` interface. You can then call ``update(data)`` with data until you have fed everything into the context. Once that is done call ``finalize()`` to finish the operation and obtain the remainder of the data. Block ciphers require that the plaintext or ciphertext always be a multiple of their block size. Because of that **padding** is sometimes required to make a message the correct size. ``CipherContext`` will not automatically apply any padding; you'll need to add your own. For block ciphers the recommended padding is :class:`~cryptography.hazmat.primitives.padding.PKCS7`. If you are using a stream cipher mode (such as :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`) you don't have to worry about this. .. method:: update(data) :param data: The data you wish to pass into the context. :type data: :term:`bytes-like` :return bytes: Returns the data that was encrypted or decrypted. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` When the ``Cipher`` was constructed in a mode that turns it into a stream cipher (e.g. :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`), this will return bytes immediately, however in other modes it will return chunks whose size is determined by the cipher's block size. .. method:: update_into(data, buf) .. versionadded:: 1.8 .. warning:: This method allows you to avoid a memory copy by passing a writable buffer and reading the resulting data. You are responsible for correctly sizing the buffer and properly handling the data. This method should only be used when extremely high performance is a requirement and you will be making many small calls to ``update_into``. :param data: The data you wish to pass into the context. :type data: :term:`bytes-like` :param buf: A writable Python buffer that the data will be written into. This buffer should be ``len(data) + n - 1`` bytes where ``n`` is the block size (in bytes) of the cipher and mode combination. :return int: Number of bytes written. :raises ValueError: This is raised if the supplied buffer is too small. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes >>> key = os.urandom(32) >>> iv = os.urandom(16) >>> cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) >>> encryptor = cipher.encryptor() >>> # the buffer needs to be at least len(data) + n - 1 where n is cipher/mode block size in bytes >>> buf = bytearray(31) >>> len_encrypted = encryptor.update_into(b"a secret message", buf) >>> # get the ciphertext from the buffer reading only the bytes written to it (len_encrypted) >>> ct = bytes(buf[:len_encrypted]) + encryptor.finalize() >>> decryptor = cipher.decryptor() >>> len_decrypted = decryptor.update_into(ct, buf) >>> # get the plaintext from the buffer reading only the bytes written (len_decrypted) >>> bytes(buf[:len_decrypted]) + decryptor.finalize() b'a secret message' .. method:: finalize() :return bytes: Returns the remainder of the data. :raises ValueError: This is raised when the data provided isn't a multiple of the algorithm's block size. Once ``finalize`` is called this object can no longer be used and :meth:`update` and :meth:`finalize` will raise an :class:`~cryptography.exceptions.AlreadyFinalized` exception. .. method:: reset_nonce(nonce) .. versionadded:: 43.0.0 This method allows changing the nonce for an already existing context. Normally the nonce is set when the context is created and internally incremented as data as passed. However, in some scenarios the same key is used repeatedly but the nonce changes non-sequentially (e.g. ``QUIC``), which requires updating the context with the new nonce. This method only works for contexts using :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20` or :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR` mode. :param nonce: The nonce to update the context with. :type data: :term:`bytes-like` :raises cryptography.exceptions.UnsupportedAlgorithm: If the algorithm does not support updating the nonce. :raises ValueError: If the nonce is not the correct length for the algorithm. .. class:: AEADCipherContext When calling ``encryptor`` or ``decryptor`` on a ``Cipher`` object with an AEAD mode (e.g. :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM`) the result will conform to the ``AEADCipherContext`` and ``CipherContext`` interfaces. If it is an encryption or decryption context it will additionally be an ``AEADEncryptionContext`` or ``AEADDecryptionContext`` instance, respectively. ``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`` to finish the operation. .. note:: In AEAD modes all data passed to ``update()`` will be both encrypted and authenticated. Do not pass encrypted data to the ``authenticate_additional_data()`` method. It is meant solely for additional data you may want to authenticate but leave unencrypted. .. method:: authenticate_additional_data(data) :param data: Any data you wish to authenticate but not encrypt. :type data: :term:`bytes-like` :raises: :class:`~cryptography.exceptions.AlreadyFinalized` .. class:: AEADEncryptionContext When creating an encryption context using ``encryptor`` on a ``Cipher`` object with an AEAD mode such as :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` an object conforming to both the ``AEADEncryptionContext`` and ``AEADCipherContext`` interfaces will be returned. This interface provides one additional attribute ``tag``. ``tag`` can only be obtained after ``finalize`` has been called. .. attribute:: tag :return bytes: Returns the tag value as bytes. :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called before the context is finalized. .. class:: AEADDecryptionContext .. versionadded:: 1.9 When creating an encryption context using ``decryptor`` on a ``Cipher`` object with an AEAD mode such as :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` an object conforming to both the ``AEADDecryptionContext`` and ``AEADCipherContext`` interfaces will be returned. This interface provides one additional method :meth:`finalize_with_tag` that allows passing the authentication tag for validation after the ciphertext has been decrypted. .. method:: finalize_with_tag(tag) :param bytes tag: The tag bytes to verify after decryption. :return bytes: Returns the remainder of the data. :raises ValueError: This is raised when the data provided isn't a multiple of the algorithm's block size, if ``min_tag_length`` is less than 4, or if ``len(tag) < min_tag_length``. ``min_tag_length`` is an argument to the ``GCM`` constructor. If the authentication tag was not already supplied to the constructor of the :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` mode object, this method must be used instead of :meth:`~cryptography.hazmat.primitives.ciphers.CipherContext.finalize`. .. class:: CipherAlgorithm A named symmetric encryption algorithm. .. attribute:: name :type: str The standard name for the mode, for example, "AES", "Camellia", or "Blowfish". .. attribute:: key_size :type: int The number of :term:`bits` in the key being used. .. class:: BlockCipherAlgorithm A block cipher algorithm. .. attribute:: block_size :type: int The number of :term:`bits` in a block. Interfaces used by the symmetric cipher modes described in :ref:`Symmetric Encryption Modes `. .. currentmodule:: cryptography.hazmat.primitives.ciphers.modes .. class:: Mode A named cipher mode. .. attribute:: name :type: str This should be the standard shorthand name for the mode, for example Cipher-Block Chaining mode is "CBC". .. method:: validate_for_algorithm(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 are not met. For example, the :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode uses this method to check that the provided initialization vector's length matches the block size of the algorithm. .. class:: ModeWithInitializationVector A cipher mode with an initialization vector. .. attribute:: initialization_vector :type: :term:`bytes-like` Exact requirements of the initialization are described by the documentation of individual modes. .. class:: ModeWithNonce A cipher mode with a nonce. .. attribute:: nonce :type: :term:`bytes-like` Exact requirements of the nonce are described by the documentation of individual modes. .. class:: ModeWithAuthenticationTag A cipher mode with an authentication tag. .. attribute:: tag :type: :term:`bytes-like` Exact requirements of the tag are described by the documentation of individual modes. .. class:: ModeWithTweak .. versionadded:: 2.1 A cipher mode with a tweak. .. attribute:: tweak :type: :term:`bytes-like` Exact requirements of the tweak are described by the documentation of individual modes. Exceptions ~~~~~~~~~~ .. currentmodule:: cryptography.exceptions .. class:: InvalidTag This is raised if an authenticated encryption tag fails to verify during decryption. .. _`described by Colin Percival`: https://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html .. _`recommends a 96-bit IV length`: https://csrc.nist.gov/pubs/sp/800/38/d/final .. _`NIST SP-800-38D`: https://csrc.nist.gov/publications/detail/sp/800-38d/final .. _`Communications Security Establishment`: https://www.cse-cst.gc.ca .. _`encrypt`: https://ssd.eff.org/en/module/what-should-i-know-about-encryption .. _`CRYPTREC`: https://www.cryptrec.go.jp/en/ .. _`original version`: https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant .. _`significant patterns in the output`: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_(ECB) .. _`International Data Encryption Algorithm`: https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm .. _`OpenPGP`: https://www.openpgp.org/ .. _`disk encryption`: https://en.wikipedia.org/wiki/Disk_encryption_theory#XTS .. _`draft-ribose-cfrg-sm4-10`: https://datatracker.ietf.org/doc/html/draft-ribose-cfrg-sm4-10 cryptography-43.0.0/docs/hazmat/primitives/twofactor.rst010064400017510000177000000237421464676315000217120ustar 00000000000000.. hazmat:: Two-factor authentication ========================= .. currentmodule:: cryptography.hazmat.primitives.twofactor This module contains algorithms related to two-factor authentication. Currently, it contains an algorithm for generating and verifying one time password values based on Hash-based message authentication codes (HMAC). .. class:: InvalidToken This is raised when the verify method of a one time password function's computed token does not match the expected token. .. currentmodule:: cryptography.hazmat.primitives.twofactor.hotp .. data:: HOTPHashTypes .. versionadded:: 40.0.0 Type alias: A union of supported hash algorithm types: :class:`~cryptography.hazmat.primitives.hashes.SHA1`, :class:`~cryptography.hazmat.primitives.hashes.SHA256` or :class:`~cryptography.hazmat.primitives.hashes.SHA512`. .. class:: HOTP(key, length, algorithm, *, enforce_key_length=True) .. versionadded:: 0.3 HOTP objects take a ``key``, ``length`` and ``algorithm`` parameter. The ``key`` should be :doc:`randomly generated bytes ` and is recommended to be 160 :term:`bits` in length. The ``length`` parameter controls the length of the generated one time password and must be >= 6 and <= 8. This is an implementation of :rfc:`4226`. .. doctest:: >>> import os >>> from cryptography.hazmat.primitives.twofactor.hotp import HOTP >>> from cryptography.hazmat.primitives.hashes import SHA1 >>> key = os.urandom(20) >>> hotp = HOTP(key, 6, SHA1()) >>> hotp_value = hotp.generate(0) >>> hotp.verify(hotp_value, 0) :param key: Per-user secret key. This value must be kept secret and be at least 128 :term:`bits`. It is recommended that the key be 160 bits. :type key: :term:`bytes-like` :param int length: Length of generated one time password as ``int``. :param cryptography.hazmat.primitives.hashes.HashAlgorithm algorithm: A :class:`~cryptography.hazmat.primitives.hashes` instance (must match :data:`HOTPHashTypes`). :param enforce_key_length: A boolean flag defaulting to True that toggles whether a minimum key length of 128 :term:`bits` is enforced. This exists to work around the fact that as documented in `Issue #2915`_, the Google Authenticator PAM module by default generates 80 bit keys. If this flag is set to False, the application developer should implement additional checks of the key length before passing it into :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP`. .. versionadded:: 1.5 :raises ValueError: This is raised if the provided ``key`` is shorter than 128 :term:`bits` or if the ``length`` parameter is not 6, 7 or 8. :raises TypeError: This is raised if the provided ``algorithm`` is not :class:`~cryptography.hazmat.primitives.hashes.SHA1()`, :class:`~cryptography.hazmat.primitives.hashes.SHA256()` or :class:`~cryptography.hazmat.primitives.hashes.SHA512()` or if the ``length`` parameter is not an integer. .. method:: generate(counter) :param int counter: The counter value used to generate the one time password. :return bytes: A one time password value. .. method:: verify(hotp, counter) :param bytes hotp: The one time password value to validate. :param int counter: The counter value to validate against. :raises cryptography.hazmat.primitives.twofactor.InvalidToken: This is raised when the supplied HOTP does not match the expected HOTP. .. method:: get_provisioning_uri(account_name, counter, issuer) .. versionadded:: 1.0 :param account_name: The display name of account, such as ``'Alice Smith'`` or ``'alice@example.com'``. :type account_name: str :param issuer: The optional display name of issuer. This is typically the provider or service the user wants to access using the OTP token. :type issuer: ``str`` or ``None`` :param int counter: The current value of counter. :return: A URI string. Throttling ~~~~~~~~~~ Due to the fact that the HOTP algorithm generates rather short tokens that are 6 - 8 digits long, brute force attacks are possible. It is highly recommended that the server that validates the token implement a throttling scheme that locks out the account for a period of time after a number of failed attempts. The number of allowed attempts should be as low as possible while still ensuring that usability is not significantly impacted. Re-synchronization of the counter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The server's counter value should only be incremented on a successful HOTP authentication. However, the counter on the client is incremented every time a new HOTP value is requested. This can lead to the counter value being out of synchronization between the client and server. Due to this, it is highly recommended that the server sets a look-ahead window that allows the server to calculate the next ``x`` HOTP values and check them against the supplied HOTP value. This can be accomplished with something similar to the following code. .. code-block:: python def verify(hotp, counter, look_ahead): assert look_ahead >= 0 correct_counter = None otp = HOTP(key, 6) for count in range(counter, counter + look_ahead): try: otp.verify(hotp, count) correct_counter = count except InvalidToken: pass return correct_counter .. currentmodule:: cryptography.hazmat.primitives.twofactor.totp .. class:: TOTP(key, length, algorithm, time_step, *, enforce_key_length=True) TOTP objects take a ``key``, ``length``, ``algorithm`` and ``time_step`` parameter. The ``key`` should be :doc:`randomly generated bytes ` and is recommended to be as long as your hash function's output (e.g 256-bit for SHA256). The ``length`` parameter controls the length of the generated one time password and must be >= 6 and <= 8. This is an implementation of :rfc:`6238`. .. doctest:: >>> import os >>> import time >>> from cryptography.hazmat.primitives.twofactor.totp import TOTP >>> from cryptography.hazmat.primitives.hashes import SHA1 >>> key = os.urandom(20) >>> totp = TOTP(key, 8, SHA1(), 30) >>> time_value = time.time() >>> totp_value = totp.generate(time_value) >>> totp.verify(totp_value, time_value) :param key: Per-user secret key. This value must be kept secret and be at least 128 :term:`bits`. It is recommended that the key be 160 bits. :type key: :term:`bytes-like` :param int length: Length of generated one time password as ``int``. :param cryptography.hazmat.primitives.hashes.HashAlgorithm algorithm: A :class:`~cryptography.hazmat.primitives.hashes` instance. :param int time_step: The time step size. The recommended size is 30. :param enforce_key_length: A boolean flag defaulting to True that toggles whether a minimum key length of 128 :term:`bits` is enforced. This exists to work around the fact that as documented in `Issue #2915`_, the Google Authenticator PAM module by default generates 80 bit keys. If this flag is set to False, the application develop should implement additional checks of the key length before passing it into :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP`. .. versionadded:: 1.5 :raises ValueError: This is raised if the provided ``key`` is shorter than 128 :term:`bits` or if the ``length`` parameter is not 6, 7 or 8. :raises TypeError: This is raised if the provided ``algorithm`` is not :class:`~cryptography.hazmat.primitives.hashes.SHA1()`, :class:`~cryptography.hazmat.primitives.hashes.SHA256()` or :class:`~cryptography.hazmat.primitives.hashes.SHA512()` or if the ``length`` parameter is not an integer. .. method:: generate(time) :param int time: The time value used to generate the one time password. :return bytes: A one time password value. .. method:: verify(totp, time) :param bytes totp: The one time password value to validate. :param int time: The time value to validate against. :raises cryptography.hazmat.primitives.twofactor.InvalidToken: This is raised when the supplied TOTP does not match the expected TOTP. .. method:: get_provisioning_uri(account_name, issuer) .. versionadded:: 1.0 :param account_name: The display name of account, such as ``'Alice Smith'`` or ``'alice@example.com'``. :type account_name: str :param issuer: The optional display name of issuer. This is typically the provider or service the user wants to access using the OTP token. :type issuer: ``str`` or ``None`` :return: A URI string. Provisioning URI ~~~~~~~~~~~~~~~~ The provisioning URI of HOTP and TOTP is a `feature of Google Authenticator`_ and not actually part of the HOTP or TOTP RFCs. However, it is widely supported by web sites and mobile applications which are using Two-Factor authentication. For generating a provisioning URI you can use the ``get_provisioning_uri`` method of HOTP/TOTP instances. .. code-block:: python counter = 5 account_name = 'alice@example.com' issuer_name = 'Example Inc' hotp_uri = hotp.get_provisioning_uri(account_name, counter, issuer_name) totp_uri = totp.get_provisioning_uri(account_name, issuer_name) A common usage is encoding the provisioning URI into QR code and guiding users to scan it with Two-Factor authentication applications in their mobile devices. .. _`feature of Google Authenticator`: https://github.com/google/google-authenticator/wiki/Key-Uri-Format .. _`Issue #2915`: https://github.com/pyca/cryptography/issues/2915 cryptography-43.0.0/docs/index.rst010064400017510000177000000052521464676315000153260ustar 00000000000000Welcome to ``pyca/cryptography`` ================================ ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and key derivation functions. For example, to encrypt something with ``cryptography``'s high level symmetric encryption recipe: .. code-block:: pycon >>> from cryptography.fernet import Fernet >>> # Put this somewhere safe! >>> key = Fernet.generate_key() >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token b'...' >>> f.decrypt(token) b'A really secret message. Not for prying eyes.' If you are interested in learning more about the field of cryptography, we recommend `Crypto 101, by Laurens Van Houtven`_ and `The Cryptopals Crypto Challenges`_. Installation ------------ You can install ``cryptography`` with ``pip``: .. code-block:: console $ pip install cryptography See :doc:`Installation ` for more information. .. _cryptography-layout: Layout ------ ``cryptography`` is broadly divided into two levels. One with safe cryptographic recipes that require little to no configuration choices. These are safe and easy to use and don't require developers to make many decisions. The other level is low-level cryptographic primitives. These are often dangerous and can be used incorrectly. They require making decisions and having an in-depth knowledge of the cryptographic concepts at work. Because of the potential danger in working at this level, this is referred to as the "hazardous materials" or "hazmat" layer. These live in the ``cryptography.hazmat`` package, and their documentation will always contain an admonition at the top. We recommend using the recipes layer whenever possible, and falling back to the hazmat layer only when necessary. .. toctree:: :maxdepth: 2 :caption: The recipes layer fernet x509/index .. toctree:: :maxdepth: 2 :caption: The hazardous materials layer hazmat/primitives/index exceptions random-numbers hazmat/decrepit/index .. toctree:: :maxdepth: 2 :caption: The cryptography open source project installation changelog faq development/index openssl security limitations api-stability doing-a-release community glossary .. note:: ``cryptography`` has not been subjected to an external audit of its code or documentation. If you're interested in discussing an audit please :doc:`get in touch `. .. _`Crypto 101, by Laurens Van Houtven`: https://www.crypto101.io/ .. _`The Cryptopals Crypto Challenges`: https://cryptopals.com/ cryptography-43.0.0/docs/installation.rst010064400017510000177000000270151464676315000167210ustar 00000000000000Installation ============ You can install ``cryptography`` with ``pip``: .. code-block:: console $ pip install cryptography If this does not work please **upgrade your pip** first, as that is the single most common cause of installation problems. Supported platforms ------------------- Currently we test ``cryptography`` on Python 3.7+ and PyPy3 7.3.11+ on these operating systems. * x86-64 RHEL 8.x * x86-64 CentOS 9 Stream * x86-64 Fedora (latest) * x86-64 macOS 13 Ventura and ARM64 macOS 14 Sonoma * x86-64 Ubuntu 20.04, 22.04, 24.04, rolling * ARM64 Ubuntu rolling * x86-64 Debian Bullseye (11.x), Bookworm (12.x), Trixie (13.x), and Sid (unstable) * x86-64 and ARM64 Alpine (latest) * 32-bit and 64-bit Python on 64-bit Windows Server 2022 We test compiling with ``clang`` as well as ``gcc`` and use the following OpenSSL releases in addition to distribution provided releases from the above supported platforms: * ``OpenSSL 3.0-latest`` * ``OpenSSL 3.1-latest`` * ``OpenSSL 3.2-latest`` * ``OpenSSL 3.3-latest`` We also test against the latest commit of BoringSSL as well as versions of LibreSSL that are receiving security support at the time of a given ``cryptography`` release. Building cryptography on Windows -------------------------------- The wheel package on Windows is a statically linked build (as of 0.5) so all dependencies are included. To install ``cryptography``, you will typically just run .. code-block:: console $ pip install cryptography If you prefer to compile it yourself you'll need to have OpenSSL installed. You can compile OpenSSL yourself as well or use `a binary distribution`_. Be sure to download the proper version for your architecture and Python (VC2015 is required for 3.7 and above). Wherever you place your copy of OpenSSL you'll need to set the ``OPENSSL_DIR`` environment variable to include the proper location. For example: .. code-block:: console C:\> \path\to\vcvarsall.bat x86_amd64 C:\> set OPENSSL_DIR=C:\OpenSSL-win64 C:\> pip install cryptography You will also need to have :ref:`Rust installed and available`. If you need to rebuild ``cryptography`` for any reason be sure to clear the local `wheel cache`_. .. _build-on-linux: Building cryptography on Linux ------------------------------ .. note:: You should **upgrade pip** and attempt to install ``cryptography`` again before following the instructions to compile it below. Most Linux platforms will receive a binary wheel and require no compiler if you have an updated ``pip``! ``cryptography`` ships ``manylinux`` wheels (as of 2.0) so all dependencies are included. For users on **pip 19.3** or above running on a ``manylinux2014`` (or greater) compatible distribution (or **pip 21.2.4** for ``musllinux``) all you should need to do is: .. code-block:: console $ pip install cryptography If you want to compile ``cryptography`` yourself you'll need a C compiler, a Rust compiler, headers for Python (if you're not using ``pypy``), and headers for the OpenSSL and ``libffi`` libraries available on your system. On all Linux distributions you will need to have :ref:`Rust installed and available`. Alpine ~~~~~~ .. warning:: The Rust available by default in Alpine < 3.17 is older than the minimum supported version. See the :ref:`Rust installation instructions ` for information about installing a newer Rust. .. code-block:: console $ sudo apk add gcc musl-dev python3-dev libffi-dev openssl-dev cargo pkgconfig If you get an error with ``openssl-dev`` you may have to use ``libressl-dev``. Debian/Ubuntu ~~~~~~~~~~~~~ .. warning:: The Rust available in Debian versions prior to Bookworm are older than the minimum supported version. See the :ref:`Rust installation instructions ` for information about installing a newer Rust. .. code-block:: console $ sudo apt-get install build-essential libssl-dev libffi-dev \ python3-dev cargo pkg-config Fedora/RHEL/CentOS ~~~~~~~~~~~~~~~~~~ .. warning:: For RHEL and CentOS you must be on version 8.8 or newer for the command below to install a sufficiently new Rust. If your Rust is less than 1.65.0 please see the :ref:`Rust installation instructions ` for information about installing a newer Rust. .. code-block:: console $ sudo dnf install redhat-rpm-config gcc libffi-devel python3-devel \ openssl-devel cargo pkg-config Building ~~~~~~~~ You should now be able to build and install cryptography. To avoid getting the pre-built wheel on ``manylinux`` compatible distributions you'll need to use ``--no-binary``. .. code-block:: console $ pip install cryptography --no-binary cryptography Using your own OpenSSL on Linux ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python links to OpenSSL for its own purposes and this can sometimes cause problems when you wish to use a different version of OpenSSL with cryptography. If you want to use cryptography with your own build of OpenSSL you will need to make sure that the build is configured correctly so that your version of OpenSSL doesn't conflict with Python's. The options you need to add allow the linker to identify every symbol correctly even when multiple versions of the library are linked into the same program. If you are using your distribution's source packages these will probably be patched in for you already, otherwise you'll need to use options something like this when configuring OpenSSL: .. code-block:: console $ ./config -Wl,-Bsymbolic-functions -fPIC shared Static Wheels ~~~~~~~~~~~~~ Cryptography ships statically-linked wheels for macOS, Windows, and Linux (via ``manylinux`` and ``musllinux``). This allows compatible environments to use the most recent OpenSSL, regardless of what is shipped by default on those platforms. If you are using a platform not covered by our wheels, you can build your own statically-linked wheels that will work on your own systems. This will allow you to continue to use relatively old Linux distributions (such as LTS releases), while making sure you have the most recent OpenSSL available to your Python programs. To do so, you should find yourself a machine that is as similar as possible to your target environment (e.g. your production environment): for example, spin up a new cloud server running your target Linux distribution. On this machine, install the Cryptography dependencies as mentioned in :ref:`build-on-linux`. Please also make sure you have `virtualenv`_ installed: this should be available from your system package manager. Then, paste the following into a shell script. You'll need to populate the ``OPENSSL_VERSION`` variable. To do that, visit `openssl.org`_ and find the latest non-FIPS release version number, then set the string appropriately. For example, for OpenSSL 1.1.1k, use ``OPENSSL_VERSION="1.1.1k"``. When this shell script is complete, you'll find a collection of wheel files in a directory called ``wheelhouse``. These wheels can be installed by a sufficiently-recent version of ``pip``. The Cryptography wheel in this directory contains a statically-linked OpenSSL binding, which ensures that you have access to the most-recent OpenSSL releases without corrupting your system dependencies. .. code-block:: console set -e OPENSSL_VERSION="VERSIONGOESHERE" CWD=$(pwd) virtualenv env . env/bin/activate pip install -U setuptools pip install -U wheel pip curl -O https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz tar xvf openssl-${OPENSSL_VERSION}.tar.gz cd openssl-${OPENSSL_VERSION} ./config no-shared no-ssl2 no-ssl3 -fPIC --prefix=${CWD}/openssl make && make install cd .. OPENSSL_DIR="${CWD}/openssl" pip wheel --no-cache-dir --no-binary cryptography cryptography Building cryptography on macOS ------------------------------ .. note:: If installation gives a ``fatal error: 'openssl/aes.h' file not found`` see the :doc:`FAQ ` for information about how to fix this issue. The wheel package on macOS is a statically linked build (as of 1.0.1) so for users with pip 8 or above you only need one step: .. code-block:: console $ pip install cryptography If you want to build cryptography yourself or are on an older macOS version, cryptography requires the presence of a C compiler, development headers, and the proper libraries. On macOS much of this is provided by Apple's Xcode development tools. To install the Xcode command line tools (on macOS 10.10+) open a terminal window and run: .. code-block:: console $ xcode-select --install This will install a compiler (clang) along with (most of) the required development headers. You will also need to have :ref:`Rust installed and available`, which can be obtained from `Homebrew`_, `MacPorts`_, or directly from the Rust website. If you are linking against a ``universal2`` archive of OpenSSL, the minimum supported Rust version is 1.66.0. Finally you need OpenSSL, which you can obtain from `Homebrew`_ or `MacPorts`_. Cryptography does **not** support the OpenSSL/LibreSSL libraries Apple ships in its base operating system. To build cryptography and dynamically link it: `Homebrew`_ .. code-block:: console $ brew install openssl@3 rust $ env OPENSSL_DIR="$(brew --prefix openssl@3)" pip install cryptography `MacPorts`_: .. code-block:: console $ sudo port install openssl rust $ env OPENSSL_DIR="-L/opt/local" pip install cryptography You can also build cryptography statically: `Homebrew`_ .. code-block:: console $ brew install openssl@3 rust $ env OPENSSL_STATIC=1 OPENSSL_DIR="$(brew --prefix openssl@3)" pip install cryptography `MacPorts`_: .. code-block:: console $ sudo port install openssl rust $ env OPENSSL_STATIC=1 OPENSSL_DIR="/opt/local" pip install cryptography If you need to rebuild ``cryptography`` for any reason be sure to clear the local `wheel cache`_. Rust ---- .. note:: If you are using Linux, then you should **upgrade pip** (in a virtual environment!) and attempt to install ``cryptography`` again before trying to install the Rust toolchain. On most Linux distributions, the latest version of ``pip`` will be able to install a binary wheel, so you won't need a Rust toolchain. Building ``cryptography`` requires having a working Rust toolchain. The current minimum supported Rust version is 1.65.0. **This is newer than the Rust some package managers ship**, so users may need to install with the instructions below. Instructions for installing Rust can be found on `the Rust Project's website`_. We recommend installing Rust with ``rustup`` (as documented by the Rust Project) in order to ensure you have a recent version. Rust is only required when building ``cryptography``, meaning that you may install it for the duration of your ``pip install`` command and then remove it from a system. A Rust toolchain is not required to **use** ``cryptography``. In deployments such as ``docker``, you may use a multi-stage ``Dockerfile`` where you install Rust during the build phase but do not install it in the runtime image. This is the same as the C compiler toolchain which is also required to build ``cryptography``, but not afterwards. .. _`Homebrew`: https://brew.sh .. _`MacPorts`: https://www.macports.org .. _`a binary distribution`: https://wiki.openssl.org/index.php/Binaries .. _virtualenv: https://virtualenv.pypa.io/en/latest/ .. _openssl.org: https://www.openssl.org/source/ .. _`wheel cache`: https://pip.pypa.io/en/stable/cli/pip_install/#caching .. _`the Rust Project's website`: https://www.rust-lang.org/tools/install cryptography-43.0.0/docs/limitations.rst010064400017510000177000000044531464676315000165550ustar 00000000000000Known security limitations ========================== Secure memory wiping -------------------- `Memory wiping`_ is used to protect secret data or key material from attackers with access to deallocated memory. This is a defense-in-depth measure against vulnerabilities that leak application memory. Many ``cryptography`` APIs which accept ``bytes`` also accept types which implement the buffer interface. Thus, users wishing to do so can pass ``memoryview`` or another mutable type to ``cryptography`` APIs, and overwrite the contents once the data is no longer needed. However, ``cryptography`` does not clear memory by default, as there is no way to clear immutable structures such as ``bytes``. As a result, ``cryptography``, like almost all software in Python is potentially vulnerable to this attack. The `CERT secure coding guidelines`_ assesses this issue as "Severity: medium, Likelihood: unlikely, Remediation Cost: expensive to repair" and we do not consider this a high risk for most users. RSA PKCS1 v1.5 constant time decryption --------------------------------------- RSA decryption has several different modes, one of which is PKCS1 v1.5. When used in **online contexts**, a secure protocol implementation requires that peers not be able to tell whether RSA PKCS1 v1.5 decryption failed or succeeded, even by timing variability. ``cryptography`` does not provide an API that makes this possible, due to the fact that RSA decryption raises an exception on failure, which takes a different amount of time than returning a value in the success case. Fixing this would require a new API in ``cryptography``, but OpenSSL does not expose an API for straightforwardly implementing this while reusing its own constant-time logic. See `issue 6167`_ for more information. For this reason we recommend not implementing online protocols that use RSA PKCS1 v1.5 decryption with ``cryptography`` -- independent of this limitation, such protocols generally have poor security properties due to their lack of forward security. .. _`Memory wiping`: https://devblogs.microsoft.com/oldnewthing/?p=4223 .. _`CERT secure coding guidelines`: https://wiki.sei.cmu.edu/confluence/display/c/MEM03-C.+Clear+sensitive+information+stored+in+reusable+resources .. _`issue 6167`: https://github.com/pyca/cryptography/issues/6167#issuecomment-1276151799cryptography-43.0.0/docs/make.bat010064400017510000177000000117641464676315000150770ustar 00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Cryptography.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Cryptography.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end cryptography-43.0.0/docs/openssl.rst010064400017510000177000000031051464676315000156750ustar 00000000000000Use of OpenSSL ============== ``cryptography`` depends on the `OpenSSL`_ C library for all cryptographic operation. OpenSSL is the de facto standard for cryptographic libraries and provides high performance along with various certifications that may be relevant to developers. A list of supported versions can be found in our :doc:`/installation` documentation. In general the backend should be considered an internal implementation detail of the project, but there are some public methods available for debugging purposes. .. data:: cryptography.hazmat.backends.openssl.backend .. method:: openssl_version_text() :return text: The friendly string name of the loaded OpenSSL library. This is not necessarily the same version as it was compiled against. .. method:: openssl_version_number() .. versionadded:: 1.8 :return int: The integer version of the loaded OpenSSL library. This is defined in ``opensslv.h`` as ``OPENSSL_VERSION_NUMBER`` and is typically shown in hexadecimal (e.g. ``0x1010003f``). This is not necessarily the same version as it was compiled against. .. _legacy-provider: Legacy provider in OpenSSL 3.x ------------------------------ .. versionadded:: 39.0.0 Users can set ``CRYPTOGRAPHY_OPENSSL_NO_LEGACY`` environment variable to disable the legacy provider in OpenSSL 3.x. This will disable legacy cryptographic algorithms, including ``Blowfish``, ``CAST5``, ``SEED``, ``ARC4``, and ``RC2`` (which is used by some encrypted serialization formats). .. _`OpenSSL`: https://www.openssl.org/ cryptography-43.0.0/docs/random-numbers.rst010064400017510000177000000026111464676315000171440ustar 00000000000000Random number generation ======================== When generating random data for use in cryptographic operations, such as an initialization vector for encryption in :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode, you do not want to use the standard :mod:`random` module APIs. This is because they do not provide a cryptographically secure random number generator, which can result in major security issues depending on the algorithms in use. Therefore, it is our recommendation to `always use your operating system's provided random number generator`_, which is available as :func:`os.urandom`. For example, if you need 16 bytes of random data for an initialization vector, you can obtain them with: .. doctest:: >>> import os >>> iv = os.urandom(16) If you need your random number as an big integer, you can use ``int.from_bytes`` to convert the result of ``os.urandom``: .. code-block:: pycon >>> serial = int.from_bytes(os.urandom(16), byteorder="big") In addition, the `Python standard library`_ includes the ``secrets`` module, which can be used for generating cryptographically secure random numbers, with specific helpers for text-based formats. .. _`always use your operating system's provided random number generator`: https://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ .. _`Python standard library`: https://docs.python.org/3/library/secrets.html cryptography-43.0.0/docs/security.rst010064400017510000177000000076321464676315000160720ustar 00000000000000Security ======== We take the security of ``cryptography`` seriously. The following are a set of policies we have adopted to ensure that security issues are addressed in a timely fashion. Known vulnerabilities --------------------- A list of all known vulnerabilities in ``cryptography`` can be found on `osv.dev`_, as well as other ecosystem vulnerability databases. They can automatically be scanned for using tools such as `pip-audit`_ or `osv-scan`_. Infrastructure -------------- In addition to ``cryptography``'s code, we're also concerned with the security of the infrastructure we run (primarily ``cryptography.io``). If you discover a security vulnerability in our infrastructure, we ask you to report it using the same procedure. What is a security issue? ------------------------- Anytime it's possible to write code using ``cryptography``'s public API which does not provide the guarantees that a reasonable developer would expect it to based on our documentation. That's a bit academic, but basically it means the scope of what we consider a vulnerability is broad, and we do not require a proof of concept or even a specific exploit, merely a reasonable threat model under which ``cryptography`` could be attacked. To give a few examples of things we would consider security issues: * If a recipe, such as Fernet, made it easy for a user to bypass confidentiality or integrity with the public API (e.g. if the API let a user reuse nonces). * If, under any circumstances, we used a CSPRNG which wasn't fork-safe. * If ``cryptography`` used an API in an underlying C library and failed to handle error conditions safely. Examples of things we wouldn't consider security issues: * Offering ECB mode for symmetric encryption in the *Hazmat* layer. Though ECB is critically weak, it is documented as being weak in our documentation. * Using a variable time comparison somewhere, if it's not possible to articulate any particular program in which this would result in problematic information disclosure. In general, if you're unsure, we request that you to default to treating things as security issues and handling them sensitively, the worst thing that can happen is that we'll ask you to file a public issue. Reporting a security issue -------------------------- We ask that you do not report security issues to our normal GitHub issue tracker. If you believe you've identified a security issue with ``cryptography``, please report it via our `security advisory page`_. Once you've submitted an issue, you should receive an acknowledgment within 48 hours, and depending on the action to be taken, you may receive further follow-up. Supported Versions ------------------ At any given time, we will provide security support for the `main`_ branch as well as the most recent release. New releases for OpenSSL updates -------------------------------- As of versions 0.5, 1.0.1, and 2.0.0, ``cryptography`` statically links OpenSSL in binary distributions for Windows, macOS, and Linux respectively, to ease installation. Due to this, ``cryptography`` will release a new version whenever OpenSSL has a security or bug fix release to avoid shipping insecure software. Like all our other releases, this will be announced on the mailing list and we strongly recommend that you upgrade as soon as possible. Disclosure Process ------------------ When we become aware of a security bug in ``cryptography``, we will endeavor to fix it and issue a release as quickly as possible. We will generally issue a new release for any security issue. The steps for issuing a security release are described in our :doc:`/doing-a-release` documentation. .. _`osv.dev`: https://osv.dev/list?ecosystem=PyPI&q=cryptography .. _`pip-audit`: https://pypi.org/project/pip-audit/ .. _`osv-scan`: https://google.github.io/osv-scanner/ .. _`security advisory page`: https://github.com/pyca/cryptography/security/advisories/new .. _`main`: https://github.com/pyca/cryptography cryptography-43.0.0/docs/spelling_wordlist.txt010064400017510000177000000025501464676315000177700ustar 00000000000000AArch accessor affine Authenticator authenticator backend Backends backends bcrypt Bleichenbacher Blowfish boolean BoringSSL Botan Brainpool Bullseye Capitan Carmichael CentOS changelog Changelog ciphertext codebook committer committers conda CPython Cryptanalysis crypto cryptographic cryptographically de Debian deallocated decrypt decrypts Decrypts decrypted decrypting deprecations DER dereference deserialize deserialized Deserialization deserializing Diffie Diffie disambiguating Django Docstrings El Encodings endian Euler extendable facto fallback Fernet fernet FIPS Google hazmat Homebrew hostname hostnames incrementing indistinguishability initialisms interoperability interoperable introspectability invariants iOS iterable Kerberos Keychain Koblitz Lange logins metadata MGF Monterey Mozilla multi namespace namespaces macOS naïve Nonces nonces online paddings Parallelization personalization RHEL parsers Parsers PEM pickleable plaintext Poly pre precompute precomputed preprocessor preprocessors presentational pseudorandom PSS pyOpenSSL pytest relicensed responder runtime Schneier scrypt serializer Serializers setuptools SHA Solaris Sonoma SPKI Sur syscall Tanja testability Thawte timestamp timestamps toolchain totient Trixie tunable Ubuntu unencrypted unicode unpadded unpadding Ventura verifier Verifier Verisign versioning wildcard WoSign Wycheproof Xcode XEX cryptography-43.0.0/docs/x509/certificate-transparency.rst010064400017510000177000000065261464676315000217220ustar 00000000000000Certificate Transparency ======================== .. currentmodule:: cryptography.x509.certificate_transparency `Certificate Transparency`_ is a set of protocols specified in :rfc:`6962` which allow X.509 certificates to be sent to append-only logs and have small cryptographic proofs that a certificate has been publicly logged. This allows for external auditing of the certificates that a certificate authority has issued. .. class:: SignedCertificateTimestamp .. versionadded:: 2.0 SignedCertificateTimestamps (SCTs) are small cryptographically signed assertions that the specified certificate has been submitted to a Certificate Transparency Log, and that it will be part of the public log within some time period, this is called the "maximum merge delay" (MMD) and each log specifies its own. .. attribute:: version :type: :class:`~cryptography.x509.certificate_transparency.Version` The SCT version as an enumeration. Currently only one version has been specified. .. attribute:: log_id :type: bytes An opaque identifier, indicating which log this SCT is from. This is the SHA256 hash of the log's public key. .. attribute:: timestamp :type: :class:`datetime.datetime` A naïve datetime representing the time in UTC at which the log asserts the certificate had been submitted to it. .. attribute:: entry_type :type: :class:`~cryptography.x509.certificate_transparency.LogEntryType` The type of submission to the log that this SCT is for. Log submissions can either be certificates themselves or "pre-certificates" which indicate a binding-intent to issue a certificate for the same data, with SCTs embedded in it. .. attribute:: signature_hash_algorithm .. versionadded:: 38.0.0 :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` The hashing algorithm used by this SCT's signature. .. attribute:: signature_algorithm .. versionadded:: 38.0.0 :type: :class:`~cryptography.x509.certificate_transparency.SignatureAlgorithm` The signing algorithm used by this SCT's signature. .. attribute:: signature .. versionadded:: 38.0.0 :type: bytes The raw bytes of the signatures embedded in the SCT. .. attribute:: extension_bytes .. versionadded:: 38.0.0 :type: bytes Any raw extension bytes. .. class:: Version .. versionadded:: 2.0 An enumeration for SignedCertificateTimestamp versions. .. attribute:: v1 For version 1 SignedCertificateTimestamps. .. class:: LogEntryType .. versionadded:: 2.0 An enumeration for SignedCertificateTimestamp log entry types. .. attribute:: X509_CERTIFICATE For SCTs corresponding to X.509 certificates. .. attribute:: PRE_CERTIFICATE For SCTs corresponding to pre-certificates. .. class:: SignatureAlgorithm .. versionadded:: 38.0.0 An enumeration for SignedCertificateTimestamp signature algorithms. These are exactly the same as SignatureAlgorithm in :rfc:`5246` (TLS 1.2). .. attribute:: ANONYMOUS .. attribute:: RSA .. attribute:: DSA .. attribute:: ECDSA .. _`Certificate Transparency`: https://certificate.transparency.dev/ cryptography-43.0.0/docs/x509/index.rst010064400017510000177000000007521464676315000160330ustar 00000000000000X.509 ===== X.509 is an ITU-T standard for a `public key infrastructure`_. X.509v3 is defined in :rfc:`5280` (which obsoletes :rfc:`2459` and :rfc:`3280`). X.509 certificates are commonly used in protocols like `TLS`_. .. toctree:: :maxdepth: 2 tutorial certificate-transparency ocsp verification reference .. _`public key infrastructure`: https://en.wikipedia.org/wiki/Public_key_infrastructure .. _`TLS`: https://en.wikipedia.org/wiki/Transport_Layer_Security cryptography-43.0.0/docs/x509/ocsp.rst010064400017510000177000001025021464676315000156640ustar 00000000000000OCSP ==== .. currentmodule:: cryptography.x509.ocsp .. testsetup:: import base64 pem_cert = b""" -----BEGIN CERTIFICATE----- MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu 6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y 3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc 8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF 7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP -----END CERTIFICATE----- """ pem_issuer = b""" -----BEGIN CERTIFICATE----- MIIEJTCCAw2gAwIBAgIDAjp3MA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMTQwODI5MjEzOTMyWhcNMjIwNTIwMjEzOTMyWjBHMQswCQYDVQQG EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXUmFwaWRTU0wg U0hBMjU2IENBIC0gRzMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv VJvZWF0eLFbG1eh/9H0WA//Qi1rkjqfdVC7UBMBdmJyNkA+8EGVf2prWRHzAn7Xp SowLBkMEu/SW4ib2YQGRZjEiwzQ0Xz8/kS9EX9zHFLYDn4ZLDqP/oIACg8PTH2lS 1p1kD8mD5xvEcKyU58Okaiy9uJ5p2L4KjxZjWmhxgHsw3hUEv8zTvz5IBVV6s9cQ DAP8m/0Ip4yM26eO8R5j3LMBL3+vV8M8SKeDaCGnL+enP/C1DPz1hNFTvA5yT2AM QriYrRmIV9cE7Ie/fodOoyH5U/02mEiN1vi7SPIpyGTRzFRIU4uvt2UevykzKdkp YEj4/5G8V1jlNS67abZZAgMBAAGjggEdMIIBGTAfBgNVHSMEGDAWgBTAephojYn7 qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUw5zz/NNGCDS7zkZ/oHxb8+IIy1kwEgYD VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwNQYDVR0fBC4wLDAqoCig JoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9iYWwuY3JsMC4GCCsGAQUF BwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMEwGA1UdIARF MEMwQQYKYIZIAYb4RQEHNjAzMDEGCCsGAQUFBwIBFiVodHRwOi8vd3d3Lmdlb3Ry dXN0LmNvbS9yZXNvdXJjZXMvY3BzMA0GCSqGSIb3DQEBCwUAA4IBAQCjWB7GQzKs rC+TeLfqrlRARy1+eI1Q9vhmrNZPc9ZE768LzFvB9E+aj0l+YK/CJ8cW8fuTgZCp fO9vfm5FlBaEvexJ8cQO9K8EWYOHDyw7l8NaEpt7BDV7o5UzCHuTcSJCs6nZb0+B kvwHtnm8hEqddwnxxYny8LScVKoSew26T++TGezvfU5ho452nFnPjJSxhJf3GrkH uLLGTxN5279PURt/aQ1RKsHWFf83UTRlUfQevjhq7A6rvz17OQV79PP7GqHQyH5O ZI3NjGFVkP46yl0lD/gdo0p0Vk8aVUBwdSWmMy66S6VdU5oNMOGNX2Esr8zvsJmh gP8L8mJMcCaY -----END CERTIFICATE----- """ pem_responder_cert = b""" -----BEGIN CERTIFICATE----- MIIBPjCB5KADAgECAgQHW80VMAoGCCqGSM49BAMCMCcxCzAJBgNVBAYTAlVTMRgw FgYDVQQDDA9DcnlwdG9ncmFwaHkgQ0EwHhcNMTgxMDA3MTIzNTEwWhcNMjgxMDA0 MTIzNTEwWjAnMQswCQYDVQQGEwJVUzEYMBYGA1UEAwwPQ3J5cHRvZ3JhcGh5IENB MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbQ2E0N/E3R0zEG+qa+yAFXBY6Fte QzyvFdq7EZHDktlyUllaVJBrbX1ItV0MlayFwwQPhZmuLPpQBzuVKyrUfTAKBggq hkjOPQQDAgNJADBGAiEAo0NQRmfPvhWQpSvJzV+2Ag441Zeckk+bib7swduQIjIC IQCqYD9pArB2SWfmhQCSZkNEATlsPIML8lvlSkbNcrmrqQ== -----END CERTIFICATE----- """ pem_responder_key = b""" -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgO+vsRu8xDIVZE+xh s8ESqJqcpJlwmj8CtF8HPHxrDSGhRANCAARtDYTQ38TdHTMQb6pr7IAVcFjoW15D PK8V2rsRkcOS2XJSWVpUkGttfUi1XQyVrIXDBA+Fma4s+lAHO5UrKtR9 -----END PRIVATE KEY----- """ der_ocsp_req = ( b"0V0T0R0P0N0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x148\xcaF\x8c" b"\x07D\x8d\xf4\x81\x96\xc7mmLpQ\x9e`\xa7\xbd\x04\x14yu\xbb\x84:\xcb" b",\xdez\t\xbe1\x1bC\xbc\x1c*MSX\x02\x15\x00\x98\xd9\xe5\xc0\xb4\xc3" b"sU-\xf7|]\x0f\x1e\xb5\x12\x8eIE\xf9" ) der_ocsp_resp_unauth = b"0\x03\n\x01\x06" OCSP (Online Certificate Status Protocol) is a method of checking the revocation status of certificates. It is specified in :rfc:`6960`, as well as other obsoleted RFCs. Loading Requests ~~~~~~~~~~~~~~~~ .. function:: load_der_ocsp_request(data) .. versionadded:: 2.4 Deserialize an OCSP request from DER encoded data. :param bytes data: The DER encoded OCSP request data. :returns: An instance of :class:`~cryptography.x509.ocsp.OCSPRequest`. .. doctest:: >>> from cryptography.x509 import ocsp >>> ocsp_req = ocsp.load_der_ocsp_request(der_ocsp_req) >>> print(ocsp_req.serial_number) 872625873161273451176241581705670534707360122361 Creating Requests ~~~~~~~~~~~~~~~~~ .. class:: OCSPRequestBuilder .. versionadded:: 2.4 This class is used to create :class:`~cryptography.x509.ocsp.OCSPRequest` objects. .. method:: add_certificate(cert, issuer, algorithm) Adds a request using a certificate, issuer certificate, and hash algorithm. You can call this method or ``add_certificate_by_hash`` only once. :param cert: The :class:`~cryptography.x509.Certificate` whose validity is being checked. :param issuer: The issuer :class:`~cryptography.x509.Certificate` of the certificate that is being checked. :param algorithm: A :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance. For OCSP only :class:`~cryptography.hazmat.primitives.hashes.SHA1`, :class:`~cryptography.hazmat.primitives.hashes.SHA224`, :class:`~cryptography.hazmat.primitives.hashes.SHA256`, :class:`~cryptography.hazmat.primitives.hashes.SHA384`, and :class:`~cryptography.hazmat.primitives.hashes.SHA512` are allowed. .. method:: add_certificate_by_hash(issuer_name_hash, issuer_key_hash, serial_number, algorithm) .. versionadded:: 39.0.0 Adds a request using the issuer's name hash, key hash, the certificate serial number and hash algorithm. You can call this method or ``add_certificate`` only once. :param issuer_name_hash: The hash of the issuer's DER encoded name using the same hash algorithm as the one specified in the ``algorithm`` parameter. :type issuer_name_hash: bytes :param issuer_key_hash: The hash of the issuer's public key bit string DER encoding using the same hash algorithm as the one specified in the ``algorithm`` parameter. :type issuer_key_hash: bytes :param serial_number: The serial number of the certificate being checked. :type serial_number: int :param algorithm: A :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance. For OCSP only :class:`~cryptography.hazmat.primitives.hashes.SHA1`, :class:`~cryptography.hazmat.primitives.hashes.SHA224`, :class:`~cryptography.hazmat.primitives.hashes.SHA256`, :class:`~cryptography.hazmat.primitives.hashes.SHA384`, and :class:`~cryptography.hazmat.primitives.hashes.SHA512` are allowed. .. method:: add_extension(extval, critical) Adds an extension to the request. :param extval: An extension conforming to the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and handled. .. method:: build() :returns: A new :class:`~cryptography.x509.ocsp.OCSPRequest`. .. doctest:: >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.hashes import SHA256 >>> from cryptography.x509 import load_pem_x509_certificate, ocsp >>> cert = load_pem_x509_certificate(pem_cert) >>> issuer = load_pem_x509_certificate(pem_issuer) >>> builder = ocsp.OCSPRequestBuilder() >>> # SHA256 is in this example because while RFC 5019 originally >>> # required SHA1 RFC 6960 updates that to SHA256. >>> # However, depending on your requirements you may need to use SHA1 >>> # for compatibility reasons. >>> builder = builder.add_certificate(cert, issuer, SHA256()) >>> req = builder.build() >>> base64.b64encode(req.public_bytes(serialization.Encoding.DER)) b'MF8wXTBbMFkwVzANBglghkgBZQMEAgEFAAQgn3BowBaoh77h17ULfkX6781dUDPD82Taj8wO1jZWhZoEINxPgjoQth3w7q4AouKKerMxIMIuUG4EuWU2pZfwih52AgI/IA==' Loading Responses ~~~~~~~~~~~~~~~~~ .. function:: load_der_ocsp_response(data) .. versionadded:: 2.4 Deserialize an OCSP response from DER encoded data. :param bytes data: The DER encoded OCSP response data. :returns: An instance of :class:`~cryptography.x509.ocsp.OCSPResponse`. .. doctest:: >>> from cryptography.x509 import ocsp >>> ocsp_resp = ocsp.load_der_ocsp_response(der_ocsp_resp_unauth) >>> print(ocsp_resp.response_status) OCSPResponseStatus.UNAUTHORIZED Creating Responses ~~~~~~~~~~~~~~~~~~ .. class:: OCSPResponseBuilder .. versionadded:: 2.4 This class is used to create :class:`~cryptography.x509.ocsp.OCSPResponse` objects. You cannot set ``produced_at`` on OCSP responses at this time. Instead the field is set to current UTC time when calling ``sign``. For unsuccessful statuses call the class method :meth:`~cryptography.x509.ocsp.OCSPResponseBuilder.build_unsuccessful`. .. method:: add_response(cert, issuer, algorithm, cert_status, this_update, next_update, revocation_time, revocation_reason) This method adds status information about the certificate that was requested to the response. :param cert: The :class:`~cryptography.x509.Certificate` whose validity is being checked. :param issuer: The issuer :class:`~cryptography.x509.Certificate` of the certificate that is being checked. :param algorithm: A :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance. For OCSP only :class:`~cryptography.hazmat.primitives.hashes.SHA1`, :class:`~cryptography.hazmat.primitives.hashes.SHA224`, :class:`~cryptography.hazmat.primitives.hashes.SHA256`, :class:`~cryptography.hazmat.primitives.hashes.SHA384`, and :class:`~cryptography.hazmat.primitives.hashes.SHA512` are allowed. :param cert_status: An item from the :class:`~cryptography.x509.ocsp.OCSPCertStatus` enumeration. :param this_update: A naïve :class:`datetime.datetime` object representing the most recent time in UTC at which the status being indicated is known by the responder to be correct. :param next_update: A naïve :class:`datetime.datetime` object or ``None``. The time in UTC at or before which newer information will be available about the status of the certificate. :param revocation_time: A naïve :class:`datetime.datetime` object or ``None`` if the ``cert`` is not revoked. The time in UTC at which the certificate was revoked. :param revocation_reason: An item from the :class:`~cryptography.x509.ReasonFlags` enumeration or ``None`` if the ``cert`` is not revoked. .. method:: certificates(certs) Add additional certificates that should be used to verify the signature on the response. This is typically used when the responder utilizes an OCSP delegate. :param list certs: A list of :class:`~cryptography.x509.Certificate` objects. .. method:: responder_id(encoding, responder_cert) Set the ``responderID`` on the OCSP response. This is the data a client will use to determine what certificate signed the response. :param responder_cert: The :class:`~cryptography.x509.Certificate` object for the certificate whose private key will sign the OCSP response. If the certificate and key do not match an error will be raised when calling ``sign``. :param encoding: Either :attr:`~cryptography.x509.ocsp.OCSPResponderEncoding.HASH` or :attr:`~cryptography.x509.ocsp.OCSPResponderEncoding.NAME`. .. method:: add_extension(extval, critical) Adds an extension to the response. :param extval: An extension conforming to the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and handled. .. method:: sign(private_key, algorithm) Creates the OCSP response that can then be serialized and sent to clients. This method will create a :attr:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` response. :param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` that will be used to sign the response. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the signature. This must be ``None`` if the ``private_key`` is an :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or an :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` and an instance of a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. Please note that :class:`~cryptography.hazmat.primitives.hashes.SHA1` can not be used here, regardless of if it was used for :meth:`~cryptography.x509.ocsp.OCSPResponseBuilder.add_response` or not. :returns: A new :class:`~cryptography.x509.ocsp.OCSPResponse`. .. doctest:: >>> import datetime >>> from cryptography.hazmat.primitives import hashes, serialization >>> from cryptography.x509 import load_pem_x509_certificate, ocsp >>> cert = load_pem_x509_certificate(pem_cert) >>> issuer = load_pem_x509_certificate(pem_issuer) >>> responder_cert = load_pem_x509_certificate(pem_responder_cert) >>> responder_key = serialization.load_pem_private_key(pem_responder_key, None) >>> builder = ocsp.OCSPResponseBuilder() >>> # SHA256 is in this example because while RFC 5019 originally >>> # required SHA1 RFC 6960 updates that to SHA256. >>> # However, depending on your requirements you may need to use SHA1 >>> # for compatibility reasons. >>> builder = builder.add_response( ... cert=cert, issuer=issuer, algorithm=hashes.SHA256(), ... cert_status=ocsp.OCSPCertStatus.GOOD, ... this_update=datetime.datetime.now(), ... next_update=datetime.datetime.now(), ... revocation_time=None, revocation_reason=None ... ).responder_id( ... ocsp.OCSPResponderEncoding.HASH, responder_cert ... ) >>> response = builder.sign(responder_key, hashes.SHA256()) >>> response.certificate_status .. classmethod:: build_unsuccessful(response_status) Creates an unsigned OCSP response which can then be serialized and sent to clients. ``build_unsuccessful`` may only be called with a :class:`~cryptography.x509.ocsp.OCSPResponseStatus` that is not ``SUCCESSFUL``. Since this is a class method note that no other methods can or should be called as unsuccessful statuses do not encode additional data. :returns: A new :class:`~cryptography.x509.ocsp.OCSPResponse`. .. doctest:: >>> from cryptography.hazmat.primitives import hashes, serialization >>> from cryptography.x509 import load_pem_x509_certificate, ocsp >>> response = ocsp.OCSPResponseBuilder.build_unsuccessful( ... ocsp.OCSPResponseStatus.UNAUTHORIZED ... ) >>> response.response_status Interfaces ~~~~~~~~~~ .. class:: OCSPRequest .. versionadded:: 2.4 An ``OCSPRequest`` is an object containing information about a certificate whose status is being checked. .. attribute:: issuer_key_hash :type: bytes The hash of the certificate issuer's key. The hash algorithm used is defined by the ``hash_algorithm`` property. .. attribute:: issuer_name_hash :type: bytes The hash of the certificate issuer's name. The hash algorithm used is defined by the ``hash_algorithm`` property. .. attribute:: hash_algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` The algorithm used to generate the ``issuer_key_hash`` and ``issuer_name_hash``. .. attribute:: serial_number :type: int The serial number of the certificate to check. .. attribute:: extensions :type: :class:`~cryptography.x509.Extensions` The extensions encoded in the request. .. method:: public_bytes(encoding) :param encoding: The encoding to use. Only :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` is supported. :return bytes: The serialized OCSP request. .. class:: OCSPResponse .. versionadded:: 2.4 An ``OCSPResponse`` is the data provided by an OCSP responder in response to an ``OCSPRequest``. .. attribute:: response_status :type: :class:`~cryptography.x509.ocsp.OCSPResponseStatus` The status of the response. .. attribute:: signature_algorithm_oid :type: :class:`~cryptography.x509.ObjectIdentifier` Returns the object identifier of the signature algorithm used to sign the response. This will be one of the OIDs from :class:`~cryptography.x509.oid.SignatureAlgorithmOID`. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: signature_hash_algorithm .. versionadded:: 2.5 :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` Returns the :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which was used in signing this response. Can be ``None`` if signature did not use separate hash (:attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED25519`, :attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED448`). .. attribute:: signature :type: bytes The signature bytes. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: tbs_response_bytes :type: bytes The DER encoded bytes payload that is hashed and then signed. This data may be used to validate the signature on the OCSP response. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: certificates :type: list A list of zero or more :class:`~cryptography.x509.Certificate` objects used to help build a chain to verify the OCSP response. This situation occurs when the OCSP responder uses a delegate certificate. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: responder_key_hash :type: bytes or None The responder's key hash or ``None`` if the response has a ``responder_name``. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: responder_name :type: :class:`~cryptography.x509.Name` or None The responder's ``Name`` or ``None`` if the response has a ``responder_key_hash``. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: produced_at :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.ocsp.OCSPResponse.produced_at_utc`. A naïve datetime representing the time when the response was produced. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: produced_at_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the time when the response was produced. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL`. .. attribute:: certificate_status :type: :class:`~cryptography.x509.ocsp.OCSPCertStatus` The status of the certificate being checked. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: revocation_time :type: :class:`datetime.datetime` or None .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.ocsp.OCSPResponse.revocation_time_utc`. A naïve datetime representing the time when the certificate was revoked or ``None`` if the certificate has not been revoked. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: revocation_time_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` or None A timezone-aware datetime representing the time when the certificate was revoked or ``None`` if the certificate has not been revoked. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: revocation_reason :type: :class:`~cryptography.x509.ReasonFlags` or None The reason the certificate was revoked or ``None`` if not specified or not revoked. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: this_update :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.ocsp.OCSPResponse.this_update_utc`. A naïve datetime representing the most recent time at which the status being indicated is known by the responder to have been correct. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: this_update_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the most recent time at which the status being indicated is known by the responder to have been correct. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: next_update :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.ocsp.OCSPResponse.next_update_utc`. A naïve datetime representing the time when newer information will be available. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: next_update_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the time when newer information will be available. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: issuer_key_hash :type: bytes The hash of the certificate issuer's key. The hash algorithm used is defined by the ``hash_algorithm`` property. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: issuer_name_hash :type: bytes The hash of the certificate issuer's name. The hash algorithm used is defined by the ``hash_algorithm`` property. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: hash_algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` The algorithm used to generate the ``issuer_key_hash`` and ``issuer_name_hash``. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: serial_number :type: int The serial number of the certificate that was checked. :raises ValueError: If ``response_status`` is not :class:`~cryptography.x509.ocsp.OCSPResponseStatus.SUCCESSFUL` or if multiple SINGLERESPs are present. .. attribute:: extensions :type: :class:`~cryptography.x509.Extensions` The extensions encoded in the response. .. attribute:: single_extensions .. versionadded:: 2.9 :type: :class:`~cryptography.x509.Extensions` The single extensions encoded in the response. .. attribute:: responses .. versionadded:: 37.0.0 :type: an iterator over :class:`~cryptography.x509.ocsp.OCSPSingleResponse` An iterator to access individual SINGLERESP structures. .. method:: public_bytes(encoding) :param encoding: The encoding to use. Only :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` is supported. :return bytes: The serialized OCSP response. .. class:: OCSPResponseStatus .. versionadded:: 2.4 An enumeration of response statuses. .. attribute:: SUCCESSFUL Represents a successful OCSP response. .. attribute:: MALFORMED_REQUEST May be returned by an OCSP responder that is unable to parse a given request. .. attribute:: INTERNAL_ERROR May be returned by an OCSP responder that is currently experiencing operational problems. .. attribute:: TRY_LATER May be returned by an OCSP responder that is overloaded. .. attribute:: SIG_REQUIRED May be returned by an OCSP responder that requires signed OCSP requests. .. attribute:: UNAUTHORIZED May be returned by an OCSP responder when queried for a certificate for which the responder is unaware or an issuer for which the responder is not authoritative. .. class:: OCSPCertStatus .. versionadded:: 2.4 An enumeration of certificate statuses in an OCSP response. .. attribute:: GOOD The value for a certificate that is not revoked. .. attribute:: REVOKED The certificate being checked is revoked. .. attribute:: UNKNOWN The certificate being checked is not known to the OCSP responder. .. class:: OCSPResponderEncoding .. versionadded:: 2.4 An enumeration of ``responderID`` encodings that can be passed to :meth:`~cryptography.x509.ocsp.OCSPResponseBuilder.responder_id`. .. attribute:: HASH Encode the hash of the public key whose corresponding private key signed the response. .. attribute:: NAME Encode the X.509 ``Name`` of the certificate whose private key signed the response. .. class:: OCSPSingleResponse .. versionadded:: 37.0.0 A class representing a single certificate response bundled into a larger OCSPResponse. Accessed via OCSPResponse.responses. .. attribute:: certificate_status :type: :class:`~cryptography.x509.ocsp.OCSPCertStatus` The status of the certificate being checked. .. attribute:: revocation_time :type: :class:`datetime.datetime` or None .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.revocation_time_utc`. A naïve datetime representing the time when the certificate was revoked or ``None`` if the certificate has not been revoked. .. attribute:: revocation_time_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` or None A timezone-aware datetime representing the time when the certificate was revoked or ``None`` if the certificate has not been revoked. .. attribute:: revocation_reason :type: :class:`~cryptography.x509.ReasonFlags` or None The reason the certificate was revoked or ``None`` if not specified or not revoked. .. attribute:: this_update :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.this_update_utc`. A naïve datetime representing the most recent time at which the status being indicated is known by the responder to have been correct. .. attribute:: this_update_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the most recent time at which the status being indicated is known by the responder to have been correct. .. attribute:: next_update :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.ocsp.OCSPSingleResponse.next_update_utc`. A naïve datetime representing the time when newer information will be available. .. attribute:: next_update_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the time when newer information will be available. .. attribute:: issuer_key_hash :type: bytes The hash of the certificate issuer's key. The hash algorithm used is defined by the ``hash_algorithm`` property. .. attribute:: issuer_name_hash :type: bytes The hash of the certificate issuer's name. The hash algorithm used is defined by the ``hash_algorithm`` property. .. attribute:: hash_algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` The algorithm used to generate the ``issuer_key_hash`` and ``issuer_name_hash``. .. attribute:: serial_number :type: int The serial number of the certificate that was checked. cryptography-43.0.0/docs/x509/reference.rst010064400017510000177000004144441464676315000166710ustar 00000000000000X.509 Reference =============== .. currentmodule:: cryptography.x509 .. testsetup:: pem_crl_data = b""" -----BEGIN X509 CRL----- MIIBtDCBnQIBATANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw MDAwWjA+MDwCAQAYDzIwMTUwMTAxMDAwMDAwWjAmMBgGA1UdGAQRGA8yMDE1MDEw MTAwMDAwMFowCgYDVR0VBAMKAQEwDQYJKoZIhvcNAQELBQADggEBABRA4ww50Lz5 zk1j2+aluC4HPHqb7o06h4pTDcCGeXUKXIGeP5ntGGmIoxa26sNoLeOr8+5b43Gf yWraHertllOwaOpNFEe+YZFaE9femtoDbf+GLMvRx/0wDfd3KxPoXnXKMXb2d1w4 RCLgmkYx6JyvS+5ciuLQVIKC+l7jwIUeZFLJMUJ8msM4pFYoGameeZmtjMbd/TNg cVBfmZxNMHuLladJxvSo2esARo0TYPhYsgrREKoHwhpzSxdynjn4bOVkILfguwsN qtEEMZFEv5Kb0GqRp2+Iagv2S6dg9JGvxVdsoGjaB6EbYSZ3Psx4aODasIn11uwo X4B9vUQNXqc= -----END X509 CRL----- """.strip() pem_req_data = b""" -----BEGIN CERTIFICATE REQUEST----- MIICcDCCAVgCAQAwDTELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQCb+ec0zYAYLzk/MDdDJYvzdvEO2ZUrBYM6z1r8NedwpJfxUWqC hvK1cpc9EbQeCwS1eooTIGoNveeCrwL+pWdmf1sh6gz7SsxdN/07nyhSM8M6Xkec +tGrjyi1H/N1afwWXox3WcvBNbxu3Df5RKLDb0yt9aqhmJylbl/tbvgJesXymwmp Rc1vXL0fOedUtuAJ3xQ15M0pgLF8qDn4lySJz25x76pMYPeN5/a7x+SR/jj81kep VaVpuh/2hePV5uwUX3uWoj5sAkrBCifi4NPge0Npd6KeKVvXytLOymH/4+WvV719 wCO+MyrkhpdHSakJDTIaQIxsqVeVVKdPLAPJAgMBAAGgHjAcBgkqhkiG9w0BCQcx DwwNY2hhbGxlbmdlIG1lITANBgkqhkiG9w0BAQsFAAOCAQEAMmgeSa8szbjPFD/4 vcPBr/vBEROFGgL8mX3o5pF9gpr7nRjhLKBkgJvlRm6Ma3Xvdfc/r5Hp2ZBTA7sZ ZYhyeezGfCQN/Qhda1v+sCwG58IjvGfCSS7Y5tGlEBQ4MDf0Q7PYPSxaNUEBH7vo +M7U+nFuNSmyWlt6SFBSkohZkWoVSGx3KsAO+SAHYZ7JtqsAS/dm7Dflp8KxeDg7 wzGBDQRpGF4CpI1VQjGSJQXSEdD+J7mtvBEOD34abRfV6zOUGzOOo3NWE6wNpYgt 0A7gVlzSYpdwqjBdvACfXR2r/mu+4KkAvYh8WwCiTcYgGjl2pT1bO4hEmcJ0RSWy /fGD8Q== -----END CERTIFICATE REQUEST----- """.strip() pem_data = b""" -----BEGIN CERTIFICATE----- MIIDfDCCAmSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJVUzEf MB0GA1UEChMWVGVzdCBDZXJ0aWZpY2F0ZXMgMjAxMTEVMBMGA1UEAxMMVHJ1c3Qg QW5jaG9yMB4XDTEwMDEwMTA4MzAwMFoXDTMwMTIzMTA4MzAwMFowQDELMAkGA1UE BhMCVVMxHzAdBgNVBAoTFlRlc3QgQ2VydGlmaWNhdGVzIDIwMTExEDAOBgNVBAMT B0dvb2QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQWJpHYo37 Xfb7oJSPe+WvfTlzIG21WQ7MyMbGtK/m8mejCzR6c+f/pJhEH/OcDSMsXq8h5kXa BGqWK+vSwD/Pzp5OYGptXmGPcthDtAwlrafkGOS4GqIJ8+k9XGKs+vQUXJKsOk47 RuzD6PZupq4s16xaLVqYbUC26UcY08GpnoLNHJZS/EmXw1ZZ3d4YZjNlpIpWFNHn UGmdiGKXUPX/9H0fVjIAaQwjnGAbpgyCumWgzIwPpX+ElFOUr3z7BoVnFKhIXze+ VmQGSWxZxvWDUN90Ul0tLEpLgk3OVxUB4VUGuf15OJOpgo1xibINPmWt14Vda2N9 yrNKloJGZNqLAgMBAAGjfDB6MB8GA1UdIwQYMBaAFOR9X9FclYYILAWuvnW2ZafZ XahmMB0GA1UdDgQWBBRYAYQkG7wrUpRKPaUQchRR9a86yTAOBgNVHQ8BAf8EBAMC AQYwFwYDVR0gBBAwDjAMBgpghkgBZQMCATABMA8GA1UdEwEB/wQFMAMBAf8wDQYJ KoZIhvcNAQELBQADggEBADWHlxbmdTXNwBL/llwhQqwnazK7CC2WsXBBqgNPWj7m tvQ+aLG8/50Qc2Sun7o2VnwF9D18UUe8Gj3uPUYH+oSI1vDdyKcjmMbKRU4rk0eo 3UHNDXwqIVc9CQS9smyV+x1HCwL4TTrq+LXLKx/qVij0Yqk+UJfAtrg2jnYKXsCu FMBQQnWCGrwa1g1TphRp/RmYHnMynYFmZrXtzFz+U9XEA7C+gPq4kqDI/iVfIT1s 6lBtdB50lrDVwl2oYfAvW/6sC2se2QleZidUmrziVNP4oEeXINokU6T6p//HM1FG QYw2jOvpKcKtWCSAnegEbgsGYzATKjmPJPJ0npHFqzM= -----END CERTIFICATE----- """.strip() cryptography_cert_pem = b""" -----BEGIN CERTIFICATE----- MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu 6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y 3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc 8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF 7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP -----END CERTIFICATE----- """.strip() pem_issuer_public_key = b""" -----BEGIN RSA PUBLIC KEY----- MIICCgKCAgEAyYcqyuT6oQxpvg/VSn2Zc68wZ823D0VAJ2woramFx+2KPWB7B7Ot tVSNRfm0OxJOU3TFAoep54Z2wgOoz0zRmeW6/7gvIuBKp2TW0qZAt3l9sgpE29iw CsoZQlMrLKiDPzCC6Fptk+YPSST9sqwhWDKK1QvOg68DKRxTpEek1hBpC0XRsnuX fvJJQqP39vxzpA0PsicI/wrvWX3vO8z+j9+botPerbeamoeHCsc0xgTLyIygWysB rNskxlzC2U4Kw6mQhGghlLReo1rFsO2/hLTnvLs+Y1lQhnFeOKCx1WVXhzBIyO9B dVVH5Cinb5wBNKvxbevRf4icdWcwtknmgKf69xj7yvFjt/vft74BB1Y5ltLYFmEb 0JBxm5MAJfW4YnMQr0AxdjOhjHq4MN7X4ZzwEpJaYJdRmvMsMGN88cyjYPxsaOG+ dZ/E9MmTjh0gnTjyD4gmsvR/gtTR/XFJ2wkbnnL1RyxNi6j2UW8C7tpNv0TIuArx 3SHGPZN0WsaKTxZPb0L/ob1WBT0mhiq1GzB431cXgbxyh8EdKk+xSptA3V+ca2V2 NuXlJIJaOoPMj/qjDW4I/peKGnk9tLknJ0hpRzz11j77pJsV0dGoGKVHIR2oZqT5 0ZJJb5DXNbiTnspKLNmBt0YlNiXtlCIPxVUkhL141FuCLc8h6FjD6E0CAwEAAQ== -----END RSA PUBLIC KEY----- """.strip() pem_data_to_check = b""" -----BEGIN CERTIFICATE----- MIIErjCCApagAwIBAgIUUrUZsZrrBmRD2hvRuspp+lPsZXcwDQYJKoZIhvcNAQEN BQAwETEPMA0GA1UEAwwGSXNzdWVyMB4XDTE4MTAwODEzNDg1NFoXDTE4MTAxODEz NDg1NFowETEPMA0GA1UEAwwGSXNzdWVyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAyYcqyuT6oQxpvg/VSn2Zc68wZ823D0VAJ2woramFx+2KPWB7B7Ot tVSNRfm0OxJOU3TFAoep54Z2wgOoz0zRmeW6/7gvIuBKp2TW0qZAt3l9sgpE29iw CsoZQlMrLKiDPzCC6Fptk+YPSST9sqwhWDKK1QvOg68DKRxTpEek1hBpC0XRsnuX fvJJQqP39vxzpA0PsicI/wrvWX3vO8z+j9+botPerbeamoeHCsc0xgTLyIygWysB rNskxlzC2U4Kw6mQhGghlLReo1rFsO2/hLTnvLs+Y1lQhnFeOKCx1WVXhzBIyO9B dVVH5Cinb5wBNKvxbevRf4icdWcwtknmgKf69xj7yvFjt/vft74BB1Y5ltLYFmEb 0JBxm5MAJfW4YnMQr0AxdjOhjHq4MN7X4ZzwEpJaYJdRmvMsMGN88cyjYPxsaOG+ dZ/E9MmTjh0gnTjyD4gmsvR/gtTR/XFJ2wkbnnL1RyxNi6j2UW8C7tpNv0TIuArx 3SHGPZN0WsaKTxZPb0L/ob1WBT0mhiq1GzB431cXgbxyh8EdKk+xSptA3V+ca2V2 NuXlJIJaOoPMj/qjDW4I/peKGnk9tLknJ0hpRzz11j77pJsV0dGoGKVHIR2oZqT5 0ZJJb5DXNbiTnspKLNmBt0YlNiXtlCIPxVUkhL141FuCLc8h6FjD6E0CAwEAATAN BgkqhkiG9w0BAQ0FAAOCAgEAVFzNKhEpkH8V8l0NEBAZHNi1e+lcg35fZZ9plqcw Pvk+6M7LW0KD0QWYQWm/dJme4DFsM7lh5u4/m+H4yS7/RP9pads9YwBudchvGR1c S4CCrRAmO8/A0vpQJcEwdS7fdYShBsqMrZ2TvzceVn2dvQbxB6pLkK7KIbDPVJA2 HXFFXe2npHmdc80iTz2ShbdVSvyPvk6vc6NFFCg6lSQFuif3vV0+aYqi6DXv4h92 9qAdES8ZLDfDulxyajyPbtF35f2Of99CumP5UzG4RQbvtI8gShuK1YFYe2sWJFE0 MgSsqGCbl5mcrWxm9YxysRKMZ+Hc4tnkvfmG6GsKtp8u/5pG11XgxXaQl4fZ7JNa QFuD5gEXkEC1mCnhWlnguJgjQlpKadMOORmVTqG9dNQ6GEsha+XWpinm5L9fEZuA F88nNyubKLwEl68N7WWWKQlIl4q8Pe5FEp1pd9rLjOW4gzgYBccIfBK3oMC7uFJg a/9GeOKPiq90UMrCI+CAsIbzuPOaAp3g69JonuDwcs4cu8ui1udxs9q7ox3qSWGZ G1U/hmwvZH9kfIv5BKIzNLy4oxXPDJ7MZIBsxVxaNv8KUQ/JLtpVJa3oYqEx18+V JNr8Pr3y61X8pLmJnaCu+ixshiy2gjxXxDFBVEEt1G9JHrSs3R+yvcHxCrM3+ian Nh4= -----END CERTIFICATE----- """.strip() rsa_pss_pem_cert = b""" -----BEGIN CERTIFICATE----- MIIDfTCCAjCgAwIBAgIUP4D/5rcT93vdYGPhsKf+hbes/JgwQgYJKoZIhvcNAQEK MDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF AKIEAgIA3jAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8wHhcNMjIwNDMwMjAz MTE4WhcNMzMwNDEyMjAzMTE4WjAaMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8w ggEgMAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKCAQEAt1jpboUoNppBVamc+nA+zEjl jn/gPbRFCvyveRd8Yr0p8y1mlmjKXcQlXcHPVM4TopgFXqDykIHXxJxLV56ysb4K UGe0nxpmhEso5ZGUgkDIIoH0NAQAsS8rS2ZzNJcLrLGrMY6DRgFsa+G6h2DvMwgl nsX++a8FIm7Vu+OZnfWpDEuhJU4TRtHVviJSYkFMckyYBB48k1MU+0b4pezHconZ mMEisBFFbwarNvowf2i/tRESe3myKXfiJsZZ2UzdE3FqycSgw1tx8qV/Z8myozUW uihIdw8TGbbsJhEeVFxQEP/DVzC6HHDI3EVpr2jPYeIE60hhZwM7jUmQscLerQID AQABo1MwUTAdBgNVHQ4EFgQUb1QD8QEIQn5DALIAujTDATssNcQwHwYDVR0jBBgw FoAUb1QD8QEIQn5DALIAujTDATssNcQwDwYDVR0TAQH/BAUwAwEB/zBCBgkqhkiG 9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFl AwQCAQUAogQCAgDeA4IBAQAvKBXlx07tdmtfhNTPn16dupBIS5344ZE4tfGSE5Ir iA1X0bukKQ6V+6xJXGreaIw0wvwtIeI/R0JwcR114HBDqjt40vklyNSpGCJzgkfD Q/d8JXN/MLyQrk+5F9JMy+HuZAgefAQAjugC6389Klpqx2Z1CgwmALhjIs48GnMp Iz9vU2O6RDkMBlBRdmfkJVjhhPvJYpDDW1ic5O3pxtMoiC1tAHHMm4gzM1WCFeOh cDNxABlvVNPTnqkOhKBmmwRaBwdvvksgeu2RyBNR0KEy44gWzYB9/Ter2t4Z8ASq qCv8TuYr2QGaCnI2FVS5S9n6l4JNkFHqPMtuhrkr3gEz -----END CERTIFICATE----- """.strip() Loading Certificates ~~~~~~~~~~~~~~~~~~~~ .. function:: load_pem_x509_certificate(data) :canonical: cryptography.x509.base.load_pem_x509_certificate .. versionadded:: 0.7 Deserialize a certificate from PEM encoded data. PEM certificates are base64 decoded and have delimiters that look like ``-----BEGIN CERTIFICATE-----``. :param bytes data: The PEM encoded certificate data. :returns: An instance of :class:`~cryptography.x509.Certificate`. .. doctest:: >>> from cryptography import x509 >>> cert = x509.load_pem_x509_certificate(pem_data) >>> cert.serial_number 2 .. function:: load_pem_x509_certificates(data) :canonical: cryptography.x509.base.load_pem_x509_certificates .. versionadded:: 39.0.0 Deserialize one or more certificates from PEM encoded data. This is like :func:`~cryptography.x509.load_pem_x509_certificate`, but allows for loading multiple certificates (as adjacent PEMs) at once. :param bytes data: One or more PEM-encoded certificates. :returns: list of :class:`~cryptography.x509.Certificate` :raises ValueError: If there isn't at least one certificate, or if any certificate is malformed. .. function:: load_der_x509_certificate(data) :canonical: cryptography.x509.base.load_der_x509_certificate .. versionadded:: 0.7 Deserialize a certificate from DER encoded data. DER is a binary format and is commonly found in files with the ``.cer`` extension (although file extensions are not a guarantee of encoding type). :param bytes data: The DER encoded certificate data. :returns: An instance of :class:`~cryptography.x509.Certificate`. Loading Certificate Revocation Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. function:: load_pem_x509_crl(data) :canonical: cryptography.x509.base.load_pem_x509_crl .. versionadded:: 1.1 Deserialize a certificate revocation list (CRL) from PEM encoded data. PEM requests are base64 decoded and have delimiters that look like ``-----BEGIN X509 CRL-----``. :param bytes data: The PEM encoded request data. :returns: An instance of :class:`~cryptography.x509.CertificateRevocationList`. .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes >>> crl = x509.load_pem_x509_crl(pem_crl_data) >>> isinstance(crl.signature_hash_algorithm, hashes.SHA256) True .. function:: load_der_x509_crl(data) :canonical: cryptography.x509.base.load_der_x509_crl .. versionadded:: 1.1 Deserialize a certificate revocation list (CRL) from DER encoded data. DER is a binary format. :param bytes data: The DER encoded request data. :returns: An instance of :class:`~cryptography.x509.CertificateRevocationList`. Loading Certificate Signing Requests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. function:: load_pem_x509_csr(data) :canonical: cryptography.x509.base.load_pem_x509_csr .. versionadded:: 0.9 Deserialize a certificate signing request (CSR) from PEM encoded data. PEM requests are base64 decoded and have delimiters that look like ``-----BEGIN CERTIFICATE REQUEST-----``. This format is also known as PKCS#10. :param bytes data: The PEM encoded request data. :returns: An instance of :class:`~cryptography.x509.CertificateSigningRequest`. .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes >>> csr = x509.load_pem_x509_csr(pem_req_data) >>> isinstance(csr.signature_hash_algorithm, hashes.SHA256) True .. function:: load_der_x509_csr(data) :canonical: cryptography.x509.base.load_der_x509_csr .. versionadded:: 0.9 Deserialize a certificate signing request (CSR) from DER encoded data. DER is a binary format and is not commonly used with CSRs. :param bytes data: The DER encoded request data. :returns: An instance of :class:`~cryptography.x509.CertificateSigningRequest`. X.509 Certificate Object ~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: Certificate :canonical: cryptography.x509.base.Certificate .. versionadded:: 0.7 .. attribute:: version :type: :class:`~cryptography.x509.Version` The certificate version as an enumeration. Version 3 certificates are the latest version and also the only type you should see in practice. :raises cryptography.x509.InvalidVersion: If the version in the certificate is not a known :class:`X.509 version `. .. doctest:: >>> cert.version .. method:: fingerprint(algorithm) :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the fingerprint. :return bytes: The fingerprint using the supplied hash algorithm, as bytes. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> cert.fingerprint(hashes.SHA256()) b'\x86\xd2\x187Gc\xfc\xe7}[+E9\x8d\xb4\x8f\x10\xe5S\xda\x18u\xbe}a\x03\x08[\xac\xa04?' .. attribute:: serial_number :type: int The serial as a Python integer. .. doctest:: >>> cert.serial_number 2 .. method:: public_key() The public key associated with the certificate. :returns: One of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> public_key = cert.public_key() >>> isinstance(public_key, rsa.RSAPublicKey) True .. attribute:: public_key_algorithm_oid .. versionadded:: 43.0.0 :type: :class:`ObjectIdentifier` Returns the :class:`ObjectIdentifier` of the public key algorithm found inside the certificate. This will be one of the OIDs from :class:`~cryptography.x509.oid.PublicKeyAlgorithmOID`. .. doctest:: >>> cert.public_key_algorithm_oid .. attribute:: not_valid_before :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.Certificate.not_valid_before_utc`. A naïve datetime representing the beginning of the validity period for the certificate in UTC. This value is inclusive. .. doctest:: >>> cert.not_valid_before datetime.datetime(2010, 1, 1, 8, 30) .. attribute:: not_valid_before_utc .. versionadded:: 42.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the beginning of the validity period for the certificate in UTC. This value is inclusive. .. doctest:: >>> cert.not_valid_before_utc datetime.datetime(2010, 1, 1, 8, 30, tzinfo=datetime.timezone.utc) .. attribute:: not_valid_after :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.Certificate.not_valid_after_utc`. A naïve datetime representing the end of the validity period for the certificate in UTC. This value is inclusive. .. doctest:: >>> cert.not_valid_after datetime.datetime(2030, 12, 31, 8, 30) .. attribute:: not_valid_after_utc .. versionadded:: 42.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the end of the validity period for the certificate in UTC. This value is inclusive. .. doctest:: >>> cert.not_valid_after_utc datetime.datetime(2030, 12, 31, 8, 30, tzinfo=datetime.timezone.utc) .. attribute:: issuer .. versionadded:: 0.8 :type: :class:`Name` The :class:`Name` of the issuer. .. attribute:: subject .. versionadded:: 0.8 :type: :class:`Name` The :class:`Name` of the subject. .. attribute:: signature_hash_algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` Returns the :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which was used in signing this certificate. Can be ``None`` if signature did not use separate hash (:attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED25519`, :attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED448`). .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> isinstance(cert.signature_hash_algorithm, hashes.SHA256) True .. attribute:: signature_algorithm_oid .. versionadded:: 1.6 :type: :class:`ObjectIdentifier` Returns the :class:`ObjectIdentifier` of the signature algorithm used to sign the certificate. This will be one of the OIDs from :class:`~cryptography.x509.oid.SignatureAlgorithmOID`. .. doctest:: >>> cert.signature_algorithm_oid .. attribute:: signature_algorithm_parameters .. versionadded:: 41.0.0 Returns the parameters of the signature algorithm used to sign the certificate. For RSA signatures it will return either a :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` object. For ECDSA signatures it will return an :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA`. For EdDSA and DSA signatures it will return ``None``. These objects can be used to verify signatures on the certificate. :returns: None, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`, or :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import padding >>> pss_cert = x509.load_pem_x509_certificate(rsa_pss_pem_cert) >>> isinstance(pss_cert.signature_algorithm_parameters, padding.PSS) True .. attribute:: extensions :type: :class:`Extensions` The extensions encoded in the certificate. :raises cryptography.x509.DuplicateExtension: If more than one extension of the same type is found within the certificate. :raises cryptography.x509.UnsupportedGeneralNameType: If an extension contains a general name that is not supported. .. doctest:: >>> for ext in cert.extensions: ... print(ext) , critical=False, value=)> , critical=False, value=)> , critical=True, value=)> , critical=False, value=, policy_qualifiers=None)>])>)> , critical=True, value=)> .. attribute:: signature .. versionadded:: 1.2 :type: bytes The bytes of the certificate's signature. .. attribute:: tbs_certificate_bytes .. versionadded:: 1.2 :type: bytes The DER encoded bytes payload (as defined by :rfc:`5280`) that is hashed and then signed by the private key of the certificate's issuer. This data may be used to validate a signature, but use extreme caution as certificate validation is a complex problem that involves much more than just signature checks. To validate the signature on a certificate you can do the following. Note: This only verifies that the certificate was signed with the private key associated with the public key provided and does not perform any of the other checks needed for secure certificate validation. Additionally, this example will only work for RSA public keys with ``PKCS1v15`` signatures, and so it can't be used for general purpose signature verification. .. doctest:: >>> from cryptography.hazmat.primitives.serialization import load_pem_public_key >>> from cryptography.hazmat.primitives.asymmetric import padding >>> issuer_public_key = load_pem_public_key(pem_issuer_public_key) >>> cert_to_check = x509.load_pem_x509_certificate(pem_data_to_check) >>> issuer_public_key.verify( ... cert_to_check.signature, ... cert_to_check.tbs_certificate_bytes, ... # Depends on the algorithm used to create the certificate ... padding.PKCS1v15(), ... cert_to_check.signature_hash_algorithm, ... ) An :class:`~cryptography.exceptions.InvalidSignature` exception will be raised if the signature fails to verify. .. method:: verify_directly_issued_by(issuer) .. versionadded:: 40.0.0 :param issuer: The issuer certificate to check against. :type issuer: :class:`~cryptography.x509.Certificate` .. warning:: This method verifies that the certificate issuer name matches the issuer subject name and that the certificate is signed by the issuer's private key. **No other validation is performed.** Callers are responsible for performing any additional validations required for their use case (e.g. checking the validity period, whether the signer is allowed to issue certificates, that the issuing certificate has a strong public key, etc). Validates that the certificate is signed by the provided issuer and that the issuer's subject name matches the issuer name of the certificate. :return: None :raise ValueError: If the issuer name on the certificate does not match the subject name of the issuer or the signature algorithm is unsupported. :raise TypeError: If the issuer does not have a supported public key type. :raise cryptography.exceptions.InvalidSignature: If the signature fails to verify. .. attribute:: tbs_precertificate_bytes .. versionadded:: 38.0.0 :type: bytes :raises ValueError: If the certificate doesn't have the expected Certificate Transparency extensions. The DER encoded bytes payload (as defined by :rfc:`6962`) that is hashed and then signed by the private key of the pre-certificate's issuer. This data may be used to validate a Signed Certificate Timestamp's signature, but use extreme caution as SCT validation is a complex problem that involves much more than just signature checks. This method is primarily useful in the context of programs that interact with and verify the products of Certificate Transparency logs, as specified in :rfc:`6962`. If you are not directly interacting with a Certificate Transparency log, this method unlikely to be what you want. To make unintentional misuse less likely, it raises a ``ValueError`` if the underlying certificate does not contain the expected Certificate Transparency extensions. .. method:: public_bytes(encoding) .. versionadded:: 1.0 :param encoding: The :class:`~cryptography.hazmat.primitives.serialization.Encoding` that will be used to serialize the certificate. :return bytes: The data that can be written to a file or sent over the network to be verified by clients. X.509 CRL (Certificate Revocation List) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateRevocationList :canonical: cryptography.x509.base.CertificateRevocationList .. versionadded:: 1.0 A CertificateRevocationList is an object representing a list of revoked certificates. The object is iterable and will yield the RevokedCertificate objects stored in this CRL. .. doctest:: >>> len(crl) 1 >>> revoked_certificate = crl[0] >>> type(revoked_certificate) >>> for r in crl: ... print(r.serial_number) 0 .. method:: fingerprint(algorithm) :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the fingerprint. :return bytes: The fingerprint using the supplied hash algorithm, as bytes. .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> crl.fingerprint(hashes.SHA256()) b'\xe3\x1d\xb5P\x18\x9ed\x9f\x16O\x9dm\xc1>\x8c\xca\xb1\xc6x?T\x9f\xe9t_\x1d\x8dF8V\xf78' .. method:: get_revoked_certificate_by_serial_number(serial_number) .. versionadded:: 2.3 :param serial_number: The serial as a Python integer. :returns: :class:`~cryptography.x509.RevokedCertificate` if the ``serial_number`` is present in the CRL or ``None`` if it is not. .. attribute:: signature_hash_algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` Returns the :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which was used in signing this CRL. Can be ``None`` if signature did not use separate hash (:attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED25519`, :attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED448`). .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> isinstance(crl.signature_hash_algorithm, hashes.SHA256) True .. attribute:: signature_algorithm_oid .. versionadded:: 1.6 :type: :class:`ObjectIdentifier` Returns the :class:`ObjectIdentifier` of the signature algorithm used to sign the CRL. This will be one of the OIDs from :class:`~cryptography.x509.oid.SignatureAlgorithmOID`. .. doctest:: >>> crl.signature_algorithm_oid .. attribute:: signature_algorithm_parameters .. versionadded:: 42.0.0 Returns the parameters of the signature algorithm used to sign the certificate revocation list. For RSA signatures it will return either a :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` object. For ECDSA signatures it will return an :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA`. For EdDSA and DSA signatures it will return ``None``. These objects can be used to verify the CRL signature. :returns: None, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`, or :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` .. attribute:: issuer :type: :class:`Name` The :class:`Name` of the issuer. .. doctest:: >>> crl.issuer .. attribute:: next_update :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.CertificateRevocationList.next_update_utc`. A naïve datetime representing when the next update to this CRL is expected. .. doctest:: >>> crl.next_update datetime.datetime(2016, 1, 1, 0, 0) .. attribute:: next_update_utc .. versionadded:: 42.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing when the next update to this CRL is expected. .. doctest:: >>> crl.next_update_utc datetime.datetime(2016, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) .. attribute:: last_update :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.CertificateRevocationList.last_update_utc`. A naïve datetime representing when this CRL was last updated. .. doctest:: >>> crl.last_update datetime.datetime(2015, 1, 1, 0, 0) .. attribute:: last_update_utc .. versionadded:: 42.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing when this CRL was last updated. .. doctest:: >>> crl.last_update_utc datetime.datetime(2015, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) .. attribute:: extensions :type: :class:`Extensions` The extensions encoded in the CRL. .. attribute:: signature .. versionadded:: 1.2 :type: bytes The bytes of the CRL's signature. .. attribute:: tbs_certlist_bytes .. versionadded:: 1.2 :type: bytes The DER encoded bytes payload (as defined by :rfc:`5280`) that is hashed and then signed by the private key of the CRL's issuer. This data may be used to validate a signature, but use extreme caution as CRL validation is a complex problem that involves much more than just signature checks. .. method:: public_bytes(encoding) .. versionadded:: 1.2 :param encoding: The :class:`~cryptography.hazmat.primitives.serialization.Encoding` that will be used to serialize the certificate revocation list. :return bytes: The data that can be written to a file or sent over the network and used as part of a certificate verification process. .. method:: is_signature_valid(public_key) .. versionadded:: 2.1 .. warning:: Checking the validity of the signature on the CRL is insufficient to know if the CRL should be trusted. More details are available in :rfc:`5280`. Returns True if the CRL signature is correct for given public key, False otherwise. X.509 Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateBuilder :canonical: cryptography.x509.base.CertificateBuilder .. versionadded:: 1.0 .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> from cryptography.x509.oid import NameOID >>> import datetime >>> one_day = datetime.timedelta(1, 0, 0) >>> private_key = rsa.generate_private_key( ... public_exponent=65537, ... key_size=2048, ... ) >>> public_key = private_key.public_key() >>> builder = x509.CertificateBuilder() >>> builder = builder.subject_name(x509.Name([ ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'), ... ])) >>> builder = builder.issuer_name(x509.Name([ ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'), ... ])) >>> builder = builder.not_valid_before(datetime.datetime.today() - one_day) >>> builder = builder.not_valid_after(datetime.datetime.today() + (one_day * 30)) >>> builder = builder.serial_number(x509.random_serial_number()) >>> builder = builder.public_key(public_key) >>> builder = builder.add_extension( ... x509.SubjectAlternativeName( ... [x509.DNSName('cryptography.io')] ... ), ... critical=False ... ) >>> builder = builder.add_extension( ... x509.BasicConstraints(ca=False, path_length=None), critical=True, ... ) >>> certificate = builder.sign( ... private_key=private_key, algorithm=hashes.SHA256(), ... ) >>> isinstance(certificate, x509.Certificate) True .. method:: issuer_name(name) Sets the issuer's distinguished name. :param name: The :class:`~cryptography.x509.Name` that describes the issuer (CA). .. method:: subject_name(name) Sets the subject's distinguished name. :param name: The :class:`~cryptography.x509.Name` that describes the subject. .. method:: public_key(public_key) Sets the subject's public key. :param public_key: The subject's public key. This can be one of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. method:: serial_number(serial_number) Sets the certificate's serial number (an integer). The CA's policy determines how it attributes serial numbers to certificates. This number must uniquely identify the certificate given the issuer. `CABForum Guidelines`_ require entropy in the serial number to provide protection against hash collision attacks. For more information on secure random number generation, see :doc:`/random-numbers`. :param serial_number: Integer number that will be used by the CA to identify this certificate (most notably during certificate revocation checking). Users should consider using :func:`~cryptography.x509.random_serial_number` when possible. .. method:: not_valid_before(time) Sets the certificate's activation time. This is the time from which clients can start trusting the certificate. It may be different from the time at which the certificate was created. :param time: The :class:`datetime.datetime` object (in UTC) that marks the activation time for the certificate. The certificate may not be trusted clients if it is used before this time. .. method:: not_valid_after(time) Sets the certificate's expiration time. This is the time from which clients should no longer trust the certificate. The CA's policy will determine how long the certificate should remain in use. :param time: The :class:`datetime.datetime` object (in UTC) that marks the expiration time for the certificate. The certificate may not be trusted clients if it is used after this time. .. method:: add_extension(extval, critical) Adds an X.509 extension to the certificate. :param extval: An extension conforming to the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and handled by whoever reads the certificate. .. method:: sign(private_key, algorithm, *, rsa_padding=None) Sign the certificate using the CA's private key. :param private_key: The key that will be used to sign the certificate, one of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the signature. This must be ``None`` if the ``private_key`` is an :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or an :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` and an instance of a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. :param rsa_padding: .. versionadded:: 41.0.0 This is a keyword-only argument. If ``private_key`` is an ``RSAPrivateKey`` then this can be set to either :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign with those respective paddings. If this is ``None`` then RSA keys will default to ``PKCS1v15`` padding. All other key types **must** not pass a value other than ``None``. :type rsa_padding: ``None``, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` :returns: :class:`~cryptography.x509.Certificate` X.509 CSR (Certificate Signing Request) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateSigningRequest :canonical: cryptography.x509.base.CertificateSigningRequest .. versionadded:: 0.9 .. method:: public_key() The public key associated with the request. :returns: One of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> public_key = csr.public_key() >>> isinstance(public_key, rsa.RSAPublicKey) True .. attribute:: public_key_algorithm_oid .. versionadded:: 43.0.0 :type: :class:`ObjectIdentifier` Returns the :class:`ObjectIdentifier` of the public key algorithm found inside the certificate. This will be one of the OIDs from :class:`~cryptography.x509.oid.PublicKeyAlgorithmOID`. .. doctest:: >>> csr.public_key_algorithm_oid .. attribute:: subject :type: :class:`Name` The :class:`Name` of the subject. .. attribute:: signature_hash_algorithm :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` Returns the :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which was used in signing this request. Can be ``None`` if signature did not use separate hash (:attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED25519`, :attr:`~cryptography.x509.oid.SignatureAlgorithmOID.ED448`). .. doctest:: >>> from cryptography.hazmat.primitives import hashes >>> isinstance(csr.signature_hash_algorithm, hashes.SHA256) True .. attribute:: signature_algorithm_oid .. versionadded:: 1.6 :type: :class:`ObjectIdentifier` Returns the :class:`ObjectIdentifier` of the signature algorithm used to sign the request. This will be one of the OIDs from :class:`~cryptography.x509.oid.SignatureAlgorithmOID`. .. doctest:: >>> csr.signature_algorithm_oid .. attribute:: signature_algorithm_parameters .. versionadded:: 42.0.0 Returns the parameters of the signature algorithm used to sign the certificate signing request. For RSA signatures it will return either a :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` object. For ECDSA signatures it will return an :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA`. For EdDSA and DSA signatures it will return ``None``. These objects can be used to verify signatures on the signing request. :returns: None, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`, or :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` .. attribute:: extensions :type: :class:`Extensions` The extensions encoded in the certificate signing request. :raises cryptography.x509.DuplicateExtension: If more than one extension of the same type is found within the certificate signing request. :raises cryptography.x509.UnsupportedGeneralNameType: If an extension contains a general name that is not supported. .. attribute:: attributes .. versionadded:: 36.0.0 :type: :class:`Attributes` The attributes encoded in the certificate signing request. .. method:: public_bytes(encoding) .. versionadded:: 1.0 :param encoding: The :class:`~cryptography.hazmat.primitives.serialization.Encoding` that will be used to serialize the certificate request. :return bytes: The data that can be written to a file or sent over the network to be signed by the certificate authority. .. attribute:: signature .. versionadded:: 1.2 :type: bytes The bytes of the certificate signing request's signature. .. attribute:: tbs_certrequest_bytes .. versionadded:: 1.2 :type: bytes The DER encoded bytes payload (as defined by :rfc:`2986`) that is hashed and then signed by the private key (corresponding to the public key embedded in the CSR). This data may be used to validate the CSR signature. .. attribute:: is_signature_valid .. versionadded:: 1.3 Returns True if the CSR signature is correct, False otherwise. X.509 Certificate Revocation List Builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateRevocationListBuilder :canonical: cryptography.x509.base.CertificateRevocationListBuilder .. versionadded:: 1.2 .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> from cryptography.x509.oid import NameOID >>> import datetime >>> one_day = datetime.timedelta(1, 0, 0) >>> private_key = rsa.generate_private_key( ... public_exponent=65537, ... key_size=2048, ... ) >>> builder = x509.CertificateRevocationListBuilder() >>> builder = builder.issuer_name(x509.Name([ ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io CA'), ... ])) >>> builder = builder.last_update(datetime.datetime.today()) >>> builder = builder.next_update(datetime.datetime.today() + one_day) >>> revoked_cert = x509.RevokedCertificateBuilder().serial_number( ... 333 ... ).revocation_date( ... datetime.datetime.today() ... ).build() >>> builder = builder.add_revoked_certificate(revoked_cert) >>> crl = builder.sign( ... private_key=private_key, algorithm=hashes.SHA256(), ... ) >>> len(crl) 1 .. method:: issuer_name(name) Sets the issuer's distinguished name. :param name: The :class:`~cryptography.x509.Name` that describes the issuer (CA). .. method:: last_update(time) Sets this CRL's activation time. This is the time from which clients can start trusting this CRL. It may be different from the time at which this CRL was created. This is also known as the ``thisUpdate`` time. :param time: The :class:`datetime.datetime` object (in UTC) that marks the activation time for this CRL. The CRL may not be trusted if it is used before this time. .. method:: next_update(time) Sets this CRL's next update time. This is the time by which a new CRL will be issued. The CA is allowed to issue a new CRL before this date, however clients are not required to check for it. :param time: The :class:`datetime.datetime` object (in UTC) that marks the next update time for this CRL. .. method:: add_extension(extval, critical) Adds an X.509 extension to this CRL. :param extval: An extension with the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to ``True`` if the extension must be understood and handled by whoever reads the CRL. .. method:: add_revoked_certificate(revoked_certificate) Adds a revoked certificate to this CRL. :param revoked_certificate: An instance of :class:`~cryptography.x509.RevokedCertificate`. These can be obtained from an existing CRL or created with :class:`~cryptography.x509.RevokedCertificateBuilder`. .. method:: sign(private_key, algorithm, *, rsa_padding=None) Sign this CRL using the CA's private key. :param private_key: The private key that will be used to sign the certificate, one of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the signature. This must be ``None`` if the ``private_key`` is an :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or an :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` and an instance of a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. :param rsa_padding: .. versionadded:: 42.0.0 This is a keyword-only argument. If ``private_key`` is an ``RSAPrivateKey`` then this can be set to either :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign with those respective paddings. If this is ``None`` then RSA keys will default to ``PKCS1v15`` padding. All other key types **must** not pass a value other than ``None``. :type rsa_padding: ``None``, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` :returns: :class:`~cryptography.x509.CertificateRevocationList` X.509 Revoked Certificate Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: RevokedCertificate :canonical: cryptography.x509.base.RevokedCertificate .. versionadded:: 1.0 .. attribute:: serial_number :type: :class:`int` An integer representing the serial number of the revoked certificate. .. doctest:: >>> revoked_certificate.serial_number 0 .. attribute:: revocation_date :type: :class:`datetime.datetime` .. warning:: This property is deprecated and will be removed in a future version. Please switch to the timezone-aware variant :meth:`~cryptography.x509.RevokedCertificate.revocation_date_utc`. A naïve datetime representing the date this certificates was revoked. .. doctest:: >>> revoked_certificate.revocation_date datetime.datetime(2015, 1, 1, 0, 0) .. attribute:: revocation_date_utc .. versionadded:: 42.0.0 :type: :class:`datetime.datetime` A timezone-aware datetime representing the date this certificates was revoked. .. doctest:: >>> revoked_certificate.revocation_date_utc datetime.datetime(2015, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) .. attribute:: extensions :type: :class:`Extensions` The extensions encoded in the revoked certificate. .. doctest:: >>> for ext in revoked_certificate.extensions: ... print(ext) , critical=False, value=)> , critical=False, value=)> X.509 Revoked Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: RevokedCertificateBuilder :canonical: cryptography.x509.base.RevokedCertificateBuilder This class is used to create :class:`~cryptography.x509.RevokedCertificate` objects that can be used with the :class:`~cryptography.x509.CertificateRevocationListBuilder`. .. versionadded:: 1.2 .. doctest:: >>> from cryptography import x509 >>> import datetime >>> builder = x509.RevokedCertificateBuilder() >>> builder = builder.revocation_date(datetime.datetime.today()) >>> builder = builder.serial_number(3333) >>> revoked_certificate = builder.build() >>> isinstance(revoked_certificate, x509.RevokedCertificate) True .. method:: serial_number(serial_number) Sets the revoked certificate's serial number. :param serial_number: Integer number that is used to identify the revoked certificate. .. method:: revocation_date(time) Sets the certificate's revocation date. :param time: The :class:`datetime.datetime` object (in UTC) that marks the revocation time for the certificate. .. method:: add_extension(extval, critical) Adds an X.509 extension to this revoked certificate. :param extval: An instance of one of the :ref:`CRL entry extensions `. :param critical: Set to ``True`` if the extension must be understood and handled. .. method:: build() Create a revoked certificate object. :returns: :class:`~cryptography.x509.RevokedCertificate` X.509 CSR (Certificate Signing Request) Builder Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateSigningRequestBuilder :canonical: cryptography.x509.base.CertificateSigningRequestBuilder .. versionadded:: 1.0 .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> from cryptography.x509.oid import AttributeOID, NameOID >>> private_key = rsa.generate_private_key( ... public_exponent=65537, ... key_size=2048, ... ) >>> builder = x509.CertificateSigningRequestBuilder() >>> builder = builder.subject_name(x509.Name([ ... x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'), ... ])) >>> builder = builder.add_extension( ... x509.BasicConstraints(ca=False, path_length=None), critical=True, ... ) >>> builder = builder.add_attribute( ... AttributeOID.CHALLENGE_PASSWORD, b"changeit" ... ) >>> request = builder.sign( ... private_key, hashes.SHA256() ... ) >>> isinstance(request, x509.CertificateSigningRequest) True .. method:: subject_name(name) :param name: The :class:`~cryptography.x509.Name` of the certificate subject. :returns: A new :class:`~cryptography.x509.CertificateSigningRequestBuilder`. .. method:: add_extension(extension, critical) :param extension: An extension conforming to the :class:`~cryptography.x509.ExtensionType` interface. :param critical: Set to `True` if the extension must be understood and handled by whoever reads the certificate. :returns: A new :class:`~cryptography.x509.CertificateSigningRequestBuilder`. .. method:: add_attribute(oid, value) .. versionadded:: 3.0 :param oid: An :class:`ObjectIdentifier` instance. :param value: The value of the attribute. :type value: bytes :returns: A new :class:`~cryptography.x509.CertificateSigningRequestBuilder`. .. method:: sign(private_key, algorithm, *, rsa_padding=None) :param private_key: The private key that will be used to sign the request. When the request is signed by a certificate authority, the private key's associated public key will be stored in the resulting certificate. One of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that will be used to generate the request signature. This must be ``None`` if the ``private_key`` is an :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey` or an :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey` and an instance of a :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` otherwise. :param rsa_padding: .. versionadded:: 42.0.0 This is a keyword-only argument. If ``private_key`` is an ``RSAPrivateKey`` then this can be set to either :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign with those respective paddings. If this is ``None`` then RSA keys will default to ``PKCS1v15`` padding. All other key types **must** not pass a value other than ``None``. :type rsa_padding: ``None``, :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`, or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` :returns: A new :class:`~cryptography.x509.CertificateSigningRequest`. .. class:: Name :canonical: cryptography.x509.name.Name .. versionadded:: 0.8 An X509 Name is an ordered list of attributes. The object is iterable to get every attribute or you can use :meth:`Name.get_attributes_for_oid` to obtain the specific type you want. Names are sometimes represented as a slash or comma delimited string (e.g. ``/CN=mydomain.com/O=My Org/C=US`` or ``CN=mydomain.com,O=My Org,C=US``). Technically, a Name is a list of *sets* of attributes, called *Relative Distinguished Names* or *RDNs*, although multi-valued RDNs are rarely encountered. The iteration order of values within a multi-valued RDN is preserved. If you need to handle multi-valued RDNs, the ``rdns`` property gives access to an ordered list of :class:`RelativeDistinguishedName` objects. A Name can be initialized with an iterable of :class:`NameAttribute` (the common case where each RDN has a single attribute) or an iterable of :class:`RelativeDistinguishedName` objects (in the rare case of multi-valued RDNs). .. doctest:: >>> len(cert.subject) 3 >>> for attribute in cert.subject: ... print(attribute) , value='US')> , value='Test Certificates 2011')> , value='Good CA')> .. attribute:: rdns .. versionadded:: 1.6 :type: list of :class:`RelativeDistinguishedName` .. classmethod:: from_rfc4514_string(data, attr_name_overrides=None) .. versionadded: 37.0.0 :param str data: An :rfc:`4514` string. :param attr_name_overrides: Specify custom OID to name mappings, which can be used to match vendor-specific extensions. See :class:`~cryptography.x509.oid.NameOID` for common attribute OIDs. :returns: A :class:`Name` parsed from ``data``. .. doctest:: >>> x509.Name.from_rfc4514_string("CN=cryptography.io") >>> x509.Name.from_rfc4514_string("E=pyca@cryptography.io", {"E": NameOID.EMAIL_ADDRESS}) .. method:: get_attributes_for_oid(oid) :param oid: An :class:`ObjectIdentifier` instance. :returns: A list of :class:`NameAttribute` instances that match the OID provided. If nothing matches an empty list will be returned. .. doctest:: >>> cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) [, value='Good CA')>] .. method:: public_bytes() .. versionadded:: 1.6 :return bytes: The DER encoded name. .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 .. versionchanged:: 36.0.0 Added ``attr_name_overrides`` parameter. Format the given name as a :rfc:`4514` Distinguished Name string, for example ``CN=mydomain.com,O=My Org,C=US``. By default, attributes ``CN``, ``L``, ``ST``, ``O``, ``OU``, ``C``, ``STREET``, ``DC``, ``UID`` are represented by their short name. Unrecognized attributes are formatted as dotted OID strings. Example: .. doctest:: >>> name = x509.Name([ ... x509.NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"), ... x509.NameAttribute(NameOID.COMMON_NAME, "Santa Claus"), ... ]) >>> name.rfc4514_string() 'CN=Santa Claus,1.2.840.113549.1.9.1=santa@north.pole' >>> name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"}) 'CN=Santa Claus,E=santa@north.pole' :type attr_name_overrides: Dict-like mapping from :class:`~cryptography.x509.ObjectIdentifier` to ``str`` :param attr_name_overrides: Specify custom OID to name mappings, which can be used to match vendor-specific extensions. See :class:`~cryptography.x509.oid.NameOID` for common attribute OIDs. :rtype: str .. class:: Version :canonical: cryptography.x509.base.Version .. versionadded:: 0.7 An enumeration for X.509 versions. .. attribute:: v1 For version 1 X.509 certificates. .. attribute:: v3 For version 3 X.509 certificates. .. class:: NameAttribute :canonical: cryptography.x509.name.NameAttribute .. versionadded:: 0.8 An X.509 name consists of a list of :class:`RelativeDistinguishedName` instances, which consist of a set of :class:`NameAttribute` instances. .. attribute:: oid :type: :class:`ObjectIdentifier` The attribute OID. .. attribute:: value :type: ``str`` or ``bytes`` The value of the attribute. This will generally be a ``str``, the only times it can be a ``bytes`` is when :attr:`oid` is ``X500_UNIQUE_IDENTIFIER``. .. attribute:: rfc4514_attribute_name .. versionadded:: 35.0.0 :type: str The :rfc:`4514` short attribute name (for example "CN"), or the OID dotted string if a short name is unavailable. .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 .. versionchanged:: 36.0.0 Added ``attr_name_overrides`` parameter. :return str: Format the given attribute as a :rfc:`4514` Distinguished Name string. :type attr_name_overrides: Dict-like mapping from :class:`~cryptography.x509.ObjectIdentifier` to ``str`` :param attr_name_overrides: Specify custom OID to name mappings, which can be used to match vendor-specific extensions. .. class:: RelativeDistinguishedName(attributes) :canonical: cryptography.x509.name.RelativeDistinguishedName .. versionadded:: 1.6 A relative distinguished name is a non-empty set of name attributes. The object is iterable to get every attribute, preserving the original order. Passing duplicate attributes to the constructor raises ``ValueError``. .. method:: get_attributes_for_oid(oid) :param oid: An :class:`ObjectIdentifier` instance. :returns: A list of :class:`NameAttribute` instances that match the OID provided. The list should contain zero or one values. .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 .. versionchanged:: 36.0.0 Added ``attr_name_overrides`` parameter. :return str: Format the given RDN set as a :rfc:`4514` Distinguished Name string. :type attr_name_overrides: Dict-like mapping from :class:`~cryptography.x509.ObjectIdentifier` to ``str`` :param attr_name_overrides: Specify custom OID to name mappings, which can be used to match vendor-specific extensions. .. class:: ObjectIdentifier :canonical: ObjectIdentifier .. versionadded:: 0.8 Object identifiers (frequently seen abbreviated as OID) identify the type of a value (see: :class:`NameAttribute`). .. attribute:: dotted_string :type: :class:`str` The dotted string value of the OID (e.g. ``"2.5.4.3"``) .. _general_name_classes: General Name Classes ~~~~~~~~~~~~~~~~~~~~ .. class:: GeneralName :canonical: cryptography.x509.general_name.GeneralName .. versionadded:: 0.9 This is the generic interface that all the following classes are registered against. .. class:: RFC822Name(value) :canonical: cryptography.x509.general_name.RFC822Name .. versionadded:: 0.9 .. versionchanged:: 3.1 :term:`U-label` support has been removed. Encode them to :term:`A-label` before use. This corresponds to an email address. For example, ``user@example.com``. :param value: The email address. If the address contains an internationalized domain name then it must be encoded to an :term:`A-label` string before being passed. :raises ValueError: If the provided string is not an :term:`A-label`. .. attribute:: value :type: str .. class:: DNSName(value) :canonical: cryptography.x509.general_name.DNSName .. versionadded:: 0.9 .. versionchanged:: 3.1 :term:`U-label` support has been removed. Encode them to :term:`A-label` before use. This corresponds to a domain name. For example, ``cryptography.io``. :param value: The domain name. If it is an internationalized domain name then it must be encoded to an :term:`A-label` string before being passed. :raises ValueError: If the provided string is not an :term:`A-label`. :type: str .. attribute:: value :type: str .. class:: DirectoryName(value) :canonical: cryptography.x509.general_name.DirectoryName .. versionadded:: 0.9 This corresponds to a directory name. .. attribute:: value :type: :class:`Name` .. class:: UniformResourceIdentifier(value) :canonical: cryptography.x509.general_name.UniformResourceIdentifier .. versionadded:: 0.9 .. versionchanged:: 3.1 :term:`U-label` support has been removed. Encode them to :term:`A-label` before use. This corresponds to a uniform resource identifier. For example, ``https://cryptography.io``. :param value: The URI. If it contains an internationalized domain name then it must be encoded to an :term:`A-label` string before being passed. :raises ValueError: If the provided string is not an :term:`A-label`. .. attribute:: value :type: str .. class:: IPAddress(value) :canonical: cryptography.x509.general_name.IPAddress .. versionadded:: 0.9 This corresponds to an IP address. .. attribute:: value :type: :class:`~ipaddress.IPv4Address`, :class:`~ipaddress.IPv6Address`, :class:`~ipaddress.IPv4Network`, or :class:`~ipaddress.IPv6Network`. .. class:: RegisteredID(value) :canonical: cryptography.x509.general_name.RegisteredID .. versionadded:: 0.9 This corresponds to a registered ID. .. attribute:: value :type: :class:`ObjectIdentifier` .. class:: OtherName(type_id, value) :canonical: cryptography.x509.general_name.OtherName .. versionadded:: 1.0 This corresponds to an ``otherName.`` An ``otherName`` has a type identifier and a value represented in binary DER format. .. attribute:: type_id :type: :class:`ObjectIdentifier` .. attribute:: value :type: bytes X.509 Extensions ~~~~~~~~~~~~~~~~ .. class:: Extensions :canonical: cryptography.x509.extensions.Extensions .. versionadded:: 0.9 An X.509 Extensions instance is an ordered list of extensions. The object is iterable to get every extension. .. method:: get_extension_for_oid(oid) :param oid: An :class:`ObjectIdentifier` instance. :returns: An instance of :class:`Extension`. :raises cryptography.x509.ExtensionNotFound: If the certificate does not have the extension requested. .. doctest:: >>> from cryptography.x509.oid import ExtensionOID >>> cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) , critical=True, value=)> .. method:: get_extension_for_class(extclass) .. versionadded:: 1.1 :param extclass: An extension class. :returns: An instance of :class:`Extension`. :raises cryptography.x509.ExtensionNotFound: If the certificate does not have the extension requested. .. doctest:: >>> from cryptography import x509 >>> cert.extensions.get_extension_for_class(x509.BasicConstraints) , critical=True, value=)> .. class:: Extension :canonical: cryptography.x509.extensions.Extension .. versionadded:: 0.9 .. attribute:: oid :type: :class:`ObjectIdentifier` One of the :class:`~cryptography.x509.oid.ExtensionOID` OIDs. .. attribute:: critical :type: bool Determines whether a given extension is critical or not. :rfc:`5280` requires that "A certificate-using system MUST reject the certificate if it encounters a critical extension it does not recognize or a critical extension that contains information that it cannot process". .. attribute:: value Returns an instance of the extension type corresponding to the OID. .. class:: ExtensionType :canonical: cryptography.x509.extensions.ExtensionType .. versionadded:: 1.0 This is the interface against which all the following extension types are registered. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns the OID associated with the given extension type. .. method:: public_bytes() .. versionadded:: 36.0.0 :return bytes: A bytes string representing the extension's DER encoded value. .. class:: KeyUsage(digital_signature, content_commitment, key_encipherment, data_encipherment, key_agreement, key_cert_sign, crl_sign, encipher_only, decipher_only) :canonical: cryptography.x509.extensions.KeyUsage .. versionadded:: 0.9 The key usage extension defines the purpose of the key contained in the certificate. The usage restriction might be employed when a key that could be used for more than one operation is to be restricted. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.KEY_USAGE`. .. attribute:: digital_signature :type: bool This purpose is set to true when the subject public key is used for verifying digital signatures, other than signatures on certificates (``key_cert_sign``) and CRLs (``crl_sign``). .. attribute:: content_commitment :type: bool This purpose is set to true when the subject public key is used for verifying digital signatures, other than signatures on certificates (``key_cert_sign``) and CRLs (``crl_sign``). It is used to provide a non-repudiation service that protects against the signing entity falsely denying some action. In the case of later conflict, a reliable third party may determine the authenticity of the signed data. This was called ``non_repudiation`` in older revisions of the X.509 specification. .. attribute:: key_encipherment :type: bool This purpose is set to true when the subject public key is used for enciphering private or secret keys. .. attribute:: data_encipherment :type: bool This purpose is set to true when the subject public key is used for directly enciphering raw user data without the use of an intermediate symmetric cipher. .. attribute:: key_agreement :type: bool This purpose is set to true when the subject public key is used for key agreement. For example, when a Diffie-Hellman key is to be used for key management, then this purpose is set to true. .. attribute:: key_cert_sign :type: bool This purpose is set to true when the subject public key is used for verifying signatures on public key certificates. If this purpose is set to true then ``ca`` must be true in the :class:`BasicConstraints` extension. .. attribute:: crl_sign :type: bool This purpose is set to true when the subject public key is used for verifying signatures on certificate revocation lists. .. attribute:: encipher_only :type: bool When this purposes is set to true and the ``key_agreement`` purpose is also set, the subject public key may be used only for enciphering data while performing key agreement. :raises ValueError: This is raised if accessed when ``key_agreement`` is false. .. attribute:: decipher_only :type: bool When this purposes is set to true and the ``key_agreement`` purpose is also set, the subject public key may be used only for deciphering data while performing key agreement. :raises ValueError: This is raised if accessed when ``key_agreement`` is false. .. class:: BasicConstraints(ca, path_length) :canonical: cryptography.x509.extensions.BasicConstraints .. versionadded:: 0.9 Basic constraints is an X.509 extension type that defines whether a given certificate is allowed to sign additional certificates and what path length restrictions may exist. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.BASIC_CONSTRAINTS`. .. attribute:: ca :type: bool Whether the certificate can sign certificates. .. attribute:: path_length :type: int or None The maximum path length for certificates subordinate to this certificate. This attribute only has meaning if ``ca`` is true. If ``ca`` is true then a path length of None means there's no restriction on the number of subordinate CAs in the certificate chain. If it is zero or greater then it defines the maximum length for a subordinate CA's certificate chain. For example, a ``path_length`` of 1 means the certificate can sign a subordinate CA, but the subordinate CA is not allowed to create subordinates with ``ca`` set to true. .. class:: ExtendedKeyUsage(usages) :canonical: cryptography.x509.extensions.ExtendedKeyusage .. versionadded:: 0.9 This extension indicates one or more purposes for which the certified public key may be used, in addition to or in place of the basic purposes indicated in the key usage extension. The object is iterable to obtain the list of :class:`~cryptography.x509.oid.ExtendedKeyUsageOID` OIDs present. :param list usages: A list of :class:`~cryptography.x509.oid.ExtendedKeyUsageOID` OIDs. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.EXTENDED_KEY_USAGE`. .. class:: OCSPNoCheck() :canonical: cryptography.x509.extensions.OCSPNoCheck .. versionadded:: 1.0 This presence of this extension indicates that an OCSP client can trust a responder for the lifetime of the responder's certificate. CAs issuing such a certificate should realize that a compromise of the responder's key is as serious as the compromise of a CA key used to sign CRLs, at least for the validity period of this certificate. CA's may choose to issue this type of certificate with a very short lifetime and renew it frequently. This extension is only relevant when the certificate is an authorized OCSP responder. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.OCSP_NO_CHECK`. .. class:: TLSFeature(features) :canonical: cryptography.x509.extensions.TLSFeature .. versionadded:: 2.1 The TLS Feature extension is defined in :rfc:`7633` and is used in certificates for OCSP Must-Staple. The object is iterable to get every element. :param list features: A list of features to enable from the :class:`~cryptography.x509.TLSFeatureType` enum. At this time only ``status_request`` or ``status_request_v2`` are allowed. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.TLS_FEATURE`. .. class:: TLSFeatureType :canonical: cryptography.x509.extensions.TLSFeatureType .. versionadded:: 2.1 An enumeration of TLS Feature types. .. attribute:: status_request This feature type is defined in :rfc:`6066` and, when embedded in an X.509 certificate, signals to the client that it should require a stapled OCSP response in the TLS handshake. Commonly known as OCSP Must-Staple in certificates. .. attribute:: status_request_v2 This feature type is defined in :rfc:`6961`. This value is not commonly used and if you want to enable OCSP Must-Staple you should use ``status_request``. .. class:: NameConstraints(permitted_subtrees, excluded_subtrees) :canonical: cryptography.x509.extensions.NameConstraints .. versionadded:: 1.0 The name constraints extension, which only has meaning in a CA certificate, defines a name space within which all subject names in certificates issued beneath the CA certificate must (or must not) be in. For specific details on the way this extension should be processed see :rfc:`5280`. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.NAME_CONSTRAINTS`. .. attribute:: permitted_subtrees :type: list of :class:`GeneralName` objects or None The set of permitted name patterns. If a name matches this and an element in ``excluded_subtrees`` it is invalid. At least one of ``permitted_subtrees`` and ``excluded_subtrees`` will be non-None. .. attribute:: excluded_subtrees :type: list of :class:`GeneralName` objects or None Any name matching a restriction in the ``excluded_subtrees`` field is invalid regardless of information appearing in the ``permitted_subtrees``. At least one of ``permitted_subtrees`` and ``excluded_subtrees`` will be non-None. .. class:: AuthorityKeyIdentifier(key_identifier, authority_cert_issuer, authority_cert_serial_number) :canonical: cryptography.x509.extensions.AuthorityKeyIdentifier .. versionadded:: 0.9 The authority key identifier extension provides a means of identifying the public key corresponding to the private key used to sign a certificate. This extension is typically used to assist in determining the appropriate certificate chain. For more information about generation and use of this extension see `RFC 5280 section 4.2.1.1`_. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.AUTHORITY_KEY_IDENTIFIER`. .. attribute:: key_identifier :type: bytes A value derived from the public key used to verify the certificate's signature. .. attribute:: authority_cert_issuer :type: A list of :class:`GeneralName` instances or None The :class:`GeneralName` (one or multiple) of the issuer's issuer. .. attribute:: authority_cert_serial_number :type: int or None The serial number of the issuer's issuer. .. classmethod:: from_issuer_public_key(public_key) .. versionadded:: 1.0 .. note:: This method should be used if the issuer certificate does not contain a :class:`~cryptography.x509.SubjectKeyIdentifier`. Otherwise, use :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier`. Creates a new AuthorityKeyIdentifier instance using the public key provided to generate the appropriate digest. This should be the **issuer's public key**. The resulting object will contain :attr:`~cryptography.x509.AuthorityKeyIdentifier.key_identifier`, but :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_issuer` and :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_serial_number` will be None. The generated ``key_identifier`` is the SHA1 hash of the ``subjectPublicKey`` ASN.1 bit string. This is the first recommendation in :rfc:`5280` section 4.2.1.2. :param public_key: One of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPublicKeyTypes`. .. doctest:: >>> from cryptography import x509 >>> issuer_cert = x509.load_pem_x509_certificate(pem_data) >>> x509.AuthorityKeyIdentifier.from_issuer_public_key(issuer_cert.public_key()) .. classmethod:: from_issuer_subject_key_identifier(ski) .. versionadded:: 1.3 .. note:: This method should be used if the issuer certificate contains a :class:`~cryptography.x509.SubjectKeyIdentifier`. Otherwise, use :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_public_key`. Creates a new AuthorityKeyIdentifier instance using the SubjectKeyIdentifier from the issuer certificate. The resulting object will contain :attr:`~cryptography.x509.AuthorityKeyIdentifier.key_identifier`, but :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_issuer` and :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_serial_number` will be None. :param ski: The :class:`~cryptography.x509.SubjectKeyIdentifier` from the issuer certificate. .. doctest:: >>> from cryptography import x509 >>> issuer_cert = x509.load_pem_x509_certificate(pem_data) >>> ski_ext = issuer_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) >>> x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ski_ext.value) .. class:: SubjectKeyIdentifier(digest) :canonical: cryptography.x509.extensions.SubjectKeyIdentifier .. versionadded:: 0.9 The subject key identifier extension provides a means of identifying certificates that contain a particular public key. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_KEY_IDENTIFIER`. .. attribute:: key_identifier .. versionadded:: 35.0.0 :type: bytes The binary value of the identifier. .. attribute:: digest :type: bytes The binary value of the identifier. An alias of ``key_identifier``. .. classmethod:: from_public_key(public_key) .. versionadded:: 1.0 Creates a new SubjectKeyIdentifier instance using the public key provided to generate the appropriate digest. This should be the public key that is in the certificate. The generated digest is the SHA1 hash of the ``subjectPublicKey`` ASN.1 bit string. This is the first recommendation in :rfc:`5280` section 4.2.1.2. :param public_key: One of :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: >>> from cryptography import x509 >>> csr = x509.load_pem_x509_csr(pem_req_data) >>> x509.SubjectKeyIdentifier.from_public_key(csr.public_key()) .. class:: SubjectAlternativeName(general_names) :canonical: cryptography.x509.extensions.SubjectAlternativeName .. versionadded:: 0.9 Subject alternative name is an X.509 extension that provides a list of :ref:`general name ` instances that provide a set of identities for which the certificate is valid. The object is iterable to get every element. :param list general_names: A list of :class:`GeneralName` instances. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME`. .. method:: get_values_for_type(type) :param type: A :class:`GeneralName` instance. This is one of the :ref:`general name classes `. :returns: A list of values extracted from the matched general names. The type of the returned values depends on the :class:`GeneralName`. .. doctest:: >>> from cryptography import x509 >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.x509.oid import ExtensionOID >>> cert = x509.load_pem_x509_certificate(cryptography_cert_pem) >>> # Get the subjectAltName extension from the certificate >>> ext = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME) >>> # Get the dNSName entries from the SAN extension >>> ext.value.get_values_for_type(x509.DNSName) ['www.cryptography.io', 'cryptography.io'] .. class:: IssuerAlternativeName(general_names) :canonical: cryptography.x509.extensions.IssuerAlternativeName .. versionadded:: 1.0 Issuer alternative name is an X.509 extension that provides a list of :ref:`general name ` instances that provide a set of identities for the certificate issuer. The object is iterable to get every element. :param list general_names: A list of :class:`GeneralName` instances. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.ISSUER_ALTERNATIVE_NAME`. .. method:: get_values_for_type(type) :param type: A :class:`GeneralName` instance. This is one of the :ref:`general name classes `. :returns: A list of values extracted from the matched general names. .. class:: PrecertificateSignedCertificateTimestamps(scts) :canonical: cryptography.x509.extensions.PrecertificateSignedCertificateTimestamps .. versionadded:: 2.0 This extension contains :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp` instances which were issued for the pre-certificate corresponding to this certificate. These can be used to verify that the certificate is included in a public Certificate Transparency log. It is an iterable containing one or more :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp` objects. :param list scts: A ``list`` of :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp` objects. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS`. .. class:: PrecertPoison() :canonical: cryptography.x509.extensions.PrecertPoison .. versionadded:: 2.4 This extension indicates that the certificate should not be treated as a certificate for the purposes of validation, but is instead for submission to a certificate transparency log in order to obtain SCTs which will be embedded in a :class:`PrecertificateSignedCertificateTimestamps` extension on the final certificate. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.PRECERT_POISON`. .. class:: SignedCertificateTimestamps(scts) :canonical: cryptography.x509.extensions.SignedCertificateTimestamps .. versionadded:: 3.0 This extension contains :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp` instances. These can be used to verify that the certificate is included in a public Certificate Transparency log. This extension is only found in OCSP responses. For SCTs in an X.509 certificate see :class:`~cryptography.x509.PrecertificateSignedCertificateTimestamps`. It is an iterable containing one or more :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp` objects. :param list scts: A ``list`` of :class:`~cryptography.x509.certificate_transparency.SignedCertificateTimestamp` objects. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS`. .. class:: DeltaCRLIndicator(crl_number) :canonical: cryptography.x509.extensions.DeltaCRLIndicator .. versionadded:: 2.1 The delta CRL indicator is a CRL extension that identifies a CRL as being a delta CRL. Delta CRLs contain updates to revocation information previously distributed, rather than all the information that would appear in a complete CRL. :param int crl_number: The CRL number of the complete CRL that the delta CRL is updating. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.DELTA_CRL_INDICATOR`. .. attribute:: crl_number :type: int .. class:: AuthorityInformationAccess(descriptions) :canonical: cryptography.x509.extensions.AuthorityInformationAccess .. versionadded:: 0.9 The authority information access extension indicates how to access information and services for the issuer of the certificate in which the extension appears. Information and services may include online validation services (such as OCSP) and issuer data. It is an iterable, containing one or more :class:`~cryptography.x509.AccessDescription` instances. :param list descriptions: A list of :class:`AccessDescription` objects. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.AUTHORITY_INFORMATION_ACCESS`. .. class:: SubjectInformationAccess(descriptions) :canonical: cryptography.x509.extensions.SubjectInformationAccess .. versionadded:: 3.0 The subject information access extension indicates how to access information and services for the subject of the certificate in which the extension appears. When the subject is a CA, information and services may include certificate validation services and CA policy data. When the subject is an end entity, the information describes the type of services offered and how to access them. It is an iterable, containing one or more :class:`~cryptography.x509.AccessDescription` instances. :param list descriptions: A list of :class:`AccessDescription` objects. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_INFORMATION_ACCESS`. .. class:: AccessDescription(access_method, access_location) :canonical: cryptography.x509.extensions.AccessDescription .. versionadded:: 0.9 .. attribute:: access_method :type: :class:`ObjectIdentifier` The access method defines what the ``access_location`` means. It must be :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` or :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS` when used with :class:`~cryptography.x509.AuthorityInformationAccess` or :attr:`~cryptography.x509.oid.SubjectInformationAccessOID.CA_REPOSITORY` when used with :class:`~cryptography.x509.SubjectInformationAccess`. If it is :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` the access location will be where to obtain OCSP information for the certificate. If it is :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS` the access location will provide additional information about the issuing certificate. Finally, if it is :attr:`~cryptography.x509.oid.SubjectInformationAccessOID.CA_REPOSITORY` the access location will be the location of the CA's repository. .. attribute:: access_location :type: :class:`GeneralName` Where to access the information defined by the access method. .. class:: FreshestCRL(distribution_points) :canonical: cryptography.x509.extensions.FreshestCRL .. versionadded:: 2.1 The freshest CRL extension (also known as Delta CRL Distribution Point) identifies how delta CRL information is obtained. It is an iterable, containing one or more :class:`DistributionPoint` instances. :param list distribution_points: A list of :class:`DistributionPoint` instances. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.FRESHEST_CRL`. .. class:: CRLDistributionPoints(distribution_points) :canonical: cryptography.x509.extensions.CRLDistributionPoints .. versionadded:: 0.9 The CRL distribution points extension identifies how CRL information is obtained. It is an iterable, containing one or more :class:`DistributionPoint` instances. :param list distribution_points: A list of :class:`DistributionPoint` instances. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.CRL_DISTRIBUTION_POINTS`. .. class:: DistributionPoint(full_name, relative_name, reasons, crl_issuer) :canonical: cryptography.x509.extensions.DistributionPoint .. versionadded:: 0.9 .. attribute:: full_name :type: list of :class:`GeneralName` instances or None This field describes methods to retrieve the CRL. At most one of ``full_name`` or ``relative_name`` will be non-None. .. attribute:: relative_name :type: :class:`RelativeDistinguishedName` or None This field describes methods to retrieve the CRL relative to the CRL issuer. At most one of ``full_name`` or ``relative_name`` will be non-None. .. versionchanged:: 1.6 Changed from :class:`Name` to :class:`RelativeDistinguishedName`. .. attribute:: crl_issuer :type: list of :class:`GeneralName` instances or None Information about the issuer of the CRL. .. attribute:: reasons :type: frozenset of :class:`ReasonFlags` or None The reasons a given distribution point may be used for when performing revocation checks. .. class:: ReasonFlags :canonical: cryptography.x509.extensions.ReasonFlags .. versionadded:: 0.9 An enumeration for CRL reasons. .. attribute:: unspecified It is unspecified why the certificate was revoked. This reason cannot be used as a reason flag in a :class:`DistributionPoint`. .. attribute:: key_compromise This reason indicates that the private key was compromised. .. attribute:: ca_compromise This reason indicates that the CA issuing the certificate was compromised. .. attribute:: affiliation_changed This reason indicates that the subject's name or other information has changed. .. attribute:: superseded This reason indicates that a certificate has been superseded. .. attribute:: cessation_of_operation This reason indicates that the certificate is no longer required. .. attribute:: certificate_hold This reason indicates that the certificate is on hold. .. attribute:: privilege_withdrawn This reason indicates that the privilege granted by this certificate have been withdrawn. .. attribute:: aa_compromise When an attribute authority has been compromised. .. attribute:: remove_from_crl This reason indicates that the certificate was on hold and should be removed from the CRL. This reason cannot be used as a reason flag in a :class:`DistributionPoint`. .. class:: InhibitAnyPolicy(skip_certs) :canonical: cryptography.x509.extensions.InhibitAnyPolicy .. versionadded:: 1.0 The inhibit ``anyPolicy`` extension indicates that the special OID :attr:`~cryptography.x509.oid.CertificatePoliciesOID.ANY_POLICY`, is not considered an explicit match for other :class:`CertificatePolicies` except when it appears in an intermediate self-issued CA certificate. The value indicates the number of additional non-self-issued certificates that may appear in the path before :attr:`~cryptography.x509.oid.CertificatePoliciesOID.ANY_POLICY` is no longer permitted. For example, a value of one indicates that :attr:`~cryptography.x509.oid.CertificatePoliciesOID.ANY_POLICY` may be processed in certificates issued by the subject of this certificate, but not in additional certificates in the path. .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.INHIBIT_ANY_POLICY`. .. attribute:: skip_certs :type: int .. class:: PolicyConstraints :canonical: cryptography.x509.extensions.PolicyConstraints .. versionadded:: 1.3 The policy constraints extension is used to inhibit policy mapping or require that each certificate in a chain contain an acceptable policy identifier. For more information about the use of this extension see :rfc:`5280`. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.POLICY_CONSTRAINTS`. .. attribute:: require_explicit_policy :type: int or None If this field is not None, the value indicates the number of additional certificates that may appear in the chain before an explicit policy is required for the entire path. When an explicit policy is required, it is necessary for all certificates in the chain to contain an acceptable policy identifier in the certificate policies extension. An acceptable policy identifier is the identifier of a policy required by the user of the certification path or the identifier of a policy that has been declared equivalent through policy mapping. .. attribute:: inhibit_policy_mapping :type: int or None If this field is not None, the value indicates the number of additional certificates that may appear in the chain before policy mapping is no longer permitted. For example, a value of one indicates that policy mapping may be processed in certificates issued by the subject of this certificate, but not in additional certificates in the chain. .. class:: CRLNumber(crl_number) :canonical: cryptography.x509.extensions.CRLNumber .. versionadded:: 1.2 The CRL number is a CRL extension that conveys a monotonically increasing sequence number for a given CRL scope and CRL issuer. This extension allows users to easily determine when a particular CRL supersedes another CRL. :rfc:`5280` requires that this extension be present in conforming CRLs. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.CRL_NUMBER`. .. attribute:: crl_number :type: int .. class:: IssuingDistributionPoint(full_name, relative_name,\ only_contains_user_certs, only_contains_ca_certs, only_some_reasons,\ indirect_crl, only_contains_attribute_certs) :canonical: cryptography.x509.extensions.IssuingDistributionPoint .. versionadded:: 2.5 Issuing distribution point is a CRL extension that identifies the CRL distribution point and scope for a particular CRL. It indicates whether the CRL covers revocation for end entity certificates only, CA certificates only, attribute certificates only, or a limited set of reason codes. For specific details on the way this extension should be processed see :rfc:`5280`. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.ISSUING_DISTRIBUTION_POINT`. .. attribute:: only_contains_user_certs :type: bool Set to ``True`` if the CRL this extension is embedded within only contains information about user certificates. .. attribute:: only_contains_ca_certs :type: bool Set to ``True`` if the CRL this extension is embedded within only contains information about CA certificates. .. attribute:: indirect_crl :type: bool Set to ``True`` if the CRL this extension is embedded within includes certificates issued by one or more authorities other than the CRL issuer. .. attribute:: only_contains_attribute_certs :type: bool Set to ``True`` if the CRL this extension is embedded within only contains information about attribute certificates. .. attribute:: only_some_reasons :type: frozenset of :class:`ReasonFlags` or None The reasons for which the issuing distribution point is valid. None indicates that it is valid for all reasons. .. attribute:: full_name :type: list of :class:`GeneralName` instances or None This field describes methods to retrieve the CRL. At most one of ``full_name`` or ``relative_name`` will be non-None. .. attribute:: relative_name :type: :class:`RelativeDistinguishedName` or None This field describes methods to retrieve the CRL relative to the CRL issuer. At most one of ``full_name`` or ``relative_name`` will be non-None. .. class:: UnrecognizedExtension :canonical: cryptography.x509.extensions.UnrecognizedExtension .. versionadded:: 1.2 A generic extension class used to hold the raw value of extensions that ``cryptography`` does not know how to parse. This can also be used when creating new certificates, CRLs, or OCSP requests and responses to encode extensions that ``cryptography`` does not know how to generate. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns the OID associated with this extension. .. attribute:: value :type: bytes Returns the DER encoded bytes payload of the extension. .. class:: MSCertificateTemplate(template_id, major_version, minor_version) :canonical: cryptography.x509.extensions.MSCertificateTemplate .. versionadded:: 41.0.0 The Microsoft certificate template extension is a proprietary Microsoft PKI extension that is used to provide information about the template associated with the certificate. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.MS_CERTIFICATE_TEMPLATE`. .. attribute:: template_id :type: :class:`ObjectIdentifier` .. attribute:: major_version :type: int or None .. attribute:: minor_version :type: int or None .. class:: CertificatePolicies(policies) :canonical: cryptography.x509.extensions.CertificatePolicies .. versionadded:: 0.9 The certificate policies extension is an iterable, containing one or more :class:`PolicyInformation` instances. :param list policies: A list of :class:`PolicyInformation` instances. As an example of how ``CertificatePolicies`` might be used, if you wanted to check if a certificated contained the CAB Forum's "domain-validated" policy, you might write code like: .. code-block:: python def contains_domain_validated(policies): return any( policy.policy_identifier.dotted_string == "2.23.140.1.2.1" for policy in policies ) .. attribute:: oid .. versionadded:: 1.0 :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.ExtensionOID.CERTIFICATE_POLICIES`. Certificate Policies Classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These classes may be present within a :class:`CertificatePolicies` instance. .. class:: PolicyInformation(policy_identifier, policy_qualifiers) :canonical: cryptography.x509.extensions.PolicyInformation .. versionadded:: 0.9 Contains a policy identifier and an optional list of qualifiers. .. attribute:: policy_identifier :type: :class:`ObjectIdentifier` .. attribute:: policy_qualifiers :type: list A list consisting of ``str`` and/or :class:`UserNotice` objects. If the value is ``str`` it is a pointer to the practice statement published by the certificate authority. If it is a user notice it is meant for display to the relying party when the certificate is used. .. class:: UserNotice(notice_reference, explicit_text) :canonical: cryptography.x509.extensions.UserNotice .. versionadded:: 0.9 User notices are intended for display to a relying party when a certificate is used. In practice, few if any UIs expose this data and it is a rarely encoded component. .. attribute:: notice_reference :type: :class:`NoticeReference` or None The notice reference field names an organization and identifies, by number, a particular statement prepared by that organization. .. attribute:: explicit_text This field includes an arbitrary textual statement directly in the certificate. :type: str .. class:: NoticeReference(organization, notice_numbers) :canonical: cryptography.x509.extensions.NoticeReference Notice reference can name an organization and provide information about notices related to the certificate. For example, it might identify the organization name and notice number 1. Application software could have a notice file containing the current set of notices for the named organization; the application would then extract the notice text from the file and display it. In practice this is rarely seen. .. versionadded:: 0.9 .. attribute:: organization :type: str .. attribute:: notice_numbers :type: list A list of integers. .. _crl_entry_extensions: CRL Entry Extensions ~~~~~~~~~~~~~~~~~~~~ These extensions are only valid within a :class:`RevokedCertificate` object. .. class:: CertificateIssuer(general_names) :canonical: cryptography.x509.extensions.CertificateIssuer .. versionadded:: 1.2 The certificate issuer is an extension that is only valid inside :class:`~cryptography.x509.RevokedCertificate` objects. If the ``indirectCRL`` property of the parent CRL's IssuingDistributionPoint extension is set, then this extension identifies the certificate issuer associated with the revoked certificate. The object is iterable to get every element. :param list general_names: A list of :class:`GeneralName` instances. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CERTIFICATE_ISSUER`. .. method:: get_values_for_type(type) :param type: A :class:`GeneralName` instance. This is one of the :ref:`general name classes `. :returns: A list of values extracted from the matched general names. The type of the returned values depends on the :class:`GeneralName`. .. class:: CRLReason(reason) :canonical: cryptography.x509.extensions.CRLReason .. versionadded:: 1.2 CRL reason (also known as ``reasonCode``) is an extension that is only valid inside :class:`~cryptography.x509.RevokedCertificate` objects. It identifies a reason for the certificate revocation. :param reason: An element from :class:`~cryptography.x509.ReasonFlags`. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CRL_REASON`. .. attribute:: reason :type: An element from :class:`~cryptography.x509.ReasonFlags` .. class:: InvalidityDate(invalidity_date) :canonical: cryptography.x509.extensions.InvalidityDate .. versionadded:: 1.2 Invalidity date is an extension that is only valid inside :class:`~cryptography.x509.RevokedCertificate` objects. It provides the date on which it is known or suspected that the private key was compromised or that the certificate otherwise became invalid. This date may be earlier than the revocation date in the CRL entry, which is the date at which the CA processed the revocation. :param invalidity_date: The :class:`datetime.datetime` when it is known or suspected that the private key was compromised. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.INVALIDITY_DATE`. .. attribute:: invalidity_date :type: :class:`datetime.datetime` .. attribute:: invalidity_date_utc .. versionadded:: 43.0.0 :type: :class:`datetime.datetime` The invalidity date in UTC as a timezone-aware datetime object. OCSP Extensions ~~~~~~~~~~~~~~~ .. class:: OCSPNonce(nonce) :canonical: cryptography.x509.extensions.OCSPNonce .. versionadded:: 2.4 OCSP nonce is an extension that is only valid inside :class:`~cryptography.x509.ocsp.OCSPRequest` and :class:`~cryptography.x509.ocsp.OCSPResponse` objects. The nonce cryptographically binds a request and a response to prevent replay attacks. In practice nonces are rarely used in OCSP due to the desire to precompute OCSP responses at large scale. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.OCSPExtensionOID.NONCE`. .. attribute:: nonce :type: bytes .. class:: OCSPAcceptableResponses(response) :canonical: cryptography.x509.extensions.OCSPAcceptableResponses .. versionadded:: 41.0.0 OCSP acceptable responses is an extension that is only valid inside :class:`~cryptography.x509.ocsp.OCSPRequest` objects. This allows an OCSP client to tell the server what types of responses it supports. In practice this is rarely used, because there is only one kind of OCSP response in wide use. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns :attr:`~cryptography.x509.oid.OCSPExtensionOID.ACCEPTABLE_RESPONSES`. .. attribute:: nonce :type: bytes X.509 Request Attributes ~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: Attributes :canonical: cryptography.x509.base.Attributes .. versionadded:: 36.0.0 An Attributes instance is an ordered list of attributes. The object is iterable to get every attribute. Each returned element is an :class:`Attribute`. .. method:: get_attribute_for_oid(oid) .. versionadded:: 36.0.0 :param oid: An :class:`ObjectIdentifier` instance. :returns: The :class:`Attribute` or an exception if not found. :raises cryptography.x509.AttributeNotFound: If the request does not have the attribute requested. .. class:: Attribute :canonical: cryptography.x509.base.Attribute .. versionadded:: 36.0.0 An attribute associated with an X.509 request. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns the object identifier for the attribute. .. attribute:: value :type: bytes Returns the value of the attribute. Object Identifiers ~~~~~~~~~~~~~~~~~~ X.509 elements are frequently identified by :class:`ObjectIdentifier` instances. The following common OIDs are available as constants. .. currentmodule:: cryptography.x509.oid .. class:: NameOID :canonical: cryptography.hazmat._oid.NameOID These OIDs are typically seen in X.509 names. .. versionadded:: 1.0 .. attribute:: COMMON_NAME Corresponds to the dotted string ``"2.5.4.3"``. Historically the domain name would be encoded here for server certificates. :rfc:`2818` deprecates this practice and names of that type should now be located in a :class:`~cryptography.x509.SubjectAlternativeName` extension. .. attribute:: COUNTRY_NAME Corresponds to the dotted string ``"2.5.4.6"``. .. attribute:: LOCALITY_NAME Corresponds to the dotted string ``"2.5.4.7"``. .. attribute:: STATE_OR_PROVINCE_NAME Corresponds to the dotted string ``"2.5.4.8"``. .. attribute:: STREET_ADDRESS .. versionadded:: 1.6 Corresponds to the dotted string ``"2.5.4.9"``. .. attribute:: ORGANIZATION_IDENTIFIER .. versionadded:: 42.0.0 Corresponds to the dotted string ``"2.5.4.97"``. .. attribute:: ORGANIZATION_NAME Corresponds to the dotted string ``"2.5.4.10"``. .. attribute:: ORGANIZATIONAL_UNIT_NAME Corresponds to the dotted string ``"2.5.4.11"``. .. attribute:: SERIAL_NUMBER Corresponds to the dotted string ``"2.5.4.5"``. This is distinct from the serial number of the certificate itself (which can be obtained with :attr:`~cryptography.x509.Certificate.serial_number`). .. attribute:: SURNAME Corresponds to the dotted string ``"2.5.4.4"``. .. attribute:: GIVEN_NAME Corresponds to the dotted string ``"2.5.4.42"``. .. attribute:: TITLE Corresponds to the dotted string ``"2.5.4.12"``. .. attribute:: INITIALS .. versionadded:: 41.0.0 Corresponds to the dotted string ``"2.5.4.43"``. .. attribute:: GENERATION_QUALIFIER Corresponds to the dotted string ``"2.5.4.44"``. .. attribute:: X500_UNIQUE_IDENTIFIER .. versionadded:: 1.6 Corresponds to the dotted string ``"2.5.4.45"``. .. attribute:: DN_QUALIFIER Corresponds to the dotted string ``"2.5.4.46"``. This specifies disambiguating information to add to the relative distinguished name of an entry. See :rfc:`2256`. .. attribute:: PSEUDONYM Corresponds to the dotted string ``"2.5.4.65"``. .. attribute:: USER_ID .. versionadded:: 1.6 Corresponds to the dotted string ``"0.9.2342.19200300.100.1.1"``. .. attribute:: DOMAIN_COMPONENT Corresponds to the dotted string ``"0.9.2342.19200300.100.1.25"``. A string holding one component of a domain name. See :rfc:`4519`. .. attribute:: EMAIL_ADDRESS Corresponds to the dotted string ``"1.2.840.113549.1.9.1"``. .. attribute:: JURISDICTION_COUNTRY_NAME Corresponds to the dotted string ``"1.3.6.1.4.1.311.60.2.1.3"``. .. attribute:: JURISDICTION_LOCALITY_NAME Corresponds to the dotted string ``"1.3.6.1.4.1.311.60.2.1.1"``. .. attribute:: JURISDICTION_STATE_OR_PROVINCE_NAME Corresponds to the dotted string ``"1.3.6.1.4.1.311.60.2.1.2"``. .. attribute:: BUSINESS_CATEGORY Corresponds to the dotted string ``"2.5.4.15"``. .. attribute:: POSTAL_ADDRESS .. versionadded:: 1.6 Corresponds to the dotted string ``"2.5.4.16"``. .. attribute:: POSTAL_CODE .. versionadded:: 1.6 Corresponds to the dotted string ``"2.5.4.17"``. .. attribute:: UNSTRUCTURED_NAME .. versionadded:: 3.0 Corresponds to the dotted string ``"1.2.840.113549.1.9.2"``. .. class:: SignatureAlgorithmOID :canonical: cryptography.hazmat._oid.SignatureAlgorithmOID .. versionadded:: 1.0 .. attribute:: RSA_WITH_MD5 Corresponds to the dotted string ``"1.2.840.113549.1.1.4"``. This is an MD5 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA1 Corresponds to the dotted string ``"1.2.840.113549.1.1.5"``. This is a SHA1 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA224 Corresponds to the dotted string ``"1.2.840.113549.1.1.14"``. This is a SHA224 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA256 Corresponds to the dotted string ``"1.2.840.113549.1.1.11"``. This is a SHA256 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA384 Corresponds to the dotted string ``"1.2.840.113549.1.1.12"``. This is a SHA384 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA512 Corresponds to the dotted string ``"1.2.840.113549.1.1.13"``. This is a SHA512 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA3_224 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.13"``. This is a SHA3-224 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA3_256 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.14"``. This is a SHA3-256 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA3_384 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.15"``. This is a SHA3-384 digest signed by an RSA key. .. attribute:: RSA_WITH_SHA3_512 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.16"``. This is a SHA3-512 digest signed by an RSA key. .. attribute:: RSASSA_PSS .. versionadded:: 2.3 Corresponds to the dotted string ``"1.2.840.113549.1.1.10"``. This is signed by an RSA key using the Probabilistic Signature Scheme (PSS) padding from :rfc:`4055`. The hash function and padding are defined by signature algorithm parameters. .. attribute:: ECDSA_WITH_SHA1 Corresponds to the dotted string ``"1.2.840.10045.4.1"``. This is a SHA1 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA224 Corresponds to the dotted string ``"1.2.840.10045.4.3.1"``. This is a SHA224 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA256 Corresponds to the dotted string ``"1.2.840.10045.4.3.2"``. This is a SHA256 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA384 Corresponds to the dotted string ``"1.2.840.10045.4.3.3"``. This is a SHA384 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA512 Corresponds to the dotted string ``"1.2.840.10045.4.3.4"``. This is a SHA512 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA3_224 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.9"``. This is a SHA3-224 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA3_256 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.10"``. This is a SHA3-256 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA3_384 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.11"``. This is a SHA3-384 digest signed by an ECDSA key. .. attribute:: ECDSA_WITH_SHA3_512 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.12"``. This is a SHA3-512 digest signed by an ECDSA key. .. attribute:: DSA_WITH_SHA1 Corresponds to the dotted string ``"1.2.840.10040.4.3"``. This is a SHA1 digest signed by a DSA key. .. attribute:: DSA_WITH_SHA224 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.1"``. This is a SHA224 digest signed by a DSA key. .. attribute:: DSA_WITH_SHA256 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.2"``. This is a SHA256 digest signed by a DSA key. .. attribute:: DSA_WITH_SHA384 .. versionadded:: 36.0.0 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.3"``. This is a SHA384 digest signed by a DSA key. .. attribute:: DSA_WITH_SHA512 .. versionadded:: 36.0.0 Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.4"``. This is a SHA512 digest signed by a DSA key. .. attribute:: ED25519 .. versionadded:: 2.8 Corresponds to the dotted string ``"1.3.101.112"``. This is a signature using an ed25519 key. .. attribute:: ED448 .. versionadded:: 2.8 Corresponds to the dotted string ``"1.3.101.113"``. This is a signature using an ed448 key. .. class:: ExtendedKeyUsageOID :canonical: cryptography.hazmat._oid.ExtendedKeyUsageOID .. versionadded:: 1.0 .. attribute:: SERVER_AUTH Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.1"``. This is used to denote that a certificate may be used for TLS web server authentication. .. attribute:: CLIENT_AUTH Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.2"``. This is used to denote that a certificate may be used for TLS web client authentication. .. attribute:: CODE_SIGNING Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.3"``. This is used to denote that a certificate may be used for code signing. .. attribute:: EMAIL_PROTECTION Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.4"``. This is used to denote that a certificate may be used for email protection. .. attribute:: TIME_STAMPING Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.8"``. This is used to denote that a certificate may be used for time stamping. .. attribute:: OCSP_SIGNING Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.9"``. This is used to denote that a certificate may be used for signing OCSP responses. .. attribute:: ANY_EXTENDED_KEY_USAGE .. versionadded:: 2.0 Corresponds to the dotted string ``"2.5.29.37.0"``. This is used to denote that a certificate may be used for _any_ purposes. However, :rfc:`5280` additionally notes that applications that require the presence of a particular purpose _MAY_ reject certificates that include the ``anyExtendedKeyUsage`` OID but not the particular OID expected for the application. Therefore, the presence of this OID does not mean a given application will accept the certificate for all purposes. .. attribute:: SMARTCARD_LOGON .. versionadded:: 35.0.0 Corresponds to the dotted string ``"1.3.6.1.4.1.311.20.2.2"``. This is used to denote that a certificate may be used for ``PKINIT`` access on Windows. .. attribute:: KERBEROS_PKINIT_KDC .. versionadded:: 35.0.0 Corresponds to the dotted string ``"1.3.6.1.5.2.3.5"``. This is used to denote that a certificate may be used as a Kerberos domain controller certificate authorizing ``PKINIT`` access. For more information see :rfc:`4556`. .. attribute:: IPSEC_IKE .. versionadded:: 37.0.0 Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.17"``. This is used to denote that a certificate may be assigned to an IPSEC SA, and can be used by the assignee to initiate an IPSec Internet Key Exchange. For more information see :rfc:`4945`. .. attribute:: CERTIFICATE_TRANSPARENCY .. versionadded:: 38.0.0 Corresponds to the dotted string ``"1.3.6.1.4.1.11129.2.4.4"``. This is used to denote that a certificate may be used as a pre-certificate signing certificate for Certificate Transparency log operation purposes. For more information see :rfc:`6962`. .. class:: AuthorityInformationAccessOID :canonical: cryptography.hazmat._oid.AuthorityInformationAccessOID .. versionadded:: 1.0 .. attribute:: OCSP Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1"``. Used as the identifier for OCSP data in :class:`~cryptography.x509.AccessDescription` objects. .. attribute:: CA_ISSUERS Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.2"``. Used as the identifier for CA issuer data in :class:`~cryptography.x509.AccessDescription` objects. .. class:: SubjectInformationAccessOID :canonical: cryptography.hazmat._oid.SubjectInformationAccessOID .. versionadded:: 3.0 .. attribute:: CA_REPOSITORY Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.5"``. Used as the identifier for CA repository data in :class:`~cryptography.x509.AccessDescription` objects. .. class:: CertificatePoliciesOID :canonical: cryptography.hazmat._oid.CertificatePoliciesOID .. versionadded:: 1.0 .. attribute:: CPS_QUALIFIER Corresponds to the dotted string ``"1.3.6.1.5.5.7.2.1"``. .. attribute:: CPS_USER_NOTICE Corresponds to the dotted string ``"1.3.6.1.5.5.7.2.2"``. .. attribute:: ANY_POLICY Corresponds to the dotted string ``"2.5.29.32.0"``. .. class:: ExtensionOID :canonical: cryptography.hazmat._oid.ExtensionOID .. versionadded:: 1.0 .. attribute:: BASIC_CONSTRAINTS Corresponds to the dotted string ``"2.5.29.19"``. The identifier for the :class:`~cryptography.x509.BasicConstraints` extension type. .. attribute:: KEY_USAGE Corresponds to the dotted string ``"2.5.29.15"``. The identifier for the :class:`~cryptography.x509.KeyUsage` extension type. .. attribute:: SUBJECT_ALTERNATIVE_NAME Corresponds to the dotted string ``"2.5.29.17"``. The identifier for the :class:`~cryptography.x509.SubjectAlternativeName` extension type. .. attribute:: ISSUER_ALTERNATIVE_NAME Corresponds to the dotted string ``"2.5.29.18"``. The identifier for the :class:`~cryptography.x509.IssuerAlternativeName` extension type. .. attribute:: SUBJECT_KEY_IDENTIFIER Corresponds to the dotted string ``"2.5.29.14"``. The identifier for the :class:`~cryptography.x509.SubjectKeyIdentifier` extension type. .. attribute:: NAME_CONSTRAINTS Corresponds to the dotted string ``"2.5.29.30"``. The identifier for the :class:`~cryptography.x509.NameConstraints` extension type. .. attribute:: CRL_DISTRIBUTION_POINTS Corresponds to the dotted string ``"2.5.29.31"``. The identifier for the :class:`~cryptography.x509.CRLDistributionPoints` extension type. .. attribute:: CERTIFICATE_POLICIES Corresponds to the dotted string ``"2.5.29.32"``. The identifier for the :class:`~cryptography.x509.CertificatePolicies` extension type. .. attribute:: AUTHORITY_KEY_IDENTIFIER Corresponds to the dotted string ``"2.5.29.35"``. The identifier for the :class:`~cryptography.x509.AuthorityKeyIdentifier` extension type. .. attribute:: EXTENDED_KEY_USAGE Corresponds to the dotted string ``"2.5.29.37"``. The identifier for the :class:`~cryptography.x509.ExtendedKeyUsage` extension type. .. attribute:: AUTHORITY_INFORMATION_ACCESS Corresponds to the dotted string ``"1.3.6.1.5.5.7.1.1"``. The identifier for the :class:`~cryptography.x509.AuthorityInformationAccess` extension type. .. attribute:: SUBJECT_INFORMATION_ACCESS .. versionadded:: 3.0 Corresponds to the dotted string ``"1.3.6.1.5.5.7.1.11"``. The identifier for the :class:`~cryptography.x509.SubjectInformationAccess` extension type. .. attribute:: INHIBIT_ANY_POLICY Corresponds to the dotted string ``"2.5.29.54"``. The identifier for the :class:`~cryptography.x509.InhibitAnyPolicy` extension type. .. attribute:: OCSP_NO_CHECK Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.5"``. The identifier for the :class:`~cryptography.x509.OCSPNoCheck` extension type. .. attribute:: TLS_FEATURE Corresponds to the dotted string ``"1.3.6.1.5.5.7.1.24"``. The identifier for the :class:`~cryptography.x509.TLSFeature` extension type. .. attribute:: CRL_NUMBER Corresponds to the dotted string ``"2.5.29.20"``. The identifier for the ``CRLNumber`` extension type. This extension only has meaning for certificate revocation lists. .. attribute:: DELTA_CRL_INDICATOR .. versionadded:: 2.1 Corresponds to the dotted string ``"2.5.29.27"``. The identifier for the ``DeltaCRLIndicator`` extension type. This extension only has meaning for certificate revocation lists. .. attribute:: PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS .. versionadded:: 1.9 Corresponds to the dotted string ``"1.3.6.1.4.1.11129.2.4.2"``. .. attribute:: PRECERT_POISON .. versionadded:: 2.4 Corresponds to the dotted string ``"1.3.6.1.4.1.11129.2.4.3"``. .. attribute:: SIGNED_CERTIFICATE_TIMESTAMPS .. versionadded:: 3.0 Corresponds to the dotted string ``"1.3.6.1.4.1.11129.2.4.5"``. .. attribute:: POLICY_CONSTRAINTS Corresponds to the dotted string ``"2.5.29.36"``. The identifier for the :class:`~cryptography.x509.PolicyConstraints` extension type. .. attribute:: FRESHEST_CRL Corresponds to the dotted string ``"2.5.29.46"``. The identifier for the :class:`~cryptography.x509.FreshestCRL` extension type. .. attribute:: ISSUING_DISTRIBUTION_POINT .. versionadded:: 2.4 Corresponds to the dotted string ``"2.5.29.28"``. .. attribute:: POLICY_MAPPINGS Corresponds to the dotted string ``"2.5.29.33"``. .. attribute:: SUBJECT_DIRECTORY_ATTRIBUTES Corresponds to the dotted string ``"2.5.29.9"``. .. attribute:: MS_CERTIFICATE_TEMPLATE .. versionadded:: 41.0.0 Corresponds to the dotted string ``"1.3.6.1.4.1.311.21.7"``. .. class:: CRLEntryExtensionOID :canonical: cryptography.hazmat._oid.CRLEntryExtensionOID .. versionadded:: 1.2 .. attribute:: CERTIFICATE_ISSUER Corresponds to the dotted string ``"2.5.29.29"``. .. attribute:: CRL_REASON Corresponds to the dotted string ``"2.5.29.21"``. .. attribute:: INVALIDITY_DATE Corresponds to the dotted string ``"2.5.29.24"``. .. class:: OCSPExtensionOID :canonical: cryptography.hazmat._oid.OCSPExtensionOID .. versionadded:: 2.4 .. attribute:: NONCE Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.2"``. .. attribute:: ACCEPTABLE_RESPONSES .. versionadded:: 41.0.0 Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.4"``. .. class:: AttributeOID :canonical: cryptography.hazmat._oid.AttributeOID .. versionadded:: 3.0 .. attribute:: CHALLENGE_PASSWORD Corresponds to the dotted string ``"1.2.840.113549.1.9.7"``. .. attribute:: UNSTRUCTURED_NAME Corresponds to the dotted string ``"1.2.840.113549.1.9.2"``. .. class:: PublicKeyAlgorithmOID :canonical: cryptography.hazmat._oid.PublicKeyAlgorithmOID .. versionadded:: 43.0.0 .. attribute:: DSA Corresponds to the dotted string ``"1.2.840.10040.4.1"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` public key. .. attribute:: EC_PUBLIC_KEY Corresponds to the dotted string ``"1.2.840.10045.2.1"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` public key. .. attribute:: RSAES_PKCS1_v1_5 Corresponds to the dotted string ``"1.2.840.113549.1.1.1"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` public key with :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` padding. .. attribute:: RSASSA_PSS Corresponds to the dotted string ``"1.2.840.113549.1.1.10"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` public key with :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` padding. .. attribute:: X25519 Corresponds to the dotted string ``"1.3.101.110"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey` public key. .. attribute:: X448 Corresponds to the dotted string ``"1.3.101.111"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey` public key. .. attribute:: ED25519 Corresponds to the dotted string ``"1.3.101.112"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` public key. .. attribute:: ED448 Corresponds to the dotted string ``"1.3.101.113"``. This is a :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey` public key. Helper Functions ~~~~~~~~~~~~~~~~ .. currentmodule:: cryptography.x509 .. function:: random_serial_number() :canonical: cryptography.x509.base.random_serial_number .. versionadded:: 1.6 Generates a random serial number suitable for use when constructing certificates. Exceptions ~~~~~~~~~~ .. currentmodule:: cryptography.x509 .. class:: InvalidVersion :canonical: cryptography.x509.base.InvalidVersion This is raised when an X.509 certificate has an invalid version number. .. attribute:: parsed_version :type: int Returns the raw version that was parsed from the certificate. .. class:: DuplicateExtension :canonical: cryptography.x509.extensions.DuplicateExtension This is raised when more than one X.509 extension of the same type is found within a certificate. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns the OID. .. class:: ExtensionNotFound :canonical: cryptography.x509.extensions.ExtensionNotFound This is raised when calling :meth:`Extensions.get_extension_for_oid` with an extension OID that is not present in the certificate. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns the OID. .. class:: AttributeNotFound :canonical: cryptography.x509.base.AttributeNotFound This is raised when calling :meth:`Attributes.get_attribute_for_oid` with an attribute OID that is not present in the request. .. attribute:: oid :type: :class:`ObjectIdentifier` Returns the OID. .. class:: UnsupportedGeneralNameType :canonical: cryptography.x509.general_name.UnsupportedGeneralNameType This is raised when a certificate contains an unsupported general name type in an extension. .. attribute:: type :type: int The integer value of the unsupported type. The complete list of types can be found in `RFC 5280 section 4.2.1.6`_. .. _`RFC 5280 section 4.2.1.1`: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1 .. _`RFC 5280 section 4.2.1.6`: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 .. _`CABForum Guidelines`: https://cabforum.org/baseline-requirements-documents/ cryptography-43.0.0/docs/x509/tutorial.rst010064400017510000177000000341021464676315000165630ustar 00000000000000Tutorial ======== X.509 certificates are used to authenticate clients and servers. The most common use case is for web servers using HTTPS. Creating a Certificate Signing Request (CSR) -------------------------------------------- When obtaining a certificate from a certificate authority (CA), the usual flow is: 1. You generate a private/public key pair. 2. You create a request for a certificate, which is signed by your key (to prove that you own that key). 3. You give your CSR to a CA (but *not* the private key). 4. The CA validates that you own the resource (e.g. domain) you want a certificate for. 5. The CA gives you a certificate, signed by them, which identifies your public key, and the resource you are authenticated for. 6. You configure your server to use that certificate, combined with your private key, to server traffic. If you want to obtain a certificate from a typical commercial CA, here's how. First, you'll need to generate a private key, we'll generate an RSA key (these are the most common types of keys on the web right now): .. code-block:: pycon >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import rsa >>> # Generate our key >>> key = rsa.generate_private_key( ... public_exponent=65537, ... key_size=2048, ... ) >>> # Write our key to disk for safe keeping >>> with open("path/to/store/key.pem", "wb") as f: ... f.write(key.private_bytes( ... encoding=serialization.Encoding.PEM, ... format=serialization.PrivateFormat.TraditionalOpenSSL, ... encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), ... )) If you've already generated a key you can load it with :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`. Next we need to generate a certificate signing request. A typical CSR contains a few details: * Information about our public key (including a signature of the entire body). * Information about who *we* are. * Information about what domains this certificate is for. .. code-block:: pycon >>> from cryptography import x509 >>> from cryptography.x509.oid import NameOID >>> from cryptography.hazmat.primitives import hashes >>> # Generate a CSR >>> csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ ... # Provide various details about who we are. ... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), ... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), ... x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), ... ])).add_extension( ... x509.SubjectAlternativeName([ ... # Describe what sites we want this certificate for. ... x509.DNSName("mysite.com"), ... x509.DNSName("www.mysite.com"), ... x509.DNSName("subdomain.mysite.com"), ... ]), ... critical=False, ... # Sign the CSR with our private key. ... ).sign(key, hashes.SHA256()) >>> # Write our CSR out to disk. >>> with open("path/to/csr.pem", "wb") as f: ... f.write(csr.public_bytes(serialization.Encoding.PEM)) Now we can give our CSR to a CA, who will give a certificate to us in return. Creating a self-signed certificate ---------------------------------- While most of the time you want a certificate that has been *signed* by someone else (i.e. a certificate authority), so that trust is established, sometimes you want to create a self-signed certificate. Self-signed certificates are not issued by a certificate authority, but instead they are signed by the private key corresponding to the public key they embed. This means that other people don't trust these certificates, but it also means they can be issued very easily. In general the only use case for a self-signed certificate is local testing, where you don't need anyone else to trust your certificate. Like generating a CSR, we start with creating a new private key: .. code-block:: pycon >>> # Generate our key >>> key = rsa.generate_private_key( ... public_exponent=65537, ... key_size=2048, ... ) >>> # Write our key to disk for safe keeping >>> with open("path/to/store/key.pem", "wb") as f: ... f.write(key.private_bytes( ... encoding=serialization.Encoding.PEM, ... format=serialization.PrivateFormat.TraditionalOpenSSL, ... encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), ... )) Then we generate the certificate itself: .. code-block:: pycon >>> # Various details about who we are. For a self-signed certificate the >>> # subject and issuer are always the same. >>> subject = issuer = x509.Name([ ... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), ... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), ... x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), ... ]) >>> cert = x509.CertificateBuilder().subject_name( ... subject ... ).issuer_name( ... issuer ... ).public_key( ... key.public_key() ... ).serial_number( ... x509.random_serial_number() ... ).not_valid_before( ... datetime.datetime.now(datetime.timezone.utc) ... ).not_valid_after( ... # Our certificate will be valid for 10 days ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10) ... ).add_extension( ... x509.SubjectAlternativeName([x509.DNSName("localhost")]), ... critical=False, ... # Sign our certificate with our private key ... ).sign(key, hashes.SHA256()) >>> # Write our certificate out to disk. >>> with open("path/to/certificate.pem", "wb") as f: ... f.write(cert.public_bytes(serialization.Encoding.PEM)) And now we have a private key and certificate that can be used for local testing. Creating a CA hierarchy ----------------------- When building your own root hierarchy you need to generate a CA and then issue certificates (typically intermediates) using it. This example shows how to generate a root CA, a signing intermediate, and issues a leaf certificate off that intermediate. X.509 is a complex specification so this example will require adaptation (typically different extensions) for specific operating environments. Note that this example does not add CRL distribution point or OCSP AIA extensions, nor does it save the key/certs to persistent storage. .. doctest:: >>> import datetime >>> from cryptography.hazmat.primitives.asymmetric import ec >>> from cryptography.hazmat.primitives import hashes >>> from cryptography.x509.oid import NameOID >>> from cryptography import x509 >>> # Generate our key >>> root_key = ec.generate_private_key(ec.SECP256R1()) >>> subject = issuer = x509.Name([ ... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), ... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), ... x509.NameAttribute(NameOID.COMMON_NAME, "PyCA Docs Root CA"), ... ]) >>> root_cert = x509.CertificateBuilder().subject_name( ... subject ... ).issuer_name( ... issuer ... ).public_key( ... root_key.public_key() ... ).serial_number( ... x509.random_serial_number() ... ).not_valid_before( ... datetime.datetime.now(datetime.timezone.utc) ... ).not_valid_after( ... # Our certificate will be valid for ~10 years ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*10) ... ).add_extension( ... x509.BasicConstraints(ca=True, path_length=None), ... critical=True, ... ).add_extension( ... x509.KeyUsage( ... digital_signature=True, ... content_commitment=False, ... key_encipherment=False, ... data_encipherment=False, ... key_agreement=False, ... key_cert_sign=True, ... crl_sign=True, ... encipher_only=False, ... decipher_only=False, ... ), ... critical=True, ... ).add_extension( ... x509.SubjectKeyIdentifier.from_public_key(root_key.public_key()), ... critical=False, ... ).sign(root_key, hashes.SHA256()) With a root certificate created we now want to create our intermediate. .. doctest:: >>> # Generate our intermediate key >>> int_key = ec.generate_private_key(ec.SECP256R1()) >>> subject = x509.Name([ ... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), ... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), ... x509.NameAttribute(NameOID.COMMON_NAME, "PyCA Docs Intermediate CA"), ... ]) >>> int_cert = x509.CertificateBuilder().subject_name( ... subject ... ).issuer_name( ... root_cert.subject ... ).public_key( ... int_key.public_key() ... ).serial_number( ... x509.random_serial_number() ... ).not_valid_before( ... datetime.datetime.now(datetime.timezone.utc) ... ).not_valid_after( ... # Our intermediate will be valid for ~3 years ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365*3) ... ).add_extension( ... # Allow no further intermediates (path length 0) ... x509.BasicConstraints(ca=True, path_length=0), ... critical=True, ... ).add_extension( ... x509.KeyUsage( ... digital_signature=True, ... content_commitment=False, ... key_encipherment=False, ... data_encipherment=False, ... key_agreement=False, ... key_cert_sign=True, ... crl_sign=True, ... encipher_only=False, ... decipher_only=False, ... ), ... critical=True, ... ).add_extension( ... x509.SubjectKeyIdentifier.from_public_key(int_key.public_key()), ... critical=False, ... ).add_extension( ... x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( ... root_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value ... ), ... critical=False, ... ).sign(root_key, hashes.SHA256()) Now we can issue an end entity certificate off this chain. .. doctest:: >>> ee_key = ec.generate_private_key(ec.SECP256R1()) >>> subject = x509.Name([ ... x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), ... x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), ... ]) >>> ee_cert = x509.CertificateBuilder().subject_name( ... subject ... ).issuer_name( ... int_cert.subject ... ).public_key( ... ee_key.public_key() ... ).serial_number( ... x509.random_serial_number() ... ).not_valid_before( ... datetime.datetime.now(datetime.timezone.utc) ... ).not_valid_after( ... # Our cert will be valid for 10 days ... datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10) ... ).add_extension( ... x509.SubjectAlternativeName([ ... # Describe what sites we want this certificate for. ... x509.DNSName("cryptography.io"), ... x509.DNSName("www.cryptography.io"), ... ]), ... critical=False, ... ).add_extension( ... x509.BasicConstraints(ca=False, path_length=None), ... critical=True, ... ).add_extension( ... x509.KeyUsage( ... digital_signature=True, ... content_commitment=False, ... key_encipherment=True, ... data_encipherment=False, ... key_agreement=False, ... key_cert_sign=False, ... crl_sign=True, ... encipher_only=False, ... decipher_only=False, ... ), ... critical=True, ... ).add_extension( ... x509.ExtendedKeyUsage([ ... x509.ExtendedKeyUsageOID.CLIENT_AUTH, ... x509.ExtendedKeyUsageOID.SERVER_AUTH, ... ]), ... critical=False, ... ).add_extension( ... x509.SubjectKeyIdentifier.from_public_key(ee_key.public_key()), ... critical=False, ... ).add_extension( ... x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( ... int_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value ... ), ... critical=False, ... ).sign(int_key, hashes.SHA256()) And finally we use the verification APIs to validate the chain. .. doctest:: >>> from cryptography.x509 import DNSName >>> from cryptography.x509.verification import PolicyBuilder, Store >>> store = Store([root_cert]) >>> builder = PolicyBuilder().store(store) >>> verifier = builder.build_server_verifier(DNSName("cryptography.io")) >>> chain = verifier.verify(ee_cert, [int_cert]) >>> len(chain) 3 Determining Certificate or Certificate Signing Request Key Type --------------------------------------------------------------- Certificates and certificate signing requests can be issued with multiple key types. You can determine what the key type is by using ``isinstance`` checks: .. code-block:: pycon >>> public_key = cert.public_key() >>> if isinstance(public_key, rsa.RSAPublicKey): ... # Do something RSA specific ... elif isinstance(public_key, ec.EllipticCurvePublicKey): ... # Do something EC specific ... else: ... # Remember to handle this case cryptography-43.0.0/docs/x509/verification.rst010064400017510000177000000242601464676315000174060ustar 00000000000000X.509 Verification ================== .. currentmodule:: cryptography.x509.verification .. module:: cryptography.x509.verification Support for X.509 certificate verification, also known as path validation or chain building. .. note:: While usable, these APIs should be considered unstable and not yet subject to our backwards compatibility policy. Example usage, with `certifi `_ providing the root of trust: .. testsetup:: from cryptography.x509 import load_pem_x509_certificate, load_pem_x509_certificates from datetime import datetime peer = load_pem_x509_certificate(b""" -----BEGIN CERTIFICATE----- MIIDgTCCAwegAwIBAgISBJUzlK20QGqPf5xI0aoE8OIBMAoGCCqGSM49BAMDMDIx CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF MTAeFw0yMzExMjIyMDUyNDBaFw0yNDAyMjAyMDUyMzlaMBoxGDAWBgNVBAMTD2Ny eXB0b2dyYXBoeS5pbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAh2A0yuOByJ lxK3ps5vbSOT6ZmvAlflGLn8kEseeodIAockm0ISTb/NGSpu/SY4ITefAOSaulKn BzDgmqjGRKujggITMIICDzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYB BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJu7f03HjjwJ MU6rfwDBzxySTrs5MB8GA1UdIwQYMBaAFFrz7Sv8NsI3eblSMOpUb89Vyy6sMFUG CCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2UxLm8ubGVuY3Iub3Jn MCIGCCsGAQUFBzAChhZodHRwOi8vZTEuaS5sZW5jci5vcmcvMBoGA1UdEQQTMBGC D2NyeXB0b2dyYXBoeS5pbzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQYGCisGAQQB 1nkCBAIEgfcEgfQA8gB3AEiw42vapkc0D+VqAvqdMOscUgHLVt0sgdm7v6s52IRz AAABi/kFXv4AAAQDAEgwRgIhAI9uF526YzU/DEfpmWRA28fn9gryrWMUCXQnEejQ K/trAiEA12ePSql3sGJ/QgXc6ceQB/XAdwzwDB+2CHr6T14vvvUAdwDuzdBk1dsa zsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYv5BV8kAAAEAwBIMEYCIQD1mqTn b1hOpZWAUlwVM4EJLYA9HtlOvF70bfrGHpAX4gIhAI8pktDxrUwfTXPuA+eMFPbC QraG6dMkB+HOmTz+hgKyMAoGCCqGSM49BAMDA2gAMGUCMQC+PwiHciKMaJyRJkGa KFjT/1ICAUsCm8o5h4Xxm0LoOCJVggaXeamDEYnPWbxGETgCME5TJzLIDuF3z6vX 1SLZDdvHEHLKfOL8/h8KctkjLQ8OJycxwIc+zK+xexVoIuxRhA== -----END CERTIFICATE----- """ ) untrusted_intermediates = load_pem_x509_certificates(b""" -----BEGIN CERTIFICATE----- MIICxjCCAk2gAwIBAgIRALO93/inhFu86QOgQTWzSkUwCgYIKoZIzj0EAwMwTzEL MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjAwOTA0MDAwMDAwWhcN MjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5j cnlwdDELMAkGA1UEAxMCRTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQkXC2iKv0c S6Zdl3MnMayyoGli72XoprDwrEuf/xwLcA/TmC9N/A8AmzfwdAVXMpcuBe8qQyWj +240JxP2T35p0wKZXuskR5LBJJvmsSGPwSSB/GjMH2m6WPUZIvd0xhajggEIMIIB BDAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFrz7Sv8NsI3eblSMOpUb89V yy6sMB8GA1UdIwQYMBaAFHxClq7eS0g7+pL4nozPbYupcjeVMDIGCCsGAQUFBwEB BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gyLmkubGVuY3Iub3JnLzAnBgNVHR8E IDAeMBygGqAYhhZodHRwOi8veDIuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYG Z4EMAQIBMA0GCysGAQQBgt8TAQEBMAoGCCqGSM49BAMDA2cAMGQCMHt01VITjWH+ Dbo/AwCd89eYhNlXLr3pD5xcSAQh8suzYHKOl9YST8pE9kLJ03uGqQIwWrGxtO3q YJkgsTgDyj2gJrjubi1K9sZmHzOa25JK1fUpE8ZwYii6I4zPPS/Lgul/ -----END CERTIFICATE----- """) verification_time = datetime.fromisoformat("2024-01-12T00:00:00Z") .. doctest:: >>> from cryptography.x509 import Certificate, DNSName, load_pem_x509_certificates >>> from cryptography.x509.verification import PolicyBuilder, Store >>> import certifi >>> from datetime import datetime >>> with open(certifi.where(), "rb") as pems: ... store = Store(load_pem_x509_certificates(pems.read())) >>> builder = PolicyBuilder().store(store) >>> builder = builder.time(verification_time) >>> verifier = builder.build_server_verifier(DNSName("cryptography.io")) >>> # NOTE: peer and untrusted_intermediates are Certificate and >>> # list[Certificate] respectively, and should be loaded from the >>> # application context that needs them verified, such as a >>> # TLS socket. >>> chain = verifier.verify(peer, untrusted_intermediates) .. class:: Store(certs) .. versionadded:: 42.0.0 A Store is an opaque set of public keys and subject identifiers that are considered trusted *a priori*. Stores are typically created from the host OS's root of trust, from a well-known source such as a browser CA bundle, or from a small set of manually pre-trusted entities. :param certs: A list of one or more :class:`cryptography.x509.Certificate` instances. .. class:: Subject .. versionadded:: 42.0.0 Type alias: A union of all subject types supported: :class:`cryptography.x509.general_name.DNSName`, :class:`cryptography.x509.general_name.IPAddress`. .. class:: VerifiedClient .. versionadded:: 43.0.0 .. attribute:: subjects :type: list of :class:`~cryptography.x509.GeneralName` The subjects presented in the verified client's Subject Alternative Name extension. .. attribute:: chain :type: A list of :class:`~cryptography.x509.Certificate`, in leaf-first order The chain of certificates that forms the valid chain to the client certificate. .. class:: ClientVerifier .. versionadded:: 43.0.0 A ClientVerifier verifies client certificates. It contains and describes various pieces of configurable path validation logic, such as how deep prospective validation chains may go, which signature algorithms are allowed, and so forth. ClientVerifier instances cannot be constructed directly; :class:`PolicyBuilder` must be used. .. attribute:: validation_time :type: :class:`datetime.datetime` The verifier's validation time. .. attribute:: max_chain_depth :type: :class:`int` The verifier's maximum intermediate CA chain depth. .. attribute:: store :type: :class:`Store` The verifier's trust store. .. method:: verify(leaf, intermediates) Performs path validation on ``leaf``, returning a valid path if one exists. The path is returned in leaf-first order: the first member is ``leaf``, followed by the intermediates used (if any), followed by a member of the ``store``. :param leaf: The leaf :class:`~cryptography.x509.Certificate` to validate :param intermediates: A :class:`list` of intermediate :class:`~cryptography.x509.Certificate` to attempt to use :returns: A new instance of :class:`VerifiedClient` :raises VerificationError: If a valid chain cannot be constructed :raises UnsupportedGeneralNameType: If a valid chain exists, but contains an unsupported general name type .. class:: ServerVerifier .. versionadded:: 42.0.0 A ServerVerifier verifies server certificates. It contains and describes various pieces of configurable path validation logic, such as which subject to expect, how deep prospective validation chains may go, which signature algorithms are allowed, and so forth. ServerVerifier instances cannot be constructed directly; :class:`PolicyBuilder` must be used. .. attribute:: subject :type: :class:`Subject` The verifier's subject. .. attribute:: validation_time :type: :class:`datetime.datetime` The verifier's validation time. .. attribute:: max_chain_depth :type: :class:`int` The verifier's maximum intermediate CA chain depth. .. attribute:: store :type: :class:`Store` The verifier's trust store. .. method:: verify(leaf, intermediates) Performs path validation on ``leaf``, returning a valid path if one exists. The path is returned in leaf-first order: the first member is ``leaf``, followed by the intermediates used (if any), followed by a member of the ``store``. :param leaf: The leaf :class:`~cryptography.x509.Certificate` to validate :param intermediates: A :class:`list` of intermediate :class:`~cryptography.x509.Certificate` to attempt to use :returns: A list containing a valid chain from ``leaf`` to a member of :class:`ServerVerifier.store`. :raises VerificationError: If a valid chain cannot be constructed .. class:: VerificationError .. versionadded:: 42.0.0 The error raised when path validation fails. .. class:: PolicyBuilder .. versionadded:: 42.0.0 A PolicyBuilder provides a builder-style interface for constructing a Verifier. .. method:: time(new_time) Sets the verifier's verification time. If not called explicitly, this is set to :meth:`datetime.datetime.now` when :meth:`build_server_verifier` or :meth:`build_client_verifier` is called. :param new_time: The :class:`datetime.datetime` to use in the verifier :returns: A new instance of :class:`PolicyBuilder` .. method:: store(new_store) Sets the verifier's trust store. :param new_store: The :class:`Store` to use in the verifier :returns: A new instance of :class:`PolicyBuilder` .. method:: max_chain_depth(new_max_chain_depth) Sets the verifier's maximum chain building depth. This depth behaves tracks the length of the intermediate CA chain: a maximum depth of zero means that the leaf must be directly issued by a member of the store, a depth of one means no more than one intermediate CA, and so forth. Note that self-issued intermediates don't count against the chain depth, per RFC 5280. :param new_max_chain_depth: The maximum depth to allow in the verifier :returns: A new instance of :class:`PolicyBuilder` .. method:: build_server_verifier(subject) Builds a verifier for verifying server certificates. :param subject: A :class:`Subject` to use in the verifier :returns: An instance of :class:`ServerVerifier` .. method:: build_client_verifier() .. versionadded:: 43.0.0 Builds a verifier for verifying client certificates. .. warning:: This API is not suitable for website (i.e. server) certificate verification. You **must** use :meth:`build_server_verifier` for server verification. :returns: An instance of :class:`ClientVerifier` cryptography-43.0.0/noxfile.py010064400017510000177000000232151464676315000145520ustar 00000000000000# 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. from __future__ import annotations import glob import itertools import json import pathlib import re import sys import uuid import nox try: import tomllib except ImportError: import tomli as tomllib # type: ignore[import-not-found,no-redef] nox.options.reuse_existing_virtualenvs = True def install( session: nox.Session, *args: str, verbose: bool = True, ) -> None: if verbose: args += ("-v",) session.install( "-c", "ci-constraints-requirements.txt", *args, silent=False, ) def load_pyproject_toml() -> dict: with (pathlib.Path(__file__).parent / "pyproject.toml").open("rb") as f: return tomllib.load(f) @nox.session @nox.session(name="tests-ssh") @nox.session(name="tests-randomorder") @nox.session(name="tests-nocoverage") def tests(session: nox.Session) -> None: extras = "test" if session.name == "tests-ssh": extras += ",ssh" if session.name == "tests-randomorder": extras += ",test-randomorder" prof_location = ( pathlib.Path(".") / ".rust-cov" / str(uuid.uuid4()) ).absolute() if session.name != "tests-nocoverage": session.env.update( { "RUSTFLAGS": "-Cinstrument-coverage " + session.env.get("RUSTFLAGS", ""), "LLVM_PROFILE_FILE": str(prof_location / "cov-%p.profraw"), } ) install(session, "-e", "./vectors") install(session, f".[{extras}]") session.run("pip", "list") if session.name != "tests-nocoverage": cov_args = [ "--cov=cryptography", "--cov=tests", ] else: cov_args = [] if session.posargs: tests = session.posargs else: tests = ["tests/"] session.run( "pytest", "-n", "auto", "--dist=worksteal", *cov_args, "--durations=10", *tests, ) if session.name != "tests-nocoverage": [rust_so] = glob.glob( f"{session.virtualenv.location}/**/cryptography/hazmat/bindings/_rust.*", recursive=True, ) process_rust_coverage(session, [rust_so], prof_location) @nox.session def docs(session: nox.Session) -> None: install(session, ".[docs,docstest,sdist,ssh]") temp_dir = session.create_tmp() session.run( "sphinx-build", "-T", "-W", "-b", "html", "-d", f"{temp_dir}/doctrees", "docs", "docs/_build/html", ) session.run( "sphinx-build", "-T", "-W", "-b", "latex", "-d", f"{temp_dir}/doctrees", "docs", "docs/_build/latex", ) session.run( "sphinx-build", "-T", "-W", "-b", "doctest", "-d", f"{temp_dir}/doctrees", "docs", "docs/_build/html", ) session.run( "sphinx-build", "-T", "-W", "-b", "spelling", "docs", "docs/_build/html", ) session.run( "python3", "-m", "readme_renderer", "README.rst", "-o", "/dev/null" ) @nox.session(name="docs-linkcheck") def docs_linkcheck(session: nox.Session) -> None: install(session, ".[docs]") session.run( "sphinx-build", "-W", "-b", "linkcheck", "docs", "docs/_build/html" ) @nox.session def flake(session: nox.Session) -> None: # TODO: Ideally there'd be a pip flag to install just our dependencies, # but not install us. pyproject_data = load_pyproject_toml() install(session, "-e", "vectors/") install( session, *pyproject_data["build-system"]["requires"], *pyproject_data["project"]["optional-dependencies"]["pep8test"], *pyproject_data["project"]["optional-dependencies"]["test"], *pyproject_data["project"]["optional-dependencies"]["ssh"], *pyproject_data["project"]["optional-dependencies"]["nox"], ) session.run("ruff", "check", ".") session.run("ruff", "format", "--check", ".") session.run( "mypy", "src/cryptography/", "vectors/cryptography_vectors/", "tests/", "release.py", "noxfile.py", ) session.run("check-sdist", "--no-isolation") @nox.session @nox.session(name="rust-noclippy") def rust(session: nox.Session) -> None: prof_location = ( pathlib.Path(".") / ".rust-cov" / str(uuid.uuid4()) ).absolute() session.env.update( { "RUSTFLAGS": "-Cinstrument-coverage " + session.env.get("RUSTFLAGS", ""), "LLVM_PROFILE_FILE": str(prof_location / "cov-%p.profraw"), } ) # TODO: Ideally there'd be a pip flag to install just our dependencies, # but not install us. pyproject_data = load_pyproject_toml() install(session, *pyproject_data["build-system"]["requires"]) with session.chdir("src/rust/"): session.run("cargo", "fmt", "--all", "--", "--check", external=True) if session.name != "rust-noclippy": session.run( "cargo", "clippy", "--all", "--", "-D", "warnings", external=True, ) build_output = session.run( "cargo", "test", "--no-default-features", "--all", "--no-run", "-q", "--message-format=json", external=True, silent=True, ) session.run( "cargo", "test", "--no-default-features", "--all", external=True ) # It's None on install-only invocations if build_output is not None: assert isinstance(build_output, str) rust_tests = [] for line in build_output.splitlines(): data = json.loads(line) if data.get("profile", {}).get("test", False): rust_tests.extend(data["filenames"]) process_rust_coverage(session, rust_tests, prof_location) @nox.session(venv_backend="uv") def local(session): pyproject_data = load_pyproject_toml() install(session, "-e", "./vectors") install( session, *pyproject_data["build-system"]["requires"], *pyproject_data["project"]["optional-dependencies"]["pep8test"], *pyproject_data["project"]["optional-dependencies"]["test"], *pyproject_data["project"]["optional-dependencies"]["ssh"], *pyproject_data["project"]["optional-dependencies"]["nox"], verbose=False, ) session.run("ruff", "format", ".") session.run("ruff", "check", ".") with session.chdir("src/rust/"): session.run("cargo", "fmt", "--all", external=True) session.run("cargo", "check", "--all", "--tests", external=True) session.run( "cargo", "clippy", "--all", "--", "-D", "warnings", external=True, ) session.run( "mypy", "src/cryptography/", "vectors/cryptography_vectors/", "tests/", "release.py", "noxfile.py", ) session.run( "maturin", "develop", "--release", "--uv", ) if session.posargs: tests = session.posargs else: tests = ["tests/"] session.run( "pytest", "-n", "auto", "--dist=worksteal", "--durations=10", *tests, ) with session.chdir("src/rust/"): session.run( "cargo", "test", "--no-default-features", "--all", external=True ) LCOV_SOURCEFILE_RE = re.compile( r"^SF:.*[\\/]src[\\/]rust[\\/](.*)$", flags=re.MULTILINE ) BIN_EXT = ".exe" if sys.platform == "win32" else "" def process_rust_coverage( session: nox.Session, rust_binaries: list[str], prof_raw_location: pathlib.Path, ) -> None: # Hitting weird issues merging Windows and Linux Rust coverage, so just # say the hell with it. if sys.platform == "win32": return target_libdir = session.run( "rustc", "--print", "target-libdir", external=True, silent=True ) if target_libdir is not None: target_bindir = pathlib.Path(target_libdir).parent / "bin" profraws = [ str(prof_raw_location / p) for p in prof_raw_location.glob("*.profraw") ] session.run( str(target_bindir / ("llvm-profdata" + BIN_EXT)), "merge", "-sparse", *profraws, "-o", "rust-cov.profdata", external=True, ) lcov_data = session.run( str(target_bindir / ("llvm-cov" + BIN_EXT)), "export", rust_binaries[0], *itertools.chain.from_iterable( ["-object", b] for b in rust_binaries[1:] ), "-instr-profile=rust-cov.profdata", "--ignore-filename-regex=[/\\].cargo[/\\]", "--ignore-filename-regex=[/\\]rustc[/\\]", "--ignore-filename-regex=[/\\].rustup[/\\]toolchains[/\\]", "--ignore-filename-regex=[/\\]target[/\\]", "--format=lcov", silent=True, external=True, ) assert isinstance(lcov_data, str) lcov_data = LCOV_SOURCEFILE_RE.sub( lambda m: "SF:src/rust/" + m.group(1).replace("\\", "/"), lcov_data.replace("\r\n", "\n"), ) with open(f"{uuid.uuid4()}.lcov", "w") as f: f.write(lcov_data) cryptography-43.0.0/pyproject.toml010064400017510000177000000116711464676315000154530ustar 00000000000000[build-system] # These requirements must be kept sync with the requirements in # ./github/requirements/build-requirements.{in,txt} requires = [ "maturin>=1,<2", # Must be kept in sync with `project.dependencies` "cffi>=1.12; platform_python_implementation != 'PyPy'", # Needed because cffi imports distutils, and in Python 3.12, distutils has # been removed from the stdlib, but installing setuptools puts it back. "setuptools", ] build-backend = "maturin" [project] name = "cryptography" version = "43.0.0" authors = [ {name = "The Python Cryptographic Authority and individual contributors", email = "cryptography-dev@python.org"} ] description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." readme = "README.rst" license = {text = "Apache-2.0 OR BSD-3-Clause"} classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX", "Operating System :: POSIX :: BSD", "Operating System :: POSIX :: Linux", 'Operating System :: Microsoft :: Windows', "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Security :: Cryptography", ] requires-python = ">=3.7" dependencies = [ # Must be kept in sync with `build-system.requires` "cffi>=1.12; platform_python_implementation != 'PyPy'", ] [project.urls] homepage = "https://github.com/pyca/cryptography" documentation = "https://cryptography.io/" source = "https://github.com/pyca/cryptography/" issues = "https://github.com/pyca/cryptography/issues" changelog = "https://cryptography.io/en/latest/changelog/" [project.optional-dependencies] ssh = ["bcrypt >=3.1.5"] # All the following are used for our own testing. nox = ["nox"] test = [ "cryptography_vectors==43.0.0", "pytest >=6.2.0", "pytest-benchmark", "pytest-cov", "pytest-xdist", "pretend", "certifi", ] test-randomorder = ["pytest-randomly"] docs = ["sphinx >=5.3.0", "sphinx-rtd-theme >=1.1.1"] docstest = ["pyenchant >=1.6.11", "readme-renderer", "sphinxcontrib-spelling >=4.0.1"] sdist = ["build"] # `click` included because its needed to type check `release.py` pep8test = ["ruff", "mypy", "check-sdist", "click"] [tool.maturin] python-source = "src" python-packages = ["cryptography"] manifest-path = "src/rust/Cargo.toml" module-name = "cryptography.hazmat.bindings._rust" locked = true sdist-generator = "git" features = ["pyo3/abi3-py37"] include = [ "CHANGELOG.rst", "CONTRIBUTING.rst", "LICENSE", "LICENSE.APACHE", "LICENSE.BSD", "docs/**/*", "src/_cffi_src/**/*.py", "src/_cffi_src/**/*.c", "src/_cffi_src/**/*.h", "src/rust/**/Cargo.toml", "src/rust/**/Cargo.lock", "src/rust/**/*.rs", "tests/**/*.py", ] exclude = [ "vectors/**/*", "src/rust/target/**/*", "docs/_build/**/*", ".github/**/*", ".readthedocs.yml", "ci-constraints-requirements.txt", "mypy.ini", ] [tool.pytest.ini_options] addopts = "-r s --capture=no --strict-markers --benchmark-disable" console_output_style = "progress-even-when-capture-no" markers = [ "skip_fips: this test is not executed in FIPS mode", "supported: parametrized test requiring only_if and skip_message", ] [tool.mypy] show_error_codes = true check_untyped_defs = true no_implicit_reexport = true warn_redundant_casts = true warn_unused_ignores = true warn_unused_configs = true strict_equality = true [[tool.mypy.overrides]] module = [ "pretend" ] ignore_missing_imports = true [tool.coverage.run] branch = true relative_files = true source = [ "cryptography", "tests/", ] [tool.coverage.paths] source = [ "src/cryptography", "*.nox/*/lib*/python*/site-packages/cryptography", "*.nox\\*\\Lib\\site-packages\\cryptography", "*.nox/pypy/site-packages/cryptography", ] tests =[ "tests/", "*tests\\", ] [tool.coverage.report] exclude_lines = [ "@abc.abstractmethod", "@typing.overload", "if typing.TYPE_CHECKING", ] [tool.ruff] line-length = 79 lint.ignore = ['N818'] lint.select = ['E', 'F', 'I', 'N', 'W', 'UP', 'RUF'] [tool.ruff.lint.isort] known-first-party = ["cryptography", "cryptography_vectors", "tests"] [tool.check-sdist] git-only = [ "vectors/*", "release.py", "ci-constraints-requirements.txt", ".gitattributes", ".gitignore", ] cryptography-43.0.0/release.py010064400017510000177000000047571464676315000145400ustar 00000000000000# 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. import pathlib import re import subprocess import click import tomllib from packaging.version import Version def run(*args: str) -> None: print(f"[running] {list(args)}") subprocess.check_call(list(args)) @click.group() def cli(): pass @cli.command() def release() -> None: base_dir = pathlib.Path(__file__).parent with (base_dir / "pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) version = pyproject["project"]["version"] if Version(version).is_prerelease: raise RuntimeError( f"Can't release, pyproject.toml version is pre-release: {version}" ) # Tag and push the tag (this will trigger the wheel builder in Actions) run("git", "tag", "-s", version, "-m", f"{version} release") run("git", "push", "--tags", "git@github.com:pyca/cryptography.git") def replace_pattern(p: pathlib.Path, pattern: str, replacement: str) -> None: content = p.read_text() match = re.search(pattern, content, re.MULTILINE) assert match is not None start, end = match.span() new_content = content[:start] + replacement + content[end:] p.write_text(new_content) def replace_version( p: pathlib.Path, variable_name: str, new_version: str ) -> None: replace_pattern( p, rf"^{variable_name}\s*=\s*.*$", f'{variable_name} = "{new_version}"' ) @cli.command() @click.argument("new_version") def bump_version(new_version: str) -> None: base_dir = pathlib.Path(__file__).parent replace_version(base_dir / "pyproject.toml", "version", new_version) replace_version( base_dir / "src/cryptography/__about__.py", "__version__", new_version ) replace_version( base_dir / "vectors/pyproject.toml", "version", new_version, ) replace_version( base_dir / "vectors/cryptography_vectors/__about__.py", "__version__", new_version, ) if Version(new_version).is_prerelease: replace_pattern( base_dir / "pyproject.toml", r'"cryptography_vectors(==.*?)?"', '"cryptography_vectors"', ) else: replace_pattern( base_dir / "pyproject.toml", r'"cryptography_vectors(==.*?)?"', f'"cryptography_vectors=={new_version}"', ) if __name__ == "__main__": cli() cryptography-43.0.0/src/_cffi_src/__init__.py010064400017510000177000000000001464676315000173340ustar 00000000000000cryptography-43.0.0/src/_cffi_src/build_openssl.py010064400017510000177000000030431464676315000204510ustar 00000000000000# 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. from __future__ import annotations import os import pathlib import platform import sys # Add the src directory to the path so we can import _cffi_src.utils src_dir = str(pathlib.Path(__file__).parent.parent) sys.path.insert(0, src_dir) from _cffi_src.utils import build_ffi_for_binding # noqa: E402 ffi = build_ffi_for_binding( module_name="_openssl", module_prefix="_cffi_src.openssl.", modules=[ # This goes first so we can define some cryptography-wide symbols. "cryptography", "asn1", "bignum", "bio", "crypto", "dh", "dsa", "ec", "engine", "err", "evp", "nid", "objects", "opensslv", "pem", "rand", "rsa", "ssl", "x509", "x509name", "x509v3", "x509_vfy", ], ) if __name__ == "__main__": out_dir = os.environ["OUT_DIR"] module_name, source, source_extension, kwds = ffi._assigned_source c_file = os.path.join(out_dir, module_name + source_extension) if platform.python_implementation() == "PyPy": # Necessary because CFFI will ignore this if there's no declarations. ffi.embedding_api( """ extern "Python" void Cryptography_unused(void); """ ) ffi.embedding_init_code("") ffi.emit_c_code(c_file) cryptography-43.0.0/src/_cffi_src/openssl/__init__.py010064400017510000177000000002641464676315000210330ustar 00000000000000# 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. cryptography-43.0.0/src/_cffi_src/openssl/asn1.py010064400017510000177000000034771464676315000201470ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef int... time_t; typedef ... ASN1_INTEGER; struct asn1_string_st { int length; int type; unsigned char *data; long flags; }; typedef struct asn1_string_st ASN1_OCTET_STRING; typedef struct asn1_string_st ASN1_IA5STRING; typedef struct asn1_string_st ASN1_TIME; typedef ... ASN1_OBJECT; typedef struct asn1_string_st ASN1_STRING; typedef ... ASN1_GENERALIZEDTIME; typedef ... ASN1_ENUMERATED; static const int V_ASN1_GENERALIZEDTIME; static const int MBSTRING_UTF8; """ FUNCTIONS = """ /* ASN1 STRING */ const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *); /* ASN1 INTEGER */ void ASN1_INTEGER_free(ASN1_INTEGER *); int ASN1_INTEGER_set(ASN1_INTEGER *, long); /* ASN1 TIME */ ASN1_TIME *ASN1_TIME_new(void); void ASN1_TIME_free(ASN1_TIME *); int ASN1_TIME_set_string(ASN1_TIME *, const char *); /* ASN1 GENERALIZEDTIME */ void ASN1_GENERALIZEDTIME_free(ASN1_GENERALIZEDTIME *); /* ASN1 ENUMERATED */ ASN1_ENUMERATED *ASN1_ENUMERATED_new(void); void ASN1_ENUMERATED_free(ASN1_ENUMERATED *); int ASN1_ENUMERATED_set(ASN1_ENUMERATED *, long); int ASN1_STRING_type(const ASN1_STRING *); int ASN1_STRING_to_UTF8(unsigned char **, const ASN1_STRING *); int i2a_ASN1_INTEGER(BIO *, const ASN1_INTEGER *); ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(const ASN1_TIME *, ASN1_GENERALIZEDTIME **); int ASN1_STRING_length(ASN1_STRING *); BIGNUM *ASN1_INTEGER_to_BN(ASN1_INTEGER *, BIGNUM *); ASN1_INTEGER *BN_to_ASN1_INTEGER(BIGNUM *, ASN1_INTEGER *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/bignum.py010064400017510000177000000021361464676315000205550ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ static const long Cryptography_HAS_PRIME_CHECKS; typedef ... BN_CTX; typedef ... BIGNUM; typedef int... BN_ULONG; """ FUNCTIONS = """ BIGNUM *BN_new(void); void BN_free(BIGNUM *); int BN_rand_range(BIGNUM *, const BIGNUM *); int BN_set_word(BIGNUM *, BN_ULONG); char *BN_bn2hex(const BIGNUM *); int BN_hex2bn(BIGNUM **, const char *); /* The following 3 prime methods are exposed for Tribler. */ int BN_generate_prime_ex(BIGNUM *, int, int, const BIGNUM *, const BIGNUM *, BN_GENCB *); int BN_is_prime_ex(const BIGNUM *, int, BN_CTX *, BN_GENCB *); const int BN_prime_checks_for_size(int); """ CUSTOMIZATIONS = """ #if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_PRIME_CHECKS = 0; int (*BN_prime_checks_for_size)(int) = NULL; #else static const long Cryptography_HAS_PRIME_CHECKS = 1; #endif """ cryptography-43.0.0/src/_cffi_src/openssl/bio.py010064400017510000177000000023221464676315000200420ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... BIO; typedef ... BIO_METHOD; typedef ... BIO_ADDR; """ FUNCTIONS = """ int BIO_free(BIO *); BIO *BIO_new_file(const char *, const char *); int BIO_read(BIO *, void *, int); int BIO_write(BIO *, const void *, int); BIO *BIO_new(BIO_METHOD *); const BIO_METHOD *BIO_s_mem(void); BIO *BIO_new_mem_buf(const void *, int); long BIO_set_mem_eof_return(BIO *, int); long BIO_get_mem_data(BIO *, char **); int BIO_should_read(BIO *); int BIO_should_write(BIO *); int BIO_should_io_special(BIO *); int BIO_should_retry(BIO *); int BIO_reset(BIO *); BIO_ADDR *BIO_ADDR_new(void); void BIO_ADDR_free(BIO_ADDR *); """ CUSTOMIZATIONS = """ #if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL #if !defined(_WIN32) #include #endif #include typedef struct sockaddr BIO_ADDR; BIO_ADDR *BIO_ADDR_new(void) { return malloc(sizeof(struct sockaddr_storage)); } void BIO_ADDR_free(BIO_ADDR *ptr) { free(ptr); } #endif """ cryptography-43.0.0/src/_cffi_src/openssl/crypto.py010064400017510000177000000012211464676315000206060ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ static const int OPENSSL_VERSION; static const int OPENSSL_CFLAGS; static const int OPENSSL_BUILT_ON; static const int OPENSSL_PLATFORM; static const int OPENSSL_DIR; """ FUNCTIONS = """ void OPENSSL_cleanup(void); unsigned long OpenSSL_version_num(void); const char *OpenSSL_version(int); void *OPENSSL_malloc(size_t); void OPENSSL_free(void *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/cryptography.py010064400017510000177000000024761464676315000220360ustar 00000000000000# 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. from __future__ import annotations INCLUDES = r""" /* define our OpenSSL API compatibility level to 1.1.0. Any symbols older than that will raise an error during compilation. */ #define OPENSSL_API_COMPAT 0x10100000L #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include /* undef some macros that are defined by wincrypt.h but are also types in boringssl. openssl has worked around this but boring has not yet. see: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/base /win/wincrypt_shim.h */ #undef X509_NAME #undef X509_EXTENSIONS #undef PKCS7_SIGNER_INFO #endif #include #if defined(LIBRESSL_VERSION_NUMBER) #define CRYPTOGRAPHY_IS_LIBRESSL 1 #else #define CRYPTOGRAPHY_IS_LIBRESSL 0 #endif #if defined(OPENSSL_IS_BORINGSSL) #define CRYPTOGRAPHY_IS_BORINGSSL 1 #else #define CRYPTOGRAPHY_IS_BORINGSSL 0 #endif #if OPENSSL_VERSION_NUMBER < 0x10101050 #error "pyca/cryptography MUST be linked with Openssl 1.1.1e or later" #endif """ TYPES = """ """ FUNCTIONS = """ """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/dh.py010064400017510000177000000005501464676315000176650ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... DH; """ FUNCTIONS = """ void DH_free(DH *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/dsa.py010064400017510000177000000010431464676315000200370ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... DSA; """ FUNCTIONS = """ int DSA_generate_key(DSA *); DSA *DSA_new(void); void DSA_free(DSA *); int DSA_generate_parameters_ex(DSA *, int, unsigned char *, int, int *, unsigned long *, BN_GENCB *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/ec.py010064400017510000177000000010771464676315000176660ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include #include """ TYPES = """ typedef ... EC_KEY; typedef struct { int nid; const char *comment; } EC_builtin_curve; """ FUNCTIONS = """ size_t EC_get_builtin_curves(EC_builtin_curve *, size_t); void EC_KEY_free(EC_KEY *); EC_KEY *EC_KEY_new_by_curve_name(int); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/engine.py010064400017510000177000000046621464676315000205470ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... ENGINE; typedef ... UI_METHOD; static const long Cryptography_HAS_ENGINE; """ FUNCTIONS = """ ENGINE *ENGINE_by_id(const char *); int ENGINE_init(ENGINE *); int ENGINE_finish(ENGINE *); ENGINE *ENGINE_get_default_RAND(void); int ENGINE_set_default_RAND(ENGINE *); void ENGINE_unregister_RAND(ENGINE *); int ENGINE_ctrl_cmd(ENGINE *, const char *, long, void *, void (*)(void), int); int ENGINE_free(ENGINE *); const char *ENGINE_get_name(const ENGINE *); // These bindings are unused by cryptography or pyOpenSSL but are present // for advanced users who need them. int ENGINE_ctrl_cmd_string(ENGINE *, const char *, const char *, int); void ENGINE_load_builtin_engines(void); EVP_PKEY *ENGINE_load_private_key(ENGINE *, const char *, UI_METHOD *, void *); EVP_PKEY *ENGINE_load_public_key(ENGINE *, const char *, UI_METHOD *, void *); """ CUSTOMIZATIONS = """ #ifdef OPENSSL_NO_ENGINE static const long Cryptography_HAS_ENGINE = 0; #if CRYPTOGRAPHY_IS_BORINGSSL typedef void UI_METHOD; #endif /* Despite being OPENSSL_NO_ENGINE, BoringSSL/LibreSSL define these symbols. */ #if !CRYPTOGRAPHY_IS_BORINGSSL && !CRYPTOGRAPHY_IS_LIBRESSL int (*ENGINE_free)(ENGINE *) = NULL; void (*ENGINE_load_builtin_engines)(void) = NULL; #endif ENGINE *(*ENGINE_get_default_RAND)(void) = NULL; int (*ENGINE_set_default_RAND)(ENGINE *) = NULL; void (*ENGINE_unregister_RAND)(ENGINE *) = NULL; #if !CRYPTOGRAPHY_IS_LIBRESSL ENGINE *(*ENGINE_by_id)(const char *) = NULL; int (*ENGINE_init)(ENGINE *) = NULL; int (*ENGINE_finish)(ENGINE *) = NULL; int (*ENGINE_ctrl_cmd)(ENGINE *, const char *, long, void *, void (*)(void), int) = NULL; const char *(*ENGINE_get_id)(const ENGINE *) = NULL; const char *(*ENGINE_get_name)(const ENGINE *) = NULL; int (*ENGINE_ctrl_cmd_string)(ENGINE *, const char *, const char *, int) = NULL; EVP_PKEY *(*ENGINE_load_private_key)(ENGINE *, const char *, UI_METHOD *, void *) = NULL; EVP_PKEY *(*ENGINE_load_public_key)(ENGINE *, const char *, UI_METHOD *, void *) = NULL; #endif #else static const long Cryptography_HAS_ENGINE = 1; #endif """ cryptography-43.0.0/src/_cffi_src/openssl/err.py010064400017510000177000000027201464676315000200630ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ static const int EVP_F_EVP_ENCRYPTFINAL_EX; static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; static const int ERR_LIB_EVP; static const int SSL_TLSEXT_ERR_OK; static const int SSL_TLSEXT_ERR_ALERT_FATAL; static const int SSL_TLSEXT_ERR_NOACK; static const int SSL_R_UNEXPECTED_EOF_WHILE_READING; static const int Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING; """ FUNCTIONS = """ const char *ERR_lib_error_string(unsigned long); const char *ERR_func_error_string(unsigned long); const char *ERR_reason_error_string(unsigned long); unsigned long ERR_get_error(void); unsigned long ERR_peek_error(void); void ERR_clear_error(void); void ERR_put_error(int, int, int, const char *, int); int ERR_GET_REASON(unsigned long); """ CUSTOMIZATIONS = """ #if CRYPTOGRAPHY_IS_BORINGSSL static const int EVP_F_EVP_ENCRYPTFINAL_EX = 0; static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH = 0; #endif /* SSL_R_UNEXPECTED_EOF_WHILE_READING is needed for pyOpenSSL with OpenSSL 3+ */ #if defined(SSL_R_UNEXPECTED_EOF_WHILE_READING) #define Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING 1 #else #define Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING 0 #define SSL_R_UNEXPECTED_EOF_WHILE_READING 0 #endif """ cryptography-43.0.0/src/_cffi_src/openssl/evp.py010064400017510000177000000032471464676315000200720ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... EVP_CIPHER; typedef ... EVP_MD; typedef ... EVP_MD_CTX; typedef ... EVP_PKEY; typedef ... EVP_PKEY_CTX; static const int EVP_PKEY_RSA; static const int EVP_PKEY_DSA; static const int EVP_PKEY_DH; static const int EVP_PKEY_EC; static const int EVP_MAX_MD_SIZE; static const int Cryptography_HAS_EVP_PKEY_DHX; """ FUNCTIONS = """ const EVP_CIPHER *EVP_get_cipherbyname(const char *); const EVP_MD *EVP_get_digestbyname(const char *); EVP_PKEY *EVP_PKEY_new(void); void EVP_PKEY_free(EVP_PKEY *); int EVP_PKEY_type(int); int EVP_PKEY_size(EVP_PKEY *); RSA *EVP_PKEY_get1_RSA(EVP_PKEY *); int EVP_SignInit(EVP_MD_CTX *, const EVP_MD *); int EVP_SignUpdate(EVP_MD_CTX *, const void *, size_t); int EVP_SignFinal(EVP_MD_CTX *, unsigned char *, unsigned int *, EVP_PKEY *); int EVP_VerifyInit(EVP_MD_CTX *, const EVP_MD *); int EVP_VerifyUpdate(EVP_MD_CTX *, const void *, size_t); int EVP_VerifyFinal(EVP_MD_CTX *, const unsigned char *, unsigned int, EVP_PKEY *); int EVP_PKEY_set1_RSA(EVP_PKEY *, RSA *); int EVP_PKEY_set1_DSA(EVP_PKEY *, DSA *); int EVP_PKEY_id(const EVP_PKEY *); EVP_MD_CTX *EVP_MD_CTX_new(void); void EVP_MD_CTX_free(EVP_MD_CTX *); int EVP_PKEY_bits(const EVP_PKEY *); int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); """ CUSTOMIZATIONS = """ #ifdef EVP_PKEY_DHX const long Cryptography_HAS_EVP_PKEY_DHX = 1; #else const long Cryptography_HAS_EVP_PKEY_DHX = 0; #endif """ cryptography-43.0.0/src/_cffi_src/openssl/nid.py010064400017510000177000000006561464676315000200530ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ static const int NID_undef; static const int NID_subject_alt_name; static const int NID_crl_reason; """ FUNCTIONS = """ """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/objects.py010064400017510000177000000007101464676315000207210ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ """ FUNCTIONS = """ const char *OBJ_nid2ln(int); const char *OBJ_nid2sn(int); int OBJ_obj2nid(const ASN1_OBJECT *); int OBJ_txt2nid(const char *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/opensslv.py010064400017510000177000000010531464676315000211420ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ /* Note that these will be resolved when cryptography is compiled and are NOT guaranteed to be the version that it actually loads. */ static const int OPENSSL_VERSION_NUMBER; static const char *const OPENSSL_VERSION_TEXT; """ FUNCTIONS = """ """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/pem.py010064400017510000177000000023111464676315000200500ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef int pem_password_cb(char *buf, int size, int rwflag, void *userdata); """ FUNCTIONS = """ X509 *PEM_read_bio_X509(BIO *, X509 **, pem_password_cb *, void *); int PEM_write_bio_X509(BIO *, X509 *); int PEM_write_bio_PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *, unsigned char *, int, pem_password_cb *, void *); EVP_PKEY *PEM_read_bio_PrivateKey(BIO *, EVP_PKEY **, pem_password_cb *, void *); int PEM_write_bio_X509_REQ(BIO *, X509_REQ *); X509_REQ *PEM_read_bio_X509_REQ(BIO *, X509_REQ **, pem_password_cb *, void *); X509_CRL *PEM_read_bio_X509_CRL(BIO *, X509_CRL **, pem_password_cb *, void *); int PEM_write_bio_X509_CRL(BIO *, X509_CRL *); DH *PEM_read_bio_DHparams(BIO *, DH **, pem_password_cb *, void *); EVP_PKEY *PEM_read_bio_PUBKEY(BIO *, EVP_PKEY **, pem_password_cb *, void *); int PEM_write_bio_PUBKEY(BIO *, EVP_PKEY *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/pkcs7.py010064400017510000177000000006341464676315000203240ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... PKCS7; """ FUNCTIONS = """ void PKCS7_free(PKCS7 *); PKCS7 *SMIME_read_PKCS7(BIO *, BIO **); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/rand.py010064400017510000177000000006551464676315000202240ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ """ FUNCTIONS = """ void RAND_add(const void *, int, double); int RAND_status(void); int RAND_bytes(unsigned char *, int); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/rsa.py010064400017510000177000000014501464676315000200570ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... RSA; typedef ... BN_GENCB; static const int RSA_F4; static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION; """ FUNCTIONS = """ RSA *RSA_new(void); void RSA_free(RSA *); int RSA_generate_key_ex(RSA *, int, BIGNUM *, BN_GENCB *); int RSA_check_key(const RSA *); int RSA_print(BIO *, const RSA *, int); """ CUSTOMIZATIONS = """ #if defined(EVP_PKEY_CTRL_RSA_IMPLICIT_REJECTION) static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION = 1; #else static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION = 0; #endif """ cryptography-43.0.0/src/_cffi_src/openssl/ssl.py010064400017510000177000000624201464676315000200770ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ static const long Cryptography_HAS_SSL_ST; static const long Cryptography_HAS_TLS_ST; static const long Cryptography_HAS_TLSv1_3_FUNCTIONS; static const long Cryptography_HAS_SIGALGS; static const long Cryptography_HAS_PSK; static const long Cryptography_HAS_PSK_TLSv1_3; static const long Cryptography_HAS_VERIFIED_CHAIN; static const long Cryptography_HAS_KEYLOG; static const long Cryptography_HAS_SSL_COOKIE; static const long Cryptography_HAS_OP_NO_RENEGOTIATION; static const long Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF; static const long Cryptography_HAS_ALPN; static const long Cryptography_HAS_NEXTPROTONEG; static const long Cryptography_HAS_SET_CERT_CB; static const long Cryptography_HAS_GET_EXTMS_SUPPORT; static const long Cryptography_HAS_CUSTOM_EXT; static const long Cryptography_HAS_SRTP; static const long Cryptography_HAS_DTLS_GET_DATA_MTU; static const long SSL_FILETYPE_PEM; static const long SSL_FILETYPE_ASN1; static const long SSL_ERROR_NONE; static const long SSL_ERROR_ZERO_RETURN; static const long SSL_ERROR_WANT_READ; static const long SSL_ERROR_WANT_WRITE; static const long SSL_ERROR_WANT_X509_LOOKUP; static const long SSL_ERROR_SYSCALL; static const long SSL_ERROR_SSL; static const long SSL_SENT_SHUTDOWN; static const long SSL_RECEIVED_SHUTDOWN; static const long SSL_OP_NO_SSLv2; static const long SSL_OP_NO_SSLv3; static const long SSL_OP_NO_TLSv1; static const long SSL_OP_NO_TLSv1_1; static const long SSL_OP_NO_TLSv1_2; static const long SSL_OP_NO_TLSv1_3; static const long SSL_OP_NO_RENEGOTIATION; static const long SSL_OP_NO_COMPRESSION; static const long SSL_OP_SINGLE_DH_USE; static const long SSL_OP_EPHEMERAL_RSA; static const long SSL_OP_MICROSOFT_SESS_ID_BUG; static const long SSL_OP_NETSCAPE_CHALLENGE_BUG; static const long SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; static const long SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG; static const long SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER; static const long SSL_OP_MSIE_SSLV2_RSA_PADDING; static const long SSL_OP_SSLEAY_080_CLIENT_DH_BUG; static const long SSL_OP_TLS_D5_BUG; static const long SSL_OP_TLS_BLOCK_PADDING_BUG; static const long SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; static const long SSL_OP_CIPHER_SERVER_PREFERENCE; static const long SSL_OP_TLS_ROLLBACK_BUG; static const long SSL_OP_PKCS1_CHECK_1; static const long SSL_OP_PKCS1_CHECK_2; static const long SSL_OP_NETSCAPE_CA_DN_BUG; static const long SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG; static const long SSL_OP_NO_QUERY_MTU; static const long SSL_OP_COOKIE_EXCHANGE; static const long SSL_OP_NO_TICKET; static const long SSL_OP_ALL; static const long SSL_OP_SINGLE_ECDH_USE; static const long SSL_OP_IGNORE_UNEXPECTED_EOF; static const long SSL_OP_LEGACY_SERVER_CONNECT; static const long SSL_VERIFY_PEER; static const long SSL_VERIFY_FAIL_IF_NO_PEER_CERT; static const long SSL_VERIFY_CLIENT_ONCE; static const long SSL_VERIFY_NONE; static const long SSL_VERIFY_POST_HANDSHAKE; static const long SSL_SESS_CACHE_OFF; static const long SSL_SESS_CACHE_CLIENT; static const long SSL_SESS_CACHE_SERVER; static const long SSL_SESS_CACHE_BOTH; static const long SSL_SESS_CACHE_NO_AUTO_CLEAR; static const long SSL_SESS_CACHE_NO_INTERNAL_LOOKUP; static const long SSL_SESS_CACHE_NO_INTERNAL_STORE; static const long SSL_SESS_CACHE_NO_INTERNAL; static const long SSL_ST_CONNECT; static const long SSL_ST_ACCEPT; static const long SSL_ST_MASK; static const long SSL_ST_INIT; static const long SSL_ST_BEFORE; static const long SSL_ST_OK; static const long SSL_ST_RENEGOTIATE; static const long SSL_CB_LOOP; static const long SSL_CB_EXIT; static const long SSL_CB_READ; static const long SSL_CB_WRITE; static const long SSL_CB_ALERT; static const long SSL_CB_READ_ALERT; static const long SSL_CB_WRITE_ALERT; static const long SSL_CB_ACCEPT_LOOP; static const long SSL_CB_ACCEPT_EXIT; static const long SSL_CB_CONNECT_LOOP; static const long SSL_CB_CONNECT_EXIT; static const long SSL_CB_HANDSHAKE_START; static const long SSL_CB_HANDSHAKE_DONE; static const long SSL_MODE_RELEASE_BUFFERS; static const long SSL_MODE_ENABLE_PARTIAL_WRITE; static const long SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; static const long SSL_MODE_AUTO_RETRY; static const long TLS_ST_BEFORE; static const long TLS_ST_OK; static const long SSL3_VERSION; static const long TLS1_VERSION; static const long TLS1_1_VERSION; static const long TLS1_2_VERSION; static const long TLS1_3_VERSION; typedef ... SSL_METHOD; typedef ... SSL_CTX; typedef ... SSL_SESSION; typedef ... SSL; static const long TLSEXT_NAMETYPE_host_name; static const long TLSEXT_STATUSTYPE_ocsp; typedef ... SSL_CIPHER; typedef struct { const char *name; unsigned long id; } SRTP_PROTECTION_PROFILE; """ FUNCTIONS = """ /* SSL */ const char *SSL_state_string_long(const SSL *); SSL_SESSION *SSL_get1_session(SSL *); int SSL_set_session(SSL *, SSL_SESSION *); int SSL_session_reused(const SSL *); SSL *SSL_new(SSL_CTX *); void SSL_free(SSL *); int SSL_set_fd(SSL *, int); SSL_CTX *SSL_set_SSL_CTX(SSL *, SSL_CTX *); void SSL_set_bio(SSL *, BIO *, BIO *); void SSL_set_connect_state(SSL *); void SSL_set_accept_state(SSL *); void SSL_set_shutdown(SSL *, int); int SSL_get_shutdown(const SSL *); int SSL_pending(const SSL *); int SSL_write(SSL *, const void *, int); int SSL_read(SSL *, void *, int); int SSL_peek(SSL *, void *, int); X509 *SSL_get_certificate(const SSL *); X509 *SSL_get_peer_certificate(const SSL *); int SSL_get_ex_data_X509_STORE_CTX_idx(void); void SSL_set_verify(SSL *, int, int (*)(int, X509_STORE_CTX *)); int SSL_get_verify_mode(const SSL *); long SSL_get_extms_support(SSL *); X509_VERIFY_PARAM *SSL_get0_param(SSL *); X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *); Cryptography_STACK_OF_X509 *SSL_get_peer_cert_chain(const SSL *); Cryptography_STACK_OF_X509 *SSL_get0_verified_chain(const SSL *); Cryptography_STACK_OF_X509_NAME *SSL_get_client_CA_list(const SSL *); int SSL_get_error(const SSL *, int); long SSL_get_verify_result(const SSL *ssl); int SSL_do_handshake(SSL *); int SSL_shutdown(SSL *); int SSL_renegotiate(SSL *); int SSL_renegotiate_pending(SSL *); const char *SSL_get_cipher_list(const SSL *, int); int SSL_use_certificate(SSL *, X509 *); int SSL_use_PrivateKey(SSL *, EVP_PKEY *); /* context */ void SSL_CTX_free(SSL_CTX *); long SSL_CTX_set_timeout(SSL_CTX *, long); int SSL_CTX_set_default_verify_paths(SSL_CTX *); void SSL_CTX_set_verify(SSL_CTX *, int, int (*)(int, X509_STORE_CTX *)); void SSL_CTX_set_verify_depth(SSL_CTX *, int); int SSL_CTX_get_verify_mode(const SSL_CTX *); int SSL_CTX_get_verify_depth(const SSL_CTX *); int SSL_CTX_set_cipher_list(SSL_CTX *, const char *); int SSL_CTX_load_verify_locations(SSL_CTX *, const char *, const char *); void SSL_CTX_set_default_passwd_cb(SSL_CTX *, pem_password_cb *); int SSL_CTX_use_certificate(SSL_CTX *, X509 *); int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int); int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *); int SSL_CTX_use_PrivateKey(SSL_CTX *, EVP_PKEY *); int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int); int SSL_CTX_check_private_key(const SSL_CTX *); void SSL_CTX_set_cookie_generate_cb(SSL_CTX *, int (*)( SSL *, unsigned char *, unsigned int * )); void SSL_CTX_set_cookie_verify_cb(SSL_CTX *, int (*)( SSL *, const unsigned char *, unsigned int )); int SSL_CTX_use_psk_identity_hint(SSL_CTX *, const char *); void SSL_CTX_set_psk_server_callback(SSL_CTX *, unsigned int (*)( SSL *, const char *, unsigned char *, unsigned int )); void SSL_CTX_set_psk_client_callback(SSL_CTX *, unsigned int (*)( SSL *, const char *, char *, unsigned int, unsigned char *, unsigned int )); void SSL_CTX_set_psk_find_session_callback(SSL_CTX *, int (*)( SSL *, const unsigned char *, size_t, SSL_SESSION ** )); void SSL_CTX_set_psk_use_session_callback(SSL_CTX *, int (*)( SSL *, const EVP_MD *, const unsigned char **, size_t *, SSL_SESSION ** )); const SSL_CIPHER *SSL_CIPHER_find(SSL *, const unsigned char *); /* Wrap SSL_SESSION_new to avoid namespace collision. */ SSL_SESSION *Cryptography_SSL_SESSION_new(void); int SSL_SESSION_set1_master_key(SSL_SESSION *, const unsigned char *, size_t); int SSL_SESSION_set_cipher(SSL_SESSION *, const SSL_CIPHER *); int SSL_SESSION_set_protocol_version(SSL_SESSION *, int); int SSL_CTX_set_session_id_context(SSL_CTX *, const unsigned char *, unsigned int); X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *); void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *); int SSL_CTX_add_client_CA(SSL_CTX *, X509 *); void SSL_CTX_set_client_CA_list(SSL_CTX *, Cryptography_STACK_OF_X509_NAME *); void SSL_CTX_set_info_callback(SSL_CTX *, void (*)(const SSL *, int, int)); void SSL_CTX_set_msg_callback(SSL_CTX *, void (*)( int, int, int, const void *, size_t, SSL *, void * )); void SSL_CTX_set_msg_callback_arg(SSL_CTX *, void *); void SSL_CTX_set_keylog_callback(SSL_CTX *, void (*)(const SSL *, const char *)); void (*SSL_CTX_get_keylog_callback(SSL_CTX *))(const SSL *, const char *); long SSL_CTX_set1_sigalgs_list(SSL_CTX *, const char *); /* SSL_SESSION */ void SSL_SESSION_free(SSL_SESSION *); /* Information about actually used cipher */ const char *SSL_CIPHER_get_name(const SSL_CIPHER *); int SSL_CIPHER_get_bits(const SSL_CIPHER *, int *); size_t SSL_get_finished(const SSL *, void *, size_t); size_t SSL_get_peer_finished(const SSL *, void *, size_t); Cryptography_STACK_OF_X509_NAME *SSL_load_client_CA_file(const char *); const char *SSL_get_servername(const SSL *, const int); const char *SSL_CIPHER_get_version(const SSL_CIPHER *); SSL_SESSION *SSL_get_session(const SSL *); uint64_t SSL_set_options(SSL *, uint64_t); uint64_t SSL_get_options(SSL *); int SSL_want_read(const SSL *); int SSL_want_write(const SSL *); long SSL_total_renegotiations(SSL *); long SSL_CTX_set_min_proto_version(SSL_CTX *, int); long SSL_CTX_set_max_proto_version(SSL_CTX *, int); long SSL_CTX_set_tmp_ecdh(SSL_CTX *, EC_KEY *); long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *); long SSL_CTX_set_session_cache_mode(SSL_CTX *, long); long SSL_CTX_get_session_cache_mode(SSL_CTX *); long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *); uint64_t SSL_CTX_set_options(SSL_CTX *, uint64_t); uint64_t SSL_CTX_get_options(SSL_CTX *); long SSL_CTX_set_mode(SSL_CTX *, long); long SSL_CTX_clear_mode(SSL_CTX *, long); long SSL_set_mode(SSL *, long); long SSL_clear_mode(SSL *, long); const SSL_METHOD *DTLS_method(void); const SSL_METHOD *DTLS_server_method(void); const SSL_METHOD *DTLS_client_method(void); const SSL_METHOD *TLS_method(void); const SSL_METHOD *TLS_server_method(void); const SSL_METHOD *TLS_client_method(void); /*- These aren't macros these arguments are all const X on openssl > 1.0.x -*/ SSL_CTX *SSL_CTX_new(SSL_METHOD *); long SSL_CTX_get_timeout(const SSL_CTX *); const SSL_CIPHER *SSL_get_current_cipher(const SSL *); const char *SSL_get_version(const SSL *); int SSL_version(const SSL *); void SSL_set_tlsext_host_name(SSL *, char *); void SSL_CTX_set_tlsext_servername_callback( SSL_CTX *, int (*)(SSL *, int *, void *)); long SSL_set_tlsext_status_ocsp_resp(SSL *, unsigned char *, int); long SSL_get_tlsext_status_ocsp_resp(SSL *, const unsigned char **); long SSL_set_tlsext_status_type(SSL *, long); long SSL_CTX_set_tlsext_status_cb(SSL_CTX *, int(*)(SSL *, void *)); long SSL_CTX_set_tlsext_status_arg(SSL_CTX *, void *); int SSL_CTX_set_tlsext_use_srtp(SSL_CTX *, const char *); int SSL_set_tlsext_use_srtp(SSL *, const char *); SRTP_PROTECTION_PROFILE *SSL_get_selected_srtp_profile(SSL *); int SSL_CTX_set_alpn_protos(SSL_CTX *, const unsigned char *, unsigned); int SSL_set_alpn_protos(SSL *, const unsigned char *, unsigned); void SSL_CTX_set_alpn_select_cb(SSL_CTX *, int (*) (SSL *, const unsigned char **, unsigned char *, const unsigned char *, unsigned int, void *), void *); void SSL_get0_alpn_selected(const SSL *, const unsigned char **, unsigned *); void SSL_CTX_set_cert_cb(SSL_CTX *, int (*)(SSL *, void *), void *); void SSL_set_cert_cb(SSL *, int (*)(SSL *, void *), void *); size_t SSL_SESSION_get_master_key(const SSL_SESSION *, unsigned char *, size_t); size_t SSL_get_client_random(const SSL *, unsigned char *, size_t); size_t SSL_get_server_random(const SSL *, unsigned char *, size_t); int SSL_export_keying_material(SSL *, unsigned char *, size_t, const char *, size_t, const unsigned char *, size_t, int); /* DTLS support */ long Cryptography_DTLSv1_get_timeout(SSL *, time_t *, long *); long DTLSv1_handle_timeout(SSL *); long SSL_set_mtu(SSL *, long); int DTLSv1_listen(SSL *, BIO_ADDR *); size_t DTLS_get_data_mtu(SSL *); /* Custom extensions. */ typedef int (*custom_ext_add_cb)(SSL *, unsigned int, const unsigned char **, size_t *, int *, void *); typedef void (*custom_ext_free_cb)(SSL *, unsigned int, const unsigned char *, void *); typedef int (*custom_ext_parse_cb)(SSL *, unsigned int, const unsigned char *, size_t, int *, void *); int SSL_CTX_add_client_custom_ext(SSL_CTX *, unsigned int, custom_ext_add_cb, custom_ext_free_cb, void *, custom_ext_parse_cb, void *); int SSL_CTX_add_server_custom_ext(SSL_CTX *, unsigned int, custom_ext_add_cb, custom_ext_free_cb, void *, custom_ext_parse_cb, void *); int SSL_extension_supported(unsigned int); int SSL_CTX_set_ciphersuites(SSL_CTX *, const char *); int SSL_verify_client_post_handshake(SSL *); void SSL_CTX_set_post_handshake_auth(SSL_CTX *, int); void SSL_set_post_handshake_auth(SSL *, int); uint32_t SSL_SESSION_get_max_early_data(const SSL_SESSION *); int SSL_write_early_data(SSL *, const void *, size_t, size_t *); int SSL_read_early_data(SSL *, void *, size_t, size_t *); int SSL_CTX_set_max_early_data(SSL_CTX *, uint32_t); /* Added as an advanced user escape hatch. This symbol is tied to engine support but is declared in ssl.h */ int SSL_CTX_set_client_cert_engine(SSL_CTX *, ENGINE *); """ CUSTOMIZATIONS = """ #ifdef OPENSSL_NO_ENGINE int (*SSL_CTX_set_client_cert_engine)(SSL_CTX *, ENGINE *) = NULL; #endif #if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_VERIFIED_CHAIN = 0; Cryptography_STACK_OF_X509 *(*SSL_get0_verified_chain)(const SSL *) = NULL; #else static const long Cryptography_HAS_VERIFIED_CHAIN = 1; #endif static const long Cryptography_HAS_KEYLOG = 1; static const long Cryptography_HAS_NEXTPROTONEG = 0; static const long Cryptography_HAS_ALPN = 1; #ifdef SSL_OP_NO_RENEGOTIATION static const long Cryptography_HAS_OP_NO_RENEGOTIATION = 1; #else static const long Cryptography_HAS_OP_NO_RENEGOTIATION = 0; static const long SSL_OP_NO_RENEGOTIATION = 0; #endif #ifdef SSL_OP_IGNORE_UNEXPECTED_EOF static const long Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF = 1; #else static const long Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF = 0; static const long SSL_OP_IGNORE_UNEXPECTED_EOF = 1; #endif #if CRYPTOGRAPHY_IS_LIBRESSL void (*SSL_CTX_set_cert_cb)(SSL_CTX *, int (*)(SSL *, void *), void *) = NULL; void (*SSL_set_cert_cb)(SSL *, int (*)(SSL *, void *), void *) = NULL; static const long Cryptography_HAS_SET_CERT_CB = 0; long (*SSL_get_extms_support)(SSL *) = NULL; static const long Cryptography_HAS_GET_EXTMS_SUPPORT = 0; #else static const long Cryptography_HAS_SET_CERT_CB = 1; static const long Cryptography_HAS_GET_EXTMS_SUPPORT = 1; #endif /* in OpenSSL 1.1.0 the SSL_ST values were renamed to TLS_ST and several were removed */ #if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_SSL_ST = 1; #else static const long Cryptography_HAS_SSL_ST = 0; static const long SSL_ST_BEFORE = 0; static const long SSL_ST_OK = 0; static const long SSL_ST_INIT = 0; static const long SSL_ST_RENEGOTIATE = 0; #endif #if !CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_TLS_ST = 1; #else static const long Cryptography_HAS_TLS_ST = 0; static const long TLS_ST_BEFORE = 0; static const long TLS_ST_OK = 0; #endif #if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_DTLS_GET_DATA_MTU = 0; size_t (*DTLS_get_data_mtu)(SSL *) = NULL; #else static const long Cryptography_HAS_DTLS_GET_DATA_MTU = 1; #endif /* Wrap DTLSv1_get_timeout to avoid cffi to handle a 'struct timeval'. */ long Cryptography_DTLSv1_get_timeout(SSL *ssl, time_t *ptv_sec, long *ptv_usec) { struct timeval tv = { 0 }; long r = DTLSv1_get_timeout(ssl, &tv); if (r == 1) { if (ptv_sec) { *ptv_sec = tv.tv_sec; } if (ptv_usec) { *ptv_usec = tv.tv_usec; } } return r; } #if CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_SIGALGS = 0; const long (*SSL_CTX_set1_sigalgs_list)(SSL_CTX *, const char *) = NULL; #else static const long Cryptography_HAS_SIGALGS = 1; #endif #if CRYPTOGRAPHY_IS_LIBRESSL || defined(OPENSSL_NO_PSK) static const long Cryptography_HAS_PSK = 0; int (*SSL_CTX_use_psk_identity_hint)(SSL_CTX *, const char *) = NULL; void (*SSL_CTX_set_psk_server_callback)(SSL_CTX *, unsigned int (*)( SSL *, const char *, unsigned char *, unsigned int )) = NULL; void (*SSL_CTX_set_psk_client_callback)(SSL_CTX *, unsigned int (*)( SSL *, const char *, char *, unsigned int, unsigned char *, unsigned int )) = NULL; #else static const long Cryptography_HAS_PSK = 1; #endif #if !CRYPTOGRAPHY_IS_LIBRESSL && !CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_CUSTOM_EXT = 1; #else static const long Cryptography_HAS_CUSTOM_EXT = 0; typedef int (*custom_ext_add_cb)(SSL *, unsigned int, const unsigned char **, size_t *, int *, void *); typedef void (*custom_ext_free_cb)(SSL *, unsigned int, const unsigned char *, void *); typedef int (*custom_ext_parse_cb)(SSL *, unsigned int, const unsigned char *, size_t, int *, void *); int (*SSL_CTX_add_client_custom_ext)(SSL_CTX *, unsigned int, custom_ext_add_cb, custom_ext_free_cb, void *, custom_ext_parse_cb, void *) = NULL; int (*SSL_CTX_add_server_custom_ext)(SSL_CTX *, unsigned int, custom_ext_add_cb, custom_ext_free_cb, void *, custom_ext_parse_cb, void *) = NULL; int (*SSL_extension_supported)(unsigned int) = NULL; #endif #ifndef OPENSSL_NO_SRTP static const long Cryptography_HAS_SRTP = 1; #else static const long Cryptography_HAS_SRTP = 0; int (*SSL_CTX_set_tlsext_use_srtp)(SSL_CTX *, const char *) = NULL; int (*SSL_set_tlsext_use_srtp)(SSL *, const char *) = NULL; SRTP_PROTECTION_PROFILE * (*SSL_get_selected_srtp_profile)(SSL *) = NULL; #endif #if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 0; static const long SSL_VERIFY_POST_HANDSHAKE = 0; int (*SSL_CTX_set_ciphersuites)(SSL_CTX *, const char *) = NULL; int (*SSL_verify_client_post_handshake)(SSL *) = NULL; void (*SSL_CTX_set_post_handshake_auth)(SSL_CTX *, int) = NULL; void (*SSL_set_post_handshake_auth)(SSL *, int) = NULL; uint32_t (*SSL_SESSION_get_max_early_data)(const SSL_SESSION *) = NULL; int (*SSL_write_early_data)(SSL *, const void *, size_t, size_t *) = NULL; int (*SSL_read_early_data)(SSL *, void *, size_t, size_t *) = NULL; int (*SSL_CTX_set_max_early_data)(SSL_CTX *, uint32_t) = NULL; #else static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 1; #endif #if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_SSL_COOKIE = 0; static const long SSL_OP_COOKIE_EXCHANGE = 0; int (*DTLSv1_listen)(SSL *, BIO_ADDR *) = NULL; void (*SSL_CTX_set_cookie_generate_cb)(SSL_CTX *, int (*)( SSL *, unsigned char *, unsigned int * )) = NULL; void (*SSL_CTX_set_cookie_verify_cb)(SSL_CTX *, int (*)( SSL *, const unsigned char *, unsigned int )) = NULL; #else static const long Cryptography_HAS_SSL_COOKIE = 1; #endif #if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_PSK_TLSv1_3 = 0; void (*SSL_CTX_set_psk_find_session_callback)(SSL_CTX *, int (*)( SSL *, const unsigned char *, size_t, SSL_SESSION ** )) = NULL; void (*SSL_CTX_set_psk_use_session_callback)(SSL_CTX *, int (*)( SSL *, const EVP_MD *, const unsigned char **, size_t *, SSL_SESSION ** )) = NULL; #if CRYPTOGRAPHY_IS_BORINGSSL const SSL_CIPHER *(*SSL_CIPHER_find)(SSL *, const unsigned char *) = NULL; #endif int (*SSL_SESSION_set1_master_key)(SSL_SESSION *, const unsigned char *, size_t) = NULL; int (*SSL_SESSION_set_cipher)(SSL_SESSION *, const SSL_CIPHER *) = NULL; #if !CRYPTOGRAPHY_IS_BORINGSSL int (*SSL_SESSION_set_protocol_version)(SSL_SESSION *, int) = NULL; #endif SSL_SESSION *(*Cryptography_SSL_SESSION_new)(void) = NULL; #else static const long Cryptography_HAS_PSK_TLSv1_3 = 1; SSL_SESSION *Cryptography_SSL_SESSION_new(void) { return SSL_SESSION_new(); } #endif """ cryptography-43.0.0/src/_cffi_src/openssl/x509.py010064400017510000177000000137041464676315000200040ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include /* * This is part of a work-around for the difficulty cffi has in dealing with * `STACK_OF(foo)` as the name of a type. We invent a new, simpler name that * will be an alias for this type and use the alias throughout. This works * together with another opaque typedef for the same name in the TYPES section. * Note that the result is an opaque type. */ typedef STACK_OF(X509) Cryptography_STACK_OF_X509; typedef STACK_OF(X509_REVOKED) Cryptography_STACK_OF_X509_REVOKED; """ TYPES = """ typedef ... Cryptography_STACK_OF_X509; typedef ... Cryptography_STACK_OF_X509_REVOKED; typedef ... X509_ALGOR; typedef ... X509_EXTENSION; typedef ... X509_EXTENSIONS; typedef ... X509_REQ; typedef ... X509_REVOKED; typedef ... X509_CRL; typedef ... X509; typedef void (*sk_X509_EXTENSION_freefunc)(X509_EXTENSION *); """ FUNCTIONS = """ X509 *X509_new(void); void X509_free(X509 *); X509 *X509_dup(X509 *); int X509_up_ref(X509 *); int X509_print_ex(BIO *, X509 *, unsigned long, unsigned long); int X509_set_version(X509 *, long); EVP_PKEY *X509_get_pubkey(X509 *); int X509_set_pubkey(X509 *, EVP_PKEY *); unsigned char *X509_alias_get0(X509 *, int *); int X509_alias_set1(X509 *, const unsigned char *, int); int X509_sign(X509 *, EVP_PKEY *, const EVP_MD *); int X509_digest(const X509 *, const EVP_MD *, unsigned char *, unsigned int *); ASN1_TIME *X509_gmtime_adj(ASN1_TIME *, long); unsigned long X509_subject_name_hash(X509 *); int X509_set_subject_name(X509 *, X509_NAME *); int X509_set_issuer_name(X509 *, X509_NAME *); int X509_add_ext(X509 *, X509_EXTENSION *, int); X509_EXTENSION *X509_EXTENSION_dup(X509_EXTENSION *); ASN1_OBJECT *X509_EXTENSION_get_object(X509_EXTENSION *); void X509_EXTENSION_free(X509_EXTENSION *); int X509_REQ_set_version(X509_REQ *, long); X509_REQ *X509_REQ_new(void); void X509_REQ_free(X509_REQ *); int X509_REQ_set_pubkey(X509_REQ *, EVP_PKEY *); int X509_REQ_sign(X509_REQ *, EVP_PKEY *, const EVP_MD *); int X509_REQ_verify(X509_REQ *, EVP_PKEY *); EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *); int X509_REQ_print_ex(BIO *, X509_REQ *, unsigned long, unsigned long); int X509_REQ_add_extensions(X509_REQ *, X509_EXTENSIONS *); X509_EXTENSIONS *X509_REQ_get_extensions(X509_REQ *); int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int); ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *); X509_REVOKED *X509_REVOKED_new(void); void X509_REVOKED_free(X509_REVOKED *); int X509_REVOKED_set_serialNumber(X509_REVOKED *, ASN1_INTEGER *); int X509_REVOKED_add1_ext_i2d(X509_REVOKED *, int, void *, int, unsigned long); X509_EXTENSION *X509_REVOKED_delete_ext(X509_REVOKED *, int); int X509_REVOKED_set_revocationDate(X509_REVOKED *, ASN1_TIME *); X509_CRL *X509_CRL_new(void); X509_CRL *d2i_X509_CRL_bio(BIO *, X509_CRL **); int X509_CRL_add0_revoked(X509_CRL *, X509_REVOKED *); int X509_CRL_print(BIO *, X509_CRL *); int X509_CRL_set_issuer_name(X509_CRL *, X509_NAME *); int X509_CRL_set_version(X509_CRL *, long); int X509_CRL_sign(X509_CRL *, EVP_PKEY *, const EVP_MD *); int X509_CRL_sort(X509_CRL *); int i2d_X509_CRL_bio(BIO *, X509_CRL *); void X509_CRL_free(X509_CRL *); /* ASN1 serialization */ int i2d_X509_bio(BIO *, X509 *); X509 *d2i_X509_bio(BIO *, X509 **); int i2d_X509_REQ_bio(BIO *, X509_REQ *); X509_REQ *d2i_X509_REQ_bio(BIO *, X509_REQ **); int i2d_PrivateKey_bio(BIO *, EVP_PKEY *); EVP_PKEY *d2i_PrivateKey_bio(BIO *, EVP_PKEY **); int i2d_PUBKEY_bio(BIO *, EVP_PKEY *); EVP_PKEY *d2i_PUBKEY_bio(BIO *, EVP_PKEY **); ASN1_INTEGER *X509_get_serialNumber(X509 *); int X509_set_serialNumber(X509 *, ASN1_INTEGER *); const char *X509_verify_cert_error_string(long); const char *X509_get_default_cert_dir(void); const char *X509_get_default_cert_file(void); const char *X509_get_default_cert_dir_env(void); const char *X509_get_default_cert_file_env(void); int X509_get_ext_count(const X509 *); X509_EXTENSION *X509_get_ext(const X509 *, int); X509_NAME *X509_get_subject_name(const X509 *); X509_NAME *X509_get_issuer_name(const X509 *); int X509_EXTENSION_get_critical(const X509_EXTENSION *); int X509_REVOKED_get_ext_count(const X509_REVOKED *); X509_EXTENSION *X509_REVOKED_get_ext(const X509_REVOKED *, int); X509_REVOKED *X509_REVOKED_dup(X509_REVOKED *); const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *); long X509_get_version(X509 *); ASN1_TIME *X509_getm_notBefore(const X509 *); ASN1_TIME *X509_getm_notAfter(const X509 *); long X509_REQ_get_version(X509_REQ *); X509_NAME *X509_REQ_get_subject_name(X509_REQ *); Cryptography_STACK_OF_X509 *sk_X509_new_null(void); void sk_X509_free(Cryptography_STACK_OF_X509 *); int sk_X509_num(Cryptography_STACK_OF_X509 *); int sk_X509_push(Cryptography_STACK_OF_X509 *, X509 *); X509 *sk_X509_value(Cryptography_STACK_OF_X509 *, int); X509_EXTENSIONS *sk_X509_EXTENSION_new_null(void); int sk_X509_EXTENSION_num(X509_EXTENSIONS *); X509_EXTENSION *sk_X509_EXTENSION_value(X509_EXTENSIONS *, int); int sk_X509_EXTENSION_push(X509_EXTENSIONS *, X509_EXTENSION *); void sk_X509_EXTENSION_free(X509_EXTENSIONS *); void sk_X509_EXTENSION_pop_free(X509_EXTENSIONS *, sk_X509_EXTENSION_freefunc); int sk_X509_REVOKED_num(Cryptography_STACK_OF_X509_REVOKED *); X509_REVOKED *sk_X509_REVOKED_value(Cryptography_STACK_OF_X509_REVOKED *, int); X509_NAME *X509_CRL_get_issuer(X509_CRL *); Cryptography_STACK_OF_X509_REVOKED *X509_CRL_get_REVOKED(X509_CRL *); int X509_CRL_set1_lastUpdate(X509_CRL *, const ASN1_TIME *); int X509_CRL_set1_nextUpdate(X509_CRL *, const ASN1_TIME *); const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *); const ASN1_TIME *X509_REVOKED_get0_revocationDate(const X509_REVOKED *); void X509_ALGOR_get0(const ASN1_OBJECT **, int *, const void **, const X509_ALGOR *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/x509_vfy.py010064400017510000177000000163501464676315000206700ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include /* * This is part of a work-around for the difficulty cffi has in dealing with * `STACK_OF(foo)` as the name of a type. We invent a new, simpler name that * will be an alias for this type and use the alias throughout. This works * together with another opaque typedef for the same name in the TYPES section. * Note that the result is an opaque type. */ typedef STACK_OF(X509_OBJECT) Cryptography_STACK_OF_X509_OBJECT; """ TYPES = """ typedef ... Cryptography_STACK_OF_X509_OBJECT; typedef ... X509_OBJECT; typedef ... X509_STORE; typedef ... X509_VERIFY_PARAM; typedef ... X509_STORE_CTX; typedef int (*X509_STORE_CTX_get_issuer_fn)(X509 **, X509_STORE_CTX *, X509 *); static const int X509_V_OK; static const int X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; static const int X509_V_ERR_UNABLE_TO_GET_CRL; static const int X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE; static const int X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE; static const int X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY; static const int X509_V_ERR_CERT_SIGNATURE_FAILURE; static const int X509_V_ERR_CRL_SIGNATURE_FAILURE; static const int X509_V_ERR_CERT_NOT_YET_VALID; static const int X509_V_ERR_CERT_HAS_EXPIRED; static const int X509_V_ERR_CRL_NOT_YET_VALID; static const int X509_V_ERR_CRL_HAS_EXPIRED; static const int X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD; static const int X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD; static const int X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD; static const int X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD; static const int X509_V_ERR_OUT_OF_MEM; static const int X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT; static const int X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN; static const int X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY; static const int X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE; static const int X509_V_ERR_CERT_CHAIN_TOO_LONG; static const int X509_V_ERR_CERT_REVOKED; static const int X509_V_ERR_INVALID_CA; static const int X509_V_ERR_PATH_LENGTH_EXCEEDED; static const int X509_V_ERR_INVALID_PURPOSE; static const int X509_V_ERR_CERT_UNTRUSTED; static const int X509_V_ERR_CERT_REJECTED; static const int X509_V_ERR_SUBJECT_ISSUER_MISMATCH; static const int X509_V_ERR_AKID_SKID_MISMATCH; static const int X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH; static const int X509_V_ERR_KEYUSAGE_NO_CERTSIGN; static const int X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER; static const int X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION; static const int X509_V_ERR_KEYUSAGE_NO_CRL_SIGN; static const int X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION; static const int X509_V_ERR_INVALID_NON_CA; static const int X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED; static const int X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE; static const int X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED; static const int X509_V_ERR_INVALID_EXTENSION; static const int X509_V_ERR_INVALID_POLICY_EXTENSION; static const int X509_V_ERR_NO_EXPLICIT_POLICY; static const int X509_V_ERR_DIFFERENT_CRL_SCOPE; static const int X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE; static const int X509_V_ERR_UNNESTED_RESOURCE; static const int X509_V_ERR_PERMITTED_VIOLATION; static const int X509_V_ERR_EXCLUDED_VIOLATION; static const int X509_V_ERR_SUBTREE_MINMAX; static const int X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE; static const int X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX; static const int X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; static const int X509_V_ERR_CRL_PATH_VALIDATION_ERROR; static const int X509_V_ERR_HOSTNAME_MISMATCH; static const int X509_V_ERR_EMAIL_MISMATCH; static const int X509_V_ERR_IP_ADDRESS_MISMATCH; static const int X509_V_ERR_APPLICATION_VERIFICATION; /* While these are defined in the source as ints, they're tagged here as longs, just in case they ever grow to large, such as what we saw with OP_ALL. */ /* Verification parameters */ static const long X509_V_FLAG_CRL_CHECK; static const long X509_V_FLAG_CRL_CHECK_ALL; static const long X509_V_FLAG_IGNORE_CRITICAL; static const long X509_V_FLAG_X509_STRICT; static const long X509_V_FLAG_ALLOW_PROXY_CERTS; static const long X509_V_FLAG_POLICY_CHECK; static const long X509_V_FLAG_EXPLICIT_POLICY; static const long X509_V_FLAG_INHIBIT_MAP; static const long X509_V_FLAG_CHECK_SS_SIGNATURE; static const long X509_V_FLAG_PARTIAL_CHAIN; static const long X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; static const long X509_CHECK_FLAG_NO_WILDCARDS; static const long X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; static const long X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS; static const long X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS; static const long X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; /* Included due to external consumer, see https://github.com/pyca/pyopenssl/issues/1031 */ static const long X509_PURPOSE_SSL_CLIENT; static const long X509_PURPOSE_SSL_SERVER; static const long X509_PURPOSE_NS_SSL_SERVER; static const long X509_PURPOSE_SMIME_SIGN; static const long X509_PURPOSE_SMIME_ENCRYPT; static const long X509_PURPOSE_CRL_SIGN; static const long X509_PURPOSE_ANY; static const long X509_PURPOSE_OCSP_HELPER; static const long X509_PURPOSE_TIMESTAMP_SIGN; """ FUNCTIONS = """ int X509_verify_cert(X509_STORE_CTX *); /* X509_STORE */ X509_STORE *X509_STORE_new(void); int X509_STORE_add_cert(X509_STORE *, X509 *); int X509_STORE_add_crl(X509_STORE *, X509_CRL *); int X509_STORE_load_locations(X509_STORE *, const char *, const char *); int X509_STORE_set1_param(X509_STORE *, X509_VERIFY_PARAM *); int X509_STORE_set_default_paths(X509_STORE *); int X509_STORE_set_flags(X509_STORE *, unsigned long); /* Included due to external consumer, see https://github.com/pyca/pyopenssl/issues/1031 */ int X509_STORE_set_purpose(X509_STORE *, int); int X509_STORE_up_ref(X509_STORE *); void X509_STORE_free(X509_STORE *); /* X509_STORE_CTX */ X509_STORE_CTX *X509_STORE_CTX_new(void); void X509_STORE_CTX_cleanup(X509_STORE_CTX *); void X509_STORE_CTX_free(X509_STORE_CTX *); int X509_STORE_CTX_init(X509_STORE_CTX *, X509_STORE *, X509 *, Cryptography_STACK_OF_X509 *); Cryptography_STACK_OF_X509 *X509_STORE_CTX_get1_chain(X509_STORE_CTX *); int X509_STORE_CTX_get_error(X509_STORE_CTX *); void X509_STORE_CTX_set_error(X509_STORE_CTX *, int); int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *); X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *); void *X509_STORE_CTX_get_ex_data(X509_STORE_CTX *, int); /* X509_VERIFY_PARAM */ X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void); int X509_VERIFY_PARAM_set_flags(X509_VERIFY_PARAM *, unsigned long); void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *, time_t); void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *); int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *, const char *, size_t); void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *, unsigned int); int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *, const unsigned char *, size_t); int sk_X509_OBJECT_num(Cryptography_STACK_OF_X509_OBJECT *); Cryptography_STACK_OF_X509_OBJECT *X509_STORE_get0_objects(X509_STORE *); X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/x509name.py010064400017510000177000000032621464676315000206430ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include /* * See the comment above Cryptography_STACK_OF_X509 in x509.py */ typedef STACK_OF(X509_NAME) Cryptography_STACK_OF_X509_NAME; """ TYPES = """ typedef ... X509_NAME; typedef ... X509_NAME_ENTRY; typedef ... Cryptography_STACK_OF_X509_NAME; """ FUNCTIONS = """ X509_NAME *X509_NAME_new(void); void X509_NAME_free(X509_NAME *); unsigned long X509_NAME_hash(X509_NAME *); int i2d_X509_NAME(X509_NAME *, unsigned char **); X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *, int); void X509_NAME_ENTRY_free(X509_NAME_ENTRY *); int X509_NAME_get_index_by_NID(X509_NAME *, int, int); int X509_NAME_cmp(const X509_NAME *, const X509_NAME *); X509_NAME *X509_NAME_dup(X509_NAME *); int X509_NAME_entry_count(const X509_NAME *); X509_NAME_ENTRY *X509_NAME_get_entry(const X509_NAME *, int); char *X509_NAME_oneline(const X509_NAME *, char *, int); ASN1_OBJECT *X509_NAME_ENTRY_get_object(const X509_NAME_ENTRY *); ASN1_STRING *X509_NAME_ENTRY_get_data(const X509_NAME_ENTRY *); int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, const unsigned char *, int, int, int); Cryptography_STACK_OF_X509_NAME *sk_X509_NAME_new_null(void); int sk_X509_NAME_num(Cryptography_STACK_OF_X509_NAME *); int sk_X509_NAME_push(Cryptography_STACK_OF_X509_NAME *, X509_NAME *); X509_NAME *sk_X509_NAME_value(Cryptography_STACK_OF_X509_NAME *, int); void sk_X509_NAME_free(Cryptography_STACK_OF_X509_NAME *); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/openssl/x509v3.py010064400017510000177000000023721464676315000202540ustar 00000000000000# 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. from __future__ import annotations INCLUDES = """ #include """ TYPES = """ typedef ... CONF; typedef struct { X509 *issuer_cert; X509 *subject_cert; ...; } X509V3_CTX; static const int GEN_EMAIL; static const int GEN_DNS; static const int GEN_URI; typedef ... GENERAL_NAMES; /* Only include the one union element used by pyOpenSSL. */ typedef struct { int type; union { ASN1_IA5STRING *ia5; /* rfc822Name, dNSName, */ /* uniformResourceIdentifier */ } d; ...; } GENERAL_NAME; """ FUNCTIONS = """ void X509V3_set_ctx(X509V3_CTX *, X509 *, X509 *, X509_REQ *, X509_CRL *, int); int GENERAL_NAME_print(BIO *, GENERAL_NAME *); void GENERAL_NAMES_free(GENERAL_NAMES *); void *X509V3_EXT_d2i(X509_EXTENSION *); X509_EXTENSION *X509V3_EXT_nconf(CONF *, X509V3_CTX *, const char *, const char *); void X509V3_set_ctx_nodb(X509V3_CTX *); int sk_GENERAL_NAME_num(GENERAL_NAMES *); GENERAL_NAME *sk_GENERAL_NAME_value(GENERAL_NAMES *, int); """ CUSTOMIZATIONS = """ """ cryptography-43.0.0/src/_cffi_src/utils.py010064400017510000177000000045451464676315000167570ustar 00000000000000# 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. from __future__ import annotations import os import platform import sys from cffi import FFI # Load the cryptography __about__ to get the current package version base_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) about: dict = {} with open(os.path.join(base_src, "cryptography", "__about__.py")) as f: exec(f.read(), about) def build_ffi_for_binding( module_name: str, module_prefix: str, modules: list[str], ): """ Modules listed in ``modules`` should have the following attributes: * ``INCLUDES``: A string containing C includes. * ``TYPES``: A string containing C declarations for types. * ``FUNCTIONS``: A string containing C declarations for functions & macros. * ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this can be used to do things like test for a define and provide an alternate implementation based on that. """ types = [] includes = [] functions = [] customizations = [] for name in modules: __import__(module_prefix + name) module = sys.modules[module_prefix + name] types.append(module.TYPES) functions.append(module.FUNCTIONS) includes.append(module.INCLUDES) customizations.append(module.CUSTOMIZATIONS) verify_source = "\n".join(includes + customizations) return build_ffi( module_name, cdef_source="\n".join(types + functions), verify_source=verify_source, ) def build_ffi( module_name: str, cdef_source: str, verify_source: str, ): ffi = FFI() # Always add the CRYPTOGRAPHY_PACKAGE_VERSION to the shared object cdef_source += "\nstatic const char *const CRYPTOGRAPHY_PACKAGE_VERSION;" verify_source += '\n#define CRYPTOGRAPHY_PACKAGE_VERSION "{}"'.format( about["__version__"] ) if platform.python_implementation() == "PyPy": verify_source += r""" int Cryptography_make_openssl_module(void) { int result; Py_BEGIN_ALLOW_THREADS result = cffi_start_python(); Py_END_ALLOW_THREADS return result; } """ ffi.cdef(cdef_source) ffi.set_source( module_name, verify_source, ) return ffi cryptography-43.0.0/src/cryptography/__about__.py010064400017510000177000000006751464676315000203430ustar 00000000000000# 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. from __future__ import annotations __all__ = [ "__author__", "__copyright__", "__version__", ] __version__ = "43.0.0" __author__ = "The Python Cryptographic Authority and individual contributors" __copyright__ = f"Copyright 2013-2024 {__author__}" cryptography-43.0.0/src/cryptography/__init__.py010064400017510000177000000005541464676315000201700ustar 00000000000000# 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. from __future__ import annotations from cryptography.__about__ import __author__, __copyright__, __version__ __all__ = [ "__author__", "__copyright__", "__version__", ] cryptography-43.0.0/src/cryptography/exceptions.py010064400017510000177000000020771464676315000206140ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions if typing.TYPE_CHECKING: from cryptography.hazmat.bindings._rust import openssl as rust_openssl _Reasons = rust_exceptions._Reasons class UnsupportedAlgorithm(Exception): def __init__(self, message: str, reason: _Reasons | None = None) -> None: super().__init__(message) self._reason = reason class AlreadyFinalized(Exception): pass class AlreadyUpdated(Exception): pass class NotYetFinalized(Exception): pass class InvalidTag(Exception): pass class InvalidSignature(Exception): pass class InternalError(Exception): def __init__( self, msg: str, err_code: list[rust_openssl.OpenSSLError] ) -> None: super().__init__(msg) self.err_code = err_code class InvalidKey(Exception): pass cryptography-43.0.0/src/cryptography/fernet.py010064400017510000177000000150501464676315000177110ustar 00000000000000# 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. from __future__ import annotations import base64 import binascii import os import time import typing from cryptography import utils from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.hmac import HMAC class InvalidToken(Exception): pass _MAX_CLOCK_SKEW = 60 class Fernet: def __init__( self, key: bytes | str, backend: typing.Any = None, ) -> None: try: key = base64.urlsafe_b64decode(key) except binascii.Error as exc: raise ValueError( "Fernet key must be 32 url-safe base64-encoded bytes." ) from exc if len(key) != 32: raise ValueError( "Fernet key must be 32 url-safe base64-encoded bytes." ) self._signing_key = key[:16] self._encryption_key = key[16:] @classmethod def generate_key(cls) -> bytes: return base64.urlsafe_b64encode(os.urandom(32)) def encrypt(self, data: bytes) -> bytes: return self.encrypt_at_time(data, int(time.time())) def encrypt_at_time(self, data: bytes, current_time: int) -> bytes: iv = os.urandom(16) return self._encrypt_from_parts(data, current_time, iv) def _encrypt_from_parts( self, data: bytes, current_time: int, iv: bytes ) -> bytes: utils._check_bytes("data", data) padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() encryptor = Cipher( algorithms.AES(self._encryption_key), modes.CBC(iv), ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() basic_parts = ( b"\x80" + current_time.to_bytes(length=8, byteorder="big") + iv + ciphertext ) h = HMAC(self._signing_key, hashes.SHA256()) h.update(basic_parts) hmac = h.finalize() return base64.urlsafe_b64encode(basic_parts + hmac) def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes: timestamp, data = Fernet._get_unverified_token_data(token) if ttl is None: time_info = None else: time_info = (ttl, int(time.time())) return self._decrypt_data(data, timestamp, time_info) def decrypt_at_time( self, token: bytes | str, ttl: int, current_time: int ) -> bytes: if ttl is None: raise ValueError( "decrypt_at_time() can only be used with a non-None ttl" ) timestamp, data = Fernet._get_unverified_token_data(token) return self._decrypt_data(data, timestamp, (ttl, current_time)) def extract_timestamp(self, token: bytes | str) -> int: timestamp, data = Fernet._get_unverified_token_data(token) # Verify the token was not tampered with. self._verify_signature(data) return timestamp @staticmethod def _get_unverified_token_data(token: bytes | str) -> tuple[int, bytes]: if not isinstance(token, (str, bytes)): raise TypeError("token must be bytes or str") try: data = base64.urlsafe_b64decode(token) except (TypeError, binascii.Error): raise InvalidToken if not data or data[0] != 0x80: raise InvalidToken if len(data) < 9: raise InvalidToken timestamp = int.from_bytes(data[1:9], byteorder="big") return timestamp, data def _verify_signature(self, data: bytes) -> None: h = HMAC(self._signing_key, hashes.SHA256()) h.update(data[:-32]) try: h.verify(data[-32:]) except InvalidSignature: raise InvalidToken def _decrypt_data( self, data: bytes, timestamp: int, time_info: tuple[int, int] | None, ) -> bytes: if time_info is not None: ttl, current_time = time_info if timestamp + ttl < current_time: raise InvalidToken if current_time + _MAX_CLOCK_SKEW < timestamp: raise InvalidToken self._verify_signature(data) iv = data[9:25] ciphertext = data[25:-32] decryptor = Cipher( algorithms.AES(self._encryption_key), modes.CBC(iv) ).decryptor() plaintext_padded = decryptor.update(ciphertext) try: plaintext_padded += decryptor.finalize() except ValueError: raise InvalidToken unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded = unpadder.update(plaintext_padded) try: unpadded += unpadder.finalize() except ValueError: raise InvalidToken return unpadded class MultiFernet: def __init__(self, fernets: typing.Iterable[Fernet]): fernets = list(fernets) if not fernets: raise ValueError( "MultiFernet requires at least one Fernet instance" ) self._fernets = fernets def encrypt(self, msg: bytes) -> bytes: return self.encrypt_at_time(msg, int(time.time())) def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes: return self._fernets[0].encrypt_at_time(msg, current_time) def rotate(self, msg: bytes | str) -> bytes: timestamp, data = Fernet._get_unverified_token_data(msg) for f in self._fernets: try: p = f._decrypt_data(data, timestamp, None) break except InvalidToken: pass else: raise InvalidToken iv = os.urandom(16) return self._fernets[0]._encrypt_from_parts(p, timestamp, iv) def decrypt(self, msg: bytes | str, ttl: int | None = None) -> bytes: for f in self._fernets: try: return f.decrypt(msg, ttl) except InvalidToken: pass raise InvalidToken def decrypt_at_time( self, msg: bytes | str, ttl: int, current_time: int ) -> bytes: for f in self._fernets: try: return f.decrypt_at_time(msg, ttl, current_time) except InvalidToken: pass raise InvalidToken cryptography-43.0.0/src/cryptography/hazmat/__init__.py010064400017510000177000000007071464676315000214540ustar 00000000000000# 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. from __future__ import annotations """ Hazardous Materials This is a "Hazardous Materials" module. You should ONLY use it if you're 100% absolutely sure that you know what you're doing because this module is full of land mines, dragons, and dinosaurs with laser guns. """ cryptography-43.0.0/src/cryptography/hazmat/_oid.py010064400017510000177000000355411464676315000206330ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.bindings._rust import ( ObjectIdentifier as ObjectIdentifier, ) from cryptography.hazmat.primitives import hashes class ExtensionOID: SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9") SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14") KEY_USAGE = ObjectIdentifier("2.5.29.15") SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30") CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31") CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32") POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33") AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35") POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36") EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37") FRESHEST_CRL = ObjectIdentifier("2.5.29.46") INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54") ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28") AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1") SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11") OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24") CRL_NUMBER = ObjectIdentifier("2.5.29.20") DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27") PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier( "1.3.6.1.4.1.11129.2.4.2" ) PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3") SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7") class OCSPExtensionOID: NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2") ACCEPTABLE_RESPONSES = ObjectIdentifier("1.3.6.1.5.5.7.48.1.4") class CRLEntryExtensionOID: CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") CRL_REASON = ObjectIdentifier("2.5.29.21") INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") class NameOID: COMMON_NAME = ObjectIdentifier("2.5.4.3") COUNTRY_NAME = ObjectIdentifier("2.5.4.6") LOCALITY_NAME = ObjectIdentifier("2.5.4.7") STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") STREET_ADDRESS = ObjectIdentifier("2.5.4.9") ORGANIZATION_IDENTIFIER = ObjectIdentifier("2.5.4.97") ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") SURNAME = ObjectIdentifier("2.5.4.4") GIVEN_NAME = ObjectIdentifier("2.5.4.42") TITLE = ObjectIdentifier("2.5.4.12") INITIALS = ObjectIdentifier("2.5.4.43") GENERATION_QUALIFIER = ObjectIdentifier("2.5.4.44") X500_UNIQUE_IDENTIFIER = ObjectIdentifier("2.5.4.45") DN_QUALIFIER = ObjectIdentifier("2.5.4.46") PSEUDONYM = ObjectIdentifier("2.5.4.65") USER_ID = ObjectIdentifier("0.9.2342.19200300.100.1.1") DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25") EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1") JURISDICTION_COUNTRY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.3") JURISDICTION_LOCALITY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.1") JURISDICTION_STATE_OR_PROVINCE_NAME = ObjectIdentifier( "1.3.6.1.4.1.311.60.2.1.2" ) BUSINESS_CATEGORY = ObjectIdentifier("2.5.4.15") POSTAL_ADDRESS = ObjectIdentifier("2.5.4.16") POSTAL_CODE = ObjectIdentifier("2.5.4.17") INN = ObjectIdentifier("1.2.643.3.131.1.1") OGRN = ObjectIdentifier("1.2.643.100.1") SNILS = ObjectIdentifier("1.2.643.100.3") UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") class SignatureAlgorithmOID: RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4") RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5") # This is an alternate OID for RSA with SHA1 that is occasionally seen _RSA_WITH_SHA1 = ObjectIdentifier("1.3.14.3.2.29") RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14") RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11") RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12") RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13") RSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.13") RSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.14") RSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.15") RSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.16") RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") ECDSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10045.4.1") ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1") ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2") ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3") ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4") ECDSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.9") ECDSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.10") ECDSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.11") ECDSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.12") DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3") DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1") DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2") DSA_WITH_SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.3.3") DSA_WITH_SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.3.4") ED25519 = ObjectIdentifier("1.3.101.112") ED448 = ObjectIdentifier("1.3.101.113") GOSTR3411_94_WITH_3410_2001 = ObjectIdentifier("1.2.643.2.2.3") GOSTR3410_2012_WITH_3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") _SIG_OIDS_TO_HASH: dict[ObjectIdentifier, hashes.HashAlgorithm | None] = { SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(), SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(), SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(), SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(), SignatureAlgorithmOID.RSA_WITH_SHA3_224: hashes.SHA3_224(), SignatureAlgorithmOID.RSA_WITH_SHA3_256: hashes.SHA3_256(), SignatureAlgorithmOID.RSA_WITH_SHA3_384: hashes.SHA3_384(), SignatureAlgorithmOID.RSA_WITH_SHA3_512: hashes.SHA3_512(), SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(), SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(), SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(), SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(), SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(), SignatureAlgorithmOID.ECDSA_WITH_SHA3_224: hashes.SHA3_224(), SignatureAlgorithmOID.ECDSA_WITH_SHA3_256: hashes.SHA3_256(), SignatureAlgorithmOID.ECDSA_WITH_SHA3_384: hashes.SHA3_384(), SignatureAlgorithmOID.ECDSA_WITH_SHA3_512: hashes.SHA3_512(), SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(), SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(), SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256(), SignatureAlgorithmOID.ED25519: None, SignatureAlgorithmOID.ED448: None, SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: None, SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: None, SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: None, } class PublicKeyAlgorithmOID: DSA = ObjectIdentifier("1.2.840.10040.4.1") EC_PUBLIC_KEY = ObjectIdentifier("1.2.840.10045.2.1") RSAES_PKCS1_v1_5 = ObjectIdentifier("1.2.840.113549.1.1.1") RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") X25519 = ObjectIdentifier("1.3.101.110") X448 = ObjectIdentifier("1.3.101.111") ED25519 = ObjectIdentifier("1.3.101.112") ED448 = ObjectIdentifier("1.3.101.113") class ExtendedKeyUsageOID: SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") CODE_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.3") EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4") TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8") OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9") ANY_EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37.0") SMARTCARD_LOGON = ObjectIdentifier("1.3.6.1.4.1.311.20.2.2") KERBEROS_PKINIT_KDC = ObjectIdentifier("1.3.6.1.5.2.3.5") IPSEC_IKE = ObjectIdentifier("1.3.6.1.5.5.7.3.17") CERTIFICATE_TRANSPARENCY = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4") class AuthorityInformationAccessOID: CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") class SubjectInformationAccessOID: CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5") class CertificatePoliciesOID: CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1") CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2") ANY_POLICY = ObjectIdentifier("2.5.29.32.0") class AttributeOID: CHALLENGE_PASSWORD = ObjectIdentifier("1.2.840.113549.1.9.7") UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") _OID_NAMES = { NameOID.COMMON_NAME: "commonName", NameOID.COUNTRY_NAME: "countryName", NameOID.LOCALITY_NAME: "localityName", NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName", NameOID.STREET_ADDRESS: "streetAddress", NameOID.ORGANIZATION_NAME: "organizationName", NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName", NameOID.SERIAL_NUMBER: "serialNumber", NameOID.SURNAME: "surname", NameOID.GIVEN_NAME: "givenName", NameOID.TITLE: "title", NameOID.GENERATION_QUALIFIER: "generationQualifier", NameOID.X500_UNIQUE_IDENTIFIER: "x500UniqueIdentifier", NameOID.DN_QUALIFIER: "dnQualifier", NameOID.PSEUDONYM: "pseudonym", NameOID.USER_ID: "userID", NameOID.DOMAIN_COMPONENT: "domainComponent", NameOID.EMAIL_ADDRESS: "emailAddress", NameOID.JURISDICTION_COUNTRY_NAME: "jurisdictionCountryName", NameOID.JURISDICTION_LOCALITY_NAME: "jurisdictionLocalityName", NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME: ( "jurisdictionStateOrProvinceName" ), NameOID.BUSINESS_CATEGORY: "businessCategory", NameOID.POSTAL_ADDRESS: "postalAddress", NameOID.POSTAL_CODE: "postalCode", NameOID.INN: "INN", NameOID.OGRN: "OGRN", NameOID.SNILS: "SNILS", NameOID.UNSTRUCTURED_NAME: "unstructuredName", SignatureAlgorithmOID.RSA_WITH_MD5: "md5WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA1: "sha1WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA224: "sha224WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", SignatureAlgorithmOID.RSASSA_PSS: "RSASSA-PSS", SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", SignatureAlgorithmOID.ECDSA_WITH_SHA384: "ecdsa-with-SHA384", SignatureAlgorithmOID.ECDSA_WITH_SHA512: "ecdsa-with-SHA512", SignatureAlgorithmOID.DSA_WITH_SHA1: "dsa-with-sha1", SignatureAlgorithmOID.DSA_WITH_SHA224: "dsa-with-sha224", SignatureAlgorithmOID.DSA_WITH_SHA256: "dsa-with-sha256", SignatureAlgorithmOID.ED25519: "ed25519", SignatureAlgorithmOID.ED448: "ed448", SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: ( "GOST R 34.11-94 with GOST R 34.10-2001" ), SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: ( "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)" ), SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" ), PublicKeyAlgorithmOID.DSA: "dsaEncryption", PublicKeyAlgorithmOID.EC_PUBLIC_KEY: "id-ecPublicKey", PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5: "rsaEncryption", PublicKeyAlgorithmOID.RSASSA_PSS: "rsassaPss", PublicKeyAlgorithmOID.X25519: "X25519", PublicKeyAlgorithmOID.X448: "X448", ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection", ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping", ExtendedKeyUsageOID.OCSP_SIGNING: "OCSPSigning", ExtendedKeyUsageOID.SMARTCARD_LOGON: "msSmartcardLogin", ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC: "pkInitKDC", ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", ExtensionOID.KEY_USAGE: "keyUsage", ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( "signedCertificateTimestampList" ), ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: ( "signedCertificateTimestampList" ), ExtensionOID.PRECERT_POISON: "ctPoison", ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate", CRLEntryExtensionOID.CRL_REASON: "cRLReason", CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", ExtensionOID.POLICY_MAPPINGS: "policyMappings", ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "authorityKeyIdentifier", ExtensionOID.POLICY_CONSTRAINTS: "policyConstraints", ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", ExtensionOID.FRESHEST_CRL: "freshestCRL", ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", ExtensionOID.ISSUING_DISTRIBUTION_POINT: "issuingDistributionPoint", ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", ExtensionOID.CRL_NUMBER: "cRLNumber", ExtensionOID.DELTA_CRL_INDICATOR: "deltaCRLIndicator", ExtensionOID.TLS_FEATURE: "TLSFeature", AuthorityInformationAccessOID.OCSP: "OCSP", AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers", SubjectInformationAccessOID.CA_REPOSITORY: "caRepository", CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps", CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice", OCSPExtensionOID.NONCE: "OCSPNonce", AttributeOID.CHALLENGE_PASSWORD: "challengePassword", } cryptography-43.0.0/src/cryptography/hazmat/backends/__init__.py010064400017510000177000000005511464676315000232230ustar 00000000000000# 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. from __future__ import annotations from typing import Any def default_backend() -> Any: from cryptography.hazmat.backends.openssl.backend import backend return backend cryptography-43.0.0/src/cryptography/hazmat/backends/openssl/__init__.py010064400017510000177000000004611464676315000247060ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.backends.openssl.backend import backend __all__ = ["backend"] cryptography-43.0.0/src/cryptography/hazmat/backends/openssl/backend.py010064400017510000177000000226601464676315000245430ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl import binding from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.asymmetric.padding import ( MGF1, OAEP, PSS, PKCS1v15, ) from cryptography.hazmat.primitives.ciphers import ( CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ) from cryptography.hazmat.primitives.ciphers.modes import ( CBC, Mode, ) class Backend: """ OpenSSL API binding interfaces. """ name = "openssl" # TripleDES encryption is disallowed/deprecated throughout 2023 in # FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA). _fips_ciphers = (AES,) # Sometimes SHA1 is still permissible. That logic is contained # within the various *_supported methods. _fips_hashes = ( hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, hashes.SHA512_224, hashes.SHA512_256, hashes.SHA3_224, hashes.SHA3_256, hashes.SHA3_384, hashes.SHA3_512, hashes.SHAKE128, hashes.SHAKE256, ) _fips_ecdh_curves = ( ec.SECP224R1, ec.SECP256R1, ec.SECP384R1, ec.SECP521R1, ) _fips_rsa_min_key_size = 2048 _fips_rsa_min_public_exponent = 65537 _fips_dsa_min_modulus = 1 << 2048 _fips_dh_min_key_size = 2048 _fips_dh_min_modulus = 1 << _fips_dh_min_key_size def __init__(self) -> None: self._binding = binding.Binding() self._ffi = self._binding.ffi self._lib = self._binding.lib self._fips_enabled = rust_openssl.is_fips_enabled() def __repr__(self) -> str: return ( f"" ) def openssl_assert(self, ok: bool) -> None: return binding._openssl_assert(ok) def _enable_fips(self) -> None: # This function enables FIPS mode for OpenSSL 3.0.0 on installs that # have the FIPS provider installed properly. rust_openssl.enable_fips(rust_openssl._providers) assert rust_openssl.is_fips_enabled() self._fips_enabled = rust_openssl.is_fips_enabled() def openssl_version_text(self) -> str: """ Friendly string name of the loaded OpenSSL library. This is not necessarily the same version as it was compiled against. Example: OpenSSL 3.2.1 30 Jan 2024 """ return rust_openssl.openssl_version_text() def openssl_version_number(self) -> int: return rust_openssl.openssl_version() def _evp_md_from_algorithm(self, algorithm: hashes.HashAlgorithm): if algorithm.name in ("blake2b", "blake2s"): alg = f"{algorithm.name}{algorithm.digest_size * 8}".encode( "ascii" ) else: alg = algorithm.name.encode("ascii") evp_md = self._lib.EVP_get_digestbyname(alg) return evp_md def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): return False evp_md = self._evp_md_from_algorithm(algorithm) return evp_md != self._ffi.NULL def signature_hash_supported( self, algorithm: hashes.HashAlgorithm ) -> bool: # Dedicated check for hashing algorithm use in message digest for # signatures, e.g. RSA PKCS#1 v1.5 SHA1 (sha1WithRSAEncryption). if self._fips_enabled and isinstance(algorithm, hashes.SHA1): return False return self.hash_supported(algorithm) def scrypt_supported(self) -> bool: if self._fips_enabled: return False else: return hasattr(rust_openssl.kdf, "derive_scrypt") def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: # FIPS mode still allows SHA1 for HMAC if self._fips_enabled and isinstance(algorithm, hashes.SHA1): return True return self.hash_supported(algorithm) def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: if self._fips_enabled: # FIPS mode requires AES. TripleDES is disallowed/deprecated in # FIPS 140-3. if not isinstance(cipher, self._fips_ciphers): return False return rust_openssl.ciphers.cipher_supported(cipher, mode) def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: return self.hmac_supported(algorithm) def _consume_errors(self) -> list[rust_openssl.OpenSSLError]: return rust_openssl.capture_error_stack() def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and isinstance(algorithm, hashes.SHA1): return False return isinstance( algorithm, ( hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, ), ) def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: if isinstance(padding, PKCS1v15): return True elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): # SHA1 is permissible in MGF1 in FIPS even when SHA1 is blocked # as signature algorithm. if self._fips_enabled and isinstance( padding._mgf._algorithm, hashes.SHA1 ): return True else: return self.hash_supported(padding._mgf._algorithm) elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): return self._oaep_hash_supported( padding._mgf._algorithm ) and self._oaep_hash_supported(padding._algorithm) else: return False def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool: if self._fips_enabled and isinstance(padding, PKCS1v15): return False else: return self.rsa_padding_supported(padding) def dsa_supported(self) -> bool: return ( not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL and not self._fips_enabled ) def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if not self.dsa_supported(): return False return self.signature_hash_supported(algorithm) def cmac_algorithm_supported(self, algorithm) -> bool: return self.cipher_supported( algorithm, CBC(b"\x00" * algorithm.block_size) ) def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: if self._fips_enabled and not isinstance( curve, self._fips_ecdh_curves ): return False return rust_openssl.ec.curve_supported(curve) def elliptic_curve_signature_algorithm_supported( self, signature_algorithm: ec.EllipticCurveSignatureAlgorithm, curve: ec.EllipticCurve, ) -> bool: # We only support ECDSA right now. if not isinstance(signature_algorithm, ec.ECDSA): return False return self.elliptic_curve_supported(curve) and ( isinstance(signature_algorithm.algorithm, asym_utils.Prehashed) or self.hash_supported(signature_algorithm.algorithm) ) def elliptic_curve_exchange_algorithm_supported( self, algorithm: ec.ECDH, curve: ec.EllipticCurve ) -> bool: return self.elliptic_curve_supported(curve) and isinstance( algorithm, ec.ECDH ) def dh_supported(self) -> bool: return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL def dh_x942_serialization_supported(self) -> bool: return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 def x25519_supported(self) -> bool: if self._fips_enabled: return False return True def x448_supported(self) -> bool: if self._fips_enabled: return False return ( not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL ) def ed25519_supported(self) -> bool: if self._fips_enabled: return False return True def ed448_supported(self) -> bool: if self._fips_enabled: return False return ( not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL ) def ecdsa_deterministic_supported(self) -> bool: return ( rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER and not self._fips_enabled ) def poly1305_supported(self) -> bool: if self._fips_enabled: return False return True def pkcs7_supported(self) -> bool: return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL backend = Backend() cryptography-43.0.0/src/cryptography/hazmat/bindings/__init__.py010064400017510000177000000002641464676315000232470ustar 00000000000000# 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. cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/__init__.pyi010064400017510000177000000013411464676315000245510ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives import padding def check_pkcs7_padding(data: bytes) -> bool: ... def check_ansix923_padding(data: bytes) -> bool: ... class PKCS7PaddingContext(padding.PaddingContext): def __init__(self, block_size: int) -> None: ... def update(self, data: bytes) -> bytes: ... def finalize(self) -> bytes: ... class ObjectIdentifier: def __init__(self, val: str) -> None: ... @property def dotted_string(self) -> str: ... @property def _name(self) -> str: ... T = typing.TypeVar("T") cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/_openssl.pyi010064400017510000177000000003461464676315000246400ustar 00000000000000# 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. import typing lib = typing.Any ffi = typing.Any cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/asn1.pyi010064400017510000177000000005421464676315000236560ustar 00000000000000# 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. def decode_dss_signature(signature: bytes) -> tuple[int, int]: ... def encode_dss_signature(r: int, s: int) -> bytes: ... def parse_spki_for_data(data: bytes) -> bytes: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/exceptions.pyi010064400017510000177000000012001464676315000251650ustar 00000000000000# 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. class _Reasons: BACKEND_MISSING_INTERFACE: _Reasons UNSUPPORTED_HASH: _Reasons UNSUPPORTED_CIPHER: _Reasons UNSUPPORTED_PADDING: _Reasons UNSUPPORTED_MGF: _Reasons UNSUPPORTED_PUBLIC_KEY_ALGORITHM: _Reasons UNSUPPORTED_ELLIPTIC_CURVE: _Reasons UNSUPPORTED_SERIALIZATION: _Reasons UNSUPPORTED_X509: _Reasons UNSUPPORTED_EXCHANGE_ALGORITHM: _Reasons UNSUPPORTED_DIFFIE_HELLMAN: _Reasons UNSUPPORTED_MAC: _Reasons cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/ocsp.pyi010064400017510000177000000015441464676315000237630ustar 00000000000000# 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. from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes from cryptography.x509 import ocsp class OCSPRequest: ... class OCSPResponse: ... class OCSPSingleResponse: ... def load_der_ocsp_request(data: bytes) -> ocsp.OCSPRequest: ... def load_der_ocsp_response(data: bytes) -> ocsp.OCSPResponse: ... def create_ocsp_request( builder: ocsp.OCSPRequestBuilder, ) -> ocsp.OCSPRequest: ... def create_ocsp_response( status: ocsp.OCSPResponseStatus, builder: ocsp.OCSPResponseBuilder | None, private_key: PrivateKeyTypes | None, hash_algorithm: hashes.HashAlgorithm | None, ) -> ocsp.OCSPResponse: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi010064400017510000177000000025301464676315000262350ustar 00000000000000# 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. import typing from cryptography.hazmat.bindings._rust.openssl import ( aead, ciphers, cmac, dh, dsa, ec, ed448, ed25519, hashes, hmac, kdf, keys, poly1305, rsa, x448, x25519, ) __all__ = [ "aead", "ciphers", "cmac", "dh", "dsa", "ec", "ed448", "ed25519", "hashes", "hmac", "kdf", "keys", "openssl_version", "openssl_version_text", "poly1305", "raise_openssl_error", "rsa", "x448", "x25519", ] CRYPTOGRAPHY_IS_LIBRESSL: bool CRYPTOGRAPHY_IS_BORINGSSL: bool CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: bool CRYPTOGRAPHY_OPENSSL_320_OR_GREATER: bool class Providers: ... _legacy_provider_loaded: bool _providers: Providers def openssl_version() -> int: ... def openssl_version_text() -> str: ... def raise_openssl_error() -> typing.NoReturn: ... def capture_error_stack() -> list[OpenSSLError]: ... def is_fips_enabled() -> bool: ... def enable_fips(providers: Providers) -> None: ... class OpenSSLError: @property def lib(self) -> int: ... @property def reason(self) -> int: ... @property def reason_text(self) -> bytes: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/aead.pyi010064400017510000177000000047711464676315000254010ustar 00000000000000# 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. class AESGCM: def __init__(self, key: bytes) -> None: ... @staticmethod def generate_key(key_size: int) -> bytes: ... def encrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... def decrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... class ChaCha20Poly1305: def __init__(self, key: bytes) -> None: ... @staticmethod def generate_key() -> bytes: ... def encrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... def decrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... class AESCCM: def __init__(self, key: bytes, tag_length: int = 16) -> None: ... @staticmethod def generate_key(key_size: int) -> bytes: ... def encrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... def decrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... class AESSIV: def __init__(self, key: bytes) -> None: ... @staticmethod def generate_key(key_size: int) -> bytes: ... def encrypt( self, data: bytes, associated_data: list[bytes] | None, ) -> bytes: ... def decrypt( self, data: bytes, associated_data: list[bytes] | None, ) -> bytes: ... class AESOCB3: def __init__(self, key: bytes) -> None: ... @staticmethod def generate_key(key_size: int) -> bytes: ... def encrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... def decrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... class AESGCMSIV: def __init__(self, key: bytes) -> None: ... @staticmethod def generate_key(key_size: int) -> bytes: ... def encrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... def decrypt( self, nonce: bytes, data: bytes, associated_data: bytes | None, ) -> bytes: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi010064400017510000177000000024251464676315000261360ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import modes @typing.overload def create_encryption_ctx( algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag ) -> ciphers.AEADEncryptionContext: ... @typing.overload def create_encryption_ctx( algorithm: ciphers.CipherAlgorithm, mode: modes.Mode ) -> ciphers.CipherContext: ... @typing.overload def create_decryption_ctx( algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag ) -> ciphers.AEADDecryptionContext: ... @typing.overload def create_decryption_ctx( algorithm: ciphers.CipherAlgorithm, mode: modes.Mode ) -> ciphers.CipherContext: ... def cipher_supported( algorithm: ciphers.CipherAlgorithm, mode: modes.Mode ) -> bool: ... def _advance( ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int ) -> None: ... def _advance_aad( ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int ) -> None: ... class CipherContext: ... class AEADEncryptionContext: ... class AEADDecryptionContext: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi010064400017510000177000000010641464676315000254020ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives import ciphers class CMAC: def __init__( self, algorithm: ciphers.BlockCipherAlgorithm, backend: typing.Any = None, ) -> None: ... def update(self, data: bytes) -> None: ... def finalize(self) -> bytes: ... def verify(self, signature: bytes) -> None: ... def copy(self) -> CMAC: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi010064400017510000177000000030341464676315000250710ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives.asymmetric import dh MIN_MODULUS_SIZE: int class DHPrivateKey: ... class DHPublicKey: ... class DHParameters: ... class DHPrivateNumbers: def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: ... def private_key(self, backend: typing.Any = None) -> dh.DHPrivateKey: ... @property def x(self) -> int: ... @property def public_numbers(self) -> DHPublicNumbers: ... class DHPublicNumbers: def __init__( self, y: int, parameter_numbers: DHParameterNumbers ) -> None: ... def public_key(self, backend: typing.Any = None) -> dh.DHPublicKey: ... @property def y(self) -> int: ... @property def parameter_numbers(self) -> DHParameterNumbers: ... class DHParameterNumbers: def __init__(self, p: int, g: int, q: int | None = None) -> None: ... def parameters(self, backend: typing.Any = None) -> dh.DHParameters: ... @property def p(self) -> int: ... @property def g(self) -> int: ... @property def q(self) -> int | None: ... def generate_parameters( generator: int, key_size: int, backend: typing.Any = None ) -> dh.DHParameters: ... def from_pem_parameters( data: bytes, backend: typing.Any = None ) -> dh.DHParameters: ... def from_der_parameters( data: bytes, backend: typing.Any = None ) -> dh.DHParameters: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi010064400017510000177000000024231464676315000252460ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives.asymmetric import dsa class DSAPrivateKey: ... class DSAPublicKey: ... class DSAParameters: ... class DSAPrivateNumbers: def __init__(self, x: int, public_numbers: DSAPublicNumbers) -> None: ... @property def x(self) -> int: ... @property def public_numbers(self) -> DSAPublicNumbers: ... def private_key(self, backend: typing.Any = None) -> dsa.DSAPrivateKey: ... class DSAPublicNumbers: def __init__( self, y: int, parameter_numbers: DSAParameterNumbers ) -> None: ... @property def y(self) -> int: ... @property def parameter_numbers(self) -> DSAParameterNumbers: ... def public_key(self, backend: typing.Any = None) -> dsa.DSAPublicKey: ... class DSAParameterNumbers: def __init__(self, p: int, q: int, g: int) -> None: ... @property def p(self) -> int: ... @property def q(self) -> int: ... @property def g(self) -> int: ... def parameters(self, backend: typing.Any = None) -> dsa.DSAParameters: ... def generate_parameters(key_size: int) -> dsa.DSAParameters: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/ec.pyi010064400017510000177000000032331464676315000250660ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives.asymmetric import ec class ECPrivateKey: ... class ECPublicKey: ... class EllipticCurvePrivateNumbers: def __init__( self, private_value: int, public_numbers: EllipticCurvePublicNumbers ) -> None: ... def private_key( self, backend: typing.Any = None ) -> ec.EllipticCurvePrivateKey: ... @property def private_value(self) -> int: ... @property def public_numbers(self) -> EllipticCurvePublicNumbers: ... class EllipticCurvePublicNumbers: def __init__(self, x: int, y: int, curve: ec.EllipticCurve) -> None: ... def public_key( self, backend: typing.Any = None ) -> ec.EllipticCurvePublicKey: ... @property def x(self) -> int: ... @property def y(self) -> int: ... @property def curve(self) -> ec.EllipticCurve: ... def __eq__(self, other: object) -> bool: ... def curve_supported(curve: ec.EllipticCurve) -> bool: ... def generate_private_key( curve: ec.EllipticCurve, backend: typing.Any = None ) -> ec.EllipticCurvePrivateKey: ... def from_private_numbers( numbers: ec.EllipticCurvePrivateNumbers, ) -> ec.EllipticCurvePrivateKey: ... def from_public_numbers( numbers: ec.EllipticCurvePublicNumbers, ) -> ec.EllipticCurvePublicKey: ... def from_public_bytes( curve: ec.EllipticCurve, data: bytes ) -> ec.EllipticCurvePublicKey: ... def derive_private_key( private_value: int, curve: ec.EllipticCurve ) -> ec.EllipticCurvePrivateKey: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi010064400017510000177000000007551464676315000255030ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric import ed25519 class Ed25519PrivateKey: ... class Ed25519PublicKey: ... def generate_key() -> ed25519.Ed25519PrivateKey: ... def from_private_bytes(data: bytes) -> ed25519.Ed25519PrivateKey: ... def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi010064400017510000177000000007331464676315000253310ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric import ed448 class Ed448PrivateKey: ... class Ed448PublicKey: ... def generate_key() -> ed448.Ed448PrivateKey: ... def from_private_bytes(data: bytes) -> ed448.Ed448PrivateKey: ... def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi010064400017510000177000000010751464676315000257540ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives import hashes class Hash(hashes.HashContext): def __init__( self, algorithm: hashes.HashAlgorithm, backend: typing.Any = None ) -> None: ... @property def algorithm(self) -> hashes.HashAlgorithm: ... def update(self, data: bytes) -> None: ... def finalize(self) -> bytes: ... def copy(self) -> Hash: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi010064400017510000177000000012261464676315000254070ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives import hashes class HMAC(hashes.HashContext): def __init__( self, key: bytes, algorithm: hashes.HashAlgorithm, backend: typing.Any = None, ) -> None: ... @property def algorithm(self) -> hashes.HashAlgorithm: ... def update(self, data: bytes) -> None: ... def finalize(self) -> bytes: ... def verify(self, signature: bytes) -> None: ... def copy(self) -> HMAC: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi010064400017510000177000000010401464676315000252350ustar 00000000000000# 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. from cryptography.hazmat.primitives.hashes import HashAlgorithm def derive_pbkdf2_hmac( key_material: bytes, algorithm: HashAlgorithm, salt: bytes, iterations: int, length: int, ) -> bytes: ... def derive_scrypt( key_material: bytes, salt: bytes, n: int, r: int, p: int, max_mem: int, length: int, ) -> bytes: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/keys.pyi010064400017510000177000000015501464676315000254520ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives.asymmetric.types import ( PrivateKeyTypes, PublicKeyTypes, ) def load_der_private_key( data: bytes, password: bytes | None, backend: typing.Any = None, *, unsafe_skip_rsa_key_validation: bool = False, ) -> PrivateKeyTypes: ... def load_pem_private_key( data: bytes, password: bytes | None, backend: typing.Any = None, *, unsafe_skip_rsa_key_validation: bool = False, ) -> PrivateKeyTypes: ... def load_der_public_key( data: bytes, backend: typing.Any = None, ) -> PublicKeyTypes: ... def load_pem_public_key( data: bytes, backend: typing.Any = None, ) -> PublicKeyTypes: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi010064400017510000177000000010341464676315000257700ustar 00000000000000# 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. class Poly1305: def __init__(self, key: bytes) -> None: ... @staticmethod def generate_tag(key: bytes, data: bytes) -> bytes: ... @staticmethod def verify_tag(key: bytes, data: bytes, tag: bytes) -> None: ... def update(self, data: bytes) -> None: ... def finalize(self) -> bytes: ... def verify(self, tag: bytes) -> None: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi010064400017510000177000000025241464676315000252660ustar 00000000000000# 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. import typing from cryptography.hazmat.primitives.asymmetric import rsa class RSAPrivateKey: ... class RSAPublicKey: ... class RSAPrivateNumbers: def __init__( self, p: int, q: int, d: int, dmp1: int, dmq1: int, iqmp: int, public_numbers: RSAPublicNumbers, ) -> None: ... @property def p(self) -> int: ... @property def q(self) -> int: ... @property def d(self) -> int: ... @property def dmp1(self) -> int: ... @property def dmq1(self) -> int: ... @property def iqmp(self) -> int: ... @property def public_numbers(self) -> RSAPublicNumbers: ... def private_key( self, backend: typing.Any = None, *, unsafe_skip_rsa_key_validation: bool = False, ) -> rsa.RSAPrivateKey: ... class RSAPublicNumbers: def __init__(self, e: int, n: int) -> None: ... @property def n(self) -> int: ... @property def e(self) -> int: ... def public_key(self, backend: typing.Any = None) -> rsa.RSAPublicKey: ... def generate_private_key( public_exponent: int, key_size: int, ) -> rsa.RSAPrivateKey: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi010064400017510000177000000007441464676315000253600ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric import x25519 class X25519PrivateKey: ... class X25519PublicKey: ... def generate_key() -> x25519.X25519PrivateKey: ... def from_private_bytes(data: bytes) -> x25519.X25519PrivateKey: ... def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi010064400017510000177000000007221464676315000252060ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric import x448 class X448PrivateKey: ... class X448PublicKey: ... def generate_key() -> x448.X448PrivateKey: ... def from_private_bytes(data: bytes) -> x448.X448PrivateKey: ... def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi010064400017510000177000000025621464676315000241230ustar 00000000000000# 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. import typing from cryptography import x509 from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes from cryptography.hazmat.primitives.serialization import ( KeySerializationEncryption, ) from cryptography.hazmat.primitives.serialization.pkcs12 import ( PKCS12KeyAndCertificates, PKCS12PrivateKeyTypes, ) class PKCS12Certificate: def __init__( self, cert: x509.Certificate, friendly_name: bytes | None ) -> None: ... @property def friendly_name(self) -> bytes | None: ... @property def certificate(self) -> x509.Certificate: ... def load_key_and_certificates( data: bytes, password: bytes | None, backend: typing.Any = None, ) -> tuple[ PrivateKeyTypes | None, x509.Certificate | None, list[x509.Certificate], ]: ... def load_pkcs12( data: bytes, password: bytes | None, backend: typing.Any = None, ) -> PKCS12KeyAndCertificates: ... def serialize_key_and_certificates( name: bytes | None, key: PKCS12PrivateKeyTypes | None, cert: x509.Certificate | None, cas: typing.Iterable[x509.Certificate | PKCS12Certificate] | None, encryption_algorithm: KeySerializationEncryption, ) -> bytes: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi010064400017510000177000000017141464676315000240450ustar 00000000000000# 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. import typing from cryptography import x509 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import pkcs7 def serialize_certificates( certs: list[x509.Certificate], encoding: serialization.Encoding, ) -> bytes: ... def encrypt_and_serialize( builder: pkcs7.PKCS7EnvelopeBuilder, encoding: serialization.Encoding, options: typing.Iterable[pkcs7.PKCS7Options], ) -> bytes: ... def sign_and_serialize( builder: pkcs7.PKCS7SignatureBuilder, encoding: serialization.Encoding, options: typing.Iterable[pkcs7.PKCS7Options], ) -> bytes: ... def load_pem_pkcs7_certificates( data: bytes, ) -> list[x509.Certificate]: ... def load_der_pkcs7_certificates( data: bytes, ) -> list[x509.Certificate]: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/test_support.pyi010064400017510000177000000016501464676315000255700ustar 00000000000000# 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. from cryptography import x509 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.serialization import pkcs7 class TestCertificate: not_after_tag: int not_before_tag: int issuer_value_tags: list[int] subject_value_tags: list[int] def test_parse_certificate(data: bytes) -> TestCertificate: ... def pkcs7_decrypt( encoding: serialization.Encoding, msg: bytes, pkey: serialization.pkcs7.PKCS7PrivateKeyTypes, cert_recipient: x509.Certificate, options: list[pkcs7.PKCS7Options], ) -> bytes: ... def pkcs7_verify( encoding: serialization.Encoding, sig: bytes, msg: bytes | None, certs: list[x509.Certificate], options: list[pkcs7.PKCS7Options], ) -> None: ... cryptography-43.0.0/src/cryptography/hazmat/bindings/_rust/x509.pyi010064400017510000177000000067371464676315000235350ustar 00000000000000# 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. import datetime import typing from cryptography import x509 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes def load_pem_x509_certificate( data: bytes, backend: typing.Any = None ) -> x509.Certificate: ... def load_der_x509_certificate( data: bytes, backend: typing.Any = None ) -> x509.Certificate: ... def load_pem_x509_certificates( data: bytes, ) -> list[x509.Certificate]: ... def load_pem_x509_crl( data: bytes, backend: typing.Any = None ) -> x509.CertificateRevocationList: ... def load_der_x509_crl( data: bytes, backend: typing.Any = None ) -> x509.CertificateRevocationList: ... def load_pem_x509_csr( data: bytes, backend: typing.Any = None ) -> x509.CertificateSigningRequest: ... def load_der_x509_csr( data: bytes, backend: typing.Any = None ) -> x509.CertificateSigningRequest: ... def encode_name_bytes(name: x509.Name) -> bytes: ... def encode_extension_value(extension: x509.ExtensionType) -> bytes: ... def create_x509_certificate( builder: x509.CertificateBuilder, private_key: PrivateKeyTypes, hash_algorithm: hashes.HashAlgorithm | None, rsa_padding: PKCS1v15 | PSS | None, ) -> x509.Certificate: ... def create_x509_csr( builder: x509.CertificateSigningRequestBuilder, private_key: PrivateKeyTypes, hash_algorithm: hashes.HashAlgorithm | None, rsa_padding: PKCS1v15 | PSS | None, ) -> x509.CertificateSigningRequest: ... def create_x509_crl( builder: x509.CertificateRevocationListBuilder, private_key: PrivateKeyTypes, hash_algorithm: hashes.HashAlgorithm | None, rsa_padding: PKCS1v15 | PSS | None, ) -> x509.CertificateRevocationList: ... class Sct: ... class Certificate: ... class RevokedCertificate: ... class CertificateRevocationList: ... class CertificateSigningRequest: ... class PolicyBuilder: def time(self, new_time: datetime.datetime) -> PolicyBuilder: ... def store(self, new_store: Store) -> PolicyBuilder: ... def max_chain_depth(self, new_max_chain_depth: int) -> PolicyBuilder: ... def build_client_verifier(self) -> ClientVerifier: ... def build_server_verifier( self, subject: x509.verification.Subject ) -> ServerVerifier: ... class VerifiedClient: @property def subjects(self) -> list[x509.GeneralName]: ... @property def chain(self) -> list[x509.Certificate]: ... class ClientVerifier: @property def validation_time(self) -> datetime.datetime: ... @property def store(self) -> Store: ... @property def max_chain_depth(self) -> int: ... def verify( self, leaf: x509.Certificate, intermediates: list[x509.Certificate], ) -> VerifiedClient: ... class ServerVerifier: @property def subject(self) -> x509.verification.Subject: ... @property def validation_time(self) -> datetime.datetime: ... @property def store(self) -> Store: ... @property def max_chain_depth(self) -> int: ... def verify( self, leaf: x509.Certificate, intermediates: list[x509.Certificate], ) -> list[x509.Certificate]: ... class Store: def __init__(self, certs: list[x509.Certificate]) -> None: ... class VerificationError(Exception): pass cryptography-43.0.0/src/cryptography/hazmat/bindings/openssl/__init__.py010064400017510000177000000002641464676315000247320ustar 00000000000000# 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. cryptography-43.0.0/src/cryptography/hazmat/bindings/openssl/_conditional.py010064400017510000177000000120531464676315000256340ustar 00000000000000# 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. from __future__ import annotations def cryptography_has_set_cert_cb() -> list[str]: return [ "SSL_CTX_set_cert_cb", "SSL_set_cert_cb", ] def cryptography_has_ssl_st() -> list[str]: return [ "SSL_ST_BEFORE", "SSL_ST_OK", "SSL_ST_INIT", "SSL_ST_RENEGOTIATE", ] def cryptography_has_tls_st() -> list[str]: return [ "TLS_ST_BEFORE", "TLS_ST_OK", ] def cryptography_has_ssl_sigalgs() -> list[str]: return [ "SSL_CTX_set1_sigalgs_list", ] def cryptography_has_psk() -> list[str]: return [ "SSL_CTX_use_psk_identity_hint", "SSL_CTX_set_psk_server_callback", "SSL_CTX_set_psk_client_callback", ] def cryptography_has_psk_tlsv13() -> list[str]: return [ "SSL_CTX_set_psk_find_session_callback", "SSL_CTX_set_psk_use_session_callback", "Cryptography_SSL_SESSION_new", "SSL_CIPHER_find", "SSL_SESSION_set1_master_key", "SSL_SESSION_set_cipher", "SSL_SESSION_set_protocol_version", ] def cryptography_has_custom_ext() -> list[str]: return [ "SSL_CTX_add_client_custom_ext", "SSL_CTX_add_server_custom_ext", "SSL_extension_supported", ] def cryptography_has_tlsv13_functions() -> list[str]: return [ "SSL_VERIFY_POST_HANDSHAKE", "SSL_CTX_set_ciphersuites", "SSL_verify_client_post_handshake", "SSL_CTX_set_post_handshake_auth", "SSL_set_post_handshake_auth", "SSL_SESSION_get_max_early_data", "SSL_write_early_data", "SSL_read_early_data", "SSL_CTX_set_max_early_data", ] def cryptography_has_engine() -> list[str]: return [ "ENGINE_by_id", "ENGINE_init", "ENGINE_finish", "ENGINE_get_default_RAND", "ENGINE_set_default_RAND", "ENGINE_unregister_RAND", "ENGINE_ctrl_cmd", "ENGINE_free", "ENGINE_get_name", "ENGINE_ctrl_cmd_string", "ENGINE_load_builtin_engines", "ENGINE_load_private_key", "ENGINE_load_public_key", "SSL_CTX_set_client_cert_engine", ] def cryptography_has_verified_chain() -> list[str]: return [ "SSL_get0_verified_chain", ] def cryptography_has_srtp() -> list[str]: return [ "SSL_CTX_set_tlsext_use_srtp", "SSL_set_tlsext_use_srtp", "SSL_get_selected_srtp_profile", ] def cryptography_has_op_no_renegotiation() -> list[str]: return [ "SSL_OP_NO_RENEGOTIATION", ] def cryptography_has_dtls_get_data_mtu() -> list[str]: return [ "DTLS_get_data_mtu", ] def cryptography_has_ssl_cookie() -> list[str]: return [ "SSL_OP_COOKIE_EXCHANGE", "DTLSv1_listen", "SSL_CTX_set_cookie_generate_cb", "SSL_CTX_set_cookie_verify_cb", ] def cryptography_has_prime_checks() -> list[str]: return [ "BN_prime_checks_for_size", ] def cryptography_has_unexpected_eof_while_reading() -> list[str]: return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"] def cryptography_has_ssl_op_ignore_unexpected_eof() -> list[str]: return [ "SSL_OP_IGNORE_UNEXPECTED_EOF", ] def cryptography_has_get_extms_support() -> list[str]: return ["SSL_get_extms_support"] # This is a mapping of # {condition: function-returning-names-dependent-on-that-condition} so we can # loop over them and delete unsupported names at runtime. It will be removed # when cffi supports #if in cdef. We use functions instead of just a dict of # lists so we can use coverage to measure which are used. CONDITIONAL_NAMES = { "Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb, "Cryptography_HAS_SSL_ST": cryptography_has_ssl_st, "Cryptography_HAS_TLS_ST": cryptography_has_tls_st, "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs, "Cryptography_HAS_PSK": cryptography_has_psk, "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, "Cryptography_HAS_ENGINE": cryptography_has_engine, "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, "Cryptography_HAS_OP_NO_RENEGOTIATION": ( cryptography_has_op_no_renegotiation ), "Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu, "Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie, "Cryptography_HAS_PRIME_CHECKS": cryptography_has_prime_checks, "Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": ( cryptography_has_unexpected_eof_while_reading ), "Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": ( cryptography_has_ssl_op_ignore_unexpected_eof ), "Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support, } cryptography-43.0.0/src/cryptography/hazmat/bindings/openssl/binding.py010064400017510000177000000077121464676315000246120ustar 00000000000000# 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. from __future__ import annotations import os import sys import threading import types import typing import warnings import cryptography from cryptography.exceptions import InternalError from cryptography.hazmat.bindings._rust import _openssl, openssl from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES def _openssl_assert(ok: bool) -> None: if not ok: errors = openssl.capture_error_stack() raise InternalError( "Unknown OpenSSL error. This error is commonly encountered when " "another library is not cleaning up the OpenSSL error stack. If " "you are using cryptography with another library that uses " "OpenSSL try disabling it before reporting a bug. Otherwise " "please file an issue at https://github.com/pyca/cryptography/" "issues with information on how to reproduce " f"this. ({errors!r})", errors, ) def build_conditional_library( lib: typing.Any, conditional_names: dict[str, typing.Callable[[], list[str]]], ) -> typing.Any: conditional_lib = types.ModuleType("lib") conditional_lib._original_lib = lib # type: ignore[attr-defined] excluded_names = set() for condition, names_cb in conditional_names.items(): if not getattr(lib, condition): excluded_names.update(names_cb()) for attr in dir(lib): if attr not in excluded_names: setattr(conditional_lib, attr, getattr(lib, attr)) return conditional_lib class Binding: """ OpenSSL API wrapper. """ lib: typing.ClassVar = None ffi = _openssl.ffi _lib_loaded = False _init_lock = threading.Lock() def __init__(self) -> None: self._ensure_ffi_initialized() @classmethod def _ensure_ffi_initialized(cls) -> None: with cls._init_lock: if not cls._lib_loaded: cls.lib = build_conditional_library( _openssl.lib, CONDITIONAL_NAMES ) cls._lib_loaded = True @classmethod def init_static_locks(cls) -> None: cls._ensure_ffi_initialized() def _verify_package_version(version: str) -> None: # Occasionally we run into situations where the version of the Python # package does not match the version of the shared object that is loaded. # This may occur in environments where multiple versions of cryptography # are installed and available in the python path. To avoid errors cropping # up later this code checks that the currently imported package and the # shared object that were loaded have the same version and raise an # ImportError if they do not so_package_version = _openssl.ffi.string( _openssl.lib.CRYPTOGRAPHY_PACKAGE_VERSION ) if version.encode("ascii") != so_package_version: raise ImportError( "The version of cryptography does not match the loaded " "shared object. This can happen if you have multiple copies of " "cryptography installed in your Python path. Please try creating " "a new virtual environment to resolve this issue. " f"Loaded python version: {version}, " f"shared object version: {so_package_version}" ) _openssl_assert( _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), ) _verify_package_version(cryptography.__version__) Binding.init_static_locks() if ( sys.platform == "win32" and os.environ.get("PROCESSOR_ARCHITEW6432") is not None ): warnings.warn( "You are using cryptography on a 32-bit Python on a 64-bit Windows " "Operating System. Cryptography will be significantly faster if you " "switch to using a 64-bit Python.", UserWarning, stacklevel=2, ) cryptography-43.0.0/src/cryptography/hazmat/decrepit/__init__.py010064400017510000177000000003301464676315000232430ustar 00000000000000# 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. from __future__ import annotations cryptography-43.0.0/src/cryptography/hazmat/decrepit/ciphers/__init__.py010064400017510000177000000003301464676315000247000ustar 00000000000000# 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. from __future__ import annotations cryptography-43.0.0/src/cryptography/hazmat/decrepit/ciphers/algorithms.py010064400017510000177000000047301464676315000253220ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.primitives._cipheralgorithm import ( BlockCipherAlgorithm, CipherAlgorithm, _verify_key_size, ) class ARC4(CipherAlgorithm): name = "RC4" key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 class TripleDES(BlockCipherAlgorithm): name = "3DES" block_size = 64 key_sizes = frozenset([64, 128, 192]) def __init__(self, key: bytes): if len(key) == 8: key += key + key elif len(key) == 16: key += key[:8] self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 class Blowfish(BlockCipherAlgorithm): name = "Blowfish" block_size = 64 key_sizes = frozenset(range(32, 449, 8)) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 class CAST5(BlockCipherAlgorithm): name = "CAST5" block_size = 64 key_sizes = frozenset(range(40, 129, 8)) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 class SEED(BlockCipherAlgorithm): name = "SEED" block_size = 128 key_sizes = frozenset([128]) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 class IDEA(BlockCipherAlgorithm): name = "IDEA" block_size = 64 key_sizes = frozenset([128]) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 # This class only allows RC2 with a 128-bit key. No support for # effective key bits or other key sizes is provided. class RC2(BlockCipherAlgorithm): name = "RC2" block_size = 64 key_sizes = frozenset([128]) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 cryptography-43.0.0/src/cryptography/hazmat/primitives/__init__.py010064400017510000177000000002641464676315000236450ustar 00000000000000# 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. cryptography-43.0.0/src/cryptography/hazmat/primitives/_asymmetric.py010064400017510000177000000010241464676315000244150ustar 00000000000000# 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. from __future__ import annotations import abc # This exists to break an import cycle. It is normally accessible from the # asymmetric padding module. class AsymmetricPadding(metaclass=abc.ABCMeta): @property @abc.abstractmethod def name(self) -> str: """ A string naming this padding (e.g. "PSS", "PKCS1"). """ cryptography-43.0.0/src/cryptography/hazmat/primitives/_cipheralgorithm.py010064400017510000177000000027271464676315000254340ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography import utils # This exists to break an import cycle. It is normally accessible from the # ciphers module. class CipherAlgorithm(metaclass=abc.ABCMeta): @property @abc.abstractmethod def name(self) -> str: """ A string naming this mode (e.g. "AES", "Camellia"). """ @property @abc.abstractmethod def key_sizes(self) -> frozenset[int]: """ Valid key sizes for this algorithm in bits """ @property @abc.abstractmethod def key_size(self) -> int: """ The size of the key being used as an integer in bits (e.g. 128, 256). """ class BlockCipherAlgorithm(CipherAlgorithm): key: bytes @property @abc.abstractmethod def block_size(self) -> int: """ The size of a block as an integer in bits (e.g. 64, 128). """ def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: # Verify that the key is instance of bytes utils._check_byteslike("key", key) # Verify that the key size matches the expected key size if len(key) * 8 not in algorithm.key_sizes: raise ValueError( f"Invalid key size ({len(key) * 8}) for {algorithm.name}." ) return key cryptography-43.0.0/src/cryptography/hazmat/primitives/_serialization.py010064400017510000177000000120261464676315000251210ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography import utils from cryptography.hazmat.primitives.hashes import HashAlgorithm # This exists to break an import cycle. These classes are normally accessible # from the serialization module. class PBES(utils.Enum): PBESv1SHA1And3KeyTripleDESCBC = "PBESv1 using SHA1 and 3-Key TripleDES" PBESv2SHA256AndAES256CBC = "PBESv2 using SHA256 PBKDF2 and AES256 CBC" class Encoding(utils.Enum): PEM = "PEM" DER = "DER" OpenSSH = "OpenSSH" Raw = "Raw" X962 = "ANSI X9.62" SMIME = "S/MIME" class PrivateFormat(utils.Enum): PKCS8 = "PKCS8" TraditionalOpenSSL = "TraditionalOpenSSL" Raw = "Raw" OpenSSH = "OpenSSH" PKCS12 = "PKCS12" def encryption_builder(self) -> KeySerializationEncryptionBuilder: if self not in (PrivateFormat.OpenSSH, PrivateFormat.PKCS12): raise ValueError( "encryption_builder only supported with PrivateFormat.OpenSSH" " and PrivateFormat.PKCS12" ) return KeySerializationEncryptionBuilder(self) class PublicFormat(utils.Enum): SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1" PKCS1 = "Raw PKCS#1" OpenSSH = "OpenSSH" Raw = "Raw" CompressedPoint = "X9.62 Compressed Point" UncompressedPoint = "X9.62 Uncompressed Point" class ParameterFormat(utils.Enum): PKCS3 = "PKCS3" class KeySerializationEncryption(metaclass=abc.ABCMeta): pass class BestAvailableEncryption(KeySerializationEncryption): def __init__(self, password: bytes): if not isinstance(password, bytes) or len(password) == 0: raise ValueError("Password must be 1 or more bytes.") self.password = password class NoEncryption(KeySerializationEncryption): pass class KeySerializationEncryptionBuilder: def __init__( self, format: PrivateFormat, *, _kdf_rounds: int | None = None, _hmac_hash: HashAlgorithm | None = None, _key_cert_algorithm: PBES | None = None, ) -> None: self._format = format self._kdf_rounds = _kdf_rounds self._hmac_hash = _hmac_hash self._key_cert_algorithm = _key_cert_algorithm def kdf_rounds(self, rounds: int) -> KeySerializationEncryptionBuilder: if self._kdf_rounds is not None: raise ValueError("kdf_rounds already set") if not isinstance(rounds, int): raise TypeError("kdf_rounds must be an integer") if rounds < 1: raise ValueError("kdf_rounds must be a positive integer") return KeySerializationEncryptionBuilder( self._format, _kdf_rounds=rounds, _hmac_hash=self._hmac_hash, _key_cert_algorithm=self._key_cert_algorithm, ) def hmac_hash( self, algorithm: HashAlgorithm ) -> KeySerializationEncryptionBuilder: if self._format is not PrivateFormat.PKCS12: raise TypeError( "hmac_hash only supported with PrivateFormat.PKCS12" ) if self._hmac_hash is not None: raise ValueError("hmac_hash already set") return KeySerializationEncryptionBuilder( self._format, _kdf_rounds=self._kdf_rounds, _hmac_hash=algorithm, _key_cert_algorithm=self._key_cert_algorithm, ) def key_cert_algorithm( self, algorithm: PBES ) -> KeySerializationEncryptionBuilder: if self._format is not PrivateFormat.PKCS12: raise TypeError( "key_cert_algorithm only supported with " "PrivateFormat.PKCS12" ) if self._key_cert_algorithm is not None: raise ValueError("key_cert_algorithm already set") return KeySerializationEncryptionBuilder( self._format, _kdf_rounds=self._kdf_rounds, _hmac_hash=self._hmac_hash, _key_cert_algorithm=algorithm, ) def build(self, password: bytes) -> KeySerializationEncryption: if not isinstance(password, bytes) or len(password) == 0: raise ValueError("Password must be 1 or more bytes.") return _KeySerializationEncryption( self._format, password, kdf_rounds=self._kdf_rounds, hmac_hash=self._hmac_hash, key_cert_algorithm=self._key_cert_algorithm, ) class _KeySerializationEncryption(KeySerializationEncryption): def __init__( self, format: PrivateFormat, password: bytes, *, kdf_rounds: int | None, hmac_hash: HashAlgorithm | None, key_cert_algorithm: PBES | None, ): self._format = format self.password = password self._kdf_rounds = kdf_rounds self._hmac_hash = hmac_hash self._key_cert_algorithm = key_cert_algorithm cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/__init__.py010064400017510000177000000002641464676315000260220ustar 00000000000000# 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. cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/dh.py010064400017510000177000000065341464676315000246640ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization generate_parameters = rust_openssl.dh.generate_parameters DHPrivateNumbers = rust_openssl.dh.DHPrivateNumbers DHPublicNumbers = rust_openssl.dh.DHPublicNumbers DHParameterNumbers = rust_openssl.dh.DHParameterNumbers class DHParameters(metaclass=abc.ABCMeta): @abc.abstractmethod def generate_private_key(self) -> DHPrivateKey: """ Generates and returns a DHPrivateKey. """ @abc.abstractmethod def parameter_bytes( self, encoding: _serialization.Encoding, format: _serialization.ParameterFormat, ) -> bytes: """ Returns the parameters serialized as bytes. """ @abc.abstractmethod def parameter_numbers(self) -> DHParameterNumbers: """ Returns a DHParameterNumbers. """ DHParametersWithSerialization = DHParameters DHParameters.register(rust_openssl.dh.DHParameters) class DHPublicKey(metaclass=abc.ABCMeta): @property @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod def parameters(self) -> DHParameters: """ The DHParameters object associated with this public key. """ @abc.abstractmethod def public_numbers(self) -> DHPublicNumbers: """ Returns a DHPublicNumbers. """ @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ Returns the key serialized as bytes. """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ DHPublicKeyWithSerialization = DHPublicKey DHPublicKey.register(rust_openssl.dh.DHPublicKey) class DHPrivateKey(metaclass=abc.ABCMeta): @property @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod def public_key(self) -> DHPublicKey: """ The DHPublicKey associated with this private key. """ @abc.abstractmethod def parameters(self) -> DHParameters: """ The DHParameters object associated with this private key. """ @abc.abstractmethod def exchange(self, peer_public_key: DHPublicKey) -> bytes: """ Given peer's DHPublicKey, carry out the key exchange and return shared key as bytes. """ @abc.abstractmethod def private_numbers(self) -> DHPrivateNumbers: """ Returns a DHPrivateNumbers. """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ Returns the key serialized as bytes. """ DHPrivateKeyWithSerialization = DHPrivateKey DHPrivateKey.register(rust_openssl.dh.DHPrivateKey) cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/dsa.py010064400017510000177000000075511464676315000250400ustar 00000000000000# 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. from __future__ import annotations import abc import typing from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives.asymmetric import utils as asym_utils class DSAParameters(metaclass=abc.ABCMeta): @abc.abstractmethod def generate_private_key(self) -> DSAPrivateKey: """ Generates and returns a DSAPrivateKey. """ @abc.abstractmethod def parameter_numbers(self) -> DSAParameterNumbers: """ Returns a DSAParameterNumbers. """ DSAParametersWithNumbers = DSAParameters DSAParameters.register(rust_openssl.dsa.DSAParameters) class DSAPrivateKey(metaclass=abc.ABCMeta): @property @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod def public_key(self) -> DSAPublicKey: """ The DSAPublicKey associated with this private key. """ @abc.abstractmethod def parameters(self) -> DSAParameters: """ The DSAParameters object associated with this private key. """ @abc.abstractmethod def sign( self, data: bytes, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> bytes: """ Signs the data """ @abc.abstractmethod def private_numbers(self) -> DSAPrivateNumbers: """ Returns a DSAPrivateNumbers. """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ Returns the key serialized as bytes. """ DSAPrivateKeyWithSerialization = DSAPrivateKey DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey) class DSAPublicKey(metaclass=abc.ABCMeta): @property @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod def parameters(self) -> DSAParameters: """ The DSAParameters object associated with this public key. """ @abc.abstractmethod def public_numbers(self) -> DSAPublicNumbers: """ Returns a DSAPublicNumbers. """ @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ Returns the key serialized as bytes. """ @abc.abstractmethod def verify( self, signature: bytes, data: bytes, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> None: """ Verifies the signature of the data. """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ DSAPublicKeyWithSerialization = DSAPublicKey DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) DSAPrivateNumbers = rust_openssl.dsa.DSAPrivateNumbers DSAPublicNumbers = rust_openssl.dsa.DSAPublicNumbers DSAParameterNumbers = rust_openssl.dsa.DSAParameterNumbers def generate_parameters( key_size: int, backend: typing.Any = None ) -> DSAParameters: if key_size not in (1024, 2048, 3072, 4096): raise ValueError("Key size must be 1024, 2048, 3072, or 4096 bits.") return rust_openssl.dsa.generate_parameters(key_size) def generate_private_key( key_size: int, backend: typing.Any = None ) -> DSAPrivateKey: parameters = generate_parameters(key_size) return parameters.generate_private_key() cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/ec.py010064400017510000177000000242741464676315000246610ustar 00000000000000# 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. from __future__ import annotations import abc import typing from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat._oid import ObjectIdentifier from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives.asymmetric import utils as asym_utils class EllipticCurveOID: SECP192R1 = ObjectIdentifier("1.2.840.10045.3.1.1") SECP224R1 = ObjectIdentifier("1.3.132.0.33") SECP256K1 = ObjectIdentifier("1.3.132.0.10") SECP256R1 = ObjectIdentifier("1.2.840.10045.3.1.7") SECP384R1 = ObjectIdentifier("1.3.132.0.34") SECP521R1 = ObjectIdentifier("1.3.132.0.35") BRAINPOOLP256R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.7") BRAINPOOLP384R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.11") BRAINPOOLP512R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.13") SECT163K1 = ObjectIdentifier("1.3.132.0.1") SECT163R2 = ObjectIdentifier("1.3.132.0.15") SECT233K1 = ObjectIdentifier("1.3.132.0.26") SECT233R1 = ObjectIdentifier("1.3.132.0.27") SECT283K1 = ObjectIdentifier("1.3.132.0.16") SECT283R1 = ObjectIdentifier("1.3.132.0.17") SECT409K1 = ObjectIdentifier("1.3.132.0.36") SECT409R1 = ObjectIdentifier("1.3.132.0.37") SECT571K1 = ObjectIdentifier("1.3.132.0.38") SECT571R1 = ObjectIdentifier("1.3.132.0.39") class EllipticCurve(metaclass=abc.ABCMeta): @property @abc.abstractmethod def name(self) -> str: """ The name of the curve. e.g. secp256r1. """ @property @abc.abstractmethod def key_size(self) -> int: """ Bit size of a secret scalar for the curve. """ class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): @property @abc.abstractmethod def algorithm( self, ) -> asym_utils.Prehashed | hashes.HashAlgorithm: """ The digest algorithm used with this signature. """ class EllipticCurvePrivateKey(metaclass=abc.ABCMeta): @abc.abstractmethod def exchange( self, algorithm: ECDH, peer_public_key: EllipticCurvePublicKey ) -> bytes: """ Performs a key exchange operation using the provided algorithm with the provided peer's public key. """ @abc.abstractmethod def public_key(self) -> EllipticCurvePublicKey: """ The EllipticCurvePublicKey for this private key. """ @property @abc.abstractmethod def curve(self) -> EllipticCurve: """ The EllipticCurve that this key is on. """ @property @abc.abstractmethod def key_size(self) -> int: """ Bit size of a secret scalar for the curve. """ @abc.abstractmethod def sign( self, data: bytes, signature_algorithm: EllipticCurveSignatureAlgorithm, ) -> bytes: """ Signs the data """ @abc.abstractmethod def private_numbers(self) -> EllipticCurvePrivateNumbers: """ Returns an EllipticCurvePrivateNumbers. """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ Returns the key serialized as bytes. """ EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey EllipticCurvePrivateKey.register(rust_openssl.ec.ECPrivateKey) class EllipticCurvePublicKey(metaclass=abc.ABCMeta): @property @abc.abstractmethod def curve(self) -> EllipticCurve: """ The EllipticCurve that this key is on. """ @property @abc.abstractmethod def key_size(self) -> int: """ Bit size of a secret scalar for the curve. """ @abc.abstractmethod def public_numbers(self) -> EllipticCurvePublicNumbers: """ Returns an EllipticCurvePublicNumbers. """ @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ Returns the key serialized as bytes. """ @abc.abstractmethod def verify( self, signature: bytes, data: bytes, signature_algorithm: EllipticCurveSignatureAlgorithm, ) -> None: """ Verifies the signature of the data. """ @classmethod def from_encoded_point( cls, curve: EllipticCurve, data: bytes ) -> EllipticCurvePublicKey: utils._check_bytes("data", data) if len(data) == 0: raise ValueError("data must not be an empty byte string") if data[0] not in [0x02, 0x03, 0x04]: raise ValueError("Unsupported elliptic curve point type") return rust_openssl.ec.from_public_bytes(curve, data) @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey EllipticCurvePublicKey.register(rust_openssl.ec.ECPublicKey) EllipticCurvePrivateNumbers = rust_openssl.ec.EllipticCurvePrivateNumbers EllipticCurvePublicNumbers = rust_openssl.ec.EllipticCurvePublicNumbers class SECT571R1(EllipticCurve): name = "sect571r1" key_size = 570 class SECT409R1(EllipticCurve): name = "sect409r1" key_size = 409 class SECT283R1(EllipticCurve): name = "sect283r1" key_size = 283 class SECT233R1(EllipticCurve): name = "sect233r1" key_size = 233 class SECT163R2(EllipticCurve): name = "sect163r2" key_size = 163 class SECT571K1(EllipticCurve): name = "sect571k1" key_size = 571 class SECT409K1(EllipticCurve): name = "sect409k1" key_size = 409 class SECT283K1(EllipticCurve): name = "sect283k1" key_size = 283 class SECT233K1(EllipticCurve): name = "sect233k1" key_size = 233 class SECT163K1(EllipticCurve): name = "sect163k1" key_size = 163 class SECP521R1(EllipticCurve): name = "secp521r1" key_size = 521 class SECP384R1(EllipticCurve): name = "secp384r1" key_size = 384 class SECP256R1(EllipticCurve): name = "secp256r1" key_size = 256 class SECP256K1(EllipticCurve): name = "secp256k1" key_size = 256 class SECP224R1(EllipticCurve): name = "secp224r1" key_size = 224 class SECP192R1(EllipticCurve): name = "secp192r1" key_size = 192 class BrainpoolP256R1(EllipticCurve): name = "brainpoolP256r1" key_size = 256 class BrainpoolP384R1(EllipticCurve): name = "brainpoolP384r1" key_size = 384 class BrainpoolP512R1(EllipticCurve): name = "brainpoolP512r1" key_size = 512 _CURVE_TYPES: dict[str, EllipticCurve] = { "prime192v1": SECP192R1(), "prime256v1": SECP256R1(), "secp192r1": SECP192R1(), "secp224r1": SECP224R1(), "secp256r1": SECP256R1(), "secp384r1": SECP384R1(), "secp521r1": SECP521R1(), "secp256k1": SECP256K1(), "sect163k1": SECT163K1(), "sect233k1": SECT233K1(), "sect283k1": SECT283K1(), "sect409k1": SECT409K1(), "sect571k1": SECT571K1(), "sect163r2": SECT163R2(), "sect233r1": SECT233R1(), "sect283r1": SECT283R1(), "sect409r1": SECT409R1(), "sect571r1": SECT571R1(), "brainpoolP256r1": BrainpoolP256R1(), "brainpoolP384r1": BrainpoolP384R1(), "brainpoolP512r1": BrainpoolP512R1(), } class ECDSA(EllipticCurveSignatureAlgorithm): def __init__( self, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, deterministic_signing: bool = False, ): from cryptography.hazmat.backends.openssl.backend import backend if ( deterministic_signing and not backend.ecdsa_deterministic_supported() ): raise UnsupportedAlgorithm( "ECDSA with deterministic signature (RFC 6979) is not " "supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) self._algorithm = algorithm self._deterministic_signing = deterministic_signing @property def algorithm( self, ) -> asym_utils.Prehashed | hashes.HashAlgorithm: return self._algorithm @property def deterministic_signing( self, ) -> bool: return self._deterministic_signing generate_private_key = rust_openssl.ec.generate_private_key def derive_private_key( private_value: int, curve: EllipticCurve, backend: typing.Any = None, ) -> EllipticCurvePrivateKey: if not isinstance(private_value, int): raise TypeError("private_value must be an integer type.") if private_value <= 0: raise ValueError("private_value must be a positive integer.") return rust_openssl.ec.derive_private_key(private_value, curve) class ECDH: pass _OID_TO_CURVE = { EllipticCurveOID.SECP192R1: SECP192R1, EllipticCurveOID.SECP224R1: SECP224R1, EllipticCurveOID.SECP256K1: SECP256K1, EllipticCurveOID.SECP256R1: SECP256R1, EllipticCurveOID.SECP384R1: SECP384R1, EllipticCurveOID.SECP521R1: SECP521R1, EllipticCurveOID.BRAINPOOLP256R1: BrainpoolP256R1, EllipticCurveOID.BRAINPOOLP384R1: BrainpoolP384R1, EllipticCurveOID.BRAINPOOLP512R1: BrainpoolP512R1, EllipticCurveOID.SECT163K1: SECT163K1, EllipticCurveOID.SECT163R2: SECT163R2, EllipticCurveOID.SECT233K1: SECT233K1, EllipticCurveOID.SECT233R1: SECT233R1, EllipticCurveOID.SECT283K1: SECT283K1, EllipticCurveOID.SECT283R1: SECT283R1, EllipticCurveOID.SECT409K1: SECT409K1, EllipticCurveOID.SECT409R1: SECT409R1, EllipticCurveOID.SECT571K1: SECT571K1, EllipticCurveOID.SECT571R1: SECT571R1, } def get_curve_for_oid(oid: ObjectIdentifier) -> type[EllipticCurve]: try: return _OID_TO_CURVE[oid] except KeyError: raise LookupError( "The provided object identifier has no matching elliptic " "curve class" ) cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/ed25519.py010064400017510000177000000065371464676315000252720ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization class Ed25519PublicKey(metaclass=abc.ABCMeta): @classmethod def from_public_bytes(cls, data: bytes) -> Ed25519PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): raise UnsupportedAlgorithm( "ed25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) return rust_openssl.ed25519.from_public_bytes(data) @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ The serialized bytes of the public key. """ @abc.abstractmethod def public_bytes_raw(self) -> bytes: """ The raw bytes of the public key. Equivalent to public_bytes(Raw, Raw). """ @abc.abstractmethod def verify(self, signature: bytes, data: bytes) -> None: """ Verify the signature. """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) class Ed25519PrivateKey(metaclass=abc.ABCMeta): @classmethod def generate(cls) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): raise UnsupportedAlgorithm( "ed25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) return rust_openssl.ed25519.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): raise UnsupportedAlgorithm( "ed25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) return rust_openssl.ed25519.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> Ed25519PublicKey: """ The Ed25519PublicKey derived from the private key. """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ The serialized bytes of the private key. """ @abc.abstractmethod def private_bytes_raw(self) -> bytes: """ The raw bytes of the private key. Equivalent to private_bytes(Raw, Raw, NoEncryption()). """ @abc.abstractmethod def sign(self, data: bytes) -> bytes: """ Signs the data. """ Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/ed448.py010064400017510000177000000066001464676315000251130ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization class Ed448PublicKey(metaclass=abc.ABCMeta): @classmethod def from_public_bytes(cls, data: bytes) -> Ed448PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): raise UnsupportedAlgorithm( "ed448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) return rust_openssl.ed448.from_public_bytes(data) @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ The serialized bytes of the public key. """ @abc.abstractmethod def public_bytes_raw(self) -> bytes: """ The raw bytes of the public key. Equivalent to public_bytes(Raw, Raw). """ @abc.abstractmethod def verify(self, signature: bytes, data: bytes) -> None: """ Verify the signature. """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ if hasattr(rust_openssl, "ed448"): Ed448PublicKey.register(rust_openssl.ed448.Ed448PublicKey) class Ed448PrivateKey(metaclass=abc.ABCMeta): @classmethod def generate(cls) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): raise UnsupportedAlgorithm( "ed448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) return rust_openssl.ed448.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): raise UnsupportedAlgorithm( "ed448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) return rust_openssl.ed448.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> Ed448PublicKey: """ The Ed448PublicKey derived from the private key. """ @abc.abstractmethod def sign(self, data: bytes) -> bytes: """ Signs the data. """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ The serialized bytes of the private key. """ @abc.abstractmethod def private_bytes_raw(self) -> bytes: """ The raw bytes of the private key. Equivalent to private_bytes(Raw, Raw, NoEncryption()). """ if hasattr(rust_openssl, "x448"): Ed448PrivateKey.register(rust_openssl.ed448.Ed448PrivateKey) cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/padding.py010064400017510000177000000055051464676315000256740ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives._asymmetric import ( AsymmetricPadding as AsymmetricPadding, ) from cryptography.hazmat.primitives.asymmetric import rsa class PKCS1v15(AsymmetricPadding): name = "EMSA-PKCS1-v1_5" class _MaxLength: "Sentinel value for `MAX_LENGTH`." class _Auto: "Sentinel value for `AUTO`." class _DigestLength: "Sentinel value for `DIGEST_LENGTH`." class PSS(AsymmetricPadding): MAX_LENGTH = _MaxLength() AUTO = _Auto() DIGEST_LENGTH = _DigestLength() name = "EMSA-PSS" _salt_length: int | _MaxLength | _Auto | _DigestLength def __init__( self, mgf: MGF, salt_length: int | _MaxLength | _Auto | _DigestLength, ) -> None: self._mgf = mgf if not isinstance( salt_length, (int, _MaxLength, _Auto, _DigestLength) ): raise TypeError( "salt_length must be an integer, MAX_LENGTH, " "DIGEST_LENGTH, or AUTO" ) if isinstance(salt_length, int) and salt_length < 0: raise ValueError("salt_length must be zero or greater.") self._salt_length = salt_length @property def mgf(self) -> MGF: return self._mgf class OAEP(AsymmetricPadding): name = "EME-OAEP" def __init__( self, mgf: MGF, algorithm: hashes.HashAlgorithm, label: bytes | None, ): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of hashes.HashAlgorithm.") self._mgf = mgf self._algorithm = algorithm self._label = label @property def algorithm(self) -> hashes.HashAlgorithm: return self._algorithm @property def mgf(self) -> MGF: return self._mgf class MGF(metaclass=abc.ABCMeta): _algorithm: hashes.HashAlgorithm class MGF1(MGF): MAX_LENGTH = _MaxLength() def __init__(self, algorithm: hashes.HashAlgorithm): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of hashes.HashAlgorithm.") self._algorithm = algorithm def calculate_max_pss_salt_length( key: rsa.RSAPrivateKey | rsa.RSAPublicKey, hash_algorithm: hashes.HashAlgorithm, ) -> int: if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): raise TypeError("key must be an RSA public or private key") # bit length - 1 per RFC 3447 emlen = (key.key_size + 6) // 8 salt_length = emlen - hash_algorithm.digest_size - 2 assert salt_length >= 0 return salt_length cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/rsa.py010064400017510000177000000167251464676315000250610ustar 00000000000000# 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. from __future__ import annotations import abc import typing from math import gcd from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding from cryptography.hazmat.primitives.asymmetric import utils as asym_utils class RSAPrivateKey(metaclass=abc.ABCMeta): @abc.abstractmethod def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: """ Decrypts the provided ciphertext. """ @property @abc.abstractmethod def key_size(self) -> int: """ The bit length of the public modulus. """ @abc.abstractmethod def public_key(self) -> RSAPublicKey: """ The RSAPublicKey associated with this private key. """ @abc.abstractmethod def sign( self, data: bytes, padding: AsymmetricPadding, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> bytes: """ Signs the data. """ @abc.abstractmethod def private_numbers(self) -> RSAPrivateNumbers: """ Returns an RSAPrivateNumbers. """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ Returns the key serialized as bytes. """ RSAPrivateKeyWithSerialization = RSAPrivateKey RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey) class RSAPublicKey(metaclass=abc.ABCMeta): @abc.abstractmethod def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: """ Encrypts the given plaintext. """ @property @abc.abstractmethod def key_size(self) -> int: """ The bit length of the public modulus. """ @abc.abstractmethod def public_numbers(self) -> RSAPublicNumbers: """ Returns an RSAPublicNumbers """ @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ Returns the key serialized as bytes. """ @abc.abstractmethod def verify( self, signature: bytes, data: bytes, padding: AsymmetricPadding, algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> None: """ Verifies the signature of the data. """ @abc.abstractmethod def recover_data_from_signature( self, signature: bytes, padding: AsymmetricPadding, algorithm: hashes.HashAlgorithm | None, ) -> bytes: """ Recovers the original data from the signature. """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ RSAPublicKeyWithSerialization = RSAPublicKey RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey) RSAPrivateNumbers = rust_openssl.rsa.RSAPrivateNumbers RSAPublicNumbers = rust_openssl.rsa.RSAPublicNumbers def generate_private_key( public_exponent: int, key_size: int, backend: typing.Any = None, ) -> RSAPrivateKey: _verify_rsa_parameters(public_exponent, key_size) return rust_openssl.rsa.generate_private_key(public_exponent, key_size) def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: if public_exponent not in (3, 65537): raise ValueError( "public_exponent must be either 3 (for legacy compatibility) or " "65537. Almost everyone should choose 65537 here!" ) if key_size < 1024: raise ValueError("key_size must be at least 1024-bits.") def _modinv(e: int, m: int) -> int: """ Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1 """ x1, x2 = 1, 0 a, b = e, m while b > 0: q, r = divmod(a, b) xn = x1 - q * x2 a, b, x1, x2 = b, r, x2, xn return x1 % m def rsa_crt_iqmp(p: int, q: int) -> int: """ Compute the CRT (q ** -1) % p value from RSA primes p and q. """ return _modinv(q, p) def rsa_crt_dmp1(private_exponent: int, p: int) -> int: """ Compute the CRT private_exponent % (p - 1) value from the RSA private_exponent (d) and p. """ return private_exponent % (p - 1) def rsa_crt_dmq1(private_exponent: int, q: int) -> int: """ Compute the CRT private_exponent % (q - 1) value from the RSA private_exponent (d) and q. """ return private_exponent % (q - 1) def rsa_recover_private_exponent(e: int, p: int, q: int) -> int: """ Compute the RSA private_exponent (d) given the public exponent (e) and the RSA primes p and q. This uses the Carmichael totient function to generate the smallest possible working value of the private exponent. """ # This lambda_n is the Carmichael totient function. # The original RSA paper uses the Euler totient function # here: phi_n = (p - 1) * (q - 1) # Either version of the private exponent will work, but the # one generated by the older formulation may be larger # than necessary. (lambda_n always divides phi_n) # # TODO: Replace with lcm(p - 1, q - 1) once the minimum # supported Python version is >= 3.9. lambda_n = (p - 1) * (q - 1) // gcd(p - 1, q - 1) return _modinv(e, lambda_n) # Controls the number of iterations rsa_recover_prime_factors will perform # to obtain the prime factors. Each iteration increments by 2 so the actual # maximum attempts is half this number. _MAX_RECOVERY_ATTEMPTS = 1000 def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: """ Compute factors p and q from the private exponent d. We assume that n has no more than two factors. This function is adapted from code in PyCrypto. """ # See 8.2.2(i) in Handbook of Applied Cryptography. ktot = d * e - 1 # The quantity d*e-1 is a multiple of phi(n), even, # and can be represented as t*2^s. t = ktot while t % 2 == 0: t = t // 2 # Cycle through all multiplicative inverses in Zn. # The algorithm is non-deterministic, but there is a 50% chance # any candidate a leads to successful factoring. # See "Digitalized Signatures and Public Key Functions as Intractable # as Factorization", M. Rabin, 1979 spotted = False a = 2 while not spotted and a < _MAX_RECOVERY_ATTEMPTS: k = t # Cycle through all values a^{t*2^i}=a^k while k < ktot: cand = pow(a, k, n) # Check if a^k is a non-trivial root of unity (mod n) if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1: # We have found a number such that (cand-1)(cand+1)=0 (mod n). # Either of the terms divides n. p = gcd(cand + 1, n) spotted = True break k *= 2 # This value was not any good... let's try another! a += 2 if not spotted: raise ValueError("Unable to compute factors p and q from exponent d.") # Found ! q, r = divmod(n, p) assert r == 0 p, q = sorted((p, q), reverse=True) return (p, q) cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/types.py010064400017510000177000000056641464676315000254400ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography import utils from cryptography.hazmat.primitives.asymmetric import ( dh, dsa, ec, ed448, ed25519, rsa, x448, x25519, ) # Every asymmetric key type PublicKeyTypes = typing.Union[ dh.DHPublicKey, dsa.DSAPublicKey, rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey, ed448.Ed448PublicKey, x25519.X25519PublicKey, x448.X448PublicKey, ] PUBLIC_KEY_TYPES = PublicKeyTypes utils.deprecated( PUBLIC_KEY_TYPES, __name__, "Use PublicKeyTypes instead", utils.DeprecatedIn40, name="PUBLIC_KEY_TYPES", ) # Every asymmetric key type PrivateKeyTypes = typing.Union[ dh.DHPrivateKey, ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey, rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, x25519.X25519PrivateKey, x448.X448PrivateKey, ] PRIVATE_KEY_TYPES = PrivateKeyTypes utils.deprecated( PRIVATE_KEY_TYPES, __name__, "Use PrivateKeyTypes instead", utils.DeprecatedIn40, name="PRIVATE_KEY_TYPES", ) # Just the key types we allow to be used for x509 signing. This mirrors # the certificate public key types CertificateIssuerPrivateKeyTypes = typing.Union[ ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey, rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, ] CERTIFICATE_PRIVATE_KEY_TYPES = CertificateIssuerPrivateKeyTypes utils.deprecated( CERTIFICATE_PRIVATE_KEY_TYPES, __name__, "Use CertificateIssuerPrivateKeyTypes instead", utils.DeprecatedIn40, name="CERTIFICATE_PRIVATE_KEY_TYPES", ) # Just the key types we allow to be used for x509 signing. This mirrors # the certificate private key types CertificateIssuerPublicKeyTypes = typing.Union[ dsa.DSAPublicKey, rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey, ed448.Ed448PublicKey, ] CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES = CertificateIssuerPublicKeyTypes utils.deprecated( CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES, __name__, "Use CertificateIssuerPublicKeyTypes instead", utils.DeprecatedIn40, name="CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES", ) # This type removes DHPublicKey. x448/x25519 can be a public key # but cannot be used in signing so they are allowed here. CertificatePublicKeyTypes = typing.Union[ dsa.DSAPublicKey, rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey, ed448.Ed448PublicKey, x25519.X25519PublicKey, x448.X448PublicKey, ] CERTIFICATE_PUBLIC_KEY_TYPES = CertificatePublicKeyTypes utils.deprecated( CERTIFICATE_PUBLIC_KEY_TYPES, __name__, "Use CertificatePublicKeyTypes instead", utils.DeprecatedIn40, name="CERTIFICATE_PUBLIC_KEY_TYPES", ) cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/utils.py010064400017510000177000000014261464676315000254240ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.bindings._rust import asn1 from cryptography.hazmat.primitives import hashes decode_dss_signature = asn1.decode_dss_signature encode_dss_signature = asn1.encode_dss_signature class Prehashed: def __init__(self, algorithm: hashes.HashAlgorithm): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of HashAlgorithm.") self._algorithm = algorithm self._digest_size = algorithm.digest_size @property def digest_size(self) -> int: return self._digest_size cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/x25519.py010064400017510000177000000064151464676315000251440ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization class X25519PublicKey(metaclass=abc.ABCMeta): @classmethod def from_public_bytes(cls, data: bytes) -> X25519PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): raise UnsupportedAlgorithm( "X25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return rust_openssl.x25519.from_public_bytes(data) @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ The serialized bytes of the public key. """ @abc.abstractmethod def public_bytes_raw(self) -> bytes: """ The raw bytes of the public key. Equivalent to public_bytes(Raw, Raw). """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) class X25519PrivateKey(metaclass=abc.ABCMeta): @classmethod def generate(cls) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): raise UnsupportedAlgorithm( "X25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return rust_openssl.x25519.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): raise UnsupportedAlgorithm( "X25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return rust_openssl.x25519.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> X25519PublicKey: """ Returns the public key associated with this private key """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ The serialized bytes of the private key. """ @abc.abstractmethod def private_bytes_raw(self) -> bytes: """ The raw bytes of the private key. Equivalent to private_bytes(Raw, Raw, NoEncryption()). """ @abc.abstractmethod def exchange(self, peer_public_key: X25519PublicKey) -> bytes: """ Performs a key exchange operation using the provided peer's public key. """ X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) cryptography-43.0.0/src/cryptography/hazmat/primitives/asymmetric/x448.py010064400017510000177000000064561464676315000250030ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization class X448PublicKey(metaclass=abc.ABCMeta): @classmethod def from_public_bytes(cls, data: bytes) -> X448PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): raise UnsupportedAlgorithm( "X448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return rust_openssl.x448.from_public_bytes(data) @abc.abstractmethod def public_bytes( self, encoding: _serialization.Encoding, format: _serialization.PublicFormat, ) -> bytes: """ The serialized bytes of the public key. """ @abc.abstractmethod def public_bytes_raw(self) -> bytes: """ The raw bytes of the public key. Equivalent to public_bytes(Raw, Raw). """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ if hasattr(rust_openssl, "x448"): X448PublicKey.register(rust_openssl.x448.X448PublicKey) class X448PrivateKey(metaclass=abc.ABCMeta): @classmethod def generate(cls) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): raise UnsupportedAlgorithm( "X448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return rust_openssl.x448.generate_key() @classmethod def from_private_bytes(cls, data: bytes) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): raise UnsupportedAlgorithm( "X448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return rust_openssl.x448.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> X448PublicKey: """ Returns the public key associated with this private key """ @abc.abstractmethod def private_bytes( self, encoding: _serialization.Encoding, format: _serialization.PrivateFormat, encryption_algorithm: _serialization.KeySerializationEncryption, ) -> bytes: """ The serialized bytes of the private key. """ @abc.abstractmethod def private_bytes_raw(self) -> bytes: """ The raw bytes of the private key. Equivalent to private_bytes(Raw, Raw, NoEncryption()). """ @abc.abstractmethod def exchange(self, peer_public_key: X448PublicKey) -> bytes: """ Performs a key exchange operation using the provided peer's public key. """ if hasattr(rust_openssl, "x448"): X448PrivateKey.register(rust_openssl.x448.X448PrivateKey) cryptography-43.0.0/src/cryptography/hazmat/primitives/ciphers/__init__.py010064400017510000177000000012501464676315000252760ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.primitives._cipheralgorithm import ( BlockCipherAlgorithm, CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.base import ( AEADCipherContext, AEADDecryptionContext, AEADEncryptionContext, Cipher, CipherContext, ) __all__ = [ "AEADCipherContext", "AEADDecryptionContext", "AEADEncryptionContext", "BlockCipherAlgorithm", "Cipher", "CipherAlgorithm", "CipherContext", ] cryptography-43.0.0/src/cryptography/hazmat/primitives/ciphers/aead.py010064400017510000177000000011721464676315000244340ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.bindings._rust import openssl as rust_openssl __all__ = [ "AESCCM", "AESGCM", "AESGCMSIV", "AESOCB3", "AESSIV", "ChaCha20Poly1305", ] AESGCM = rust_openssl.aead.AESGCM ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305 AESCCM = rust_openssl.aead.AESCCM AESSIV = rust_openssl.aead.AESSIV AESOCB3 = rust_openssl.aead.AESOCB3 AESGCMSIV = rust_openssl.aead.AESGCMSIV cryptography-43.0.0/src/cryptography/hazmat/primitives/ciphers/algorithms.py010064400017510000177000000101771464676315000257200ustar 00000000000000# 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. from __future__ import annotations from cryptography import utils from cryptography.hazmat.decrepit.ciphers.algorithms import ( ARC4 as ARC4, ) from cryptography.hazmat.decrepit.ciphers.algorithms import ( CAST5 as CAST5, ) from cryptography.hazmat.decrepit.ciphers.algorithms import ( IDEA as IDEA, ) from cryptography.hazmat.decrepit.ciphers.algorithms import ( SEED as SEED, ) from cryptography.hazmat.decrepit.ciphers.algorithms import ( Blowfish as Blowfish, ) from cryptography.hazmat.decrepit.ciphers.algorithms import ( TripleDES as TripleDES, ) from cryptography.hazmat.primitives._cipheralgorithm import _verify_key_size from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, CipherAlgorithm, ) class AES(BlockCipherAlgorithm): name = "AES" block_size = 128 # 512 added to support AES-256-XTS, which uses 512-bit keys key_sizes = frozenset([128, 192, 256, 512]) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 class AES128(BlockCipherAlgorithm): name = "AES" block_size = 128 key_sizes = frozenset([128]) key_size = 128 def __init__(self, key: bytes): self.key = _verify_key_size(self, key) class AES256(BlockCipherAlgorithm): name = "AES" block_size = 128 key_sizes = frozenset([256]) key_size = 256 def __init__(self, key: bytes): self.key = _verify_key_size(self, key) class Camellia(BlockCipherAlgorithm): name = "camellia" block_size = 128 key_sizes = frozenset([128, 192, 256]) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 utils.deprecated( ARC4, __name__, "ARC4 has been moved to " "cryptography.hazmat.decrepit.ciphers.algorithms.ARC4 and " "will be removed from this module in 48.0.0.", utils.DeprecatedIn43, name="ARC4", ) utils.deprecated( TripleDES, __name__, "TripleDES has been moved to " "cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and " "will be removed from this module in 48.0.0.", utils.DeprecatedIn43, name="TripleDES", ) utils.deprecated( Blowfish, __name__, "Blowfish has been moved to " "cryptography.hazmat.decrepit.ciphers.algorithms.Blowfish and " "will be removed from this module in 45.0.0.", utils.DeprecatedIn37, name="Blowfish", ) utils.deprecated( CAST5, __name__, "CAST5 has been moved to " "cryptography.hazmat.decrepit.ciphers.algorithms.CAST5 and " "will be removed from this module in 45.0.0.", utils.DeprecatedIn37, name="CAST5", ) utils.deprecated( IDEA, __name__, "IDEA has been moved to " "cryptography.hazmat.decrepit.ciphers.algorithms.IDEA and " "will be removed from this module in 45.0.0.", utils.DeprecatedIn37, name="IDEA", ) utils.deprecated( SEED, __name__, "SEED has been moved to " "cryptography.hazmat.decrepit.ciphers.algorithms.SEED and " "will be removed from this module in 45.0.0.", utils.DeprecatedIn37, name="SEED", ) class ChaCha20(CipherAlgorithm): name = "ChaCha20" key_sizes = frozenset([256]) def __init__(self, key: bytes, nonce: bytes): self.key = _verify_key_size(self, key) utils._check_byteslike("nonce", nonce) if len(nonce) != 16: raise ValueError("nonce must be 128-bits (16 bytes)") self._nonce = nonce @property def nonce(self) -> bytes: return self._nonce @property def key_size(self) -> int: return len(self.key) * 8 class SM4(BlockCipherAlgorithm): name = "SM4" block_size = 128 key_sizes = frozenset([128]) def __init__(self, key: bytes): self.key = _verify_key_size(self, key) @property def key_size(self) -> int: return len(self.key) * 8 cryptography-43.0.0/src/cryptography/hazmat/primitives/ciphers/base.py010064400017510000177000000101631464676315000244540ustar 00000000000000# 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. from __future__ import annotations import abc import typing from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm from cryptography.hazmat.primitives.ciphers import modes class CipherContext(metaclass=abc.ABCMeta): @abc.abstractmethod def update(self, data: bytes) -> bytes: """ Processes the provided bytes through the cipher and returns the results as bytes. """ @abc.abstractmethod def update_into(self, data: bytes, buf: bytes) -> int: """ Processes the provided bytes and writes the resulting data into the provided buffer. Returns the number of bytes written. """ @abc.abstractmethod def finalize(self) -> bytes: """ Returns the results of processing the final block as bytes. """ @abc.abstractmethod def reset_nonce(self, nonce: bytes) -> None: """ Resets the nonce for the cipher context to the provided value. Raises an exception if it does not support reset or if the provided nonce does not have a valid length. """ class AEADCipherContext(CipherContext, metaclass=abc.ABCMeta): @abc.abstractmethod def authenticate_additional_data(self, data: bytes) -> None: """ Authenticates the provided bytes. """ class AEADDecryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): @abc.abstractmethod def finalize_with_tag(self, tag: bytes) -> bytes: """ Returns the results of processing the final block as bytes and allows delayed passing of the authentication tag. """ class AEADEncryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): @property @abc.abstractmethod def tag(self) -> bytes: """ Returns tag bytes. This is only available after encryption is finalized. """ Mode = typing.TypeVar( "Mode", bound=typing.Optional[modes.Mode], covariant=True ) class Cipher(typing.Generic[Mode]): def __init__( self, algorithm: CipherAlgorithm, mode: Mode, backend: typing.Any = None, ) -> None: if not isinstance(algorithm, CipherAlgorithm): raise TypeError("Expected interface of CipherAlgorithm.") if mode is not None: # mypy needs this assert to narrow the type from our generic # type. Maybe it won't some time in the future. assert isinstance(mode, modes.Mode) mode.validate_for_algorithm(algorithm) self.algorithm = algorithm self.mode = mode @typing.overload def encryptor( self: Cipher[modes.ModeWithAuthenticationTag], ) -> AEADEncryptionContext: ... @typing.overload def encryptor( self: _CIPHER_TYPE, ) -> CipherContext: ... def encryptor(self): if isinstance(self.mode, modes.ModeWithAuthenticationTag): if self.mode.tag is not None: raise ValueError( "Authentication tag must be None when encrypting." ) return rust_openssl.ciphers.create_encryption_ctx( self.algorithm, self.mode ) @typing.overload def decryptor( self: Cipher[modes.ModeWithAuthenticationTag], ) -> AEADDecryptionContext: ... @typing.overload def decryptor( self: _CIPHER_TYPE, ) -> CipherContext: ... def decryptor(self): return rust_openssl.ciphers.create_decryption_ctx( self.algorithm, self.mode ) _CIPHER_TYPE = Cipher[ typing.Union[ modes.ModeWithNonce, modes.ModeWithTweak, None, modes.ECB, modes.ModeWithInitializationVector, ] ] CipherContext.register(rust_openssl.ciphers.CipherContext) AEADEncryptionContext.register(rust_openssl.ciphers.AEADEncryptionContext) AEADDecryptionContext.register(rust_openssl.ciphers.AEADDecryptionContext) cryptography-43.0.0/src/cryptography/hazmat/primitives/ciphers/modes.py010064400017510000177000000200001464676315000246400ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.primitives._cipheralgorithm import ( BlockCipherAlgorithm, CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers import algorithms class Mode(metaclass=abc.ABCMeta): @property @abc.abstractmethod def name(self) -> str: """ A string naming this mode (e.g. "ECB", "CBC"). """ @abc.abstractmethod def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: """ Checks that all the necessary invariants of this (mode, algorithm) combination are met. """ class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod def initialization_vector(self) -> bytes: """ The value of the initialization vector for this mode as bytes. """ class ModeWithTweak(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod def tweak(self) -> bytes: """ The value of the tweak for this mode as bytes. """ class ModeWithNonce(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod def nonce(self) -> bytes: """ The value of the nonce for this mode as bytes. """ class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod def tag(self) -> bytes | None: """ The value of the tag supplied to the constructor of this mode. """ def _check_aes_key_length(self: Mode, algorithm: CipherAlgorithm) -> None: if algorithm.key_size > 256 and algorithm.name == "AES": raise ValueError( "Only 128, 192, and 256 bit keys are allowed for this AES mode" ) def _check_iv_length( self: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm ) -> None: iv_len = len(self.initialization_vector) if iv_len * 8 != algorithm.block_size: raise ValueError(f"Invalid IV size ({iv_len}) for {self.name}.") def _check_nonce_length( nonce: bytes, name: str, algorithm: CipherAlgorithm ) -> None: if not isinstance(algorithm, BlockCipherAlgorithm): raise UnsupportedAlgorithm( f"{name} requires a block cipher algorithm", _Reasons.UNSUPPORTED_CIPHER, ) if len(nonce) * 8 != algorithm.block_size: raise ValueError(f"Invalid nonce size ({len(nonce)}) for {name}.") def _check_iv_and_key_length( self: ModeWithInitializationVector, algorithm: CipherAlgorithm ) -> None: if not isinstance(algorithm, BlockCipherAlgorithm): raise UnsupportedAlgorithm( f"{self} requires a block cipher algorithm", _Reasons.UNSUPPORTED_CIPHER, ) _check_aes_key_length(self, algorithm) _check_iv_length(self, algorithm) class CBC(ModeWithInitializationVector): name = "CBC" def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property def initialization_vector(self) -> bytes: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length class XTS(ModeWithTweak): name = "XTS" def __init__(self, tweak: bytes): utils._check_byteslike("tweak", tweak) if len(tweak) != 16: raise ValueError("tweak must be 128-bits (16 bytes)") self._tweak = tweak @property def tweak(self) -> bytes: return self._tweak def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: if isinstance(algorithm, (algorithms.AES128, algorithms.AES256)): raise TypeError( "The AES128 and AES256 classes do not support XTS, please use " "the standard AES class instead." ) if algorithm.key_size not in (256, 512): raise ValueError( "The XTS specification requires a 256-bit key for AES-128-XTS" " and 512-bit key for AES-256-XTS" ) class ECB(Mode): name = "ECB" validate_for_algorithm = _check_aes_key_length class OFB(ModeWithInitializationVector): name = "OFB" def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property def initialization_vector(self) -> bytes: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length class CFB(ModeWithInitializationVector): name = "CFB" def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property def initialization_vector(self) -> bytes: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length class CFB8(ModeWithInitializationVector): name = "CFB8" def __init__(self, initialization_vector: bytes): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property def initialization_vector(self) -> bytes: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length class CTR(ModeWithNonce): name = "CTR" def __init__(self, nonce: bytes): utils._check_byteslike("nonce", nonce) self._nonce = nonce @property def nonce(self) -> bytes: return self._nonce def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: _check_aes_key_length(self, algorithm) _check_nonce_length(self.nonce, self.name, algorithm) class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): name = "GCM" _MAX_ENCRYPTED_BYTES = (2**39 - 256) // 8 _MAX_AAD_BYTES = (2**64) // 8 def __init__( self, initialization_vector: bytes, tag: bytes | None = None, min_tag_length: int = 16, ): # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive # This is a sane limit anyway so we'll enforce it here. utils._check_byteslike("initialization_vector", initialization_vector) if len(initialization_vector) < 8 or len(initialization_vector) > 128: raise ValueError( "initialization_vector must be between 8 and 128 bytes (64 " "and 1024 bits)." ) self._initialization_vector = initialization_vector if tag is not None: utils._check_bytes("tag", tag) if min_tag_length < 4: raise ValueError("min_tag_length must be >= 4") if len(tag) < min_tag_length: raise ValueError( f"Authentication tag must be {min_tag_length} bytes or " "longer." ) self._tag = tag self._min_tag_length = min_tag_length @property def tag(self) -> bytes | None: return self._tag @property def initialization_vector(self) -> bytes: return self._initialization_vector def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: _check_aes_key_length(self, algorithm) if not isinstance(algorithm, BlockCipherAlgorithm): raise UnsupportedAlgorithm( "GCM requires a block cipher algorithm", _Reasons.UNSUPPORTED_CIPHER, ) block_size_bytes = algorithm.block_size // 8 if self._tag is not None and len(self._tag) > block_size_bytes: raise ValueError( f"Authentication tag cannot be more than {block_size_bytes} " "bytes." ) cryptography-43.0.0/src/cryptography/hazmat/primitives/cmac.py010064400017510000177000000005221464676315000230060ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.bindings._rust import openssl as rust_openssl __all__ = ["CMAC"] CMAC = rust_openssl.cmac.CMAC cryptography-43.0.0/src/cryptography/hazmat/primitives/constant_time.py010064400017510000177000000006461464676315000247610ustar 00000000000000# 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. from __future__ import annotations import hmac def bytes_eq(a: bytes, b: bytes) -> bool: if not isinstance(a, bytes) or not isinstance(b, bytes): raise TypeError("a and b must be bytes.") return hmac.compare_digest(a, b) cryptography-43.0.0/src/cryptography/hazmat/primitives/hashes.py010064400017510000177000000117431464676315000233650ustar 00000000000000# 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. from __future__ import annotations import abc from cryptography.hazmat.bindings._rust import openssl as rust_openssl __all__ = [ "MD5", "SHA1", "SHA3_224", "SHA3_256", "SHA3_384", "SHA3_512", "SHA224", "SHA256", "SHA384", "SHA512", "SHA512_224", "SHA512_256", "SHAKE128", "SHAKE256", "SM3", "BLAKE2b", "BLAKE2s", "ExtendableOutputFunction", "Hash", "HashAlgorithm", "HashContext", ] class HashAlgorithm(metaclass=abc.ABCMeta): @property @abc.abstractmethod def name(self) -> str: """ A string naming this algorithm (e.g. "sha256", "md5"). """ @property @abc.abstractmethod def digest_size(self) -> int: """ The size of the resulting digest in bytes. """ @property @abc.abstractmethod def block_size(self) -> int | None: """ The internal block size of the hash function, or None if the hash function does not use blocks internally (e.g. SHA3). """ class HashContext(metaclass=abc.ABCMeta): @property @abc.abstractmethod def algorithm(self) -> HashAlgorithm: """ A HashAlgorithm that will be used by this context. """ @abc.abstractmethod def update(self, data: bytes) -> None: """ Processes the provided bytes through the hash. """ @abc.abstractmethod def finalize(self) -> bytes: """ Finalizes the hash context and returns the hash digest as bytes. """ @abc.abstractmethod def copy(self) -> HashContext: """ Return a HashContext that is a copy of the current context. """ Hash = rust_openssl.hashes.Hash HashContext.register(Hash) class ExtendableOutputFunction(metaclass=abc.ABCMeta): """ An interface for extendable output functions. """ class SHA1(HashAlgorithm): name = "sha1" digest_size = 20 block_size = 64 class SHA512_224(HashAlgorithm): # noqa: N801 name = "sha512-224" digest_size = 28 block_size = 128 class SHA512_256(HashAlgorithm): # noqa: N801 name = "sha512-256" digest_size = 32 block_size = 128 class SHA224(HashAlgorithm): name = "sha224" digest_size = 28 block_size = 64 class SHA256(HashAlgorithm): name = "sha256" digest_size = 32 block_size = 64 class SHA384(HashAlgorithm): name = "sha384" digest_size = 48 block_size = 128 class SHA512(HashAlgorithm): name = "sha512" digest_size = 64 block_size = 128 class SHA3_224(HashAlgorithm): # noqa: N801 name = "sha3-224" digest_size = 28 block_size = None class SHA3_256(HashAlgorithm): # noqa: N801 name = "sha3-256" digest_size = 32 block_size = None class SHA3_384(HashAlgorithm): # noqa: N801 name = "sha3-384" digest_size = 48 block_size = None class SHA3_512(HashAlgorithm): # noqa: N801 name = "sha3-512" digest_size = 64 block_size = None class SHAKE128(HashAlgorithm, ExtendableOutputFunction): name = "shake128" block_size = None def __init__(self, digest_size: int): if not isinstance(digest_size, int): raise TypeError("digest_size must be an integer") if digest_size < 1: raise ValueError("digest_size must be a positive integer") self._digest_size = digest_size @property def digest_size(self) -> int: return self._digest_size class SHAKE256(HashAlgorithm, ExtendableOutputFunction): name = "shake256" block_size = None def __init__(self, digest_size: int): if not isinstance(digest_size, int): raise TypeError("digest_size must be an integer") if digest_size < 1: raise ValueError("digest_size must be a positive integer") self._digest_size = digest_size @property def digest_size(self) -> int: return self._digest_size class MD5(HashAlgorithm): name = "md5" digest_size = 16 block_size = 64 class BLAKE2b(HashAlgorithm): name = "blake2b" _max_digest_size = 64 _min_digest_size = 1 block_size = 128 def __init__(self, digest_size: int): if digest_size != 64: raise ValueError("Digest size must be 64") self._digest_size = digest_size @property def digest_size(self) -> int: return self._digest_size class BLAKE2s(HashAlgorithm): name = "blake2s" block_size = 64 _max_digest_size = 32 _min_digest_size = 1 def __init__(self, digest_size: int): if digest_size != 32: raise ValueError("Digest size must be 32") self._digest_size = digest_size @property def digest_size(self) -> int: return self._digest_size class SM3(HashAlgorithm): name = "sm3" digest_size = 32 block_size = 64 cryptography-43.0.0/src/cryptography/hazmat/primitives/hmac.py010064400017510000177000000006471464676315000230230ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes __all__ = ["HMAC"] HMAC = rust_openssl.hmac.HMAC hashes.HashContext.register(HMAC) cryptography-43.0.0/src/cryptography/hazmat/primitives/kdf/__init__.py010064400017510000177000000013561464676315000244140ustar 00000000000000# 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. from __future__ import annotations import abc class KeyDerivationFunction(metaclass=abc.ABCMeta): @abc.abstractmethod def derive(self, key_material: bytes) -> bytes: """ Deterministically generates and returns a new key based on the existing key material. """ @abc.abstractmethod def verify(self, key_material: bytes, expected_key: bytes) -> None: """ Checks whether the key generated by the key material matches the expected derived key. Raises an exception if they do not match. """ cryptography-43.0.0/src/cryptography/hazmat/primitives/kdf/concatkdf.py010064400017510000177000000071461464676315000246140ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction def _int_to_u32be(n: int) -> bytes: return n.to_bytes(length=4, byteorder="big") def _common_args_checks( algorithm: hashes.HashAlgorithm, length: int, otherinfo: bytes | None, ) -> None: max_length = algorithm.digest_size * (2**32 - 1) if length > max_length: raise ValueError(f"Cannot derive keys larger than {max_length} bits.") if otherinfo is not None: utils._check_bytes("otherinfo", otherinfo) def _concatkdf_derive( key_material: bytes, length: int, auxfn: typing.Callable[[], hashes.HashContext], otherinfo: bytes, ) -> bytes: utils._check_byteslike("key_material", key_material) output = [b""] outlen = 0 counter = 1 while length > outlen: h = auxfn() h.update(_int_to_u32be(counter)) h.update(key_material) h.update(otherinfo) output.append(h.finalize()) outlen += len(output[-1]) counter += 1 return b"".join(output)[:length] class ConcatKDFHash(KeyDerivationFunction): def __init__( self, algorithm: hashes.HashAlgorithm, length: int, otherinfo: bytes | None, backend: typing.Any = None, ): _common_args_checks(algorithm, length, otherinfo) self._algorithm = algorithm self._length = length self._otherinfo: bytes = otherinfo if otherinfo is not None else b"" self._used = False def _hash(self) -> hashes.Hash: return hashes.Hash(self._algorithm) def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized self._used = True return _concatkdf_derive( key_material, self._length, self._hash, self._otherinfo ) def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey class ConcatKDFHMAC(KeyDerivationFunction): def __init__( self, algorithm: hashes.HashAlgorithm, length: int, salt: bytes | None, otherinfo: bytes | None, backend: typing.Any = None, ): _common_args_checks(algorithm, length, otherinfo) self._algorithm = algorithm self._length = length self._otherinfo: bytes = otherinfo if otherinfo is not None else b"" if algorithm.block_size is None: raise TypeError(f"{algorithm.name} is unsupported for ConcatKDF") if salt is None: salt = b"\x00" * algorithm.block_size else: utils._check_bytes("salt", salt) self._salt = salt self._used = False def _hmac(self) -> hmac.HMAC: return hmac.HMAC(self._salt, self._algorithm) def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized self._used = True return _concatkdf_derive( key_material, self._length, self._hmac, self._otherinfo ) def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey cryptography-43.0.0/src/cryptography/hazmat/primitives/kdf/hkdf.py010064400017510000177000000057071464676315000235750ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction class HKDF(KeyDerivationFunction): def __init__( self, algorithm: hashes.HashAlgorithm, length: int, salt: bytes | None, info: bytes | None, backend: typing.Any = None, ): self._algorithm = algorithm if salt is None: salt = b"\x00" * self._algorithm.digest_size else: utils._check_bytes("salt", salt) self._salt = salt self._hkdf_expand = HKDFExpand(self._algorithm, length, info) def _extract(self, key_material: bytes) -> bytes: h = hmac.HMAC(self._salt, self._algorithm) h.update(key_material) return h.finalize() def derive(self, key_material: bytes) -> bytes: utils._check_byteslike("key_material", key_material) return self._hkdf_expand.derive(self._extract(key_material)) def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey class HKDFExpand(KeyDerivationFunction): def __init__( self, algorithm: hashes.HashAlgorithm, length: int, info: bytes | None, backend: typing.Any = None, ): self._algorithm = algorithm max_length = 255 * algorithm.digest_size if length > max_length: raise ValueError( f"Cannot derive keys larger than {max_length} octets." ) self._length = length if info is None: info = b"" else: utils._check_bytes("info", info) self._info = info self._used = False def _expand(self, key_material: bytes) -> bytes: output = [b""] counter = 1 while self._algorithm.digest_size * (len(output) - 1) < self._length: h = hmac.HMAC(key_material, self._algorithm) h.update(output[-1]) h.update(self._info) h.update(bytes([counter])) output.append(h.finalize()) counter += 1 return b"".join(output)[: self._length] def derive(self, key_material: bytes) -> bytes: utils._check_byteslike("key_material", key_material) if self._used: raise AlreadyFinalized self._used = True return self._expand(key_material) def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey cryptography-43.0.0/src/cryptography/hazmat/primitives/kdf/kbkdf.py010064400017510000177000000216721464676315000237410ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons, ) from cryptography.hazmat.primitives import ( ciphers, cmac, constant_time, hashes, hmac, ) from cryptography.hazmat.primitives.kdf import KeyDerivationFunction class Mode(utils.Enum): CounterMode = "ctr" class CounterLocation(utils.Enum): BeforeFixed = "before_fixed" AfterFixed = "after_fixed" MiddleFixed = "middle_fixed" class _KBKDFDeriver: def __init__( self, prf: typing.Callable, mode: Mode, length: int, rlen: int, llen: int | None, location: CounterLocation, break_location: int | None, label: bytes | None, context: bytes | None, fixed: bytes | None, ): assert callable(prf) if not isinstance(mode, Mode): raise TypeError("mode must be of type Mode") if not isinstance(location, CounterLocation): raise TypeError("location must be of type CounterLocation") if break_location is None and location is CounterLocation.MiddleFixed: raise ValueError("Please specify a break_location") if ( break_location is not None and location != CounterLocation.MiddleFixed ): raise ValueError( "break_location is ignored when location is not" " CounterLocation.MiddleFixed" ) if break_location is not None and not isinstance(break_location, int): raise TypeError("break_location must be an integer") if break_location is not None and break_location < 0: raise ValueError("break_location must be a positive integer") if (label or context) and fixed: raise ValueError( "When supplying fixed data, label and context are ignored." ) if rlen is None or not self._valid_byte_length(rlen): raise ValueError("rlen must be between 1 and 4") if llen is None and fixed is None: raise ValueError("Please specify an llen") if llen is not None and not isinstance(llen, int): raise TypeError("llen must be an integer") if llen == 0: raise ValueError("llen must be non-zero") if label is None: label = b"" if context is None: context = b"" utils._check_bytes("label", label) utils._check_bytes("context", context) self._prf = prf self._mode = mode self._length = length self._rlen = rlen self._llen = llen self._location = location self._break_location = break_location self._label = label self._context = context self._used = False self._fixed_data = fixed @staticmethod def _valid_byte_length(value: int) -> bool: if not isinstance(value, int): raise TypeError("value must be of type int") value_bin = utils.int_to_bytes(1, value) if not 1 <= len(value_bin) <= 4: return False return True def derive(self, key_material: bytes, prf_output_size: int) -> bytes: if self._used: raise AlreadyFinalized utils._check_byteslike("key_material", key_material) self._used = True # inverse floor division (equivalent to ceiling) rounds = -(-self._length // prf_output_size) output = [b""] # For counter mode, the number of iterations shall not be # larger than 2^r-1, where r <= 32 is the binary length of the counter # This ensures that the counter values used as an input to the # PRF will not repeat during a particular call to the KDF function. r_bin = utils.int_to_bytes(1, self._rlen) if rounds > pow(2, len(r_bin) * 8) - 1: raise ValueError("There are too many iterations.") fixed = self._generate_fixed_input() if self._location == CounterLocation.BeforeFixed: data_before_ctr = b"" data_after_ctr = fixed elif self._location == CounterLocation.AfterFixed: data_before_ctr = fixed data_after_ctr = b"" else: if isinstance( self._break_location, int ) and self._break_location > len(fixed): raise ValueError("break_location offset > len(fixed)") data_before_ctr = fixed[: self._break_location] data_after_ctr = fixed[self._break_location :] for i in range(1, rounds + 1): h = self._prf(key_material) counter = utils.int_to_bytes(i, self._rlen) input_data = data_before_ctr + counter + data_after_ctr h.update(input_data) output.append(h.finalize()) return b"".join(output)[: self._length] def _generate_fixed_input(self) -> bytes: if self._fixed_data and isinstance(self._fixed_data, bytes): return self._fixed_data l_val = utils.int_to_bytes(self._length * 8, self._llen) return b"".join([self._label, b"\x00", self._context, l_val]) class KBKDFHMAC(KeyDerivationFunction): def __init__( self, algorithm: hashes.HashAlgorithm, mode: Mode, length: int, rlen: int, llen: int | None, location: CounterLocation, label: bytes | None, context: bytes | None, fixed: bytes | None, backend: typing.Any = None, *, break_location: int | None = None, ): if not isinstance(algorithm, hashes.HashAlgorithm): raise UnsupportedAlgorithm( "Algorithm supplied is not a supported hash algorithm.", _Reasons.UNSUPPORTED_HASH, ) from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) if not ossl.hmac_supported(algorithm): raise UnsupportedAlgorithm( "Algorithm supplied is not a supported hmac algorithm.", _Reasons.UNSUPPORTED_HASH, ) self._algorithm = algorithm self._deriver = _KBKDFDeriver( self._prf, mode, length, rlen, llen, location, break_location, label, context, fixed, ) def _prf(self, key_material: bytes) -> hmac.HMAC: return hmac.HMAC(key_material, self._algorithm) def derive(self, key_material: bytes) -> bytes: return self._deriver.derive(key_material, self._algorithm.digest_size) def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey class KBKDFCMAC(KeyDerivationFunction): def __init__( self, algorithm, mode: Mode, length: int, rlen: int, llen: int | None, location: CounterLocation, label: bytes | None, context: bytes | None, fixed: bytes | None, backend: typing.Any = None, *, break_location: int | None = None, ): if not issubclass( algorithm, ciphers.BlockCipherAlgorithm ) or not issubclass(algorithm, ciphers.CipherAlgorithm): raise UnsupportedAlgorithm( "Algorithm supplied is not a supported cipher algorithm.", _Reasons.UNSUPPORTED_CIPHER, ) self._algorithm = algorithm self._cipher: ciphers.BlockCipherAlgorithm | None = None self._deriver = _KBKDFDeriver( self._prf, mode, length, rlen, llen, location, break_location, label, context, fixed, ) def _prf(self, _: bytes) -> cmac.CMAC: assert self._cipher is not None return cmac.CMAC(self._cipher) def derive(self, key_material: bytes) -> bytes: self._cipher = self._algorithm(key_material) assert self._cipher is not None from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) if not ossl.cmac_algorithm_supported(self._cipher): raise UnsupportedAlgorithm( "Algorithm supplied is not a supported cipher algorithm.", _Reasons.UNSUPPORTED_CIPHER, ) return self._deriver.derive(key_material, self._cipher.block_size // 8) def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey cryptography-43.0.0/src/cryptography/hazmat/primitives/kdf/pbkdf2.py010064400017510000177000000036361464676315000240300ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons, ) from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import constant_time, hashes from cryptography.hazmat.primitives.kdf import KeyDerivationFunction class PBKDF2HMAC(KeyDerivationFunction): def __init__( self, algorithm: hashes.HashAlgorithm, length: int, salt: bytes, iterations: int, backend: typing.Any = None, ): from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) if not ossl.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( f"{algorithm.name} is not supported for PBKDF2.", _Reasons.UNSUPPORTED_HASH, ) self._used = False self._algorithm = algorithm self._length = length utils._check_bytes("salt", salt) self._salt = salt self._iterations = iterations def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized("PBKDF2 instances can only be used once.") self._used = True return rust_openssl.kdf.derive_pbkdf2_hmac( key_material, self._algorithm, self._salt, self._iterations, self._length, ) def verify(self, key_material: bytes, expected_key: bytes) -> None: derived_key = self.derive(key_material) if not constant_time.bytes_eq(derived_key, expected_key): raise InvalidKey("Keys do not match.") cryptography-43.0.0/src/cryptography/hazmat/primitives/kdf/scrypt.py010064400017510000177000000044621464676315000242020ustar 00000000000000# 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. from __future__ import annotations import sys import typing from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, ) from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.kdf import KeyDerivationFunction # This is used by the scrypt tests to skip tests that require more memory # than the MEM_LIMIT _MEM_LIMIT = sys.maxsize // 2 class Scrypt(KeyDerivationFunction): def __init__( self, salt: bytes, length: int, n: int, r: int, p: int, backend: typing.Any = None, ): from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) if not ossl.scrypt_supported(): raise UnsupportedAlgorithm( "This version of OpenSSL does not support scrypt" ) self._length = length utils._check_bytes("salt", salt) if n < 2 or (n & (n - 1)) != 0: raise ValueError("n must be greater than 1 and be a power of 2.") if r < 1: raise ValueError("r must be greater than or equal to 1.") if p < 1: raise ValueError("p must be greater than or equal to 1.") self._used = False self._salt = salt self._n = n self._r = r self._p = p def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized("Scrypt instances can only be used once.") self._used = True utils._check_byteslike("key_material", key_material) return rust_openssl.kdf.derive_scrypt( key_material, self._salt, self._n, self._r, self._p, _MEM_LIMIT, self._length, ) def verify(self, key_material: bytes, expected_key: bytes) -> None: derived_key = self.derive(key_material) if not constant_time.bytes_eq(derived_key, expected_key): raise InvalidKey("Keys do not match.") cryptography-43.0.0/src/cryptography/hazmat/primitives/kdf/x963kdf.py010064400017510000177000000037101464676315000240470ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes from cryptography.hazmat.primitives.kdf import KeyDerivationFunction def _int_to_u32be(n: int) -> bytes: return n.to_bytes(length=4, byteorder="big") class X963KDF(KeyDerivationFunction): def __init__( self, algorithm: hashes.HashAlgorithm, length: int, sharedinfo: bytes | None, backend: typing.Any = None, ): max_len = algorithm.digest_size * (2**32 - 1) if length > max_len: raise ValueError(f"Cannot derive keys larger than {max_len} bits.") if sharedinfo is not None: utils._check_bytes("sharedinfo", sharedinfo) self._algorithm = algorithm self._length = length self._sharedinfo = sharedinfo self._used = False def derive(self, key_material: bytes) -> bytes: if self._used: raise AlreadyFinalized self._used = True utils._check_byteslike("key_material", key_material) output = [b""] outlen = 0 counter = 1 while self._length > outlen: h = hashes.Hash(self._algorithm) h.update(key_material) h.update(_int_to_u32be(counter)) if self._sharedinfo is not None: h.update(self._sharedinfo) output.append(h.finalize()) outlen += len(output[-1]) counter += 1 return b"".join(output)[: self._length] def verify(self, key_material: bytes, expected_key: bytes) -> None: if not constant_time.bytes_eq(self.derive(key_material), expected_key): raise InvalidKey cryptography-43.0.0/src/cryptography/hazmat/primitives/keywrap.py010064400017510000177000000130221464676315000235640ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import ECB from cryptography.hazmat.primitives.constant_time import bytes_eq def _wrap_core( wrapping_key: bytes, a: bytes, r: list[bytes], ) -> bytes: # RFC 3394 Key Wrap - 2.2.1 (index method) encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() n = len(r) for j in range(6): for i in range(n): # every encryption operation is a discrete 16 byte chunk (because # AES has a 128-bit block size) and since we're using ECB it is # safe to reuse the encryptor for the entire operation b = encryptor.update(a + r[i]) a = ( int.from_bytes(b[:8], byteorder="big") ^ ((n * j) + i + 1) ).to_bytes(length=8, byteorder="big") r[i] = b[-8:] assert encryptor.finalize() == b"" return a + b"".join(r) def aes_key_wrap( wrapping_key: bytes, key_to_wrap: bytes, backend: typing.Any = None, ) -> bytes: if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") if len(key_to_wrap) < 16: raise ValueError("The key to wrap must be at least 16 bytes") if len(key_to_wrap) % 8 != 0: raise ValueError("The key to wrap must be a multiple of 8 bytes") a = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] return _wrap_core(wrapping_key, a, r) def _unwrap_core( wrapping_key: bytes, a: bytes, r: list[bytes], ) -> tuple[bytes, list[bytes]]: # Implement RFC 3394 Key Unwrap - 2.2.2 (index method) decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() n = len(r) for j in reversed(range(6)): for i in reversed(range(n)): atr = ( int.from_bytes(a, byteorder="big") ^ ((n * j) + i + 1) ).to_bytes(length=8, byteorder="big") + r[i] # every decryption operation is a discrete 16 byte chunk so # it is safe to reuse the decryptor for the entire operation b = decryptor.update(atr) a = b[:8] r[i] = b[-8:] assert decryptor.finalize() == b"" return a, r def aes_key_wrap_with_padding( wrapping_key: bytes, key_to_wrap: bytes, backend: typing.Any = None, ) -> bytes: if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") aiv = b"\xa6\x59\x59\xa6" + len(key_to_wrap).to_bytes( length=4, byteorder="big" ) # pad the key to wrap if necessary pad = (8 - (len(key_to_wrap) % 8)) % 8 key_to_wrap = key_to_wrap + b"\x00" * pad if len(key_to_wrap) == 8: # RFC 5649 - 4.1 - exactly 8 octets after padding encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() b = encryptor.update(aiv + key_to_wrap) assert encryptor.finalize() == b"" return b else: r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] return _wrap_core(wrapping_key, aiv, r) def aes_key_unwrap_with_padding( wrapping_key: bytes, wrapped_key: bytes, backend: typing.Any = None, ) -> bytes: if len(wrapped_key) < 16: raise InvalidUnwrap("Must be at least 16 bytes") if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") if len(wrapped_key) == 16: # RFC 5649 - 4.2 - exactly two 64-bit blocks decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() out = decryptor.update(wrapped_key) assert decryptor.finalize() == b"" a = out[:8] data = out[8:] n = 1 else: r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] encrypted_aiv = r.pop(0) n = len(r) a, r = _unwrap_core(wrapping_key, encrypted_aiv, r) data = b"".join(r) # 1) Check that MSB(32,A) = A65959A6. # 2) Check that 8*(n-1) < LSB(32,A) <= 8*n. If so, let # MLI = LSB(32,A). # 3) Let b = (8*n)-MLI, and then check that the rightmost b octets of # the output data are zero. mli = int.from_bytes(a[4:], byteorder="big") b = (8 * n) - mli if ( not bytes_eq(a[:4], b"\xa6\x59\x59\xa6") or not 8 * (n - 1) < mli <= 8 * n or (b != 0 and not bytes_eq(data[-b:], b"\x00" * b)) ): raise InvalidUnwrap() if b == 0: return data else: return data[:-b] def aes_key_unwrap( wrapping_key: bytes, wrapped_key: bytes, backend: typing.Any = None, ) -> bytes: if len(wrapped_key) < 24: raise InvalidUnwrap("Must be at least 24 bytes") if len(wrapped_key) % 8 != 0: raise InvalidUnwrap("The wrapped key must be a multiple of 8 bytes") if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] a = r.pop(0) a, r = _unwrap_core(wrapping_key, a, r) if not bytes_eq(a, aiv): raise InvalidUnwrap() return b"".join(r) class InvalidUnwrap(Exception): pass cryptography-43.0.0/src/cryptography/hazmat/primitives/padding.py010064400017510000177000000126161464676315000235200ustar 00000000000000# 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. from __future__ import annotations import abc import typing from cryptography import utils from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.bindings._rust import ( PKCS7PaddingContext, check_ansix923_padding, check_pkcs7_padding, ) class PaddingContext(metaclass=abc.ABCMeta): @abc.abstractmethod def update(self, data: bytes) -> bytes: """ Pads the provided bytes and returns any available data as bytes. """ @abc.abstractmethod def finalize(self) -> bytes: """ Finalize the padding, returns bytes. """ def _byte_padding_check(block_size: int) -> None: if not (0 <= block_size <= 2040): raise ValueError("block_size must be in range(0, 2041).") if block_size % 8 != 0: raise ValueError("block_size must be a multiple of 8.") def _byte_padding_update( buffer_: bytes | None, data: bytes, block_size: int ) -> tuple[bytes, bytes]: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") utils._check_byteslike("data", data) buffer_ += bytes(data) finished_blocks = len(buffer_) // (block_size // 8) result = buffer_[: finished_blocks * (block_size // 8)] buffer_ = buffer_[finished_blocks * (block_size // 8) :] return buffer_, result def _byte_padding_pad( buffer_: bytes | None, block_size: int, paddingfn: typing.Callable[[int], bytes], ) -> bytes: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") pad_size = block_size // 8 - len(buffer_) return buffer_ + paddingfn(pad_size) def _byte_unpadding_update( buffer_: bytes | None, data: bytes, block_size: int ) -> tuple[bytes, bytes]: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") utils._check_byteslike("data", data) buffer_ += bytes(data) finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0) result = buffer_[: finished_blocks * (block_size // 8)] buffer_ = buffer_[finished_blocks * (block_size // 8) :] return buffer_, result def _byte_unpadding_check( buffer_: bytes | None, block_size: int, checkfn: typing.Callable[[bytes], int], ) -> bytes: if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") if len(buffer_) != block_size // 8: raise ValueError("Invalid padding bytes.") valid = checkfn(buffer_) if not valid: raise ValueError("Invalid padding bytes.") pad_size = buffer_[-1] return buffer_[:-pad_size] class PKCS7: def __init__(self, block_size: int): _byte_padding_check(block_size) self.block_size = block_size def padder(self) -> PaddingContext: return PKCS7PaddingContext(self.block_size) def unpadder(self) -> PaddingContext: return _PKCS7UnpaddingContext(self.block_size) class _PKCS7UnpaddingContext(PaddingContext): _buffer: bytes | None def __init__(self, block_size: int): self.block_size = block_size # TODO: more copies than necessary, we should use zero-buffer (#193) self._buffer = b"" def update(self, data: bytes) -> bytes: self._buffer, result = _byte_unpadding_update( self._buffer, data, self.block_size ) return result def finalize(self) -> bytes: result = _byte_unpadding_check( self._buffer, self.block_size, check_pkcs7_padding ) self._buffer = None return result PaddingContext.register(PKCS7PaddingContext) class ANSIX923: def __init__(self, block_size: int): _byte_padding_check(block_size) self.block_size = block_size def padder(self) -> PaddingContext: return _ANSIX923PaddingContext(self.block_size) def unpadder(self) -> PaddingContext: return _ANSIX923UnpaddingContext(self.block_size) class _ANSIX923PaddingContext(PaddingContext): _buffer: bytes | None def __init__(self, block_size: int): self.block_size = block_size # TODO: more copies than necessary, we should use zero-buffer (#193) self._buffer = b"" def update(self, data: bytes) -> bytes: self._buffer, result = _byte_padding_update( self._buffer, data, self.block_size ) return result def _padding(self, size: int) -> bytes: return bytes([0]) * (size - 1) + bytes([size]) def finalize(self) -> bytes: result = _byte_padding_pad( self._buffer, self.block_size, self._padding ) self._buffer = None return result class _ANSIX923UnpaddingContext(PaddingContext): _buffer: bytes | None def __init__(self, block_size: int): self.block_size = block_size # TODO: more copies than necessary, we should use zero-buffer (#193) self._buffer = b"" def update(self, data: bytes) -> bytes: self._buffer, result = _byte_unpadding_update( self._buffer, data, self.block_size ) return result def finalize(self) -> bytes: result = _byte_unpadding_check( self._buffer, self.block_size, check_ansix923_padding, ) self._buffer = None return result cryptography-43.0.0/src/cryptography/hazmat/primitives/poly1305.py010064400017510000177000000005431464676315000234020ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.bindings._rust import openssl as rust_openssl __all__ = ["Poly1305"] Poly1305 = rust_openssl.poly1305.Poly1305 cryptography-43.0.0/src/cryptography/hazmat/primitives/serialization/__init__.py010064400017510000177000000031651464676315000265250ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat.primitives._serialization import ( BestAvailableEncryption, Encoding, KeySerializationEncryption, NoEncryption, ParameterFormat, PrivateFormat, PublicFormat, _KeySerializationEncryption, ) from cryptography.hazmat.primitives.serialization.base import ( load_der_parameters, load_der_private_key, load_der_public_key, load_pem_parameters, load_pem_private_key, load_pem_public_key, ) from cryptography.hazmat.primitives.serialization.ssh import ( SSHCertificate, SSHCertificateBuilder, SSHCertificateType, SSHCertPrivateKeyTypes, SSHCertPublicKeyTypes, SSHPrivateKeyTypes, SSHPublicKeyTypes, load_ssh_private_key, load_ssh_public_identity, load_ssh_public_key, ) __all__ = [ "BestAvailableEncryption", "Encoding", "KeySerializationEncryption", "NoEncryption", "ParameterFormat", "PrivateFormat", "PublicFormat", "SSHCertPrivateKeyTypes", "SSHCertPublicKeyTypes", "SSHCertificate", "SSHCertificateBuilder", "SSHCertificateType", "SSHPrivateKeyTypes", "SSHPublicKeyTypes", "_KeySerializationEncryption", "load_der_parameters", "load_der_private_key", "load_der_public_key", "load_pem_parameters", "load_pem_private_key", "load_pem_public_key", "load_ssh_private_key", "load_ssh_public_identity", "load_ssh_public_key", ] cryptography-43.0.0/src/cryptography/hazmat/primitives/serialization/base.py010064400017510000177000000011471464676315000256760ustar 00000000000000# 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. from cryptography.hazmat.bindings._rust import openssl as rust_openssl load_pem_private_key = rust_openssl.keys.load_pem_private_key load_der_private_key = rust_openssl.keys.load_der_private_key load_pem_public_key = rust_openssl.keys.load_pem_public_key load_der_public_key = rust_openssl.keys.load_der_public_key load_pem_parameters = rust_openssl.dh.from_pem_parameters load_der_parameters = rust_openssl.dh.from_der_parameters cryptography-43.0.0/src/cryptography/hazmat/primitives/serialization/pkcs12.py010064400017510000177000000106141464676315000260660ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography import x509 from cryptography.hazmat.bindings._rust import pkcs12 as rust_pkcs12 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives._serialization import PBES as PBES from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed448, ed25519, rsa, ) from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes __all__ = [ "PBES", "PKCS12Certificate", "PKCS12KeyAndCertificates", "PKCS12PrivateKeyTypes", "load_key_and_certificates", "load_pkcs12", "serialize_key_and_certificates", ] PKCS12PrivateKeyTypes = typing.Union[ rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey, ] PKCS12Certificate = rust_pkcs12.PKCS12Certificate class PKCS12KeyAndCertificates: def __init__( self, key: PrivateKeyTypes | None, cert: PKCS12Certificate | None, additional_certs: list[PKCS12Certificate], ): if key is not None and not isinstance( key, ( rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey, ), ): raise TypeError( "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" " private key, or None." ) if cert is not None and not isinstance(cert, PKCS12Certificate): raise TypeError("cert must be a PKCS12Certificate object or None") if not all( isinstance(add_cert, PKCS12Certificate) for add_cert in additional_certs ): raise TypeError( "all values in additional_certs must be PKCS12Certificate" " objects" ) self._key = key self._cert = cert self._additional_certs = additional_certs @property def key(self) -> PrivateKeyTypes | None: return self._key @property def cert(self) -> PKCS12Certificate | None: return self._cert @property def additional_certs(self) -> list[PKCS12Certificate]: return self._additional_certs def __eq__(self, other: object) -> bool: if not isinstance(other, PKCS12KeyAndCertificates): return NotImplemented return ( self.key == other.key and self.cert == other.cert and self.additional_certs == other.additional_certs ) def __hash__(self) -> int: return hash((self.key, self.cert, tuple(self.additional_certs))) def __repr__(self) -> str: fmt = ( "" ) return fmt.format(self.key, self.cert, self.additional_certs) load_key_and_certificates = rust_pkcs12.load_key_and_certificates load_pkcs12 = rust_pkcs12.load_pkcs12 _PKCS12CATypes = typing.Union[ x509.Certificate, PKCS12Certificate, ] def serialize_key_and_certificates( name: bytes | None, key: PKCS12PrivateKeyTypes | None, cert: x509.Certificate | None, cas: typing.Iterable[_PKCS12CATypes] | None, encryption_algorithm: serialization.KeySerializationEncryption, ) -> bytes: if key is not None and not isinstance( key, ( rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey, ), ): raise TypeError( "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" " private key, or None." ) if not isinstance( encryption_algorithm, serialization.KeySerializationEncryption ): raise TypeError( "Key encryption algorithm must be a " "KeySerializationEncryption instance" ) if key is None and cert is None and not cas: raise ValueError("You must supply at least one of key, cert, or cas") return rust_pkcs12.serialize_key_and_certificates( name, key, cert, cas, encryption_algorithm ) cryptography-43.0.0/src/cryptography/hazmat/primitives/serialization/pkcs7.py010064400017510000177000000256051464676315000260200ustar 00000000000000# 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. from __future__ import annotations import email.base64mime import email.generator import email.message import email.policy import io import typing from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa from cryptography.utils import _check_byteslike load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates serialize_certificates = rust_pkcs7.serialize_certificates PKCS7HashTypes = typing.Union[ hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, ] PKCS7PrivateKeyTypes = typing.Union[ rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey ] class PKCS7Options(utils.Enum): Text = "Add text/plain MIME type" Binary = "Don't translate input data into canonical MIME format" DetachedSignature = "Don't embed data in the PKCS7 structure" NoCapabilities = "Don't embed SMIME capabilities" NoAttributes = "Don't embed authenticatedAttributes" NoCerts = "Don't embed signer certificate" class PKCS7SignatureBuilder: def __init__( self, data: bytes | None = None, signers: list[ tuple[ x509.Certificate, PKCS7PrivateKeyTypes, PKCS7HashTypes, padding.PSS | padding.PKCS1v15 | None, ] ] = [], additional_certs: list[x509.Certificate] = [], ): self._data = data self._signers = signers self._additional_certs = additional_certs def set_data(self, data: bytes) -> PKCS7SignatureBuilder: _check_byteslike("data", data) if self._data is not None: raise ValueError("data may only be set once") return PKCS7SignatureBuilder(data, self._signers) def add_signer( self, certificate: x509.Certificate, private_key: PKCS7PrivateKeyTypes, hash_algorithm: PKCS7HashTypes, *, rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> PKCS7SignatureBuilder: if not isinstance( hash_algorithm, ( hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, ), ): raise TypeError( "hash_algorithm must be one of hashes.SHA224, " "SHA256, SHA384, or SHA512" ) if not isinstance(certificate, x509.Certificate): raise TypeError("certificate must be a x509.Certificate") if not isinstance( private_key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey) ): raise TypeError("Only RSA & EC keys are supported at this time.") if rsa_padding is not None: if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): raise TypeError("Padding must be PSS or PKCS1v15") if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") return PKCS7SignatureBuilder( self._data, [ *self._signers, (certificate, private_key, hash_algorithm, rsa_padding), ], ) def add_certificate( self, certificate: x509.Certificate ) -> PKCS7SignatureBuilder: if not isinstance(certificate, x509.Certificate): raise TypeError("certificate must be a x509.Certificate") return PKCS7SignatureBuilder( self._data, self._signers, [*self._additional_certs, certificate] ) def sign( self, encoding: serialization.Encoding, options: typing.Iterable[PKCS7Options], backend: typing.Any = None, ) -> bytes: if len(self._signers) == 0: raise ValueError("Must have at least one signer") if self._data is None: raise ValueError("You must add data to sign") options = list(options) if not all(isinstance(x, PKCS7Options) for x in options): raise ValueError("options must be from the PKCS7Options enum") if encoding not in ( serialization.Encoding.PEM, serialization.Encoding.DER, serialization.Encoding.SMIME, ): raise ValueError( "Must be PEM, DER, or SMIME from the Encoding enum" ) # Text is a meaningless option unless it is accompanied by # DetachedSignature if ( PKCS7Options.Text in options and PKCS7Options.DetachedSignature not in options ): raise ValueError( "When passing the Text option you must also pass " "DetachedSignature" ) if PKCS7Options.Text in options and encoding in ( serialization.Encoding.DER, serialization.Encoding.PEM, ): raise ValueError( "The Text option is only available for SMIME serialization" ) # No attributes implies no capabilities so we'll error if you try to # pass both. if ( PKCS7Options.NoAttributes in options and PKCS7Options.NoCapabilities in options ): raise ValueError( "NoAttributes is a superset of NoCapabilities. Do not pass " "both values." ) return rust_pkcs7.sign_and_serialize(self, encoding, options) class PKCS7EnvelopeBuilder: def __init__( self, *, _data: bytes | None = None, _recipients: list[x509.Certificate] | None = None, ): from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) if not ossl.rsa_encryption_supported(padding=padding.PKCS1v15()): raise UnsupportedAlgorithm( "RSA with PKCS1 v1.5 padding is not supported by this version" " of OpenSSL.", _Reasons.UNSUPPORTED_PADDING, ) self._data = _data self._recipients = _recipients if _recipients is not None else [] def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder: _check_byteslike("data", data) if self._data is not None: raise ValueError("data may only be set once") return PKCS7EnvelopeBuilder(_data=data, _recipients=self._recipients) def add_recipient( self, certificate: x509.Certificate, ) -> PKCS7EnvelopeBuilder: if not isinstance(certificate, x509.Certificate): raise TypeError("certificate must be a x509.Certificate") if not isinstance(certificate.public_key(), rsa.RSAPublicKey): raise TypeError("Only RSA keys are supported at this time.") return PKCS7EnvelopeBuilder( _data=self._data, _recipients=[ *self._recipients, certificate, ], ) def encrypt( self, encoding: serialization.Encoding, options: typing.Iterable[PKCS7Options], ) -> bytes: if len(self._recipients) == 0: raise ValueError("Must have at least one recipient") if self._data is None: raise ValueError("You must add data to encrypt") options = list(options) if not all(isinstance(x, PKCS7Options) for x in options): raise ValueError("options must be from the PKCS7Options enum") if encoding not in ( serialization.Encoding.PEM, serialization.Encoding.DER, serialization.Encoding.SMIME, ): raise ValueError( "Must be PEM, DER, or SMIME from the Encoding enum" ) # Only allow options that make sense for encryption if any( opt not in [PKCS7Options.Text, PKCS7Options.Binary] for opt in options ): raise ValueError( "Only the following options are supported for encryption: " "Text, Binary" ) elif PKCS7Options.Text in options and PKCS7Options.Binary in options: # OpenSSL accepts both options at the same time, but ignores Text. # We fail defensively to avoid unexpected outputs. raise ValueError( "Cannot use Binary and Text options at the same time" ) return rust_pkcs7.encrypt_and_serialize(self, encoding, options) def _smime_signed_encode( data: bytes, signature: bytes, micalg: str, text_mode: bool ) -> bytes: # This function works pretty hard to replicate what OpenSSL does # precisely. For good and for ill. m = email.message.Message() m.add_header("MIME-Version", "1.0") m.add_header( "Content-Type", "multipart/signed", protocol="application/x-pkcs7-signature", micalg=micalg, ) m.preamble = "This is an S/MIME signed message\n" msg_part = OpenSSLMimePart() msg_part.set_payload(data) if text_mode: msg_part.add_header("Content-Type", "text/plain") m.attach(msg_part) sig_part = email.message.MIMEPart() sig_part.add_header( "Content-Type", "application/x-pkcs7-signature", name="smime.p7s" ) sig_part.add_header("Content-Transfer-Encoding", "base64") sig_part.add_header( "Content-Disposition", "attachment", filename="smime.p7s" ) sig_part.set_payload( email.base64mime.body_encode(signature, maxlinelen=65) ) del sig_part["MIME-Version"] m.attach(sig_part) fp = io.BytesIO() g = email.generator.BytesGenerator( fp, maxheaderlen=0, mangle_from_=False, policy=m.policy.clone(linesep="\r\n"), ) g.flatten(m) return fp.getvalue() def _smime_enveloped_encode(data: bytes) -> bytes: m = email.message.Message() m.add_header("MIME-Version", "1.0") m.add_header("Content-Disposition", "attachment", filename="smime.p7m") m.add_header( "Content-Type", "application/pkcs7-mime", smime_type="enveloped-data", name="smime.p7m", ) m.add_header("Content-Transfer-Encoding", "base64") m.set_payload(email.base64mime.body_encode(data, maxlinelen=65)) return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0)) class OpenSSLMimePart(email.message.MIMEPart): # A MIMEPart subclass that replicates OpenSSL's behavior of not including # a newline if there are no headers. def _write_headers(self, generator) -> None: if list(self.raw_items()): generator._write_headers(self) cryptography-43.0.0/src/cryptography/hazmat/primitives/serialization/ssh.py010064400017510000177000001453131464676315000255650ustar 00000000000000# 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. from __future__ import annotations import binascii import enum import os import re import typing import warnings from base64 import encodebytes as _base64_encode from dataclasses import dataclass from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed25519, padding, rsa, ) from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.ciphers import ( AEADDecryptionContext, Cipher, algorithms, modes, ) from cryptography.hazmat.primitives.serialization import ( Encoding, KeySerializationEncryption, NoEncryption, PrivateFormat, PublicFormat, _KeySerializationEncryption, ) try: from bcrypt import kdf as _bcrypt_kdf _bcrypt_supported = True except ImportError: _bcrypt_supported = False def _bcrypt_kdf( password: bytes, salt: bytes, desired_key_bytes: int, rounds: int, ignore_few_rounds: bool = False, ) -> bytes: raise UnsupportedAlgorithm("Need bcrypt module") _SSH_ED25519 = b"ssh-ed25519" _SSH_RSA = b"ssh-rsa" _SSH_DSA = b"ssh-dss" _ECDSA_NISTP256 = b"ecdsa-sha2-nistp256" _ECDSA_NISTP384 = b"ecdsa-sha2-nistp384" _ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" _CERT_SUFFIX = b"-cert-v01@openssh.com" # U2F application string suffixed pubkey _SK_SSH_ED25519 = b"sk-ssh-ed25519@openssh.com" _SK_SSH_ECDSA_NISTP256 = b"sk-ecdsa-sha2-nistp256@openssh.com" # These are not key types, only algorithms, so they cannot appear # as a public key type _SSH_RSA_SHA256 = b"rsa-sha2-256" _SSH_RSA_SHA512 = b"rsa-sha2-512" _SSH_PUBKEY_RC = re.compile(rb"\A(\S+)[ \t]+(\S+)") _SK_MAGIC = b"openssh-key-v1\0" _SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----" _SK_END = b"-----END OPENSSH PRIVATE KEY-----" _BCRYPT = b"bcrypt" _NONE = b"none" _DEFAULT_CIPHER = b"aes256-ctr" _DEFAULT_ROUNDS = 16 # re is only way to work on bytes-like data _PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL) # padding for max blocksize _PADDING = memoryview(bytearray(range(1, 1 + 16))) @dataclass class _SSHCipher: alg: type[algorithms.AES] key_len: int mode: type[modes.CTR] | type[modes.CBC] | type[modes.GCM] block_len: int iv_len: int tag_len: int | None is_aead: bool # ciphers that are actually used in key wrapping _SSH_CIPHERS: dict[bytes, _SSHCipher] = { b"aes256-ctr": _SSHCipher( alg=algorithms.AES, key_len=32, mode=modes.CTR, block_len=16, iv_len=16, tag_len=None, is_aead=False, ), b"aes256-cbc": _SSHCipher( alg=algorithms.AES, key_len=32, mode=modes.CBC, block_len=16, iv_len=16, tag_len=None, is_aead=False, ), b"aes256-gcm@openssh.com": _SSHCipher( alg=algorithms.AES, key_len=32, mode=modes.GCM, block_len=16, iv_len=12, tag_len=16, is_aead=True, ), } # map local curve name to key type _ECDSA_KEY_TYPE = { "secp256r1": _ECDSA_NISTP256, "secp384r1": _ECDSA_NISTP384, "secp521r1": _ECDSA_NISTP521, } def _get_ssh_key_type(key: SSHPrivateKeyTypes | SSHPublicKeyTypes) -> bytes: if isinstance(key, ec.EllipticCurvePrivateKey): key_type = _ecdsa_key_type(key.public_key()) elif isinstance(key, ec.EllipticCurvePublicKey): key_type = _ecdsa_key_type(key) elif isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): key_type = _SSH_RSA elif isinstance(key, (dsa.DSAPrivateKey, dsa.DSAPublicKey)): key_type = _SSH_DSA elif isinstance( key, (ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey) ): key_type = _SSH_ED25519 else: raise ValueError("Unsupported key type") return key_type def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes: """Return SSH key_type and curve_name for private key.""" curve = public_key.curve if curve.name not in _ECDSA_KEY_TYPE: raise ValueError( f"Unsupported curve for ssh private key: {curve.name!r}" ) return _ECDSA_KEY_TYPE[curve.name] def _ssh_pem_encode( data: bytes, prefix: bytes = _SK_START + b"\n", suffix: bytes = _SK_END + b"\n", ) -> bytes: return b"".join([prefix, _base64_encode(data), suffix]) def _check_block_size(data: bytes, block_len: int) -> None: """Require data to be full blocks""" if not data or len(data) % block_len != 0: raise ValueError("Corrupt data: missing padding") def _check_empty(data: bytes) -> None: """All data should have been parsed.""" if data: raise ValueError("Corrupt data: unparsed data") def _init_cipher( ciphername: bytes, password: bytes | None, salt: bytes, rounds: int, ) -> Cipher[modes.CBC | modes.CTR | modes.GCM]: """Generate key + iv and return cipher.""" if not password: raise ValueError("Key is password-protected.") ciph = _SSH_CIPHERS[ciphername] seed = _bcrypt_kdf( password, salt, ciph.key_len + ciph.iv_len, rounds, True ) return Cipher( ciph.alg(seed[: ciph.key_len]), ciph.mode(seed[ciph.key_len :]), ) def _get_u32(data: memoryview) -> tuple[int, memoryview]: """Uint32""" if len(data) < 4: raise ValueError("Invalid data") return int.from_bytes(data[:4], byteorder="big"), data[4:] def _get_u64(data: memoryview) -> tuple[int, memoryview]: """Uint64""" if len(data) < 8: raise ValueError("Invalid data") return int.from_bytes(data[:8], byteorder="big"), data[8:] def _get_sshstr(data: memoryview) -> tuple[memoryview, memoryview]: """Bytes with u32 length prefix""" n, data = _get_u32(data) if n > len(data): raise ValueError("Invalid data") return data[:n], data[n:] def _get_mpint(data: memoryview) -> tuple[int, memoryview]: """Big integer.""" val, data = _get_sshstr(data) if val and val[0] > 0x7F: raise ValueError("Invalid data") return int.from_bytes(val, "big"), data def _to_mpint(val: int) -> bytes: """Storage format for signed bigint.""" if val < 0: raise ValueError("negative mpint not allowed") if not val: return b"" nbytes = (val.bit_length() + 8) // 8 return utils.int_to_bytes(val, nbytes) class _FragList: """Build recursive structure without data copy.""" flist: list[bytes] def __init__(self, init: list[bytes] | None = None) -> None: self.flist = [] if init: self.flist.extend(init) def put_raw(self, val: bytes) -> None: """Add plain bytes""" self.flist.append(val) def put_u32(self, val: int) -> None: """Big-endian uint32""" self.flist.append(val.to_bytes(length=4, byteorder="big")) def put_u64(self, val: int) -> None: """Big-endian uint64""" self.flist.append(val.to_bytes(length=8, byteorder="big")) def put_sshstr(self, val: bytes | _FragList) -> None: """Bytes prefixed with u32 length""" if isinstance(val, (bytes, memoryview, bytearray)): self.put_u32(len(val)) self.flist.append(val) else: self.put_u32(val.size()) self.flist.extend(val.flist) def put_mpint(self, val: int) -> None: """Big-endian bigint prefixed with u32 length""" self.put_sshstr(_to_mpint(val)) def size(self) -> int: """Current number of bytes""" return sum(map(len, self.flist)) def render(self, dstbuf: memoryview, pos: int = 0) -> int: """Write into bytearray""" for frag in self.flist: flen = len(frag) start, pos = pos, pos + flen dstbuf[start:pos] = frag return pos def tobytes(self) -> bytes: """Return as bytes""" buf = memoryview(bytearray(self.size())) self.render(buf) return buf.tobytes() class _SSHFormatRSA: """Format for RSA keys. Public: mpint e, n Private: mpint n, e, d, iqmp, p, q """ def get_public( self, data: memoryview ) -> tuple[tuple[int, int], memoryview]: """RSA public fields""" e, data = _get_mpint(data) n, data = _get_mpint(data) return (e, n), data def load_public( self, data: memoryview ) -> tuple[rsa.RSAPublicKey, memoryview]: """Make RSA public key from data.""" (e, n), data = self.get_public(data) public_numbers = rsa.RSAPublicNumbers(e, n) public_key = public_numbers.public_key() return public_key, data def load_private( self, data: memoryview, pubfields ) -> tuple[rsa.RSAPrivateKey, memoryview]: """Make RSA private key from data.""" n, data = _get_mpint(data) e, data = _get_mpint(data) d, data = _get_mpint(data) iqmp, data = _get_mpint(data) p, data = _get_mpint(data) q, data = _get_mpint(data) if (e, n) != pubfields: raise ValueError("Corrupt data: rsa field mismatch") dmp1 = rsa.rsa_crt_dmp1(d, p) dmq1 = rsa.rsa_crt_dmq1(d, q) public_numbers = rsa.RSAPublicNumbers(e, n) private_numbers = rsa.RSAPrivateNumbers( p, q, d, dmp1, dmq1, iqmp, public_numbers ) private_key = private_numbers.private_key() return private_key, data def encode_public( self, public_key: rsa.RSAPublicKey, f_pub: _FragList ) -> None: """Write RSA public key""" pubn = public_key.public_numbers() f_pub.put_mpint(pubn.e) f_pub.put_mpint(pubn.n) def encode_private( self, private_key: rsa.RSAPrivateKey, f_priv: _FragList ) -> None: """Write RSA private key""" private_numbers = private_key.private_numbers() public_numbers = private_numbers.public_numbers f_priv.put_mpint(public_numbers.n) f_priv.put_mpint(public_numbers.e) f_priv.put_mpint(private_numbers.d) f_priv.put_mpint(private_numbers.iqmp) f_priv.put_mpint(private_numbers.p) f_priv.put_mpint(private_numbers.q) class _SSHFormatDSA: """Format for DSA keys. Public: mpint p, q, g, y Private: mpint p, q, g, y, x """ def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: """DSA public fields""" p, data = _get_mpint(data) q, data = _get_mpint(data) g, data = _get_mpint(data) y, data = _get_mpint(data) return (p, q, g, y), data def load_public( self, data: memoryview ) -> tuple[dsa.DSAPublicKey, memoryview]: """Make DSA public key from data.""" (p, q, g, y), data = self.get_public(data) parameter_numbers = dsa.DSAParameterNumbers(p, q, g) public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) self._validate(public_numbers) public_key = public_numbers.public_key() return public_key, data def load_private( self, data: memoryview, pubfields ) -> tuple[dsa.DSAPrivateKey, memoryview]: """Make DSA private key from data.""" (p, q, g, y), data = self.get_public(data) x, data = _get_mpint(data) if (p, q, g, y) != pubfields: raise ValueError("Corrupt data: dsa field mismatch") parameter_numbers = dsa.DSAParameterNumbers(p, q, g) public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) self._validate(public_numbers) private_numbers = dsa.DSAPrivateNumbers(x, public_numbers) private_key = private_numbers.private_key() return private_key, data def encode_public( self, public_key: dsa.DSAPublicKey, f_pub: _FragList ) -> None: """Write DSA public key""" public_numbers = public_key.public_numbers() parameter_numbers = public_numbers.parameter_numbers self._validate(public_numbers) f_pub.put_mpint(parameter_numbers.p) f_pub.put_mpint(parameter_numbers.q) f_pub.put_mpint(parameter_numbers.g) f_pub.put_mpint(public_numbers.y) def encode_private( self, private_key: dsa.DSAPrivateKey, f_priv: _FragList ) -> None: """Write DSA private key""" self.encode_public(private_key.public_key(), f_priv) f_priv.put_mpint(private_key.private_numbers().x) def _validate(self, public_numbers: dsa.DSAPublicNumbers) -> None: parameter_numbers = public_numbers.parameter_numbers if parameter_numbers.p.bit_length() != 1024: raise ValueError("SSH supports only 1024 bit DSA keys") class _SSHFormatECDSA: """Format for ECDSA keys. Public: str curve bytes point Private: str curve bytes point mpint secret """ def __init__(self, ssh_curve_name: bytes, curve: ec.EllipticCurve): self.ssh_curve_name = ssh_curve_name self.curve = curve def get_public( self, data: memoryview ) -> tuple[tuple[memoryview, memoryview], memoryview]: """ECDSA public fields""" curve, data = _get_sshstr(data) point, data = _get_sshstr(data) if curve != self.ssh_curve_name: raise ValueError("Curve name mismatch") if point[0] != 4: raise NotImplementedError("Need uncompressed point") return (curve, point), data def load_public( self, data: memoryview ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: """Make ECDSA public key from data.""" (_, point), data = self.get_public(data) public_key = ec.EllipticCurvePublicKey.from_encoded_point( self.curve, point.tobytes() ) return public_key, data def load_private( self, data: memoryview, pubfields ) -> tuple[ec.EllipticCurvePrivateKey, memoryview]: """Make ECDSA private key from data.""" (curve_name, point), data = self.get_public(data) secret, data = _get_mpint(data) if (curve_name, point) != pubfields: raise ValueError("Corrupt data: ecdsa field mismatch") private_key = ec.derive_private_key(secret, self.curve) return private_key, data def encode_public( self, public_key: ec.EllipticCurvePublicKey, f_pub: _FragList ) -> None: """Write ECDSA public key""" point = public_key.public_bytes( Encoding.X962, PublicFormat.UncompressedPoint ) f_pub.put_sshstr(self.ssh_curve_name) f_pub.put_sshstr(point) def encode_private( self, private_key: ec.EllipticCurvePrivateKey, f_priv: _FragList ) -> None: """Write ECDSA private key""" public_key = private_key.public_key() private_numbers = private_key.private_numbers() self.encode_public(public_key, f_priv) f_priv.put_mpint(private_numbers.private_value) class _SSHFormatEd25519: """Format for Ed25519 keys. Public: bytes point Private: bytes point bytes secret_and_point """ def get_public( self, data: memoryview ) -> tuple[tuple[memoryview], memoryview]: """Ed25519 public fields""" point, data = _get_sshstr(data) return (point,), data def load_public( self, data: memoryview ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: """Make Ed25519 public key from data.""" (point,), data = self.get_public(data) public_key = ed25519.Ed25519PublicKey.from_public_bytes( point.tobytes() ) return public_key, data def load_private( self, data: memoryview, pubfields ) -> tuple[ed25519.Ed25519PrivateKey, memoryview]: """Make Ed25519 private key from data.""" (point,), data = self.get_public(data) keypair, data = _get_sshstr(data) secret = keypair[:32] point2 = keypair[32:] if point != point2 or (point,) != pubfields: raise ValueError("Corrupt data: ed25519 field mismatch") private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) return private_key, data def encode_public( self, public_key: ed25519.Ed25519PublicKey, f_pub: _FragList ) -> None: """Write Ed25519 public key""" raw_public_key = public_key.public_bytes( Encoding.Raw, PublicFormat.Raw ) f_pub.put_sshstr(raw_public_key) def encode_private( self, private_key: ed25519.Ed25519PrivateKey, f_priv: _FragList ) -> None: """Write Ed25519 private key""" public_key = private_key.public_key() raw_private_key = private_key.private_bytes( Encoding.Raw, PrivateFormat.Raw, NoEncryption() ) raw_public_key = public_key.public_bytes( Encoding.Raw, PublicFormat.Raw ) f_keypair = _FragList([raw_private_key, raw_public_key]) self.encode_public(public_key, f_priv) f_priv.put_sshstr(f_keypair) def load_application(data) -> tuple[memoryview, memoryview]: """ U2F application strings """ application, data = _get_sshstr(data) if not application.tobytes().startswith(b"ssh:"): raise ValueError( "U2F application string does not start with b'ssh:' " f"({application})" ) return application, data class _SSHFormatSKEd25519: """ The format of a sk-ssh-ed25519@openssh.com public key is: string "sk-ssh-ed25519@openssh.com" string public key string application (user-specified, but typically "ssh:") """ def load_public( self, data: memoryview ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: """Make Ed25519 public key from data.""" public_key, data = _lookup_kformat(_SSH_ED25519).load_public(data) _, data = load_application(data) return public_key, data class _SSHFormatSKECDSA: """ The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: string "sk-ecdsa-sha2-nistp256@openssh.com" string curve name ec_point Q string application (user-specified, but typically "ssh:") """ def load_public( self, data: memoryview ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: """Make ECDSA public key from data.""" public_key, data = _lookup_kformat(_ECDSA_NISTP256).load_public(data) _, data = load_application(data) return public_key, data _KEY_FORMATS = { _SSH_RSA: _SSHFormatRSA(), _SSH_DSA: _SSHFormatDSA(), _SSH_ED25519: _SSHFormatEd25519(), _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()), _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()), _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()), _SK_SSH_ED25519: _SSHFormatSKEd25519(), _SK_SSH_ECDSA_NISTP256: _SSHFormatSKECDSA(), } def _lookup_kformat(key_type: bytes): """Return valid format or throw error""" if not isinstance(key_type, bytes): key_type = memoryview(key_type).tobytes() if key_type in _KEY_FORMATS: return _KEY_FORMATS[key_type] raise UnsupportedAlgorithm(f"Unsupported key type: {key_type!r}") SSHPrivateKeyTypes = typing.Union[ ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey, dsa.DSAPrivateKey, ed25519.Ed25519PrivateKey, ] def load_ssh_private_key( data: bytes, password: bytes | None, backend: typing.Any = None, ) -> SSHPrivateKeyTypes: """Load private key from OpenSSH custom encoding.""" utils._check_byteslike("data", data) if password is not None: utils._check_bytes("password", password) m = _PEM_RC.search(data) if not m: raise ValueError("Not OpenSSH private key format") p1 = m.start(1) p2 = m.end(1) data = binascii.a2b_base64(memoryview(data)[p1:p2]) if not data.startswith(_SK_MAGIC): raise ValueError("Not OpenSSH private key format") data = memoryview(data)[len(_SK_MAGIC) :] # parse header ciphername, data = _get_sshstr(data) kdfname, data = _get_sshstr(data) kdfoptions, data = _get_sshstr(data) nkeys, data = _get_u32(data) if nkeys != 1: raise ValueError("Only one key supported") # load public key data pubdata, data = _get_sshstr(data) pub_key_type, pubdata = _get_sshstr(pubdata) kformat = _lookup_kformat(pub_key_type) pubfields, pubdata = kformat.get_public(pubdata) _check_empty(pubdata) if (ciphername, kdfname) != (_NONE, _NONE): ciphername_bytes = ciphername.tobytes() if ciphername_bytes not in _SSH_CIPHERS: raise UnsupportedAlgorithm( f"Unsupported cipher: {ciphername_bytes!r}" ) if kdfname != _BCRYPT: raise UnsupportedAlgorithm(f"Unsupported KDF: {kdfname!r}") blklen = _SSH_CIPHERS[ciphername_bytes].block_len tag_len = _SSH_CIPHERS[ciphername_bytes].tag_len # load secret data edata, data = _get_sshstr(data) # see https://bugzilla.mindrot.org/show_bug.cgi?id=3553 for # information about how OpenSSH handles AEAD tags if _SSH_CIPHERS[ciphername_bytes].is_aead: tag = bytes(data) if len(tag) != tag_len: raise ValueError("Corrupt data: invalid tag length for cipher") else: _check_empty(data) _check_block_size(edata, blklen) salt, kbuf = _get_sshstr(kdfoptions) rounds, kbuf = _get_u32(kbuf) _check_empty(kbuf) ciph = _init_cipher(ciphername_bytes, password, salt.tobytes(), rounds) dec = ciph.decryptor() edata = memoryview(dec.update(edata)) if _SSH_CIPHERS[ciphername_bytes].is_aead: assert isinstance(dec, AEADDecryptionContext) _check_empty(dec.finalize_with_tag(tag)) else: # _check_block_size requires data to be a full block so there # should be no output from finalize _check_empty(dec.finalize()) else: # load secret data edata, data = _get_sshstr(data) _check_empty(data) blklen = 8 _check_block_size(edata, blklen) ck1, edata = _get_u32(edata) ck2, edata = _get_u32(edata) if ck1 != ck2: raise ValueError("Corrupt data: broken checksum") # load per-key struct key_type, edata = _get_sshstr(edata) if key_type != pub_key_type: raise ValueError("Corrupt data: key type mismatch") private_key, edata = kformat.load_private(edata, pubfields) # We don't use the comment _, edata = _get_sshstr(edata) # yes, SSH does padding check *after* all other parsing is done. # need to follow as it writes zero-byte padding too. if edata != _PADDING[: len(edata)]: raise ValueError("Corrupt data: invalid padding") if isinstance(private_key, dsa.DSAPrivateKey): warnings.warn( "SSH DSA keys are deprecated and will be removed in a future " "release.", utils.DeprecatedIn40, stacklevel=2, ) return private_key def _serialize_ssh_private_key( private_key: SSHPrivateKeyTypes, password: bytes, encryption_algorithm: KeySerializationEncryption, ) -> bytes: """Serialize private key with OpenSSH custom encoding.""" utils._check_bytes("password", password) if isinstance(private_key, dsa.DSAPrivateKey): warnings.warn( "SSH DSA key support is deprecated and will be " "removed in a future release", utils.DeprecatedIn40, stacklevel=4, ) key_type = _get_ssh_key_type(private_key) kformat = _lookup_kformat(key_type) # setup parameters f_kdfoptions = _FragList() if password: ciphername = _DEFAULT_CIPHER blklen = _SSH_CIPHERS[ciphername].block_len kdfname = _BCRYPT rounds = _DEFAULT_ROUNDS if ( isinstance(encryption_algorithm, _KeySerializationEncryption) and encryption_algorithm._kdf_rounds is not None ): rounds = encryption_algorithm._kdf_rounds salt = os.urandom(16) f_kdfoptions.put_sshstr(salt) f_kdfoptions.put_u32(rounds) ciph = _init_cipher(ciphername, password, salt, rounds) else: ciphername = kdfname = _NONE blklen = 8 ciph = None nkeys = 1 checkval = os.urandom(4) comment = b"" # encode public and private parts together f_public_key = _FragList() f_public_key.put_sshstr(key_type) kformat.encode_public(private_key.public_key(), f_public_key) f_secrets = _FragList([checkval, checkval]) f_secrets.put_sshstr(key_type) kformat.encode_private(private_key, f_secrets) f_secrets.put_sshstr(comment) f_secrets.put_raw(_PADDING[: blklen - (f_secrets.size() % blklen)]) # top-level structure f_main = _FragList() f_main.put_raw(_SK_MAGIC) f_main.put_sshstr(ciphername) f_main.put_sshstr(kdfname) f_main.put_sshstr(f_kdfoptions) f_main.put_u32(nkeys) f_main.put_sshstr(f_public_key) f_main.put_sshstr(f_secrets) # copy result info bytearray slen = f_secrets.size() mlen = f_main.size() buf = memoryview(bytearray(mlen + blklen)) f_main.render(buf) ofs = mlen - slen # encrypt in-place if ciph is not None: ciph.encryptor().update_into(buf[ofs:mlen], buf[ofs:]) return _ssh_pem_encode(buf[:mlen]) SSHPublicKeyTypes = typing.Union[ ec.EllipticCurvePublicKey, rsa.RSAPublicKey, dsa.DSAPublicKey, ed25519.Ed25519PublicKey, ] SSHCertPublicKeyTypes = typing.Union[ ec.EllipticCurvePublicKey, rsa.RSAPublicKey, ed25519.Ed25519PublicKey, ] class SSHCertificateType(enum.Enum): USER = 1 HOST = 2 class SSHCertificate: def __init__( self, _nonce: memoryview, _public_key: SSHPublicKeyTypes, _serial: int, _cctype: int, _key_id: memoryview, _valid_principals: list[bytes], _valid_after: int, _valid_before: int, _critical_options: dict[bytes, bytes], _extensions: dict[bytes, bytes], _sig_type: memoryview, _sig_key: memoryview, _inner_sig_type: memoryview, _signature: memoryview, _tbs_cert_body: memoryview, _cert_key_type: bytes, _cert_body: memoryview, ): self._nonce = _nonce self._public_key = _public_key self._serial = _serial try: self._type = SSHCertificateType(_cctype) except ValueError: raise ValueError("Invalid certificate type") self._key_id = _key_id self._valid_principals = _valid_principals self._valid_after = _valid_after self._valid_before = _valid_before self._critical_options = _critical_options self._extensions = _extensions self._sig_type = _sig_type self._sig_key = _sig_key self._inner_sig_type = _inner_sig_type self._signature = _signature self._cert_key_type = _cert_key_type self._cert_body = _cert_body self._tbs_cert_body = _tbs_cert_body @property def nonce(self) -> bytes: return bytes(self._nonce) def public_key(self) -> SSHCertPublicKeyTypes: # make mypy happy until we remove DSA support entirely and # the underlying union won't have a disallowed type return typing.cast(SSHCertPublicKeyTypes, self._public_key) @property def serial(self) -> int: return self._serial @property def type(self) -> SSHCertificateType: return self._type @property def key_id(self) -> bytes: return bytes(self._key_id) @property def valid_principals(self) -> list[bytes]: return self._valid_principals @property def valid_before(self) -> int: return self._valid_before @property def valid_after(self) -> int: return self._valid_after @property def critical_options(self) -> dict[bytes, bytes]: return self._critical_options @property def extensions(self) -> dict[bytes, bytes]: return self._extensions def signature_key(self) -> SSHCertPublicKeyTypes: sigformat = _lookup_kformat(self._sig_type) signature_key, sigkey_rest = sigformat.load_public(self._sig_key) _check_empty(sigkey_rest) return signature_key def public_bytes(self) -> bytes: return ( bytes(self._cert_key_type) + b" " + binascii.b2a_base64(bytes(self._cert_body), newline=False) ) def verify_cert_signature(self) -> None: signature_key = self.signature_key() if isinstance(signature_key, ed25519.Ed25519PublicKey): signature_key.verify( bytes(self._signature), bytes(self._tbs_cert_body) ) elif isinstance(signature_key, ec.EllipticCurvePublicKey): # The signature is encoded as a pair of big-endian integers r, data = _get_mpint(self._signature) s, data = _get_mpint(data) _check_empty(data) computed_sig = asym_utils.encode_dss_signature(r, s) hash_alg = _get_ec_hash_alg(signature_key.curve) signature_key.verify( computed_sig, bytes(self._tbs_cert_body), ec.ECDSA(hash_alg) ) else: assert isinstance(signature_key, rsa.RSAPublicKey) if self._inner_sig_type == _SSH_RSA: hash_alg = hashes.SHA1() elif self._inner_sig_type == _SSH_RSA_SHA256: hash_alg = hashes.SHA256() else: assert self._inner_sig_type == _SSH_RSA_SHA512 hash_alg = hashes.SHA512() signature_key.verify( bytes(self._signature), bytes(self._tbs_cert_body), padding.PKCS1v15(), hash_alg, ) def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm: if isinstance(curve, ec.SECP256R1): return hashes.SHA256() elif isinstance(curve, ec.SECP384R1): return hashes.SHA384() else: assert isinstance(curve, ec.SECP521R1) return hashes.SHA512() def _load_ssh_public_identity( data: bytes, _legacy_dsa_allowed=False, ) -> SSHCertificate | SSHPublicKeyTypes: utils._check_byteslike("data", data) m = _SSH_PUBKEY_RC.match(data) if not m: raise ValueError("Invalid line format") key_type = orig_key_type = m.group(1) key_body = m.group(2) with_cert = False if key_type.endswith(_CERT_SUFFIX): with_cert = True key_type = key_type[: -len(_CERT_SUFFIX)] if key_type == _SSH_DSA and not _legacy_dsa_allowed: raise UnsupportedAlgorithm( "DSA keys aren't supported in SSH certificates" ) kformat = _lookup_kformat(key_type) try: rest = memoryview(binascii.a2b_base64(key_body)) except (TypeError, binascii.Error): raise ValueError("Invalid format") if with_cert: cert_body = rest inner_key_type, rest = _get_sshstr(rest) if inner_key_type != orig_key_type: raise ValueError("Invalid key format") if with_cert: nonce, rest = _get_sshstr(rest) public_key, rest = kformat.load_public(rest) if with_cert: serial, rest = _get_u64(rest) cctype, rest = _get_u32(rest) key_id, rest = _get_sshstr(rest) principals, rest = _get_sshstr(rest) valid_principals = [] while principals: principal, principals = _get_sshstr(principals) valid_principals.append(bytes(principal)) valid_after, rest = _get_u64(rest) valid_before, rest = _get_u64(rest) crit_options, rest = _get_sshstr(rest) critical_options = _parse_exts_opts(crit_options) exts, rest = _get_sshstr(rest) extensions = _parse_exts_opts(exts) # Get the reserved field, which is unused. _, rest = _get_sshstr(rest) sig_key_raw, rest = _get_sshstr(rest) sig_type, sig_key = _get_sshstr(sig_key_raw) if sig_type == _SSH_DSA and not _legacy_dsa_allowed: raise UnsupportedAlgorithm( "DSA signatures aren't supported in SSH certificates" ) # Get the entire cert body and subtract the signature tbs_cert_body = cert_body[: -len(rest)] signature_raw, rest = _get_sshstr(rest) _check_empty(rest) inner_sig_type, sig_rest = _get_sshstr(signature_raw) # RSA certs can have multiple algorithm types if ( sig_type == _SSH_RSA and inner_sig_type not in [_SSH_RSA_SHA256, _SSH_RSA_SHA512, _SSH_RSA] ) or (sig_type != _SSH_RSA and inner_sig_type != sig_type): raise ValueError("Signature key type does not match") signature, sig_rest = _get_sshstr(sig_rest) _check_empty(sig_rest) return SSHCertificate( nonce, public_key, serial, cctype, key_id, valid_principals, valid_after, valid_before, critical_options, extensions, sig_type, sig_key, inner_sig_type, signature, tbs_cert_body, orig_key_type, cert_body, ) else: _check_empty(rest) return public_key def load_ssh_public_identity( data: bytes, ) -> SSHCertificate | SSHPublicKeyTypes: return _load_ssh_public_identity(data) def _parse_exts_opts(exts_opts: memoryview) -> dict[bytes, bytes]: result: dict[bytes, bytes] = {} last_name = None while exts_opts: name, exts_opts = _get_sshstr(exts_opts) bname: bytes = bytes(name) if bname in result: raise ValueError("Duplicate name") if last_name is not None and bname < last_name: raise ValueError("Fields not lexically sorted") value, exts_opts = _get_sshstr(exts_opts) if len(value) > 0: value, extra = _get_sshstr(value) if len(extra) > 0: raise ValueError("Unexpected extra data after value") result[bname] = bytes(value) last_name = bname return result def load_ssh_public_key( data: bytes, backend: typing.Any = None ) -> SSHPublicKeyTypes: cert_or_key = _load_ssh_public_identity(data, _legacy_dsa_allowed=True) public_key: SSHPublicKeyTypes if isinstance(cert_or_key, SSHCertificate): public_key = cert_or_key.public_key() else: public_key = cert_or_key if isinstance(public_key, dsa.DSAPublicKey): warnings.warn( "SSH DSA keys are deprecated and will be removed in a future " "release.", utils.DeprecatedIn40, stacklevel=2, ) return public_key def serialize_ssh_public_key(public_key: SSHPublicKeyTypes) -> bytes: """One-line public key format for OpenSSH""" if isinstance(public_key, dsa.DSAPublicKey): warnings.warn( "SSH DSA key support is deprecated and will be " "removed in a future release", utils.DeprecatedIn40, stacklevel=4, ) key_type = _get_ssh_key_type(public_key) kformat = _lookup_kformat(key_type) f_pub = _FragList() f_pub.put_sshstr(key_type) kformat.encode_public(public_key, f_pub) pub = binascii.b2a_base64(f_pub.tobytes()).strip() return b"".join([key_type, b" ", pub]) SSHCertPrivateKeyTypes = typing.Union[ ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey, ed25519.Ed25519PrivateKey, ] # This is an undocumented limit enforced in the openssh codebase for sshd and # ssh-keygen, but it is undefined in the ssh certificates spec. _SSHKEY_CERT_MAX_PRINCIPALS = 256 class SSHCertificateBuilder: def __init__( self, _public_key: SSHCertPublicKeyTypes | None = None, _serial: int | None = None, _type: SSHCertificateType | None = None, _key_id: bytes | None = None, _valid_principals: list[bytes] = [], _valid_for_all_principals: bool = False, _valid_before: int | None = None, _valid_after: int | None = None, _critical_options: list[tuple[bytes, bytes]] = [], _extensions: list[tuple[bytes, bytes]] = [], ): self._public_key = _public_key self._serial = _serial self._type = _type self._key_id = _key_id self._valid_principals = _valid_principals self._valid_for_all_principals = _valid_for_all_principals self._valid_before = _valid_before self._valid_after = _valid_after self._critical_options = _critical_options self._extensions = _extensions def public_key( self, public_key: SSHCertPublicKeyTypes ) -> SSHCertificateBuilder: if not isinstance( public_key, ( ec.EllipticCurvePublicKey, rsa.RSAPublicKey, ed25519.Ed25519PublicKey, ), ): raise TypeError("Unsupported key type") if self._public_key is not None: raise ValueError("public_key already set") return SSHCertificateBuilder( _public_key=public_key, _serial=self._serial, _type=self._type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def serial(self, serial: int) -> SSHCertificateBuilder: if not isinstance(serial, int): raise TypeError("serial must be an integer") if not 0 <= serial < 2**64: raise ValueError("serial must be between 0 and 2**64") if self._serial is not None: raise ValueError("serial already set") return SSHCertificateBuilder( _public_key=self._public_key, _serial=serial, _type=self._type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def type(self, type: SSHCertificateType) -> SSHCertificateBuilder: if not isinstance(type, SSHCertificateType): raise TypeError("type must be an SSHCertificateType") if self._type is not None: raise ValueError("type already set") return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def key_id(self, key_id: bytes) -> SSHCertificateBuilder: if not isinstance(key_id, bytes): raise TypeError("key_id must be bytes") if self._key_id is not None: raise ValueError("key_id already set") return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=self._type, _key_id=key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def valid_principals( self, valid_principals: list[bytes] ) -> SSHCertificateBuilder: if self._valid_for_all_principals: raise ValueError( "Principals can't be set because the cert is valid " "for all principals" ) if ( not all(isinstance(x, bytes) for x in valid_principals) or not valid_principals ): raise TypeError( "principals must be a list of bytes and can't be empty" ) if self._valid_principals: raise ValueError("valid_principals already set") if len(valid_principals) > _SSHKEY_CERT_MAX_PRINCIPALS: raise ValueError( "Reached or exceeded the maximum number of valid_principals" ) return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=self._type, _key_id=self._key_id, _valid_principals=valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def valid_for_all_principals(self): if self._valid_principals: raise ValueError( "valid_principals already set, can't set " "valid_for_all_principals" ) if self._valid_for_all_principals: raise ValueError("valid_for_all_principals already set") return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=self._type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=True, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def valid_before(self, valid_before: int | float) -> SSHCertificateBuilder: if not isinstance(valid_before, (int, float)): raise TypeError("valid_before must be an int or float") valid_before = int(valid_before) if valid_before < 0 or valid_before >= 2**64: raise ValueError("valid_before must [0, 2**64)") if self._valid_before is not None: raise ValueError("valid_before already set") return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=self._type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def valid_after(self, valid_after: int | float) -> SSHCertificateBuilder: if not isinstance(valid_after, (int, float)): raise TypeError("valid_after must be an int or float") valid_after = int(valid_after) if valid_after < 0 or valid_after >= 2**64: raise ValueError("valid_after must [0, 2**64)") if self._valid_after is not None: raise ValueError("valid_after already set") return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=self._type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=valid_after, _critical_options=self._critical_options, _extensions=self._extensions, ) def add_critical_option( self, name: bytes, value: bytes ) -> SSHCertificateBuilder: if not isinstance(name, bytes) or not isinstance(value, bytes): raise TypeError("name and value must be bytes") # This is O(n**2) if name in [name for name, _ in self._critical_options]: raise ValueError("Duplicate critical option name") return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=self._type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=[*self._critical_options, (name, value)], _extensions=self._extensions, ) def add_extension( self, name: bytes, value: bytes ) -> SSHCertificateBuilder: if not isinstance(name, bytes) or not isinstance(value, bytes): raise TypeError("name and value must be bytes") # This is O(n**2) if name in [name for name, _ in self._extensions]: raise ValueError("Duplicate extension name") return SSHCertificateBuilder( _public_key=self._public_key, _serial=self._serial, _type=self._type, _key_id=self._key_id, _valid_principals=self._valid_principals, _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, _extensions=[*self._extensions, (name, value)], ) def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate: if not isinstance( private_key, ( ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey, ed25519.Ed25519PrivateKey, ), ): raise TypeError("Unsupported private key type") if self._public_key is None: raise ValueError("public_key must be set") # Not required serial = 0 if self._serial is None else self._serial if self._type is None: raise ValueError("type must be set") # Not required key_id = b"" if self._key_id is None else self._key_id # A zero length list is valid, but means the certificate # is valid for any principal of the specified type. We require # the user to explicitly set valid_for_all_principals to get # that behavior. if not self._valid_principals and not self._valid_for_all_principals: raise ValueError( "valid_principals must be set if valid_for_all_principals " "is False" ) if self._valid_before is None: raise ValueError("valid_before must be set") if self._valid_after is None: raise ValueError("valid_after must be set") if self._valid_after > self._valid_before: raise ValueError("valid_after must be earlier than valid_before") # lexically sort our byte strings self._critical_options.sort(key=lambda x: x[0]) self._extensions.sort(key=lambda x: x[0]) key_type = _get_ssh_key_type(self._public_key) cert_prefix = key_type + _CERT_SUFFIX # Marshal the bytes to be signed nonce = os.urandom(32) kformat = _lookup_kformat(key_type) f = _FragList() f.put_sshstr(cert_prefix) f.put_sshstr(nonce) kformat.encode_public(self._public_key, f) f.put_u64(serial) f.put_u32(self._type.value) f.put_sshstr(key_id) fprincipals = _FragList() for p in self._valid_principals: fprincipals.put_sshstr(p) f.put_sshstr(fprincipals.tobytes()) f.put_u64(self._valid_after) f.put_u64(self._valid_before) fcrit = _FragList() for name, value in self._critical_options: fcrit.put_sshstr(name) if len(value) > 0: foptval = _FragList() foptval.put_sshstr(value) fcrit.put_sshstr(foptval.tobytes()) else: fcrit.put_sshstr(value) f.put_sshstr(fcrit.tobytes()) fext = _FragList() for name, value in self._extensions: fext.put_sshstr(name) if len(value) > 0: fextval = _FragList() fextval.put_sshstr(value) fext.put_sshstr(fextval.tobytes()) else: fext.put_sshstr(value) f.put_sshstr(fext.tobytes()) f.put_sshstr(b"") # RESERVED FIELD # encode CA public key ca_type = _get_ssh_key_type(private_key) caformat = _lookup_kformat(ca_type) caf = _FragList() caf.put_sshstr(ca_type) caformat.encode_public(private_key.public_key(), caf) f.put_sshstr(caf.tobytes()) # Sigs according to the rules defined for the CA's public key # (RFC4253 section 6.6 for ssh-rsa, RFC5656 for ECDSA, # and RFC8032 for Ed25519). if isinstance(private_key, ed25519.Ed25519PrivateKey): signature = private_key.sign(f.tobytes()) fsig = _FragList() fsig.put_sshstr(ca_type) fsig.put_sshstr(signature) f.put_sshstr(fsig.tobytes()) elif isinstance(private_key, ec.EllipticCurvePrivateKey): hash_alg = _get_ec_hash_alg(private_key.curve) signature = private_key.sign(f.tobytes(), ec.ECDSA(hash_alg)) r, s = asym_utils.decode_dss_signature(signature) fsig = _FragList() fsig.put_sshstr(ca_type) fsigblob = _FragList() fsigblob.put_mpint(r) fsigblob.put_mpint(s) fsig.put_sshstr(fsigblob.tobytes()) f.put_sshstr(fsig.tobytes()) else: assert isinstance(private_key, rsa.RSAPrivateKey) # Just like Golang, we're going to use SHA512 for RSA # https://cs.opensource.google/go/x/crypto/+/refs/tags/ # v0.4.0:ssh/certs.go;l=445 # RFC 8332 defines SHA256 and 512 as options fsig = _FragList() fsig.put_sshstr(_SSH_RSA_SHA512) signature = private_key.sign( f.tobytes(), padding.PKCS1v15(), hashes.SHA512() ) fsig.put_sshstr(signature) f.put_sshstr(fsig.tobytes()) cert_data = binascii.b2a_base64(f.tobytes()).strip() # load_ssh_public_identity returns a union, but this is # guaranteed to be an SSHCertificate, so we cast to make # mypy happy. return typing.cast( SSHCertificate, load_ssh_public_identity(b"".join([cert_prefix, b" ", cert_data])), ) cryptography-43.0.0/src/cryptography/hazmat/primitives/twofactor/__init__.py010064400017510000177000000004021464676315000256470ustar 00000000000000# 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. from __future__ import annotations class InvalidToken(Exception): pass cryptography-43.0.0/src/cryptography/hazmat/primitives/twofactor/hotp.py010064400017510000177000000056401464676315000250730ustar 00000000000000# 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. from __future__ import annotations import base64 import typing from urllib.parse import quote, urlencode from cryptography.hazmat.primitives import constant_time, hmac from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 from cryptography.hazmat.primitives.twofactor import InvalidToken HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512] def _generate_uri( hotp: HOTP, type_name: str, account_name: str, issuer: str | None, extra_parameters: list[tuple[str, int]], ) -> str: parameters = [ ("digits", hotp._length), ("secret", base64.b32encode(hotp._key)), ("algorithm", hotp._algorithm.name.upper()), ] if issuer is not None: parameters.append(("issuer", issuer)) parameters.extend(extra_parameters) label = ( f"{quote(issuer)}:{quote(account_name)}" if issuer else quote(account_name) ) return f"otpauth://{type_name}/{label}?{urlencode(parameters)}" class HOTP: def __init__( self, key: bytes, length: int, algorithm: HOTPHashTypes, backend: typing.Any = None, enforce_key_length: bool = True, ) -> None: if len(key) < 16 and enforce_key_length is True: raise ValueError("Key length has to be at least 128 bits.") if not isinstance(length, int): raise TypeError("Length parameter must be an integer type.") if length < 6 or length > 8: raise ValueError("Length of HOTP has to be between 6 and 8.") if not isinstance(algorithm, (SHA1, SHA256, SHA512)): raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.") self._key = key self._length = length self._algorithm = algorithm def generate(self, counter: int) -> bytes: truncated_value = self._dynamic_truncate(counter) hotp = truncated_value % (10**self._length) return "{0:0{1}}".format(hotp, self._length).encode() def verify(self, hotp: bytes, counter: int) -> None: if not constant_time.bytes_eq(self.generate(counter), hotp): raise InvalidToken("Supplied HOTP value does not match.") def _dynamic_truncate(self, counter: int) -> int: ctx = hmac.HMAC(self._key, self._algorithm) ctx.update(counter.to_bytes(length=8, byteorder="big")) hmac_value = ctx.finalize() offset = hmac_value[len(hmac_value) - 1] & 0b1111 p = hmac_value[offset : offset + 4] return int.from_bytes(p, byteorder="big") & 0x7FFFFFFF def get_provisioning_uri( self, account_name: str, counter: int, issuer: str | None ) -> str: return _generate_uri( self, "hotp", account_name, issuer, [("counter", int(counter))] ) cryptography-43.0.0/src/cryptography/hazmat/primitives/twofactor/totp.py010064400017510000177000000026521464676315000251070ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.twofactor import InvalidToken from cryptography.hazmat.primitives.twofactor.hotp import ( HOTP, HOTPHashTypes, _generate_uri, ) class TOTP: def __init__( self, key: bytes, length: int, algorithm: HOTPHashTypes, time_step: int, backend: typing.Any = None, enforce_key_length: bool = True, ): self._time_step = time_step self._hotp = HOTP( key, length, algorithm, enforce_key_length=enforce_key_length ) def generate(self, time: int | float) -> bytes: counter = int(time / self._time_step) return self._hotp.generate(counter) def verify(self, totp: bytes, time: int) -> None: if not constant_time.bytes_eq(self.generate(time), totp): raise InvalidToken("Supplied TOTP value does not match.") def get_provisioning_uri( self, account_name: str, issuer: str | None ) -> str: return _generate_uri( self._hotp, "totp", account_name, issuer, [("period", int(self._time_step))], ) cryptography-43.0.0/src/cryptography/py.typed010064400017510000177000000000001464676315000175400ustar 00000000000000cryptography-43.0.0/src/cryptography/utils.py010064400017510000177000000075251464676315000175760ustar 00000000000000# 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. from __future__ import annotations import enum import sys import types import typing import warnings # We use a UserWarning subclass, instead of DeprecationWarning, because CPython # decided deprecation warnings should be invisible by default. class CryptographyDeprecationWarning(UserWarning): pass # Several APIs were deprecated with no specific end-of-life date because of the # ubiquity of their use. They should not be removed until we agree on when that # cycle ends. DeprecatedIn36 = CryptographyDeprecationWarning DeprecatedIn37 = CryptographyDeprecationWarning DeprecatedIn40 = CryptographyDeprecationWarning DeprecatedIn41 = CryptographyDeprecationWarning DeprecatedIn42 = CryptographyDeprecationWarning DeprecatedIn43 = CryptographyDeprecationWarning def _check_bytes(name: str, value: bytes) -> None: if not isinstance(value, bytes): raise TypeError(f"{name} must be bytes") def _check_byteslike(name: str, value: bytes) -> None: try: memoryview(value) except TypeError: raise TypeError(f"{name} must be bytes-like") def int_to_bytes(integer: int, length: int | None = None) -> bytes: if length == 0: raise ValueError("length argument can't be 0") return integer.to_bytes( length or (integer.bit_length() + 7) // 8 or 1, "big" ) class InterfaceNotImplemented(Exception): pass class _DeprecatedValue: def __init__(self, value: object, message: str, warning_class): self.value = value self.message = message self.warning_class = warning_class class _ModuleWithDeprecations(types.ModuleType): def __init__(self, module: types.ModuleType): super().__init__(module.__name__) self.__dict__["_module"] = module def __getattr__(self, attr: str) -> object: obj = getattr(self._module, attr) if isinstance(obj, _DeprecatedValue): warnings.warn(obj.message, obj.warning_class, stacklevel=2) obj = obj.value return obj def __setattr__(self, attr: str, value: object) -> None: setattr(self._module, attr, value) def __delattr__(self, attr: str) -> None: obj = getattr(self._module, attr) if isinstance(obj, _DeprecatedValue): warnings.warn(obj.message, obj.warning_class, stacklevel=2) delattr(self._module, attr) def __dir__(self) -> typing.Sequence[str]: return ["_module", *dir(self._module)] def deprecated( value: object, module_name: str, message: str, warning_class: type[Warning], name: str | None = None, ) -> _DeprecatedValue: module = sys.modules[module_name] if not isinstance(module, _ModuleWithDeprecations): sys.modules[module_name] = module = _ModuleWithDeprecations(module) dv = _DeprecatedValue(value, message, warning_class) # Maintain backwards compatibility with `name is None` for pyOpenSSL. if name is not None: setattr(module, name, dv) return dv def cached_property(func: typing.Callable) -> property: cached_name = f"_cached_{func}" sentinel = object() def inner(instance: object): cache = getattr(instance, cached_name, sentinel) if cache is not sentinel: return cache result = func(instance) setattr(instance, cached_name, result) return result return property(inner) # Python 3.10 changed representation of enums. We use well-defined object # representation and string representation from Python 3.9. class Enum(enum.Enum): def __repr__(self) -> str: return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>" def __str__(self) -> str: return f"{self.__class__.__name__}.{self._name_}" cryptography-43.0.0/src/cryptography/x509/__init__.py010064400017510000177000000174541464676315000207040ustar 00000000000000# 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. from __future__ import annotations from cryptography.x509 import certificate_transparency, verification from cryptography.x509.base import ( Attribute, AttributeNotFound, Attributes, Certificate, CertificateBuilder, CertificateRevocationList, CertificateRevocationListBuilder, CertificateSigningRequest, CertificateSigningRequestBuilder, InvalidVersion, RevokedCertificate, RevokedCertificateBuilder, Version, load_der_x509_certificate, load_der_x509_crl, load_der_x509_csr, load_pem_x509_certificate, load_pem_x509_certificates, load_pem_x509_crl, load_pem_x509_csr, random_serial_number, ) from cryptography.x509.extensions import ( AccessDescription, AuthorityInformationAccess, AuthorityKeyIdentifier, BasicConstraints, CertificateIssuer, CertificatePolicies, CRLDistributionPoints, CRLNumber, CRLReason, DeltaCRLIndicator, DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension, ExtensionNotFound, Extensions, ExtensionType, FreshestCRL, GeneralNames, InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, IssuingDistributionPoint, KeyUsage, MSCertificateTemplate, NameConstraints, NoticeReference, OCSPAcceptableResponses, OCSPNoCheck, OCSPNonce, PolicyConstraints, PolicyInformation, PrecertificateSignedCertificateTimestamps, PrecertPoison, ReasonFlags, SignedCertificateTimestamps, SubjectAlternativeName, SubjectInformationAccess, SubjectKeyIdentifier, TLSFeature, TLSFeatureType, UnrecognizedExtension, UserNotice, ) from cryptography.x509.general_name import ( DirectoryName, DNSName, GeneralName, IPAddress, OtherName, RegisteredID, RFC822Name, UniformResourceIdentifier, UnsupportedGeneralNameType, ) from cryptography.x509.name import ( Name, NameAttribute, RelativeDistinguishedName, ) from cryptography.x509.oid import ( AuthorityInformationAccessOID, CertificatePoliciesOID, CRLEntryExtensionOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, PublicKeyAlgorithmOID, SignatureAlgorithmOID, ) OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS OID_AUTHORITY_KEY_IDENTIFIER = ExtensionOID.AUTHORITY_KEY_IDENTIFIER OID_BASIC_CONSTRAINTS = ExtensionOID.BASIC_CONSTRAINTS OID_CERTIFICATE_POLICIES = ExtensionOID.CERTIFICATE_POLICIES OID_CRL_DISTRIBUTION_POINTS = ExtensionOID.CRL_DISTRIBUTION_POINTS OID_EXTENDED_KEY_USAGE = ExtensionOID.EXTENDED_KEY_USAGE OID_FRESHEST_CRL = ExtensionOID.FRESHEST_CRL OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME OID_KEY_USAGE = ExtensionOID.KEY_USAGE OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS OID_POLICY_MAPPINGS = ExtensionOID.POLICY_MAPPINGS OID_SUBJECT_ALTERNATIVE_NAME = ExtensionOID.SUBJECT_ALTERNATIVE_NAME OID_SUBJECT_DIRECTORY_ATTRIBUTES = ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES OID_SUBJECT_INFORMATION_ACCESS = ExtensionOID.SUBJECT_INFORMATION_ACCESS OID_SUBJECT_KEY_IDENTIFIER = ExtensionOID.SUBJECT_KEY_IDENTIFIER OID_DSA_WITH_SHA1 = SignatureAlgorithmOID.DSA_WITH_SHA1 OID_DSA_WITH_SHA224 = SignatureAlgorithmOID.DSA_WITH_SHA224 OID_DSA_WITH_SHA256 = SignatureAlgorithmOID.DSA_WITH_SHA256 OID_ECDSA_WITH_SHA1 = SignatureAlgorithmOID.ECDSA_WITH_SHA1 OID_ECDSA_WITH_SHA224 = SignatureAlgorithmOID.ECDSA_WITH_SHA224 OID_ECDSA_WITH_SHA256 = SignatureAlgorithmOID.ECDSA_WITH_SHA256 OID_ECDSA_WITH_SHA384 = SignatureAlgorithmOID.ECDSA_WITH_SHA384 OID_ECDSA_WITH_SHA512 = SignatureAlgorithmOID.ECDSA_WITH_SHA512 OID_RSA_WITH_MD5 = SignatureAlgorithmOID.RSA_WITH_MD5 OID_RSA_WITH_SHA1 = SignatureAlgorithmOID.RSA_WITH_SHA1 OID_RSA_WITH_SHA224 = SignatureAlgorithmOID.RSA_WITH_SHA224 OID_RSA_WITH_SHA256 = SignatureAlgorithmOID.RSA_WITH_SHA256 OID_RSA_WITH_SHA384 = SignatureAlgorithmOID.RSA_WITH_SHA384 OID_RSA_WITH_SHA512 = SignatureAlgorithmOID.RSA_WITH_SHA512 OID_RSASSA_PSS = SignatureAlgorithmOID.RSASSA_PSS OID_COMMON_NAME = NameOID.COMMON_NAME OID_COUNTRY_NAME = NameOID.COUNTRY_NAME OID_DOMAIN_COMPONENT = NameOID.DOMAIN_COMPONENT OID_DN_QUALIFIER = NameOID.DN_QUALIFIER OID_EMAIL_ADDRESS = NameOID.EMAIL_ADDRESS OID_GENERATION_QUALIFIER = NameOID.GENERATION_QUALIFIER OID_GIVEN_NAME = NameOID.GIVEN_NAME OID_LOCALITY_NAME = NameOID.LOCALITY_NAME OID_ORGANIZATIONAL_UNIT_NAME = NameOID.ORGANIZATIONAL_UNIT_NAME OID_ORGANIZATION_NAME = NameOID.ORGANIZATION_NAME OID_PSEUDONYM = NameOID.PSEUDONYM OID_SERIAL_NUMBER = NameOID.SERIAL_NUMBER OID_STATE_OR_PROVINCE_NAME = NameOID.STATE_OR_PROVINCE_NAME OID_SURNAME = NameOID.SURNAME OID_TITLE = NameOID.TITLE OID_CLIENT_AUTH = ExtendedKeyUsageOID.CLIENT_AUTH OID_CODE_SIGNING = ExtendedKeyUsageOID.CODE_SIGNING OID_EMAIL_PROTECTION = ExtendedKeyUsageOID.EMAIL_PROTECTION OID_OCSP_SIGNING = ExtendedKeyUsageOID.OCSP_SIGNING OID_SERVER_AUTH = ExtendedKeyUsageOID.SERVER_AUTH OID_TIME_STAMPING = ExtendedKeyUsageOID.TIME_STAMPING OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE OID_CERTIFICATE_ISSUER = CRLEntryExtensionOID.CERTIFICATE_ISSUER OID_CRL_REASON = CRLEntryExtensionOID.CRL_REASON OID_INVALIDITY_DATE = CRLEntryExtensionOID.INVALIDITY_DATE OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS OID_OCSP = AuthorityInformationAccessOID.OCSP __all__ = [ "OID_CA_ISSUERS", "OID_OCSP", "AccessDescription", "Attribute", "AttributeNotFound", "Attributes", "AuthorityInformationAccess", "AuthorityKeyIdentifier", "BasicConstraints", "CRLDistributionPoints", "CRLNumber", "CRLReason", "Certificate", "CertificateBuilder", "CertificateIssuer", "CertificatePolicies", "CertificateRevocationList", "CertificateRevocationListBuilder", "CertificateSigningRequest", "CertificateSigningRequestBuilder", "DNSName", "DeltaCRLIndicator", "DirectoryName", "DistributionPoint", "DuplicateExtension", "ExtendedKeyUsage", "Extension", "ExtensionNotFound", "ExtensionType", "Extensions", "FreshestCRL", "GeneralName", "GeneralNames", "IPAddress", "InhibitAnyPolicy", "InvalidVersion", "InvalidityDate", "IssuerAlternativeName", "IssuingDistributionPoint", "KeyUsage", "MSCertificateTemplate", "Name", "NameAttribute", "NameConstraints", "NameOID", "NoticeReference", "OCSPAcceptableResponses", "OCSPNoCheck", "OCSPNonce", "ObjectIdentifier", "OtherName", "PolicyConstraints", "PolicyInformation", "PrecertPoison", "PrecertificateSignedCertificateTimestamps", "PublicKeyAlgorithmOID", "RFC822Name", "ReasonFlags", "RegisteredID", "RelativeDistinguishedName", "RevokedCertificate", "RevokedCertificateBuilder", "SignatureAlgorithmOID", "SignedCertificateTimestamps", "SubjectAlternativeName", "SubjectInformationAccess", "SubjectKeyIdentifier", "TLSFeature", "TLSFeatureType", "UniformResourceIdentifier", "UnrecognizedExtension", "UnsupportedGeneralNameType", "UserNotice", "Version", "certificate_transparency", "load_der_x509_certificate", "load_der_x509_crl", "load_der_x509_csr", "load_pem_x509_certificate", "load_pem_x509_certificates", "load_pem_x509_crl", "load_pem_x509_csr", "random_serial_number", "verification", "verification", ] cryptography-43.0.0/src/cryptography/x509/base.py010064400017510000177000001103311464676315000200430ustar 00000000000000# 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. from __future__ import annotations import abc import datetime import os import typing import warnings from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed448, ed25519, padding, rsa, x448, x25519, ) from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPrivateKeyTypes, CertificateIssuerPublicKeyTypes, CertificatePublicKeyTypes, ) from cryptography.x509.extensions import ( Extension, Extensions, ExtensionType, _make_sequence_methods, ) from cryptography.x509.name import Name, _ASN1Type from cryptography.x509.oid import ObjectIdentifier _EARLIEST_UTC_TIME = datetime.datetime(1950, 1, 1) # This must be kept in sync with sign.rs's list of allowable types in # identify_hash_type _AllowedHashTypes = typing.Union[ hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, hashes.SHA3_224, hashes.SHA3_256, hashes.SHA3_384, hashes.SHA3_512, ] class AttributeNotFound(Exception): def __init__(self, msg: str, oid: ObjectIdentifier) -> None: super().__init__(msg) self.oid = oid def _reject_duplicate_extension( extension: Extension[ExtensionType], extensions: list[Extension[ExtensionType]], ) -> None: # This is quadratic in the number of extensions for e in extensions: if e.oid == extension.oid: raise ValueError("This extension has already been set.") def _reject_duplicate_attribute( oid: ObjectIdentifier, attributes: list[tuple[ObjectIdentifier, bytes, int | None]], ) -> None: # This is quadratic in the number of attributes for attr_oid, _, _ in attributes: if attr_oid == oid: raise ValueError("This attribute has already been set.") def _convert_to_naive_utc_time(time: datetime.datetime) -> datetime.datetime: """Normalizes a datetime to a naive datetime in UTC. time -- datetime to normalize. Assumed to be in UTC if not timezone aware. """ if time.tzinfo is not None: offset = time.utcoffset() offset = offset if offset else datetime.timedelta() return time.replace(tzinfo=None) - offset else: return time class Attribute: def __init__( self, oid: ObjectIdentifier, value: bytes, _type: int = _ASN1Type.UTF8String.value, ) -> None: self._oid = oid self._value = value self._type = _type @property def oid(self) -> ObjectIdentifier: return self._oid @property def value(self) -> bytes: return self._value def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, Attribute): return NotImplemented return ( self.oid == other.oid and self.value == other.value and self._type == other._type ) def __hash__(self) -> int: return hash((self.oid, self.value, self._type)) class Attributes: def __init__( self, attributes: typing.Iterable[Attribute], ) -> None: self._attributes = list(attributes) __len__, __iter__, __getitem__ = _make_sequence_methods("_attributes") def __repr__(self) -> str: return f"" def get_attribute_for_oid(self, oid: ObjectIdentifier) -> Attribute: for attr in self: if attr.oid == oid: return attr raise AttributeNotFound(f"No {oid} attribute was found", oid) class Version(utils.Enum): v1 = 0 v3 = 2 class InvalidVersion(Exception): def __init__(self, msg: str, parsed_version: int) -> None: super().__init__(msg) self.parsed_version = parsed_version class Certificate(metaclass=abc.ABCMeta): @abc.abstractmethod def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: """ Returns bytes using digest passed. """ @property @abc.abstractmethod def serial_number(self) -> int: """ Returns certificate serial number """ @property @abc.abstractmethod def version(self) -> Version: """ Returns the certificate version """ @abc.abstractmethod def public_key(self) -> CertificatePublicKeyTypes: """ Returns the public key """ @property @abc.abstractmethod def public_key_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the public key. """ @property @abc.abstractmethod def not_valid_before(self) -> datetime.datetime: """ Not before time (represented as UTC datetime) """ @property @abc.abstractmethod def not_valid_before_utc(self) -> datetime.datetime: """ Not before time (represented as a non-naive UTC datetime) """ @property @abc.abstractmethod def not_valid_after(self) -> datetime.datetime: """ Not after time (represented as UTC datetime) """ @property @abc.abstractmethod def not_valid_after_utc(self) -> datetime.datetime: """ Not after time (represented as a non-naive UTC datetime) """ @property @abc.abstractmethod def issuer(self) -> Name: """ Returns the issuer name object. """ @property @abc.abstractmethod def subject(self) -> Name: """ Returns the subject name object. """ @property @abc.abstractmethod def signature_hash_algorithm( self, ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. """ @property @abc.abstractmethod def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ @property @abc.abstractmethod def signature_algorithm_parameters( self, ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA: """ Returns the signature algorithm parameters. """ @property @abc.abstractmethod def extensions(self) -> Extensions: """ Returns an Extensions object. """ @property @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature bytes. """ @property @abc.abstractmethod def tbs_certificate_bytes(self) -> bytes: """ Returns the tbsCertificate payload bytes as defined in RFC 5280. """ @property @abc.abstractmethod def tbs_precertificate_bytes(self) -> bytes: """ Returns the tbsCertificate payload bytes with the SCT list extension stripped. """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ @abc.abstractmethod def __hash__(self) -> int: """ Computes a hash. """ @abc.abstractmethod def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Serializes the certificate to PEM or DER format. """ @abc.abstractmethod def verify_directly_issued_by(self, issuer: Certificate) -> None: """ This method verifies that certificate issuer name matches the issuer subject name and that the certificate is signed by the issuer's private key. No other validation is performed. """ # Runtime isinstance checks need this since the rust class is not a subclass. Certificate.register(rust_x509.Certificate) class RevokedCertificate(metaclass=abc.ABCMeta): @property @abc.abstractmethod def serial_number(self) -> int: """ Returns the serial number of the revoked certificate. """ @property @abc.abstractmethod def revocation_date(self) -> datetime.datetime: """ Returns the date of when this certificate was revoked. """ @property @abc.abstractmethod def revocation_date_utc(self) -> datetime.datetime: """ Returns the date of when this certificate was revoked as a non-naive UTC datetime. """ @property @abc.abstractmethod def extensions(self) -> Extensions: """ Returns an Extensions object containing a list of Revoked extensions. """ # Runtime isinstance checks need this since the rust class is not a subclass. RevokedCertificate.register(rust_x509.RevokedCertificate) class _RawRevokedCertificate(RevokedCertificate): def __init__( self, serial_number: int, revocation_date: datetime.datetime, extensions: Extensions, ): self._serial_number = serial_number self._revocation_date = revocation_date self._extensions = extensions @property def serial_number(self) -> int: return self._serial_number @property def revocation_date(self) -> datetime.datetime: warnings.warn( "Properties that return a naïve datetime object have been " "deprecated. Please switch to revocation_date_utc.", utils.DeprecatedIn42, stacklevel=2, ) return self._revocation_date @property def revocation_date_utc(self) -> datetime.datetime: return self._revocation_date.replace(tzinfo=datetime.timezone.utc) @property def extensions(self) -> Extensions: return self._extensions class CertificateRevocationList(metaclass=abc.ABCMeta): @abc.abstractmethod def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Serializes the CRL to PEM or DER format. """ @abc.abstractmethod def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: """ Returns bytes using digest passed. """ @abc.abstractmethod def get_revoked_certificate_by_serial_number( self, serial_number: int ) -> RevokedCertificate | None: """ Returns an instance of RevokedCertificate or None if the serial_number is not in the CRL. """ @property @abc.abstractmethod def signature_hash_algorithm( self, ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. """ @property @abc.abstractmethod def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ @property @abc.abstractmethod def signature_algorithm_parameters( self, ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA: """ Returns the signature algorithm parameters. """ @property @abc.abstractmethod def issuer(self) -> Name: """ Returns the X509Name with the issuer of this CRL. """ @property @abc.abstractmethod def next_update(self) -> datetime.datetime | None: """ Returns the date of next update for this CRL. """ @property @abc.abstractmethod def next_update_utc(self) -> datetime.datetime | None: """ Returns the date of next update for this CRL as a non-naive UTC datetime. """ @property @abc.abstractmethod def last_update(self) -> datetime.datetime: """ Returns the date of last update for this CRL. """ @property @abc.abstractmethod def last_update_utc(self) -> datetime.datetime: """ Returns the date of last update for this CRL as a non-naive UTC datetime. """ @property @abc.abstractmethod def extensions(self) -> Extensions: """ Returns an Extensions object containing a list of CRL extensions. """ @property @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature bytes. """ @property @abc.abstractmethod def tbs_certlist_bytes(self) -> bytes: """ Returns the tbsCertList payload bytes as defined in RFC 5280. """ @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ @abc.abstractmethod def __len__(self) -> int: """ Number of revoked certificates in the CRL. """ @typing.overload def __getitem__(self, idx: int) -> RevokedCertificate: ... @typing.overload def __getitem__(self, idx: slice) -> list[RevokedCertificate]: ... @abc.abstractmethod def __getitem__( self, idx: int | slice ) -> RevokedCertificate | list[RevokedCertificate]: """ Returns a revoked certificate (or slice of revoked certificates). """ @abc.abstractmethod def __iter__(self) -> typing.Iterator[RevokedCertificate]: """ Iterator over the revoked certificates """ @abc.abstractmethod def is_signature_valid( self, public_key: CertificateIssuerPublicKeyTypes ) -> bool: """ Verifies signature of revocation list against given public key. """ CertificateRevocationList.register(rust_x509.CertificateRevocationList) class CertificateSigningRequest(metaclass=abc.ABCMeta): @abc.abstractmethod def __eq__(self, other: object) -> bool: """ Checks equality. """ @abc.abstractmethod def __hash__(self) -> int: """ Computes a hash. """ @abc.abstractmethod def public_key(self) -> CertificatePublicKeyTypes: """ Returns the public key """ @property @abc.abstractmethod def subject(self) -> Name: """ Returns the subject name object. """ @property @abc.abstractmethod def signature_hash_algorithm( self, ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate. """ @property @abc.abstractmethod def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ @property @abc.abstractmethod def signature_algorithm_parameters( self, ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA: """ Returns the signature algorithm parameters. """ @property @abc.abstractmethod def extensions(self) -> Extensions: """ Returns the extensions in the signing request. """ @property @abc.abstractmethod def attributes(self) -> Attributes: """ Returns an Attributes object. """ @abc.abstractmethod def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Encodes the request to PEM or DER format. """ @property @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature bytes. """ @property @abc.abstractmethod def tbs_certrequest_bytes(self) -> bytes: """ Returns the PKCS#10 CertificationRequestInfo bytes as defined in RFC 2986. """ @property @abc.abstractmethod def is_signature_valid(self) -> bool: """ Verifies signature of signing request. """ @abc.abstractmethod def get_attribute_for_oid(self, oid: ObjectIdentifier) -> bytes: """ Get the attribute value for a given OID. """ # Runtime isinstance checks need this since the rust class is not a subclass. CertificateSigningRequest.register(rust_x509.CertificateSigningRequest) load_pem_x509_certificate = rust_x509.load_pem_x509_certificate load_der_x509_certificate = rust_x509.load_der_x509_certificate load_pem_x509_certificates = rust_x509.load_pem_x509_certificates load_pem_x509_csr = rust_x509.load_pem_x509_csr load_der_x509_csr = rust_x509.load_der_x509_csr load_pem_x509_crl = rust_x509.load_pem_x509_crl load_der_x509_crl = rust_x509.load_der_x509_crl class CertificateSigningRequestBuilder: def __init__( self, subject_name: Name | None = None, extensions: list[Extension[ExtensionType]] = [], attributes: list[tuple[ObjectIdentifier, bytes, int | None]] = [], ): """ Creates an empty X.509 certificate request (v1). """ self._subject_name = subject_name self._extensions = extensions self._attributes = attributes def subject_name(self, name: Name) -> CertificateSigningRequestBuilder: """ Sets the certificate requestor's distinguished name. """ if not isinstance(name, Name): raise TypeError("Expecting x509.Name object.") if self._subject_name is not None: raise ValueError("The subject name may only be set once.") return CertificateSigningRequestBuilder( name, self._extensions, self._attributes ) def add_extension( self, extval: ExtensionType, critical: bool ) -> CertificateSigningRequestBuilder: """ Adds an X.509 extension to the certificate request. """ if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return CertificateSigningRequestBuilder( self._subject_name, [*self._extensions, extension], self._attributes, ) def add_attribute( self, oid: ObjectIdentifier, value: bytes, *, _tag: _ASN1Type | None = None, ) -> CertificateSigningRequestBuilder: """ Adds an X.509 attribute with an OID and associated value. """ if not isinstance(oid, ObjectIdentifier): raise TypeError("oid must be an ObjectIdentifier") if not isinstance(value, bytes): raise TypeError("value must be bytes") if _tag is not None and not isinstance(_tag, _ASN1Type): raise TypeError("tag must be _ASN1Type") _reject_duplicate_attribute(oid, self._attributes) if _tag is not None: tag = _tag.value else: tag = None return CertificateSigningRequestBuilder( self._subject_name, self._extensions, [*self._attributes, (oid, value, tag)], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, algorithm: _AllowedHashTypes | None, backend: typing.Any = None, *, rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> CertificateSigningRequest: """ Signs the request using the requestor's private key. """ if self._subject_name is None: raise ValueError("A CertificateSigningRequest must have a subject") if rsa_padding is not None: if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): raise TypeError("Padding must be PSS or PKCS1v15") if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") return rust_x509.create_x509_csr( self, private_key, algorithm, rsa_padding ) class CertificateBuilder: _extensions: list[Extension[ExtensionType]] def __init__( self, issuer_name: Name | None = None, subject_name: Name | None = None, public_key: CertificatePublicKeyTypes | None = None, serial_number: int | None = None, not_valid_before: datetime.datetime | None = None, not_valid_after: datetime.datetime | None = None, extensions: list[Extension[ExtensionType]] = [], ) -> None: self._version = Version.v3 self._issuer_name = issuer_name self._subject_name = subject_name self._public_key = public_key self._serial_number = serial_number self._not_valid_before = not_valid_before self._not_valid_after = not_valid_after self._extensions = extensions def issuer_name(self, name: Name) -> CertificateBuilder: """ Sets the CA's distinguished name. """ if not isinstance(name, Name): raise TypeError("Expecting x509.Name object.") if self._issuer_name is not None: raise ValueError("The issuer name may only be set once.") return CertificateBuilder( name, self._subject_name, self._public_key, self._serial_number, self._not_valid_before, self._not_valid_after, self._extensions, ) def subject_name(self, name: Name) -> CertificateBuilder: """ Sets the requestor's distinguished name. """ if not isinstance(name, Name): raise TypeError("Expecting x509.Name object.") if self._subject_name is not None: raise ValueError("The subject name may only be set once.") return CertificateBuilder( self._issuer_name, name, self._public_key, self._serial_number, self._not_valid_before, self._not_valid_after, self._extensions, ) def public_key( self, key: CertificatePublicKeyTypes, ) -> CertificateBuilder: """ Sets the requestor's public key (as found in the signing request). """ if not isinstance( key, ( dsa.DSAPublicKey, rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey, ed448.Ed448PublicKey, x25519.X25519PublicKey, x448.X448PublicKey, ), ): raise TypeError( "Expecting one of DSAPublicKey, RSAPublicKey," " EllipticCurvePublicKey, Ed25519PublicKey," " Ed448PublicKey, X25519PublicKey, or " "X448PublicKey." ) if self._public_key is not None: raise ValueError("The public key may only be set once.") return CertificateBuilder( self._issuer_name, self._subject_name, key, self._serial_number, self._not_valid_before, self._not_valid_after, self._extensions, ) def serial_number(self, number: int) -> CertificateBuilder: """ Sets the certificate serial number. """ if not isinstance(number, int): raise TypeError("Serial number must be of integral type.") if self._serial_number is not None: raise ValueError("The serial number may only be set once.") if number <= 0: raise ValueError("The serial number should be positive.") # ASN.1 integers are always signed, so most significant bit must be # zero. if number.bit_length() >= 160: # As defined in RFC 5280 raise ValueError( "The serial number should not be more than 159 bits." ) return CertificateBuilder( self._issuer_name, self._subject_name, self._public_key, number, self._not_valid_before, self._not_valid_after, self._extensions, ) def not_valid_before(self, time: datetime.datetime) -> CertificateBuilder: """ Sets the certificate activation time. """ if not isinstance(time, datetime.datetime): raise TypeError("Expecting datetime object.") if self._not_valid_before is not None: raise ValueError("The not valid before may only be set once.") time = _convert_to_naive_utc_time(time) if time < _EARLIEST_UTC_TIME: raise ValueError( "The not valid before date must be on or after" " 1950 January 1)." ) if self._not_valid_after is not None and time > self._not_valid_after: raise ValueError( "The not valid before date must be before the not valid after " "date." ) return CertificateBuilder( self._issuer_name, self._subject_name, self._public_key, self._serial_number, time, self._not_valid_after, self._extensions, ) def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder: """ Sets the certificate expiration time. """ if not isinstance(time, datetime.datetime): raise TypeError("Expecting datetime object.") if self._not_valid_after is not None: raise ValueError("The not valid after may only be set once.") time = _convert_to_naive_utc_time(time) if time < _EARLIEST_UTC_TIME: raise ValueError( "The not valid after date must be on or after" " 1950 January 1." ) if ( self._not_valid_before is not None and time < self._not_valid_before ): raise ValueError( "The not valid after date must be after the not valid before " "date." ) return CertificateBuilder( self._issuer_name, self._subject_name, self._public_key, self._serial_number, self._not_valid_before, time, self._extensions, ) def add_extension( self, extval: ExtensionType, critical: bool ) -> CertificateBuilder: """ Adds an X.509 extension to the certificate. """ if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return CertificateBuilder( self._issuer_name, self._subject_name, self._public_key, self._serial_number, self._not_valid_before, self._not_valid_after, [*self._extensions, extension], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, algorithm: _AllowedHashTypes | None, backend: typing.Any = None, *, rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> Certificate: """ Signs the certificate using the CA's private key. """ if self._subject_name is None: raise ValueError("A certificate must have a subject name") if self._issuer_name is None: raise ValueError("A certificate must have an issuer name") if self._serial_number is None: raise ValueError("A certificate must have a serial number") if self._not_valid_before is None: raise ValueError("A certificate must have a not valid before time") if self._not_valid_after is None: raise ValueError("A certificate must have a not valid after time") if self._public_key is None: raise ValueError("A certificate must have a public key") if rsa_padding is not None: if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): raise TypeError("Padding must be PSS or PKCS1v15") if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") return rust_x509.create_x509_certificate( self, private_key, algorithm, rsa_padding ) class CertificateRevocationListBuilder: _extensions: list[Extension[ExtensionType]] _revoked_certificates: list[RevokedCertificate] def __init__( self, issuer_name: Name | None = None, last_update: datetime.datetime | None = None, next_update: datetime.datetime | None = None, extensions: list[Extension[ExtensionType]] = [], revoked_certificates: list[RevokedCertificate] = [], ): self._issuer_name = issuer_name self._last_update = last_update self._next_update = next_update self._extensions = extensions self._revoked_certificates = revoked_certificates def issuer_name( self, issuer_name: Name ) -> CertificateRevocationListBuilder: if not isinstance(issuer_name, Name): raise TypeError("Expecting x509.Name object.") if self._issuer_name is not None: raise ValueError("The issuer name may only be set once.") return CertificateRevocationListBuilder( issuer_name, self._last_update, self._next_update, self._extensions, self._revoked_certificates, ) def last_update( self, last_update: datetime.datetime ) -> CertificateRevocationListBuilder: if not isinstance(last_update, datetime.datetime): raise TypeError("Expecting datetime object.") if self._last_update is not None: raise ValueError("Last update may only be set once.") last_update = _convert_to_naive_utc_time(last_update) if last_update < _EARLIEST_UTC_TIME: raise ValueError( "The last update date must be on or after 1950 January 1." ) if self._next_update is not None and last_update > self._next_update: raise ValueError( "The last update date must be before the next update date." ) return CertificateRevocationListBuilder( self._issuer_name, last_update, self._next_update, self._extensions, self._revoked_certificates, ) def next_update( self, next_update: datetime.datetime ) -> CertificateRevocationListBuilder: if not isinstance(next_update, datetime.datetime): raise TypeError("Expecting datetime object.") if self._next_update is not None: raise ValueError("Last update may only be set once.") next_update = _convert_to_naive_utc_time(next_update) if next_update < _EARLIEST_UTC_TIME: raise ValueError( "The last update date must be on or after 1950 January 1." ) if self._last_update is not None and next_update < self._last_update: raise ValueError( "The next update date must be after the last update date." ) return CertificateRevocationListBuilder( self._issuer_name, self._last_update, next_update, self._extensions, self._revoked_certificates, ) def add_extension( self, extval: ExtensionType, critical: bool ) -> CertificateRevocationListBuilder: """ Adds an X.509 extension to the certificate revocation list. """ if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return CertificateRevocationListBuilder( self._issuer_name, self._last_update, self._next_update, [*self._extensions, extension], self._revoked_certificates, ) def add_revoked_certificate( self, revoked_certificate: RevokedCertificate ) -> CertificateRevocationListBuilder: """ Adds a revoked certificate to the CRL. """ if not isinstance(revoked_certificate, RevokedCertificate): raise TypeError("Must be an instance of RevokedCertificate") return CertificateRevocationListBuilder( self._issuer_name, self._last_update, self._next_update, self._extensions, [*self._revoked_certificates, revoked_certificate], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, algorithm: _AllowedHashTypes | None, backend: typing.Any = None, *, rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> CertificateRevocationList: if self._issuer_name is None: raise ValueError("A CRL must have an issuer name") if self._last_update is None: raise ValueError("A CRL must have a last update time") if self._next_update is None: raise ValueError("A CRL must have a next update time") if rsa_padding is not None: if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): raise TypeError("Padding must be PSS or PKCS1v15") if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") return rust_x509.create_x509_crl( self, private_key, algorithm, rsa_padding ) class RevokedCertificateBuilder: def __init__( self, serial_number: int | None = None, revocation_date: datetime.datetime | None = None, extensions: list[Extension[ExtensionType]] = [], ): self._serial_number = serial_number self._revocation_date = revocation_date self._extensions = extensions def serial_number(self, number: int) -> RevokedCertificateBuilder: if not isinstance(number, int): raise TypeError("Serial number must be of integral type.") if self._serial_number is not None: raise ValueError("The serial number may only be set once.") if number <= 0: raise ValueError("The serial number should be positive") # ASN.1 integers are always signed, so most significant bit must be # zero. if number.bit_length() >= 160: # As defined in RFC 5280 raise ValueError( "The serial number should not be more than 159 bits." ) return RevokedCertificateBuilder( number, self._revocation_date, self._extensions ) def revocation_date( self, time: datetime.datetime ) -> RevokedCertificateBuilder: if not isinstance(time, datetime.datetime): raise TypeError("Expecting datetime object.") if self._revocation_date is not None: raise ValueError("The revocation date may only be set once.") time = _convert_to_naive_utc_time(time) if time < _EARLIEST_UTC_TIME: raise ValueError( "The revocation date must be on or after 1950 January 1." ) return RevokedCertificateBuilder( self._serial_number, time, self._extensions ) def add_extension( self, extval: ExtensionType, critical: bool ) -> RevokedCertificateBuilder: if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") extension = Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return RevokedCertificateBuilder( self._serial_number, self._revocation_date, [*self._extensions, extension], ) def build(self, backend: typing.Any = None) -> RevokedCertificate: if self._serial_number is None: raise ValueError("A revoked certificate must have a serial number") if self._revocation_date is None: raise ValueError( "A revoked certificate must have a revocation date" ) return _RawRevokedCertificate( self._serial_number, self._revocation_date, Extensions(self._extensions), ) def random_serial_number() -> int: return int.from_bytes(os.urandom(20), "big") >> 1 cryptography-43.0.0/src/cryptography/x509/certificate_transparency.py010064400017510000177000000043251464676315000242110ustar 00000000000000# 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. from __future__ import annotations import abc import datetime from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.hazmat.primitives.hashes import HashAlgorithm class LogEntryType(utils.Enum): X509_CERTIFICATE = 0 PRE_CERTIFICATE = 1 class Version(utils.Enum): v1 = 0 class SignatureAlgorithm(utils.Enum): """ Signature algorithms that are valid for SCTs. These are exactly the same as SignatureAlgorithm in RFC 5246 (TLS 1.2). See: """ ANONYMOUS = 0 RSA = 1 DSA = 2 ECDSA = 3 class SignedCertificateTimestamp(metaclass=abc.ABCMeta): @property @abc.abstractmethod def version(self) -> Version: """ Returns the SCT version. """ @property @abc.abstractmethod def log_id(self) -> bytes: """ Returns an identifier indicating which log this SCT is for. """ @property @abc.abstractmethod def timestamp(self) -> datetime.datetime: """ Returns the timestamp for this SCT. """ @property @abc.abstractmethod def entry_type(self) -> LogEntryType: """ Returns whether this is an SCT for a certificate or pre-certificate. """ @property @abc.abstractmethod def signature_hash_algorithm(self) -> HashAlgorithm: """ Returns the hash algorithm used for the SCT's signature. """ @property @abc.abstractmethod def signature_algorithm(self) -> SignatureAlgorithm: """ Returns the signing algorithm used for the SCT's signature. """ @property @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature for this SCT. """ @property @abc.abstractmethod def extension_bytes(self) -> bytes: """ Returns the raw bytes of any extensions for this SCT. """ SignedCertificateTimestamp.register(rust_x509.Sct) cryptography-43.0.0/src/cryptography/x509/extensions.py010064400017510000177000002034521464676315000213370ustar 00000000000000# 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. from __future__ import annotations import abc import datetime import hashlib import ipaddress import typing from cryptography import utils from cryptography.hazmat.bindings._rust import asn1 from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.hazmat.primitives import constant_time, serialization from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPublicKeyTypes, CertificatePublicKeyTypes, ) from cryptography.x509.certificate_transparency import ( SignedCertificateTimestamp, ) from cryptography.x509.general_name import ( DirectoryName, DNSName, GeneralName, IPAddress, OtherName, RegisteredID, RFC822Name, UniformResourceIdentifier, _IPAddressTypes, ) from cryptography.x509.name import Name, RelativeDistinguishedName from cryptography.x509.oid import ( CRLEntryExtensionOID, ExtensionOID, ObjectIdentifier, OCSPExtensionOID, ) ExtensionTypeVar = typing.TypeVar( "ExtensionTypeVar", bound="ExtensionType", covariant=True ) def _key_identifier_from_public_key( public_key: CertificatePublicKeyTypes, ) -> bytes: if isinstance(public_key, RSAPublicKey): data = public_key.public_bytes( serialization.Encoding.DER, serialization.PublicFormat.PKCS1, ) elif isinstance(public_key, EllipticCurvePublicKey): data = public_key.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint, ) else: # This is a very slow way to do this. serialized = public_key.public_bytes( serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo, ) data = asn1.parse_spki_for_data(serialized) return hashlib.sha1(data).digest() def _make_sequence_methods(field_name: str): def len_method(self) -> int: return len(getattr(self, field_name)) def iter_method(self): return iter(getattr(self, field_name)) def getitem_method(self, idx): return getattr(self, field_name)[idx] return len_method, iter_method, getitem_method class DuplicateExtension(Exception): def __init__(self, msg: str, oid: ObjectIdentifier) -> None: super().__init__(msg) self.oid = oid class ExtensionNotFound(Exception): def __init__(self, msg: str, oid: ObjectIdentifier) -> None: super().__init__(msg) self.oid = oid class ExtensionType(metaclass=abc.ABCMeta): oid: typing.ClassVar[ObjectIdentifier] def public_bytes(self) -> bytes: """ Serializes the extension type to DER. """ raise NotImplementedError( f"public_bytes is not implemented for extension type {self!r}" ) class Extensions: def __init__( self, extensions: typing.Iterable[Extension[ExtensionType]] ) -> None: self._extensions = list(extensions) def get_extension_for_oid( self, oid: ObjectIdentifier ) -> Extension[ExtensionType]: for ext in self: if ext.oid == oid: return ext raise ExtensionNotFound(f"No {oid} extension was found", oid) def get_extension_for_class( self, extclass: type[ExtensionTypeVar] ) -> Extension[ExtensionTypeVar]: if extclass is UnrecognizedExtension: raise TypeError( "UnrecognizedExtension can't be used with " "get_extension_for_class because more than one instance of the" " class may be present." ) for ext in self: if isinstance(ext.value, extclass): return ext raise ExtensionNotFound( f"No {extclass} extension was found", extclass.oid ) __len__, __iter__, __getitem__ = _make_sequence_methods("_extensions") def __repr__(self) -> str: return f"" class CRLNumber(ExtensionType): oid = ExtensionOID.CRL_NUMBER def __init__(self, crl_number: int) -> None: if not isinstance(crl_number, int): raise TypeError("crl_number must be an integer") self._crl_number = crl_number def __eq__(self, other: object) -> bool: if not isinstance(other, CRLNumber): return NotImplemented return self.crl_number == other.crl_number def __hash__(self) -> int: return hash(self.crl_number) def __repr__(self) -> str: return f"" @property def crl_number(self) -> int: return self._crl_number def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class AuthorityKeyIdentifier(ExtensionType): oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER def __init__( self, key_identifier: bytes | None, authority_cert_issuer: typing.Iterable[GeneralName] | None, authority_cert_serial_number: int | None, ) -> None: if (authority_cert_issuer is None) != ( authority_cert_serial_number is None ): raise ValueError( "authority_cert_issuer and authority_cert_serial_number " "must both be present or both None" ) if authority_cert_issuer is not None: authority_cert_issuer = list(authority_cert_issuer) if not all( isinstance(x, GeneralName) for x in authority_cert_issuer ): raise TypeError( "authority_cert_issuer must be a list of GeneralName " "objects" ) if authority_cert_serial_number is not None and not isinstance( authority_cert_serial_number, int ): raise TypeError("authority_cert_serial_number must be an integer") self._key_identifier = key_identifier self._authority_cert_issuer = authority_cert_issuer self._authority_cert_serial_number = authority_cert_serial_number # This takes a subset of CertificatePublicKeyTypes because an issuer # cannot have an X25519/X448 key. This introduces some unfortunate # asymmetry that requires typing users to explicitly # narrow their type, but we should make this accurate and not just # convenient. @classmethod def from_issuer_public_key( cls, public_key: CertificateIssuerPublicKeyTypes ) -> AuthorityKeyIdentifier: digest = _key_identifier_from_public_key(public_key) return cls( key_identifier=digest, authority_cert_issuer=None, authority_cert_serial_number=None, ) @classmethod def from_issuer_subject_key_identifier( cls, ski: SubjectKeyIdentifier ) -> AuthorityKeyIdentifier: return cls( key_identifier=ski.digest, authority_cert_issuer=None, authority_cert_serial_number=None, ) def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, AuthorityKeyIdentifier): return NotImplemented return ( self.key_identifier == other.key_identifier and self.authority_cert_issuer == other.authority_cert_issuer and self.authority_cert_serial_number == other.authority_cert_serial_number ) def __hash__(self) -> int: if self.authority_cert_issuer is None: aci = None else: aci = tuple(self.authority_cert_issuer) return hash( (self.key_identifier, aci, self.authority_cert_serial_number) ) @property def key_identifier(self) -> bytes | None: return self._key_identifier @property def authority_cert_issuer( self, ) -> list[GeneralName] | None: return self._authority_cert_issuer @property def authority_cert_serial_number(self) -> int | None: return self._authority_cert_serial_number def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class SubjectKeyIdentifier(ExtensionType): oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER def __init__(self, digest: bytes) -> None: self._digest = digest @classmethod def from_public_key( cls, public_key: CertificatePublicKeyTypes ) -> SubjectKeyIdentifier: return cls(_key_identifier_from_public_key(public_key)) @property def digest(self) -> bytes: return self._digest @property def key_identifier(self) -> bytes: return self._digest def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectKeyIdentifier): return NotImplemented return constant_time.bytes_eq(self.digest, other.digest) def __hash__(self) -> int: return hash(self.digest) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class AuthorityInformationAccess(ExtensionType): oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS def __init__( self, descriptions: typing.Iterable[AccessDescription] ) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( "Every item in the descriptions list must be an " "AccessDescription" ) self._descriptions = descriptions __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, AuthorityInformationAccess): return NotImplemented return self._descriptions == other._descriptions def __hash__(self) -> int: return hash(tuple(self._descriptions)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class SubjectInformationAccess(ExtensionType): oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS def __init__( self, descriptions: typing.Iterable[AccessDescription] ) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( "Every item in the descriptions list must be an " "AccessDescription" ) self._descriptions = descriptions __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectInformationAccess): return NotImplemented return self._descriptions == other._descriptions def __hash__(self) -> int: return hash(tuple(self._descriptions)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class AccessDescription: def __init__( self, access_method: ObjectIdentifier, access_location: GeneralName ) -> None: if not isinstance(access_method, ObjectIdentifier): raise TypeError("access_method must be an ObjectIdentifier") if not isinstance(access_location, GeneralName): raise TypeError("access_location must be a GeneralName") self._access_method = access_method self._access_location = access_location def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, AccessDescription): return NotImplemented return ( self.access_method == other.access_method and self.access_location == other.access_location ) def __hash__(self) -> int: return hash((self.access_method, self.access_location)) @property def access_method(self) -> ObjectIdentifier: return self._access_method @property def access_location(self) -> GeneralName: return self._access_location class BasicConstraints(ExtensionType): oid = ExtensionOID.BASIC_CONSTRAINTS def __init__(self, ca: bool, path_length: int | None) -> None: if not isinstance(ca, bool): raise TypeError("ca must be a boolean value") if path_length is not None and not ca: raise ValueError("path_length must be None when ca is False") if path_length is not None and ( not isinstance(path_length, int) or path_length < 0 ): raise TypeError( "path_length must be a non-negative integer or None" ) self._ca = ca self._path_length = path_length @property def ca(self) -> bool: return self._ca @property def path_length(self) -> int | None: return self._path_length def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, BasicConstraints): return NotImplemented return self.ca == other.ca and self.path_length == other.path_length def __hash__(self) -> int: return hash((self.ca, self.path_length)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class DeltaCRLIndicator(ExtensionType): oid = ExtensionOID.DELTA_CRL_INDICATOR def __init__(self, crl_number: int) -> None: if not isinstance(crl_number, int): raise TypeError("crl_number must be an integer") self._crl_number = crl_number @property def crl_number(self) -> int: return self._crl_number def __eq__(self, other: object) -> bool: if not isinstance(other, DeltaCRLIndicator): return NotImplemented return self.crl_number == other.crl_number def __hash__(self) -> int: return hash(self.crl_number) def __repr__(self) -> str: return f"" def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class CRLDistributionPoints(ExtensionType): oid = ExtensionOID.CRL_DISTRIBUTION_POINTS def __init__( self, distribution_points: typing.Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( isinstance(x, DistributionPoint) for x in distribution_points ): raise TypeError( "distribution_points must be a list of DistributionPoint " "objects" ) self._distribution_points = distribution_points __len__, __iter__, __getitem__ = _make_sequence_methods( "_distribution_points" ) def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CRLDistributionPoints): return NotImplemented return self._distribution_points == other._distribution_points def __hash__(self) -> int: return hash(tuple(self._distribution_points)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class FreshestCRL(ExtensionType): oid = ExtensionOID.FRESHEST_CRL def __init__( self, distribution_points: typing.Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( isinstance(x, DistributionPoint) for x in distribution_points ): raise TypeError( "distribution_points must be a list of DistributionPoint " "objects" ) self._distribution_points = distribution_points __len__, __iter__, __getitem__ = _make_sequence_methods( "_distribution_points" ) def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, FreshestCRL): return NotImplemented return self._distribution_points == other._distribution_points def __hash__(self) -> int: return hash(tuple(self._distribution_points)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class DistributionPoint: def __init__( self, full_name: typing.Iterable[GeneralName] | None, relative_name: RelativeDistinguishedName | None, reasons: frozenset[ReasonFlags] | None, crl_issuer: typing.Iterable[GeneralName] | None, ) -> None: if full_name and relative_name: raise ValueError( "You cannot provide both full_name and relative_name, at " "least one must be None." ) if not full_name and not relative_name and not crl_issuer: raise ValueError( "Either full_name, relative_name or crl_issuer must be " "provided." ) if full_name is not None: full_name = list(full_name) if not all(isinstance(x, GeneralName) for x in full_name): raise TypeError( "full_name must be a list of GeneralName objects" ) if relative_name: if not isinstance(relative_name, RelativeDistinguishedName): raise TypeError( "relative_name must be a RelativeDistinguishedName" ) if crl_issuer is not None: crl_issuer = list(crl_issuer) if not all(isinstance(x, GeneralName) for x in crl_issuer): raise TypeError( "crl_issuer must be None or a list of general names" ) if reasons and ( not isinstance(reasons, frozenset) or not all(isinstance(x, ReasonFlags) for x in reasons) ): raise TypeError("reasons must be None or frozenset of ReasonFlags") if reasons and ( ReasonFlags.unspecified in reasons or ReasonFlags.remove_from_crl in reasons ): raise ValueError( "unspecified and remove_from_crl are not valid reasons in a " "DistributionPoint" ) self._full_name = full_name self._relative_name = relative_name self._reasons = reasons self._crl_issuer = crl_issuer def __repr__(self) -> str: return ( "".format(self) ) def __eq__(self, other: object) -> bool: if not isinstance(other, DistributionPoint): return NotImplemented return ( self.full_name == other.full_name and self.relative_name == other.relative_name and self.reasons == other.reasons and self.crl_issuer == other.crl_issuer ) def __hash__(self) -> int: if self.full_name is not None: fn: tuple[GeneralName, ...] | None = tuple(self.full_name) else: fn = None if self.crl_issuer is not None: crl_issuer: tuple[GeneralName, ...] | None = tuple(self.crl_issuer) else: crl_issuer = None return hash((fn, self.relative_name, self.reasons, crl_issuer)) @property def full_name(self) -> list[GeneralName] | None: return self._full_name @property def relative_name(self) -> RelativeDistinguishedName | None: return self._relative_name @property def reasons(self) -> frozenset[ReasonFlags] | None: return self._reasons @property def crl_issuer(self) -> list[GeneralName] | None: return self._crl_issuer class ReasonFlags(utils.Enum): unspecified = "unspecified" key_compromise = "keyCompromise" ca_compromise = "cACompromise" affiliation_changed = "affiliationChanged" superseded = "superseded" cessation_of_operation = "cessationOfOperation" certificate_hold = "certificateHold" privilege_withdrawn = "privilegeWithdrawn" aa_compromise = "aACompromise" remove_from_crl = "removeFromCRL" # These are distribution point bit string mappings. Not to be confused with # CRLReason reason flags bit string mappings. # ReasonFlags ::= BIT STRING { # unused (0), # keyCompromise (1), # cACompromise (2), # affiliationChanged (3), # superseded (4), # cessationOfOperation (5), # certificateHold (6), # privilegeWithdrawn (7), # aACompromise (8) } _REASON_BIT_MAPPING = { 1: ReasonFlags.key_compromise, 2: ReasonFlags.ca_compromise, 3: ReasonFlags.affiliation_changed, 4: ReasonFlags.superseded, 5: ReasonFlags.cessation_of_operation, 6: ReasonFlags.certificate_hold, 7: ReasonFlags.privilege_withdrawn, 8: ReasonFlags.aa_compromise, } _CRLREASONFLAGS = { ReasonFlags.key_compromise: 1, ReasonFlags.ca_compromise: 2, ReasonFlags.affiliation_changed: 3, ReasonFlags.superseded: 4, ReasonFlags.cessation_of_operation: 5, ReasonFlags.certificate_hold: 6, ReasonFlags.privilege_withdrawn: 7, ReasonFlags.aa_compromise: 8, } # CRLReason ::= ENUMERATED { # unspecified (0), # keyCompromise (1), # cACompromise (2), # affiliationChanged (3), # superseded (4), # cessationOfOperation (5), # certificateHold (6), # -- value 7 is not used # removeFromCRL (8), # privilegeWithdrawn (9), # aACompromise (10) } _CRL_ENTRY_REASON_ENUM_TO_CODE = { ReasonFlags.unspecified: 0, ReasonFlags.key_compromise: 1, ReasonFlags.ca_compromise: 2, ReasonFlags.affiliation_changed: 3, ReasonFlags.superseded: 4, ReasonFlags.cessation_of_operation: 5, ReasonFlags.certificate_hold: 6, ReasonFlags.remove_from_crl: 8, ReasonFlags.privilege_withdrawn: 9, ReasonFlags.aa_compromise: 10, } class PolicyConstraints(ExtensionType): oid = ExtensionOID.POLICY_CONSTRAINTS def __init__( self, require_explicit_policy: int | None, inhibit_policy_mapping: int | None, ) -> None: if require_explicit_policy is not None and not isinstance( require_explicit_policy, int ): raise TypeError( "require_explicit_policy must be a non-negative integer or " "None" ) if inhibit_policy_mapping is not None and not isinstance( inhibit_policy_mapping, int ): raise TypeError( "inhibit_policy_mapping must be a non-negative integer or None" ) if inhibit_policy_mapping is None and require_explicit_policy is None: raise ValueError( "At least one of require_explicit_policy and " "inhibit_policy_mapping must not be None" ) self._require_explicit_policy = require_explicit_policy self._inhibit_policy_mapping = inhibit_policy_mapping def __repr__(self) -> str: return ( "".format(self) ) def __eq__(self, other: object) -> bool: if not isinstance(other, PolicyConstraints): return NotImplemented return ( self.require_explicit_policy == other.require_explicit_policy and self.inhibit_policy_mapping == other.inhibit_policy_mapping ) def __hash__(self) -> int: return hash( (self.require_explicit_policy, self.inhibit_policy_mapping) ) @property def require_explicit_policy(self) -> int | None: return self._require_explicit_policy @property def inhibit_policy_mapping(self) -> int | None: return self._inhibit_policy_mapping def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class CertificatePolicies(ExtensionType): oid = ExtensionOID.CERTIFICATE_POLICIES def __init__(self, policies: typing.Iterable[PolicyInformation]) -> None: policies = list(policies) if not all(isinstance(x, PolicyInformation) for x in policies): raise TypeError( "Every item in the policies list must be a " "PolicyInformation" ) self._policies = policies __len__, __iter__, __getitem__ = _make_sequence_methods("_policies") def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CertificatePolicies): return NotImplemented return self._policies == other._policies def __hash__(self) -> int: return hash(tuple(self._policies)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class PolicyInformation: def __init__( self, policy_identifier: ObjectIdentifier, policy_qualifiers: typing.Iterable[str | UserNotice] | None, ) -> None: if not isinstance(policy_identifier, ObjectIdentifier): raise TypeError("policy_identifier must be an ObjectIdentifier") self._policy_identifier = policy_identifier if policy_qualifiers is not None: policy_qualifiers = list(policy_qualifiers) if not all( isinstance(x, (str, UserNotice)) for x in policy_qualifiers ): raise TypeError( "policy_qualifiers must be a list of strings and/or " "UserNotice objects or None" ) self._policy_qualifiers = policy_qualifiers def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, PolicyInformation): return NotImplemented return ( self.policy_identifier == other.policy_identifier and self.policy_qualifiers == other.policy_qualifiers ) def __hash__(self) -> int: if self.policy_qualifiers is not None: pq: tuple[str | UserNotice, ...] | None = tuple( self.policy_qualifiers ) else: pq = None return hash((self.policy_identifier, pq)) @property def policy_identifier(self) -> ObjectIdentifier: return self._policy_identifier @property def policy_qualifiers( self, ) -> list[str | UserNotice] | None: return self._policy_qualifiers class UserNotice: def __init__( self, notice_reference: NoticeReference | None, explicit_text: str | None, ) -> None: if notice_reference and not isinstance( notice_reference, NoticeReference ): raise TypeError( "notice_reference must be None or a NoticeReference" ) self._notice_reference = notice_reference self._explicit_text = explicit_text def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, UserNotice): return NotImplemented return ( self.notice_reference == other.notice_reference and self.explicit_text == other.explicit_text ) def __hash__(self) -> int: return hash((self.notice_reference, self.explicit_text)) @property def notice_reference(self) -> NoticeReference | None: return self._notice_reference @property def explicit_text(self) -> str | None: return self._explicit_text class NoticeReference: def __init__( self, organization: str | None, notice_numbers: typing.Iterable[int], ) -> None: self._organization = organization notice_numbers = list(notice_numbers) if not all(isinstance(x, int) for x in notice_numbers): raise TypeError("notice_numbers must be a list of integers") self._notice_numbers = notice_numbers def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, NoticeReference): return NotImplemented return ( self.organization == other.organization and self.notice_numbers == other.notice_numbers ) def __hash__(self) -> int: return hash((self.organization, tuple(self.notice_numbers))) @property def organization(self) -> str | None: return self._organization @property def notice_numbers(self) -> list[int]: return self._notice_numbers class ExtendedKeyUsage(ExtensionType): oid = ExtensionOID.EXTENDED_KEY_USAGE def __init__(self, usages: typing.Iterable[ObjectIdentifier]) -> None: usages = list(usages) if not all(isinstance(x, ObjectIdentifier) for x in usages): raise TypeError( "Every item in the usages list must be an ObjectIdentifier" ) self._usages = usages __len__, __iter__, __getitem__ = _make_sequence_methods("_usages") def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, ExtendedKeyUsage): return NotImplemented return self._usages == other._usages def __hash__(self) -> int: return hash(tuple(self._usages)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class OCSPNoCheck(ExtensionType): oid = ExtensionOID.OCSP_NO_CHECK def __eq__(self, other: object) -> bool: if not isinstance(other, OCSPNoCheck): return NotImplemented return True def __hash__(self) -> int: return hash(OCSPNoCheck) def __repr__(self) -> str: return "" def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class PrecertPoison(ExtensionType): oid = ExtensionOID.PRECERT_POISON def __eq__(self, other: object) -> bool: if not isinstance(other, PrecertPoison): return NotImplemented return True def __hash__(self) -> int: return hash(PrecertPoison) def __repr__(self) -> str: return "" def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class TLSFeature(ExtensionType): oid = ExtensionOID.TLS_FEATURE def __init__(self, features: typing.Iterable[TLSFeatureType]) -> None: features = list(features) if ( not all(isinstance(x, TLSFeatureType) for x in features) or len(features) == 0 ): raise TypeError( "features must be a list of elements from the TLSFeatureType " "enum" ) self._features = features __len__, __iter__, __getitem__ = _make_sequence_methods("_features") def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, TLSFeature): return NotImplemented return self._features == other._features def __hash__(self) -> int: return hash(tuple(self._features)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class TLSFeatureType(utils.Enum): # status_request is defined in RFC 6066 and is used for what is commonly # called OCSP Must-Staple when present in the TLS Feature extension in an # X.509 certificate. status_request = 5 # status_request_v2 is defined in RFC 6961 and allows multiple OCSP # responses to be provided. It is not currently in use by clients or # servers. status_request_v2 = 17 _TLS_FEATURE_TYPE_TO_ENUM = {x.value: x for x in TLSFeatureType} class InhibitAnyPolicy(ExtensionType): oid = ExtensionOID.INHIBIT_ANY_POLICY def __init__(self, skip_certs: int) -> None: if not isinstance(skip_certs, int): raise TypeError("skip_certs must be an integer") if skip_certs < 0: raise ValueError("skip_certs must be a non-negative integer") self._skip_certs = skip_certs def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, InhibitAnyPolicy): return NotImplemented return self.skip_certs == other.skip_certs def __hash__(self) -> int: return hash(self.skip_certs) @property def skip_certs(self) -> int: return self._skip_certs def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class KeyUsage(ExtensionType): oid = ExtensionOID.KEY_USAGE def __init__( self, digital_signature: bool, content_commitment: bool, key_encipherment: bool, data_encipherment: bool, key_agreement: bool, key_cert_sign: bool, crl_sign: bool, encipher_only: bool, decipher_only: bool, ) -> None: if not key_agreement and (encipher_only or decipher_only): raise ValueError( "encipher_only and decipher_only can only be true when " "key_agreement is true" ) self._digital_signature = digital_signature self._content_commitment = content_commitment self._key_encipherment = key_encipherment self._data_encipherment = data_encipherment self._key_agreement = key_agreement self._key_cert_sign = key_cert_sign self._crl_sign = crl_sign self._encipher_only = encipher_only self._decipher_only = decipher_only @property def digital_signature(self) -> bool: return self._digital_signature @property def content_commitment(self) -> bool: return self._content_commitment @property def key_encipherment(self) -> bool: return self._key_encipherment @property def data_encipherment(self) -> bool: return self._data_encipherment @property def key_agreement(self) -> bool: return self._key_agreement @property def key_cert_sign(self) -> bool: return self._key_cert_sign @property def crl_sign(self) -> bool: return self._crl_sign @property def encipher_only(self) -> bool: if not self.key_agreement: raise ValueError( "encipher_only is undefined unless key_agreement is true" ) else: return self._encipher_only @property def decipher_only(self) -> bool: if not self.key_agreement: raise ValueError( "decipher_only is undefined unless key_agreement is true" ) else: return self._decipher_only def __repr__(self) -> str: try: encipher_only = self.encipher_only decipher_only = self.decipher_only except ValueError: # Users found None confusing because even though encipher/decipher # have no meaning unless key_agreement is true, to construct an # instance of the class you still need to pass False. encipher_only = False decipher_only = False return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, KeyUsage): return NotImplemented return ( self.digital_signature == other.digital_signature and self.content_commitment == other.content_commitment and self.key_encipherment == other.key_encipherment and self.data_encipherment == other.data_encipherment and self.key_agreement == other.key_agreement and self.key_cert_sign == other.key_cert_sign and self.crl_sign == other.crl_sign and self._encipher_only == other._encipher_only and self._decipher_only == other._decipher_only ) def __hash__(self) -> int: return hash( ( self.digital_signature, self.content_commitment, self.key_encipherment, self.data_encipherment, self.key_agreement, self.key_cert_sign, self.crl_sign, self._encipher_only, self._decipher_only, ) ) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class NameConstraints(ExtensionType): oid = ExtensionOID.NAME_CONSTRAINTS def __init__( self, permitted_subtrees: typing.Iterable[GeneralName] | None, excluded_subtrees: typing.Iterable[GeneralName] | None, ) -> None: if permitted_subtrees is not None: permitted_subtrees = list(permitted_subtrees) if not permitted_subtrees: raise ValueError( "permitted_subtrees must be a non-empty list or None" ) if not all(isinstance(x, GeneralName) for x in permitted_subtrees): raise TypeError( "permitted_subtrees must be a list of GeneralName objects " "or None" ) self._validate_tree(permitted_subtrees) if excluded_subtrees is not None: excluded_subtrees = list(excluded_subtrees) if not excluded_subtrees: raise ValueError( "excluded_subtrees must be a non-empty list or None" ) if not all(isinstance(x, GeneralName) for x in excluded_subtrees): raise TypeError( "excluded_subtrees must be a list of GeneralName objects " "or None" ) self._validate_tree(excluded_subtrees) if permitted_subtrees is None and excluded_subtrees is None: raise ValueError( "At least one of permitted_subtrees and excluded_subtrees " "must not be None" ) self._permitted_subtrees = permitted_subtrees self._excluded_subtrees = excluded_subtrees def __eq__(self, other: object) -> bool: if not isinstance(other, NameConstraints): return NotImplemented return ( self.excluded_subtrees == other.excluded_subtrees and self.permitted_subtrees == other.permitted_subtrees ) def _validate_tree(self, tree: typing.Iterable[GeneralName]) -> None: self._validate_ip_name(tree) self._validate_dns_name(tree) def _validate_ip_name(self, tree: typing.Iterable[GeneralName]) -> None: if any( isinstance(name, IPAddress) and not isinstance( name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network) ) for name in tree ): raise TypeError( "IPAddress name constraints must be an IPv4Network or" " IPv6Network object" ) def _validate_dns_name(self, tree: typing.Iterable[GeneralName]) -> None: if any( isinstance(name, DNSName) and "*" in name.value for name in tree ): raise ValueError( "DNSName name constraints must not contain the '*' wildcard" " character" ) def __repr__(self) -> str: return ( f"" ) def __hash__(self) -> int: if self.permitted_subtrees is not None: ps: tuple[GeneralName, ...] | None = tuple(self.permitted_subtrees) else: ps = None if self.excluded_subtrees is not None: es: tuple[GeneralName, ...] | None = tuple(self.excluded_subtrees) else: es = None return hash((ps, es)) @property def permitted_subtrees( self, ) -> list[GeneralName] | None: return self._permitted_subtrees @property def excluded_subtrees( self, ) -> list[GeneralName] | None: return self._excluded_subtrees def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class Extension(typing.Generic[ExtensionTypeVar]): def __init__( self, oid: ObjectIdentifier, critical: bool, value: ExtensionTypeVar ) -> None: if not isinstance(oid, ObjectIdentifier): raise TypeError( "oid argument must be an ObjectIdentifier instance." ) if not isinstance(critical, bool): raise TypeError("critical must be a boolean value") self._oid = oid self._critical = critical self._value = value @property def oid(self) -> ObjectIdentifier: return self._oid @property def critical(self) -> bool: return self._critical @property def value(self) -> ExtensionTypeVar: return self._value def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, Extension): return NotImplemented return ( self.oid == other.oid and self.critical == other.critical and self.value == other.value ) def __hash__(self) -> int: return hash((self.oid, self.critical, self.value)) class GeneralNames: def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: general_names = list(general_names) if not all(isinstance(x, GeneralName) for x in general_names): raise TypeError( "Every item in the general_names list must be an " "object conforming to the GeneralName interface" ) self._general_names = general_names __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @typing.overload def get_values_for_type( self, type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( self, type: type[OtherName] ) -> list[OtherName]: ... def get_values_for_type( self, type: type[DNSName] | type[DirectoryName] | type[IPAddress] | type[OtherName] | type[RFC822Name] | type[RegisteredID] | type[UniformResourceIdentifier], ) -> ( list[_IPAddressTypes] | list[str] | list[OtherName] | list[Name] | list[ObjectIdentifier] ): # Return the value of each GeneralName, except for OtherName instances # which we return directly because it has two important properties not # just one value. objs = (i for i in self if isinstance(i, type)) if type != OtherName: return [i.value for i in objs] return list(objs) def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, GeneralNames): return NotImplemented return self._general_names == other._general_names def __hash__(self) -> int: return hash(tuple(self._general_names)) class SubjectAlternativeName(ExtensionType): oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @typing.overload def get_values_for_type( self, type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( self, type: type[OtherName] ) -> list[OtherName]: ... def get_values_for_type( self, type: type[DNSName] | type[DirectoryName] | type[IPAddress] | type[OtherName] | type[RFC822Name] | type[RegisteredID] | type[UniformResourceIdentifier], ) -> ( list[_IPAddressTypes] | list[str] | list[OtherName] | list[Name] | list[ObjectIdentifier] ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectAlternativeName): return NotImplemented return self._general_names == other._general_names def __hash__(self) -> int: return hash(self._general_names) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class IssuerAlternativeName(ExtensionType): oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @typing.overload def get_values_for_type( self, type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( self, type: type[OtherName] ) -> list[OtherName]: ... def get_values_for_type( self, type: type[DNSName] | type[DirectoryName] | type[IPAddress] | type[OtherName] | type[RFC822Name] | type[RegisteredID] | type[UniformResourceIdentifier], ) -> ( list[_IPAddressTypes] | list[str] | list[OtherName] | list[Name] | list[ObjectIdentifier] ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, IssuerAlternativeName): return NotImplemented return self._general_names == other._general_names def __hash__(self) -> int: return hash(self._general_names) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class CertificateIssuer(ExtensionType): oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @typing.overload def get_values_for_type( self, type: type[DNSName] | type[UniformResourceIdentifier] | type[RFC822Name], ) -> list[str]: ... @typing.overload def get_values_for_type( self, type: type[DirectoryName], ) -> list[Name]: ... @typing.overload def get_values_for_type( self, type: type[RegisteredID], ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( self, type: type[IPAddress] ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( self, type: type[OtherName] ) -> list[OtherName]: ... def get_values_for_type( self, type: type[DNSName] | type[DirectoryName] | type[IPAddress] | type[OtherName] | type[RFC822Name] | type[RegisteredID] | type[UniformResourceIdentifier], ) -> ( list[_IPAddressTypes] | list[str] | list[OtherName] | list[Name] | list[ObjectIdentifier] ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CertificateIssuer): return NotImplemented return self._general_names == other._general_names def __hash__(self) -> int: return hash(self._general_names) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class CRLReason(ExtensionType): oid = CRLEntryExtensionOID.CRL_REASON def __init__(self, reason: ReasonFlags) -> None: if not isinstance(reason, ReasonFlags): raise TypeError("reason must be an element from ReasonFlags") self._reason = reason def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CRLReason): return NotImplemented return self.reason == other.reason def __hash__(self) -> int: return hash(self.reason) @property def reason(self) -> ReasonFlags: return self._reason def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class InvalidityDate(ExtensionType): oid = CRLEntryExtensionOID.INVALIDITY_DATE def __init__(self, invalidity_date: datetime.datetime) -> None: if not isinstance(invalidity_date, datetime.datetime): raise TypeError("invalidity_date must be a datetime.datetime") self._invalidity_date = invalidity_date def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, InvalidityDate): return NotImplemented return self.invalidity_date == other.invalidity_date def __hash__(self) -> int: return hash(self.invalidity_date) @property def invalidity_date(self) -> datetime.datetime: return self._invalidity_date @property def invalidity_date_utc(self) -> datetime.datetime: if self._invalidity_date.tzinfo is None: return self._invalidity_date.replace(tzinfo=datetime.timezone.utc) else: return self._invalidity_date.astimezone(tz=datetime.timezone.utc) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class PrecertificateSignedCertificateTimestamps(ExtensionType): oid = ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS def __init__( self, signed_certificate_timestamps: typing.Iterable[ SignedCertificateTimestamp ], ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( isinstance(sct, SignedCertificateTimestamp) for sct in signed_certificate_timestamps ): raise TypeError( "Every item in the signed_certificate_timestamps list must be " "a SignedCertificateTimestamp" ) self._signed_certificate_timestamps = signed_certificate_timestamps __len__, __iter__, __getitem__ = _make_sequence_methods( "_signed_certificate_timestamps" ) def __repr__(self) -> str: return f"" def __hash__(self) -> int: return hash(tuple(self._signed_certificate_timestamps)) def __eq__(self, other: object) -> bool: if not isinstance(other, PrecertificateSignedCertificateTimestamps): return NotImplemented return ( self._signed_certificate_timestamps == other._signed_certificate_timestamps ) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class SignedCertificateTimestamps(ExtensionType): oid = ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS def __init__( self, signed_certificate_timestamps: typing.Iterable[ SignedCertificateTimestamp ], ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( isinstance(sct, SignedCertificateTimestamp) for sct in signed_certificate_timestamps ): raise TypeError( "Every item in the signed_certificate_timestamps list must be " "a SignedCertificateTimestamp" ) self._signed_certificate_timestamps = signed_certificate_timestamps __len__, __iter__, __getitem__ = _make_sequence_methods( "_signed_certificate_timestamps" ) def __repr__(self) -> str: return f"" def __hash__(self) -> int: return hash(tuple(self._signed_certificate_timestamps)) def __eq__(self, other: object) -> bool: if not isinstance(other, SignedCertificateTimestamps): return NotImplemented return ( self._signed_certificate_timestamps == other._signed_certificate_timestamps ) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class OCSPNonce(ExtensionType): oid = OCSPExtensionOID.NONCE def __init__(self, nonce: bytes) -> None: if not isinstance(nonce, bytes): raise TypeError("nonce must be bytes") self._nonce = nonce def __eq__(self, other: object) -> bool: if not isinstance(other, OCSPNonce): return NotImplemented return self.nonce == other.nonce def __hash__(self) -> int: return hash(self.nonce) def __repr__(self) -> str: return f"" @property def nonce(self) -> bytes: return self._nonce def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class OCSPAcceptableResponses(ExtensionType): oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES def __init__(self, responses: typing.Iterable[ObjectIdentifier]) -> None: responses = list(responses) if any(not isinstance(r, ObjectIdentifier) for r in responses): raise TypeError("All responses must be ObjectIdentifiers") self._responses = responses def __eq__(self, other: object) -> bool: if not isinstance(other, OCSPAcceptableResponses): return NotImplemented return self._responses == other._responses def __hash__(self) -> int: return hash(tuple(self._responses)) def __repr__(self) -> str: return f"" def __iter__(self) -> typing.Iterator[ObjectIdentifier]: return iter(self._responses) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class IssuingDistributionPoint(ExtensionType): oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT def __init__( self, full_name: typing.Iterable[GeneralName] | None, relative_name: RelativeDistinguishedName | None, only_contains_user_certs: bool, only_contains_ca_certs: bool, only_some_reasons: frozenset[ReasonFlags] | None, indirect_crl: bool, only_contains_attribute_certs: bool, ) -> None: if full_name is not None: full_name = list(full_name) if only_some_reasons and ( not isinstance(only_some_reasons, frozenset) or not all(isinstance(x, ReasonFlags) for x in only_some_reasons) ): raise TypeError( "only_some_reasons must be None or frozenset of ReasonFlags" ) if only_some_reasons and ( ReasonFlags.unspecified in only_some_reasons or ReasonFlags.remove_from_crl in only_some_reasons ): raise ValueError( "unspecified and remove_from_crl are not valid reasons in an " "IssuingDistributionPoint" ) if not ( isinstance(only_contains_user_certs, bool) and isinstance(only_contains_ca_certs, bool) and isinstance(indirect_crl, bool) and isinstance(only_contains_attribute_certs, bool) ): raise TypeError( "only_contains_user_certs, only_contains_ca_certs, " "indirect_crl and only_contains_attribute_certs " "must all be boolean." ) crl_constraints = [ only_contains_user_certs, only_contains_ca_certs, indirect_crl, only_contains_attribute_certs, ] if len([x for x in crl_constraints if x]) > 1: raise ValueError( "Only one of the following can be set to True: " "only_contains_user_certs, only_contains_ca_certs, " "indirect_crl, only_contains_attribute_certs" ) if not any( [ only_contains_user_certs, only_contains_ca_certs, indirect_crl, only_contains_attribute_certs, full_name, relative_name, only_some_reasons, ] ): raise ValueError( "Cannot create empty extension: " "if only_contains_user_certs, only_contains_ca_certs, " "indirect_crl, and only_contains_attribute_certs are all False" ", then either full_name, relative_name, or only_some_reasons " "must have a value." ) self._only_contains_user_certs = only_contains_user_certs self._only_contains_ca_certs = only_contains_ca_certs self._indirect_crl = indirect_crl self._only_contains_attribute_certs = only_contains_attribute_certs self._only_some_reasons = only_some_reasons self._full_name = full_name self._relative_name = relative_name def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, IssuingDistributionPoint): return NotImplemented return ( self.full_name == other.full_name and self.relative_name == other.relative_name and self.only_contains_user_certs == other.only_contains_user_certs and self.only_contains_ca_certs == other.only_contains_ca_certs and self.only_some_reasons == other.only_some_reasons and self.indirect_crl == other.indirect_crl and self.only_contains_attribute_certs == other.only_contains_attribute_certs ) def __hash__(self) -> int: return hash( ( self.full_name, self.relative_name, self.only_contains_user_certs, self.only_contains_ca_certs, self.only_some_reasons, self.indirect_crl, self.only_contains_attribute_certs, ) ) @property def full_name(self) -> list[GeneralName] | None: return self._full_name @property def relative_name(self) -> RelativeDistinguishedName | None: return self._relative_name @property def only_contains_user_certs(self) -> bool: return self._only_contains_user_certs @property def only_contains_ca_certs(self) -> bool: return self._only_contains_ca_certs @property def only_some_reasons( self, ) -> frozenset[ReasonFlags] | None: return self._only_some_reasons @property def indirect_crl(self) -> bool: return self._indirect_crl @property def only_contains_attribute_certs(self) -> bool: return self._only_contains_attribute_certs def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class MSCertificateTemplate(ExtensionType): oid = ExtensionOID.MS_CERTIFICATE_TEMPLATE def __init__( self, template_id: ObjectIdentifier, major_version: int | None, minor_version: int | None, ) -> None: if not isinstance(template_id, ObjectIdentifier): raise TypeError("oid must be an ObjectIdentifier") self._template_id = template_id if ( major_version is not None and not isinstance(major_version, int) ) or ( minor_version is not None and not isinstance(minor_version, int) ): raise TypeError( "major_version and minor_version must be integers or None" ) self._major_version = major_version self._minor_version = minor_version @property def template_id(self) -> ObjectIdentifier: return self._template_id @property def major_version(self) -> int | None: return self._major_version @property def minor_version(self) -> int | None: return self._minor_version def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, MSCertificateTemplate): return NotImplemented return ( self.template_id == other.template_id and self.major_version == other.major_version and self.minor_version == other.minor_version ) def __hash__(self) -> int: return hash((self.template_id, self.major_version, self.minor_version)) def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) class UnrecognizedExtension(ExtensionType): def __init__(self, oid: ObjectIdentifier, value: bytes) -> None: if not isinstance(oid, ObjectIdentifier): raise TypeError("oid must be an ObjectIdentifier") self._oid = oid self._value = value @property def oid(self) -> ObjectIdentifier: # type: ignore[override] return self._oid @property def value(self) -> bytes: return self._value def __repr__(self) -> str: return ( f"" ) def __eq__(self, other: object) -> bool: if not isinstance(other, UnrecognizedExtension): return NotImplemented return self.oid == other.oid and self.value == other.value def __hash__(self) -> int: return hash((self.oid, self.value)) def public_bytes(self) -> bytes: return self.value cryptography-43.0.0/src/cryptography/x509/general_name.py010064400017510000177000000172341464676315000215560ustar 00000000000000# 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. from __future__ import annotations import abc import ipaddress import typing from email.utils import parseaddr from cryptography.x509.name import Name from cryptography.x509.oid import ObjectIdentifier _IPAddressTypes = typing.Union[ ipaddress.IPv4Address, ipaddress.IPv6Address, ipaddress.IPv4Network, ipaddress.IPv6Network, ] class UnsupportedGeneralNameType(Exception): pass class GeneralName(metaclass=abc.ABCMeta): @property @abc.abstractmethod def value(self) -> typing.Any: """ Return the value of the object """ class RFC822Name(GeneralName): def __init__(self, value: str) -> None: if isinstance(value, str): try: value.encode("ascii") except UnicodeEncodeError: raise ValueError( "RFC822Name values should be passed as an A-label string. " "This means unicode characters should be encoded via " "a library like idna." ) else: raise TypeError("value must be string") name, address = parseaddr(value) if name or not address: # parseaddr has found a name (e.g. Name ) or the entire # value is an empty string. raise ValueError("Invalid rfc822name value") self._value = value @property def value(self) -> str: return self._value @classmethod def _init_without_validation(cls, value: str) -> RFC822Name: instance = cls.__new__(cls) instance._value = value return instance def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, RFC822Name): return NotImplemented return self.value == other.value def __hash__(self) -> int: return hash(self.value) class DNSName(GeneralName): def __init__(self, value: str) -> None: if isinstance(value, str): try: value.encode("ascii") except UnicodeEncodeError: raise ValueError( "DNSName values should be passed as an A-label string. " "This means unicode characters should be encoded via " "a library like idna." ) else: raise TypeError("value must be string") self._value = value @property def value(self) -> str: return self._value @classmethod def _init_without_validation(cls, value: str) -> DNSName: instance = cls.__new__(cls) instance._value = value return instance def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, DNSName): return NotImplemented return self.value == other.value def __hash__(self) -> int: return hash(self.value) class UniformResourceIdentifier(GeneralName): def __init__(self, value: str) -> None: if isinstance(value, str): try: value.encode("ascii") except UnicodeEncodeError: raise ValueError( "URI values should be passed as an A-label string. " "This means unicode characters should be encoded via " "a library like idna." ) else: raise TypeError("value must be string") self._value = value @property def value(self) -> str: return self._value @classmethod def _init_without_validation(cls, value: str) -> UniformResourceIdentifier: instance = cls.__new__(cls) instance._value = value return instance def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, UniformResourceIdentifier): return NotImplemented return self.value == other.value def __hash__(self) -> int: return hash(self.value) class DirectoryName(GeneralName): def __init__(self, value: Name) -> None: if not isinstance(value, Name): raise TypeError("value must be a Name") self._value = value @property def value(self) -> Name: return self._value def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, DirectoryName): return NotImplemented return self.value == other.value def __hash__(self) -> int: return hash(self.value) class RegisteredID(GeneralName): def __init__(self, value: ObjectIdentifier) -> None: if not isinstance(value, ObjectIdentifier): raise TypeError("value must be an ObjectIdentifier") self._value = value @property def value(self) -> ObjectIdentifier: return self._value def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, RegisteredID): return NotImplemented return self.value == other.value def __hash__(self) -> int: return hash(self.value) class IPAddress(GeneralName): def __init__(self, value: _IPAddressTypes) -> None: if not isinstance( value, ( ipaddress.IPv4Address, ipaddress.IPv6Address, ipaddress.IPv4Network, ipaddress.IPv6Network, ), ): raise TypeError( "value must be an instance of ipaddress.IPv4Address, " "ipaddress.IPv6Address, ipaddress.IPv4Network, or " "ipaddress.IPv6Network" ) self._value = value @property def value(self) -> _IPAddressTypes: return self._value def _packed(self) -> bytes: if isinstance( self.value, (ipaddress.IPv4Address, ipaddress.IPv6Address) ): return self.value.packed else: return ( self.value.network_address.packed + self.value.netmask.packed ) def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, IPAddress): return NotImplemented return self.value == other.value def __hash__(self) -> int: return hash(self.value) class OtherName(GeneralName): def __init__(self, type_id: ObjectIdentifier, value: bytes) -> None: if not isinstance(type_id, ObjectIdentifier): raise TypeError("type_id must be an ObjectIdentifier") if not isinstance(value, bytes): raise TypeError("value must be a binary string") self._type_id = type_id self._value = value @property def type_id(self) -> ObjectIdentifier: return self._type_id @property def value(self) -> bytes: return self._value def __repr__(self) -> str: return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, OtherName): return NotImplemented return self.type_id == other.type_id and self.value == other.value def __hash__(self) -> int: return hash((self.type_id, self.value)) cryptography-43.0.0/src/cryptography/x509/name.py010064400017510000177000000347561464676315000200710ustar 00000000000000# 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. from __future__ import annotations import binascii import re import sys import typing import warnings from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.x509.oid import NameOID, ObjectIdentifier class _ASN1Type(utils.Enum): BitString = 3 OctetString = 4 UTF8String = 12 NumericString = 18 PrintableString = 19 T61String = 20 IA5String = 22 UTCTime = 23 GeneralizedTime = 24 VisibleString = 26 UniversalString = 28 BMPString = 30 _ASN1_TYPE_TO_ENUM = {i.value: i for i in _ASN1Type} _NAMEOID_DEFAULT_TYPE: dict[ObjectIdentifier, _ASN1Type] = { NameOID.COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString, NameOID.DN_QUALIFIER: _ASN1Type.PrintableString, NameOID.EMAIL_ADDRESS: _ASN1Type.IA5String, NameOID.DOMAIN_COMPONENT: _ASN1Type.IA5String, } # Type alias _OidNameMap = typing.Mapping[ObjectIdentifier, str] _NameOidMap = typing.Mapping[str, ObjectIdentifier] #: Short attribute names from RFC 4514: #: https://tools.ietf.org/html/rfc4514#page-7 _NAMEOID_TO_NAME: _OidNameMap = { NameOID.COMMON_NAME: "CN", NameOID.LOCALITY_NAME: "L", NameOID.STATE_OR_PROVINCE_NAME: "ST", NameOID.ORGANIZATION_NAME: "O", NameOID.ORGANIZATIONAL_UNIT_NAME: "OU", NameOID.COUNTRY_NAME: "C", NameOID.STREET_ADDRESS: "STREET", NameOID.DOMAIN_COMPONENT: "DC", NameOID.USER_ID: "UID", } _NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()} _NAMEOID_LENGTH_LIMIT = { NameOID.COUNTRY_NAME: (2, 2), NameOID.JURISDICTION_COUNTRY_NAME: (2, 2), NameOID.COMMON_NAME: (1, 64), } def _escape_dn_value(val: str | bytes) -> str: """Escape special characters in RFC4514 Distinguished Name value.""" if not val: return "" # RFC 4514 Section 2.4 defines the value as being the # (U+0023) character # followed by the hexadecimal encoding of the octets. if isinstance(val, bytes): return "#" + binascii.hexlify(val).decode("utf8") # See https://tools.ietf.org/html/rfc4514#section-2.4 val = val.replace("\\", "\\\\") val = val.replace('"', '\\"') val = val.replace("+", "\\+") val = val.replace(",", "\\,") val = val.replace(";", "\\;") val = val.replace("<", "\\<") val = val.replace(">", "\\>") val = val.replace("\0", "\\00") if val[0] in ("#", " "): val = "\\" + val if val[-1] == " ": val = val[:-1] + "\\ " return val def _unescape_dn_value(val: str) -> str: if not val: return "" # See https://tools.ietf.org/html/rfc4514#section-3 # special = escaped / SPACE / SHARP / EQUALS # escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE def sub(m): val = m.group(1) # Regular escape if len(val) == 1: return val # Hex-value scape return chr(int(val, 16)) return _RFC4514NameParser._PAIR_RE.sub(sub, val) class NameAttribute: def __init__( self, oid: ObjectIdentifier, value: str | bytes, _type: _ASN1Type | None = None, *, _validate: bool = True, ) -> None: if not isinstance(oid, ObjectIdentifier): raise TypeError( "oid argument must be an ObjectIdentifier instance." ) if _type == _ASN1Type.BitString: if oid != NameOID.X500_UNIQUE_IDENTIFIER: raise TypeError( "oid must be X500_UNIQUE_IDENTIFIER for BitString type." ) if not isinstance(value, bytes): raise TypeError("value must be bytes for BitString") else: if not isinstance(value, str): raise TypeError("value argument must be a str") length_limits = _NAMEOID_LENGTH_LIMIT.get(oid) if length_limits is not None: min_length, max_length = length_limits assert isinstance(value, str) c_len = len(value.encode("utf8")) if c_len < min_length or c_len > max_length: msg = ( f"Attribute's length must be >= {min_length} and " f"<= {max_length}, but it was {c_len}" ) if _validate is True: raise ValueError(msg) else: warnings.warn(msg, stacklevel=2) # The appropriate ASN1 string type varies by OID and is defined across # multiple RFCs including 2459, 3280, and 5280. In general UTF8String # is preferred (2459), but 3280 and 5280 specify several OIDs with # alternate types. This means when we see the sentinel value we need # to look up whether the OID has a non-UTF8 type. If it does, set it # to that. Otherwise, UTF8! if _type is None: _type = _NAMEOID_DEFAULT_TYPE.get(oid, _ASN1Type.UTF8String) if not isinstance(_type, _ASN1Type): raise TypeError("_type must be from the _ASN1Type enum") self._oid = oid self._value = value self._type = _type @property def oid(self) -> ObjectIdentifier: return self._oid @property def value(self) -> str | bytes: return self._value @property def rfc4514_attribute_name(self) -> str: """ The short attribute name (for example "CN") if available, otherwise the OID dotted string. """ return _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string) def rfc4514_string( self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. Use short attribute name if available, otherwise fall back to OID dotted string. """ attr_name = ( attr_name_overrides.get(self.oid) if attr_name_overrides else None ) if attr_name is None: attr_name = self.rfc4514_attribute_name return f"{attr_name}={_escape_dn_value(self.value)}" def __eq__(self, other: object) -> bool: if not isinstance(other, NameAttribute): return NotImplemented return self.oid == other.oid and self.value == other.value def __hash__(self) -> int: return hash((self.oid, self.value)) def __repr__(self) -> str: return f"" class RelativeDistinguishedName: def __init__(self, attributes: typing.Iterable[NameAttribute]): attributes = list(attributes) if not attributes: raise ValueError("a relative distinguished name cannot be empty") if not all(isinstance(x, NameAttribute) for x in attributes): raise TypeError("attributes must be an iterable of NameAttribute") # Keep list and frozenset to preserve attribute order where it matters self._attributes = attributes self._attribute_set = frozenset(attributes) if len(self._attribute_set) != len(attributes): raise ValueError("duplicate attributes are not allowed") def get_attributes_for_oid( self, oid: ObjectIdentifier ) -> list[NameAttribute]: return [i for i in self if i.oid == oid] def rfc4514_string( self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. Within each RDN, attributes are joined by '+', although that is rarely used in certificates. """ return "+".join( attr.rfc4514_string(attr_name_overrides) for attr in self._attributes ) def __eq__(self, other: object) -> bool: if not isinstance(other, RelativeDistinguishedName): return NotImplemented return self._attribute_set == other._attribute_set def __hash__(self) -> int: return hash(self._attribute_set) def __iter__(self) -> typing.Iterator[NameAttribute]: return iter(self._attributes) def __len__(self) -> int: return len(self._attributes) def __repr__(self) -> str: return f"" class Name: @typing.overload def __init__(self, attributes: typing.Iterable[NameAttribute]) -> None: ... @typing.overload def __init__( self, attributes: typing.Iterable[RelativeDistinguishedName] ) -> None: ... def __init__( self, attributes: typing.Iterable[NameAttribute | RelativeDistinguishedName], ) -> None: attributes = list(attributes) if all(isinstance(x, NameAttribute) for x in attributes): self._attributes = [ RelativeDistinguishedName([typing.cast(NameAttribute, x)]) for x in attributes ] elif all(isinstance(x, RelativeDistinguishedName) for x in attributes): self._attributes = typing.cast( typing.List[RelativeDistinguishedName], attributes ) else: raise TypeError( "attributes must be a list of NameAttribute" " or a list RelativeDistinguishedName" ) @classmethod def from_rfc4514_string( cls, data: str, attr_name_overrides: _NameOidMap | None = None, ) -> Name: return _RFC4514NameParser(data, attr_name_overrides or {}).parse() def rfc4514_string( self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. For example 'CN=foobar.com,O=Foo Corp,C=US' An X.509 name is a two-level structure: a list of sets of attributes. Each list element is separated by ',' and within each list element, set elements are separated by '+'. The latter is almost never used in real world certificates. According to RFC4514 section 2.1 the RDNSequence must be reversed when converting to string representation. """ return ",".join( attr.rfc4514_string(attr_name_overrides) for attr in reversed(self._attributes) ) def get_attributes_for_oid( self, oid: ObjectIdentifier ) -> list[NameAttribute]: return [i for i in self if i.oid == oid] @property def rdns(self) -> list[RelativeDistinguishedName]: return self._attributes def public_bytes(self, backend: typing.Any = None) -> bytes: return rust_x509.encode_name_bytes(self) def __eq__(self, other: object) -> bool: if not isinstance(other, Name): return NotImplemented return self._attributes == other._attributes def __hash__(self) -> int: # TODO: this is relatively expensive, if this looks like a bottleneck # for you, consider optimizing! return hash(tuple(self._attributes)) def __iter__(self) -> typing.Iterator[NameAttribute]: for rdn in self._attributes: yield from rdn def __len__(self) -> int: return sum(len(rdn) for rdn in self._attributes) def __repr__(self) -> str: rdns = ",".join(attr.rfc4514_string() for attr in self._attributes) return f"" class _RFC4514NameParser: _OID_RE = re.compile(r"(0|([1-9]\d*))(\.(0|([1-9]\d*)))+") _DESCR_RE = re.compile(r"[a-zA-Z][a-zA-Z\d-]*") _PAIR = r"\\([\\ #=\"\+,;<>]|[\da-zA-Z]{2})" _PAIR_RE = re.compile(_PAIR) _LUTF1 = r"[\x01-\x1f\x21\x24-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" _SUTF1 = r"[\x01-\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" _TUTF1 = r"[\x01-\x1F\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" _UTFMB = rf"[\x80-{chr(sys.maxunicode)}]" _LEADCHAR = rf"{_LUTF1}|{_UTFMB}" _STRINGCHAR = rf"{_SUTF1}|{_UTFMB}" _TRAILCHAR = rf"{_TUTF1}|{_UTFMB}" _STRING_RE = re.compile( rf""" ( ({_LEADCHAR}|{_PAIR}) ( ({_STRINGCHAR}|{_PAIR})* ({_TRAILCHAR}|{_PAIR}) )? )? """, re.VERBOSE, ) _HEXSTRING_RE = re.compile(r"#([\da-zA-Z]{2})+") def __init__(self, data: str, attr_name_overrides: _NameOidMap) -> None: self._data = data self._idx = 0 self._attr_name_overrides = attr_name_overrides def _has_data(self) -> bool: return self._idx < len(self._data) def _peek(self) -> str | None: if self._has_data(): return self._data[self._idx] return None def _read_char(self, ch: str) -> None: if self._peek() != ch: raise ValueError self._idx += 1 def _read_re(self, pat) -> str: match = pat.match(self._data, pos=self._idx) if match is None: raise ValueError val = match.group() self._idx += len(val) return val def parse(self) -> Name: """ Parses the `data` string and converts it to a Name. According to RFC4514 section 2.1 the RDNSequence must be reversed when converting to string representation. So, when we parse it, we need to reverse again to get the RDNs on the correct order. """ if not self._has_data(): return Name([]) rdns = [self._parse_rdn()] while self._has_data(): self._read_char(",") rdns.append(self._parse_rdn()) return Name(reversed(rdns)) def _parse_rdn(self) -> RelativeDistinguishedName: nas = [self._parse_na()] while self._peek() == "+": self._read_char("+") nas.append(self._parse_na()) return RelativeDistinguishedName(nas) def _parse_na(self) -> NameAttribute: try: oid_value = self._read_re(self._OID_RE) except ValueError: name = self._read_re(self._DESCR_RE) oid = self._attr_name_overrides.get( name, _NAME_TO_NAMEOID.get(name) ) if oid is None: raise ValueError else: oid = ObjectIdentifier(oid_value) self._read_char("=") if self._peek() == "#": value = self._read_re(self._HEXSTRING_RE) value = binascii.unhexlify(value[1:]).decode() else: raw_value = self._read_re(self._STRING_RE) value = _unescape_dn_value(raw_value) return NameAttribute(oid, value) cryptography-43.0.0/src/cryptography/x509/ocsp.py010064400017510000177000000470431464676315000201060ustar 00000000000000# 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. from __future__ import annotations import abc import datetime import typing from cryptography import utils, x509 from cryptography.hazmat.bindings._rust import ocsp from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPrivateKeyTypes, ) from cryptography.x509.base import ( _EARLIEST_UTC_TIME, _convert_to_naive_utc_time, _reject_duplicate_extension, ) class OCSPResponderEncoding(utils.Enum): HASH = "By Hash" NAME = "By Name" class OCSPResponseStatus(utils.Enum): SUCCESSFUL = 0 MALFORMED_REQUEST = 1 INTERNAL_ERROR = 2 TRY_LATER = 3 SIG_REQUIRED = 5 UNAUTHORIZED = 6 _ALLOWED_HASHES = ( hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, ) def _verify_algorithm(algorithm: hashes.HashAlgorithm) -> None: if not isinstance(algorithm, _ALLOWED_HASHES): raise ValueError( "Algorithm must be SHA1, SHA224, SHA256, SHA384, or SHA512" ) class OCSPCertStatus(utils.Enum): GOOD = 0 REVOKED = 1 UNKNOWN = 2 class _SingleResponse: def __init__( self, cert: x509.Certificate, issuer: x509.Certificate, algorithm: hashes.HashAlgorithm, cert_status: OCSPCertStatus, this_update: datetime.datetime, next_update: datetime.datetime | None, revocation_time: datetime.datetime | None, revocation_reason: x509.ReasonFlags | None, ): if not isinstance(cert, x509.Certificate) or not isinstance( issuer, x509.Certificate ): raise TypeError("cert and issuer must be a Certificate") _verify_algorithm(algorithm) if not isinstance(this_update, datetime.datetime): raise TypeError("this_update must be a datetime object") if next_update is not None and not isinstance( next_update, datetime.datetime ): raise TypeError("next_update must be a datetime object or None") self._cert = cert self._issuer = issuer self._algorithm = algorithm self._this_update = this_update self._next_update = next_update if not isinstance(cert_status, OCSPCertStatus): raise TypeError( "cert_status must be an item from the OCSPCertStatus enum" ) if cert_status is not OCSPCertStatus.REVOKED: if revocation_time is not None: raise ValueError( "revocation_time can only be provided if the certificate " "is revoked" ) if revocation_reason is not None: raise ValueError( "revocation_reason can only be provided if the certificate" " is revoked" ) else: if not isinstance(revocation_time, datetime.datetime): raise TypeError("revocation_time must be a datetime object") revocation_time = _convert_to_naive_utc_time(revocation_time) if revocation_time < _EARLIEST_UTC_TIME: raise ValueError( "The revocation_time must be on or after" " 1950 January 1." ) if revocation_reason is not None and not isinstance( revocation_reason, x509.ReasonFlags ): raise TypeError( "revocation_reason must be an item from the ReasonFlags " "enum or None" ) self._cert_status = cert_status self._revocation_time = revocation_time self._revocation_reason = revocation_reason class OCSPRequest(metaclass=abc.ABCMeta): @property @abc.abstractmethod def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ @property @abc.abstractmethod def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ @property @abc.abstractmethod def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ @property @abc.abstractmethod def serial_number(self) -> int: """ The serial number of the cert whose status is being checked """ @abc.abstractmethod def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Serializes the request to DER """ @property @abc.abstractmethod def extensions(self) -> x509.Extensions: """ The list of request extensions. Not single request extensions. """ class OCSPSingleResponse(metaclass=abc.ABCMeta): @property @abc.abstractmethod def certificate_status(self) -> OCSPCertStatus: """ The status of the certificate (an element from the OCSPCertStatus enum) """ @property @abc.abstractmethod def revocation_time(self) -> datetime.datetime | None: """ The date of when the certificate was revoked or None if not revoked. """ @property @abc.abstractmethod def revocation_time_utc(self) -> datetime.datetime | None: """ The date of when the certificate was revoked or None if not revoked. Represented as a non-naive UTC datetime. """ @property @abc.abstractmethod def revocation_reason(self) -> x509.ReasonFlags | None: """ The reason the certificate was revoked or None if not specified or not revoked. """ @property @abc.abstractmethod def this_update(self) -> datetime.datetime: """ The most recent time at which the status being indicated is known by the responder to have been correct """ @property @abc.abstractmethod def this_update_utc(self) -> datetime.datetime: """ The most recent time at which the status being indicated is known by the responder to have been correct. Represented as a non-naive UTC datetime. """ @property @abc.abstractmethod def next_update(self) -> datetime.datetime | None: """ The time when newer information will be available """ @property @abc.abstractmethod def next_update_utc(self) -> datetime.datetime | None: """ The time when newer information will be available. Represented as a non-naive UTC datetime. """ @property @abc.abstractmethod def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ @property @abc.abstractmethod def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ @property @abc.abstractmethod def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ @property @abc.abstractmethod def serial_number(self) -> int: """ The serial number of the cert whose status is being checked """ class OCSPResponse(metaclass=abc.ABCMeta): @property @abc.abstractmethod def responses(self) -> typing.Iterator[OCSPSingleResponse]: """ An iterator over the individual SINGLERESP structures in the response """ @property @abc.abstractmethod def response_status(self) -> OCSPResponseStatus: """ The status of the response. This is a value from the OCSPResponseStatus enumeration """ @property @abc.abstractmethod def signature_algorithm_oid(self) -> x509.ObjectIdentifier: """ The ObjectIdentifier of the signature algorithm """ @property @abc.abstractmethod def signature_hash_algorithm( self, ) -> hashes.HashAlgorithm | None: """ Returns a HashAlgorithm corresponding to the type of the digest signed """ @property @abc.abstractmethod def signature(self) -> bytes: """ The signature bytes """ @property @abc.abstractmethod def tbs_response_bytes(self) -> bytes: """ The tbsResponseData bytes """ @property @abc.abstractmethod def certificates(self) -> list[x509.Certificate]: """ A list of certificates used to help build a chain to verify the OCSP response. This situation occurs when the OCSP responder uses a delegate certificate. """ @property @abc.abstractmethod def responder_key_hash(self) -> bytes | None: """ The responder's key hash or None """ @property @abc.abstractmethod def responder_name(self) -> x509.Name | None: """ The responder's Name or None """ @property @abc.abstractmethod def produced_at(self) -> datetime.datetime: """ The time the response was produced """ @property @abc.abstractmethod def produced_at_utc(self) -> datetime.datetime: """ The time the response was produced. Represented as a non-naive UTC datetime. """ @property @abc.abstractmethod def certificate_status(self) -> OCSPCertStatus: """ The status of the certificate (an element from the OCSPCertStatus enum) """ @property @abc.abstractmethod def revocation_time(self) -> datetime.datetime | None: """ The date of when the certificate was revoked or None if not revoked. """ @property @abc.abstractmethod def revocation_time_utc(self) -> datetime.datetime | None: """ The date of when the certificate was revoked or None if not revoked. Represented as a non-naive UTC datetime. """ @property @abc.abstractmethod def revocation_reason(self) -> x509.ReasonFlags | None: """ The reason the certificate was revoked or None if not specified or not revoked. """ @property @abc.abstractmethod def this_update(self) -> datetime.datetime: """ The most recent time at which the status being indicated is known by the responder to have been correct """ @property @abc.abstractmethod def this_update_utc(self) -> datetime.datetime: """ The most recent time at which the status being indicated is known by the responder to have been correct. Represented as a non-naive UTC datetime. """ @property @abc.abstractmethod def next_update(self) -> datetime.datetime | None: """ The time when newer information will be available """ @property @abc.abstractmethod def next_update_utc(self) -> datetime.datetime | None: """ The time when newer information will be available. Represented as a non-naive UTC datetime. """ @property @abc.abstractmethod def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ @property @abc.abstractmethod def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ @property @abc.abstractmethod def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ @property @abc.abstractmethod def serial_number(self) -> int: """ The serial number of the cert whose status is being checked """ @property @abc.abstractmethod def extensions(self) -> x509.Extensions: """ The list of response extensions. Not single response extensions. """ @property @abc.abstractmethod def single_extensions(self) -> x509.Extensions: """ The list of single response extensions. Not response extensions. """ @abc.abstractmethod def public_bytes(self, encoding: serialization.Encoding) -> bytes: """ Serializes the response to DER """ OCSPRequest.register(ocsp.OCSPRequest) OCSPResponse.register(ocsp.OCSPResponse) OCSPSingleResponse.register(ocsp.OCSPSingleResponse) class OCSPRequestBuilder: def __init__( self, request: tuple[ x509.Certificate, x509.Certificate, hashes.HashAlgorithm ] | None = None, request_hash: tuple[bytes, bytes, int, hashes.HashAlgorithm] | None = None, extensions: list[x509.Extension[x509.ExtensionType]] = [], ) -> None: self._request = request self._request_hash = request_hash self._extensions = extensions def add_certificate( self, cert: x509.Certificate, issuer: x509.Certificate, algorithm: hashes.HashAlgorithm, ) -> OCSPRequestBuilder: if self._request is not None or self._request_hash is not None: raise ValueError("Only one certificate can be added to a request") _verify_algorithm(algorithm) if not isinstance(cert, x509.Certificate) or not isinstance( issuer, x509.Certificate ): raise TypeError("cert and issuer must be a Certificate") return OCSPRequestBuilder( (cert, issuer, algorithm), self._request_hash, self._extensions ) def add_certificate_by_hash( self, issuer_name_hash: bytes, issuer_key_hash: bytes, serial_number: int, algorithm: hashes.HashAlgorithm, ) -> OCSPRequestBuilder: if self._request is not None or self._request_hash is not None: raise ValueError("Only one certificate can be added to a request") if not isinstance(serial_number, int): raise TypeError("serial_number must be an integer") _verify_algorithm(algorithm) utils._check_bytes("issuer_name_hash", issuer_name_hash) utils._check_bytes("issuer_key_hash", issuer_key_hash) if algorithm.digest_size != len( issuer_name_hash ) or algorithm.digest_size != len(issuer_key_hash): raise ValueError( "issuer_name_hash and issuer_key_hash must be the same length " "as the digest size of the algorithm" ) return OCSPRequestBuilder( self._request, (issuer_name_hash, issuer_key_hash, serial_number, algorithm), self._extensions, ) def add_extension( self, extval: x509.ExtensionType, critical: bool ) -> OCSPRequestBuilder: if not isinstance(extval, x509.ExtensionType): raise TypeError("extension must be an ExtensionType") extension = x509.Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return OCSPRequestBuilder( self._request, self._request_hash, [*self._extensions, extension] ) def build(self) -> OCSPRequest: if self._request is None and self._request_hash is None: raise ValueError("You must add a certificate before building") return ocsp.create_ocsp_request(self) class OCSPResponseBuilder: def __init__( self, response: _SingleResponse | None = None, responder_id: tuple[x509.Certificate, OCSPResponderEncoding] | None = None, certs: list[x509.Certificate] | None = None, extensions: list[x509.Extension[x509.ExtensionType]] = [], ): self._response = response self._responder_id = responder_id self._certs = certs self._extensions = extensions def add_response( self, cert: x509.Certificate, issuer: x509.Certificate, algorithm: hashes.HashAlgorithm, cert_status: OCSPCertStatus, this_update: datetime.datetime, next_update: datetime.datetime | None, revocation_time: datetime.datetime | None, revocation_reason: x509.ReasonFlags | None, ) -> OCSPResponseBuilder: if self._response is not None: raise ValueError("Only one response per OCSPResponse.") singleresp = _SingleResponse( cert, issuer, algorithm, cert_status, this_update, next_update, revocation_time, revocation_reason, ) return OCSPResponseBuilder( singleresp, self._responder_id, self._certs, self._extensions, ) def responder_id( self, encoding: OCSPResponderEncoding, responder_cert: x509.Certificate ) -> OCSPResponseBuilder: if self._responder_id is not None: raise ValueError("responder_id can only be set once") if not isinstance(responder_cert, x509.Certificate): raise TypeError("responder_cert must be a Certificate") if not isinstance(encoding, OCSPResponderEncoding): raise TypeError( "encoding must be an element from OCSPResponderEncoding" ) return OCSPResponseBuilder( self._response, (responder_cert, encoding), self._certs, self._extensions, ) def certificates( self, certs: typing.Iterable[x509.Certificate] ) -> OCSPResponseBuilder: if self._certs is not None: raise ValueError("certificates may only be set once") certs = list(certs) if len(certs) == 0: raise ValueError("certs must not be an empty list") if not all(isinstance(x, x509.Certificate) for x in certs): raise TypeError("certs must be a list of Certificates") return OCSPResponseBuilder( self._response, self._responder_id, certs, self._extensions, ) def add_extension( self, extval: x509.ExtensionType, critical: bool ) -> OCSPResponseBuilder: if not isinstance(extval, x509.ExtensionType): raise TypeError("extension must be an ExtensionType") extension = x509.Extension(extval.oid, critical, extval) _reject_duplicate_extension(extension, self._extensions) return OCSPResponseBuilder( self._response, self._responder_id, self._certs, [*self._extensions, extension], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, algorithm: hashes.HashAlgorithm | None, ) -> OCSPResponse: if self._response is None: raise ValueError("You must add a response before signing") if self._responder_id is None: raise ValueError("You must add a responder_id before signing") return ocsp.create_ocsp_response( OCSPResponseStatus.SUCCESSFUL, self, private_key, algorithm ) @classmethod def build_unsuccessful( cls, response_status: OCSPResponseStatus ) -> OCSPResponse: if not isinstance(response_status, OCSPResponseStatus): raise TypeError( "response_status must be an item from OCSPResponseStatus" ) if response_status is OCSPResponseStatus.SUCCESSFUL: raise ValueError("response_status cannot be SUCCESSFUL") return ocsp.create_ocsp_response(response_status, None, None, None) load_der_ocsp_request = ocsp.load_der_ocsp_request load_der_ocsp_response = ocsp.load_der_ocsp_response cryptography-43.0.0/src/cryptography/x509/oid.py010064400017510000177000000015651464676315000177140ustar 00000000000000# 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. from __future__ import annotations from cryptography.hazmat._oid import ( AttributeOID, AuthorityInformationAccessOID, CertificatePoliciesOID, CRLEntryExtensionOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, OCSPExtensionOID, PublicKeyAlgorithmOID, SignatureAlgorithmOID, SubjectInformationAccessOID, ) __all__ = [ "AttributeOID", "AuthorityInformationAccessOID", "CRLEntryExtensionOID", "CertificatePoliciesOID", "ExtendedKeyUsageOID", "ExtensionOID", "NameOID", "OCSPExtensionOID", "ObjectIdentifier", "PublicKeyAlgorithmOID", "SignatureAlgorithmOID", "SubjectInformationAccessOID", ] cryptography-43.0.0/src/cryptography/x509/verification.py010064400017510000177000000014341464676315000216160ustar 00000000000000# 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. from __future__ import annotations import typing from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.x509.general_name import DNSName, IPAddress __all__ = [ "ClientVerifier", "PolicyBuilder", "ServerVerifier", "Store", "Subject", "VerificationError", "VerifiedClient", ] Store = rust_x509.Store Subject = typing.Union[DNSName, IPAddress] VerifiedClient = rust_x509.VerifiedClient ClientVerifier = rust_x509.ClientVerifier ServerVerifier = rust_x509.ServerVerifier PolicyBuilder = rust_x509.PolicyBuilder VerificationError = rust_x509.VerificationError cryptography-43.0.0/src/rust/Cargo.lock010064400017510000177000000206361464676315000162310ustar 00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "asn1" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "532ceda058281b62096b2add4ab00ab3a453d30dee28b8890f62461a0109ebbd" dependencies = [ "asn1_derive", ] [[package]] name = "asn1_derive" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6076d38cc17cc22b0f65f31170a2ee1975e6b07f0012893aefd86ce19c987" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cc" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cryptography-cffi" version = "0.1.0" dependencies = [ "cc", "openssl-sys", "pyo3", ] [[package]] name = "cryptography-keepalive" version = "0.1.0" dependencies = [ "pyo3", ] [[package]] name = "cryptography-key-parsing" version = "0.1.0" dependencies = [ "asn1", "cfg-if", "cryptography-x509", "openssl", "openssl-sys", ] [[package]] name = "cryptography-openssl" version = "0.1.0" dependencies = [ "cfg-if", "foreign-types", "foreign-types-shared", "openssl", "openssl-sys", ] [[package]] name = "cryptography-rust" version = "0.1.0" dependencies = [ "asn1", "cfg-if", "cryptography-cffi", "cryptography-keepalive", "cryptography-key-parsing", "cryptography-openssl", "cryptography-x509", "cryptography-x509-verification", "foreign-types-shared", "once_cell", "openssl", "openssl-sys", "pem", "pyo3", "self_cell", ] [[package]] name = "cryptography-x509" version = "0.1.0" dependencies = [ "asn1", ] [[package]] name = "cryptography-x509-verification" version = "0.1.0" dependencies = [ "asn1", "cryptography-key-parsing", "cryptography-x509", "once_cell", "pem", ] [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2823eb4c6453ed64055057ea8bd416eda38c71018723869dd043a3b1186115e" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-sys" version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "pem" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ "base64", ] [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "portable-atomic" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "self_cell" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "syn" version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" cryptography-43.0.0/src/rust/Cargo.toml010064400017510000177000000025771464676315000162600ustar 00000000000000[workspace.package] version = "0.1.0" authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV rust-version = "1.65.0" [package] name = "cryptography-rust" version.workspace = true authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true [dependencies] once_cell = "1" cfg-if = "1" pyo3 = { version = "0.22.2", features = ["abi3"] } asn1 = { version = "0.16.2", default-features = false } cryptography-cffi = { path = "cryptography-cffi" } cryptography-keepalive = { path = "cryptography-keepalive" } cryptography-key-parsing = { path = "cryptography-key-parsing" } cryptography-x509 = { path = "cryptography-x509" } cryptography-x509-verification = { path = "cryptography-x509-verification" } cryptography-openssl = { path = "cryptography-openssl" } pem = { version = "3", default-features = false } openssl = "0.10.65" openssl-sys = "0.9.103" foreign-types-shared = "0.1" self_cell = "1" [features] extension-module = ["pyo3/extension-module"] default = ["extension-module"] [lib] name = "cryptography_rust" crate-type = ["cdylib"] [profile.release] overflow-checks = true [workspace] members = [ "cryptography-cffi", "cryptography-keepalive", "cryptography-key-parsing", "cryptography-openssl", "cryptography-x509", "cryptography-x509-verification", ] cryptography-43.0.0/src/rust/build.rs010064400017510000177000000030551464676315000157650ustar 00000000000000// 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. use std::env; #[allow(clippy::unusual_byte_groupings)] fn main() { println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_IS_LIBRESSL)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_IS_BORINGSSL)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_OSSLCONF, values(\"OPENSSL_NO_IDEA\", \"OPENSSL_NO_CAST\", \"OPENSSL_NO_BF\", \"OPENSSL_NO_CAMELLIA\", \"OPENSSL_NO_SEED\", \"OPENSSL_NO_SM4\"))"); if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); if version >= 0x3_00_00_00_0 { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_300_OR_GREATER"); } if version >= 0x3_02_00_00_0 { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_320_OR_GREATER"); } } if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); } if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); } if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { for var in vars.split(',') { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OSSLCONF=\"{var}\""); } } } cryptography-43.0.0/src/rust/cryptography-cffi/Cargo.toml010064400017510000177000000004451464676315000217100ustar 00000000000000[package] name = "cryptography-cffi" version.workspace = true authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true [dependencies] pyo3 = { version = "0.22.2", features = ["abi3"] } openssl-sys = "0.9.103" [build-dependencies] cc = "1.1.6" cryptography-43.0.0/src/rust/cryptography-cffi/build.rs010064400017510000177000000122171464676315000214250ustar 00000000000000// 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. use std::env; use std::path::Path; use std::process::Command; fn main() { println!("cargo:rustc-check-cfg=cfg(python_implementation, values(\"CPython\", \"PyPy\"))"); let target = env::var("TARGET").unwrap(); let openssl_static = env::var("OPENSSL_STATIC") .map(|x| x == "1") .unwrap_or(false); if target.contains("apple") && openssl_static { // On (older) OSX we need to link against the clang runtime, // which is hidden in some non-default path. // // More details at https://github.com/alexcrichton/curl-rust/issues/279. if let Some(path) = macos_link_search_path() { println!("cargo:rustc-link-lib=clang_rt.osx"); println!("cargo:rustc-link-search={path}"); } } let out_dir = env::var("OUT_DIR").unwrap(); // FIXME: maybe pyo3-build-config should provide a way to do this? let python = env::var("PYO3_PYTHON").unwrap_or_else(|_| "python3".to_string()); println!("cargo:rerun-if-env-changed=PYO3_PYTHON"); println!("cargo:rerun-if-changed=../../_cffi_src/"); println!("cargo:rerun-if-changed=../../cryptography/__about__.py"); let output = Command::new(&python) .env("OUT_DIR", &out_dir) .arg("../../_cffi_src/build_openssl.py") .output() .expect("failed to execute build_openssl.py"); if !output.status.success() { panic!( "failed to run build_openssl.py, stdout: \n{}\nstderr: \n{}\n", String::from_utf8(output.stdout).unwrap(), String::from_utf8(output.stderr).unwrap() ); } let python_impl = run_python_script( &python, "import platform; print(platform.python_implementation(), end='')", ) .unwrap(); println!("cargo:rustc-cfg=python_implementation=\"{python_impl}\""); let python_includes = run_python_script( &python, "import os; \ import setuptools.dist; \ import setuptools.command.build_ext; \ b = setuptools.command.build_ext.build_ext(setuptools.dist.Distribution()); \ b.finalize_options(); \ print(os.pathsep.join(b.include_dirs), end='')", ) .unwrap(); let openssl_include = std::env::var_os("DEP_OPENSSL_INCLUDE").expect("unable to find openssl include path"); let openssl_c = Path::new(&out_dir).join("_openssl.c"); let mut build = cc::Build::new(); build .file(openssl_c) .include(openssl_include) .flag_if_supported("-Wconversion") .flag_if_supported("-Wno-error=sign-conversion") .flag_if_supported("-Wno-unused-parameter"); // We use the `-fmacro-prefix-map` option to replace the output directory in macros with a dot. // This is because we don't want a potentially random build path to end up in the binary because // CFFI generated code uses the __FILE__ macro in its debug messages. if let Some(out_dir_str) = Path::new(&out_dir).to_str() { build.flag_if_supported(format!("-fmacro-prefix-map={}=.", out_dir_str).as_str()); } for python_include in env::split_paths(&python_includes) { build.include(python_include); } // Enable abi3 mode if we're not using PyPy. if python_impl != "PyPy" { // cp37 (Python 3.7 to help our grep when we some day drop 3.7 support) build.define("Py_LIMITED_API", "0x030700f0"); } if cfg!(windows) { build.define("WIN32_LEAN_AND_MEAN", None); } build.compile("_openssl.a"); } /// Run a python script using the specified interpreter binary. fn run_python_script(interpreter: impl AsRef, script: &str) -> Result { let interpreter = interpreter.as_ref(); let out = Command::new(interpreter) .env("PYTHONIOENCODING", "utf-8") .arg("-c") .arg(script) .output(); match out { Err(err) => Err(format!( "failed to run the Python interpreter at {}: {}", interpreter.display(), err )), Ok(ok) if !ok.status.success() => Err(format!( "Python script failed: {}", String::from_utf8(ok.stderr).expect("failed to parse Python script stderr as utf-8") )), Ok(ok) => Ok( String::from_utf8(ok.stdout).expect("failed to parse Python script stdout as utf-8") ), } } fn macos_link_search_path() -> Option { let output = Command::new("clang") .arg("--print-search-dirs") .output() .ok()?; if !output.status.success() { println!( "failed to run 'clang --print-search-dirs', continuing without a link search path" ); return None; } let stdout = String::from_utf8_lossy(&output.stdout); for line in stdout.lines() { if line.contains("libraries: =") { let path = line.split('=').nth(1)?; return Some(format!("{path}/lib/darwin")); } } println!("failed to determine link search path, continuing without it"); None } cryptography-43.0.0/src/rust/cryptography-cffi/src/lib.rs010064400017510000177000000021121464676315000216540ustar 00000000000000// 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. #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] #[cfg(python_implementation = "PyPy")] extern "C" { fn Cryptography_make_openssl_module() -> std::os::raw::c_int; } #[cfg(not(python_implementation = "PyPy"))] extern "C" { fn PyInit__openssl() -> *mut pyo3::ffi::PyObject; } pub fn create_module( py: pyo3::Python<'_>, ) -> pyo3::PyResult> { #[cfg(python_implementation = "PyPy")] let openssl_mod = unsafe { let res = Cryptography_make_openssl_module(); assert_eq!(res, 0); pyo3::types::PyModule::import_bound(py, "_openssl")?.clone() }; #[cfg(not(python_implementation = "PyPy"))] // SAFETY: `PyInit__openssl` returns an owned reference. let openssl_mod = unsafe { let ptr = PyInit__openssl(); pyo3::Py::from_owned_ptr_or_err(py, ptr)?.bind(py).clone() }; Ok(openssl_mod) } cryptography-43.0.0/src/rust/cryptography-keepalive/Cargo.toml010064400017510000177000000003571464676315000227500ustar 00000000000000[package] name = "cryptography-keepalive" version.workspace = true authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true [dependencies] pyo3 = { version = "0.22.2", features = ["abi3"] } cryptography-43.0.0/src/rust/cryptography-keepalive/src/lib.rs010064400017510000177000000031571464676315000227240ustar 00000000000000// 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. #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] use pyo3::pybacked::{PyBackedBytes, PyBackedStr}; use std::cell::UnsafeCell; use std::ops::Deref; pub struct KeepAlive { values: UnsafeCell>, } /// # Safety /// Implementers of this trait must ensure that the value returned by /// `deref()` must remain valid, even if `self` is moved. pub unsafe trait StableDeref: Deref {} // SAFETY: `Vec`'s data is on the heap, so as long as it's not mutated, the // slice returned by `deref` remains valid. unsafe impl StableDeref for Vec {} // SAFETY: `PyBackedBytes`'s data is on the heap and `bytes` objects in // Python are immutable. unsafe impl StableDeref for PyBackedBytes {} // SAFETY: `PyBackedStr`'s data is on the heap and `str` objects in // Python are immutable. unsafe impl StableDeref for PyBackedStr {} #[allow(clippy::new_without_default)] impl KeepAlive { pub fn new() -> Self { KeepAlive { values: UnsafeCell::new(vec![]), } } pub fn add(&self, v: T) -> &T::Target { // SAFETY: We only ever append to `self.values`, which, when combined // with the invariants of `StableDeref`, means that the result of // `deref()` will always be valid for the lifetime of `&self`. unsafe { let values = &mut *self.values.get(); values.push(v); values.last().unwrap().deref() } } } cryptography-43.0.0/src/rust/cryptography-key-parsing/Cargo.toml010064400017510000177000000005451464676315000232330ustar 00000000000000[package] name = "cryptography-key-parsing" version.workspace = true authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true [dependencies] asn1 = { version = "0.16.2", default-features = false } cfg-if = "1" openssl = "0.10.65" openssl-sys = "0.9.103" cryptography-x509 = { path = "../cryptography-x509" } cryptography-43.0.0/src/rust/cryptography-key-parsing/build.rs010064400017510000177000000011371464676315000227460ustar 00000000000000// 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. use std::env; fn main() { println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_IS_LIBRESSL)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_IS_BORINGSSL)"); if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); } if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); } } cryptography-43.0.0/src/rust/cryptography-key-parsing/src/lib.rs010064400017510000177000000023541464676315000232060ustar 00000000000000// 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. #![forbid(unsafe_code)] #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] #![allow(unknown_lints, clippy::result_large_err)] pub mod rsa; pub mod spki; pub enum KeyParsingError { InvalidKey, ExplicitCurveUnsupported, UnsupportedKeyType(asn1::ObjectIdentifier), UnsupportedEllipticCurve(asn1::ObjectIdentifier), Parse(asn1::ParseError), OpenSSL(openssl::error::ErrorStack), } impl From for KeyParsingError { fn from(e: asn1::ParseError) -> KeyParsingError { KeyParsingError::Parse(e) } } impl From for KeyParsingError { fn from(e: openssl::error::ErrorStack) -> KeyParsingError { KeyParsingError::OpenSSL(e) } } pub type KeyParsingResult = Result; #[cfg(test)] mod tests { use super::KeyParsingError; #[test] fn test_key_parsing_error_from() { let e = openssl::error::ErrorStack::get(); assert!(matches!( KeyParsingError::from(e), KeyParsingError::OpenSSL(_) )); } } cryptography-43.0.0/src/rust/cryptography-key-parsing/src/rsa.rs010064400017510000177000000013561464676315000232260ustar 00000000000000// 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. use crate::KeyParsingResult; #[derive(asn1::Asn1Read)] pub struct Pkcs1RsaPublicKey<'a> { pub n: asn1::BigUint<'a>, e: asn1::BigUint<'a>, } pub fn parse_pkcs1_public_key( data: &[u8], ) -> KeyParsingResult> { let k = asn1::parse_single::>(data)?; let n = openssl::bn::BigNum::from_slice(k.n.as_bytes())?; let e = openssl::bn::BigNum::from_slice(k.e.as_bytes())?; let rsa = openssl::rsa::Rsa::from_public_components(n, e)?; Ok(openssl::pkey::PKey::from_rsa(rsa)?) } cryptography-43.0.0/src/rust/cryptography-key-parsing/src/spki.rs010064400017510000177000000154671464676315000234170ustar 00000000000000// 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. use cryptography_x509::common::{AlgorithmParameters, EcParameters, SubjectPublicKeyInfo}; use crate::{KeyParsingError, KeyParsingResult}; pub fn parse_public_key( data: &[u8], ) -> KeyParsingResult> { let k = asn1::parse_single::>(data)?; match k.algorithm.params { AlgorithmParameters::Ec(ec_params) => match ec_params { EcParameters::NamedCurve(curve_oid) => { let curve_nid = match curve_oid { cryptography_x509::oid::EC_SECP192R1 => openssl::nid::Nid::X9_62_PRIME192V1, cryptography_x509::oid::EC_SECP224R1 => openssl::nid::Nid::SECP224R1, cryptography_x509::oid::EC_SECP256R1 => openssl::nid::Nid::X9_62_PRIME256V1, cryptography_x509::oid::EC_SECP384R1 => openssl::nid::Nid::SECP384R1, cryptography_x509::oid::EC_SECP521R1 => openssl::nid::Nid::SECP521R1, cryptography_x509::oid::EC_SECP256K1 => openssl::nid::Nid::SECP256K1, cryptography_x509::oid::EC_SECT233R1 => openssl::nid::Nid::SECT233R1, cryptography_x509::oid::EC_SECT283R1 => openssl::nid::Nid::SECT283R1, cryptography_x509::oid::EC_SECT409R1 => openssl::nid::Nid::SECT409R1, cryptography_x509::oid::EC_SECT571R1 => openssl::nid::Nid::SECT571R1, cryptography_x509::oid::EC_SECT163R2 => openssl::nid::Nid::SECT163R2, cryptography_x509::oid::EC_SECT163K1 => openssl::nid::Nid::SECT163K1, cryptography_x509::oid::EC_SECT233K1 => openssl::nid::Nid::SECT233K1, cryptography_x509::oid::EC_SECT283K1 => openssl::nid::Nid::SECT283K1, cryptography_x509::oid::EC_SECT409K1 => openssl::nid::Nid::SECT409K1, cryptography_x509::oid::EC_SECT571K1 => openssl::nid::Nid::SECT571K1, #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] cryptography_x509::oid::EC_BRAINPOOLP256R1 => { openssl::nid::Nid::BRAINPOOL_P256R1 } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] cryptography_x509::oid::EC_BRAINPOOLP384R1 => { openssl::nid::Nid::BRAINPOOL_P384R1 } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] cryptography_x509::oid::EC_BRAINPOOLP512R1 => { openssl::nid::Nid::BRAINPOOL_P512R1 } _ => return Err(KeyParsingError::UnsupportedEllipticCurve(curve_oid)), }; let group = openssl::ec::EcGroup::from_curve_name(curve_nid) .map_err(|_| KeyParsingError::UnsupportedEllipticCurve(curve_oid))?; let mut bn_ctx = openssl::bn::BigNumContext::new()?; let ec_point = openssl::ec::EcPoint::from_bytes( &group, k.subject_public_key.as_bytes(), &mut bn_ctx, ) .map_err(|_| KeyParsingError::InvalidKey)?; let ec_key = openssl::ec::EcKey::from_public_key(&group, &ec_point)?; Ok(openssl::pkey::PKey::from_ec_key(ec_key)?) } EcParameters::ImplicitCurve(_) | EcParameters::SpecifiedCurve(_) => { Err(KeyParsingError::ExplicitCurveUnsupported) } }, AlgorithmParameters::Ed25519 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::ED25519, )?), #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] AlgorithmParameters::Ed448 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::ED448, )?), AlgorithmParameters::X25519 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::X25519, )?), #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] AlgorithmParameters::X448 => Ok(openssl::pkey::PKey::public_key_from_raw_bytes( k.subject_public_key.as_bytes(), openssl::pkey::Id::X448, )?), AlgorithmParameters::Rsa(_) | AlgorithmParameters::RsaPss(_) => { // RSA-PSS keys are treated the same as bare RSA keys. crate::rsa::parse_pkcs1_public_key(k.subject_public_key.as_bytes()) } AlgorithmParameters::Dsa(dsa_params) => { let p = openssl::bn::BigNum::from_slice(dsa_params.p.as_bytes())?; let q = openssl::bn::BigNum::from_slice(dsa_params.q.as_bytes())?; let g = openssl::bn::BigNum::from_slice(dsa_params.g.as_bytes())?; let pub_key_int = asn1::parse_single::>(k.subject_public_key.as_bytes())?; let pub_key = openssl::bn::BigNum::from_slice(pub_key_int.as_bytes())?; let dsa = openssl::dsa::Dsa::from_public_components(p, q, g, pub_key)?; Ok(openssl::pkey::PKey::from_dsa(dsa)?) } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] AlgorithmParameters::Dh(dh_params) => { let p = openssl::bn::BigNum::from_slice(dh_params.p.as_bytes())?; let q = openssl::bn::BigNum::from_slice(dh_params.q.as_bytes())?; let g = openssl::bn::BigNum::from_slice(dh_params.g.as_bytes())?; let dh = openssl::dh::Dh::from_pqg(p, Some(q), g)?; let pub_key_int = asn1::parse_single::>(k.subject_public_key.as_bytes())?; let pub_key = openssl::bn::BigNum::from_slice(pub_key_int.as_bytes())?; let dh = dh.set_public_key(pub_key)?; Ok(openssl::pkey::PKey::from_dh(dh)?) } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] AlgorithmParameters::DhKeyAgreement(dh_params) => { let p = openssl::bn::BigNum::from_slice(dh_params.p.as_bytes())?; let g = openssl::bn::BigNum::from_slice(dh_params.g.as_bytes())?; let dh = openssl::dh::Dh::from_pqg(p, None, g)?; let pub_key_int = asn1::parse_single::>(k.subject_public_key.as_bytes())?; let pub_key = openssl::bn::BigNum::from_slice(pub_key_int.as_bytes())?; let dh = dh.set_public_key(pub_key)?; Ok(openssl::pkey::PKey::from_dh(dh)?) } _ => Err(KeyParsingError::UnsupportedKeyType( k.algorithm.oid().clone(), )), } } cryptography-43.0.0/src/rust/cryptography-openssl/Cargo.toml010064400017510000177000000005051464676315000224610ustar 00000000000000[package] name = "cryptography-openssl" version.workspace = true authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true [dependencies] cfg-if = "1" openssl = "0.10.65" ffi = { package = "openssl-sys", version = "0.9.101" } foreign-types = "0.3" foreign-types-shared = "0.1" cryptography-43.0.0/src/rust/cryptography-openssl/build.rs010064400017510000177000000023411464676315000221760ustar 00000000000000// 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. use std::env; #[allow(clippy::unusual_byte_groupings)] fn main() { println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_IS_LIBRESSL)"); println!("cargo:rustc-check-cfg=cfg(CRYPTOGRAPHY_IS_BORINGSSL)"); if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); if version >= 0x3_00_00_00_0 { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_300_OR_GREATER"); } if version >= 0x3_02_00_00_0 { println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_320_OR_GREATER"); } } if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); } if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); println!("cargo:rustc-link-lib=stdc++"); } } cryptography-43.0.0/src/rust/cryptography-openssl/src/aead.rs010064400017510000177000000050351464676315000225630ustar 00000000000000// 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. use crate::{cvt, cvt_p, OpenSSLResult}; use foreign_types_shared::{ForeignType, ForeignTypeRef}; pub enum AeadType { ChaCha20Poly1305, } foreign_types::foreign_type! { type CType = ffi::EVP_AEAD_CTX; fn drop = ffi::EVP_AEAD_CTX_free; pub struct AeadCtx; pub struct AeadCtxRef; } // SAFETY: Can safely be used from multiple threads concurrently. unsafe impl Sync for AeadCtx {} // SAFETY: Can safely be sent between threads. unsafe impl Send for AeadCtx {} impl AeadCtx { pub fn new(aead: AeadType, key: &[u8]) -> OpenSSLResult { let aead = match aead { // SAFETY: No preconditions. AeadType::ChaCha20Poly1305 => unsafe { ffi::EVP_aead_chacha20_poly1305() }, }; // SAFETY: We're passing a valid key and aead. unsafe { let ctx = cvt_p(ffi::EVP_AEAD_CTX_new( aead, key.as_ptr(), key.len(), ffi::EVP_AEAD_DEFAULT_TAG_LENGTH as usize, ))?; Ok(AeadCtx::from_ptr(ctx)) } } } impl AeadCtxRef { pub fn encrypt( &self, data: &[u8], nonce: &[u8], ad: &[u8], out: &mut [u8], ) -> OpenSSLResult<()> { let mut out_len = out.len(); // SAFETY: All the lengths and pointers are known valid. unsafe { cvt(ffi::EVP_AEAD_CTX_seal( self.as_ptr(), out.as_mut_ptr(), &mut out_len, out.len(), nonce.as_ptr(), nonce.len(), data.as_ptr(), data.len(), ad.as_ptr(), ad.len(), ))?; } Ok(()) } pub fn decrypt( &self, data: &[u8], nonce: &[u8], ad: &[u8], out: &mut [u8], ) -> OpenSSLResult<()> { let mut out_len = out.len(); // SAFETY: All the lengths and pointers are known valid. unsafe { cvt(ffi::EVP_AEAD_CTX_open( self.as_ptr(), out.as_mut_ptr(), &mut out_len, out.len(), nonce.as_ptr(), nonce.len(), data.as_ptr(), data.len(), ad.as_ptr(), ad.len(), ))?; } Ok(()) } } cryptography-43.0.0/src/rust/cryptography-openssl/src/cmac.rs010064400017510000177000000041261464676315000225740ustar 00000000000000// 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. use std::ptr; use foreign_types_shared::{ForeignType, ForeignTypeRef}; use crate::hmac::DigestBytes; use crate::{cvt, cvt_p, OpenSSLResult}; foreign_types::foreign_type! { type CType = ffi::CMAC_CTX; fn drop = ffi::CMAC_CTX_free; pub struct Cmac; pub struct CmacRef; } // SAFETY: It's safe to have `&` references from multiple threads. unsafe impl Sync for Cmac {} // SAFETY: It's safe to move the `Cmac` from one thread to another. unsafe impl Send for Cmac {} impl Cmac { pub fn new(key: &[u8], cipher: &openssl::cipher::CipherRef) -> OpenSSLResult { // SAFETY: All FFI conditions are handled. unsafe { let ctx = Cmac::from_ptr(cvt_p(ffi::CMAC_CTX_new())?); cvt(ffi::CMAC_Init( ctx.as_ptr(), key.as_ptr().cast(), key.len(), cipher.as_ptr(), ptr::null_mut(), ))?; Ok(ctx) } } } impl CmacRef { pub fn update(&mut self, data: &[u8]) -> OpenSSLResult<()> { // SAFETY: All FFI conditions are handled. unsafe { cvt(ffi::CMAC_Update( self.as_ptr(), data.as_ptr().cast(), data.len(), ))?; } Ok(()) } pub fn finish(&mut self) -> OpenSSLResult { let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; let mut len = ffi::EVP_MAX_MD_SIZE as usize; // SAFETY: All FFI conditions are handled. unsafe { cvt(ffi::CMAC_Final(self.as_ptr(), buf.as_mut_ptr(), &mut len))?; } Ok(DigestBytes { buf, len }) } pub fn copy(&self) -> OpenSSLResult { // SAFETY: All FFI conditions are handled. unsafe { let h = Cmac::from_ptr(cvt_p(ffi::CMAC_CTX_new())?); cvt(ffi::CMAC_CTX_copy(h.as_ptr(), self.as_ptr()))?; Ok(h) } } } cryptography-43.0.0/src/rust/cryptography-openssl/src/fips.rs010064400017510000177000000020511464676315000226250ustar 00000000000000// 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. #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] use crate::{cvt, OpenSSLResult}; #[cfg(all( CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) ))] use std::ptr; pub fn is_enabled() -> bool { cfg_if::cfg_if! { if #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] { false } else if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] { // SAFETY: No pre-conditions unsafe { ffi::EVP_default_properties_is_fips_enabled(ptr::null_mut()) == 1 } } else { openssl::fips::enabled() } } } #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] pub fn enable() -> OpenSSLResult<()> { // SAFETY: No pre-conditions unsafe { cvt(ffi::EVP_default_properties_enable_fips(ptr::null_mut(), 1))?; } Ok(()) } cryptography-43.0.0/src/rust/cryptography-openssl/src/hmac.rs010064400017510000177000000055541464676315000226070ustar 00000000000000// 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. use std::ptr; use foreign_types_shared::{ForeignType, ForeignTypeRef}; use crate::{cvt, cvt_p, OpenSSLResult}; foreign_types::foreign_type! { type CType = ffi::HMAC_CTX; fn drop = ffi::HMAC_CTX_free; pub struct Hmac; pub struct HmacRef; } // SAFETY: It's safe to have `&` references from multiple threads. unsafe impl Sync for Hmac {} // SAFETY: It's safe to move the `Hmac` from one thread to another. unsafe impl Send for Hmac {} impl Hmac { // On BoringSSL, the length is a size_t, so the length conversion is a // no-op. #[cfg_attr(CRYPTOGRAPHY_IS_BORINGSSL, allow(clippy::useless_conversion))] pub fn new(key: &[u8], md: openssl::hash::MessageDigest) -> OpenSSLResult { // SAFETY: All FFI conditions are handled. unsafe { let h = Hmac::from_ptr(cvt_p(ffi::HMAC_CTX_new())?); cvt(ffi::HMAC_Init_ex( h.as_ptr(), key.as_ptr().cast(), key.len() .try_into() .expect("Key too long for OpenSSL's length type"), md.as_ptr(), ptr::null_mut(), ))?; Ok(h) } } } impl HmacRef { pub fn update(&mut self, data: &[u8]) -> OpenSSLResult<()> { // SAFETY: All FFI conditions are handled. unsafe { cvt(ffi::HMAC_Update(self.as_ptr(), data.as_ptr(), data.len()))?; } Ok(()) } pub fn finish(&mut self) -> OpenSSLResult { let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; let mut len = ffi::EVP_MAX_MD_SIZE as std::os::raw::c_uint; // SAFETY: All FFI conditions are handled. unsafe { cvt(ffi::HMAC_Final(self.as_ptr(), buf.as_mut_ptr(), &mut len))?; } Ok(DigestBytes { buf, len: len.try_into().unwrap(), }) } pub fn copy(&self) -> OpenSSLResult { // SAFETY: All FFI conditions are handled. unsafe { let h = Hmac::from_ptr(cvt_p(ffi::HMAC_CTX_new())?); cvt(ffi::HMAC_CTX_copy(h.as_ptr(), self.as_ptr()))?; Ok(h) } } } pub struct DigestBytes { pub(crate) buf: [u8; ffi::EVP_MAX_MD_SIZE as usize], pub(crate) len: usize, } impl std::ops::Deref for DigestBytes { type Target = [u8]; #[inline] fn deref(&self) -> &[u8] { &self.buf[..self.len] } } #[cfg(test)] mod tests { use super::DigestBytes; #[test] fn test_digest_bytes() { let d = DigestBytes { buf: [19; ffi::EVP_MAX_MD_SIZE as usize], len: 12, }; assert_eq!(&*d, b"\x13\x13\x13\x13\x13\x13\x13\x13\x13\x13\x13\x13"); } } cryptography-43.0.0/src/rust/cryptography-openssl/src/lib.rs010064400017510000177000000020511464676315000224320ustar 00000000000000// 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. #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] pub mod aead; pub mod cmac; pub mod fips; pub mod hmac; #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] pub mod poly1305; pub type OpenSSLResult = Result; #[inline] fn cvt(r: std::os::raw::c_int) -> Result { if r <= 0 { Err(openssl::error::ErrorStack::get()) } else { Ok(r) } } #[inline] fn cvt_p(r: *mut T) -> Result<*mut T, openssl::error::ErrorStack> { if r.is_null() { Err(openssl::error::ErrorStack::get()) } else { Ok(r) } } #[cfg(test)] mod tests { use std::ptr; #[test] fn test_cvt() { assert!(crate::cvt(-1).is_err()); assert!(crate::cvt_p(ptr::null_mut::<()>()).is_err()); } } cryptography-43.0.0/src/rust/cryptography-openssl/src/poly1305.rs010064400017510000177000000037251464676315000231710ustar 00000000000000// 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. use std::mem::MaybeUninit; pub struct Poly1305State { // The state data must be allocated in the heap so that its address does not change. This is // because BoringSSL APIs that take a `poly1305_state*` ignore all the data before an aligned // address. Since a stack-allocated struct would change address on every copy, BoringSSL would // interpret each copy differently, causing unexpected behavior. context: Box, } impl Poly1305State { pub fn new(key: &[u8]) -> Poly1305State { assert_eq!(key.len(), 32); let mut ctx: Box> = Box::new(MaybeUninit::::uninit()); // SAFETY: After initializing the context, unwrap the // `Box>` into a `Box` // while keeping the same memory address. See the docstring of the // `Poly1305State` struct above for the rationale. let initialized_ctx: Box = unsafe { ffi::CRYPTO_poly1305_init(ctx.as_mut().as_mut_ptr(), key.as_ptr()); let raw_ctx_ptr = (*Box::into_raw(ctx)).as_mut_ptr(); Box::from_raw(raw_ctx_ptr) }; Poly1305State { context: initialized_ctx, } } pub fn update(&mut self, data: &[u8]) { // SAFETY: context is valid, as is the data ptr. unsafe { ffi::CRYPTO_poly1305_update(self.context.as_mut(), data.as_ptr(), data.len()); }; } pub fn finalize(&mut self, output: &mut [u8]) { assert_eq!(output.len(), 16); // SAFETY: context is valid and we verified that the output is the // right length. unsafe { ffi::CRYPTO_poly1305_finish(self.context.as_mut(), output.as_mut_ptr()) }; } } cryptography-43.0.0/src/rust/cryptography-x509-verification/Cargo.toml010064400017510000177000000007141464676315000241650ustar 00000000000000[package] name = "cryptography-x509-verification" version.workspace = true authors.workspace = true edition.workspace = true publish.workspace = true rust-version.workspace = true [dependencies] asn1 = { version = "0.16.2", default-features = false } cryptography-x509 = { path = "../cryptography-x509" } cryptography-key-parsing = { path = "../cryptography-key-parsing" } once_cell = "1" [dev-dependencies] pem = { version = "3", default-features = false } cryptography-43.0.0/src/rust/cryptography-x509-verification/src/certificate.rs010064400017510000177000000051621464676315000256560ustar 00000000000000// 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. //! Validation-specific certificate functionality. use cryptography_x509::certificate::Certificate; pub(crate) fn cert_is_self_issued(cert: &Certificate<'_>) -> bool { cert.issuer() == cert.subject() } #[cfg(test)] pub(crate) mod tests { use super::cert_is_self_issued; use crate::certificate::Certificate; use crate::ops::tests::{cert, v1_cert_pem}; use crate::ops::CryptoOps; #[test] fn test_certificate_v1() { let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); assert!(!cert_is_self_issued(&cert)); } fn ca_pem() -> pem::Pem { // From vectors/cryptography_vectors/x509/custom/ca/ca.pem pem::parse( "-----BEGIN CERTIFICATE----- MIIBUTCB96ADAgECAgIDCTAKBggqhkjOPQQDAjAnMQswCQYDVQQGEwJVUzEYMBYG A1UEAwwPY3J5cHRvZ3JhcGh5IENBMB4XDTE3MDEwMTEyMDEwMFoXDTM4MTIzMTA4 MzAwMFowJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBDQTBZ MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBj/z7v5Obj13cPuwECLBnUGq0/N2CxS JE4f4BBGZ7VfFblivTvPDG++Gve0oQ+0uctuhrNQ+WxRv8GC177F+QWjEzARMA8G A1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhANES742XWm64tkGnz8Dn pG6u2lHkZFQr3oaVvPcemvlbAiEA0WGGzmYx5C9UvfXIK7NEziT4pQtyESE0uRVK Xw4nMqk= -----END CERTIFICATE-----", ) .unwrap() } #[test] fn test_certificate_ca() { let cert_pem = ca_pem(); let cert = cert(&cert_pem); assert!(cert_is_self_issued(&cert)); } pub(crate) struct PublicKeyErrorOps {} impl CryptoOps for PublicKeyErrorOps { type Key = (); type Err = (); type CertificateExtra = (); fn public_key(&self, _cert: &Certificate<'_>) -> Result { // Simulate failing to retrieve a public key. Err(()) } fn verify_signed_by( &self, _cert: &Certificate<'_>, _key: &Self::Key, ) -> Result<(), Self::Err> { Ok(()) } } #[test] fn test_certificate_public_key_error() { let cert_pem = ca_pem(); let cert = cert(&cert_pem); assert!(cert_is_self_issued(&cert)); } #[test] fn test_certificate_public_key_error_ops() { // Just to get coverage on the `PublicKeyErrorOps` helper. let cert_pem = ca_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; assert!(ops.public_key(&cert).is_err()); assert!(ops.verify_signed_by(&cert, &()).is_ok()); } } cryptography-43.0.0/src/rust/cryptography-x509-verification/src/lib.rs010064400017510000177000000425131464676315000241430ustar 00000000000000// 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. #![forbid(unsafe_code)] #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] #![allow(unknown_lints, clippy::result_large_err)] pub mod certificate; pub mod ops; pub mod policy; pub mod trust_store; pub mod types; use std::fmt::Display; use std::vec; use asn1::ObjectIdentifier; use cryptography_x509::extensions::{DuplicateExtensionsError, Extensions}; use cryptography_x509::{ extensions::{NameConstraints, SubjectAlternativeName}, name::GeneralName, oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}, }; use types::{RFC822Constraint, RFC822Name}; use crate::certificate::cert_is_self_issued; use crate::ops::{CryptoOps, VerificationCertificate}; use crate::policy::Policy; use crate::trust_store::Store; use crate::types::DNSName; use crate::types::{DNSConstraint, IPAddress, IPConstraint}; use crate::ApplyNameConstraintStatus::{Applied, Skipped}; #[derive(Debug)] pub enum ValidationError { CandidatesExhausted(Box), Malformed(asn1::ParseError), ExtensionError { oid: ObjectIdentifier, reason: &'static str, }, FatalError(&'static str), Other(String), } impl From for ValidationError { fn from(value: asn1::ParseError) -> Self { Self::Malformed(value) } } impl From for ValidationError { fn from(value: DuplicateExtensionsError) -> Self { Self::ExtensionError { oid: value.0, reason: "duplicate extension", } } } impl Display for ValidationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ValidationError::CandidatesExhausted(inner) => { write!(f, "candidates exhausted: {inner}") } ValidationError::Malformed(err) => err.fmt(f), ValidationError::ExtensionError { oid, reason } => { write!(f, "invalid extension: {oid}: {reason}") } ValidationError::FatalError(err) => write!(f, "fatal error: {err}"), ValidationError::Other(err) => write!(f, "{err}"), } } } struct Budget { name_constraint_checks: usize, } impl Budget { // Same limit as other validators const DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT: usize = 1 << 20; fn new() -> Budget { Budget { name_constraint_checks: Self::DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT, } } fn name_constraint_check(&mut self) -> Result<(), ValidationError> { self.name_constraint_checks = self.name_constraint_checks .checked_sub(1) .ok_or(ValidationError::FatalError( "Exceeded maximum name constraint check limit", ))?; Ok(()) } } struct NameChain<'a, 'chain> { child: Option<&'a NameChain<'a, 'chain>>, sans: SubjectAlternativeName<'chain>, } impl<'a, 'chain> NameChain<'a, 'chain> { fn new( child: Option<&'a NameChain<'a, 'chain>>, extensions: &Extensions<'chain>, self_issued_intermediate: bool, ) -> Result { let sans = match ( self_issued_intermediate, extensions.get_extension(&SUBJECT_ALTERNATIVE_NAME_OID), ) { (false, Some(sans)) => sans.value::>()?, // TODO: there really ought to be a better way to express an empty // `asn1::SequenceOf`. _ => asn1::parse_single(b"\x30\x00")?, }; Ok(Self { child, sans }) } fn evaluate_single_constraint( &self, constraint: &GeneralName<'chain>, san: &GeneralName<'chain>, budget: &mut Budget, ) -> Result { budget.name_constraint_check()?; match (constraint, san) { (GeneralName::DNSName(pattern), GeneralName::DNSName(name)) => { match (DNSConstraint::new(pattern.0), DNSName::new(name.0)) { (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), (_, None) => Err(ValidationError::Other(format!( "unsatisfiable DNS name constraint: malformed SAN {}", name.0 ))), (None, _) => Err(ValidationError::Other(format!( "malformed DNS name constraint: {}", pattern.0 ))), } } (GeneralName::IPAddress(pattern), GeneralName::IPAddress(name)) => { match ( IPConstraint::from_bytes(pattern), IPAddress::from_bytes(name), ) { (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), (_, None) => Err(ValidationError::Other(format!( "unsatisfiable IP name constraint: malformed SAN {:?}", name, ))), (None, _) => Err(ValidationError::Other(format!( "malformed IP name constraints: {:?}", pattern ))), } } (GeneralName::RFC822Name(pattern), GeneralName::RFC822Name(name)) => { match (RFC822Constraint::new(pattern.0), RFC822Name::new(name.0)) { (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), (_, None) => Err(ValidationError::Other(format!( "unsatisfiable RFC822 name constraint: malformed SAN {:?}", name.0, ))), (None, _) => Err(ValidationError::Other(format!( "malformed RFC822 name constraints: {:?}", pattern.0 ))), } } // All other matching pairs of (constraint, name) are currently unsupported. (GeneralName::OtherName(_), GeneralName::OtherName(_)) | (GeneralName::X400Address(_), GeneralName::X400Address(_)) | (GeneralName::DirectoryName(_), GeneralName::DirectoryName(_)) | (GeneralName::EDIPartyName(_), GeneralName::EDIPartyName(_)) | ( GeneralName::UniformResourceIdentifier(_), GeneralName::UniformResourceIdentifier(_), ) | (GeneralName::RegisteredID(_), GeneralName::RegisteredID(_)) => Err( ValidationError::Other("unsupported name constraint".to_string()), ), _ => Ok(Skipped), } } fn evaluate_constraints( &self, constraints: &NameConstraints<'chain>, budget: &mut Budget, ) -> Result<(), ValidationError> { if let Some(child) = self.child { child.evaluate_constraints(constraints, budget)?; } for san in self.sans.clone() { // If there are no applicable constraints, the SAN is considered valid so the default is true. let mut permit = true; if let Some(permitted_subtrees) = &constraints.permitted_subtrees { for p in permitted_subtrees.unwrap_read().clone() { let status = self.evaluate_single_constraint(&p.base, &san, budget)?; if status.is_applied() { permit = status.is_match(); if permit { break; } } } } if !permit { return Err(ValidationError::Other( "no permitted name constraints matched SAN".into(), )); } if let Some(excluded_subtrees) = &constraints.excluded_subtrees { for e in excluded_subtrees.unwrap_read().clone() { let status = self.evaluate_single_constraint(&e.base, &san, budget)?; if status.is_match() { return Err(ValidationError::Other( "excluded name constraint matched SAN".into(), )); } } } } Ok(()) } } pub type Chain<'a, 'c, B> = Vec<&'a VerificationCertificate<'c, B>>; pub fn verify<'a, 'chain: 'a, B: CryptoOps>( leaf: &'a VerificationCertificate<'chain, B>, intermediates: &'a [&'a VerificationCertificate<'chain, B>], policy: &'a Policy<'_, B>, store: &'a Store<'chain, B>, ) -> Result, ValidationError> { let builder = ChainBuilder::new(intermediates, policy, store); let mut budget = Budget::new(); builder.build_chain(leaf, &mut budget) } struct ChainBuilder<'a, 'chain, B: CryptoOps> { intermediates: &'a [&'a VerificationCertificate<'chain, B>], policy: &'a Policy<'a, B>, store: &'a Store<'chain, B>, } // When applying a name constraint, we need to distinguish between a few different scenarios: // * `Applied(true)`: The name constraint is the same type as the SAN and matches. // * `Applied(false)`: The name constraint is the same type as the SAN and does not match. // * `Skipped`: The name constraint is a different type to the SAN. enum ApplyNameConstraintStatus { Applied(bool), Skipped, } impl ApplyNameConstraintStatus { fn is_applied(&self) -> bool { matches!(self, Applied(_)) } fn is_match(&self) -> bool { matches!(self, Applied(true)) } } impl<'a, 'chain: 'a, B: CryptoOps> ChainBuilder<'a, 'chain, B> { fn new( intermediates: &'a [&'a VerificationCertificate<'chain, B>], policy: &'a Policy<'a, B>, store: &'a Store<'chain, B>, ) -> Self { Self { intermediates, policy, store, } } fn potential_issuers( &self, cert: &'a VerificationCertificate<'chain, B>, ) -> impl Iterator> + '_ { // TODO: Optimizations: // * Search by AKI and other identifiers? self.store .get_by_subject(&cert.certificate().tbs_cert.issuer) .iter() .chain(self.intermediates.iter().copied().filter(|&candidate| { candidate.certificate().subject() == cert.certificate().issuer() })) } fn build_chain_inner( &self, working_cert: &'a VerificationCertificate<'chain, B>, current_depth: u8, working_cert_extensions: &Extensions<'chain>, name_chain: NameChain<'_, 'chain>, budget: &mut Budget, ) -> Result, ValidationError> { if let Some(nc) = working_cert_extensions.get_extension(&NAME_CONSTRAINTS_OID) { name_chain.evaluate_constraints(&nc.value()?, budget)?; } // Look in the store's root set to see if the working cert is listed. // If it is, we've reached the end. if self.store.contains(working_cert) { return Ok(vec![working_cert]); } // Check that our current depth does not exceed our policy-configured // max depth. We do this after the root set check, since the depth // only measures the intermediate chain's length, not the root or leaf. if current_depth > self.policy.max_chain_depth { return Err(ValidationError::Other( "chain construction exceeds max depth".into(), )); } // Otherwise, we collect a list of potential issuers for this cert, // and continue with the first that verifies. let mut last_err: Option = None; for issuing_cert_candidate in self.potential_issuers(working_cert) { // A candidate issuer is said to verify if it both // signs for the working certificate and conforms to the // policy. let issuer_extensions = issuing_cert_candidate.certificate().extensions()?; match self.policy.valid_issuer( issuing_cert_candidate, working_cert.certificate(), current_depth, &issuer_extensions, ) { Ok(_) => { match self.build_chain_inner( issuing_cert_candidate, // NOTE(ww): According to RFC 5280, we should only // increase the chain depth when the certificate is **not** // self-issued. In practice however, implementations widely // ignore this requirement, and unconditionally increment // the depth with every chain member. We choose to do the same; // see `pathlen::self-issued-certs-pathlen` from x509-limbo // for the testcase we intentionally fail. // // Implementation note for someone looking to change this in the future: // care should be taken to avoid infinite recursion with self-signed // certificates in the intermediate set; changing this behavior will // also require a "is not self-signed" check on intermediate candidates. // // See https://gist.github.com/woodruffw/776153088e0df3fc2f0675c5e835f7b8 // for an example of this change. current_depth.checked_add(1).ok_or_else(|| { ValidationError::Other( "current depth calculation overflowed".to_string(), ) })?, &issuer_extensions, NameChain::new( Some(&name_chain), &issuer_extensions, // Per RFC 5280 4.2.1.10: Name constraints are not applied // to subjects in self-issued certificates, *unless* the // certificate is the "final" (i.e., leaf) certificate in the path. // We accomplish this by only collecting the SANs when the issuing // candidate (which is a non-leaf by definition) isn't self-issued. cert_is_self_issued(issuing_cert_candidate.certificate()), )?, budget, ) { Ok(mut chain) => { chain.push(working_cert); return Ok(chain); } // Immediately return on fatal error. Err(e @ ValidationError::FatalError(..)) => return Err(e), Err(e) => last_err = Some(e), }; } Err(e) => last_err = Some(e), }; } // We only reach this if we fail to hit our base case above, or if // a chain building step fails to find a next valid certificate. Err(ValidationError::CandidatesExhausted(last_err.map_or_else( || { Box::new(ValidationError::Other( "all candidates exhausted with no interior errors".to_string(), )) }, |e| match e { // Avoid spamming the user with nested `CandidatesExhausted` errors. ValidationError::CandidatesExhausted(e) => e, _ => Box::new(e), }, ))) } fn build_chain( &self, leaf: &'a VerificationCertificate<'chain, B>, budget: &mut Budget, ) -> Result, ValidationError> { // Before anything else, check whether the given leaf cert // is well-formed according to our policy (and its underlying // certificate profile). // // The leaf must be an EE; a CA cert in the leaf position will be rejected. let leaf_extensions = leaf.certificate().extensions()?; self.policy .permits_ee(leaf.certificate(), &leaf_extensions)?; let mut chain = self.build_chain_inner( leaf, 0, &leaf_extensions, NameChain::new(None, &leaf_extensions, false)?, budget, )?; // We build the chain in reverse order, fix it now. chain.reverse(); Ok(chain) } } #[cfg(test)] mod tests { use asn1::ParseError; use cryptography_x509::oid::SUBJECT_ALTERNATIVE_NAME_OID; use crate::ValidationError; #[test] fn test_validationerror_display() { let err = ValidationError::Malformed(ParseError::new(asn1::ParseErrorKind::InvalidLength)); assert_eq!(err.to_string(), "ASN.1 parsing error: invalid length"); let err = ValidationError::ExtensionError { oid: SUBJECT_ALTERNATIVE_NAME_OID, reason: "duplicate extension", }; assert_eq!( err.to_string(), "invalid extension: 2.5.29.17: duplicate extension" ); let err = ValidationError::FatalError("oops"); assert_eq!(err.to_string(), "fatal error: oops"); } } cryptography-43.0.0/src/rust/cryptography-x509-verification/src/ops.rs010064400017510000177000000053111464676315000241710ustar 00000000000000// 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. use cryptography_x509::certificate::Certificate; pub struct VerificationCertificate<'a, B: CryptoOps> { cert: Certificate<'a>, public_key: once_cell::sync::OnceCell, extra: B::CertificateExtra, } impl<'a, B: CryptoOps> VerificationCertificate<'a, B> { pub fn new(cert: Certificate<'a>, extra: B::CertificateExtra) -> Self { VerificationCertificate { cert, extra, public_key: once_cell::sync::OnceCell::new(), } } pub fn certificate(&self) -> &Certificate<'a> { &self.cert } pub fn public_key(&self, ops: &B) -> Result<&B::Key, B::Err> { self.public_key .get_or_try_init(|| ops.public_key(self.certificate())) } pub fn extra(&self) -> &B::CertificateExtra { &self.extra } } impl PartialEq for VerificationCertificate<'_, B> { fn eq(&self, other: &Self) -> bool { self.cert == other.cert } } impl Eq for VerificationCertificate<'_, B> {} pub trait CryptoOps { /// A public key type for this cryptographic backend. type Key; /// An error type for this cryptographic backend. type Err; /// Extra data that's passed around with the certificate. type CertificateExtra; /// Extracts the public key from the given `Certificate` in /// a `Key` format known by the cryptographic backend, or `None` /// if the key is malformed. fn public_key(&self, cert: &Certificate<'_>) -> Result; /// Verifies the signature on `Certificate` using the given /// `Key`. fn verify_signed_by(&self, cert: &Certificate<'_>, key: &Self::Key) -> Result<(), Self::Err>; } #[cfg(test)] pub(crate) mod tests { use cryptography_x509::certificate::Certificate; pub(crate) fn v1_cert_pem() -> pem::Pem { pem::parse( " -----BEGIN CERTIFICATE----- MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO /Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ zl9HYIMxATFyqSiD9jsx -----END CERTIFICATE-----", ) .unwrap() } pub(crate) fn cert(cert_pem: &pem::Pem) -> Certificate<'_> { asn1::parse_single(cert_pem.contents()).unwrap() } } cryptography-43.0.0/src/rust/cryptography-x509-verification/src/policy/extension.rs010064400017510000177000000664501464676315000267160ustar 00000000000000// 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. use cryptography_x509::oid::{ AUTHORITY_INFORMATION_ACCESS_OID, AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID, EXTENDED_KEY_USAGE_OID, KEY_USAGE_OID, NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID, SUBJECT_KEY_IDENTIFIER_OID, }; use cryptography_x509::{ certificate::Certificate, extensions::{Extension, Extensions}, }; use crate::{ops::CryptoOps, policy::Policy, ValidationError}; pub(crate) struct ExtensionPolicy { pub(crate) authority_information_access: ExtensionValidator, pub(crate) authority_key_identifier: ExtensionValidator, pub(crate) subject_key_identifier: ExtensionValidator, pub(crate) key_usage: ExtensionValidator, pub(crate) subject_alternative_name: ExtensionValidator, pub(crate) basic_constraints: ExtensionValidator, pub(crate) name_constraints: ExtensionValidator, pub(crate) extended_key_usage: ExtensionValidator, } impl ExtensionPolicy { pub(crate) fn permits( &self, policy: &Policy<'_, B>, cert: &Certificate<'_>, extensions: &Extensions<'_>, ) -> Result<(), ValidationError> { let mut authority_information_access_seen = false; let mut authority_key_identifier_seen = false; let mut subject_key_identifier_seen = false; let mut key_usage_seen = false; let mut subject_alternative_name_seen = false; let mut basic_constraints_seen = false; let mut name_constraints_seen = false; let mut extended_key_usage_seen = false; // Iterate over each extension and run its policy. for ext in extensions.iter() { match ext.extn_id { AUTHORITY_INFORMATION_ACCESS_OID => { authority_information_access_seen = true; self.authority_information_access .permits(policy, cert, Some(&ext))?; } AUTHORITY_KEY_IDENTIFIER_OID => { authority_key_identifier_seen = true; self.authority_key_identifier .permits(policy, cert, Some(&ext))?; } SUBJECT_KEY_IDENTIFIER_OID => { subject_key_identifier_seen = true; self.subject_key_identifier .permits(policy, cert, Some(&ext))?; } KEY_USAGE_OID => { key_usage_seen = true; self.key_usage.permits(policy, cert, Some(&ext))?; } SUBJECT_ALTERNATIVE_NAME_OID => { subject_alternative_name_seen = true; self.subject_alternative_name .permits(policy, cert, Some(&ext))?; } BASIC_CONSTRAINTS_OID => { basic_constraints_seen = true; self.basic_constraints.permits(policy, cert, Some(&ext))?; } NAME_CONSTRAINTS_OID => { name_constraints_seen = true; self.name_constraints.permits(policy, cert, Some(&ext))?; } EXTENDED_KEY_USAGE_OID => { extended_key_usage_seen = true; self.extended_key_usage.permits(policy, cert, Some(&ext))?; } _ if ext.critical => { return Err(ValidationError::ExtensionError { oid: ext.extn_id, reason: "certificate contains unaccounted-for critical extensions", }); } _ => {} } } // Now we check if there were any required extensions that aren't // present if !authority_information_access_seen { self.authority_information_access .permits(policy, cert, None)?; } if !authority_key_identifier_seen { self.authority_key_identifier.permits(policy, cert, None)?; } if !subject_key_identifier_seen { self.subject_key_identifier.permits(policy, cert, None)?; } if !key_usage_seen { self.key_usage.permits(policy, cert, None)?; } if !subject_alternative_name_seen { self.subject_alternative_name.permits(policy, cert, None)?; } if !basic_constraints_seen { self.basic_constraints.permits(policy, cert, None)?; } if !name_constraints_seen { self.name_constraints.permits(policy, cert, None)?; } if !extended_key_usage_seen { self.extended_key_usage.permits(policy, cert, None)?; } Ok(()) } } /// Represents different criticality states for an extension. pub(crate) enum Criticality { /// The extension MUST be marked as critical. Critical, /// The extension MAY be marked as critical. Agnostic, /// The extension MUST NOT be marked as critical. NonCritical, } impl Criticality { pub(crate) fn permits(&self, critical: bool) -> bool { match (self, critical) { (Criticality::Critical, true) => true, (Criticality::Critical, false) => false, (Criticality::Agnostic, _) => true, (Criticality::NonCritical, true) => false, (Criticality::NonCritical, false) => true, } } } type PresentExtensionValidatorCallback = fn(&Policy<'_, B>, &Certificate<'_>, &Extension<'_>) -> Result<(), ValidationError>; type MaybeExtensionValidatorCallback = fn(&Policy<'_, B>, &Certificate<'_>, Option<&Extension<'_>>) -> Result<(), ValidationError>; /// Represents different validation states for an extension. pub(crate) enum ExtensionValidator { /// The extension MUST NOT be present. NotPresent, /// The extension MUST be present. Present { /// The extension's criticality. criticality: Criticality, /// An optional validator over the extension's inner contents, with /// the surrounding `Policy` as context. validator: Option>, }, /// The extension MAY be present; the interior validator is /// always called if supplied, including if the extension is not present. MaybePresent { criticality: Criticality, validator: Option>, }, } impl ExtensionValidator { pub(crate) fn not_present() -> Self { Self::NotPresent } pub(crate) fn present( criticality: Criticality, validator: Option>, ) -> Self { Self::Present { criticality, validator, } } pub(crate) fn maybe_present( criticality: Criticality, validator: Option>, ) -> Self { Self::MaybePresent { criticality, validator, } } pub(crate) fn permits( &self, policy: &Policy<'_, B>, cert: &Certificate<'_>, extension: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { match (self, extension) { // Extension MUST NOT be present and isn't; OK. (ExtensionValidator::NotPresent, None) => Ok(()), // Extension MUST NOT be present but is; NOT OK. (ExtensionValidator::NotPresent, Some(extn)) => Err(ValidationError::ExtensionError { oid: extn.extn_id.clone(), reason: "Certificate contains prohibited extension", }), // Extension MUST be present but is not; NOT OK. (ExtensionValidator::Present { .. }, None) => Err(ValidationError::Other( "Certificate is missing required extension".to_string(), )), // Extension MUST be present and is; check it. ( ExtensionValidator::Present { criticality, validator, }, Some(extn), ) => { if !criticality.permits(extn.critical) { return Err(ValidationError::ExtensionError { oid: extn.extn_id.clone(), reason: "Certificate extension has incorrect criticality", }); } // If a custom validator is supplied, apply it. validator.map_or(Ok(()), |v| v(policy, cert, extn)) } // Extension MAY be present. ( ExtensionValidator::MaybePresent { criticality, validator, }, extn, ) => { match extn { // If the extension is present, apply our criticality check. Some(extn) if !criticality.permits(extn.critical) => { Err(ValidationError::ExtensionError { oid: extn.extn_id.clone(), reason: "Certificate extension has incorrect criticality", }) } // If a custom validator is supplied, apply it. _ => validator.map_or(Ok(()), |v| v(policy, cert, extn)), } } } } } pub(crate) mod ee { use cryptography_x509::{ certificate::Certificate, extensions::{ BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage, SubjectAlternativeName, }, }; use crate::{ ops::CryptoOps, policy::{Policy, ValidationError}, }; pub(crate) fn basic_constraints( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { if let Some(extn) = extn { let basic_constraints: BasicConstraints = extn.value()?; if basic_constraints.ca { return Err(ValidationError::Other( "basicConstraints.cA must not be asserted in an EE certificate".to_string(), )); } } Ok(()) } pub(crate) fn subject_alternative_name( policy: &Policy<'_, B>, cert: &Certificate<'_>, extn: &Extension<'_>, ) -> Result<(), ValidationError> { match (cert.subject().is_empty(), extn.critical) { // If the subject is empty, the SAN MUST be critical. (true, false) => { return Err(ValidationError::Other( "EE subjectAltName MUST be critical when subject is empty".to_string(), )); } // If the subject is non-empty, the SAN MUST NOT be critical. (false, true) => { return Err(ValidationError::Other( "EE subjectAltName MUST NOT be critical when subject is nonempty".to_string(), )) } _ => (), }; // NOTE: We only verify the SAN against the policy's subject if the // policy actually contains one. This enables both client and server // profiles to use this validator, **with the expectation** that // server profile construction requires a subject to be present. if let Some(sub) = policy.subject.as_ref() { let san: SubjectAlternativeName<'_> = extn.value()?; if !sub.matches(&san) { return Err(ValidationError::Other( "leaf certificate has no matching subjectAltName".into(), )); } } Ok(()) } pub(crate) fn extended_key_usage( policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { if let Some(extn) = extn { let mut ekus: ExtendedKeyUsage<'_> = extn.value()?; // CABF requires EKUs in EE certs, but this is widely ignored // by implementations (which treat a missing EKU as "any EKU"). // On the other hand, if the EKU is present, it **must** be // the one specified in the policy (e.g., `serverAuth`) and // **must not** be the explicit `anyExtendedKeyUsage` EKU. // See: CABF 7.1.2.7.10. if ekus.any(|eku| eku == policy.extended_key_usage) { Ok(()) } else { Err(ValidationError::Other("required EKU not found".to_string())) } } else { Ok(()) } } pub(crate) fn key_usage( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { if let Some(extn) = extn { let key_usage: KeyUsage<'_> = extn.value()?; if key_usage.key_cert_sign() { return Err(ValidationError::Other( "EE keyUsage must not assert keyCertSign".to_string(), )); } } Ok(()) } } pub(crate) mod ca { use cryptography_x509::{ certificate::Certificate, extensions::{ AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, Extension, KeyUsage, NameConstraints, }, oid::EKU_ANY_KEY_USAGE_OID, }; use crate::{ ops::CryptoOps, policy::{Policy, ValidationError}, }; pub(crate) fn authority_key_identifier( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { // CABF: AKI is required on all CA certificates *except* root CA certificates, // where is it merely recommended. This is slightly different from RFC 5280, // which requires AKI on all CA certificates *except* self-signed root CA certificates. // // This discrepancy poses a challenge: from a strict CABF perspective we should // require the AKI unless we're on a root CA, but we lack the context to determine that // here. We *could* infer that we're on a root by checking whether the CA is self-signed, // but many root CAs still use RSA with SHA-1 (which is intentionally unsupported // for signature verification). // // Consequently, the best we can currently do here is check whether the AKI conforms // to the CABF mandated format, *if* it exists. This means that we will accept // some chains that are not strictly CABF compliant (e.g. ones where intermediate // CAs are missing AKIs), but this is a relatively minor discrepancy. if let Some(extn) = extn { let aki: AuthorityKeyIdentifier<'_> = extn.value()?; // 7.1.2.11.1 Authority Key Identifier: // keyIdentifier MUST be present. // TODO: Check that keyIdentifier matches subjectKeyIdentifier. if aki.key_identifier.is_none() { return Err(ValidationError::Other( "authorityKeyIdentifier must contain keyIdentifier".to_string(), )); } // authorityCertIssuer and authorityCertSerialNumber MUST NOT be present. if aki.authority_cert_issuer.is_some() { return Err(ValidationError::Other( "authorityKeyIdentifier must not contain authorityCertIssuer".to_string(), )); } if aki.authority_cert_serial_number.is_some() { return Err(ValidationError::Other( "authorityKeyIdentifier must not contain authorityCertSerialNumber".to_string(), )); } } Ok(()) } pub(crate) fn key_usage( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: &Extension<'_>, ) -> Result<(), ValidationError> { let key_usage: KeyUsage<'_> = extn.value()?; if !key_usage.key_cert_sign() { return Err(ValidationError::Other( "keyUsage.keyCertSign must be asserted in a CA certificate".to_string(), )); } Ok(()) } pub(crate) fn basic_constraints( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: &Extension<'_>, ) -> Result<(), ValidationError> { let basic_constraints: BasicConstraints = extn.value()?; if !basic_constraints.ca { return Err(ValidationError::Other( "basicConstraints.cA must be asserted in a CA certificate".to_string(), )); } // NOTE: basicConstraints.pathLength is checked as part of // `Policy::permits_ca`, since we need the current chain building // depth to check it. Ok(()) } pub(crate) fn name_constraints( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { if let Some(extn) = extn { let name_constraints: NameConstraints<'_> = extn.value()?; let permitted_subtrees_empty = name_constraints .permitted_subtrees .as_ref() .map_or(true, |pst| pst.unwrap_read().is_empty()); let excluded_subtrees_empty = name_constraints .excluded_subtrees .as_ref() .map_or(true, |est| est.unwrap_read().is_empty()); if permitted_subtrees_empty && excluded_subtrees_empty { return Err(ValidationError::Other( "nameConstraints must have non-empty permittedSubtrees or excludedSubtrees" .to_string(), )); } // NOTE: Both RFC 5280 and CABF require each `GeneralSubtree` // to have `minimum=0` and `maximum=NULL`, but experimentally // not many validators check for this. } Ok(()) } pub(crate) fn extended_key_usage( policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { if let Some(extn) = extn { let mut ekus: ExtendedKeyUsage<'_> = extn.value()?; // NOTE: CABF explicitly forbids anyEKU in and most CA certs, // but this is widely (universally?) ignored by other implementations. if ekus.any(|eku| eku == policy.extended_key_usage || eku == EKU_ANY_KEY_USAGE_OID) { Ok(()) } else { Err(ValidationError::Other("required EKU not found".to_string())) } } else { Ok(()) } } } pub(crate) mod common { use cryptography_x509::{ certificate::Certificate, extensions::{Extension, SequenceOfAccessDescriptions}, }; use crate::{ ops::CryptoOps, policy::{Policy, ValidationError}, }; pub(crate) fn authority_information_access( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, extn: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { if let Some(extn) = extn { // We don't currently do anything useful with these, but we // do check that they're well-formed. let _: SequenceOfAccessDescriptions<'_> = extn.value()?; } Ok(()) } } #[cfg(test)] mod tests { use asn1::{ObjectIdentifier, SimpleAsn1Writable}; use cryptography_x509::certificate::Certificate; use cryptography_x509::extensions::{BasicConstraints, Extension}; use cryptography_x509::oid::BASIC_CONSTRAINTS_OID; use super::{Criticality, ExtensionValidator}; use crate::certificate::tests::PublicKeyErrorOps; use crate::ops::tests::{cert, v1_cert_pem}; use crate::ops::CryptoOps; use crate::policy::{Policy, Subject, ValidationError}; use crate::types::DNSName; #[test] fn test_criticality_variants() { let criticality = Criticality::Critical; assert!(criticality.permits(true)); assert!(!criticality.permits(false)); let criticality = Criticality::Agnostic; assert!(criticality.permits(true)); assert!(criticality.permits(false)); let criticality = Criticality::NonCritical; assert!(!criticality.permits(true)); assert!(criticality.permits(false)); } fn epoch() -> asn1::DateTime { asn1::DateTime::new(1970, 1, 1, 0, 0, 0).unwrap() } fn create_encoded_extension( oid: ObjectIdentifier, critical: bool, ext: &T, ) -> Vec { let ext_value = asn1::write_single(&ext).unwrap(); let ext = Extension { extn_id: oid, critical, extn_value: &ext_value, }; asn1::write_single(&ext).unwrap() } fn present_extension_validator( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, _ext: &Extension<'_>, ) -> Result<(), ValidationError> { Ok(()) } #[test] fn test_extension_validator_present() { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, ); // Test a policy that stipulates that a given extension MUST be present. let extension_validator = ExtensionValidator::present(Criticality::Critical, Some(present_extension_validator)); // Check the case where the extension is present. let bc = BasicConstraints { ca: true, path_length: Some(3), }; let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, true, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator .permits(&policy, &cert, Some(&raw_ext)) .is_ok()); // Check the case where the extension isn't present. assert!(extension_validator.permits(&policy, &cert, None).is_err()); } fn maybe_extension_validator( _policy: &Policy<'_, B>, _cert: &Certificate<'_>, _ext: Option<&Extension<'_>>, ) -> Result<(), ValidationError> { Ok(()) } #[test] fn test_extension_validator_maybe() { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, ); // Test a validator that stipulates that a given extension CAN be present. let extension_validator = ExtensionValidator::maybe_present( Criticality::Critical, Some(maybe_extension_validator), ); // Check the case where the extension is present. let bc = BasicConstraints { ca: false, path_length: Some(3), }; let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, true, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator .permits(&policy, &cert, Some(&raw_ext)) .is_ok()); // Check the case where the extension isn't present. assert!(extension_validator.permits(&policy, &cert, None).is_ok()); } #[test] fn test_extension_validator_not_present() { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, ); // Test a validator that stipulates that a given extension MUST NOT be present. let extension_validator = ExtensionValidator::not_present(); // Check the case where the extension is present. let bc = BasicConstraints { ca: false, path_length: Some(3), }; let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, true, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator .permits(&policy, &cert, Some(&raw_ext)) .is_err()); // Check the case where the extension isn't present. assert!(extension_validator.permits(&policy, &cert, None).is_ok()); } #[test] fn test_extension_validator_present_incorrect_criticality() { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, ); // Test a present policy that stipulates that a given extension MUST be critical. let extension_validator = ExtensionValidator::present(Criticality::Critical, Some(present_extension_validator)); // Mark the extension as non-critical despite our policy stipulating that it must be critical. let bc = BasicConstraints { ca: true, path_length: Some(3), }; let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, false, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator .permits(&policy, &cert, Some(&raw_ext)) .is_err()); } #[test] fn test_extension_validator_maybe_present_incorrect_criticality() { // The certificate doesn't get used for this validator, so the certificate we use isn't important. let cert_pem = v1_cert_pem(); let cert = cert(&cert_pem); let ops = PublicKeyErrorOps {}; let policy = Policy::server( ops, Subject::DNS(DNSName::new("example.com").unwrap()), epoch(), None, ); // Test a maybe present validator that stipulates that a given extension MUST be critical. let extension_validator = ExtensionValidator::maybe_present( Criticality::Critical, Some(maybe_extension_validator), ); // Mark the extension as non-critical despite our policy stipulating that it must be critical. let bc = BasicConstraints { ca: true, path_length: Some(3), }; let der_ext = create_encoded_extension(BASIC_CONSTRAINTS_OID, false, &bc); let raw_ext = asn1::parse_single(&der_ext).unwrap(); assert!(extension_validator .permits(&policy, &cert, Some(&raw_ext)) .is_err()); } } cryptography-43.0.0/src/rust/cryptography-x509-verification/src/policy/mod.rs010064400017510000177000001016601464676315000254520ustar 00000000000000// 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. mod extension; use std::collections::HashSet; use std::ops::Range; use std::sync::Arc; use asn1::ObjectIdentifier; use cryptography_key_parsing::rsa::Pkcs1RsaPublicKey; use cryptography_x509::certificate::Certificate; use cryptography_x509::common::{ AlgorithmIdentifier, AlgorithmParameters, EcParameters, RsaPssParameters, Time, PSS_SHA256_HASH_ALG, PSS_SHA256_MASK_GEN_ALG, PSS_SHA384_HASH_ALG, PSS_SHA384_MASK_GEN_ALG, PSS_SHA512_HASH_ALG, PSS_SHA512_MASK_GEN_ALG, }; use cryptography_x509::extensions::{BasicConstraints, Extensions, SubjectAlternativeName}; use cryptography_x509::name::GeneralName; use cryptography_x509::oid::{ BASIC_CONSTRAINTS_OID, EC_SECP256R1, EC_SECP384R1, EC_SECP521R1, EKU_CLIENT_AUTH_OID, EKU_SERVER_AUTH_OID, }; use once_cell::sync::Lazy; use crate::ops::CryptoOps; use crate::policy::extension::{ca, common, ee, Criticality, ExtensionPolicy, ExtensionValidator}; use crate::types::{DNSName, DNSPattern, IPAddress}; use crate::{ValidationError, VerificationCertificate}; // RSA key constraints, as defined in CA/B 6.1.5. static WEBPKI_MINIMUM_RSA_MODULUS: usize = 2048; // SubjectPublicKeyInfo AlgorithmIdentifier constants, as defined in CA/B 7.1.3.1. // RSA static SPKI_RSA: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Rsa(Some(())), }; // SECP256R1 static SPKI_SECP256R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP256R1)), }; // SECP384R1 static SPKI_SECP384R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP384R1)), }; // SECP521R1 static SPKI_SECP521R1: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Ec(EcParameters::NamedCurve(EC_SECP521R1)), }; /// Permitted algorithms, from CA/B Forum's Baseline Requirements, section 7.1.3.1 (page 96) /// https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.0.pdf pub static WEBPKI_PERMITTED_SPKI_ALGORITHMS: Lazy>>> = Lazy::new(|| { Arc::new(HashSet::from([ SPKI_RSA.clone(), SPKI_SECP256R1.clone(), SPKI_SECP384R1.clone(), SPKI_SECP521R1.clone(), ])) }); // Signature AlgorithmIdentifier constants, as defined in CA/B 7.1.3.2. // RSASSA‐PKCS1‐v1_5 with SHA‐256 static RSASSA_PKCS1V15_SHA256: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaWithSha256(Some(())), }; // RSASSA‐PKCS1‐v1_5 with SHA‐384 static RSASSA_PKCS1V15_SHA384: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaWithSha384(Some(())), }; // RSASSA‐PKCS1‐v1_5 with SHA‐512 static RSASSA_PKCS1V15_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaWithSha512(Some(())), }; // RSASSA‐PSS with SHA‐256, MGF‐1 with SHA‐256, and a salt length of 32 bytes static RSASSA_PSS_SHA256: Lazy> = Lazy::new(|| AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaPss(Some(Box::new(RsaPssParameters { hash_algorithm: PSS_SHA256_HASH_ALG, mask_gen_algorithm: PSS_SHA256_MASK_GEN_ALG, salt_length: 32, _trailer_field: None, }))), }); // RSASSA‐PSS with SHA‐384, MGF‐1 with SHA‐384, and a salt length of 48 bytes static RSASSA_PSS_SHA384: Lazy> = Lazy::new(|| AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaPss(Some(Box::new(RsaPssParameters { hash_algorithm: PSS_SHA384_HASH_ALG, mask_gen_algorithm: PSS_SHA384_MASK_GEN_ALG, salt_length: 48, _trailer_field: None, }))), }); // RSASSA‐PSS with SHA‐512, MGF‐1 with SHA‐512, and a salt length of 64 bytes static RSASSA_PSS_SHA512: Lazy> = Lazy::new(|| AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::RsaPss(Some(Box::new(RsaPssParameters { hash_algorithm: PSS_SHA512_HASH_ALG, mask_gen_algorithm: PSS_SHA512_MASK_GEN_ALG, salt_length: 64, _trailer_field: None, }))), }); // For P-256: the signature MUST use ECDSA with SHA‐256 static ECDSA_SHA256: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::EcDsaWithSha256(None), }; // For P-384: the signature MUST use ECDSA with SHA‐384 static ECDSA_SHA384: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::EcDsaWithSha384(None), }; // For P-521: the signature MUST use ECDSA with SHA‐512 static ECDSA_SHA512: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::EcDsaWithSha512(None), }; /// Permitted algorithms, from CA/B Forum's Baseline Requirements, section 7.1.3.2 (pages 96-98) /// https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.0.pdf pub static WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS: Lazy>>> = Lazy::new(|| { Arc::new(HashSet::from([ RSASSA_PKCS1V15_SHA256.clone(), RSASSA_PKCS1V15_SHA384.clone(), RSASSA_PKCS1V15_SHA512.clone(), RSASSA_PSS_SHA256.clone(), RSASSA_PSS_SHA384.clone(), RSASSA_PSS_SHA512.clone(), ECDSA_SHA256.clone(), ECDSA_SHA384.clone(), ECDSA_SHA512.clone(), ])) }); /// A default reasonable maximum chain depth. /// /// This depth was chosen to balance between common validation lengths /// (chains in the Web PKI are ordinarily no longer than 2 or 3 intermediates /// in the longest cases) and support for pathological cases. /// /// Relatively little prior art for selecting a default depth exists; /// OpenSSL defaults to a limit of 100, which is far more permissive than /// necessary. const DEFAULT_MAX_CHAIN_DEPTH: u8 = 8; /// Represents a logical certificate "subject," i.e. a principal matching /// one of the names listed in a certificate's `subjectAltNames` extension. pub enum Subject<'a> { DNS(DNSName<'a>), IP(IPAddress), } impl Subject<'_> { fn subject_alt_name_matches(&self, general_name: &GeneralName<'_>) -> bool { match (general_name, self) { (GeneralName::DNSName(pattern), Self::DNS(name)) => { DNSPattern::new(pattern.0).map_or(false, |p| p.matches(name)) } (GeneralName::IPAddress(addr), Self::IP(name)) => { IPAddress::from_bytes(addr).map_or(false, |addr| addr == *name) } _ => false, } } /// Returns true if any of the names in the given `SubjectAlternativeName` /// match this `Subject`. pub fn matches(&self, san: &SubjectAlternativeName<'_>) -> bool { san.clone().any(|gn| self.subject_alt_name_matches(&gn)) } } /// A `Policy` describes user-configurable aspects of X.509 path validation. pub struct Policy<'a, B: CryptoOps> { pub ops: B, /// A top-level constraint on the length of intermediate CA paths /// constructed under this policy. /// /// Per RFC 5280, this limits the length of the non-self-issued intermediate /// CA chain, without counting either the leaf or trust anchor. pub max_chain_depth: u8, /// A subject (i.e. DNS name or other name format) that any EE certificates /// validated by this policy must match. pub subject: Option>, /// The validation time. All certificates validated by this policy must /// be valid at this time. pub validation_time: asn1::DateTime, /// An extended key usage that must appear in EEs validated by this policy. pub extended_key_usage: ObjectIdentifier, /// The minimum RSA modulus, in bits. /// This is equivalent to the public key size, e.g. 2048 for an RSA-2048 key. pub minimum_rsa_modulus: usize, /// The set of permitted public key algorithms, identified by their /// algorithm identifiers. pub permitted_public_key_algorithms: Arc>>, /// The set of permitted signature algorithms, identified by their /// algorithm identifiers. pub permitted_signature_algorithms: Arc>>, ca_extension_policy: ExtensionPolicy, ee_extension_policy: ExtensionPolicy, } impl<'a, B: CryptoOps> Policy<'a, B> { fn new( ops: B, subject: Option>, time: asn1::DateTime, max_chain_depth: Option, extended_key_usage: ObjectIdentifier, ) -> Self { Self { ops, max_chain_depth: max_chain_depth.unwrap_or(DEFAULT_MAX_CHAIN_DEPTH), subject, validation_time: time, extended_key_usage, minimum_rsa_modulus: WEBPKI_MINIMUM_RSA_MODULUS, permitted_public_key_algorithms: Arc::clone(&*WEBPKI_PERMITTED_SPKI_ALGORITHMS), permitted_signature_algorithms: Arc::clone(&*WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS), ca_extension_policy: ExtensionPolicy { // 5280 4.2.2.1: Authority Information Access authority_information_access: ExtensionValidator::maybe_present( Criticality::NonCritical, Some(common::authority_information_access), ), // 5280 4.2.1.1: Authority Key Identifier authority_key_identifier: ExtensionValidator::maybe_present( Criticality::NonCritical, Some(ca::authority_key_identifier), ), // 5280 4.2.1.2: Subject Key Identifier // NOTE: CABF requires SKI in CA certificates, but many older CAs lack it. // We choose to be permissive here. subject_key_identifier: ExtensionValidator::maybe_present( Criticality::NonCritical, None, ), // 5280 4.2.1.3: Key Usage key_usage: ExtensionValidator::present(Criticality::Agnostic, Some(ca::key_usage)), subject_alternative_name: ExtensionValidator::maybe_present( Criticality::Agnostic, None, ), // 5280 4.2.1.9: Basic Constraints basic_constraints: ExtensionValidator::present( Criticality::Critical, Some(ca::basic_constraints), ), // 5280 4.2.1.10: Name Constraints // NOTE: MUST be critical in 5280, but CABF relaxes to MAY. name_constraints: ExtensionValidator::maybe_present( Criticality::Agnostic, Some(ca::name_constraints), ), // 5280: 4.2.1.12: Extended Key Usage // NOTE: CABF requires EKUs in many non-root CA certs, but validators widely // ignore this requirement and treat a missing EKU as "any EKU". // We choose to be permissive here. extended_key_usage: ExtensionValidator::maybe_present( Criticality::NonCritical, Some(ca::extended_key_usage), ), }, ee_extension_policy: ExtensionPolicy { // 5280 4.2.2.1: Authority Information Access authority_information_access: ExtensionValidator::maybe_present( Criticality::NonCritical, Some(common::authority_information_access), ), // 5280 4.2.1.1.: Authority Key Identifier authority_key_identifier: ExtensionValidator::present( Criticality::NonCritical, None, ), subject_key_identifier: ExtensionValidator::maybe_present( Criticality::Agnostic, None, ), // 5280 4.2.1.3: Key Usage key_usage: ExtensionValidator::maybe_present( Criticality::Agnostic, Some(ee::key_usage), ), // CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name // This validator handles both client and server cases by only matching against // the SAN if the profile contains a subject, which it won't in the client // validation case. subject_alternative_name: ExtensionValidator::present( Criticality::Agnostic, Some(ee::subject_alternative_name), ), // 5280 4.2.1.9: Basic Constraints basic_constraints: ExtensionValidator::maybe_present( Criticality::Agnostic, Some(ee::basic_constraints), ), // 5280 4.2.1.10: Name Constraints name_constraints: ExtensionValidator::not_present(), // CA/B: 7.1.2.7.10: Subscriber Certificate Extended Key Usage // NOTE: CABF requires EKUs in EE certs, while RFC 5280 does not. extended_key_usage: ExtensionValidator::maybe_present( Criticality::NonCritical, Some(ee::extended_key_usage), ), }, } } /// Create a new policy with suitable defaults for client certification /// validation. /// /// **IMPORTANT**: This is **not** the appropriate API for verifying /// website (i.e. server) certificates. For that, you **must** use /// [`Policy::server`]. pub fn client(ops: B, time: asn1::DateTime, max_chain_depth: Option) -> Self { Self::new( ops, None, time, max_chain_depth, EKU_CLIENT_AUTH_OID.clone(), ) } /// Create a new policy with defaults for the server certificate profile /// defined in the CA/B Forum's Basic Requirements. pub fn server( ops: B, subject: Subject<'a>, time: asn1::DateTime, max_chain_depth: Option, ) -> Self { Self::new( ops, Some(subject), time, max_chain_depth, EKU_SERVER_AUTH_OID.clone(), ) } fn permits_basic(&self, cert: &Certificate<'_>) -> Result<(), ValidationError> { // CA/B 7.1.1: // Certificates MUST be of type X.509 v3. if cert.tbs_cert.version != 2 { return Err(ValidationError::Other( "certificate must be an X509v3 certificate".to_string(), )); } // 5280 4.1.1.2 / 4.1.2.3: signatureAlgorithm / TBS Certificate Signature // The top-level signatureAlgorithm and TBSCert signature algorithm // MUST match. if cert.signature_alg != cert.tbs_cert.signature_alg { return Err(ValidationError::Other( "mismatch between signatureAlgorithm and SPKI algorithm".to_string(), )); } // 5280 4.1.2.2: Serial Number // Per 5280: The serial number MUST be a positive integer. // In practice, there are a few roots in common trust stores (like certifi) // that have `serial == 0`, so we can't enforce this yet. let serial = cert.tbs_cert.serial; if !(1..=21).contains(&serial.as_bytes().len()) { // Conforming CAs MUST NOT use serial numbers longer than 20 octets. // NOTE: In practice, this requires us to check for an encoding of // 21 octets, since some CAs generate 20 bytes of randomness and // then forget to check whether that number would be negative, resulting // in a 21-byte encoding. return Err(ValidationError::Other( "certificate must have a serial between 1 and 20 octets".to_string(), )); } else if serial.is_negative() { return Err(ValidationError::Other( "certificate serial number cannot be negative".to_string(), )); } // 5280 4.1.2.4: Issuer // The issuer MUST be a non-empty distinguished name. if cert.issuer().is_empty() { return Err(ValidationError::Other( "certificate must have a non-empty Issuer".to_string(), )); } // 5280 4.1.2.5: Validity // Validity dates before 2050 MUST be encoded as UTCTime; // dates in or after 2050 MUST be encoded as GeneralizedTime. let not_before = cert.tbs_cert.validity.not_before.as_datetime(); let not_after = cert.tbs_cert.validity.not_after.as_datetime(); permits_validity_date(&cert.tbs_cert.validity.not_before)?; permits_validity_date(&cert.tbs_cert.validity.not_after)?; if &self.validation_time < not_before || &self.validation_time > not_after { return Err(ValidationError::Other( "cert is not valid at validation time".to_string(), )); } Ok(()) } /// Checks whether the given CA certificate is compatible with this policy. pub(crate) fn permits_ca( &self, cert: &Certificate<'_>, current_depth: u8, extensions: &Extensions<'_>, ) -> Result<(), ValidationError> { self.permits_basic(cert)?; // 5280 4.1.2.6: Subject // CA certificates MUST have a subject populated with a non-empty distinguished name. // No check required here: `permits_basic` checks that the issuer is non-empty // and `ChainBuilder::potential_issuers` enforces subject/issuer matching, // meaning that an CA with an empty subject cannot occur in a built chain. // NOTE: This conceptually belongs in `valid_issuer`, but is easier // to test here. It's also conceptually an extension policy, but // requires a bit of extra external state (`current_depth`) that isn't // presently convenient to push into that layer. // // NOTE: BasicConstraints is required via `ca_extension_policies`, // so we always take this branch. if let Some(bc) = extensions.get_extension(&BASIC_CONSTRAINTS_OID) { let bc: BasicConstraints = bc.value()?; if bc .path_length .map_or(false, |len| u64::from(current_depth) > len) { return Err(ValidationError::Other( "path length constraint violated".to_string(), ))?; } } self.ca_extension_policy.permits(self, cert, extensions)?; Ok(()) } /// Checks whether the given EE certificate is compatible with this policy. pub(crate) fn permits_ee( &self, cert: &Certificate<'_>, extensions: &Extensions<'_>, ) -> Result<(), ValidationError> { self.permits_basic(cert)?; self.ee_extension_policy.permits(self, cert, extensions)?; Ok(()) } /// Checks whether `issuer` is a valid issuing CA for `child` at a /// path-building depth of `current_depth`. /// /// This checks that `issuer` is permitted under this policy and that /// it was used to sign for `child`. /// /// As a precondition, the caller must have already checked that /// `issuer.subject() == child.issuer()`. /// /// On success, this function returns the new path-building depth. This /// may or may not be a higher number than the original depth, depending /// on the kind of validation performed (e.g., whether the issuer was /// self-issued). pub(crate) fn valid_issuer( &self, issuer: &VerificationCertificate<'_, B>, child: &Certificate<'_>, current_depth: u8, issuer_extensions: &Extensions<'_>, ) -> Result<(), ValidationError> { // The issuer needs to be a valid CA at the current depth. self.permits_ca(issuer.certificate(), current_depth, issuer_extensions)?; // CA/B 7.1.3.1 SubjectPublicKeyInfo // NOTE: We check the issuer's SPKI here, since the issuer is // definitionally a CA and thus subject to CABF key requirements. if !self .permitted_public_key_algorithms .contains(&issuer.certificate().tbs_cert.spki.algorithm) { return Err(ValidationError::Other(format!( "Forbidden public key algorithm: {:?}", &child.tbs_cert.spki.algorithm ))); } // CA/B 7.1.3.2 Signature AlgorithmIdentifier // NOTE: We check the child's signature here, since the issuer's // signature is not necessarily subject to signature checks (e.g. // if it's a root). This works out transitively, as any non root-issuer // will be checked in its recursive step (where it'll be in the child // position). if !self .permitted_signature_algorithms .contains(&child.signature_alg) { return Err(ValidationError::Other(format!( "Forbidden signature algorithm: {:?}", &child.signature_alg ))); } // CA/B 6.1.5: Key sizes // NOTE: We don't currently enforce that RSA moduli are divisible by 8, // since other implementations don't bother. let issuer_spki = &issuer.certificate().tbs_cert.spki; if matches!( issuer_spki.algorithm.params, AlgorithmParameters::Rsa(_) | AlgorithmParameters::RsaPss(_) ) { let rsa_key: Pkcs1RsaPublicKey<'_> = asn1::parse_single(issuer_spki.subject_public_key.as_bytes())?; if rsa_key.n.as_bytes().len() * 8 < self.minimum_rsa_modulus { return Err(ValidationError::Other("RSA key is too weak".into())); } } let pk = issuer .public_key(&self.ops) .map_err(|_| ValidationError::Other("issuer has malformed public key".to_string()))?; if self.ops.verify_signed_by(child, pk).is_err() { return Err(ValidationError::Other( "signature does not match".to_string(), )); } Ok(()) } } fn permits_validity_date(validity_date: &Time) -> Result<(), ValidationError> { const GENERALIZED_DATE_INVALIDITY_RANGE: Range = 1950..2050; // NOTE: The inverse check on `asn1::UtcTime` is already done for us // by the variant's constructor. if let Time::GeneralizedTime(_) = validity_date { if GENERALIZED_DATE_INVALIDITY_RANGE.contains(&validity_date.as_datetime().year()) { return Err(ValidationError::Other( "validity dates between 1950 and 2049 must be UtcTime".to_string(), )); } } Ok(()) } #[cfg(test)] mod tests { use std::ops::Deref; use asn1::{DateTime, SequenceOfWriter}; use cryptography_x509::common::Time; use cryptography_x509::{ extensions::SubjectAlternativeName, name::{GeneralName, UnvalidatedIA5String}, }; use super::{ permits_validity_date, ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512, RSASSA_PKCS1V15_SHA256, RSASSA_PKCS1V15_SHA384, RSASSA_PKCS1V15_SHA512, RSASSA_PSS_SHA256, RSASSA_PSS_SHA384, RSASSA_PSS_SHA512, WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS, }; use crate::{ policy::{ Subject, SPKI_RSA, SPKI_SECP256R1, SPKI_SECP384R1, SPKI_SECP521R1, WEBPKI_PERMITTED_SPKI_ALGORITHMS, }, types::{DNSName, IPAddress}, }; #[test] fn test_webpki_permitted_spki_algorithms_canonical_encodings() { { assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_RSA)); let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00"; assert_eq!(asn1::write_single(&SPKI_RSA).unwrap(), exp_encoding); } { assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_SECP256R1)); let exp_encoding = b"0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07"; assert_eq!(asn1::write_single(&SPKI_SECP256R1).unwrap(), exp_encoding); } { assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_SECP384R1)); let exp_encoding = b"0\x10\x06\x07*\x86H\xce=\x02\x01\x06\x05+\x81\x04\x00\""; assert_eq!(asn1::write_single(&SPKI_SECP384R1).unwrap(), exp_encoding); } { assert!(WEBPKI_PERMITTED_SPKI_ALGORITHMS.contains(&SPKI_SECP521R1)); let exp_encoding = b"0\x10\x06\x07*\x86H\xce=\x02\x01\x06\x05+\x81\x04\x00#"; assert_eq!(asn1::write_single(&SPKI_SECP521R1).unwrap(), exp_encoding); } } #[test] fn test_webpki_permitted_signature_algorithms_canonical_encodings() { { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PKCS1V15_SHA256)); let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00"; assert_eq!( asn1::write_single(&RSASSA_PKCS1V15_SHA256).unwrap(), exp_encoding ); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PKCS1V15_SHA384)); let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0c\x05\x00"; assert_eq!( asn1::write_single(&RSASSA_PKCS1V15_SHA384).unwrap(), exp_encoding ); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PKCS1V15_SHA512)); let exp_encoding = b"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\r\x05\x00"; assert_eq!( asn1::write_single(&RSASSA_PKCS1V15_SHA512).unwrap(), exp_encoding ); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PSS_SHA256.deref())); let exp_encoding = b"0A\x06\t*\x86H\x86\xf7\r\x01\x01\n04\xa0\x0f0\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\xa1\x1c0\x1a\x06\t*\x86H\x86\xf7\r\x01\x01\x080\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\xa2\x03\x02\x01 "; assert_eq!( asn1::write_single(&RSASSA_PSS_SHA256.deref()).unwrap(), exp_encoding ); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PSS_SHA384.deref())); let exp_encoding = b"0A\x06\t*\x86H\x86\xf7\r\x01\x01\n04\xa0\x0f0\r\x06\t`\x86H\x01e\x03\x04\x02\x02\x05\x00\xa1\x1c0\x1a\x06\t*\x86H\x86\xf7\r\x01\x01\x080\r\x06\t`\x86H\x01e\x03\x04\x02\x02\x05\x00\xa2\x03\x02\x010"; assert_eq!( asn1::write_single(&RSASSA_PSS_SHA384.deref()).unwrap(), exp_encoding ); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&RSASSA_PSS_SHA512.deref())); let exp_encoding = b"0A\x06\t*\x86H\x86\xf7\r\x01\x01\n04\xa0\x0f0\r\x06\t`\x86H\x01e\x03\x04\x02\x03\x05\x00\xa1\x1c0\x1a\x06\t*\x86H\x86\xf7\r\x01\x01\x080\r\x06\t`\x86H\x01e\x03\x04\x02\x03\x05\x00\xa2\x03\x02\x01@"; assert_eq!( asn1::write_single(&RSASSA_PSS_SHA512.deref()).unwrap(), exp_encoding ); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&ECDSA_SHA256)); let exp_encoding = b"0\n\x06\x08*\x86H\xce=\x04\x03\x02"; assert_eq!(asn1::write_single(&ECDSA_SHA256).unwrap(), exp_encoding); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&ECDSA_SHA384)); let exp_encoding = b"0\n\x06\x08*\x86H\xce=\x04\x03\x03"; assert_eq!(asn1::write_single(&ECDSA_SHA384).unwrap(), exp_encoding); } { assert!(WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS.contains(&ECDSA_SHA512)); let exp_encoding = b"0\n\x06\x08*\x86H\xce=\x04\x03\x04"; assert_eq!(asn1::write_single(&ECDSA_SHA512).unwrap(), exp_encoding); } } #[test] fn test_subject_matches() { let domain_sub = Subject::DNS(DNSName::new("test.cryptography.io").unwrap()); let ip_sub = Subject::IP(IPAddress::from_str("127.0.0.1").unwrap()); // Single SAN, domain wildcard. { let domain_gn = GeneralName::DNSName(UnvalidatedIA5String("*.cryptography.io")); let san_der = asn1::write_single(&SequenceOfWriter::new([domain_gn])).unwrap(); let any_cryptography_io = asn1::parse_single::>(&san_der).unwrap(); assert!(domain_sub.matches(&any_cryptography_io)); assert!(!ip_sub.matches(&any_cryptography_io)); } // Single SAN, IP address. { let ip_gn = GeneralName::IPAddress(&[127, 0, 0, 1]); let san_der = asn1::write_single(&SequenceOfWriter::new([ip_gn])).unwrap(); let localhost = asn1::parse_single::>(&san_der).unwrap(); assert!(ip_sub.matches(&localhost)); assert!(!domain_sub.matches(&localhost)); } // Multiple SANs, both domain wildcard and IP address. { let domain_gn = GeneralName::DNSName(UnvalidatedIA5String("*.cryptography.io")); let ip_gn = GeneralName::IPAddress(&[127, 0, 0, 1]); let san_der = asn1::write_single(&SequenceOfWriter::new([domain_gn, ip_gn])).unwrap(); let any_cryptography_io_or_localhost = asn1::parse_single::>(&san_der).unwrap(); assert!(domain_sub.matches(&any_cryptography_io_or_localhost)); assert!(ip_sub.matches(&any_cryptography_io_or_localhost)); } // Single SAN, invalid domain pattern. { let domain_gn = GeneralName::DNSName(UnvalidatedIA5String("*es*.cryptography.io")); let san_der = asn1::write_single(&SequenceOfWriter::new([domain_gn])).unwrap(); let any_cryptography_io = asn1::parse_single::>(&san_der).unwrap(); assert!(!domain_sub.matches(&any_cryptography_io)); } } #[test] fn test_validity_date() { { // Pre-2050 date. let utc_dt = DateTime::new(1980, 1, 1, 0, 0, 0).unwrap(); let generalized_dt = utc_dt.clone(); let utc_validity = Time::UtcTime(asn1::UtcTime::new(utc_dt).unwrap()); let generalized_validity = Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); assert!(permits_validity_date(&utc_validity).is_ok()); assert!(permits_validity_date(&generalized_validity).is_err()); } { // 2049 date. let utc_dt = DateTime::new(2049, 1, 1, 0, 0, 0).unwrap(); let generalized_dt = utc_dt.clone(); let utc_validity = Time::UtcTime(asn1::UtcTime::new(utc_dt).unwrap()); let generalized_validity = Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); assert!(permits_validity_date(&utc_validity).is_ok()); assert!(permits_validity_date(&generalized_validity).is_err()); } { // 2050 date. let utc_dt = DateTime::new(2050, 1, 1, 0, 0, 0).unwrap(); let generalized_dt = utc_dt.clone(); assert!(asn1::UtcTime::new(utc_dt).is_err()); let generalized_validity = Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); assert!(permits_validity_date(&generalized_validity).is_ok()); } { // 2051 date. let utc_dt = DateTime::new(2051, 1, 1, 0, 0, 0).unwrap(); let generalized_dt = utc_dt.clone(); // The `asn1::UtcTime` constructor prevents this. assert!(asn1::UtcTime::new(utc_dt).is_err()); let generalized_validity = Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); assert!(permits_validity_date(&generalized_validity).is_ok()); } { // Post-2050 date. let utc_dt = DateTime::new(3050, 1, 1, 0, 0, 0).unwrap(); let generalized_dt = utc_dt.clone(); // The `asn1::UtcTime` constructor prevents this. assert!(asn1::UtcTime::new(utc_dt).is_err()); let generalized_validity = Time::GeneralizedTime(asn1::GeneralizedTime::new(generalized_dt).unwrap()); assert!(permits_validity_date(&generalized_validity).is_ok()); } } } cryptography-43.0.0/src/rust/cryptography-x509-verification/src/trust_store.rs010064400017510000177000000037321464676315000257720ustar 00000000000000// 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. use std::collections::HashMap; use cryptography_x509::name::Name; use crate::CryptoOps; use crate::VerificationCertificate; /// A `Store` represents the core state needed for X.509 path validation. pub struct Store<'a, B: CryptoOps> { by_subject: HashMap, Vec>>, } impl<'a, B: CryptoOps> Store<'a, B> { /// Create a new `Store` from the given iterable certificate source. pub fn new(trusted: impl IntoIterator>) -> Self { let mut by_subject: HashMap, Vec>> = HashMap::new(); for cert in trusted { by_subject .entry(cert.certificate().tbs_cert.subject.clone()) .or_default() .push(cert); } Store { by_subject } } /// Returns whether this store contains the given certificate. pub fn contains(&self, cert: &VerificationCertificate<'a, B>) -> bool { self.get_by_subject(&cert.certificate().tbs_cert.subject) .contains(cert) } pub fn get_by_subject(&self, subject: &Name<'a>) -> &[VerificationCertificate<'a, B>] { self.by_subject .get(subject) .map(|v| v.as_slice()) .unwrap_or_default() } } #[cfg(test)] mod tests { use super::Store; use crate::certificate::tests::PublicKeyErrorOps; use crate::ops::tests::{cert, v1_cert_pem}; use crate::VerificationCertificate; #[test] fn test_store() { let cert_pem = v1_cert_pem(); let cert1 = VerificationCertificate::new(cert(&cert_pem), ()); let cert2 = VerificationCertificate::new(cert(&cert_pem), ()); let store = Store::<'_, PublicKeyErrorOps>::new([cert1]); assert!(store.contains(&cert2)); } } cryptography-43.0.0/src/rust/cryptography-x509-verification/src/types.rs010064400017510000177000001035551464676315000245450ustar 00000000000000// 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. use std::net::IpAddr; use std::str::FromStr; use asn1::IA5String; // RFC 2822 3.2.4 static ATEXT_CHARS: &str = "!#$%&'*+-/=?^_`{|}~"; /// A `DNSName` is an `asn1::IA5String` with additional invariant preservations /// per [RFC 5280 4.2.1.6], which in turn uses the preferred name syntax defined /// in [RFC 1034 3.5] and amended in [RFC 1123 2.1]. /// /// Non-ASCII domain names (i.e., internationalized names) must be pre-encoded; /// comparisons are case-insensitive. /// /// [RFC 5280 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 /// [RFC 1034 3.5]: https://datatracker.ietf.org/doc/html/rfc1034#section-3.5 /// [RFC 1123 2.1]: https://datatracker.ietf.org/doc/html/rfc1123#section-2.1 /// /// ```rust /// # use cryptography_x509_verification::types::DNSName; /// assert_eq!(DNSName::new("foo.com").unwrap(), DNSName::new("FOO.com").unwrap()); /// ``` #[derive(Clone, Debug)] pub struct DNSName<'a>(asn1::IA5String<'a>); impl<'a> DNSName<'a> { pub fn new(value: &'a str) -> Option { // Domains cannot be empty and must (practically) // be less than 253 characters (255 in RFC 1034's octet encoding). if value.is_empty() || value.len() > 253 { None } else { for label in value.split('.') { // Individual labels cannot be empty; cannot exceed 63 characters; // cannot start or end with `-`. // NOTE: RFC 1034's grammar prohibits consecutive hyphens, but these // are used as part of the IDN prefix (e.g. `xn--`)'; we allow them here. if label.is_empty() || label.len() > 63 || label.starts_with('-') || label.ends_with('-') { return None; } // Labels must only contain `a-zA-Z0-9-`. if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { return None; } } asn1::IA5String::new(value).map(Self) } } pub fn as_str(&self) -> &'a str { self.0.as_str() } /// Return this `DNSName`'s parent domain, if it has one. /// /// ```rust /// # use cryptography_x509_verification::types::DNSName; /// let domain = DNSName::new("foo.example.com").unwrap(); /// assert_eq!(domain.parent().unwrap().as_str(), "example.com"); /// ``` pub fn parent(&self) -> Option { match self.as_str().split_once('.') { Some((_, parent)) => Self::new(parent), None => None, } } /// Returns this DNS name's labels, in reversed order /// (from top-level domain to most-specific subdomain). fn rlabels(&self) -> impl Iterator { self.as_str().rsplit('.') } /// Returns true if this domain is a subdomain of the other domain. fn is_subdomain_of(&self, other: &DNSName<'_>) -> bool { // NOTE: This is nearly identical to `DNSConstraint::matches`, // except that the subdomain must be strictly longer than the parent domain. self.as_str().len() > other.as_str().len() && self .rlabels() .zip(other.rlabels()) .all(|(a, o)| a.eq_ignore_ascii_case(o)) } } impl PartialEq for DNSName<'_> { fn eq(&self, other: &Self) -> bool { // DNS names are always case-insensitive. self.as_str().eq_ignore_ascii_case(other.as_str()) } } /// A `DNSPattern` represents a subset of the domain name wildcard matching /// behavior defined in [RFC 6125 6.4.3]. In particular, all DNS patterns /// must either be exact matches (post-normalization) *or* a single wildcard /// matching a full label in the left-most label position. Partial label matching /// (e.g. `f*o.example.com`) is not supported, nor is non-left-most matching /// (e.g. `foo.*.example.com`). /// /// [RFC 6125 6.4.3]: https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3 #[derive(Debug, PartialEq)] pub enum DNSPattern<'a> { Exact(DNSName<'a>), Wildcard(DNSName<'a>), } impl<'a> DNSPattern<'a> { pub fn new(pat: &'a str) -> Option { if let Some(pat) = pat.strip_prefix("*.") { DNSName::new(pat).map(Self::Wildcard) } else { DNSName::new(pat).map(Self::Exact) } } pub fn matches(&self, name: &DNSName<'_>) -> bool { match self { Self::Exact(pat) => pat == name, Self::Wildcard(pat) => match name.parent() { Some(ref parent) => pat == parent, // No parent means we have a single label; wildcards cannot match single labels. None => false, }, } } } /// A `DNSConstraint` represents a DNS name constraint as defined in [RFC 5280 4.2.1.10]. /// /// [RFC 5280 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 pub struct DNSConstraint<'a>(DNSName<'a>); impl<'a> DNSConstraint<'a> { pub fn new(pattern: &'a str) -> Option { DNSName::new(pattern).map(Self) } /// Returns true if this `DNSConstraint` matches the given name. /// /// Constraint matching is defined by RFC 5280: any DNS name that can /// be constructed by simply adding zero or more labels to the left-hand /// side of the name satisfies the name constraint. /// /// ```rust /// # use cryptography_x509_verification::types::{DNSConstraint, DNSName}; /// let example_com = DNSName::new("example.com").unwrap(); /// let badexample_com = DNSName::new("badexample.com").unwrap(); /// let foo_example_com = DNSName::new("foo.example.com").unwrap(); /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&example_com)); /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&foo_example_com)); /// assert!(!DNSConstraint::new(example_com.as_str()).unwrap().matches(&badexample_com)); /// ``` pub fn matches(&self, name: &DNSName<'_>) -> bool { // NOTE: This may seem like an obtuse way to perform label matching, // but it saves us a few allocations: doing a substring check instead // would require us to clone each string and do case normalization. // Note also that we check the length in advance: Rust's zip // implementation terminates with the shorter iterator, so we need // to first check that the candidate name is at least as long as // the constraint it's matching against. name.as_str().len() >= self.0.as_str().len() && self .0 .rlabels() .zip(name.rlabels()) .all(|(a, o)| a.eq_ignore_ascii_case(o)) } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct IPAddress(IpAddr); /// An `IPAddress` represents an IP address as defined in [RFC 5280 4.2.1.6]. /// /// [RFC 5280 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 impl IPAddress { #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Option { IpAddr::from_str(s).ok().map(Self::from) } /// Constructs an `IPAddress` from a slice. The provided data must be /// 4 (IPv4) or 16 (IPv6) bytes in "network byte order", as specified by /// [RFC 5280]. /// /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 pub fn from_bytes(b: &[u8]) -> Option { match b.len() { 4 => { let b: [u8; 4] = b.try_into().ok()?; Some(IpAddr::from(b).into()) } 16 => { let b: [u8; 16] = b.try_into().ok()?; Some(IpAddr::from(b).into()) } _ => None, } } /// Parses the octets of the `IPAddress` as a mask. If it is well-formed, /// i.e., has only one contiguous block of set bits starting from the most /// significant bit, a prefix is returned. pub fn as_prefix(&self) -> Option { let (leading, total) = match self.0 { IpAddr::V4(a) => { let data = u32::from_be_bytes(a.octets()); (data.leading_ones(), data.count_ones()) } IpAddr::V6(a) => { let data = u128::from_be_bytes(a.octets()); (data.leading_ones(), data.count_ones()) } }; if leading != total { None } else { Some(leading as u8) } } /// Returns a new `IPAddress` with the first `prefix` bits of the `IPAddress`. /// /// ```rust /// # use cryptography_x509_verification::types::IPAddress; /// let ip = IPAddress::from_str("192.0.2.1").unwrap(); /// assert_eq!(ip.mask(24), IPAddress::from_str("192.0.2.0").unwrap()); /// ``` pub fn mask(&self, prefix: u8) -> Self { match self.0 { IpAddr::V4(a) => { let prefix = 32u8.saturating_sub(prefix).into(); let masked = u32::from_be_bytes(a.octets()) & u32::MAX .checked_shr(prefix) .unwrap_or(0) .checked_shl(prefix) .unwrap_or(0); Self::from_bytes(&masked.to_be_bytes()).unwrap() } IpAddr::V6(a) => { let prefix = 128u8.saturating_sub(prefix).into(); let masked = u128::from_be_bytes(a.octets()) & u128::MAX .checked_shr(prefix) .unwrap_or(0) .checked_shl(prefix) .unwrap_or(0); Self::from_bytes(&masked.to_be_bytes()).unwrap() } } } } impl From for IPAddress { fn from(addr: IpAddr) -> Self { Self(addr) } } #[derive(Debug, PartialEq, Eq)] pub struct IPConstraint { address: IPAddress, prefix: u8, } /// An `IPConstraint` represents a CIDR-style IP address range used in a name constraints /// extension, as defined by [RFC 5280 4.2.1.10]. /// /// [RFC 5280 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 impl IPConstraint { /// Constructs an `IPConstraint` from a slice. The input slice must be 8 (IPv4) /// or 32 (IPv6) bytes long and contain two IP addresses, the first being /// a subnet and the second defining the subnet's mask. /// /// The subnet mask must contain only one contiguous run of set bits starting /// from the most significant bit. For example, a valid IPv4 subnet mask would /// be FF FF 00 00, whereas an invalid IPv4 subnet mask would be FF EF 00 00. pub fn from_bytes(b: &[u8]) -> Option { let slice_idx = match b.len() { 8 => 4, 32 => 16, _ => return None, }; let prefix = IPAddress::from_bytes(&b[slice_idx..])?.as_prefix()?; Some(IPConstraint { address: IPAddress::from_bytes(&b[..slice_idx])?.mask(prefix), prefix, }) } /// Determines if the `addr` is within the `IPConstraint`. /// /// ```rust /// # use cryptography_x509_verification::types::{IPAddress, IPConstraint}; /// let range_bytes = b"\xc6\x33\x64\x00\xff\xff\xff\x00"; /// let range = IPConstraint::from_bytes(range_bytes).unwrap(); /// assert!(range.matches(&IPAddress::from_str("198.51.100.42").unwrap())); /// ``` pub fn matches(&self, addr: &IPAddress) -> bool { self.address == addr.mask(self.prefix) } } /// An `RFC822Name` represents an email address, as defined in [RFC 822 6.1] /// and as amended by [RFC 2821 4.1.2]. In particular, it represents the `Mailbox` /// rule from RFC 2821's grammar. /// /// This type does not currently support the quoted local-part form; email /// addresses that use this form will be rejected. /// /// [RFC 822 6.1]: https://datatracker.ietf.org/doc/html/rfc822#section-6.1 /// [RFC 2821 4.1.2]: https://datatracker.ietf.org/doc/html/rfc2821#section-4.1.2 #[derive(PartialEq)] pub struct RFC822Name<'a> { pub mailbox: IA5String<'a>, pub domain: DNSName<'a>, } impl<'a> RFC822Name<'a> { pub fn new(value: &'a str) -> Option { // Mailbox = Local-part "@" Domain // Both must be present. let (local_part, domain) = value.split_once('@')?; let local_part = IA5String::new(local_part)?; // Local-part = Dot-string / Quoted-string // NOTE(ww): We do not support the Quoted-string form, for now. // // Dot-string: Atom *("." Atom) // Atom = 1*atext // // NOTE(ww): `atext`'s production is in RFC 2822 3.2.4. for component in local_part.as_str().split('.') { if component.is_empty() || !component .chars() .all(|c| c.is_ascii_alphanumeric() || ATEXT_CHARS.contains(c)) { return None; } } Some(Self { mailbox: local_part, domain: DNSName::new(domain)?, }) } } /// An `RFC822Constraint` represents a Name Constraint on email addresses. pub enum RFC822Constraint<'a> { /// A constraint for an exact match on a specific email address. Exact(RFC822Name<'a>), /// A constraint for any mailbox on a particular domain. OnDomain(DNSName<'a>), /// A constraint for any mailbox *within* a particular domain. /// For example, `InDomain("example.com")` will match `foo@bar.example.com` /// but not `foo@example.com`, since `bar.example.com` is in `example.com` /// but `example.com` is not within itself. InDomain(DNSName<'a>), } impl<'a> RFC822Constraint<'a> { pub fn new(constraint: &'a str) -> Option { if let Some(constraint) = constraint.strip_prefix('.') { Some(Self::InDomain(DNSName::new(constraint)?)) } else if let Some(email) = RFC822Name::new(constraint) { Some(Self::Exact(email)) } else { Some(Self::OnDomain(DNSName::new(constraint)?)) } } pub fn matches(&self, email: &RFC822Name<'_>) -> bool { match self { Self::Exact(pat) => pat == email, Self::OnDomain(pat) => &email.domain == pat, Self::InDomain(pat) => email.domain.is_subdomain_of(pat), } } } #[cfg(test)] mod tests { use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint, RFC822Name}; use super::RFC822Constraint; #[test] fn test_dnsname_debug_trait() { // Just to get coverage on the `Debug` derive. assert_eq!( "DNSName(IA5String(\"example.com\"))", format!("{:?}", DNSName::new("example.com").unwrap()) ); } #[test] fn test_dnsname_new() { assert_eq!(DNSName::new(""), None); assert_eq!(DNSName::new("."), None); assert_eq!(DNSName::new(".."), None); assert_eq!(DNSName::new(".a."), None); assert_eq!(DNSName::new("a.a."), None); assert_eq!(DNSName::new(".a"), None); assert_eq!(DNSName::new("a."), None); assert_eq!(DNSName::new("a.."), None); assert_eq!(DNSName::new(" "), None); assert_eq!(DNSName::new("\t"), None); assert_eq!(DNSName::new(" whitespace "), None); assert_eq!(DNSName::new("white. space"), None); assert_eq!(DNSName::new("!badlabel!"), None); assert_eq!(DNSName::new("bad!label"), None); assert_eq!(DNSName::new("goodlabel.!badlabel!"), None); assert_eq!(DNSName::new("-foo.bar.example.com"), None); assert_eq!(DNSName::new("foo-.bar.example.com"), None); assert_eq!(DNSName::new("foo.-bar.example.com"), None); assert_eq!(DNSName::new("foo.bar-.example.com"), None); assert_eq!(DNSName::new(&"a".repeat(64)), None); assert_eq!(DNSName::new("⚠️"), None); assert_eq!(DNSName::new(".foo.example"), None); assert_eq!(DNSName::new(".example.com"), None); let long_valid_label = "a".repeat(63); let long_name = std::iter::repeat(long_valid_label) .take(5) .collect::>() .join("."); assert_eq!(DNSName::new(&long_name), None); assert_eq!( DNSName::new(&"a".repeat(63)).unwrap().as_str(), "a".repeat(63) ); assert_eq!(DNSName::new("example.com").unwrap().as_str(), "example.com"); assert_eq!( DNSName::new("123.example.com").unwrap().as_str(), "123.example.com" ); assert_eq!(DNSName::new("EXAMPLE.com").unwrap().as_str(), "EXAMPLE.com"); assert_eq!(DNSName::new("EXAMPLE.COM").unwrap().as_str(), "EXAMPLE.COM"); assert_eq!( DNSName::new("xn--bcher-kva.example").unwrap().as_str(), "xn--bcher-kva.example" ); } #[test] fn test_dnsname_equality() { assert_ne!( DNSName::new("foo.example.com").unwrap(), DNSName::new("example.com").unwrap() ); // DNS name comparisons are case insensitive. assert_eq!( DNSName::new("EXAMPLE.COM").unwrap(), DNSName::new("example.com").unwrap() ); assert_eq!( DNSName::new("ExAmPLe.CoM").unwrap(), DNSName::new("eXaMplE.cOm").unwrap() ); } #[test] fn test_dnsname_parent() { assert_eq!(DNSName::new("localhost").unwrap().parent(), None); assert_eq!( DNSName::new("example.com").unwrap().parent().unwrap(), DNSName::new("com").unwrap() ); assert_eq!( DNSName::new("foo.example.com").unwrap().parent().unwrap(), DNSName::new("example.com").unwrap() ); } #[test] fn test_dnsname_is_subdomain_of() { for (sup, sub, check) in &[ // good cases ("example.com", "sub.example.com", true), ("example.com", "a.b.example.com", true), ("sub.example.com", "sub.sub.example.com", true), ("sub.example.com", "sub.sub.sub.example.com", true), ("com", "example.com", true), ("example.com", "com.example.com", true), ("example.com", "com.example.example.com", true), // bad cases ("example.com", "example.com", false), ("example.com", "com", false), ("sub.example.com", "example.com", false), ("sub.sub.example.com", "sub.sub.example.com", false), ("sub.sub.example.com", "example.com", false), ("com.example.com", "com.example.com", false), ("com.example.example.com", "com.example.example.com", false), ] { let sup = DNSName::new(sup).unwrap(); let sub = DNSName::new(sub).unwrap(); assert_eq!(sub.is_subdomain_of(&sup), *check); } } #[test] fn test_dnspattern_new() { assert_eq!(DNSPattern::new("*"), None); assert_eq!(DNSPattern::new("*."), None); assert_eq!(DNSPattern::new("f*o.example.com"), None); assert_eq!(DNSPattern::new("*oo.example.com"), None); assert_eq!(DNSPattern::new("fo*.example.com"), None); assert_eq!(DNSPattern::new("foo.*.example.com"), None); assert_eq!(DNSPattern::new("*.foo.*.example.com"), None); assert_eq!( DNSPattern::new("example.com").unwrap(), DNSPattern::Exact(DNSName::new("example.com").unwrap()) ); assert_eq!( DNSPattern::new("*.example.com").unwrap(), DNSPattern::Wildcard(DNSName::new("example.com").unwrap()) ); } #[test] fn test_dnspattern_matches() { let exactly_localhost = DNSPattern::new("localhost").unwrap(); let any_localhost = DNSPattern::new("*.localhost").unwrap(); let exactly_example_com = DNSPattern::new("example.com").unwrap(); let any_example_com = DNSPattern::new("*.example.com").unwrap(); // Exact patterns match only the exact name. assert!(exactly_localhost.matches(&DNSName::new("localhost").unwrap())); assert!(exactly_localhost.matches(&DNSName::new("LOCALHOST").unwrap())); assert!(exactly_example_com.matches(&DNSName::new("example.com").unwrap())); assert!(exactly_example_com.matches(&DNSName::new("EXAMPLE.com").unwrap())); assert!(!exactly_example_com.matches(&DNSName::new("foo.example.com").unwrap())); // Wildcard patterns match any subdomain, but not the parent or nested subdomains. assert!(any_example_com.matches(&DNSName::new("foo.example.com").unwrap())); assert!(any_example_com.matches(&DNSName::new("bar.example.com").unwrap())); assert!(any_example_com.matches(&DNSName::new("BAZ.example.com").unwrap())); assert!(!any_example_com.matches(&DNSName::new("example.com").unwrap())); assert!(!any_example_com.matches(&DNSName::new("foo.bar.example.com").unwrap())); assert!(!any_example_com.matches(&DNSName::new("foo.bar.baz.example.com").unwrap())); assert!(!any_localhost.matches(&DNSName::new("localhost").unwrap())); } #[test] fn test_dnsconstraint_new() { assert!(DNSConstraint::new("").is_none()); assert!(DNSConstraint::new(".").is_none()); assert!(DNSConstraint::new("*.").is_none()); assert!(DNSConstraint::new("*").is_none()); assert!(DNSConstraint::new(".example").is_none()); assert!(DNSConstraint::new("*.example").is_none()); assert!(DNSConstraint::new("*.example.com").is_none()); assert!(DNSConstraint::new("example").is_some()); assert!(DNSConstraint::new("example.com").is_some()); assert!(DNSConstraint::new("foo.example.com").is_some()); } #[test] fn test_dnsconstraint_matches() { let example_com = DNSConstraint::new("example.com").unwrap(); // Exact domain and arbitrary subdomains match. assert!(example_com.matches(&DNSName::new("example.com").unwrap())); assert!(example_com.matches(&DNSName::new("foo.example.com").unwrap())); assert!(example_com.matches(&DNSName::new("foo.bar.baz.quux.example.com").unwrap())); // Parent domains, distinct domains, and substring domains do not match. assert!(!example_com.matches(&DNSName::new("com").unwrap())); assert!(!example_com.matches(&DNSName::new("badexample.com").unwrap())); assert!(!example_com.matches(&DNSName::new("wrong.com").unwrap())); } #[test] fn test_ipaddress_from_str() { assert_ne!(IPAddress::from_str("192.168.1.1"), None) } #[test] fn test_ipaddress_from_bytes() { let ipv4 = b"\xc0\x00\x02\x01"; let ipv6 = b"\x20\x01\x0d\xb8\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x01"; let bad = b"\xde\xad"; assert_eq!( IPAddress::from_bytes(ipv4).unwrap(), IPAddress::from_str("192.0.2.1").unwrap(), ); assert_eq!( IPAddress::from_bytes(ipv6).unwrap(), IPAddress::from_str("2001:db8::1").unwrap(), ); assert_eq!(IPAddress::from_bytes(bad), None); } #[test] fn test_ipaddress_as_prefix() { let ipv4 = IPAddress::from_str("255.255.255.0").unwrap(); let ipv6 = IPAddress::from_str("ffff:ffff:ffff:ffff::").unwrap(); let ipv4_nonmask = IPAddress::from_str("192.0.2.1").unwrap(); let ipv6_nonmask = IPAddress::from_str("2001:db8::1").unwrap(); assert_eq!(ipv4.as_prefix(), Some(24)); assert_eq!(ipv6.as_prefix(), Some(64)); assert_eq!(ipv4_nonmask.as_prefix(), None); assert_eq!(ipv6_nonmask.as_prefix(), None); } #[test] fn test_ipaddress_mask() { let ipv4 = IPAddress::from_str("192.0.2.252").unwrap(); let ipv6 = IPAddress::from_str("2001:db8::f00:01ba").unwrap(); assert_eq!(ipv4.mask(0), IPAddress::from_str("0.0.0.0").unwrap()); assert_eq!(ipv4.mask(64), ipv4); assert_eq!(ipv4.mask(32), ipv4); assert_eq!(ipv4.mask(24), IPAddress::from_str("192.0.2.0").unwrap()); assert_eq!(ipv6.mask(0), IPAddress::from_str("::0").unwrap()); assert_eq!(ipv6.mask(130), ipv6); assert_eq!(ipv6.mask(128), ipv6); assert_eq!(ipv6.mask(64), IPAddress::from_str("2001:db8::").unwrap()); assert_eq!( ipv6.mask(103), IPAddress::from_str("2001:db8::e00:0").unwrap() ); } #[test] fn test_ipconstraint_from_bytes() { let ipv4_bad = b"\xc0\xa8\x01\x01\xff\xfe\xff\x00"; let ipv4_bad_many_bits = b"\xc0\xa8\x01\x01\xff\xfc\xff\x00"; let ipv4_bad_octet = b"\xc0\xa8\x01\x01\x00\xff\xff\xff"; let ipv6_bad = b"\ \x26\x01\x00\x00\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00"; let ipv6_good = b"\ \x20\x01\x0d\xb8\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x01\ \xf0\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00"; let bad = b"\xff\xff\xff"; assert_eq!(IPConstraint::from_bytes(ipv4_bad), None); assert_eq!(IPConstraint::from_bytes(ipv4_bad_many_bits), None); assert_eq!(IPConstraint::from_bytes(ipv4_bad_octet), None); assert_eq!(IPConstraint::from_bytes(ipv6_bad), None); assert_ne!(IPConstraint::from_bytes(ipv6_good), None); assert_eq!(IPConstraint::from_bytes(bad), None); // 192.168.1.1/16 let ipv4_with_extra = b"\xc0\xa8\x01\x01\xff\xff\x00\x00"; assert_ne!(IPConstraint::from_bytes(ipv4_with_extra), None); // 192.168.0.0/16 let ipv4_masked = b"\xc0\xa8\x00\x00\xff\xff\x00\x00"; assert_eq!( IPConstraint::from_bytes(ipv4_with_extra), IPConstraint::from_bytes(ipv4_masked) ); } #[test] fn test_ipconstraint_matches() { // 192.168.1.1/16 let ipv4 = IPConstraint::from_bytes(b"\xc0\xa8\x01\x01\xff\xff\x00\x00").unwrap(); let ipv4_32 = IPConstraint::from_bytes(b"\xc0\x00\x02\xde\xff\xff\xff\xff").unwrap(); let ipv6 = IPConstraint::from_bytes( b"\x26\x00\x0d\xb8\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x01\ \xff\xff\xff\xff\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00", ) .unwrap(); let ipv6_128 = IPConstraint::from_bytes( b"\x26\x00\x0d\xb8\x00\x00\x00\x00\ \x00\x00\x00\x00\xff\x00\xde\xde\ \xff\xff\xff\xff\xff\xff\xff\xff\ \xff\xff\xff\xff\xff\xff\xff\xff", ) .unwrap(); assert!(ipv4.matches(&IPAddress::from_str("192.168.0.50").unwrap())); assert!(!ipv4.matches(&IPAddress::from_str("192.160.0.50").unwrap())); assert!(ipv4_32.matches(&IPAddress::from_str("192.0.2.222").unwrap())); assert!(!ipv4_32.matches(&IPAddress::from_str("192.5.2.222").unwrap())); assert!(!ipv4_32.matches(&IPAddress::from_str("192.0.2.1").unwrap())); assert!(ipv6.matches(&IPAddress::from_str("2600:db8::abba").unwrap())); assert!(ipv6_128.matches(&IPAddress::from_str("2600:db8::ff00:dede").unwrap())); assert!(!ipv6_128.matches(&IPAddress::from_str("2600::ff00:dede").unwrap())); assert!(!ipv6_128.matches(&IPAddress::from_str("2600:db8::ff00:0").unwrap())); } #[test] fn test_rfc822name() { for bad_case in &[ "", // Missing local-part. "@example.com", " @example.com", " @example.com", // Missing domain cases. "foo", "foo@", "foo@ ", "foo@ ", // Invalid domains. "foo@!!!", "foo@white space", "foo@🙈", // Invalid local part (empty mailbox sections). ".@example.com", "foo.@example.com", ".foo@example.com", ".foo.@example.com", ".f.o.o.@example.com", // Invalid local part (@ in mailbox). "lol@lol@example.com", "lol\\@lol@example.com", "example@example.com@example.com", "@@example.com", // Invalid local part (invalid characters). "lol\"lol@example.com", "lol;lol@example.com", "🙈@example.com", // Intentionally unsupported quoted local parts. "\"validbutunsupported\"@example.com", ] { assert!(RFC822Name::new(bad_case).is_none()); } // Each good case is (address, (mailbox, domain)). for (address, (mailbox, domain)) in &[ // Normal mailboxes. ("foo@example.com", ("foo", "example.com")), ("foo.bar@example.com", ("foo.bar", "example.com")), ("foo.bar.baz@example.com", ("foo.bar.baz", "example.com")), ("1.2.3.4.5@example.com", ("1.2.3.4.5", "example.com")), // Mailboxes with special but valid characters. ("{legal}@example.com", ("{legal}", "example.com")), ("{&*.legal}@example.com", ("{&*.legal}", "example.com")), ("``````````@example.com", ("``````````", "example.com")), ("hello?@sub.example.com", ("hello?", "sub.example.com")), ] { let parsed = RFC822Name::new(&address).unwrap(); assert_eq!(&parsed.mailbox.as_str(), mailbox); assert_eq!(&parsed.domain.as_str(), domain); } } #[test] fn test_rfc822constraint_new() { for (case, valid) in &[ // good cases ("foo@example.com", true), ("foo.bar@example.com", true), ("foo!bar@example.com", true), ("example.com", true), ("sub.example.com", true), ("foo@sub.example.com", true), ("foo.bar@sub.example.com", true), ("foo!bar@sub.example.com", true), (".example.com", true), (".sub.example.com", true), // bad cases ("@example.com", false), ("@@example.com", false), ("foo@.example.com", false), (".foo@example.com", false), (".foo.@example.com", false), ("foo.@example.com", false), ("invaliddomain!", false), ("..example.com", false), ("foo..example.com", false), (".foo..example.com", false), ("..foo..example.com", false), ] { assert_eq!(RFC822Constraint::new(case).is_some(), *valid); } } #[test] fn test_rfc822constraint_matches() { { let exact = RFC822Constraint::new("foo@example.com").unwrap(); // Ordinary exact match. assert!(exact.matches(&RFC822Name::new("foo@example.com").unwrap())); // Case changes are okay in the domain. assert!(exact.matches(&RFC822Name::new("foo@EXAMPLE.com").unwrap())); // Case changes are not okay in the mailbox. assert!(!exact.matches(&RFC822Name::new("Foo@example.com").unwrap())); assert!(!exact.matches(&RFC822Name::new("FOO@example.com").unwrap())); // Different mailboxes and domains do not match. assert!(!exact.matches(&RFC822Name::new("foo.bar@example.com").unwrap())); assert!(!exact.matches(&RFC822Name::new("foo@sub.example.com").unwrap())); } { let on_domain = RFC822Constraint::new("example.com").unwrap(); // Ordinary domain matches. assert!(on_domain.matches(&RFC822Name::new("foo@example.com").unwrap())); assert!(on_domain.matches(&RFC822Name::new("bar@example.com").unwrap())); assert!(on_domain.matches(&RFC822Name::new("foo.bar@example.com").unwrap())); assert!(on_domain.matches(&RFC822Name::new("foo!bar@example.com").unwrap())); // Case changes are okay in the domain and in the mailbox, // since any mailbox on the domain is okay. assert!(on_domain.matches(&RFC822Name::new("foo@EXAMPLE.com").unwrap())); assert!(on_domain.matches(&RFC822Name::new("FOO@example.com").unwrap())); // Subdomains and other domains do not match. assert!(!on_domain.matches(&RFC822Name::new("foo@sub.example.com").unwrap())); assert!(!on_domain.matches(&RFC822Name::new("foo@localhost").unwrap())); } { let in_domain = RFC822Constraint::new(".example.com").unwrap(); // Any subdomain and mailbox matches. assert!(in_domain.matches(&RFC822Name::new("foo@sub.example.com").unwrap())); assert!(in_domain.matches(&RFC822Name::new("foo@sub.sub.example.com").unwrap())); assert!(in_domain.matches(&RFC822Name::new("foo@com.example.example.com").unwrap())); assert!(in_domain.matches(&RFC822Name::new("foo.bar@com.example.example.com").unwrap())); assert!(in_domain.matches(&RFC822Name::new("foo!bar@com.example.example.com").unwrap())); assert!(in_domain.matches(&RFC822Name::new("bar@com.example.example.com").unwrap())); // Case changes are okay in the subdomains and in the mailbox, since any mailbox // in the domain is okay. assert!(in_domain.matches(&RFC822Name::new("foo@SUB.example.com").unwrap())); assert!(in_domain.matches(&RFC822Name::new("foo@sub.EXAMPLE.com").unwrap())); assert!(in_domain.matches(&RFC822Name::new("foo@sub.example.COM").unwrap())); assert!(in_domain.matches(&RFC822Name::new("FOO@sub.example.COM").unwrap())); assert!(in_domain.matches(&RFC822Name::new("FOO@sub.example.com").unwrap())); // Superdomains and other domains do not match. assert!(!in_domain.matches(&RFC822Name::new("foo@example.com").unwrap())); assert!(!in_domain.matches(&RFC822Name::new("foo@com").unwrap())); } } } cryptography-43.0.0/src/rust/cryptography-x509/Cargo.toml010064400017510000177000000004321464676315000215020ustar 00000000000000[package] name = "cryptography-x509" version = "0.1.0" authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV rust-version = "1.65.0" [dependencies] asn1 = { version = "0.16.2", default-features = false } cryptography-43.0.0/src/rust/cryptography-x509/src/certificate.rs010064400017510000177000000042051464676315000231730ustar 00000000000000// 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. use crate::common; use crate::extensions; use crate::extensions::DuplicateExtensionsError; use crate::extensions::Extensions; use crate::name; use crate::name::NameReadable; #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct Certificate<'a> { pub tbs_cert: TbsCertificate<'a>, pub signature_alg: common::AlgorithmIdentifier<'a>, pub signature: asn1::BitString<'a>, } impl<'a> Certificate<'a> { /// Returns the certificate's issuer. pub fn issuer(&self) -> &NameReadable<'_> { self.tbs_cert.issuer.unwrap_read() } /// Returns the certificate's subject. pub fn subject(&self) -> &NameReadable<'_> { self.tbs_cert.subject.unwrap_read() } /// Returns an iterable container over the certificate's extension, or /// an error if the extension set contains a duplicate extension. pub fn extensions(&self) -> Result, DuplicateExtensionsError> { self.tbs_cert.extensions() } } #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct TbsCertificate<'a> { #[explicit(0)] #[default(0)] pub version: u8, pub serial: asn1::BigInt<'a>, pub signature_alg: common::AlgorithmIdentifier<'a>, pub issuer: name::Name<'a>, pub validity: Validity, pub subject: name::Name<'a>, pub spki: common::WithTlv<'a, common::SubjectPublicKeyInfo<'a>>, #[implicit(1)] pub issuer_unique_id: Option>, #[implicit(2)] pub subject_unique_id: Option>, #[explicit(3)] pub raw_extensions: Option>, } impl<'a> TbsCertificate<'a> { pub fn extensions(&self) -> Result, DuplicateExtensionsError> { Extensions::from_raw_extensions(self.raw_extensions.as_ref()) } } #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct Validity { pub not_before: common::Time, pub not_after: common::Time, } cryptography-43.0.0/src/rust/cryptography-x509/src/common.rs010064400017510000177000000436611464676315000222120ustar 00000000000000// 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. use asn1::Asn1DefinedByWritable; use crate::oid; #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone, Eq, Debug)] pub struct AlgorithmIdentifier<'a> { pub oid: asn1::DefinedByMarker, #[defined_by(oid)] pub params: AlgorithmParameters<'a>, } impl AlgorithmIdentifier<'_> { pub fn oid(&self) -> &asn1::ObjectIdentifier { self.params.item() } } #[derive(asn1::Asn1DefinedByRead, asn1::Asn1DefinedByWrite, PartialEq, Eq, Hash, Clone, Debug)] pub enum AlgorithmParameters<'a> { #[defined_by(oid::SHA1_OID)] Sha1(Option), #[defined_by(oid::SHA224_OID)] Sha224(Option), #[defined_by(oid::SHA256_OID)] Sha256(Option), #[defined_by(oid::SHA384_OID)] Sha384(Option), #[defined_by(oid::SHA512_OID)] Sha512(Option), #[defined_by(oid::SHA3_224_OID)] Sha3_224(Option), #[defined_by(oid::SHA3_256_OID)] Sha3_256(Option), #[defined_by(oid::SHA3_384_OID)] Sha3_384(Option), #[defined_by(oid::SHA3_512_OID)] Sha3_512(Option), #[defined_by(oid::ED25519_OID)] Ed25519, #[defined_by(oid::ED448_OID)] Ed448, #[defined_by(oid::X25519_OID)] X25519, #[defined_by(oid::X448_OID)] X448, // These encodings are only used in SPKI AlgorithmIdentifiers. #[defined_by(oid::EC_OID)] Ec(EcParameters<'a>), #[defined_by(oid::RSA_OID)] Rsa(Option), // These ECDSA algorithms should have no parameters, // but Java 11 (up to at least 11.0.19) encodes them // with NULL parameters. The JDK team is looking to // backport the fix as of June 2023. #[defined_by(oid::ECDSA_WITH_SHA224_OID)] EcDsaWithSha224(Option), #[defined_by(oid::ECDSA_WITH_SHA256_OID)] EcDsaWithSha256(Option), #[defined_by(oid::ECDSA_WITH_SHA384_OID)] EcDsaWithSha384(Option), #[defined_by(oid::ECDSA_WITH_SHA512_OID)] EcDsaWithSha512(Option), #[defined_by(oid::ECDSA_WITH_SHA3_224_OID)] EcDsaWithSha3_224, #[defined_by(oid::ECDSA_WITH_SHA3_256_OID)] EcDsaWithSha3_256, #[defined_by(oid::ECDSA_WITH_SHA3_384_OID)] EcDsaWithSha3_384, #[defined_by(oid::ECDSA_WITH_SHA3_512_OID)] EcDsaWithSha3_512, #[defined_by(oid::RSA_WITH_SHA1_OID)] RsaWithSha1(Option), #[defined_by(oid::RSA_WITH_SHA1_ALT_OID)] RsaWithSha1Alt(Option), #[defined_by(oid::RSA_WITH_SHA224_OID)] RsaWithSha224(Option), #[defined_by(oid::RSA_WITH_SHA256_OID)] RsaWithSha256(Option), #[defined_by(oid::RSA_WITH_SHA384_OID)] RsaWithSha384(Option), #[defined_by(oid::RSA_WITH_SHA512_OID)] RsaWithSha512(Option), #[defined_by(oid::RSA_WITH_SHA3_224_OID)] RsaWithSha3_224(Option), #[defined_by(oid::RSA_WITH_SHA3_256_OID)] RsaWithSha3_256(Option), #[defined_by(oid::RSA_WITH_SHA3_384_OID)] RsaWithSha3_384(Option), #[defined_by(oid::RSA_WITH_SHA3_512_OID)] RsaWithSha3_512(Option), // RsaPssParameters must be present in Certificate::tbs_cert::signature_alg::params // and Certificate::signature_alg::params, but Certificate::tbs_cert::spki::algorithm::oid // also uses RSASSA_PSS_OID and the params field is omitted since it has no meaning there. #[defined_by(oid::RSASSA_PSS_OID)] RsaPss(Option>>), #[defined_by(oid::DSA_OID)] Dsa(DssParams<'a>), #[defined_by(oid::DSA_WITH_SHA224_OID)] DsaWithSha224(Option), #[defined_by(oid::DSA_WITH_SHA256_OID)] DsaWithSha256(Option), #[defined_by(oid::DSA_WITH_SHA384_OID)] DsaWithSha384(Option), #[defined_by(oid::DSA_WITH_SHA512_OID)] DsaWithSha512(Option), #[defined_by(oid::DH_OID)] Dh(DHXParams<'a>), #[defined_by(oid::DH_KEY_AGREEMENT_OID)] DhKeyAgreement(BasicDHParams<'a>), #[defined_by(oid::PBES2_OID)] Pbes2(PBES2Params<'a>), #[defined_by(oid::PBKDF2_OID)] Pbkdf2(PBKDF2Params<'a>), #[defined_by(oid::HMAC_WITH_SHA1_OID)] HmacWithSha1(asn1::Null), #[defined_by(oid::HMAC_WITH_SHA256_OID)] HmacWithSha256(asn1::Null), // Used only in PKCS#7 AlgorithmIdentifiers // https://datatracker.ietf.org/doc/html/rfc3565#section-4.1 // // From RFC 3565 section 4.1: // The AlgorithmIdentifier parameters field MUST be present, and the // parameters field MUST contain a AES-IV: // // AES-IV ::= OCTET STRING (SIZE(16)) #[defined_by(oid::AES_128_CBC_OID)] Aes128Cbc([u8; 16]), #[defined_by(oid::AES_256_CBC_OID)] Aes256Cbc([u8; 16]), #[defined_by(oid::PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC)] Pbes1WithShaAnd3KeyTripleDesCbc(PBES1Params), #[default] Other(asn1::ObjectIdentifier, Option>), } #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Eq, Clone)] pub struct SubjectPublicKeyInfo<'a> { pub algorithm: AlgorithmIdentifier<'a>, pub subject_public_key: asn1::BitString<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub struct AttributeTypeValue<'a> { pub type_id: asn1::ObjectIdentifier, pub value: RawTlv<'a>, } // Like `asn1::Tlv` but doesn't store `full_data` so it can be constructed from // an un-encoded tag and value. #[derive(Hash, PartialEq, Eq, Clone)] pub struct RawTlv<'a> { tag: asn1::Tag, value: &'a [u8], } impl<'a> RawTlv<'a> { pub fn new(tag: asn1::Tag, value: &'a [u8]) -> Self { RawTlv { tag, value } } pub fn tag(&self) -> asn1::Tag { self.tag } pub fn data(&self) -> &'a [u8] { self.value } } impl<'a> asn1::Asn1Readable<'a> for RawTlv<'a> { fn parse(parser: &mut asn1::Parser<'a>) -> asn1::ParseResult { let tlv = parser.read_element::>()?; Ok(RawTlv::new(tlv.tag(), tlv.data())) } fn can_parse(_tag: asn1::Tag) -> bool { true } } impl<'a> asn1::Asn1Writable for RawTlv<'a> { fn write(&self, w: &mut asn1::Writer<'_>) -> asn1::WriteResult { w.write_tlv(self.tag, move |dest| dest.push_slice(self.value)) } } #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub enum Time { UtcTime(asn1::UtcTime), GeneralizedTime(asn1::GeneralizedTime), } impl Time { pub fn as_datetime(&self) -> &asn1::DateTime { match self { Time::UtcTime(data) => data.as_datetime(), Time::GeneralizedTime(data) => data.as_datetime(), } } } #[derive(Hash, PartialEq, Eq, Clone)] pub enum Asn1ReadableOrWritable { Read(T), Write(U), } impl Asn1ReadableOrWritable { pub fn new_read(v: T) -> Self { Asn1ReadableOrWritable::Read(v) } pub fn new_write(v: U) -> Self { Asn1ReadableOrWritable::Write(v) } pub fn unwrap_read(&self) -> &T { match self { Asn1ReadableOrWritable::Read(v) => v, Asn1ReadableOrWritable::Write(_) => panic!("unwrap_read called on a Write value"), } } } impl<'a, T: asn1::SimpleAsn1Readable<'a>, U> asn1::SimpleAsn1Readable<'a> for Asn1ReadableOrWritable { const TAG: asn1::Tag = T::TAG; fn parse_data(data: &'a [u8]) -> asn1::ParseResult { Ok(Self::new_read(T::parse_data(data)?)) } } impl asn1::SimpleAsn1Writable for Asn1ReadableOrWritable { const TAG: asn1::Tag = U::TAG; fn write_data(&self, w: &mut asn1::WriteBuf) -> asn1::WriteResult { match self { Asn1ReadableOrWritable::Read(v) => T::write_data(v, w), Asn1ReadableOrWritable::Write(v) => U::write_data(v, w), } } } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct DssSignature<'a> { pub r: asn1::BigUint<'a>, pub s: asn1::BigUint<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct DHParams<'a> { pub p: asn1::BigUint<'a>, pub g: asn1::BigUint<'a>, pub q: Option>, } // From PKCS#3 Section 9 // DHParameter ::= SEQUENCE { // prime INTEGER, -- p // base INTEGER, -- g // privateValueLength INTEGER OPTIONAL // } #[derive(asn1::Asn1Read, asn1::Asn1Write, Clone, PartialEq, Eq, Debug, Hash)] pub struct BasicDHParams<'a> { pub p: asn1::BigUint<'a>, pub g: asn1::BigUint<'a>, pub private_value_length: Option, } // From https://www.rfc-editor.org/rfc/rfc3279#section-2.3.3 // DomainParameters ::= SEQUENCE { // p INTEGER, -- odd prime, p=jq +1 // g INTEGER, -- generator, g // q INTEGER, -- factor of p-1 // j INTEGER OPTIONAL, -- subgroup factor // validationParms ValidationParms OPTIONAL // } #[derive(asn1::Asn1Read, asn1::Asn1Write, Clone, PartialEq, Eq, Debug, Hash)] pub struct DHXParams<'a> { pub p: asn1::BigUint<'a>, pub g: asn1::BigUint<'a>, pub q: asn1::BigUint<'a>, pub j: Option>, // No support for this, so don't bother filling out the fields. pub validation_params: Option>, } // RSA-PSS ASN.1 default hash algorithm pub const PSS_SHA1_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Sha1(Some(())), }; // RSA-PSS ASN.1 hash algorithm definitions specified under the CA/B Forum BRs. pub const PSS_SHA256_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Sha256(Some(())), }; pub const PSS_SHA384_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Sha384(Some(())), }; pub const PSS_SHA512_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Sha512(Some(())), }; // This is defined as an AlgorithmIdentifier in RFC 4055, // but the mask generation algorithm **must** contain an AlgorithmIdentifier // in its params, so we define it this way. #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] pub struct MaskGenAlgorithm<'a> { pub oid: asn1::ObjectIdentifier, pub params: AlgorithmIdentifier<'a>, } // RSA-PSS ASN.1 default mask gen algorithm pub const PSS_SHA1_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { oid: oid::MGF1_OID, params: PSS_SHA1_HASH_ALG, }; // RSA-PSS ASN.1 mask gen algorithms defined under the CA/B Forum BRs. pub const PSS_SHA256_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { oid: oid::MGF1_OID, params: PSS_SHA256_HASH_ALG, }; pub const PSS_SHA384_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { oid: oid::MGF1_OID, params: PSS_SHA384_HASH_ALG, }; pub const PSS_SHA512_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { oid: oid::MGF1_OID, params: PSS_SHA512_HASH_ALG, }; // From RFC 5480 section 2.1.1: // ECParameters ::= CHOICE { // namedCurve OBJECT IDENTIFIER // -- implicitCurve NULL // -- specifiedCurve SpecifiedECDomain } // // Only the namedCurve form may appear in PKIX. Other forms may be found in // other PKIs. #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] pub enum EcParameters<'a> { NamedCurve(asn1::ObjectIdentifier), ImplicitCurve(asn1::Null), SpecifiedCurve(asn1::Sequence<'a>), } // From RFC 4055 section 3.1: // RSASSA-PSS-params ::= SEQUENCE { // hashAlgorithm [0] HashAlgorithm DEFAULT // sha1Identifier, // maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT // mgf1SHA1Identifier, // saltLength [2] INTEGER DEFAULT 20, // trailerField [3] INTEGER DEFAULT 1 } #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] pub struct RsaPssParameters<'a> { #[explicit(0)] #[default(PSS_SHA1_HASH_ALG)] pub hash_algorithm: AlgorithmIdentifier<'a>, #[explicit(1)] #[default(PSS_SHA1_MASK_GEN_ALG)] pub mask_gen_algorithm: MaskGenAlgorithm<'a>, #[explicit(2)] #[default(20u16)] pub salt_length: u16, // While the RFC describes this field as `DEFAULT 1`, it also states that // parsers must accept this field being encoded with a value of 1, in // conflict with DER's requirement that field DEFAULT values not be // encoded. Thus we just treat this as an optional field. // // Users of this struct should supply `None` to indicate the DEFAULT value // of 1, or `Some` to indicate a different value. Note that if you supply // `Some(1)` this will result in encoding a violation of the DER rules, // thus this should never be done except to round-trip an existing // structure. #[explicit(3)] pub _trailer_field: Option, } // https://datatracker.ietf.org/doc/html/rfc3279#section-2.3.2 // // Dss-Parms ::= SEQUENCE { // p INTEGER, // q INTEGER, // g INTEGER // } #[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] pub struct DssParams<'a> { pub p: asn1::BigUint<'a>, pub q: asn1::BigUint<'a>, pub g: asn1::BigUint<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] pub struct PBES2Params<'a> { pub key_derivation_func: Box>, pub encryption_scheme: Box>, } const HMAC_SHA1_ALG: AlgorithmIdentifier<'static> = AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::HmacWithSha1(()), }; #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] pub struct PBKDF2Params<'a> { // This is technically a CHOICE that can be an otherSource. We don't // support that. pub salt: &'a [u8], pub iteration_count: u64, pub key_length: Option, #[default(HMAC_SHA1_ALG)] pub prf: Box>, } #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)] pub struct PBES1Params { pub salt: [u8; 8], pub iterations: u64, } /// A VisibleString ASN.1 element whose contents is not validated as meeting the /// requirements (visible characters of IA5), and instead is only known to be /// valid UTF-8. pub struct UnvalidatedVisibleString<'a>(pub &'a str); impl<'a> UnvalidatedVisibleString<'a> { pub fn as_str(&self) -> &'a str { self.0 } } impl<'a> asn1::SimpleAsn1Readable<'a> for UnvalidatedVisibleString<'a> { const TAG: asn1::Tag = asn1::VisibleString::TAG; fn parse_data(data: &'a [u8]) -> asn1::ParseResult { Ok(UnvalidatedVisibleString( std::str::from_utf8(data) .map_err(|_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?, )) } } impl<'a> asn1::SimpleAsn1Writable for UnvalidatedVisibleString<'a> { const TAG: asn1::Tag = asn1::VisibleString::TAG; fn write_data(&self, _: &mut asn1::WriteBuf) -> asn1::WriteResult { unimplemented!(); } } /// A BMPString ASN.1 element, where it is stored as a UTF-8 string in memory. pub struct Utf8StoredBMPString<'a>(pub &'a str); impl<'a> Utf8StoredBMPString<'a> { pub fn new(s: &'a str) -> Self { Utf8StoredBMPString(s) } } impl<'a> asn1::SimpleAsn1Writable for Utf8StoredBMPString<'a> { const TAG: asn1::Tag = asn1::BMPString::TAG; fn write_data(&self, writer: &mut asn1::WriteBuf) -> asn1::WriteResult { for ch in self.0.encode_utf16() { writer.push_slice(&ch.to_be_bytes())?; } Ok(()) } } #[derive(Clone)] pub struct WithTlv<'a, T> { tlv: asn1::Tlv<'a>, value: T, } impl<'a, T> WithTlv<'a, T> { pub fn tlv(&self) -> &asn1::Tlv<'a> { &self.tlv } } impl std::ops::Deref for WithTlv<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl<'a, T: asn1::Asn1Readable<'a>> asn1::Asn1Readable<'a> for WithTlv<'a, T> { fn parse(p: &mut asn1::Parser<'a>) -> asn1::ParseResult { let tlv = p.read_element::>()?; Ok(Self { tlv, value: tlv.parse()?, }) } fn can_parse(t: asn1::Tag) -> bool { T::can_parse(t) } } impl<'a, T: asn1::Asn1Writable> asn1::Asn1Writable for WithTlv<'a, T> { fn write(&self, w: &mut asn1::Writer<'_>) -> asn1::WriteResult<()> { self.value.write(w) } } impl PartialEq for WithTlv<'_, T> { fn eq(&self, other: &Self) -> bool { self.value == other.value } } impl Eq for WithTlv<'_, T> {} impl std::hash::Hash for WithTlv<'_, T> { fn hash(&self, state: &mut H) { self.value.hash(state) } } #[cfg(test)] mod tests { use asn1::Asn1Readable; use super::{Asn1ReadableOrWritable, RawTlv, UnvalidatedVisibleString, WithTlv}; #[test] #[should_panic] fn test_unvalidated_visible_string_write() { let v = UnvalidatedVisibleString("foo"); asn1::write_single(&v).unwrap(); } #[test] #[should_panic] fn test_asn1_readable_or_writable_unwrap_read() { Asn1ReadableOrWritable::::new_write(17).unwrap_read(); } #[test] fn test_asn1_readable_or_writable_write_read_data() { let v = Asn1ReadableOrWritable::::new_read(17); assert_eq!(&asn1::write_single(&v).unwrap(), b"\x02\x01\x11"); } #[test] fn test_raw_tlv_can_parse() { let t = asn1::Tag::from_bytes(&[0]).unwrap().0; assert!(RawTlv::can_parse(t)); } #[test] fn test_with_raw_tlv_can_parse() { let t = asn1::Tag::from_bytes(&[0x30]).unwrap().0; assert!(WithTlv::>::can_parse(t)); assert!(!WithTlv::::can_parse(t)); } } cryptography-43.0.0/src/rust/cryptography-x509/src/crl.rs010064400017510000177000000041211464676315000214660ustar 00000000000000// 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. use crate::{common, extensions, name}; pub type ReasonFlags<'a> = Option, asn1::OwnedBitString>>; #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash)] pub struct CertificateRevocationList<'a> { pub tbs_cert_list: TBSCertList<'a>, pub signature_algorithm: common::AlgorithmIdentifier<'a>, pub signature_value: asn1::BitString<'a>, } pub type RevokedCertificates<'a> = Option< common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, RevokedCertificate<'a>>, asn1::SequenceOfWriter<'a, RevokedCertificate<'a>, Vec>>, >, >; #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash)] pub struct TBSCertList<'a> { pub version: Option, pub signature: common::AlgorithmIdentifier<'a>, pub issuer: name::Name<'a>, pub this_update: common::Time, pub next_update: Option, pub revoked_certificates: RevokedCertificates<'a>, #[explicit(0)] pub raw_crl_extensions: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub struct RevokedCertificate<'a> { pub user_certificate: asn1::BigUint<'a>, pub revocation_date: common::Time, pub raw_crl_entry_extensions: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct IssuingDistributionPoint<'a> { #[explicit(0)] pub distribution_point: Option>, #[implicit(1)] #[default(false)] pub only_contains_user_certs: bool, #[implicit(2)] #[default(false)] pub only_contains_ca_certs: bool, #[implicit(3)] pub only_some_reasons: ReasonFlags<'a>, #[implicit(4)] #[default(false)] pub indirect_crl: bool, #[implicit(5)] #[default(false)] pub only_contains_attribute_certs: bool, } pub type CRLReason = asn1::Enumerated; cryptography-43.0.0/src/rust/cryptography-x509/src/csr.rs010064400017510000177000000043001464676315000214740ustar 00000000000000// 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. use crate::common; use crate::extensions; use crate::name; use crate::oid; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct Csr<'a> { pub csr_info: CertificationRequestInfo<'a>, pub signature_alg: common::AlgorithmIdentifier<'a>, pub signature: asn1::BitString<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct CertificationRequestInfo<'a> { pub version: u8, pub subject: name::Name<'a>, pub spki: common::WithTlv<'a, common::SubjectPublicKeyInfo<'a>>, #[implicit(0, required)] pub attributes: Attributes<'a>, } impl CertificationRequestInfo<'_> { pub fn get_extension_attribute( &self, ) -> Result>, asn1::ParseError> { for attribute in self.attributes.unwrap_read().clone() { if attribute.type_id == oid::EXTENSION_REQUEST || attribute.type_id == oid::MS_EXTENSION_REQUEST { check_attribute_length(attribute.values.unwrap_read().clone())?; let val = attribute.values.unwrap_read().clone().next().unwrap(); let exts = asn1::parse_single(val.full_data())?; return Ok(Some(exts)); } } Ok(None) } } pub fn check_attribute_length<'a>( values: asn1::SetOf<'a, asn1::Tlv<'a>>, ) -> Result<(), asn1::ParseError> { if values.count() > 1 { // TODO: We should raise a more specific error here // Only single-valued attributes are supported Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue)) } else { Ok(()) } } pub type Attributes<'a> = common::Asn1ReadableOrWritable< asn1::SetOf<'a, Attribute<'a>>, asn1::SetOfWriter<'a, Attribute<'a>, Vec>>, >; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct Attribute<'a> { pub type_id: asn1::ObjectIdentifier, pub values: common::Asn1ReadableOrWritable< asn1::SetOf<'a, asn1::Tlv<'a>>, asn1::SetOfWriter<'a, common::RawTlv<'a>, [common::RawTlv<'a>; 1]>, >, } cryptography-43.0.0/src/rust/cryptography-x509/src/extensions.rs010064400017510000177000000261001464676315000231060ustar 00000000000000// 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. use std::collections::HashSet; use crate::common; use crate::crl; use crate::name; pub struct DuplicateExtensionsError(pub asn1::ObjectIdentifier); pub type RawExtensions<'a> = common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, Extension<'a>>, asn1::SequenceOfWriter<'a, Extension<'a>, Vec>>, >; /// An invariant-enforcing wrapper for `RawExtensions`. /// /// In particular, an `Extensions` cannot be constructed from a `RawExtensions` /// that contains duplicated extensions (by OID). pub struct Extensions<'a>(Option>); impl<'a> Extensions<'a> { /// Create an `Extensions` from the given `RawExtensions`. /// /// Returns an `Err` variant containing the first duplicated extension's /// OID, if there are any duplicates. pub fn from_raw_extensions( raw: Option<&RawExtensions<'a>>, ) -> Result { match raw { Some(raw_exts) => { let mut seen_oids = HashSet::new(); for ext in raw_exts.unwrap_read().clone() { if !seen_oids.insert(ext.extn_id.clone()) { return Err(DuplicateExtensionsError(ext.extn_id)); } } Ok(Self(Some(raw_exts.clone()))) } None => Ok(Self(None)), } } /// Retrieves the extension identified by the given OID, /// or None if the extension is not present (or no extensions are present). pub fn get_extension(&self, oid: &asn1::ObjectIdentifier) -> Option> { self.iter().find(|ext| &ext.extn_id == oid) } /// Returns a reference to the underlying extensions. pub fn as_raw(&self) -> Option<&RawExtensions<'a>> { self.0.as_ref() } /// Returns an iterator over the underlying extensions. pub fn iter(&self) -> impl Iterator> { self.as_raw() .map(|raw| raw.unwrap_read().clone()) .into_iter() .flatten() } } #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] pub struct Extension<'a> { pub extn_id: asn1::ObjectIdentifier, #[default(false)] pub critical: bool, pub extn_value: &'a [u8], } impl<'a> Extension<'a> { pub fn value>(&self) -> asn1::ParseResult { asn1::parse_single(self.extn_value) } } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct PolicyConstraints { #[implicit(0)] pub require_explicit_policy: Option, #[implicit(1)] pub inhibit_policy_mapping: Option, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct AccessDescription<'a> { pub access_method: asn1::ObjectIdentifier, pub access_location: name::GeneralName<'a>, } pub type SequenceOfAccessDescriptions<'a> = common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, AccessDescription<'a>>, asn1::SequenceOfWriter<'a, AccessDescription<'a>, Vec>>, >; // Needed due to clippy type complexity warning. type SequenceOfPolicyQualifiers<'a> = common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, asn1::SequenceOfWriter<'a, PolicyQualifierInfo<'a>, Vec>>, >; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct PolicyInformation<'a> { pub policy_identifier: asn1::ObjectIdentifier, pub policy_qualifiers: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct PolicyQualifierInfo<'a> { pub policy_qualifier_id: asn1::ObjectIdentifier, pub qualifier: Qualifier<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub enum Qualifier<'a> { CpsUri(asn1::IA5String<'a>), UserNotice(UserNotice<'a>), } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct UserNotice<'a> { pub notice_ref: Option>, pub explicit_text: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct NoticeReference<'a> { pub organization: DisplayText<'a>, pub notice_numbers: common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, asn1::BigUint<'a>>, asn1::SequenceOfWriter<'a, asn1::BigUint<'a>, Vec>>, >, } // DisplayText also allows BMPString, which we currently do not support. #[allow(clippy::enum_variant_names)] #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub enum DisplayText<'a> { IA5String(asn1::IA5String<'a>), Utf8String(asn1::Utf8String<'a>), // Not validated due to certificates with UTF-8 in VisibleString. See PR #8884 VisibleString(common::UnvalidatedVisibleString<'a>), BmpString(asn1::BMPString<'a>), } // Needed due to clippy type complexity warning. pub type SequenceOfSubtrees<'a> = common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, GeneralSubtree<'a>>, asn1::SequenceOfWriter<'a, GeneralSubtree<'a>, Vec>>, >; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct NameConstraints<'a> { #[implicit(0)] pub permitted_subtrees: Option>, #[implicit(1)] pub excluded_subtrees: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct GeneralSubtree<'a> { pub base: name::GeneralName<'a>, #[implicit(0)] #[default(0u64)] pub minimum: u64, #[implicit(1)] pub maximum: Option, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct MSCertificateTemplate { pub template_id: asn1::ObjectIdentifier, pub major_version: Option, pub minor_version: Option, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct DistributionPoint<'a> { #[explicit(0)] pub distribution_point: Option>, #[implicit(1)] pub reasons: crl::ReasonFlags<'a>, #[implicit(2)] pub crl_issuer: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub enum DistributionPointName<'a> { #[implicit(0)] FullName(name::SequenceOfGeneralName<'a>), #[implicit(1)] NameRelativeToCRLIssuer( common::Asn1ReadableOrWritable< asn1::SetOf<'a, common::AttributeTypeValue<'a>>, asn1::SetOfWriter< 'a, common::AttributeTypeValue<'a>, Vec>, >, >, ), } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct AuthorityKeyIdentifier<'a> { #[implicit(0)] pub key_identifier: Option<&'a [u8]>, #[implicit(1)] pub authority_cert_issuer: Option>, #[implicit(2)] pub authority_cert_serial_number: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct BasicConstraints { #[default(false)] pub ca: bool, pub path_length: Option, } pub type SubjectAlternativeName<'a> = asn1::SequenceOf<'a, name::GeneralName<'a>>; pub type IssuerAlternativeName<'a> = asn1::SequenceOf<'a, name::GeneralName<'a>>; pub type ExtendedKeyUsage<'a> = asn1::SequenceOf<'a, asn1::ObjectIdentifier>; pub struct KeyUsage<'a>(asn1::BitString<'a>); impl<'a> asn1::SimpleAsn1Readable<'a> for KeyUsage<'a> { const TAG: asn1::Tag = asn1::BitString::TAG; fn parse_data(data: &'a [u8]) -> asn1::ParseResult { asn1::BitString::parse_data(data).map(Self) } } impl KeyUsage<'_> { pub fn is_zeroed(&self) -> bool { self.0.as_bytes().iter().all(|&b| b == 0) } pub fn digital_signature(&self) -> bool { self.0.has_bit_set(0) } pub fn content_commitment(&self) -> bool { self.0.has_bit_set(1) } pub fn key_encipherment(&self) -> bool { self.0.has_bit_set(2) } pub fn data_encipherment(&self) -> bool { self.0.has_bit_set(3) } pub fn key_agreement(&self) -> bool { self.0.has_bit_set(4) } pub fn key_cert_sign(&self) -> bool { self.0.has_bit_set(5) } pub fn crl_sign(&self) -> bool { self.0.has_bit_set(6) } pub fn encipher_only(&self) -> bool { self.0.has_bit_set(7) } pub fn decipher_only(&self) -> bool { self.0.has_bit_set(8) } } #[cfg(test)] mod tests { use super::{BasicConstraints, Extension, Extensions, KeyUsage}; use crate::oid::{AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID}; #[test] fn test_get_extension() { let bc = BasicConstraints { ca: true, path_length: Some(3), }; let extension = Extension { extn_id: BASIC_CONSTRAINTS_OID, critical: true, extn_value: &asn1::write_single(&bc).unwrap(), }; let extensions = asn1::SequenceOfWriter::new(vec![extension]); let der = asn1::write_single(&extensions).unwrap(); let raw = asn1::parse_single(&der).unwrap(); let extensions = Extensions::from_raw_extensions(Some(&raw)).ok().unwrap(); assert!(&extensions.get_extension(&BASIC_CONSTRAINTS_OID).is_some()); assert!(&extensions .get_extension(&AUTHORITY_KEY_IDENTIFIER_OID) .is_none()); } #[test] fn test_extensions_iter() { let bc = BasicConstraints { ca: true, path_length: Some(3), }; let extension = Extension { extn_id: BASIC_CONSTRAINTS_OID, critical: true, extn_value: &asn1::write_single(&bc).unwrap(), }; let extensions = asn1::SequenceOfWriter::new(vec![extension]); let der = asn1::write_single(&extensions).unwrap(); let parsed = asn1::parse_single(&der).unwrap(); let extensions = Extensions::from_raw_extensions(Some(&parsed)).ok().unwrap(); let extension_list: Vec<_> = extensions.iter().collect(); assert_eq!(extension_list.len(), 1); } #[test] fn test_extension_value() { let bc = BasicConstraints { ca: true, path_length: Some(3), }; let extension = Extension { extn_id: BASIC_CONSTRAINTS_OID, critical: true, extn_value: &asn1::write_single(&bc).unwrap(), }; let extracted: BasicConstraints = extension.value().unwrap(); assert_eq!(bc.ca, extracted.ca); assert_eq!(bc.path_length, extracted.path_length); } #[test] fn test_keyusage() { // let ku: KeyUsage = asn1::parse_single(data) let ku_bits = [0b1111_1111u8, 0b1000_0000u8]; let ku_bitstring = asn1::BitString::new(&ku_bits, 7).unwrap(); let asn1 = asn1::write_single(&ku_bitstring).unwrap(); let ku: KeyUsage<'_> = asn1::parse_single(&asn1).unwrap(); assert!(!ku.is_zeroed()); assert!(ku.digital_signature()); assert!(ku.content_commitment()); assert!(ku.key_encipherment()); assert!(ku.data_encipherment()); assert!(ku.key_agreement()); assert!(ku.key_cert_sign()); assert!(ku.crl_sign()); assert!(ku.encipher_only()); assert!(ku.decipher_only()); } } cryptography-43.0.0/src/rust/cryptography-x509/src/lib.rs010064400017510000177000000007651464676315000214660ustar 00000000000000// 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. #![forbid(unsafe_code)] #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] #![allow(unknown_lints, clippy::result_large_err)] pub mod certificate; pub mod common; pub mod crl; pub mod csr; pub mod extensions; pub mod name; pub mod ocsp_req; pub mod ocsp_resp; pub mod oid; pub mod pkcs12; pub mod pkcs7; cryptography-43.0.0/src/rust/cryptography-x509/src/name.rs010064400017510000177000000050411464676315000216300ustar 00000000000000// 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. use crate::common; pub type NameReadable<'a> = asn1::SequenceOf<'a, asn1::SetOf<'a, common::AttributeTypeValue<'a>>>; pub type Name<'a> = common::Asn1ReadableOrWritable< NameReadable<'a>, asn1::SequenceOfWriter< 'a, asn1::SetOfWriter<'a, common::AttributeTypeValue<'a>, Vec>>, Vec< asn1::SetOfWriter< 'a, common::AttributeTypeValue<'a>, Vec>, >, >, >, >; /// An IA5String ASN.1 element whose contents is not validated as meeting the /// requirements (ASCII characters only), and instead is only known to be /// valid UTF-8. pub struct UnvalidatedIA5String<'a>(pub &'a str); impl<'a> asn1::SimpleAsn1Readable<'a> for UnvalidatedIA5String<'a> { const TAG: asn1::Tag = asn1::IA5String::TAG; fn parse_data(data: &'a [u8]) -> asn1::ParseResult { Ok(UnvalidatedIA5String(std::str::from_utf8(data).map_err( |_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue), )?)) } } impl<'a> asn1::SimpleAsn1Writable for UnvalidatedIA5String<'a> { const TAG: asn1::Tag = asn1::IA5String::TAG; fn write_data(&self, dest: &mut asn1::WriteBuf) -> asn1::WriteResult { dest.push_slice(self.0.as_bytes()) } } #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash)] pub struct OtherName<'a> { pub type_id: asn1::ObjectIdentifier, #[explicit(0, required)] pub value: asn1::Tlv<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub enum GeneralName<'a> { #[implicit(0)] OtherName(OtherName<'a>), #[implicit(1)] RFC822Name(UnvalidatedIA5String<'a>), #[implicit(2)] DNSName(UnvalidatedIA5String<'a>), #[implicit(3)] // unsupported X400Address(asn1::Sequence<'a>), // Name is explicit per RFC 5280 Appendix A.1. #[explicit(4)] DirectoryName(Name<'a>), #[implicit(5)] // unsupported EDIPartyName(asn1::Sequence<'a>), #[implicit(6)] UniformResourceIdentifier(UnvalidatedIA5String<'a>), #[implicit(7)] IPAddress(&'a [u8]), #[implicit(8)] RegisteredID(asn1::ObjectIdentifier), } pub(crate) type SequenceOfGeneralName<'a> = common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, GeneralName<'a>>, asn1::SequenceOfWriter<'a, GeneralName<'a>, Vec>>, >; cryptography-43.0.0/src/rust/cryptography-x509/src/ocsp_req.rs010064400017510000177000000026611464676315000225300ustar 00000000000000// 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. use crate::{common, extensions, name}; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct TBSRequest<'a> { #[explicit(0)] #[default(0)] pub version: u8, #[explicit(1)] pub requestor_name: Option>, pub request_list: common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, Request<'a>>, asn1::SequenceOfWriter<'a, Request<'a>>, >, #[explicit(2)] pub raw_request_extensions: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct Request<'a> { pub req_cert: CertID<'a>, #[explicit(0)] pub single_request_extensions: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct CertID<'a> { pub hash_algorithm: common::AlgorithmIdentifier<'a>, pub issuer_name_hash: &'a [u8], pub issuer_key_hash: &'a [u8], pub serial_number: asn1::BigInt<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct OCSPRequest<'a> { pub tbs_request: TBSRequest<'a>, // Parsing out the full structure, which includes the entirety of a // certificate is more trouble than it's worth, since it's not in the // Python API. #[explicit(0)] pub optional_signature: Option>, } cryptography-43.0.0/src/rust/cryptography-x509/src/ocsp_resp.rs010064400017510000177000000047431464676315000227150ustar 00000000000000// 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. use crate::{certificate, common, crl, extensions, name, ocsp_req}; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct OCSPResponse<'a> { pub response_status: asn1::Enumerated, #[explicit(0)] pub response_bytes: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct ResponseBytes<'a> { pub response_type: asn1::ObjectIdentifier, pub response: asn1::OctetStringEncoded>, } pub type OCSPCerts<'a> = Option< common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, certificate::Certificate<'a>>, asn1::SequenceOfWriter<'a, certificate::Certificate<'a>, Vec>>, >, >; #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct BasicOCSPResponse<'a> { pub tbs_response_data: ResponseData<'a>, pub signature_algorithm: common::AlgorithmIdentifier<'a>, pub signature: asn1::BitString<'a>, #[explicit(0)] pub certs: OCSPCerts<'a>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct ResponseData<'a> { #[explicit(0)] #[default(0)] pub version: u8, pub responder_id: ResponderId<'a>, pub produced_at: asn1::GeneralizedTime, pub responses: common::Asn1ReadableOrWritable< asn1::SequenceOf<'a, SingleResponse<'a>>, asn1::SequenceOfWriter<'a, SingleResponse<'a>, Vec>>, >, #[explicit(1)] pub raw_response_extensions: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub enum ResponderId<'a> { #[explicit(1)] ByName(name::Name<'a>), #[explicit(2)] ByKey(&'a [u8]), } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct SingleResponse<'a> { pub cert_id: ocsp_req::CertID<'a>, pub cert_status: CertStatus, pub this_update: asn1::GeneralizedTime, #[explicit(0)] pub next_update: Option, #[explicit(1)] pub raw_single_extensions: Option>, } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub enum CertStatus { #[implicit(0)] Good(()), #[implicit(1)] Revoked(RevokedInfo), #[implicit(2)] Unknown(()), } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct RevokedInfo { pub revocation_time: asn1::GeneralizedTime, #[explicit(0)] pub revocation_reason: Option, } cryptography-43.0.0/src/rust/cryptography-x509/src/oid.rs010064400017510000177000000245501464676315000214710ustar 00000000000000// 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. // X.509v3 extensions pub const EXTENSION_REQUEST: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 14); pub const MS_EXTENSION_REQUEST: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 311, 2, 1, 14); pub const MS_CERTIFICATE_TEMPLATE: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 311, 21, 7); pub const PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 2); pub const PRECERT_POISON_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 3); pub const SIGNED_CERTIFICATE_TIMESTAMPS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 5); pub const AUTHORITY_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 1); pub const SUBJECT_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 11); pub const TLS_FEATURE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 24); pub const CP_CPS_URI_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 2, 1); pub const CP_USER_NOTICE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 2, 2); pub const NONCE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 2); pub const OCSP_NO_CHECK_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 5); pub const SUBJECT_DIRECTORY_ATTRIBUTES_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 9); pub const SUBJECT_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 14); pub const KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 15); pub const SUBJECT_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 17); pub const ISSUER_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 18); pub const BASIC_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 19); pub const CRL_NUMBER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 20); pub const CRL_REASON_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 21); pub const INVALIDITY_DATE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 24); pub const DELTA_CRL_INDICATOR_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 27); pub const ISSUING_DISTRIBUTION_POINT_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 28); pub const CERTIFICATE_ISSUER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 29); pub const NAME_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 30); pub const CRL_DISTRIBUTION_POINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 31); pub const CERTIFICATE_POLICIES_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 32); pub const AUTHORITY_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 35); pub const POLICY_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 36); pub const EXTENDED_KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37); pub const FRESHEST_CRL_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 46); pub const INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 54); pub const ACCEPTABLE_RESPONSES_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 4); // Public key identifiers pub const EC_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 2, 1); pub const EC_SECP192R1: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 3, 1, 1); pub const EC_SECP224R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 33); pub const EC_SECP256R1: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 3, 1, 7); pub const EC_SECP384R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 34); pub const EC_SECP521R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 35); pub const EC_SECP256K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 10); pub const EC_SECT233R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 27); pub const EC_SECT283R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 17); pub const EC_SECT409R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 37); pub const EC_SECT571R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 39); pub const EC_SECT163R2: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 15); pub const EC_SECT163K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 1); pub const EC_SECT233K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 26); pub const EC_SECT283K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 16); pub const EC_SECT409K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 36); pub const EC_SECT571K1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 132, 0, 38); pub const EC_BRAINPOOLP256R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 3, 3, 2, 8, 1, 1, 7); pub const EC_BRAINPOOLP384R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 3, 3, 2, 8, 1, 1, 11); pub const EC_BRAINPOOLP512R1: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 3, 3, 2, 8, 1, 1, 13); pub const RSA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 1); // Signing methods pub const ECDSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 1); pub const ECDSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 2); pub const ECDSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 3); pub const ECDSA_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 3, 4); pub const ECDSA_WITH_SHA3_224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 9); pub const ECDSA_WITH_SHA3_256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 10); pub const ECDSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 11); pub const ECDSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 12); pub const RSA_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 5); pub const RSA_WITH_SHA1_ALT_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 14, 3, 2, 29); pub const RSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 14); pub const RSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 11); pub const RSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 12); pub const RSA_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 13); pub const RSA_WITH_SHA3_224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 13); pub const RSA_WITH_SHA3_256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 14); pub const RSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 15); pub const RSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 16); pub const DSA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10040, 4, 1); pub const DSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 1); pub const DSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 2); pub const DSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 3); pub const DSA_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 4); pub const DH_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10046, 2, 1); pub const DH_KEY_AGREEMENT_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 3, 1); pub const X25519_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 110); pub const X448_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 111); pub const ED25519_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 112); pub const ED448_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 113); // Hashes pub const SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 14, 3, 2, 26); pub const SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 4); pub const SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 1); pub const SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 2); pub const SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 3); pub const SHA3_224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 224); pub const SHA3_256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 256); pub const SHA3_384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 384); pub const SHA3_512_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 512); pub const MGF1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 8); pub const RSASSA_PSS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 10); // Extended key usages pub const EKU_SERVER_AUTH_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 1); pub const EKU_CLIENT_AUTH_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 2); pub const EKU_CODE_SIGNING_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 3); pub const EKU_EMAIL_PROTECTION_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 4); pub const EKU_TIME_STAMPING_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 8); pub const EKU_OCSP_SIGNING_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 3, 9); pub const EKU_ANY_KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37, 0); pub const EKU_CERTIFICATE_TRANSPARENCY_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 4); pub const PBES2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 13); pub const PBKDF2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 12); pub const PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 1, 3); pub const AES_256_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 42); pub const AES_192_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 22); pub const AES_128_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 2); pub const HMAC_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 7); pub const HMAC_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 9); cryptography-43.0.0/src/rust/cryptography-x509/src/pkcs12.rs010064400017510000177000000047771464676315000220320ustar 00000000000000// 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. use crate::common::{AlgorithmIdentifier, Utf8StoredBMPString}; use crate::pkcs7; pub const CERT_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 3); pub const KEY_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 1); pub const SHROUDED_KEY_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 2); pub const X509_CERTIFICATE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 22, 1); pub const FRIENDLY_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 20); #[derive(asn1::Asn1Write)] pub struct Pfx<'a> { pub version: u8, pub auth_safe: pkcs7::ContentInfo<'a>, pub mac_data: Option>, } #[derive(asn1::Asn1Write)] pub struct MacData<'a> { pub mac: pkcs7::DigestInfo<'a>, pub salt: &'a [u8], #[default(1u64)] pub iterations: u64, } #[derive(asn1::Asn1Write)] pub struct SafeBag<'a> { pub _bag_id: asn1::DefinedByMarker, #[defined_by(_bag_id)] pub bag_value: asn1::Explicit, 0>, pub attributes: Option, Vec>>>, } #[derive(asn1::Asn1Write)] pub struct Attribute<'a> { pub _attr_id: asn1::DefinedByMarker, #[defined_by(_attr_id)] pub attr_values: AttributeSet<'a>, } #[derive(asn1::Asn1DefinedByWrite)] pub enum AttributeSet<'a> { #[defined_by(FRIENDLY_NAME_OID)] FriendlyName(asn1::SetOfWriter<'a, Utf8StoredBMPString<'a>, [Utf8StoredBMPString<'a>; 1]>), } #[derive(asn1::Asn1DefinedByWrite)] pub enum BagValue<'a> { #[defined_by(CERT_BAG_OID)] CertBag(CertBag<'a>), #[defined_by(KEY_BAG_OID)] KeyBag(asn1::Tlv<'a>), #[defined_by(SHROUDED_KEY_BAG_OID)] ShroudedKeyBag(EncryptedPrivateKeyInfo<'a>), } #[derive(asn1::Asn1Write)] pub struct CertBag<'a> { pub _cert_id: asn1::DefinedByMarker, #[defined_by(_cert_id)] pub cert_value: asn1::Explicit, 0>, } #[derive(asn1::Asn1DefinedByWrite)] pub enum CertType<'a> { #[defined_by(X509_CERTIFICATE_OID)] X509(asn1::OctetStringEncoded>), } #[derive(asn1::Asn1Write)] pub struct EncryptedPrivateKeyInfo<'a> { pub encryption_algorithm: AlgorithmIdentifier<'a>, pub encrypted_data: &'a [u8], } cryptography-43.0.0/src/rust/cryptography-x509/src/pkcs7.rs010064400017510000177000000064721464676315000217500ustar 00000000000000// 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. use crate::{certificate, common, csr, name}; pub const PKCS7_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 1); pub const PKCS7_SIGNED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 2); pub const PKCS7_ENVELOPED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 3); pub const PKCS7_ENCRYPTED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 6); #[derive(asn1::Asn1Write)] pub struct ContentInfo<'a> { pub _content_type: asn1::DefinedByMarker, #[defined_by(_content_type)] pub content: Content<'a>, } #[derive(asn1::Asn1DefinedByWrite)] pub enum Content<'a> { #[defined_by(PKCS7_ENVELOPED_DATA_OID)] EnvelopedData(asn1::Explicit>, 0>), #[defined_by(PKCS7_SIGNED_DATA_OID)] SignedData(asn1::Explicit>, 0>), #[defined_by(PKCS7_DATA_OID)] Data(Option>), #[defined_by(PKCS7_ENCRYPTED_DATA_OID)] EncryptedData(asn1::Explicit, 0>), } #[derive(asn1::Asn1Write)] pub struct SignedData<'a> { pub version: u8, pub digest_algorithms: asn1::SetOfWriter<'a, common::AlgorithmIdentifier<'a>>, pub content_info: ContentInfo<'a>, #[implicit(0)] pub certificates: Option>>, // We don't ever supply any of these, so for now, don't fill out the fields. #[implicit(1)] pub crls: Option>>, pub signer_infos: asn1::SetOfWriter<'a, SignerInfo<'a>>, } #[derive(asn1::Asn1Write)] pub struct SignerInfo<'a> { pub version: u8, pub issuer_and_serial_number: IssuerAndSerialNumber<'a>, pub digest_algorithm: common::AlgorithmIdentifier<'a>, #[implicit(0)] pub authenticated_attributes: Option>, pub digest_encryption_algorithm: common::AlgorithmIdentifier<'a>, pub encrypted_digest: &'a [u8], #[implicit(1)] pub unauthenticated_attributes: Option>, } #[derive(asn1::Asn1Write)] pub struct EnvelopedData<'a> { pub version: u8, pub recipient_infos: asn1::SetOfWriter<'a, RecipientInfo<'a>>, pub encrypted_content_info: EncryptedContentInfo<'a>, } #[derive(asn1::Asn1Write)] pub struct RecipientInfo<'a> { pub version: u8, pub issuer_and_serial_number: IssuerAndSerialNumber<'a>, pub key_encryption_algorithm: common::AlgorithmIdentifier<'a>, pub encrypted_key: &'a [u8], } #[derive(asn1::Asn1Write)] pub struct IssuerAndSerialNumber<'a> { pub issuer: name::Name<'a>, pub serial_number: asn1::BigInt<'a>, } #[derive(asn1::Asn1Write)] pub struct EncryptedData<'a> { pub version: u8, pub encrypted_content_info: EncryptedContentInfo<'a>, } #[derive(asn1::Asn1Write)] pub struct EncryptedContentInfo<'a> { pub content_type: asn1::ObjectIdentifier, pub content_encryption_algorithm: common::AlgorithmIdentifier<'a>, #[implicit(0)] pub encrypted_content: Option<&'a [u8]>, } #[derive(asn1::Asn1Write)] pub struct DigestInfo<'a> { pub algorithm: common::AlgorithmIdentifier<'a>, pub digest: &'a [u8], } cryptography-43.0.0/src/rust/src/asn1.rs010064400017510000177000000104501464676315000163140ustar 00000000000000// 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. use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo}; use pyo3::pybacked::PyBackedBytes; use pyo3::types::IntoPyDict; use pyo3::types::PyAnyMethods; use pyo3::ToPyObject; use crate::error::{CryptographyError, CryptographyResult}; use crate::types; pub(crate) fn py_oid_to_oid( py_oid: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { Ok(py_oid .downcast::()? .get() .oid .clone()) } pub(crate) fn oid_to_py_oid<'p>( py: pyo3::Python<'p>, oid: &asn1::ObjectIdentifier, ) -> pyo3::PyResult> { Ok(pyo3::Bound::new(py, crate::oid::ObjectIdentifier { oid: oid.clone() })?.into_any()) } #[pyo3::pyfunction] fn parse_spki_for_data<'p>( py: pyo3::Python<'p>, data: &[u8], ) -> Result, CryptographyError> { let spki = asn1::parse_single::>(data)?; if spki.subject_public_key.padding_bits() != 0 { return Err(pyo3::exceptions::PyValueError::new_err("Invalid public key encoding").into()); } Ok(pyo3::types::PyBytes::new_bound( py, spki.subject_public_key.as_bytes(), )) } pub(crate) fn big_byte_slice_to_py_int<'p>( py: pyo3::Python<'p>, v: &'_ [u8], ) -> pyo3::PyResult> { let int_type = py.get_type_bound::(); let kwargs = [("signed", true)].into_py_dict_bound(py); int_type.call_method(pyo3::intern!(py, "from_bytes"), (v, "big"), Some(&kwargs)) } #[pyo3::pyfunction] fn decode_dss_signature( py: pyo3::Python<'_>, data: &[u8], ) -> Result { let sig = asn1::parse_single::>(data)?; Ok(( big_byte_slice_to_py_int(py, sig.r.as_bytes())?, big_byte_slice_to_py_int(py, sig.s.as_bytes())?, ) .to_object(py)) } pub(crate) fn py_uint_to_big_endian_bytes<'p>( py: pyo3::Python<'p>, v: pyo3::Bound<'p, pyo3::types::PyLong>, ) -> pyo3::PyResult { if v.lt(0)? { return Err(pyo3::exceptions::PyValueError::new_err( "Negative integers are not supported", )); } // Round the length up so that we prefix an extra \x00. This ensures that // integers that'd have the high bit set in their first octet are not // encoded as negative in DER. let n = v .call_method0(pyo3::intern!(py, "bit_length"))? .extract::()? / 8 + 1; v.call_method1(pyo3::intern!(py, "to_bytes"), (n, "big"))? .extract() } pub(crate) fn encode_der_data<'p>( py: pyo3::Python<'p>, pem_tag: String, data: Vec, encoding: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { if encoding.is(&types::ENCODING_DER.get(py)?) { Ok(pyo3::types::PyBytes::new_bound(py, &data)) } else if encoding.is(&types::ENCODING_PEM.get(py)?) { Ok(pyo3::types::PyBytes::new_bound( py, &pem::encode_config( &pem::Pem::new(pem_tag, data), pem::EncodeConfig::new().set_line_ending(pem::LineEnding::LF), ) .into_bytes(), )) } else { Err( pyo3::exceptions::PyTypeError::new_err("encoding must be Encoding.DER or Encoding.PEM") .into(), ) } } #[pyo3::pyfunction] fn encode_dss_signature<'p>( py: pyo3::Python<'p>, r: pyo3::Bound<'_, pyo3::types::PyLong>, s: pyo3::Bound<'_, pyo3::types::PyLong>, ) -> CryptographyResult> { let r_bytes = py_uint_to_big_endian_bytes(py, r)?; let s_bytes = py_uint_to_big_endian_bytes(py, s)?; let sig = DssSignature { r: asn1::BigUint::new(&r_bytes).unwrap(), s: asn1::BigUint::new(&s_bytes).unwrap(), }; let result = asn1::write_single(&sig)?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } #[pyo3::pymodule] #[pyo3(name = "asn1")] pub(crate) mod asn1_mod { #[pymodule_export] use super::{decode_dss_signature, encode_dss_signature, parse_spki_for_data}; } cryptography-43.0.0/src/rust/src/backend/aead.rs010064400017510000177000001124101464676315000177320ustar 00000000000000// 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. use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; use pyo3::types::{PyAnyMethods, PyListMethods}; fn check_length(data: &[u8]) -> CryptographyResult<()> { if data.len() > (i32::MAX as usize) { // This is OverflowError to match what cffi would raise return Err(CryptographyError::from( pyo3::exceptions::PyOverflowError::new_err( "Data or associated data too long. Max 2**31 - 1 bytes", ), )); } Ok(()) } enum Aad<'a> { Single(CffiBuf<'a>), List(pyo3::Bound<'a, pyo3::types::PyList>), } struct EvpCipherAead { base_encryption_ctx: openssl::cipher_ctx::CipherCtx, base_decryption_ctx: openssl::cipher_ctx::CipherCtx, tag_len: usize, tag_first: bool, } impl EvpCipherAead { fn new( cipher: &openssl::cipher::CipherRef, key: &[u8], tag_len: usize, tag_first: bool, ) -> CryptographyResult { let mut base_encryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; base_encryption_ctx.encrypt_init(Some(cipher), Some(key), None)?; let mut base_decryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; base_decryption_ctx.decrypt_init(Some(cipher), Some(key), None)?; Ok(EvpCipherAead { base_encryption_ctx, base_decryption_ctx, tag_len, tag_first, }) } fn process_aad( ctx: &mut openssl::cipher_ctx::CipherCtx, aad: Option>, ) -> CryptographyResult<()> { match aad { Some(Aad::Single(ad)) => { check_length(ad.as_bytes())?; ctx.cipher_update(ad.as_bytes(), None)?; } Some(Aad::List(ads)) => { for ad in ads.iter() { let ad = ad.extract::>()?; check_length(ad.as_bytes())?; ctx.cipher_update(ad.as_bytes(), None)?; } } None => {} } Ok(()) } fn process_data( ctx: &mut openssl::cipher_ctx::CipherCtx, data: &[u8], out: &mut [u8], is_ccm: bool, ) -> CryptographyResult<()> { let bs = ctx.block_size(); // For AEADs that operate as if they are streaming there's an easy // path. For AEADs that are more like block ciphers (notably, OCB), // this is a bit more complicated. if bs == 1 { let n = ctx.cipher_update(data, Some(out))?; assert_eq!(n, data.len()); if !is_ccm { let mut final_block = [0]; let n = ctx.cipher_final(&mut final_block)?; assert_eq!(n, 0); } } else { // Our algorithm here is: split the data into the full chunks, and // the remaining partial chunk. Feed the full chunks into OpenSSL // and let it write the results to `out`. Then feed the trailer // in, allowing it to write the results to a buffer on the // stack -- this never writes anything. Finally, finalize the AEAD // and let it write the results to the stack buffer, then copy // from the stack buffer over to `out`. The indirection via the // stack buffer is required because OpenSSL uses it as scratch // space, and `out` wouldn't be long enough. let (initial, trailer) = data.split_at((data.len() / bs) * bs); let n = // SAFETY: `initial.len()` is a precise multiple of the block // size, which means the space required in the output is // exactly `initial.len()`. unsafe { ctx.cipher_update_unchecked(initial, Some(&mut out[..initial.len()]))? }; assert_eq!(n, initial.len()); assert!(bs <= 16); let mut buf = [0; 32]; let n = ctx.cipher_update(trailer, Some(&mut buf))?; assert_eq!(n, 0); let n = ctx.cipher_final(&mut buf)?; assert_eq!(n, trailer.len()); out[initial.len()..].copy_from_slice(&buf[..n]); } Ok(()) } fn encrypt<'p>( &self, py: pyo3::Python<'p>, plaintext: &[u8], aad: Option>, nonce: Option<&[u8]>, ) -> CryptographyResult> { let mut ctx = openssl::cipher_ctx::CipherCtx::new()?; ctx.copy(&self.base_encryption_ctx)?; Self::encrypt_with_context( py, ctx, plaintext, aad, nonce, self.tag_len, self.tag_first, false, ) } #[allow(clippy::too_many_arguments)] fn encrypt_with_context<'p>( py: pyo3::Python<'p>, mut ctx: openssl::cipher_ctx::CipherCtx, plaintext: &[u8], aad: Option>, nonce: Option<&[u8]>, tag_len: usize, tag_first: bool, is_ccm: bool, ) -> CryptographyResult> { check_length(plaintext)?; if !is_ccm { if let Some(nonce) = nonce { ctx.set_iv_length(nonce.len())?; } ctx.encrypt_init(None, None, nonce)?; } if is_ccm { ctx.set_data_len(plaintext.len())?; } Self::process_aad(&mut ctx, aad)?; Ok(pyo3::types::PyBytes::new_bound_with( py, plaintext.len() + tag_len, |b| { let ciphertext; let tag; if tag_first { (tag, ciphertext) = b.split_at_mut(tag_len); } else { (ciphertext, tag) = b.split_at_mut(plaintext.len()); } Self::process_data(&mut ctx, plaintext, ciphertext, is_ccm)?; ctx.tag(tag).map_err(CryptographyError::from)?; Ok(()) }, )?) } fn decrypt<'p>( &self, py: pyo3::Python<'p>, ciphertext: &[u8], aad: Option>, nonce: Option<&[u8]>, ) -> CryptographyResult> { let mut ctx = openssl::cipher_ctx::CipherCtx::new()?; ctx.copy(&self.base_decryption_ctx)?; Self::decrypt_with_context( py, ctx, ciphertext, aad, nonce, self.tag_len, self.tag_first, false, ) } #[allow(clippy::too_many_arguments)] fn decrypt_with_context<'p>( py: pyo3::Python<'p>, mut ctx: openssl::cipher_ctx::CipherCtx, ciphertext: &[u8], aad: Option>, nonce: Option<&[u8]>, tag_len: usize, tag_first: bool, is_ccm: bool, ) -> CryptographyResult> { if ciphertext.len() < tag_len { return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); } let tag; let ciphertext_data; if tag_first { // RFC 5297 defines the output as IV || C, where the tag we generate // is the "IV" and C is the ciphertext. This is the opposite of our // other AEADs, which are Ciphertext || Tag. (tag, ciphertext_data) = ciphertext.split_at(tag_len); } else { (ciphertext_data, tag) = ciphertext.split_at(ciphertext.len() - tag_len); } if !is_ccm { if let Some(nonce) = nonce { ctx.set_iv_length(nonce.len())?; } ctx.decrypt_init(None, None, nonce)?; ctx.set_tag(tag)?; } if is_ccm { ctx.set_data_len(ciphertext_data.len())?; } Self::process_aad(&mut ctx, aad)?; Ok(pyo3::types::PyBytes::new_bound_with( py, ciphertext_data.len(), |b| { Self::process_data(&mut ctx, ciphertext_data, b, is_ccm) .map_err(|_| exceptions::InvalidTag::new_err(()))?; Ok(()) }, )?) } } struct LazyEvpCipherAead { cipher: &'static openssl::cipher::CipherRef, key: pyo3::Py, tag_len: usize, tag_first: bool, is_ccm: bool, } impl LazyEvpCipherAead { #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn new( cipher: &'static openssl::cipher::CipherRef, key: pyo3::Py, tag_len: usize, tag_first: bool, is_ccm: bool, ) -> LazyEvpCipherAead { LazyEvpCipherAead { cipher, key, tag_len, tag_first, is_ccm, } } fn encrypt<'p>( &self, py: pyo3::Python<'p>, plaintext: &[u8], aad: Option>, nonce: Option<&[u8]>, ) -> CryptographyResult> { let key_buf = self.key.bind(py).extract::>()?; let mut encryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; if self.is_ccm { encryption_ctx.encrypt_init(Some(self.cipher), None, None)?; encryption_ctx.set_iv_length(nonce.as_ref().unwrap().len())?; encryption_ctx.set_tag_length(self.tag_len)?; encryption_ctx.encrypt_init(None, Some(key_buf.as_bytes()), nonce)?; } else { encryption_ctx.encrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; } EvpCipherAead::encrypt_with_context( py, encryption_ctx, plaintext, aad, nonce, self.tag_len, self.tag_first, self.is_ccm, ) } fn decrypt<'p>( &self, py: pyo3::Python<'p>, ciphertext: &[u8], aad: Option>, nonce: Option<&[u8]>, ) -> CryptographyResult> { let key_buf = self.key.bind(py).extract::>()?; let mut decryption_ctx = openssl::cipher_ctx::CipherCtx::new()?; if self.is_ccm { decryption_ctx.decrypt_init(Some(self.cipher), None, None)?; decryption_ctx.set_iv_length(nonce.as_ref().unwrap().len())?; if ciphertext.len() < self.tag_len { return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); } let (_, tag) = ciphertext.split_at(ciphertext.len() - self.tag_len); decryption_ctx.set_tag(tag)?; decryption_ctx.decrypt_init(None, Some(key_buf.as_bytes()), nonce)?; } else { decryption_ctx.decrypt_init(Some(self.cipher), Some(key_buf.as_bytes()), None)?; } EvpCipherAead::decrypt_with_context( py, decryption_ctx, ciphertext, aad, nonce, self.tag_len, self.tag_first, self.is_ccm, ) } } #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] struct EvpAead { ctx: cryptography_openssl::aead::AeadCtx, tag_len: usize, } #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] impl EvpAead { fn new( algorithm: cryptography_openssl::aead::AeadType, key: &[u8], tag_len: usize, ) -> CryptographyResult { Ok(EvpAead { ctx: cryptography_openssl::aead::AeadCtx::new(algorithm, key)?, tag_len, }) } fn encrypt<'p>( &self, py: pyo3::Python<'p>, plaintext: &[u8], aad: Option>, nonce: Option<&[u8]>, ) -> CryptographyResult> { check_length(plaintext)?; let ad = if let Some(Aad::Single(ad)) = &aad { check_length(ad.as_bytes())?; ad.as_bytes() } else { assert!(aad.is_none()); b"" }; Ok(pyo3::types::PyBytes::new_bound_with( py, plaintext.len() + self.tag_len, |b| { self.ctx .encrypt(plaintext, nonce.unwrap_or(b""), ad, b) .map_err(CryptographyError::from)?; Ok(()) }, )?) } fn decrypt<'p>( &self, py: pyo3::Python<'p>, ciphertext: &[u8], aad: Option>, nonce: Option<&[u8]>, ) -> CryptographyResult> { if ciphertext.len() < self.tag_len { return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); } let ad = if let Some(Aad::Single(ad)) = &aad { check_length(ad.as_bytes())?; ad.as_bytes() } else { assert!(aad.is_none()); b"" }; Ok(pyo3::types::PyBytes::new_bound_with( py, ciphertext.len() - self.tag_len, |b| { self.ctx .decrypt(ciphertext, nonce.unwrap_or(b""), ad, b) .map_err(|_| exceptions::InvalidTag::new_err(()))?; Ok(()) }, )?) } } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead")] struct ChaCha20Poly1305 { #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] ctx: EvpAead, #[cfg(any( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_LIBRESSL, all( not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), not(CRYPTOGRAPHY_IS_BORINGSSL) ) ))] ctx: EvpCipherAead, #[cfg(not(any( CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), CRYPTOGRAPHY_OPENSSL_320_OR_GREATER )))] ctx: LazyEvpCipherAead, } #[pyo3::pymethods] impl ChaCha20Poly1305 { #[new] fn new(py: pyo3::Python<'_>, key: pyo3::Py) -> CryptographyResult { let key_buf = key.extract::>(py)?; if key_buf.as_bytes().len() != 32 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("ChaCha20Poly1305 key must be 32 bytes."), )); } cfg_if::cfg_if! { if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] { Ok(ChaCha20Poly1305 { ctx: EvpAead::new( cryptography_openssl::aead::AeadType::ChaCha20Poly1305, key_buf.as_bytes(), 16, )?, }) } else if #[cfg(any( CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER )))] { if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "ChaCha20Poly1305 is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )); } Ok(ChaCha20Poly1305 { ctx: EvpCipherAead::new( openssl::cipher::Cipher::chacha20_poly1305(), key_buf.as_bytes(), 16, false, )?, }) } else { if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "ChaCha20Poly1305 is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )); } Ok(ChaCha20Poly1305{ ctx: LazyEvpCipherAead::new( openssl::cipher::Cipher::chacha20_poly1305(), key, 16, false, false, ) }) } } } #[staticmethod] fn generate_key(py: pyo3::Python<'_>) -> CryptographyResult> { Ok(types::OS_URANDOM.get(py)?.call1((32,))?) } #[pyo3(signature = (nonce, data, associated_data))] fn encrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() != 12 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes"), )); } self.ctx .encrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) } #[pyo3(signature = (nonce, data, associated_data))] fn decrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() != 12 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes"), )); } self.ctx .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) } } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESGCM" )] struct AesGcm { #[cfg(any( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), ))] ctx: EvpCipherAead, #[cfg(not(any( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), )))] ctx: LazyEvpCipherAead, } #[pyo3::pymethods] impl AesGcm { #[new] fn new(py: pyo3::Python<'_>, key: pyo3::Py) -> CryptographyResult { let key_buf = key.extract::>(py)?; let cipher = match key_buf.as_bytes().len() { 16 => openssl::cipher::Cipher::aes_128_gcm(), 24 => openssl::cipher::Cipher::aes_192_gcm(), 32 => openssl::cipher::Cipher::aes_256_gcm(), _ => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "AESGCM key must be 128, 192, or 256 bits.", ), )) } }; cfg_if::cfg_if! { if #[cfg(any( CRYPTOGRAPHY_OPENSSL_320_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL, not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, )))] { Ok(AesGcm { ctx: EvpCipherAead::new(cipher, key_buf.as_bytes(), 16, false)?, }) } else { Ok(AesGcm { ctx: LazyEvpCipherAead::new(cipher, key, 16, false, false), }) } } } #[staticmethod] fn generate_key( py: pyo3::Python<'_>, bit_length: usize, ) -> CryptographyResult> { if bit_length != 128 && bit_length != 192 && bit_length != 256 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("bit_length must be 128, 192, or 256"), )); } Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) } #[pyo3(signature = (nonce, data, associated_data))] fn encrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() < 8 || nonce_bytes.len() > 128 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be between 8 and 128 bytes"), )); } self.ctx .encrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) } #[pyo3(signature = (nonce, data, associated_data))] fn decrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() < 8 || nonce_bytes.len() > 128 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be between 8 and 128 bytes"), )); } self.ctx .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) } } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESCCM" )] struct AesCcm { ctx: LazyEvpCipherAead, } #[pyo3::pymethods] impl AesCcm { #[new] #[pyo3(signature = (key, tag_length=None))] fn new( py: pyo3::Python<'_>, key: pyo3::Py, tag_length: Option, ) -> CryptographyResult { cfg_if::cfg_if! { if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] { let _ = py; let _ = key; let _ = tag_length; Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "AES-CCM is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )) } else { let key_buf = key.extract::>(py)?; let cipher = match key_buf.as_bytes().len() { 16 => openssl::cipher::Cipher::aes_128_ccm(), 24 => openssl::cipher::Cipher::aes_192_ccm(), 32 => openssl::cipher::Cipher::aes_256_ccm(), _ => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "AESCCM key must be 128, 192, or 256 bits.", ), )) } }; let tag_length = tag_length.unwrap_or(16); if ![4, 6, 8, 10, 12, 14, 16].contains(&tag_length) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid tag_length"), )); } Ok(AesCcm { ctx: LazyEvpCipherAead::new(cipher, key, tag_length, false, true), }) } } } #[staticmethod] fn generate_key( py: pyo3::Python<'_>, bit_length: usize, ) -> CryptographyResult> { if bit_length != 128 && bit_length != 192 && bit_length != 256 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("bit_length must be 128, 192, or 256"), )); } Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) } #[pyo3(signature = (nonce, data, associated_data))] fn encrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() < 7 || nonce_bytes.len() > 13 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be between 7 and 13 bytes"), )); } check_length(data_bytes)?; // For information about computing this, see // https://tools.ietf.org/html/rfc3610#section-2.1 let l_val = 15 - nonce_bytes.len(); let max_length = 1usize.checked_shl(8 * l_val as u32); // If `max_length` overflowed, then it's not possible for data to be // longer than it. if max_length.map(|v| v < data_bytes.len()).unwrap_or(false) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Data too long for nonce"), )); } self.ctx.encrypt(py, data_bytes, aad, Some(nonce_bytes)) } #[pyo3(signature = (nonce, data, associated_data))] fn decrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() < 7 || nonce_bytes.len() > 13 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be between 7 and 13 bytes"), )); } // For information about computing this, see // https://tools.ietf.org/html/rfc3610#section-2.1 let l_val = 15 - nonce_bytes.len(); let max_length = 1usize.checked_shl(8 * l_val as u32); // If `max_length` overflowed, then it's not possible for data to be // longer than it. if max_length.map(|v| v < data_bytes.len()).unwrap_or(false) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Data too long for nonce"), )); } self.ctx.decrypt(py, data_bytes, aad, Some(nonce_bytes)) } } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESSIV" )] struct AesSiv { ctx: EvpCipherAead, } #[pyo3::pymethods] impl AesSiv { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { let cipher_name = match key.as_bytes().len() { 32 => "aes-128-siv", 48 => "aes-192-siv", 64 => "aes-256-siv", _ => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "AESSIV key must be 256, 384, or 512 bits.", ), )) } }; cfg_if::cfg_if! { if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] { if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "AES-SIV is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )); } let cipher = openssl::cipher::Cipher::fetch(None, cipher_name, None)?; Ok(AesSiv { ctx: EvpCipherAead::new(&cipher, key.as_bytes(), 16, true)?, }) } else { _ = cipher_name; Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "AES-SIV is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )) } } } #[staticmethod] fn generate_key( py: pyo3::Python<'_>, bit_length: usize, ) -> CryptographyResult> { if bit_length != 256 && bit_length != 384 && bit_length != 512 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("bit_length must be 256, 384, or 512"), )); } Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) } #[pyo3(signature = (data, associated_data))] fn encrypt<'p>( &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::List); if data_bytes.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("data must not be zero length"), )); }; self.ctx.encrypt(py, data_bytes, aad, None) } #[pyo3(signature = (data, associated_data))] fn decrypt<'p>( &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let aad = associated_data.map(Aad::List); self.ctx.decrypt(py, data.as_bytes(), aad, None) } } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESOCB3" )] struct AesOcb3 { ctx: EvpCipherAead, } #[pyo3::pymethods] impl AesOcb3 { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { cfg_if::cfg_if! { if #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] { _ = key; Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "AES-OCB3 is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )) } else { if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "AES-OCB3 is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )); } let cipher = match key.as_bytes().len() { 16 => openssl::cipher::Cipher::aes_128_ocb(), 24 => openssl::cipher::Cipher::aes_192_ocb(), 32 => openssl::cipher::Cipher::aes_256_ocb(), _ => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "AESOCB3 key must be 128, 192, or 256 bits.", ), )) } }; Ok(AesOcb3 { ctx: EvpCipherAead::new(cipher, key.as_bytes(), 16, false)?, }) } } } #[staticmethod] fn generate_key( py: pyo3::Python<'_>, bit_length: usize, ) -> CryptographyResult> { if bit_length != 128 && bit_length != 192 && bit_length != 256 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("bit_length must be 128, 192, or 256"), )); } Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) } #[pyo3(signature = (nonce, data, associated_data))] fn encrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() < 12 || nonce_bytes.len() > 15 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be between 12 and 15 bytes"), )); } self.ctx .encrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) } #[pyo3(signature = (nonce, data, associated_data))] fn decrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() < 12 || nonce_bytes.len() > 15 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be between 12 and 15 bytes"), )); } self.ctx .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) } } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.aead", name = "AESGCMSIV" )] struct AesGcmSiv { ctx: EvpCipherAead, } #[pyo3::pymethods] impl AesGcmSiv { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { let cipher_name = match key.as_bytes().len() { 16 => "aes-128-gcm-siv", 24 => "aes-192-gcm-siv", 32 => "aes-256-gcm-siv", _ => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "AES-GCM-SIV key must be 128, 192 or 256 bits.", ), )) } }; cfg_if::cfg_if! { if #[cfg(not(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER))] { let _ = cipher_name; Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "AES-GCM-SIV is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )) } else { if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "AES-GCM-SIV is not supported by this version of OpenSSL", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )); } let cipher = openssl::cipher::Cipher::fetch(None, cipher_name, None)?; Ok(AesGcmSiv { ctx: EvpCipherAead::new(&cipher, key.as_bytes(), 16, false)?, }) } } } #[staticmethod] fn generate_key( py: pyo3::Python<'_>, bit_length: usize, ) -> CryptographyResult> { if bit_length != 128 && bit_length != 192 && bit_length != 256 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("bit_length must be 128, 192, or 256"), )); } Ok(types::OS_URANDOM.get(py)?.call1((bit_length / 8,))?) } #[pyo3(signature = (nonce, data, associated_data))] fn encrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let data_bytes = data.as_bytes(); let aad = associated_data.map(Aad::Single); if data_bytes.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("data must not be zero length"), )); }; if nonce_bytes.len() != 12 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes long"), )); } self.ctx.encrypt(py, data_bytes, aad, Some(nonce_bytes)) } #[pyo3(signature = (nonce, data, associated_data))] fn decrypt<'p>( &self, py: pyo3::Python<'p>, nonce: CffiBuf<'_>, data: CffiBuf<'_>, associated_data: Option>, ) -> CryptographyResult> { let nonce_bytes = nonce.as_bytes(); let aad = associated_data.map(Aad::Single); if nonce_bytes.len() != 12 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Nonce must be 12 bytes long"), )); } self.ctx .decrypt(py, data.as_bytes(), aad, Some(nonce_bytes)) } } #[pyo3::pymodule] pub(crate) mod aead { #[pymodule_export] use super::{AesCcm, AesGcm, AesGcmSiv, AesOcb3, AesSiv, ChaCha20Poly1305}; } cryptography-43.0.0/src/rust/src/backend/cipher_registry.rs010064400017510000177000000273461464676315000222570ustar 00000000000000// 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. use std::collections::HashMap; use openssl::cipher::Cipher; use pyo3::types::PyAnyMethods; use crate::error::CryptographyResult; use crate::types; struct RegistryKey { algorithm: pyo3::PyObject, mode: pyo3::PyObject, key_size: Option, algorithm_hash: isize, mode_hash: isize, } impl RegistryKey { fn new( py: pyo3::Python<'_>, algorithm: pyo3::PyObject, mode: pyo3::PyObject, key_size: Option, ) -> CryptographyResult { Ok(Self { algorithm: algorithm.clone_ref(py), mode: mode.clone_ref(py), key_size, algorithm_hash: algorithm.bind(py).hash()?, mode_hash: mode.bind(py).hash()?, }) } } impl PartialEq for RegistryKey { fn eq(&self, other: &RegistryKey) -> bool { self.algorithm.is(&other.algorithm) && self.mode.is(&other.mode) && (self.key_size == other.key_size || self.key_size.is_none() || other.key_size.is_none()) } } impl Eq for RegistryKey {} impl std::hash::Hash for RegistryKey { fn hash(&self, state: &mut H) { self.algorithm_hash.hash(state); self.mode_hash.hash(state); } } enum RegistryCipher { Ref(&'static openssl::cipher::CipherRef), Owned(Cipher), } impl From<&'static openssl::cipher::CipherRef> for RegistryCipher { fn from(c: &'static openssl::cipher::CipherRef) -> RegistryCipher { RegistryCipher::Ref(c) } } impl From for RegistryCipher { fn from(c: Cipher) -> RegistryCipher { RegistryCipher::Owned(c) } } struct RegistryBuilder<'p> { py: pyo3::Python<'p>, m: HashMap, } impl<'p> RegistryBuilder<'p> { fn new(py: pyo3::Python<'p>) -> Self { RegistryBuilder { py, m: HashMap::new(), } } fn add( &mut self, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, mode: &pyo3::Bound<'_, pyo3::PyAny>, key_size: Option, cipher: impl Into, ) -> CryptographyResult<()> { self.m.insert( RegistryKey::new( self.py, algorithm.clone().unbind(), mode.clone().unbind(), key_size, )?, cipher.into(), ); Ok(()) } fn build(self) -> HashMap { self.m } } fn get_cipher_registry( py: pyo3::Python<'_>, ) -> CryptographyResult<&HashMap> { static REGISTRY: pyo3::sync::GILOnceCell> = pyo3::sync::GILOnceCell::new(); REGISTRY.get_or_try_init(py, || { let mut m = RegistryBuilder::new(py); let aes = types::AES.get(py)?; let aes128 = types::AES128.get(py)?; let aes256 = types::AES256.get(py)?; let triple_des = types::TRIPLE_DES.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] let camellia = types::CAMELLIA.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] let blowfish = types::BLOWFISH.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] let cast5 = types::CAST5.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] let idea = types::IDEA.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] let sm4 = types::SM4.get(py)?; #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] let seed = types::SEED.get(py)?; let arc4 = types::ARC4.get(py)?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] let chacha20 = types::CHACHA20.get(py)?; let rc2 = types::RC2.get(py)?; let cbc = types::CBC.get(py)?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] let cfb = types::CFB.get(py)?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] let cfb8 = types::CFB8.get(py)?; let ofb = types::OFB.get(py)?; let ecb = types::ECB.get(py)?; let ctr = types::CTR.get(py)?; let gcm = types::GCM.get(py)?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] let xts = types::XTS.get(py)?; let none = py.None(); let none_type = none.bind(py).get_type(); m.add(&aes, &cbc, Some(128), Cipher::aes_128_cbc())?; m.add(&aes, &cbc, Some(192), Cipher::aes_192_cbc())?; m.add(&aes, &cbc, Some(256), Cipher::aes_256_cbc())?; m.add(&aes, &ofb, Some(128), Cipher::aes_128_ofb())?; m.add(&aes, &ofb, Some(192), Cipher::aes_192_ofb())?; m.add(&aes, &ofb, Some(256), Cipher::aes_256_ofb())?; m.add(&aes, &gcm, Some(128), Cipher::aes_128_gcm())?; m.add(&aes, &gcm, Some(192), Cipher::aes_192_gcm())?; m.add(&aes, &gcm, Some(256), Cipher::aes_256_gcm())?; m.add(&aes, &ctr, Some(128), Cipher::aes_128_ctr())?; m.add(&aes, &ctr, Some(192), Cipher::aes_192_ctr())?; m.add(&aes, &ctr, Some(256), Cipher::aes_256_ctr())?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { m.add(&aes, &cfb8, Some(128), Cipher::aes_128_cfb8())?; m.add(&aes, &cfb8, Some(192), Cipher::aes_192_cfb8())?; m.add(&aes, &cfb8, Some(256), Cipher::aes_256_cfb8())?; m.add(&aes, &cfb, Some(128), Cipher::aes_128_cfb128())?; m.add(&aes, &cfb, Some(192), Cipher::aes_192_cfb128())?; m.add(&aes, &cfb, Some(256), Cipher::aes_256_cfb128())?; } m.add(&aes, &ecb, Some(128), Cipher::aes_128_ecb())?; m.add(&aes, &ecb, Some(192), Cipher::aes_192_ecb())?; m.add(&aes, &ecb, Some(256), Cipher::aes_256_ecb())?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { m.add(&aes, &xts, Some(256), Cipher::aes_128_xts())?; m.add(&aes, &xts, Some(512), Cipher::aes_256_xts())?; } m.add(&aes128, &cbc, Some(128), Cipher::aes_128_cbc())?; m.add(&aes256, &cbc, Some(256), Cipher::aes_256_cbc())?; m.add(&aes128, &ofb, Some(128), Cipher::aes_128_ofb())?; m.add(&aes256, &ofb, Some(256), Cipher::aes_256_ofb())?; m.add(&aes128, &gcm, Some(128), Cipher::aes_128_gcm())?; m.add(&aes256, &gcm, Some(256), Cipher::aes_256_gcm())?; m.add(&aes128, &ctr, Some(128), Cipher::aes_128_ctr())?; m.add(&aes256, &ctr, Some(256), Cipher::aes_256_ctr())?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { m.add(&aes128, &cfb8, Some(128), Cipher::aes_128_cfb8())?; m.add(&aes256, &cfb8, Some(256), Cipher::aes_256_cfb8())?; m.add(&aes128, &cfb, Some(128), Cipher::aes_128_cfb128())?; m.add(&aes256, &cfb, Some(256), Cipher::aes_256_cfb128())?; } m.add(&aes128, &ecb, Some(128), Cipher::aes_128_ecb())?; m.add(&aes256, &ecb, Some(256), Cipher::aes_256_ecb())?; m.add(&triple_des, &cbc, Some(192), Cipher::des_ede3_cbc())?; m.add(&triple_des, &ecb, Some(192), Cipher::des_ede3_ecb())?; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { m.add(&triple_des, &cfb8, Some(192), Cipher::des_ede3_cfb8())?; m.add(&triple_des, &cfb, Some(192), Cipher::des_ede3_cfb64())?; m.add(&triple_des, &ofb, Some(192), Cipher::des_ede3_ofb())?; } #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] { m.add(&camellia, &cbc, Some(128), Cipher::camellia128_cbc())?; m.add(&camellia, &cbc, Some(192), Cipher::camellia192_cbc())?; m.add(&camellia, &cbc, Some(256), Cipher::camellia256_cbc())?; m.add(&camellia, &ecb, Some(128), Cipher::camellia128_ecb())?; m.add(&camellia, &ecb, Some(192), Cipher::camellia192_ecb())?; m.add(&camellia, &ecb, Some(256), Cipher::camellia256_ecb())?; m.add(&camellia, &ofb, Some(128), Cipher::camellia128_ofb())?; m.add(&camellia, &ofb, Some(192), Cipher::camellia192_ofb())?; m.add(&camellia, &ofb, Some(256), Cipher::camellia256_ofb())?; m.add(&camellia, &cfb, Some(128), Cipher::camellia128_cfb128())?; m.add(&camellia, &cfb, Some(192), Cipher::camellia192_cfb128())?; m.add(&camellia, &cfb, Some(256), Cipher::camellia256_cfb128())?; } #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] { m.add(&sm4, &cbc, Some(128), Cipher::sm4_cbc())?; m.add(&sm4, &ctr, Some(128), Cipher::sm4_ctr())?; m.add(&sm4, &cfb, Some(128), Cipher::sm4_cfb128())?; m.add(&sm4, &ofb, Some(128), Cipher::sm4_ofb())?; m.add(&sm4, &ecb, Some(128), Cipher::sm4_ecb())?; #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] if let Ok(c) = Cipher::fetch(None, "sm4-gcm", None) { m.add(&sm4, &gcm, Some(128), c)?; } } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] m.add(&chacha20, none_type.as_any(), None, Cipher::chacha20())?; // Don't register legacy ciphers if they're unavailable. In theory // this shouldn't be necessary but OpenSSL 3 will return an EVP_CIPHER // even when the cipher is unavailable. if cfg!(not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)) || types::LEGACY_PROVIDER_LOADED.get(py)?.is_truthy()? { #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] { m.add(&blowfish, &cbc, None, Cipher::bf_cbc())?; m.add(&blowfish, &cfb, None, Cipher::bf_cfb64())?; m.add(&blowfish, &ofb, None, Cipher::bf_ofb())?; m.add(&blowfish, &ecb, None, Cipher::bf_ecb())?; } #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] { m.add(&seed, &cbc, Some(128), Cipher::seed_cbc())?; m.add(&seed, &cfb, Some(128), Cipher::seed_cfb128())?; m.add(&seed, &ofb, Some(128), Cipher::seed_ofb())?; m.add(&seed, &ecb, Some(128), Cipher::seed_ecb())?; } #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] { m.add(&cast5, &cbc, None, Cipher::cast5_cbc())?; m.add(&cast5, &ecb, None, Cipher::cast5_ecb())?; m.add(&cast5, &ofb, None, Cipher::cast5_ofb())?; m.add(&cast5, &cfb, None, Cipher::cast5_cfb64())?; } #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] { m.add(&idea, &cbc, Some(128), Cipher::idea_cbc())?; m.add(&idea, &ecb, Some(128), Cipher::idea_ecb())?; m.add(&idea, &ofb, Some(128), Cipher::idea_ofb())?; m.add(&idea, &cfb, Some(128), Cipher::idea_cfb64())?; } m.add(&arc4, none_type.as_any(), None, Cipher::rc4())?; if let Some(rc2_cbc) = Cipher::from_nid(openssl::nid::Nid::RC2_CBC) { m.add(&rc2, &cbc, Some(128), rc2_cbc)?; } } Ok(m.build()) }) } pub(crate) fn get_cipher<'py>( py: pyo3::Python<'py>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, mode_cls: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let registry = get_cipher_registry(py)?; let key_size = algorithm .getattr(pyo3::intern!(py, "key_size"))? .extract()?; let key = RegistryKey::new(py, algorithm.get_type().into(), mode_cls.into(), key_size)?; match registry.get(&key) { Some(RegistryCipher::Ref(c)) => Ok(Some(c)), Some(RegistryCipher::Owned(c)) => Ok(Some(c)), None => Ok(None), } } cryptography-43.0.0/src/rust/src/backend/ciphers.rs010064400017510000177000000500561464676315000205040ustar 00000000000000// 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. use crate::backend::cipher_registry; use crate::buf::{CffiBuf, CffiMutBuf}; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; use crate::types; use pyo3::types::PyAnyMethods; use pyo3::IntoPy; pub(crate) struct CipherContext { ctx: openssl::cipher_ctx::CipherCtx, py_mode: pyo3::PyObject, py_algorithm: pyo3::PyObject, side: openssl::symm::Mode, } impl CipherContext { pub(crate) fn new( py: pyo3::Python<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, mode: pyo3::Bound<'_, pyo3::PyAny>, side: openssl::symm::Mode, ) -> CryptographyResult { let cipher = match cipher_registry::get_cipher(py, algorithm.clone(), mode.get_type().into_any())? { Some(c) => c, None => { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!( "cipher {} in {} mode is not supported ", algorithm.getattr(pyo3::intern!(py, "name"))?, if mode.is_truthy()? { mode.getattr(pyo3::intern!(py, "name"))? } else { mode } ), exceptions::Reasons::UNSUPPORTED_CIPHER, )), )) } }; let iv_nonce = if mode.is_instance(&types::MODE_WITH_INITIALIZATION_VECTOR.get(py)?)? { Some( mode.getattr(pyo3::intern!(py, "initialization_vector"))? .extract::>()?, ) } else if mode.is_instance(&types::MODE_WITH_TWEAK.get(py)?)? { Some( mode.getattr(pyo3::intern!(py, "tweak"))? .extract::>()?, ) } else if mode.is_instance(&types::MODE_WITH_NONCE.get(py)?)? { Some( mode.getattr(pyo3::intern!(py, "nonce"))? .extract::>()?, ) } else if algorithm.is_instance(&types::CHACHA20.get(py)?)? { Some( algorithm .getattr(pyo3::intern!(py, "nonce"))? .extract::>()?, ) } else { None }; let key = algorithm .getattr(pyo3::intern!(py, "key"))? .extract::>()?; let init_op = match side { openssl::symm::Mode::Encrypt => openssl::cipher_ctx::CipherCtxRef::encrypt_init, openssl::symm::Mode::Decrypt => openssl::cipher_ctx::CipherCtxRef::decrypt_init, }; let mut ctx = openssl::cipher_ctx::CipherCtx::new()?; init_op(&mut ctx, Some(cipher), None, None)?; ctx.set_key_length(key.as_bytes().len())?; if let Some(iv) = iv_nonce.as_ref() { if cipher.iv_length() != 0 && cipher.iv_length() != iv.as_bytes().len() { ctx.set_iv_length(iv.as_bytes().len())?; } } if mode.is_instance(&types::XTS.get(py)?)? { init_op( &mut ctx, None, Some(key.as_bytes()), iv_nonce.as_ref().map(|b| b.as_bytes()), ) .map_err(|_| { pyo3::exceptions::PyValueError::new_err( "In XTS mode duplicated keys are not allowed", ) })?; } else { init_op( &mut ctx, None, Some(key.as_bytes()), iv_nonce.as_ref().map(|b| b.as_bytes()), )?; }; ctx.set_padding(false); Ok(CipherContext { ctx, py_mode: mode.into(), py_algorithm: algorithm.into(), side, }) } fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { if !self .py_mode .bind(py) .is_instance(&types::MODE_WITH_NONCE.get(py)?)? && !self .py_algorithm .bind(py) .is_instance(&types::CHACHA20.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "This algorithm or mode does not support resetting the nonce.", exceptions::Reasons::UNSUPPORTED_CIPHER, )), )); } if nonce.as_bytes().len() != self.ctx.iv_length() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "Nonce must be {} bytes long", self.ctx.iv_length() )), )); } let init_op = match self.side { openssl::symm::Mode::Encrypt => openssl::cipher_ctx::CipherCtxRef::encrypt_init, openssl::symm::Mode::Decrypt => openssl::cipher_ctx::CipherCtxRef::decrypt_init, }; init_op(&mut self.ctx, None, None, Some(nonce.as_bytes()))?; Ok(()) } fn update<'p>( &mut self, py: pyo3::Python<'p>, buf: &[u8], ) -> CryptographyResult> { let mut out_buf = vec![0; buf.len() + self.ctx.block_size()]; let n = self.update_into(py, buf, &mut out_buf)?; Ok(pyo3::types::PyBytes::new_bound(py, &out_buf[..n])) } pub(crate) fn update_into( &mut self, py: pyo3::Python<'_>, buf: &[u8], out_buf: &mut [u8], ) -> CryptographyResult { if out_buf.len() < (buf.len() + self.ctx.block_size() - 1) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "buffer must be at least {} bytes for this payload", buf.len() + self.ctx.block_size() - 1 )), )); } let mut total_written = 0; for chunk in buf.chunks(1 << 29) { // SAFETY: We ensure that outbuf is sufficiently large above. unsafe { let n = if self.py_mode.bind(py).is_instance(&types::XTS.get(py)?)? { self.ctx.cipher_update_unchecked(chunk, Some(&mut out_buf[total_written..])).map_err(|_| { pyo3::exceptions::PyValueError::new_err( "In XTS mode you must supply at least a full block in the first update call. For AES this is 16 bytes." ) })? } else { self.ctx .cipher_update_unchecked(chunk, Some(&mut out_buf[total_written..]))? }; total_written += n; } } Ok(total_written) } fn authenticate_additional_data(&mut self, buf: &[u8]) -> CryptographyResult<()> { self.ctx.cipher_update(buf, None)?; Ok(()) } pub(crate) fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let mut out_buf = vec![0; self.ctx.block_size()]; let n = self.ctx.cipher_final(&mut out_buf).or_else(|e| { if e.errors().is_empty() && self .py_mode .bind(py) .is_instance(&types::MODE_WITH_AUTHENTICATION_TAG.get(py)?)? { return Err(CryptographyError::from(exceptions::InvalidTag::new_err(()))); } Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "The length of the provided data is not a multiple of the block length.", ), )) })?; Ok(pyo3::types::PyBytes::new_bound(py, &out_buf[..n])) } } #[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.ciphers", name = "CipherContext" )] struct PyCipherContext { ctx: Option, } #[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.ciphers", name = "AEADEncryptionContext" )] struct PyAEADEncryptionContext { ctx: Option, tag: Option>, updated: bool, bytes_remaining: u64, aad_bytes_remaining: u64, } #[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.ciphers", name = "AEADDecryptionContext" )] struct PyAEADDecryptionContext { ctx: Option, updated: bool, bytes_remaining: u64, aad_bytes_remaining: u64, } fn get_mut_ctx(ctx: Option<&mut CipherContext>) -> pyo3::PyResult<&mut CipherContext> { ctx.ok_or_else(|| exceptions::AlreadyFinalized::new_err("Context was already finalized.")) } #[pyo3::pymethods] impl PyCipherContext { fn update<'p>( &mut self, py: pyo3::Python<'p>, buf: CffiBuf<'_>, ) -> CryptographyResult> { get_mut_ctx(self.ctx.as_mut())?.update(py, buf.as_bytes()) } fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { get_mut_ctx(self.ctx.as_mut())?.reset_nonce(py, nonce) } fn update_into( &mut self, py: pyo3::Python<'_>, buf: CffiBuf<'_>, mut out_buf: CffiMutBuf<'_>, ) -> CryptographyResult { get_mut_ctx(self.ctx.as_mut())?.update_into(py, buf.as_bytes(), out_buf.as_mut_bytes()) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let result = get_mut_ctx(self.ctx.as_mut())?.finalize(py)?; self.ctx = None; Ok(result) } } #[pyo3::pymethods] impl PyAEADEncryptionContext { fn update<'p>( &mut self, py: pyo3::Python<'p>, buf: CffiBuf<'_>, ) -> CryptographyResult> { let data = buf.as_bytes(); self.updated = true; self.bytes_remaining = self .bytes_remaining .checked_sub(data.len().try_into().unwrap()) .ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") })?; get_mut_ctx(self.ctx.as_mut())?.update(py, data) } fn update_into( &mut self, py: pyo3::Python<'_>, buf: CffiBuf<'_>, mut out_buf: CffiMutBuf<'_>, ) -> CryptographyResult { let data = buf.as_bytes(); self.updated = true; self.bytes_remaining = self .bytes_remaining .checked_sub(data.len().try_into().unwrap()) .ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") })?; get_mut_ctx(self.ctx.as_mut())?.update_into(py, data, out_buf.as_mut_bytes()) } fn authenticate_additional_data(&mut self, buf: CffiBuf<'_>) -> CryptographyResult<()> { let ctx = get_mut_ctx(self.ctx.as_mut())?; if self.updated { return Err(CryptographyError::from( exceptions::AlreadyUpdated::new_err("Update has been called on this context."), )); } let data = buf.as_bytes(); self.aad_bytes_remaining = self .aad_bytes_remaining .checked_sub(data.len().try_into().unwrap()) .ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Exceeded maximum AAD byte limit") })?; ctx.authenticate_additional_data(data) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let ctx = get_mut_ctx(self.ctx.as_mut())?; let result = ctx.finalize(py)?; // XXX: do not hard code 16 let tag = pyo3::types::PyBytes::new_bound_with(py, 16, |t| { ctx.ctx.tag(t).map_err(CryptographyError::from)?; Ok(()) })?; self.tag = Some(tag.unbind()); self.ctx = None; Ok(result) } #[getter] fn tag(&self, py: pyo3::Python<'_>) -> CryptographyResult> { Ok(self .tag .as_ref() .ok_or_else(|| { exceptions::NotYetFinalized::new_err( "You must finalize encryption before getting the tag.", ) })? .clone_ref(py)) } fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { get_mut_ctx(self.ctx.as_mut())?.reset_nonce(py, nonce) } } #[pyo3::pymethods] impl PyAEADDecryptionContext { fn update<'p>( &mut self, py: pyo3::Python<'p>, buf: CffiBuf<'_>, ) -> CryptographyResult> { let data = buf.as_bytes(); self.updated = true; self.bytes_remaining = self .bytes_remaining .checked_sub(data.len().try_into().unwrap()) .ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") })?; get_mut_ctx(self.ctx.as_mut())?.update(py, data) } fn update_into( &mut self, py: pyo3::Python<'_>, buf: CffiBuf<'_>, mut out_buf: CffiMutBuf<'_>, ) -> CryptographyResult { let data = buf.as_bytes(); self.updated = true; self.bytes_remaining = self .bytes_remaining .checked_sub(data.len().try_into().unwrap()) .ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Exceeded maximum encrypted byte limit") })?; get_mut_ctx(self.ctx.as_mut())?.update_into(py, data, out_buf.as_mut_bytes()) } fn authenticate_additional_data(&mut self, buf: CffiBuf<'_>) -> CryptographyResult<()> { let ctx = get_mut_ctx(self.ctx.as_mut())?; if self.updated { return Err(CryptographyError::from( exceptions::AlreadyUpdated::new_err("Update has been called on this context."), )); } let data = buf.as_bytes(); self.aad_bytes_remaining = self .aad_bytes_remaining .checked_sub(data.len().try_into().unwrap()) .ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Exceeded maximum AAD byte limit") })?; ctx.authenticate_additional_data(data) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let ctx = get_mut_ctx(self.ctx.as_mut())?; if ctx .py_mode .bind(py) .getattr(pyo3::intern!(py, "tag"))? .is_none() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Authentication tag must be provided when decrypting.", ), )); } let result = ctx.finalize(py)?; self.ctx = None; Ok(result) } fn finalize_with_tag<'p>( &mut self, py: pyo3::Python<'p>, tag: &[u8], ) -> CryptographyResult> { let ctx = get_mut_ctx(self.ctx.as_mut())?; if !ctx .py_mode .bind(py) .getattr(pyo3::intern!(py, "tag"))? .is_none() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Authentication tag must be provided only once.", ), )); } let min_tag_length = ctx .py_mode .bind(py) .getattr(pyo3::intern!(py, "_min_tag_length"))? .extract()?; // XXX: Do not hard code 16 if tag.len() < min_tag_length { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "Authentication tag must be {} bytes or longer.", min_tag_length )), )); } else if tag.len() > 16 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "Authentication tag cannot be more than {} bytes.", 16 )), )); } ctx.ctx.set_tag(tag)?; let result = ctx.finalize(py)?; self.ctx = None; Ok(result) } fn reset_nonce(&mut self, py: pyo3::Python<'_>, nonce: CffiBuf<'_>) -> CryptographyResult<()> { get_mut_ctx(self.ctx.as_mut())?.reset_nonce(py, nonce) } } #[pyo3::pyfunction] fn create_encryption_ctx( py: pyo3::Python<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, mode: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let ctx = CipherContext::new(py, algorithm, mode.clone(), openssl::symm::Mode::Encrypt)?; if mode.is_instance(&types::MODE_WITH_AUTHENTICATION_TAG.get(py)?)? { Ok(PyAEADEncryptionContext { ctx: Some(ctx), tag: None, updated: false, bytes_remaining: mode .getattr(pyo3::intern!(py, "_MAX_ENCRYPTED_BYTES"))? .extract()?, aad_bytes_remaining: mode .getattr(pyo3::intern!(py, "_MAX_AAD_BYTES"))? .extract()?, } .into_py(py)) } else { Ok(PyCipherContext { ctx: Some(ctx) }.into_py(py)) } } #[pyo3::pyfunction] fn create_decryption_ctx( py: pyo3::Python<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, mode: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let mut ctx = CipherContext::new(py, algorithm, mode.clone(), openssl::symm::Mode::Decrypt)?; if mode.is_instance(&types::MODE_WITH_AUTHENTICATION_TAG.get(py)?)? { if let Some(tag) = mode .getattr(pyo3::intern!(py, "tag"))? .extract::>()? { ctx.ctx.set_tag(&tag)?; } Ok(PyAEADDecryptionContext { ctx: Some(ctx), updated: false, bytes_remaining: mode .getattr(pyo3::intern!(py, "_MAX_ENCRYPTED_BYTES"))? .extract()?, aad_bytes_remaining: mode .getattr(pyo3::intern!(py, "_MAX_AAD_BYTES"))? .extract()?, } .into_py(py)) } else { Ok(PyCipherContext { ctx: Some(ctx) }.into_py(py)) } } #[pyo3::pyfunction] fn cipher_supported( py: pyo3::Python<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, mode: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { Ok(cipher_registry::get_cipher(py, algorithm, mode.get_type().into_any())?.is_some()) } #[pyo3::pyfunction] fn _advance(ctx: pyo3::Bound<'_, pyo3::PyAny>, n: u64) { if let Ok(c) = ctx.downcast::() { c.borrow_mut().bytes_remaining -= n; } else if let Ok(c) = ctx.downcast::() { c.borrow_mut().bytes_remaining -= n; } } #[pyo3::pyfunction] fn _advance_aad(ctx: pyo3::Bound<'_, pyo3::PyAny>, n: u64) { if let Ok(c) = ctx.downcast::() { c.borrow_mut().aad_bytes_remaining -= n; } else if let Ok(c) = ctx.downcast::() { c.borrow_mut().aad_bytes_remaining -= n; } } #[pyo3::pymodule] pub(crate) mod ciphers { #[pymodule_export] use super::{ _advance, _advance_aad, cipher_supported, create_decryption_ctx, create_encryption_ctx, PyAEADDecryptionContext, PyAEADEncryptionContext, PyCipherContext, }; } cryptography-43.0.0/src/rust/src/backend/cmac.rs010064400017510000177000000064111464676315000177460ustar 00000000000000// 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. use crate::backend::cipher_registry; use crate::backend::hashes::already_finalized_error; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; use pyo3::types::{PyAnyMethods, PyBytesMethods}; #[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.cmac", name = "CMAC" )] struct Cmac { ctx: Option, } impl Cmac { fn get_ctx(&self) -> CryptographyResult<&cryptography_openssl::cmac::Cmac> { if let Some(ctx) = self.ctx.as_ref() { return Ok(ctx); }; Err(already_finalized_error()) } fn get_mut_ctx(&mut self) -> CryptographyResult<&mut cryptography_openssl::cmac::Cmac> { if let Some(ctx) = self.ctx.as_mut() { return Ok(ctx); } Err(already_finalized_error()) } } #[pyo3::pymethods] impl Cmac { #[new] #[pyo3(signature = (algorithm, backend=None))] fn new( py: pyo3::Python<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, backend: Option>, ) -> CryptographyResult { let _ = backend; if !algorithm.is_instance(&types::BLOCK_CIPHER_ALGORITHM.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Expected instance of BlockCipherAlgorithm.", ), )); } let cipher = cipher_registry::get_cipher(py, algorithm.clone(), types::CBC.get(py)?)? .ok_or_else(|| { exceptions::UnsupportedAlgorithm::new_err(( "CMAC is not supported with this algorithm", exceptions::Reasons::UNSUPPORTED_CIPHER, )) })?; let key = algorithm .getattr(pyo3::intern!(py, "key"))? .extract::>()?; let ctx = cryptography_openssl::cmac::Cmac::new(key.as_bytes(), cipher)?; Ok(Cmac { ctx: Some(ctx) }) } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { self.get_mut_ctx()?.update(data.as_bytes())?; Ok(()) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let data = self.get_mut_ctx()?.finish()?; self.ctx = None; Ok(pyo3::types::PyBytes::new_bound(py, &data)) } fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { let actual = self.finalize(py)?; let actual = actual.as_bytes(); if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err("Signature did not match digest."), )); } Ok(()) } fn copy(&self) -> CryptographyResult { Ok(Cmac { ctx: Some(self.get_ctx()?.copy()?), }) } } #[pyo3::pymodule] pub(crate) mod cmac { #[pymodule_export] use super::Cmac; } cryptography-43.0.0/src/rust/src/backend/dh.rs010064400017510000177000000407171464676315000174450ustar 00000000000000// 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. use cryptography_x509::common; use crate::asn1::encode_der_data; use crate::backend::utils; use crate::error::{CryptographyError, CryptographyResult}; use crate::{types, x509}; use pyo3::types::PyAnyMethods; const MIN_MODULUS_SIZE: u32 = 512; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] pub(crate) struct DHPrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] pub(crate) struct DHPublicKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.dh")] struct DHParameters { dh: openssl::dh::Dh, } #[pyo3::pyfunction] #[pyo3(signature = (generator, key_size, backend=None))] fn generate_parameters( generator: u32, key_size: u32, backend: Option>, ) -> CryptographyResult { let _ = backend; if key_size < MIN_MODULUS_SIZE { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "DH key_size must be at least {MIN_MODULUS_SIZE} bits" )), )); } if generator != 2 && generator != 5 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("DH generator must be 2 or 5"), )); } let dh = openssl::dh::Dh::generate_params(key_size, generator) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Unable to generate DH parameters"))?; Ok(DHParameters { dh }) } pub(crate) fn private_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> DHPrivateKey { DHPrivateKey { pkey: pkey.to_owned(), } } pub(crate) fn public_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> DHPublicKey { DHPublicKey { pkey: pkey.to_owned(), } } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] fn from_der_parameters( data: &[u8], backend: Option>, ) -> CryptographyResult { let _ = backend; let asn1_params = asn1::parse_single::>(data)?; let p = openssl::bn::BigNum::from_slice(asn1_params.p.as_bytes())?; let q = asn1_params .q .map(|q| openssl::bn::BigNum::from_slice(q.as_bytes())) .transpose()?; let g = openssl::bn::BigNum::from_slice(asn1_params.g.as_bytes())?; Ok(DHParameters { dh: openssl::dh::Dh::from_pqg(p, q, g)?, }) } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] fn from_pem_parameters( data: &[u8], backend: Option>, ) -> CryptographyResult { let _ = backend; let parsed = x509::find_in_pem( data, |p| p.tag() == "DH PARAMETERS" || p.tag() == "X9.42 DH PARAMETERS", "Valid PEM but no BEGIN DH PARAMETERS/END DH PARAMETERS delimiters. Are you sure this is a DH parameters?", )?; from_der_parameters(parsed.contents(), None) } fn dh_parameters_from_numbers( py: pyo3::Python<'_>, numbers: &DHParameterNumbers, ) -> CryptographyResult> { let p = utils::py_int_to_bn(py, numbers.p.bind(py))?; let q = numbers .q .as_ref() .map(|v| utils::py_int_to_bn(py, v.bind(py))) .transpose()?; let g = utils::py_int_to_bn(py, numbers.g.bind(py))?; Ok(openssl::dh::Dh::from_pqg(p, q, g)?) } fn clone_dh( dh: &openssl::dh::Dh, ) -> CryptographyResult> { let p = dh.prime_p().to_owned()?; let q = dh.prime_q().map(|q| q.to_owned()).transpose()?; let g = dh.generator().to_owned()?; Ok(openssl::dh::Dh::from_pqg(p, q, g)?) } #[pyo3::pymethods] impl DHPrivateKey { #[getter] fn key_size(&self) -> i32 { self.pkey.dh().unwrap().prime_p().num_bits() } fn exchange<'p>( &self, py: pyo3::Python<'p>, peer_public_key: &DHPublicKey, ) -> CryptographyResult> { let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; deriver .set_peer(&peer_public_key.pkey) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; let len = deriver.len()?; Ok(pyo3::types::PyBytes::new_bound_with(py, len, |b| { let n = deriver.derive(b).unwrap(); let pad = b.len() - n; if pad > 0 { b.copy_within(0..n, pad); for c in b.iter_mut().take(pad) { *c = 0; } } Ok(()) })?) } fn private_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dh = self.pkey.dh().unwrap(); let py_p = utils::bn_to_py_int(py, dh.prime_p())?; let py_q = dh .prime_q() .map(|q| utils::bn_to_py_int(py, q)) .transpose()?; let py_g = utils::bn_to_py_int(py, dh.generator())?; let py_pub_key = utils::bn_to_py_int(py, dh.public_key())?; let py_private_key = utils::bn_to_py_int(py, dh.private_key())?; let parameter_numbers = DHParameterNumbers { p: py_p.extract()?, q: py_q.map(|q| q.extract()).transpose()?, g: py_g.extract()?, }; let public_numbers = DHPublicNumbers { y: py_pub_key.extract()?, parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, }; Ok(DHPrivateNumbers { x: py_private_key.extract()?, public_numbers: pyo3::Py::new(py, public_numbers)?, }) } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn public_key(&self) -> CryptographyResult { let orig_dh = self.pkey.dh().unwrap(); let dh = clone_dh(&orig_dh)?; let pkey = openssl::pkey::PKey::from_dh(dh.set_public_key(orig_dh.public_key().to_owned()?)?)?; Ok(DHPublicKey { pkey }) } fn parameters(&self) -> CryptographyResult { Ok(DHParameters { dh: clone_dh(&self.pkey.dh().unwrap())?, }) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { if !format.is(&types::PRIVATE_FORMAT_PKCS8.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "DH private keys support only PKCS8 serialization", ), )); } utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, true, false, ) } } #[pyo3::pymethods] impl DHPublicKey { #[getter] fn key_size(&self) -> i32 { self.pkey.dh().unwrap().prime_p().num_bits() } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { if !format.is(&types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "DH public keys support only SubjectPublicKeyInfo serialization", ), )); } utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } fn parameters(&self) -> CryptographyResult { Ok(DHParameters { dh: clone_dh(&self.pkey.dh().unwrap())?, }) } fn public_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dh = self.pkey.dh().unwrap(); let py_p = utils::bn_to_py_int(py, dh.prime_p())?; let py_q = dh .prime_q() .map(|q| utils::bn_to_py_int(py, q)) .transpose()?; let py_g = utils::bn_to_py_int(py, dh.generator())?; let py_pub_key = utils::bn_to_py_int(py, dh.public_key())?; let parameter_numbers = DHParameterNumbers { p: py_p.extract()?, q: py_q.map(|q| q.extract()).transpose()?, g: py_g.extract()?, }; Ok(DHPublicNumbers { y: py_pub_key.extract()?, parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, }) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pymethods] impl DHParameters { #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn generate_private_key(&self) -> CryptographyResult { let dh = clone_dh(&self.dh)?.generate_key()?; Ok(DHPrivateKey { pkey: openssl::pkey::PKey::from_dh(dh)?, }) } fn parameter_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let py_p = utils::bn_to_py_int(py, self.dh.prime_p())?; let py_q = self .dh .prime_q() .map(|q| utils::bn_to_py_int(py, q)) .transpose()?; let py_g = utils::bn_to_py_int(py, self.dh.generator())?; Ok(DHParameterNumbers { p: py_p.extract()?, q: py_q.map(|q| q.extract()).transpose()?, g: py_g.extract()?, }) } fn parameter_bytes<'p>( &self, py: pyo3::Python<'p>, encoding: pyo3::Bound<'p, pyo3::PyAny>, format: pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { if !format.is(&types::PARAMETER_FORMAT_PKCS3.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Only PKCS3 serialization is supported"), )); } let p_bytes = utils::bn_to_big_endian_bytes(self.dh.prime_p())?; let q_bytes = self .dh .prime_q() .map(utils::bn_to_big_endian_bytes) .transpose()?; let g_bytes = utils::bn_to_big_endian_bytes(self.dh.generator())?; let asn1dh_params = common::DHParams { p: asn1::BigUint::new(&p_bytes).unwrap(), q: q_bytes.as_ref().map(|q| asn1::BigUint::new(q).unwrap()), g: asn1::BigUint::new(&g_bytes).unwrap(), }; let data = asn1::write_single(&asn1dh_params)?; let tag = if q_bytes.is_none() { "DH PARAMETERS" } else { "X9.42 DH PARAMETERS" }; encode_der_data(py, tag.to_string(), data, &encoding) } } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] struct DHPrivateNumbers { #[pyo3(get)] x: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] struct DHPublicNumbers { #[pyo3(get)] y: pyo3::Py, #[pyo3(get)] parameter_numbers: pyo3::Py, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.dh")] struct DHParameterNumbers { #[pyo3(get)] p: pyo3::Py, #[pyo3(get)] g: pyo3::Py, #[pyo3(get)] q: Option>, } #[pyo3::pymethods] impl DHPrivateNumbers { #[new] fn new( x: pyo3::Py, public_numbers: pyo3::Py, ) -> DHPrivateNumbers { DHPrivateNumbers { x, public_numbers } } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] #[pyo3(signature = (backend=None))] fn private_key( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let dh = dh_parameters_from_numbers(py, self.public_numbers.get().parameter_numbers.get())?; let pub_key = utils::py_int_to_bn(py, self.public_numbers.get().y.bind(py))?; let priv_key = utils::py_int_to_bn(py, self.x.bind(py))?; let dh = dh.set_key(pub_key, priv_key)?; if !dh.check_key()? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "DH private numbers did not pass safety checks.", ), )); } let pkey = openssl::pkey::PKey::from_dh(dh)?; Ok(DHPrivateKey { pkey }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.x.bind(py).eq(other.x.bind(py))? && self .public_numbers .bind(py) .eq(other.public_numbers.bind(py))?) } } #[pyo3::pymethods] impl DHPublicNumbers { #[new] fn new( y: pyo3::Py, parameter_numbers: pyo3::Py, ) -> DHPublicNumbers { DHPublicNumbers { y, parameter_numbers, } } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let dh = dh_parameters_from_numbers(py, self.parameter_numbers.get())?; let pub_key = utils::py_int_to_bn(py, self.y.bind(py))?; let pkey = openssl::pkey::PKey::from_dh(dh.set_public_key(pub_key)?)?; Ok(DHPublicKey { pkey }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.y.bind(py).eq(other.y.bind(py))? && self .parameter_numbers .bind(py) .eq(other.parameter_numbers.bind(py))?) } } #[pyo3::pymethods] impl DHParameterNumbers { #[new] #[pyo3(signature = (p, g, q=None))] fn new( py: pyo3::Python<'_>, p: pyo3::Py, g: pyo3::Py, q: Option>, ) -> CryptographyResult { if g.bind(py).lt(2)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("DH generator must be 2 or greater"), )); } if p.bind(py) .call_method0("bit_length")? .lt(MIN_MODULUS_SIZE)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "p (modulus) must be at least {MIN_MODULUS_SIZE}-bit" )), )); } Ok(DHParameterNumbers { p, g, q }) } #[pyo3(signature = (backend=None))] fn parameters( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let dh = dh_parameters_from_numbers(py, self)?; Ok(DHParameters { dh }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { let q_equal = match (self.q.as_ref(), other.q.as_ref()) { (Some(self_q), Some(other_q)) => self_q.bind(py).eq(other_q.bind(py))?, (None, None) => true, _ => false, }; Ok(self.p.bind(py).eq(other.p.bind(py))? && self.g.bind(py).eq(other.g.bind(py))? && q_equal) } } #[pyo3::pymodule] pub(crate) mod dh { #[pymodule_export] use super::{ from_der_parameters, from_pem_parameters, generate_parameters, DHParameterNumbers, DHParameters, DHPrivateKey, DHPrivateNumbers, DHPublicKey, DHPublicNumbers, }; } cryptography-43.0.0/src/rust/src/backend/dsa.rs010064400017510000177000000353161464676315000176200ustar 00000000000000// 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. use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; use pyo3::types::PyAnyMethods; #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAPrivateKey" )] pub(crate) struct DsaPrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAPublicKey" )] pub(crate) struct DsaPublicKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.dsa", name = "DSAParameters" )] struct DsaParameters { dsa: openssl::dsa::Dsa, } pub(crate) fn private_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> DsaPrivateKey { DsaPrivateKey { pkey: pkey.to_owned(), } } pub(crate) fn public_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> DsaPublicKey { DsaPublicKey { pkey: pkey.to_owned(), } } #[pyo3::pyfunction] fn generate_parameters(key_size: u32) -> CryptographyResult { let dsa = openssl::dsa::Dsa::generate_params(key_size)?; Ok(DsaParameters { dsa }) } fn clone_dsa_params( d: &openssl::dsa::Dsa, ) -> Result, openssl::error::ErrorStack> { openssl::dsa::Dsa::from_pqg(d.p().to_owned()?, d.q().to_owned()?, d.g().to_owned()?) } #[pyo3::pymethods] impl DsaPrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let (data, _) = utils::calculate_digest_and_algorithm(py, data.as_bytes(), &algorithm)?; let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; signer.sign_init()?; let mut sig = vec![]; signer.sign_to_vec(data.as_bytes(), &mut sig)?; Ok(pyo3::types::PyBytes::new_bound(py, &sig)) } #[getter] fn key_size(&self) -> i32 { self.pkey.dsa().unwrap().p().num_bits() } fn public_key(&self) -> CryptographyResult { let priv_dsa = self.pkey.dsa()?; let pub_dsa = openssl::dsa::Dsa::from_public_components( priv_dsa.p().to_owned()?, priv_dsa.q().to_owned()?, priv_dsa.g().to_owned()?, priv_dsa.pub_key().to_owned()?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_dsa(pub_dsa)?; Ok(DsaPublicKey { pkey }) } fn parameters(&self) -> CryptographyResult { let dsa = clone_dsa_params(&self.pkey.dsa().unwrap())?; Ok(DsaParameters { dsa }) } fn private_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dsa = self.pkey.dsa().unwrap(); let py_p = utils::bn_to_py_int(py, dsa.p())?; let py_q = utils::bn_to_py_int(py, dsa.q())?; let py_g = utils::bn_to_py_int(py, dsa.g())?; let py_pub_key = utils::bn_to_py_int(py, dsa.pub_key())?; let py_private_key = utils::bn_to_py_int(py, dsa.priv_key())?; let parameter_numbers = DsaParameterNumbers { p: py_p.extract()?, q: py_q.extract()?, g: py_g.extract()?, }; let public_numbers = DsaPublicNumbers { y: py_pub_key.extract()?, parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, }; Ok(DsaPrivateNumbers { x: py_private_key.extract()?, public_numbers: pyo3::Py::new(py, public_numbers)?, }) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, true, false, ) } } #[pyo3::pymethods] impl DsaPublicKey { fn verify( &self, py: pyo3::Python<'_>, signature: CffiBuf<'_>, data: CffiBuf<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { let (data, _) = utils::calculate_digest_and_algorithm(py, data.as_bytes(), &algorithm)?; let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; verifier.verify_init()?; let valid = verifier .verify(data.as_bytes(), signature.as_bytes()) .unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), )); } Ok(()) } #[getter] fn key_size(&self) -> i32 { self.pkey.dsa().unwrap().p().num_bits() } fn parameters(&self) -> CryptographyResult { let dsa = clone_dsa_params(&self.pkey.dsa().unwrap())?; Ok(DsaParameters { dsa }) } fn public_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let dsa = self.pkey.dsa().unwrap(); let py_p = utils::bn_to_py_int(py, dsa.p())?; let py_q = utils::bn_to_py_int(py, dsa.q())?; let py_g = utils::bn_to_py_int(py, dsa.g())?; let py_pub_key = utils::bn_to_py_int(py, dsa.pub_key())?; let parameter_numbers = DsaParameterNumbers { p: py_p.extract()?, q: py_q.extract()?, g: py_g.extract()?, }; Ok(DsaPublicNumbers { y: py_pub_key.extract()?, parameter_numbers: pyo3::Py::new(py, parameter_numbers)?, }) } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pymethods] impl DsaParameters { fn generate_private_key(&self) -> CryptographyResult { let dsa = clone_dsa_params(&self.dsa)?.generate_key()?; let pkey = openssl::pkey::PKey::from_dsa(dsa)?; Ok(DsaPrivateKey { pkey }) } fn parameter_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let py_p = utils::bn_to_py_int(py, self.dsa.p())?; let py_q = utils::bn_to_py_int(py, self.dsa.q())?; let py_g = utils::bn_to_py_int(py, self.dsa.g())?; Ok(DsaParameterNumbers { p: py_p.extract()?, q: py_q.extract()?, g: py_g.extract()?, }) } } fn check_dsa_parameters( py: pyo3::Python<'_>, parameters: &DsaParameterNumbers, ) -> CryptographyResult<()> { if ![1024, 2048, 3072, 4096].contains( ¶meters .p .bind(py) .call_method0("bit_length")? .extract::()?, ) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "p must be exactly 1024, 2048, 3072, or 4096 bits long", ), )); } if ![160, 224, 256].contains( ¶meters .q .bind(py) .call_method0("bit_length")? .extract::()?, ) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("q must be exactly 160, 224, or 256 bits long"), )); } if parameters.g.bind(py).le(1)? || parameters.g.bind(py).ge(parameters.p.bind(py))? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("g, p don't satisfy 1 < g < p."), )); } Ok(()) } fn check_dsa_private_numbers( py: pyo3::Python<'_>, numbers: &DsaPrivateNumbers, ) -> CryptographyResult<()> { let params = numbers.public_numbers.get().parameter_numbers.get(); check_dsa_parameters(py, params)?; if numbers.x.bind(py).le(0)? || numbers.x.bind(py).ge(params.q.bind(py))? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("x must be > 0 and < q."), )); } if numbers.public_numbers.get().y.bind(py).ne(params .g .bind(py) .pow(numbers.x.bind(py), Some(params.p.bind(py)))?)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("y must be equal to (g ** x % p)."), )); } Ok(()) } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.dsa", name = "DSAPrivateNumbers" )] struct DsaPrivateNumbers { #[pyo3(get)] x: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.dsa", name = "DSAPublicNumbers" )] struct DsaPublicNumbers { #[pyo3(get)] y: pyo3::Py, #[pyo3(get)] parameter_numbers: pyo3::Py, } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.dsa", name = "DSAParameterNumbers" )] struct DsaParameterNumbers { #[pyo3(get)] p: pyo3::Py, #[pyo3(get)] q: pyo3::Py, #[pyo3(get)] g: pyo3::Py, } #[pyo3::pymethods] impl DsaPrivateNumbers { #[new] fn new( x: pyo3::Py, public_numbers: pyo3::Py, ) -> DsaPrivateNumbers { DsaPrivateNumbers { x, public_numbers } } #[pyo3(signature = (backend=None))] fn private_key( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let public_numbers = self.public_numbers.get(); let parameter_numbers = public_numbers.parameter_numbers.get(); check_dsa_private_numbers(py, self)?; let dsa = openssl::dsa::Dsa::from_private_components( utils::py_int_to_bn(py, parameter_numbers.p.bind(py))?, utils::py_int_to_bn(py, parameter_numbers.q.bind(py))?, utils::py_int_to_bn(py, parameter_numbers.g.bind(py))?, utils::py_int_to_bn(py, self.x.bind(py))?, utils::py_int_to_bn(py, public_numbers.y.bind(py))?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_dsa(dsa)?; Ok(DsaPrivateKey { pkey }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.x.bind(py).eq(other.x.bind(py))? && self .public_numbers .bind(py) .eq(other.public_numbers.bind(py))?) } } #[pyo3::pymethods] impl DsaPublicNumbers { #[new] fn new( y: pyo3::Py, parameter_numbers: pyo3::Py, ) -> DsaPublicNumbers { DsaPublicNumbers { y, parameter_numbers, } } #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let parameter_numbers = self.parameter_numbers.get(); check_dsa_parameters(py, parameter_numbers)?; let dsa = openssl::dsa::Dsa::from_public_components( utils::py_int_to_bn(py, parameter_numbers.p.bind(py))?, utils::py_int_to_bn(py, parameter_numbers.q.bind(py))?, utils::py_int_to_bn(py, parameter_numbers.g.bind(py))?, utils::py_int_to_bn(py, self.y.bind(py))?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_dsa(dsa)?; Ok(DsaPublicKey { pkey }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.y.bind(py).eq(other.y.bind(py))? && self .parameter_numbers .bind(py) .eq(other.parameter_numbers.bind(py))?) } fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let y = self.y.bind(py); let parameter_numbers = self.parameter_numbers.bind(py).repr()?; Ok(format!( "" )) } } #[pyo3::pymethods] impl DsaParameterNumbers { #[new] fn new( p: pyo3::Py, q: pyo3::Py, g: pyo3::Py, ) -> DsaParameterNumbers { DsaParameterNumbers { p, q, g } } #[pyo3(signature = (backend=None))] fn parameters( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; check_dsa_parameters(py, self)?; let dsa = openssl::dsa::Dsa::from_pqg( utils::py_int_to_bn(py, self.p.bind(py))?, utils::py_int_to_bn(py, self.q.bind(py))?, utils::py_int_to_bn(py, self.g.bind(py))?, ) .unwrap(); Ok(DsaParameters { dsa }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.p.bind(py).eq(other.p.bind(py))? && self.q.bind(py).eq(other.q.bind(py))? && self.g.bind(py).eq(other.g.bind(py))?) } fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let p = self.p.bind(py); let q = self.q.bind(py); let g = self.g.bind(py); Ok(format!("")) } } #[pyo3::pymodule] pub(crate) mod dsa { #[pymodule_export] use super::{ generate_parameters, DsaParameterNumbers, DsaParameters, DsaPrivateKey, DsaPrivateNumbers, DsaPublicKey, DsaPublicNumbers, }; } cryptography-43.0.0/src/rust/src/backend/ec.rs010064400017510000177000000560531464676315000174410ustar 00000000000000// 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. use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use pyo3::types::{PyAnyMethods, PyDictMethods}; use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] pub(crate) struct ECPrivateKey { pkey: openssl::pkey::PKey, #[pyo3(get)] curve: pyo3::Py, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] pub(crate) struct ECPublicKey { pkey: openssl::pkey::PKey, #[pyo3(get)] curve: pyo3::Py, } fn curve_from_py_curve( py: pyo3::Python<'_>, py_curve: pyo3::Bound<'_, pyo3::PyAny>, allow_curve_class: bool, ) -> CryptographyResult { if !py_curve.is_instance(&types::ELLIPTIC_CURVE.get(py)?)? { if allow_curve_class { let warning_cls = types::DEPRECATED_IN_42.get(py)?; let warning_msg = "Curve argument must be an instance of an EllipticCurve class. Did you pass a class by mistake? This will be an exception in a future version of cryptography."; pyo3::PyErr::warn_bound(py, &warning_cls, warning_msg, 1)?; } else { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err("curve must be an EllipticCurve instance"), )); } } let py_curve_name = py_curve.getattr(pyo3::intern!(py, "name"))?; let nid = match &*py_curve_name.extract::()? { "secp192r1" => openssl::nid::Nid::X9_62_PRIME192V1, "secp224r1" => openssl::nid::Nid::SECP224R1, "secp256r1" => openssl::nid::Nid::X9_62_PRIME256V1, "secp384r1" => openssl::nid::Nid::SECP384R1, "secp521r1" => openssl::nid::Nid::SECP521R1, "secp256k1" => openssl::nid::Nid::SECP256K1, "sect233r1" => openssl::nid::Nid::SECT233R1, "sect283r1" => openssl::nid::Nid::SECT283R1, "sect409r1" => openssl::nid::Nid::SECT409R1, "sect571r1" => openssl::nid::Nid::SECT571R1, "sect163r2" => openssl::nid::Nid::SECT163R2, "sect163k1" => openssl::nid::Nid::SECT163K1, "sect233k1" => openssl::nid::Nid::SECT233K1, "sect283k1" => openssl::nid::Nid::SECT283K1, "sect409k1" => openssl::nid::Nid::SECT409K1, "sect571k1" => openssl::nid::Nid::SECT571K1, #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] "brainpoolP256r1" => openssl::nid::Nid::BRAINPOOL_P256R1, #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] "brainpoolP384r1" => openssl::nid::Nid::BRAINPOOL_P384R1, #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] "brainpoolP512r1" => openssl::nid::Nid::BRAINPOOL_P512R1, curve_name => { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!("Curve {curve_name} is not supported"), exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, )), )); } }; Ok(openssl::ec::EcGroup::from_curve_name(nid)?) } fn py_curve_from_curve<'p>( py: pyo3::Python<'p>, curve: &openssl::ec::EcGroupRef, ) -> CryptographyResult> { if curve.asn1_flag() == openssl::ec::Asn1Flag::EXPLICIT_CURVE { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "ECDSA keys with explicit parameters are unsupported at this time", ), )); } let name = curve.curve_name().unwrap().short_name()?; types::CURVE_TYPES .get(py)? .extract::>()? .get_item(name)? .ok_or_else(|| { CryptographyError::from(exceptions::UnsupportedAlgorithm::new_err(( format!("{name} is not a supported elliptic curve"), exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, ))) }) } fn check_key_infinity( ec: &openssl::ec::EcKeyRef, ) -> CryptographyResult<()> { if ec.public_key().is_infinity(ec.group()) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Cannot load an EC public key where the point is at infinity", ), )); } Ok(()) } #[pyo3::pyfunction] fn curve_supported(py: pyo3::Python<'_>, py_curve: pyo3::Bound<'_, pyo3::PyAny>) -> bool { curve_from_py_curve(py, py_curve, false).is_ok() } pub(crate) fn private_key_from_pkey( py: pyo3::Python<'_>, pkey: &openssl::pkey::PKeyRef, ) -> CryptographyResult { let curve = py_curve_from_curve(py, pkey.ec_key().unwrap().group())?; check_key_infinity(&pkey.ec_key().unwrap())?; Ok(ECPrivateKey { pkey: pkey.to_owned(), curve: curve.into(), }) } pub(crate) fn public_key_from_pkey( py: pyo3::Python<'_>, pkey: &openssl::pkey::PKeyRef, ) -> CryptographyResult { let ec = pkey.ec_key()?; let curve = py_curve_from_curve(py, ec.group())?; check_key_infinity(&ec)?; Ok(ECPublicKey { pkey: pkey.to_owned(), curve: curve.into(), }) } #[pyo3::pyfunction] #[pyo3(signature = (curve, backend=None))] fn generate_private_key( py: pyo3::Python<'_>, curve: pyo3::Bound<'_, pyo3::PyAny>, backend: Option>, ) -> CryptographyResult { let _ = backend; let ossl_curve = curve_from_py_curve(py, curve, true)?; let key = openssl::ec::EcKey::generate(&ossl_curve)?; Ok(ECPrivateKey { pkey: openssl::pkey::PKey::from_ec_key(key)?, curve: py_curve_from_curve(py, &ossl_curve)?.into(), }) } #[pyo3::pyfunction] fn derive_private_key( py: pyo3::Python<'_>, py_private_value: &pyo3::Bound<'_, pyo3::types::PyLong>, py_curve: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let curve = curve_from_py_curve(py, py_curve.clone(), false)?; let private_value = utils::py_int_to_bn(py, py_private_value)?; let mut point = openssl::ec::EcPoint::new(&curve)?; let bn_ctx = openssl::bn::BigNumContext::new()?; point.mul_generator(&curve, &private_value, &bn_ctx)?; let ec = openssl::ec::EcKey::from_private_components(&curve, &private_value, &point) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid EC key"))?; check_key_infinity(&ec)?; let pkey = openssl::pkey::PKey::from_ec_key(ec)?; Ok(ECPrivateKey { pkey, curve: py_curve.into(), }) } #[pyo3::pyfunction] fn from_public_bytes( py: pyo3::Python<'_>, py_curve: pyo3::Bound<'_, pyo3::PyAny>, data: &[u8], ) -> CryptographyResult { let curve = curve_from_py_curve(py, py_curve.clone(), false)?; let mut bn_ctx = openssl::bn::BigNumContext::new()?; let point = openssl::ec::EcPoint::from_bytes(&curve, data, &mut bn_ctx) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid EC key."))?; let ec = openssl::ec::EcKey::from_public_key(&curve, &point)?; let pkey = openssl::pkey::PKey::from_ec_key(ec)?; Ok(ECPublicKey { pkey, curve: py_curve.into(), }) } #[pyo3::pymethods] impl ECPrivateKey { #[getter] fn key_size<'p>( &'p self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { self.curve.bind(py).getattr(pyo3::intern!(py, "key_size")) } fn exchange<'p>( &self, py: pyo3::Python<'p>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, peer_public_key: &ECPublicKey, ) -> CryptographyResult> { if !algorithm.is_instance(&types::ECDH.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "Unsupported EC exchange algorithm", exceptions::Reasons::UNSUPPORTED_EXCHANGE_ALGORITHM, )), )); } let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; // If `set_peer_ex` is available, we don't valid the key. This is // because we already validated it sufficiently when we created the // ECPublicKey object. #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] deriver .set_peer_ex(&peer_public_key.pkey, false) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; #[cfg(not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER))] deriver .set_peer(&peer_public_key.pkey) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; let len = deriver.len()?; Ok(pyo3::types::PyBytes::new_bound_with(py, len, |b| { let n = deriver.derive(b).map_err(|_| { pyo3::exceptions::PyValueError::new_err("Error computing shared key.") })?; assert_eq!(n, b.len()); Ok(()) })?) } fn sign<'p>( &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, signature_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { if !signature_algorithm.is_instance(&types::ECDSA.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "Unsupported elliptic curve signature algorithm", exceptions::Reasons::UNSUPPORTED_PUBLIC_KEY_ALGORITHM, )), )); } let bound_algorithm = signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?; let (data, algo) = utils::calculate_digest_and_algorithm(py, data.as_bytes(), &bound_algorithm)?; let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; signer.sign_init()?; cfg_if::cfg_if! { if #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)]{ let deterministic: bool = signature_algorithm .getattr(pyo3::intern!(py, "deterministic_signing"))? .extract()?; if deterministic { let hash_function_name = algo .getattr(pyo3::intern!(py, "name"))? .extract::()?; let hash_function = openssl::md::Md::fetch(None, &hash_function_name, None)?; // Setting a deterministic nonce type requires to explicitly set the hash function. // See https://github.com/openssl/openssl/issues/23205 signer.set_signature_md(&hash_function)?; signer.set_nonce_type(openssl::pkey_ctx::NonceType::DETERMINISTIC_K)?; } else { signer.set_nonce_type(openssl::pkey_ctx::NonceType::RANDOM_K)?; } } else { let _ = algo; } } // TODO: This does an extra allocation and copy. This can't easily use // `PyBytes::new_with` because the exact length of the signature isn't // easily known a priori (if `r` or `s` has a leading 0, the signature // will be a byte or two shorter than the maximum possible length). let mut sig = vec![]; signer.sign_to_vec(data.as_bytes(), &mut sig)?; Ok(pyo3::types::PyBytes::new_bound(py, &sig)) } fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { let orig_ec = self.pkey.ec_key().unwrap(); let ec = openssl::ec::EcKey::from_public_key(orig_ec.group(), orig_ec.public_key())?; let pkey = openssl::pkey::PKey::from_ec_key(ec)?; Ok(ECPublicKey { pkey, curve: self.curve.clone_ref(py), }) } fn private_numbers( &self, py: pyo3::Python<'_>, ) -> CryptographyResult { let ec = self.pkey.ec_key().unwrap(); let mut bn_ctx = openssl::bn::BigNumContext::new()?; let mut x = openssl::bn::BigNum::new()?; let mut y = openssl::bn::BigNum::new()?; ec.public_key() .affine_coordinates(ec.group(), &mut x, &mut y, &mut bn_ctx)?; let py_x = utils::bn_to_py_int(py, &x)?; let py_y = utils::bn_to_py_int(py, &y)?; let py_private_key = utils::bn_to_py_int(py, ec.private_key())?; let public_numbers = EllipticCurvePublicNumbers { x: py_x.extract()?, y: py_y.extract()?, curve: self.curve.clone_ref(py), }; Ok(EllipticCurvePrivateNumbers { private_value: py_private_key.extract()?, public_numbers: pyo3::Py::new(py, public_numbers)?, }) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, true, false, ) } } #[pyo3::pymethods] impl ECPublicKey { #[getter] fn key_size<'p>( &'p self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { self.curve.bind(py).getattr(pyo3::intern!(py, "key_size")) } fn verify( &self, py: pyo3::Python<'_>, signature: CffiBuf<'_>, data: CffiBuf<'_>, signature_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { if !signature_algorithm.is_instance(&types::ECDSA.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "Unsupported elliptic curve signature algorithm", exceptions::Reasons::UNSUPPORTED_PUBLIC_KEY_ALGORITHM, )), )); } let (data, _) = utils::calculate_digest_and_algorithm( py, data.as_bytes(), &signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?, )?; let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; verifier.verify_init()?; let valid = verifier .verify(data.as_bytes(), signature.as_bytes()) .unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), )); } Ok(()) } fn public_numbers( &self, py: pyo3::Python<'_>, ) -> CryptographyResult { let ec = self.pkey.ec_key().unwrap(); let mut bn_ctx = openssl::bn::BigNumContext::new()?; let mut x = openssl::bn::BigNum::new()?; let mut y = openssl::bn::BigNum::new()?; ec.public_key() .affine_coordinates(ec.group(), &mut x, &mut y, &mut bn_ctx)?; let py_x = utils::bn_to_py_int(py, &x)?; let py_y = utils::bn_to_py_int(py, &y)?; Ok(EllipticCurvePublicNumbers { x: py_x.extract()?, y: py_y.extract()?, curve: self.curve.clone_ref(py), }) } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] struct EllipticCurvePrivateNumbers { #[pyo3(get)] private_value: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.primitives.asymmetric.ec")] struct EllipticCurvePublicNumbers { #[pyo3(get)] x: pyo3::Py, #[pyo3(get)] y: pyo3::Py, #[pyo3(get)] curve: pyo3::Py, } fn public_key_from_numbers( py: pyo3::Python<'_>, numbers: &EllipticCurvePublicNumbers, curve: &openssl::ec::EcGroupRef, ) -> CryptographyResult> { if numbers.x.bind(py).lt(0)? || numbers.y.bind(py).lt(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Invalid EC key. Both x and y must be non-negative.", ), )); } let x = utils::py_int_to_bn(py, numbers.x.bind(py))?; let y = utils::py_int_to_bn(py, numbers.y.bind(py))?; let mut point = openssl::ec::EcPoint::new(curve)?; let mut bn_ctx = openssl::bn::BigNumContext::new()?; point .set_affine_coordinates_gfp(curve, &x, &y, &mut bn_ctx) .map_err(|_| { pyo3::exceptions::PyValueError::new_err( "Invalid EC key. Point is not on the curve specified.", ) })?; Ok(openssl::ec::EcKey::from_public_key(curve, &point)?) } #[pyo3::pymethods] impl EllipticCurvePrivateNumbers { #[new] fn new( private_value: pyo3::Py, public_numbers: pyo3::Py, ) -> EllipticCurvePrivateNumbers { EllipticCurvePrivateNumbers { private_value, public_numbers, } } #[pyo3(signature = (backend=None))] fn private_key( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let curve = curve_from_py_curve(py, self.public_numbers.get().curve.bind(py).clone(), false)?; let public_key = public_key_from_numbers(py, self.public_numbers.get(), &curve)?; let private_value = utils::py_int_to_bn(py, self.private_value.bind(py))?; let mut bn_ctx = openssl::bn::BigNumContext::new()?; let mut expected_pub = openssl::ec::EcPoint::new(&curve)?; expected_pub.mul_generator(&curve, &private_value, &bn_ctx)?; if !expected_pub.eq(&curve, public_key.public_key(), &mut bn_ctx)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid EC key."), )); } let private_key = openssl::ec::EcKey::from_private_components( &curve, &private_value, public_key.public_key(), ) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid EC key."))?; let pkey = openssl::pkey::PKey::from_ec_key(private_key)?; Ok(ECPrivateKey { pkey, curve: self.public_numbers.get().curve.clone_ref(py), }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self .private_value .bind(py) .eq(other.private_value.bind(py))? && self .public_numbers .bind(py) .eq(other.public_numbers.bind(py))?) } fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { let mut hasher = DefaultHasher::new(); self.private_value.bind(py).hash()?.hash(&mut hasher); self.public_numbers.bind(py).hash()?.hash(&mut hasher); Ok(hasher.finish()) } } #[pyo3::pymethods] impl EllipticCurvePublicNumbers { #[new] fn new( py: pyo3::Python<'_>, x: pyo3::Py, y: pyo3::Py, curve: pyo3::Py, ) -> CryptographyResult { if !curve .bind(py) .is_instance(&types::ELLIPTIC_CURVE.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "curve must provide the EllipticCurve interface.", ), )); } Ok(EllipticCurvePublicNumbers { x, y, curve }) } #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let curve = curve_from_py_curve(py, self.curve.bind(py).clone(), false)?; let public_key = public_key_from_numbers(py, self, &curve)?; let pkey = openssl::pkey::PKey::from_ec_key(public_key)?; Ok(ECPublicKey { pkey, curve: self.curve.clone_ref(py), }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.x.bind(py).eq(other.x.bind(py))? && self.y.bind(py).eq(other.y.bind(py))? && self .curve .bind(py) .getattr(pyo3::intern!(py, "name"))? .eq(other.curve.bind(py).getattr(pyo3::intern!(py, "name"))?)? && self .curve .bind(py) .getattr(pyo3::intern!(py, "key_size"))? .eq(other .curve .bind(py) .getattr(pyo3::intern!(py, "key_size"))?)?) } fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { let mut hasher = DefaultHasher::new(); self.x.bind(py).hash()?.hash(&mut hasher); self.y.bind(py).hash()?.hash(&mut hasher); self.curve .bind(py) .getattr(pyo3::intern!(py, "name"))? .hash()? .hash(&mut hasher); self.curve .bind(py) .getattr(pyo3::intern!(py, "key_size"))? .hash()? .hash(&mut hasher); Ok(hasher.finish()) } fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let x = self.x.bind(py); let y = self.y.bind(py); let curve_name = self.curve.bind(py).getattr(pyo3::intern!(py, "name"))?; Ok(format!( "" )) } } #[pyo3::pymodule] pub(crate) mod ec { #[pymodule_export] use super::{ curve_supported, derive_private_key, from_public_bytes, generate_private_key, ECPrivateKey, ECPublicKey, EllipticCurvePrivateNumbers, EllipticCurvePublicNumbers, }; } cryptography-43.0.0/src/rust/src/backend/ed25519.rs010064400017510000177000000121011464676315000200320ustar 00000000000000// 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. use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] pub(crate) struct Ed25519PrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] pub(crate) struct Ed25519PublicKey { pkey: openssl::pkey::PKey, } #[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(Ed25519PrivateKey { pkey: openssl::pkey::PKey::generate_ed25519()?, }) } pub(crate) fn private_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> Ed25519PrivateKey { Ed25519PrivateKey { pkey: pkey.to_owned(), } } pub(crate) fn public_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> Ed25519PublicKey { Ed25519PublicKey { pkey: pkey.to_owned(), } } #[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes( data.as_bytes(), openssl::pkey::Id::ED25519, ) .map_err(|_| { pyo3::exceptions::PyValueError::new_err("An Ed25519 private key is 32 bytes long") })?; Ok(Ed25519PrivateKey { pkey }) } #[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::ED25519) .map_err(|_| { pyo3::exceptions::PyValueError::new_err("An Ed25519 public key is 32 bytes long") })?; Ok(Ed25519PublicKey { pkey }) } #[pyo3::pymethods] impl Ed25519PrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, ) -> CryptographyResult> { let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; let len = signer.len()?; Ok(pyo3::types::PyBytes::new_bound_with(py, len, |b| { let n = signer .sign_oneshot(b, data.as_bytes()) .map_err(CryptographyError::from)?; assert_eq!(n, b.len()); Ok(()) })?) } fn public_key(&self) -> CryptographyResult { let raw_bytes = self.pkey.raw_public_key()?; Ok(Ed25519PublicKey { pkey: openssl::pkey::PKey::public_key_from_raw_bytes( &raw_bytes, openssl::pkey::Id::ED25519, )?, }) } fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, true, true, ) } } #[pyo3::pymethods] impl Ed25519PublicKey { fn verify(&self, signature: CffiBuf<'_>, data: CffiBuf<'_>) -> CryptographyResult<()> { let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? .verify_oneshot(signature.as_bytes(), data.as_bytes()) .unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), )); } Ok(()) } fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pymodule] pub(crate) mod ed25519 { #[pymodule_export] use super::{ from_private_bytes, from_public_bytes, generate_key, Ed25519PrivateKey, Ed25519PublicKey, }; } cryptography-43.0.0/src/rust/src/backend/ed448.rs010064400017510000177000000117671464676315000177050ustar 00000000000000// 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. use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] pub(crate) struct Ed448PrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ed448")] pub(crate) struct Ed448PublicKey { pkey: openssl::pkey::PKey, } #[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(Ed448PrivateKey { pkey: openssl::pkey::PKey::generate_ed448()?, }) } pub(crate) fn private_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> Ed448PrivateKey { Ed448PrivateKey { pkey: pkey.to_owned(), } } pub(crate) fn public_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> Ed448PublicKey { Ed448PublicKey { pkey: pkey.to_owned(), } } #[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::ED448) .map_err(|_| { pyo3::exceptions::PyValueError::new_err("An Ed448 private key is 56 bytes long") })?; Ok(Ed448PrivateKey { pkey }) } #[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::ED448) .map_err(|_| { pyo3::exceptions::PyValueError::new_err("An Ed448 public key is 57 bytes long") })?; Ok(Ed448PublicKey { pkey }) } #[pyo3::pymethods] impl Ed448PrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, ) -> CryptographyResult> { let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; let len = signer.len()?; Ok(pyo3::types::PyBytes::new_bound_with(py, len, |b| { let n = signer .sign_oneshot(b, data.as_bytes()) .map_err(CryptographyError::from)?; assert_eq!(n, b.len()); Ok(()) })?) } fn public_key(&self) -> CryptographyResult { let raw_bytes = self.pkey.raw_public_key()?; Ok(Ed448PublicKey { pkey: openssl::pkey::PKey::public_key_from_raw_bytes( &raw_bytes, openssl::pkey::Id::ED448, )?, }) } fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, true, true, ) } } #[pyo3::pymethods] impl Ed448PublicKey { fn verify(&self, signature: CffiBuf<'_>, data: CffiBuf<'_>) -> CryptographyResult<()> { let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? .verify_oneshot(signature.as_bytes(), data.as_bytes())?; if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), )); } Ok(()) } fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pymodule] pub(crate) mod ed448 { #[pymodule_export] use super::{ from_private_bytes, from_public_bytes, generate_key, Ed448PrivateKey, Ed448PublicKey, }; } cryptography-43.0.0/src/rust/src/backend/hashes.rs010064400017510000177000000107101464676315000203130ustar 00000000000000// 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. use pyo3::types::PyAnyMethods; use pyo3::IntoPy; use std::borrow::Cow; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; #[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")] pub(crate) struct Hash { #[pyo3(get)] algorithm: pyo3::Py, ctx: Option, } pub(crate) fn already_finalized_error() -> CryptographyError { CryptographyError::from(exceptions::AlreadyFinalized::new_err( "Context was already finalized.", )) } impl Hash { fn get_ctx(&self) -> CryptographyResult<&openssl::hash::Hasher> { if let Some(ctx) = self.ctx.as_ref() { return Ok(ctx); }; Err(already_finalized_error()) } fn get_mut_ctx(&mut self) -> CryptographyResult<&mut openssl::hash::Hasher> { if let Some(ctx) = self.ctx.as_mut() { return Ok(ctx); } Err(already_finalized_error()) } } pub(crate) fn message_digest_from_algorithm( py: pyo3::Python<'_>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { if !algorithm.is_instance(&types::HASH_ALGORITHM.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err("Expected instance of hashes.HashAlgorithm."), )); } let name = algorithm .getattr(pyo3::intern!(py, "name"))? .extract::()?; let openssl_name = if name == "blake2b" || name == "blake2s" { let digest_size = algorithm .getattr(pyo3::intern!(py, "digest_size"))? .extract::()?; Cow::Owned(format!("{}{}", name, digest_size * 8)) } else { Cow::Borrowed(name.as_ref()) }; match openssl::hash::MessageDigest::from_name(&openssl_name) { Some(md) => Ok(md), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!("{name} is not a supported hash on this backend"), exceptions::Reasons::UNSUPPORTED_HASH, )), )), } } impl Hash { pub(crate) fn update_bytes(&mut self, data: &[u8]) -> CryptographyResult<()> { self.get_mut_ctx()?.update(data)?; Ok(()) } } #[pyo3::pymethods] impl Hash { #[new] #[pyo3(signature = (algorithm, backend=None))] pub(crate) fn new( py: pyo3::Python<'_>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, backend: Option<&pyo3::Bound<'_, pyo3::PyAny>>, ) -> CryptographyResult { let _ = backend; let md = message_digest_from_algorithm(py, algorithm)?; let ctx = openssl::hash::Hasher::new(md)?; Ok(Hash { algorithm: algorithm.clone().into_py(py), ctx: Some(ctx), }) } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { self.update_bytes(data.as_bytes()) } pub(crate) fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] { let algorithm = self.algorithm.clone_ref(py); let algorithm = algorithm.bind(py); if algorithm.is_instance(&types::EXTENDABLE_OUTPUT_FUNCTION.get(py)?)? { let ctx = self.get_mut_ctx()?; let digest_size = algorithm .getattr(pyo3::intern!(py, "digest_size"))? .extract::()?; let result = pyo3::types::PyBytes::new_bound_with(py, digest_size, |b| { ctx.finish_xof(b).unwrap(); Ok(()) })?; self.ctx = None; return Ok(result); } } let data = self.get_mut_ctx()?.finish()?; self.ctx = None; Ok(pyo3::types::PyBytes::new_bound(py, &data)) } fn copy(&self, py: pyo3::Python<'_>) -> CryptographyResult { Ok(Hash { algorithm: self.algorithm.clone_ref(py), ctx: Some(self.get_ctx()?.clone()), }) } } #[pyo3::pymodule] pub(crate) mod hashes { #[pymodule_export] use super::Hash; } cryptography-43.0.0/src/rust/src/backend/hmac.rs010064400017510000177000000065361464676315000177630ustar 00000000000000// 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. use crate::backend::hashes::{already_finalized_error, message_digest_from_algorithm}; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; use pyo3::types::PyBytesMethods; #[pyo3::pyclass( module = "cryptography.hazmat.bindings._rust.openssl.hmac", name = "HMAC" )] pub(crate) struct Hmac { #[pyo3(get)] algorithm: pyo3::Py, ctx: Option, } impl Hmac { pub(crate) fn new_bytes( py: pyo3::Python<'_>, key: &[u8], algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let md = message_digest_from_algorithm(py, algorithm)?; let ctx = cryptography_openssl::hmac::Hmac::new(key, md).map_err(|_| { exceptions::UnsupportedAlgorithm::new_err(( "Digest is not supported for HMAC", exceptions::Reasons::UNSUPPORTED_HASH, )) })?; Ok(Hmac { ctx: Some(ctx), algorithm: algorithm.clone().unbind(), }) } pub(crate) fn update_bytes(&mut self, data: &[u8]) -> CryptographyResult<()> { self.get_mut_ctx()?.update(data)?; Ok(()) } fn get_ctx(&self) -> CryptographyResult<&cryptography_openssl::hmac::Hmac> { if let Some(ctx) = self.ctx.as_ref() { return Ok(ctx); }; Err(already_finalized_error()) } fn get_mut_ctx(&mut self) -> CryptographyResult<&mut cryptography_openssl::hmac::Hmac> { if let Some(ctx) = self.ctx.as_mut() { return Ok(ctx); } Err(already_finalized_error()) } } #[pyo3::pymethods] impl Hmac { #[new] #[pyo3(signature = (key, algorithm, backend=None))] fn new( py: pyo3::Python<'_>, key: CffiBuf<'_>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, backend: Option>, ) -> CryptographyResult { let _ = backend; Hmac::new_bytes(py, key.as_bytes(), algorithm) } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { self.update_bytes(data.as_bytes()) } pub(crate) fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let data = self.get_mut_ctx()?.finish()?; self.ctx = None; Ok(pyo3::types::PyBytes::new_bound(py, &data)) } fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { let actual_bound = self.finalize(py)?; let actual = actual_bound.as_bytes(); if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err("Signature did not match digest."), )); } Ok(()) } fn copy(&self, py: pyo3::Python<'_>) -> CryptographyResult { Ok(Hmac { ctx: Some(self.get_ctx()?.copy()?), algorithm: self.algorithm.clone_ref(py), }) } } #[pyo3::pymodule] pub(crate) mod hmac { #[pymodule_export] use super::Hmac; } cryptography-43.0.0/src/rust/src/backend/kdf.rs010064400017510000177000000035221464676315000176070ustar 00000000000000// 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. use crate::backend::hashes; use crate::buf::CffiBuf; use crate::error::CryptographyResult; #[pyo3::pyfunction] pub(crate) fn derive_pbkdf2_hmac<'p>( py: pyo3::Python<'p>, key_material: CffiBuf<'_>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, salt: &[u8], iterations: usize, length: usize, ) -> CryptographyResult> { let md = hashes::message_digest_from_algorithm(py, algorithm)?; Ok(pyo3::types::PyBytes::new_bound_with(py, length, |b| { openssl::pkcs5::pbkdf2_hmac(key_material.as_bytes(), salt, iterations, md, b).unwrap(); Ok(()) })?) } #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] #[pyo3::pyfunction] #[allow(clippy::too_many_arguments)] fn derive_scrypt<'p>( py: pyo3::Python<'p>, key_material: CffiBuf<'_>, salt: &[u8], n: u64, r: u64, p: u64, max_mem: u64, length: usize, ) -> CryptographyResult> { Ok(pyo3::types::PyBytes::new_bound_with(py, length, |b| { openssl::pkcs5::scrypt(key_material.as_bytes(), salt, n, r, p, max_mem, b).map_err(|_| { // memory required formula explained here: // https://blog.filippo.io/the-scrypt-parameters/ let min_memory = 128 * n * r / (1024 * 1024); pyo3::exceptions::PyMemoryError::new_err(format!( "Not enough memory to derive key. These parameters require {min_memory}MB of memory." )) }) })?) } #[pyo3::pymodule] pub(crate) mod kdf { #[pymodule_export] use super::derive_pbkdf2_hmac; #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] #[pymodule_export] use super::derive_scrypt; } cryptography-43.0.0/src/rust/src/backend/keys.rs010064400017510000177000000242301464676315000200150ustar 00000000000000// 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. use pyo3::IntoPy; use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; #[pyo3::pyfunction] #[pyo3(signature = (data, password, backend=None, *, unsafe_skip_rsa_key_validation=false))] fn load_der_private_key( py: pyo3::Python<'_>, data: CffiBuf<'_>, password: Option>, backend: Option>, unsafe_skip_rsa_key_validation: bool, ) -> CryptographyResult { let _ = backend; if let Ok(pkey) = openssl::pkey::PKey::private_key_from_der(data.as_bytes()) { if password.is_some() { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Password was given but private key is not encrypted.", ), )); } return private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation); } let password = password.as_ref().map(CffiBuf::as_bytes); let mut status = utils::PasswordCallbackStatus::Unused; let pkey = openssl::pkey::PKey::private_key_from_pkcs8_callback( data.as_bytes(), utils::password_callback(&mut status, password), ); let pkey = utils::handle_key_load_result(py, pkey, status, password)?; private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation) } #[pyo3::pyfunction] #[pyo3(signature = (data, password, backend=None, *, unsafe_skip_rsa_key_validation=false))] fn load_pem_private_key( py: pyo3::Python<'_>, data: CffiBuf<'_>, password: Option>, backend: Option>, unsafe_skip_rsa_key_validation: bool, ) -> CryptographyResult { let _ = backend; let password = password.as_ref().map(CffiBuf::as_bytes); let mut status = utils::PasswordCallbackStatus::Unused; let pkey = openssl::pkey::PKey::private_key_from_pem_callback( data.as_bytes(), utils::password_callback(&mut status, password), ); let pkey = utils::handle_key_load_result(py, pkey, status, password)?; private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation) } pub(crate) fn private_key_from_pkey( py: pyo3::Python<'_>, pkey: &openssl::pkey::PKeyRef, unsafe_skip_rsa_key_validation: bool, ) -> CryptographyResult { match pkey.id() { openssl::pkey::Id::RSA => Ok(crate::backend::rsa::private_key_from_pkey( pkey, unsafe_skip_rsa_key_validation, )? .into_py(py)), openssl::pkey::Id::RSA_PSS => { // At the moment the way we handle RSA PSS keys is to strip the // PSS constraints from them and treat them as normal RSA keys // Unfortunately the RSA * itself tracks this data so we need to // extract, serialize, and reload it without the constraints. let der_bytes = pkey.rsa()?.private_key_to_der()?; let rsa = openssl::rsa::Rsa::private_key_from_der(&der_bytes)?; let pkey = openssl::pkey::PKey::from_rsa(rsa)?; Ok( crate::backend::rsa::private_key_from_pkey(&pkey, unsafe_skip_rsa_key_validation)? .into_py(py), ) } openssl::pkey::Id::EC => { Ok(crate::backend::ec::private_key_from_pkey(py, pkey)?.into_py(py)) } openssl::pkey::Id::X25519 => { Ok(crate::backend::x25519::private_key_from_pkey(pkey).into_py(py)) } #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] openssl::pkey::Id::X448 => { Ok(crate::backend::x448::private_key_from_pkey(pkey).into_py(py)) } openssl::pkey::Id::ED25519 => { Ok(crate::backend::ed25519::private_key_from_pkey(pkey).into_py(py)) } #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] openssl::pkey::Id::ED448 => { Ok(crate::backend::ed448::private_key_from_pkey(pkey).into_py(py)) } openssl::pkey::Id::DSA => Ok(crate::backend::dsa::private_key_from_pkey(pkey).into_py(py)), openssl::pkey::Id::DH => Ok(crate::backend::dh::private_key_from_pkey(pkey).into_py(py)), #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] openssl::pkey::Id::DHX => Ok(crate::backend::dh::private_key_from_pkey(pkey).into_py(py)), _ => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err("Unsupported key type."), )), } } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] fn load_der_public_key( py: pyo3::Python<'_>, data: CffiBuf<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; load_der_public_key_bytes(py, data.as_bytes()) } pub(crate) fn load_der_public_key_bytes( py: pyo3::Python<'_>, data: &[u8], ) -> CryptographyResult { match cryptography_key_parsing::spki::parse_public_key(data) { Ok(pkey) => public_key_from_pkey(py, &pkey, pkey.id()), // 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) Err(e) => { // Use the original error. let pkey = cryptography_key_parsing::rsa::parse_pkcs1_public_key(data).map_err(|_| e)?; public_key_from_pkey(py, &pkey, pkey.id()) } } } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] fn load_pem_public_key( py: pyo3::Python<'_>, data: CffiBuf<'_>, backend: Option>, ) -> CryptographyResult { let _ = backend; let p = pem::parse(data.as_bytes())?; let pkey = match p.tag() { "RSA PUBLIC KEY" => { // We try to parse it as a PKCS1 first since that's the PEM delimiter, and if // that fails we try to parse it as an SPKI. This is to match the permissiveness // of OpenSSL, which doesn't care about the delimiter. match cryptography_key_parsing::rsa::parse_pkcs1_public_key(p.contents()) { Ok(pkey) => pkey, Err(err) => { let pkey = cryptography_key_parsing::spki::parse_public_key(p.contents()) .map_err(|_| err)?; if pkey.id() != openssl::pkey::Id::RSA { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Incorrect PEM delimiter for key type.", ), )); } pkey } } } "PUBLIC KEY" => cryptography_key_parsing::spki::parse_public_key(p.contents())?, _ => return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Valid PEM but no BEGIN PUBLIC KEY/END PUBLIC KEY delimiters. Are you sure this is a public key?" ))), }; public_key_from_pkey(py, &pkey, pkey.id()) } fn public_key_from_pkey( py: pyo3::Python<'_>, pkey: &openssl::pkey::PKeyRef, id: openssl::pkey::Id, ) -> CryptographyResult { // `id` is a separate argument so we can test this while passing something // unsupported. match id { openssl::pkey::Id::RSA => Ok(crate::backend::rsa::public_key_from_pkey(pkey).into_py(py)), openssl::pkey::Id::EC => { Ok(crate::backend::ec::public_key_from_pkey(py, pkey)?.into_py(py)) } openssl::pkey::Id::X25519 => { Ok(crate::backend::x25519::public_key_from_pkey(pkey).into_py(py)) } #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] openssl::pkey::Id::X448 => Ok(crate::backend::x448::public_key_from_pkey(pkey).into_py(py)), openssl::pkey::Id::ED25519 => { Ok(crate::backend::ed25519::public_key_from_pkey(pkey).into_py(py)) } #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] openssl::pkey::Id::ED448 => { Ok(crate::backend::ed448::public_key_from_pkey(pkey).into_py(py)) } openssl::pkey::Id::DSA => Ok(crate::backend::dsa::public_key_from_pkey(pkey).into_py(py)), openssl::pkey::Id::DH => Ok(crate::backend::dh::public_key_from_pkey(pkey).into_py(py)), #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] openssl::pkey::Id::DHX => Ok(crate::backend::dh::public_key_from_pkey(pkey).into_py(py)), _ => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err("Unsupported key type."), )), } } #[pyo3::pymodule] pub(crate) mod keys { #[pymodule_export] use super::{ load_der_private_key, load_der_public_key, load_pem_private_key, load_pem_public_key, }; } #[cfg(test)] mod tests { #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use super::{private_key_from_pkey, public_key_from_pkey}; #[test] #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn test_public_key_from_pkey_unknown_key() { pyo3::prepare_freethreaded_python(); pyo3::Python::with_gil(|py| { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(&[0; 32], openssl::pkey::Id::X25519) .unwrap(); // Pass a nonsense id for this key to test the unsupported // algorithm path. assert!(public_key_from_pkey(py, &pkey, openssl::pkey::Id::CMAC).is_err()); }); } #[test] #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn test_private_key_from_pkey_unknown_key() { pyo3::prepare_freethreaded_python(); pyo3::Python::with_gil(|py| { let pkey = openssl::pkey::PKey::hmac(&[0; 32]).unwrap(); assert!(private_key_from_pkey(py, &pkey, false).is_err()); }); } } cryptography-43.0.0/src/rust/src/backend/mod.rs010064400017510000177000000013341464676315000176210ustar 00000000000000// 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. pub(crate) mod aead; pub(crate) mod cipher_registry; pub(crate) mod ciphers; pub(crate) mod cmac; pub(crate) mod dh; pub(crate) mod dsa; pub(crate) mod ec; pub(crate) mod ed25519; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] pub(crate) mod ed448; pub(crate) mod hashes; pub(crate) mod hmac; pub(crate) mod kdf; pub(crate) mod keys; pub(crate) mod poly1305; pub(crate) mod rsa; pub(crate) mod utils; pub(crate) mod x25519; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] pub(crate) mod x448; cryptography-43.0.0/src/rust/src/backend/poly1305.rs010064400017510000177000000126611464676315000203430ustar 00000000000000// 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. use crate::backend::hashes::already_finalized_error; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; use pyo3::types::PyBytesMethods; #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] struct Poly1305Boring { context: cryptography_openssl::poly1305::Poly1305State, } #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] impl Poly1305Boring { fn new(key: CffiBuf<'_>) -> CryptographyResult { if key.as_bytes().len() != 32 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long"), )); } let ctx = cryptography_openssl::poly1305::Poly1305State::new(key.as_bytes()); Ok(Poly1305Boring { context: ctx }) } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { self.context.update(data.as_bytes()); Ok(()) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let result = pyo3::types::PyBytes::new_bound_with(py, 16usize, |b| { self.context.finalize(b.as_mut()); Ok(()) })?; Ok(result) } } #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] struct Poly1305Open { signer: openssl::sign::Signer<'static>, } #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] impl Poly1305Open { fn new(key: CffiBuf<'_>) -> CryptographyResult { if cryptography_openssl::fips::is_enabled() { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "poly1305 is not supported by this version of OpenSSL.", exceptions::Reasons::UNSUPPORTED_MAC, )), )); } let pkey = openssl::pkey::PKey::private_key_from_raw_bytes( key.as_bytes(), openssl::pkey::Id::POLY1305, ) .map_err(|_| pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long"))?; Ok(Poly1305Open { signer: openssl::sign::Signer::new_without_digest(&pkey).map_err(|_| { pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long") })?, }) } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { let buf = data.as_bytes(); self.signer.update(buf)?; Ok(()) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let result = pyo3::types::PyBytes::new_bound_with(py, self.signer.len()?, |b| { let n = self.signer.sign(b).unwrap(); assert_eq!(n, b.len()); Ok(()) })?; Ok(result) } } #[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")] struct Poly1305 { #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] inner: Option, #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] inner: Option, } #[pyo3::pymethods] impl Poly1305 { #[new] fn new(key: CffiBuf<'_>) -> CryptographyResult { #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_LIBRESSL))] return Ok(Poly1305 { inner: Some(Poly1305Boring::new(key)?), }); #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] return Ok(Poly1305 { inner: Some(Poly1305Open::new(key)?), }); } #[staticmethod] fn generate_tag<'p>( py: pyo3::Python<'p>, key: CffiBuf<'_>, data: CffiBuf<'_>, ) -> CryptographyResult> { let mut p = Poly1305::new(key)?; p.update(data)?; p.finalize(py) } #[staticmethod] fn verify_tag( py: pyo3::Python<'_>, key: CffiBuf<'_>, data: CffiBuf<'_>, tag: &[u8], ) -> CryptographyResult<()> { let mut p = Poly1305::new(key)?; p.update(data)?; p.verify(py, tag) } fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> { self.inner .as_mut() .map_or(Err(already_finalized_error()), |b| b.update(data)) } fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let res = self .inner .as_mut() .map_or(Err(already_finalized_error()), |b| b.finalize(py)); self.inner = None; res } fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { let actual_bound = self.finalize(py)?; let actual = actual_bound.as_bytes(); if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err("Value did not match computed tag."), )); } Ok(()) } } #[pyo3::pymodule] pub(crate) mod poly1305 { #[pymodule_export] use super::Poly1305; } cryptography-43.0.0/src/rust/src/backend/rsa.rs010064400017510000177000000662571464676315000176460ustar 00000000000000// 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. use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use crate::backend::{hashes, utils}; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; use pyo3::types::PyAnyMethods; #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.rsa", name = "RSAPrivateKey" )] pub(crate) struct RsaPrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.bindings._rust.openssl.rsa", name = "RSAPublicKey" )] pub(crate) struct RsaPublicKey { pkey: openssl::pkey::PKey, } fn check_rsa_private_key( rsa: &openssl::rsa::Rsa, ) -> CryptographyResult<()> { if !rsa.check_key().unwrap_or(false) || rsa.p().unwrap().is_even() || rsa.q().unwrap().is_even() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid private key"), )); } Ok(()) } pub(crate) fn private_key_from_pkey( pkey: &openssl::pkey::PKeyRef, unsafe_skip_rsa_key_validation: bool, ) -> CryptographyResult { if !unsafe_skip_rsa_key_validation { check_rsa_private_key(&pkey.rsa().unwrap())?; } Ok(RsaPrivateKey { pkey: pkey.to_owned(), }) } pub(crate) fn public_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> RsaPublicKey { RsaPublicKey { pkey: pkey.to_owned(), } } #[pyo3::pyfunction] fn generate_private_key(public_exponent: u32, key_size: u32) -> CryptographyResult { let e = openssl::bn::BigNum::from_u32(public_exponent)?; let rsa = openssl::rsa::Rsa::generate_with_e(key_size, &e)?; let pkey = openssl::pkey::PKey::from_rsa(rsa)?; Ok(RsaPrivateKey { pkey }) } fn oaep_hash_supported(md: &openssl::hash::MessageDigest) -> bool { (!cryptography_openssl::fips::is_enabled() && md == &openssl::hash::MessageDigest::sha1()) || md == &openssl::hash::MessageDigest::sha224() || md == &openssl::hash::MessageDigest::sha256() || md == &openssl::hash::MessageDigest::sha384() || md == &openssl::hash::MessageDigest::sha512() } fn setup_encryption_ctx( py: pyo3::Python<'_>, ctx: &mut openssl::pkey_ctx::PkeyCtx, padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { if !padding.is_instance(&types::ASYMMETRIC_PADDING.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Padding must be an instance of AsymmetricPadding.", ), )); } let padding_enum = if padding.is_instance(&types::PKCS1V15.get(py)?)? { openssl::rsa::Padding::PKCS1 } else if padding.is_instance(&types::OAEP.get(py)?)? { if !padding .getattr(pyo3::intern!(py, "_mgf"))? .is_instance(&types::MGF1.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "Only MGF1 is supported.", exceptions::Reasons::UNSUPPORTED_MGF, )), )); } openssl::rsa::Padding::PKCS1_OAEP } else { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!( "{} is not supported by this backend.", padding.getattr(pyo3::intern!(py, "name"))? ), exceptions::Reasons::UNSUPPORTED_PADDING, )), )); }; ctx.set_rsa_padding(padding_enum)?; if padding_enum == openssl::rsa::Padding::PKCS1_OAEP { let mgf1_md = hashes::message_digest_from_algorithm( py, &padding .getattr(pyo3::intern!(py, "_mgf"))? .getattr(pyo3::intern!(py, "_algorithm"))?, )?; let oaep_md = hashes::message_digest_from_algorithm( py, &padding.getattr(pyo3::intern!(py, "_algorithm"))?, )?; if !oaep_hash_supported(&mgf1_md) || !oaep_hash_supported(&oaep_md) { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "This combination of padding and hash algorithm is not supported", exceptions::Reasons::UNSUPPORTED_PADDING, )), )); } ctx.set_rsa_mgf1_md(openssl::md::Md::from_nid(mgf1_md.type_()).unwrap())?; ctx.set_rsa_oaep_md(openssl::md::Md::from_nid(oaep_md.type_()).unwrap())?; if let Some(label) = padding .getattr(pyo3::intern!(py, "_label"))? .extract::>()? { if !label.is_empty() { ctx.set_rsa_oaep_label(&label)?; } } } Ok(()) } fn setup_signature_ctx( py: pyo3::Python<'_>, ctx: &mut openssl::pkey_ctx::PkeyCtx, padding: &pyo3::Bound<'_, pyo3::PyAny>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, key_size: usize, is_signing: bool, ) -> CryptographyResult<()> { if !padding.is_instance(&types::ASYMMETRIC_PADDING.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Padding must be an instance of AsymmetricPadding.", ), )); } let padding_enum = if padding.is_instance(&types::PKCS1V15.get(py)?)? { openssl::rsa::Padding::PKCS1 } else if padding.is_instance(&types::PSS.get(py)?)? { if !padding .getattr(pyo3::intern!(py, "_mgf"))? .is_instance(&types::MGF1.get(py)?)? { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "Only MGF1 is supported.", exceptions::Reasons::UNSUPPORTED_MGF, )), )); } // PSS padding requires a hash algorithm if !algorithm.is_instance(&types::HASH_ALGORITHM.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Expected instance of hashes.HashAlgorithm.", ), )); } if algorithm .getattr(pyo3::intern!(py, "digest_size"))? .extract::()? + 2 > key_size { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Digest too large for key size. Use a larger key or different digest.", ), )); } openssl::rsa::Padding::PKCS1_PSS } else { return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!( "{} is not supported by this backend.", padding.getattr(pyo3::intern!(py, "name"))? ), exceptions::Reasons::UNSUPPORTED_PADDING, )), )); }; if !algorithm.is_none() { let md = hashes::message_digest_from_algorithm(py, algorithm)?; ctx.set_signature_md(openssl::md::Md::from_nid(md.type_()).unwrap()) .or_else(|_| { Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!( "{} is not supported by this backend for RSA signing.", algorithm.getattr(pyo3::intern!(py, "name"))? ), exceptions::Reasons::UNSUPPORTED_HASH, )), )) })?; } ctx.set_rsa_padding(padding_enum).or_else(|_| { Err(exceptions::UnsupportedAlgorithm::new_err(( format!( "{} is not supported for the RSA signature operation", padding.getattr(pyo3::intern!(py, "name"))? ), exceptions::Reasons::UNSUPPORTED_PADDING, ))) })?; if padding_enum == openssl::rsa::Padding::PKCS1_PSS { let salt = padding.getattr(pyo3::intern!(py, "_salt_length"))?; if salt.is_instance(&types::PADDING_MAX_LENGTH.get(py)?)? { ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::MAXIMUM_LENGTH)?; } else if salt.is_instance(&types::PADDING_DIGEST_LENGTH.get(py)?)? { ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; } else if salt.is_instance(&types::PADDING_AUTO.get(py)?)? { if is_signing { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "PSS salt length can only be set to Auto when verifying", ), )); } } else { ctx.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::custom(salt.extract::()?))?; }; let mgf1_md = hashes::message_digest_from_algorithm( py, &padding .getattr(pyo3::intern!(py, "_mgf"))? .getattr(pyo3::intern!(py, "_algorithm"))?, )?; ctx.set_rsa_mgf1_md(openssl::md::Md::from_nid(mgf1_md.type_()).unwrap())?; } Ok(()) } #[pyo3::pymethods] impl RsaPrivateKey { fn sign<'p>( &self, py: pyo3::Python<'p>, data: CffiBuf<'_>, padding: &pyo3::Bound<'p, pyo3::PyAny>, algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let (data, algorithm) = utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.sign_init().map_err(|_| { pyo3::exceptions::PyValueError::new_err("Unable to sign/verify with this key") })?; setup_signature_ctx(py, &mut ctx, padding, &algorithm, self.pkey.size(), true)?; let length = ctx.sign(data.as_bytes(), None)?; Ok(pyo3::types::PyBytes::new_bound_with(py, length, |b| { let length = ctx.sign(data.as_bytes(), Some(b)).map_err(|_| { pyo3::exceptions::PyValueError::new_err( "Digest or salt length too long for key size. Use a larger key or shorter salt length if you are specifying a PSS salt", ) })?; assert_eq!(length, b.len()); Ok(()) })?.into_any()) } fn decrypt<'p>( &self, py: pyo3::Python<'p>, ciphertext: &[u8], padding: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let key_size_bytes = usize::try_from((self.pkey.rsa().unwrap().n().num_bits() + 7) / 8).unwrap(); if key_size_bytes != ciphertext.len() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Ciphertext length must be equal to key size.", ), )); } let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.decrypt_init()?; setup_encryption_ctx(py, &mut ctx, padding)?; // Everything from this line onwards is written with the goal of being // as constant-time as is practical given the constraints of // rust-openssl and our API. See Bleichenbacher's '98 attack on RSA, // and its many many variants. As such, you should not attempt to // change this (particularly to "clean it up") without understanding // why it was written this way (see Chesterton's Fence), and without // measuring to verify you have not introduced observable time // differences. // // Once OpenSSL 3.2.0 is out, this can be simplified, as OpenSSL will // have its own mitigations for Bleichenbacher's attack. let length = ctx.decrypt(ciphertext, None).unwrap(); let mut plaintext = vec![0; length]; let result = ctx.decrypt(ciphertext, Some(&mut plaintext)); let py_result = pyo3::types::PyBytes::new_bound(py, &plaintext[..*result.as_ref().unwrap_or(&length)]); if result.is_err() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Decryption failed"), )); } Ok(py_result) } #[getter] fn key_size(&self) -> i32 { self.pkey.rsa().unwrap().n().num_bits() } fn public_key(&self) -> CryptographyResult { let priv_rsa = self.pkey.rsa().unwrap(); let rsa = openssl::rsa::Rsa::from_public_components( priv_rsa.n().to_owned()?, priv_rsa.e().to_owned()?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_rsa(rsa)?; Ok(RsaPublicKey { pkey }) } fn private_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let rsa = self.pkey.rsa().unwrap(); let py_p = utils::bn_to_py_int(py, rsa.p().unwrap())?; let py_q = utils::bn_to_py_int(py, rsa.q().unwrap())?; let py_d = utils::bn_to_py_int(py, rsa.d())?; let py_dmp1 = utils::bn_to_py_int(py, rsa.dmp1().unwrap())?; let py_dmq1 = utils::bn_to_py_int(py, rsa.dmq1().unwrap())?; let py_iqmp = utils::bn_to_py_int(py, rsa.iqmp().unwrap())?; let py_e = utils::bn_to_py_int(py, rsa.e())?; let py_n = utils::bn_to_py_int(py, rsa.n())?; let public_numbers = RsaPublicNumbers { e: py_e.extract()?, n: py_n.extract()?, }; Ok(RsaPrivateNumbers { p: py_p.extract()?, q: py_q.extract()?, d: py_d.extract()?, dmp1: py_dmp1.extract()?, dmq1: py_dmq1.extract()?, iqmp: py_iqmp.extract()?, public_numbers: pyo3::Py::new(py, public_numbers)?, }) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, true, false, ) } } #[pyo3::pymethods] impl RsaPublicKey { fn verify( &self, py: pyo3::Python<'_>, signature: CffiBuf<'_>, data: CffiBuf<'_>, padding: &pyo3::Bound<'_, pyo3::PyAny>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { let (data, algorithm) = utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.verify_init()?; setup_signature_ctx(py, &mut ctx, padding, &algorithm, self.pkey.size(), false)?; let valid = ctx .verify(data.as_bytes(), signature.as_bytes()) .unwrap_or(false); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), )); } Ok(()) } fn encrypt<'p>( &self, py: pyo3::Python<'p>, plaintext: &[u8], padding: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.encrypt_init()?; setup_encryption_ctx(py, &mut ctx, padding)?; let length = ctx.encrypt(plaintext, None)?; Ok(pyo3::types::PyBytes::new_bound_with(py, length, |b| { let length = ctx .encrypt(plaintext, Some(b)) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Encryption failed"))?; assert_eq!(length, b.len()); Ok(()) })?) } fn recover_data_from_signature<'p>( &self, py: pyo3::Python<'p>, signature: &[u8], padding: &pyo3::Bound<'_, pyo3::PyAny>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { if algorithm.is_instance(&types::PREHASHED.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Prehashed is only supported in the sign and verify methods. It cannot be used with recover_data_from_signature.", ), )); } let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.verify_recover_init()?; setup_signature_ctx(py, &mut ctx, padding, algorithm, self.pkey.size(), false)?; let length = ctx.verify_recover(signature, None)?; let mut buf = vec![0u8; length]; let length = ctx .verify_recover(signature, Some(&mut buf)) .map_err(|_| exceptions::InvalidSignature::new_err(()))?; Ok(pyo3::types::PyBytes::new_bound(py, &buf[..length])) } #[getter] fn key_size(&self) -> i32 { self.pkey.rsa().unwrap().n().num_bits() } fn public_numbers(&self, py: pyo3::Python<'_>) -> CryptographyResult { let rsa = self.pkey.rsa().unwrap(); let py_e = utils::bn_to_py_int(py, rsa.e())?; let py_n = utils::bn_to_py_int(py, rsa.n())?; Ok(RsaPublicNumbers { e: py_e.extract()?, n: py_n.extract()?, }) } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.rsa", name = "RSAPrivateNumbers" )] struct RsaPrivateNumbers { #[pyo3(get)] p: pyo3::Py, #[pyo3(get)] q: pyo3::Py, #[pyo3(get)] d: pyo3::Py, #[pyo3(get)] dmp1: pyo3::Py, #[pyo3(get)] dmq1: pyo3::Py, #[pyo3(get)] iqmp: pyo3::Py, #[pyo3(get)] public_numbers: pyo3::Py, } #[pyo3::pyclass( frozen, module = "cryptography.hazmat.primitives.asymmetric.rsa", name = "RSAPublicNumbers" )] struct RsaPublicNumbers { #[pyo3(get)] e: pyo3::Py, #[pyo3(get)] n: pyo3::Py, } #[allow(clippy::too_many_arguments)] fn check_private_key_components( p: &pyo3::Bound<'_, pyo3::types::PyLong>, q: &pyo3::Bound<'_, pyo3::types::PyLong>, private_exponent: &pyo3::Bound<'_, pyo3::types::PyLong>, dmp1: &pyo3::Bound<'_, pyo3::types::PyLong>, dmq1: &pyo3::Bound<'_, pyo3::types::PyLong>, iqmp: &pyo3::Bound<'_, pyo3::types::PyLong>, public_exponent: &pyo3::Bound<'_, pyo3::types::PyLong>, modulus: &pyo3::Bound<'_, pyo3::types::PyLong>, ) -> CryptographyResult<()> { if modulus.lt(3)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("modulus must be >= 3."), )); } if p.ge(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("p must be < modulus."), )); } if q.ge(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("q must be < modulus."), )); } if dmp1.ge(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("dmp1 must be < modulus."), )); } if dmq1.ge(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("dmq1 must be < modulus."), )); } if iqmp.ge(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("iqmp must be < modulus."), )); } if private_exponent.ge(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("private_exponent must be < modulus."), )); } if public_exponent.lt(3)? || public_exponent.ge(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("public_exponent must be >= 3 and < modulus."), )); } if public_exponent.bitand(1)?.eq(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("public_exponent must be odd."), )); } if dmp1.bitand(1)?.eq(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("dmp1 must be odd."), )); } if dmq1.bitand(1)?.eq(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("dmq1 must be odd."), )); } if p.mul(q)?.ne(modulus)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("p*q must equal modulus."), )); } Ok(()) } #[pyo3::pymethods] impl RsaPrivateNumbers { #[new] fn new( p: pyo3::Py, q: pyo3::Py, d: pyo3::Py, dmp1: pyo3::Py, dmq1: pyo3::Py, iqmp: pyo3::Py, public_numbers: pyo3::Py, ) -> RsaPrivateNumbers { Self { p, q, d, dmp1, dmq1, iqmp, public_numbers, } } #[pyo3(signature = (backend = None, *, unsafe_skip_rsa_key_validation = false))] fn private_key( &self, py: pyo3::Python<'_>, backend: Option<&pyo3::Bound<'_, pyo3::PyAny>>, unsafe_skip_rsa_key_validation: bool, ) -> CryptographyResult { let _ = backend; check_private_key_components( self.p.bind(py), self.q.bind(py), self.d.bind(py), self.dmp1.bind(py), self.dmq1.bind(py), self.iqmp.bind(py), self.public_numbers.get().e.bind(py), self.public_numbers.get().n.bind(py), )?; let public_numbers = self.public_numbers.get(); let rsa = openssl::rsa::Rsa::from_private_components( utils::py_int_to_bn(py, public_numbers.n.bind(py))?, utils::py_int_to_bn(py, public_numbers.e.bind(py))?, utils::py_int_to_bn(py, self.d.bind(py))?, utils::py_int_to_bn(py, self.p.bind(py))?, utils::py_int_to_bn(py, self.q.bind(py))?, utils::py_int_to_bn(py, self.dmp1.bind(py))?, utils::py_int_to_bn(py, self.dmq1.bind(py))?, utils::py_int_to_bn(py, self.iqmp.bind(py))?, ) .unwrap(); if !unsafe_skip_rsa_key_validation { check_rsa_private_key(&rsa)?; } let pkey = openssl::pkey::PKey::from_rsa(rsa)?; Ok(RsaPrivateKey { pkey }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.p.bind(py).eq(other.p.bind(py))? && self.q.bind(py).eq(other.q.bind(py))? && self.d.bind(py).eq(other.d.bind(py))? && self.dmp1.bind(py).eq(other.dmp1.bind(py))? && self.dmq1.bind(py).eq(other.dmq1.bind(py))? && self.iqmp.bind(py).eq(other.iqmp.bind(py))? && self .public_numbers .bind(py) .eq(other.public_numbers.bind(py))?) } fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { let mut hasher = DefaultHasher::new(); self.p.bind(py).hash()?.hash(&mut hasher); self.q.bind(py).hash()?.hash(&mut hasher); self.d.bind(py).hash()?.hash(&mut hasher); self.dmp1.bind(py).hash()?.hash(&mut hasher); self.dmq1.bind(py).hash()?.hash(&mut hasher); self.iqmp.bind(py).hash()?.hash(&mut hasher); self.public_numbers.bind(py).hash()?.hash(&mut hasher); Ok(hasher.finish()) } } fn check_public_key_components( e: &pyo3::Bound<'_, pyo3::types::PyLong>, n: &pyo3::Bound<'_, pyo3::types::PyLong>, ) -> CryptographyResult<()> { if n.lt(3)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("n must be >= 3."), )); } if e.lt(3)? || e.ge(n)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("e must be >= 3 and < n."), )); } if e.bitand(1)?.eq(0)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("e must be odd."), )); } Ok(()) } #[pyo3::pymethods] impl RsaPublicNumbers { #[new] fn new(e: pyo3::Py, n: pyo3::Py) -> RsaPublicNumbers { RsaPublicNumbers { e, n } } #[pyo3(signature = (backend=None))] fn public_key( &self, py: pyo3::Python<'_>, backend: Option<&pyo3::Bound<'_, pyo3::PyAny>>, ) -> CryptographyResult { let _ = backend; check_public_key_components(self.e.bind(py), self.n.bind(py))?; let rsa = openssl::rsa::Rsa::from_public_components( utils::py_int_to_bn(py, self.n.bind(py))?, utils::py_int_to_bn(py, self.e.bind(py))?, ) .unwrap(); let pkey = openssl::pkey::PKey::from_rsa(rsa)?; Ok(RsaPublicKey { pkey }) } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { Ok(self.e.bind(py).eq(other.e.bind(py))? && self.n.bind(py).eq(other.n.bind(py))?) } fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { let mut hasher = DefaultHasher::new(); self.e.bind(py).hash()?.hash(&mut hasher); self.n.bind(py).hash()?.hash(&mut hasher); Ok(hasher.finish()) } fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let e = self.e.bind(py); let n = self.n.bind(py); Ok(format!("")) } } #[pyo3::pymodule] pub(crate) mod rsa { #[pymodule_export] use super::{ generate_private_key, RsaPrivateKey, RsaPrivateNumbers, RsaPublicKey, RsaPublicNumbers, }; } cryptography-43.0.0/src/rust/src/backend/utils.rs010064400017510000177000000431751464676315000202130ustar 00000000000000// 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. use crate::backend::hashes::Hash; use crate::error::{CryptographyError, CryptographyResult}; use crate::{error, types}; use pyo3::types::{PyAnyMethods, PyBytesMethods}; use pyo3::ToPyObject; pub(crate) fn py_int_to_bn( py: pyo3::Python<'_>, v: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let n = v .call_method0(pyo3::intern!(py, "bit_length"))? .extract::()? / 8 + 1; let bytes = v .call_method1(pyo3::intern!(py, "to_bytes"), (n, pyo3::intern!(py, "big")))? .extract::()?; Ok(openssl::bn::BigNum::from_slice(&bytes)?) } pub(crate) fn bn_to_py_int<'p>( py: pyo3::Python<'p>, b: &openssl::bn::BigNumRef, ) -> CryptographyResult> { assert!(!b.is_negative()); let int_type = py.get_type_bound::(); Ok(int_type.call_method1( pyo3::intern!(py, "from_bytes"), (b.to_vec(), pyo3::intern!(py, "big")), )?) } pub(crate) fn bn_to_big_endian_bytes(b: &openssl::bn::BigNumRef) -> CryptographyResult> { Ok(b.to_vec_padded(b.num_bits() / 8 + 1)?) } #[allow(clippy::too_many_arguments)] pub(crate) fn pkey_private_bytes<'p>( py: pyo3::Python<'p>, key_obj: &pyo3::Bound<'p, pyo3::PyAny>, pkey: &openssl::pkey::PKey, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, openssh_allowed: bool, raw_allowed: bool, ) -> CryptographyResult> { if !encoding.is_instance(&types::ENCODING.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "encoding must be an item from the Encoding enum", ), )); } if !format.is_instance(&types::PRIVATE_FORMAT.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "format must be an item from the PrivateFormat enum", ), )); } if !encryption_algorithm.is_instance(&types::KEY_SERIALIZATION_ENCRYPTION.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Encryption algorithm must be a KeySerializationEncryption instance", ), )); } if raw_allowed && (encoding.is(&types::ENCODING_RAW.get(py)?) || format.is(&types::PRIVATE_FORMAT_RAW.get(py)?)) { if !encoding.is(&types::ENCODING_RAW.get(py)?) || !format.is(&types::PRIVATE_FORMAT_RAW.get(py)?) || !encryption_algorithm.is_instance(&types::NO_ENCRYPTION.get(py)?)? { return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "When using Raw both encoding and format must be Raw and encryption_algorithm must be NoEncryption()" ))); } let raw_bytes = pkey.raw_private_key()?; return Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)); } let py_password; let password = if encryption_algorithm.is_instance(&types::NO_ENCRYPTION.get(py)?)? { b"" as &[u8] } else if encryption_algorithm.is_instance(&types::BEST_AVAILABLE_ENCRYPTION.get(py)?)? || (encryption_algorithm.is_instance(&types::ENCRYPTION_BUILDER.get(py)?)? && encryption_algorithm .getattr(pyo3::intern!(py, "_format"))? .is(format)) { py_password = encryption_algorithm .getattr(pyo3::intern!(py, "password"))? .extract::()?; &py_password } else { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Unsupported encryption type"), )); }; if password.len() > 1023 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Passwords longer than 1023 bytes are not supported by this backend", ), )); } if format.is(&types::PRIVATE_FORMAT_PKCS8.get(py)?) { if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { pkey.private_key_to_pem_pkcs8()? } else { pkey.private_key_to_pem_pkcs8_passphrase( openssl::symm::Cipher::aes_256_cbc(), password, )? }; return Ok(pyo3::types::PyBytes::new_bound(py, &pem_bytes)); } else if encoding.is(&types::ENCODING_DER.get(py)?) { let der_bytes = if password.is_empty() { pkey.private_key_to_pkcs8()? } else { pkey.private_key_to_pkcs8_passphrase( openssl::symm::Cipher::aes_256_cbc(), password, )? }; return Ok(pyo3::types::PyBytes::new_bound(py, &der_bytes)); } return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Unsupported encoding for PKCS8"), )); } if format.is(&types::PRIVATE_FORMAT_TRADITIONAL_OPENSSL.get(py)?) { if cryptography_openssl::fips::is_enabled() && !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Encrypted traditional OpenSSL format is not supported in FIPS mode", ), )); } if let Ok(rsa) = pkey.rsa() { if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { rsa.private_key_to_pem()? } else { rsa.private_key_to_pem_passphrase( openssl::symm::Cipher::aes_256_cbc(), password, )? }; return Ok(pyo3::types::PyBytes::new_bound(py, &pem_bytes)); } else if encoding.is(&types::ENCODING_DER.get(py)?) { if !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Encryption is not supported for DER encoded traditional OpenSSL keys", ), )); } let der_bytes = rsa.private_key_to_der()?; return Ok(pyo3::types::PyBytes::new_bound(py, &der_bytes)); } } else if let Ok(dsa) = pkey.dsa() { if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { dsa.private_key_to_pem()? } else { dsa.private_key_to_pem_passphrase( openssl::symm::Cipher::aes_256_cbc(), password, )? }; return Ok(pyo3::types::PyBytes::new_bound(py, &pem_bytes)); } else if encoding.is(&types::ENCODING_DER.get(py)?) { if !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Encryption is not supported for DER encoded traditional OpenSSL keys", ), )); } let der_bytes = dsa.private_key_to_der()?; return Ok(pyo3::types::PyBytes::new_bound(py, &der_bytes)); } } else if let Ok(ec) = pkey.ec_key() { if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = if password.is_empty() { ec.private_key_to_pem()? } else { ec.private_key_to_pem_passphrase( openssl::symm::Cipher::aes_256_cbc(), password, )? }; return Ok(pyo3::types::PyBytes::new_bound(py, &pem_bytes)); } else if encoding.is(&types::ENCODING_DER.get(py)?) { if !password.is_empty() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Encryption is not supported for DER encoded traditional OpenSSL keys", ), )); } let der_bytes = ec.private_key_to_der()?; return Ok(pyo3::types::PyBytes::new_bound(py, &der_bytes)); } } } // OpenSSH + PEM if openssh_allowed && format.is(&types::PRIVATE_FORMAT_OPENSSH.get(py)?) { if encoding.is(&types::ENCODING_PEM.get(py)?) { return Ok(types::SERIALIZE_SSH_PRIVATE_KEY .get(py)? .call1((key_obj, password, encryption_algorithm))? .extract()?); } return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "OpenSSH private key format can only be used with PEM encoding", ), )); } Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("format is invalid with this key"), )) } pub(crate) fn pkey_public_bytes<'p>( py: pyo3::Python<'p>, key_obj: &pyo3::Bound<'p, pyo3::PyAny>, pkey: &openssl::pkey::PKey, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, openssh_allowed: bool, raw_allowed: bool, ) -> CryptographyResult> { if !encoding.is_instance(&types::ENCODING.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "encoding must be an item from the Encoding enum", ), )); } if !format.is_instance(&types::PUBLIC_FORMAT.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "format must be an item from the PublicFormat enum", ), )); } if raw_allowed && (encoding.is(&types::ENCODING_RAW.get(py)?) || format.is(&types::PUBLIC_FORMAT_RAW.get(py)?)) { if !encoding.is(&types::ENCODING_RAW.get(py)?) || !format.is(&types::PUBLIC_FORMAT_RAW.get(py)?) { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "When using Raw both encoding and format must be Raw", ), )); } let raw_bytes = pkey.raw_public_key()?; return Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)); } // SubjectPublicKeyInfo + PEM/DER if format.is(&types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?) { if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = pkey.public_key_to_pem()?; return Ok(pyo3::types::PyBytes::new_bound(py, &pem_bytes)); } else if encoding.is(&types::ENCODING_DER.get(py)?) { let der_bytes = pkey.public_key_to_der()?; return Ok(pyo3::types::PyBytes::new_bound(py, &der_bytes)); } return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "SubjectPublicKeyInfo works only with PEM or DER encoding", ), )); } if let Ok(ec) = pkey.ec_key() { if encoding.is(&types::ENCODING_X962.get(py)?) { let point_form = if format.is(&types::PUBLIC_FORMAT_UNCOMPRESSED_POINT.get(py)?) { openssl::ec::PointConversionForm::UNCOMPRESSED } else if format.is(&types::PUBLIC_FORMAT_COMPRESSED_POINT.get(py)?) { openssl::ec::PointConversionForm::COMPRESSED } else { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "X962 encoding must be used with CompressedPoint or UncompressedPoint format" ) )); }; let mut bn_ctx = openssl::bn::BigNumContext::new()?; let data = ec .public_key() .to_bytes(ec.group(), point_form, &mut bn_ctx)?; return Ok(pyo3::types::PyBytes::new_bound(py, &data)); } } if let Ok(rsa) = pkey.rsa() { if format.is(&types::PUBLIC_FORMAT_PKCS1.get(py)?) { if encoding.is(&types::ENCODING_PEM.get(py)?) { let pem_bytes = rsa.public_key_to_pem_pkcs1()?; return Ok(pyo3::types::PyBytes::new_bound(py, &pem_bytes)); } else if encoding.is(&types::ENCODING_DER.get(py)?) { let der_bytes = rsa.public_key_to_der_pkcs1()?; return Ok(pyo3::types::PyBytes::new_bound(py, &der_bytes)); } return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "PKCS1 works only with PEM or DER encoding", ), )); } } // OpenSSH + OpenSSH if openssh_allowed && format.is(&types::PUBLIC_FORMAT_OPENSSH.get(py)?) { if encoding.is(&types::ENCODING_OPENSSH.get(py)?) { return Ok(types::SERIALIZE_SSH_PUBLIC_KEY .get(py)? .call1((key_obj,))? .extract()?); } return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "OpenSSH format must be used with OpenSSH encoding", ), )); } Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("format is invalid with this key"), )) } pub(crate) enum BytesOrPyBytes<'a> { Bytes(&'a [u8]), PyBytes(pyo3::Bound<'a, pyo3::types::PyBytes>), } impl BytesOrPyBytes<'_> { pub(crate) fn as_bytes(&self) -> &[u8] { match self { BytesOrPyBytes::Bytes(v) => v, BytesOrPyBytes::PyBytes(v) => v.as_bytes(), } } } pub(crate) fn calculate_digest_and_algorithm<'p>( py: pyo3::Python<'p>, data: &'p [u8], algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult<(BytesOrPyBytes<'p>, pyo3::Bound<'p, pyo3::PyAny>)> { let (algorithm, data) = if algorithm.is_instance(&types::PREHASHED.get(py)?)? { ( algorithm.getattr("_algorithm")?, BytesOrPyBytes::Bytes(data), ) } else { // Potential optimization: rather than allocate a PyBytes in // `h.finalize()`, have a way to get the `DigestBytes` directly. let mut h = Hash::new(py, algorithm, None)?; h.update_bytes(data)?; (algorithm.clone(), BytesOrPyBytes::PyBytes(h.finalize(py)?)) }; if data.as_bytes().len() != algorithm.getattr("digest_size")?.extract()? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "The provided data must be the same length as the hash algorithm's digest size.", ), )); } Ok((data, algorithm)) } pub(crate) enum PasswordCallbackStatus { Unused, Used, BufferTooSmall(usize), } pub(crate) fn password_callback<'a>( status: &'a mut PasswordCallbackStatus, password: Option<&'a [u8]>, ) -> impl FnOnce(&mut [u8]) -> Result + 'a { move |buf| { *status = PasswordCallbackStatus::Used; match password.as_ref() { Some(p) if p.len() <= buf.len() => { buf[..p.len()].copy_from_slice(p); Ok(p.len()) } Some(_) => { *status = PasswordCallbackStatus::BufferTooSmall(buf.len()); Ok(0) } None => Ok(0), } } } pub(crate) fn handle_key_load_result( py: pyo3::Python<'_>, pkey: Result, openssl::error::ErrorStack>, status: PasswordCallbackStatus, password: Option<&[u8]>, ) -> CryptographyResult> { match (pkey, status, password) { (Ok(k), PasswordCallbackStatus::Unused, None) | (Ok(k), PasswordCallbackStatus::Used, Some(_)) => Ok(k), (Ok(_), PasswordCallbackStatus::Unused, Some(_)) => Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Password was given but private key is not encrypted.", ), )), (_, PasswordCallbackStatus::Used, None | Some(b"")) => Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( "Password was not given but private key is encrypted", ), )), (_, PasswordCallbackStatus::BufferTooSmall(size), _) => Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "Passwords longer than {size} bytes are not supported" )), )), (Err(e), _, _) => { let errors = error::list_from_openssl_error(py, e); Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(( "Could not deserialize key data. The data may be in an incorrect format, the provided password may be incorrect, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).", errors.to_object(py), )) )) } } } cryptography-43.0.0/src/rust/src/backend/x25519.rs010064400017510000177000000114051464676315000177170ustar 00000000000000// 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. use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::CryptographyResult; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] pub(crate) struct X25519PrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x25519")] pub(crate) struct X25519PublicKey { pkey: openssl::pkey::PKey, } #[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(X25519PrivateKey { pkey: openssl::pkey::PKey::generate_x25519()?, }) } pub(crate) fn private_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> X25519PrivateKey { X25519PrivateKey { pkey: pkey.to_owned(), } } pub(crate) fn public_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> X25519PublicKey { X25519PublicKey { pkey: pkey.to_owned(), } } #[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X25519) .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( "An X25519 private key is 32 bytes long: {e}" )) })?; Ok(X25519PrivateKey { pkey }) } #[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::X25519) .map_err(|_| { pyo3::exceptions::PyValueError::new_err("An X25519 public key is 32 bytes long") })?; Ok(X25519PublicKey { pkey }) } #[pyo3::pymethods] impl X25519PrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, peer_public_key: &X25519PublicKey, ) -> CryptographyResult> { let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; deriver.set_peer(&peer_public_key.pkey)?; Ok(pyo3::types::PyBytes::new_bound_with( py, deriver.len()?, |b| { let n = deriver.derive(b).map_err(|_| { pyo3::exceptions::PyValueError::new_err("Error computing shared key.") })?; assert_eq!(n, b.len()); Ok(()) }, )?) } fn public_key(&self) -> CryptographyResult { let raw_bytes = self.pkey.raw_public_key()?; Ok(X25519PublicKey { pkey: openssl::pkey::PKey::public_key_from_raw_bytes( &raw_bytes, openssl::pkey::Id::X25519, )?, }) } fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, false, true, ) } } #[pyo3::pymethods] impl X25519PublicKey { fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pymodule] pub(crate) mod x25519 { #[pymodule_export] use super::{ from_private_bytes, from_public_bytes, generate_key, X25519PrivateKey, X25519PublicKey, }; } cryptography-43.0.0/src/rust/src/backend/x448.rs010064400017510000177000000112741464676315000175550ustar 00000000000000// 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. use crate::backend::utils; use crate::buf::CffiBuf; use crate::error::CryptographyResult; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] pub(crate) struct X448PrivateKey { pkey: openssl::pkey::PKey, } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.x448")] pub(crate) struct X448PublicKey { pkey: openssl::pkey::PKey, } #[pyo3::pyfunction] fn generate_key() -> CryptographyResult { Ok(X448PrivateKey { pkey: openssl::pkey::PKey::generate_x448()?, }) } pub(crate) fn private_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> X448PrivateKey { X448PrivateKey { pkey: pkey.to_owned(), } } pub(crate) fn public_key_from_pkey( pkey: &openssl::pkey::PKeyRef, ) -> X448PublicKey { X448PublicKey { pkey: pkey.to_owned(), } } #[pyo3::pyfunction] fn from_private_bytes(data: CffiBuf<'_>) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(data.as_bytes(), openssl::pkey::Id::X448) .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( "An X448 private key is 56 bytes long: {e}" )) })?; Ok(X448PrivateKey { pkey }) } #[pyo3::pyfunction] fn from_public_bytes(data: &[u8]) -> pyo3::PyResult { let pkey = openssl::pkey::PKey::public_key_from_raw_bytes(data, openssl::pkey::Id::X448) .map_err(|_| { pyo3::exceptions::PyValueError::new_err("An X448 public key is 32 bytes long") })?; Ok(X448PublicKey { pkey }) } #[pyo3::pymethods] impl X448PrivateKey { fn exchange<'p>( &self, py: pyo3::Python<'p>, peer_public_key: &X448PublicKey, ) -> CryptographyResult> { let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; deriver.set_peer(&peer_public_key.pkey)?; Ok(pyo3::types::PyBytes::new_bound_with( py, deriver.len()?, |b| { let n = deriver.derive(b).map_err(|_| { pyo3::exceptions::PyValueError::new_err("Error computing shared key.") })?; assert_eq!(n, b.len()); Ok(()) }, )?) } fn public_key(&self) -> CryptographyResult { let raw_bytes = self.pkey.raw_public_key()?; Ok(X448PublicKey { pkey: openssl::pkey::PKey::public_key_from_raw_bytes( &raw_bytes, openssl::pkey::Id::X448, )?, }) } fn private_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_private_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn private_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_private_bytes( py, slf, &slf.borrow().pkey, encoding, format, encryption_algorithm, false, true, ) } } #[pyo3::pymethods] impl X448PublicKey { fn public_bytes_raw<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let raw_bytes = self.pkey.raw_public_key()?; Ok(pyo3::types::PyBytes::new_bound(py, &raw_bytes)) } fn public_bytes<'p>( slf: &pyo3::Bound<'p, Self>, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, format: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) } fn __eq__(&self, other: pyo3::PyRef<'_, Self>) -> bool { self.pkey.public_eq(&other.pkey) } fn __copy__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } } #[pyo3::pymodule] pub(crate) mod x448 { #[pymodule_export] use super::{ from_private_bytes, from_public_bytes, generate_key, X448PrivateKey, X448PublicKey, }; } cryptography-43.0.0/src/rust/src/buf.rs010064400017510000177000000075661464676315000162440ustar 00000000000000// 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. use crate::types; use pyo3::types::IntoPyDict; use pyo3::types::PyAnyMethods; use std::slice; pub(crate) struct CffiBuf<'p> { pyobj: pyo3::Bound<'p, pyo3::PyAny>, _bufobj: pyo3::Bound<'p, pyo3::PyAny>, buf: &'p [u8], } fn _extract_buffer_length<'p>( pyobj: &pyo3::Bound<'p, pyo3::PyAny>, mutable: bool, ) -> pyo3::PyResult<(pyo3::Bound<'p, pyo3::PyAny>, usize)> { let py = pyobj.py(); let bufobj = if mutable { let kwargs = [(pyo3::intern!(py, "require_writable"), true)].into_py_dict_bound(py); types::FFI_FROM_BUFFER .get(py)? .call((pyobj,), Some(&kwargs))? } else { types::FFI_FROM_BUFFER.get(py)?.call1((pyobj,))? }; let ptrval = types::FFI_CAST .get(py)? .call1((pyo3::intern!(py, "uintptr_t"), bufobj.clone()))? .call_method0(pyo3::intern!(py, "__int__"))? .extract::()?; Ok((bufobj, ptrval)) } impl<'a> CffiBuf<'a> { pub(crate) fn from_bytes(py: pyo3::Python<'a>, buf: &'a [u8]) -> Self { CffiBuf { pyobj: py.None().into_bound(py), _bufobj: py.None().into_bound(py), buf, } } pub(crate) fn as_bytes(&self) -> &[u8] { self.buf } pub(crate) fn into_pyobj(self) -> pyo3::Bound<'a, pyo3::PyAny> { self.pyobj } } impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { fn extract_bound(pyobj: &pyo3::Bound<'a, pyo3::PyAny>) -> pyo3::PyResult { let (bufobj, ptrval) = _extract_buffer_length(pyobj, false)?; let len = bufobj.len()?; let buf = if len == 0 { &[] } else { // SAFETY: _extract_buffer_length ensures that we have a valid ptr // and length (and we ensure we meet slice's requirements for // 0-length slices above), we're keeping pyobj alive which ensures // the buffer is valid. But! There is no actually guarantee // against concurrent mutation. See // https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ // for details. This is the same as our cffi status quo ante, so // we're doing an unsound thing and living with it. unsafe { slice::from_raw_parts(ptrval as *const u8, len) } }; Ok(CffiBuf { pyobj: pyobj.clone(), _bufobj: bufobj, buf, }) } } pub(crate) struct CffiMutBuf<'p> { _pyobj: pyo3::Bound<'p, pyo3::PyAny>, _bufobj: pyo3::Bound<'p, pyo3::PyAny>, buf: &'p mut [u8], } impl CffiMutBuf<'_> { pub(crate) fn as_mut_bytes(&mut self) -> &mut [u8] { self.buf } } impl<'a> pyo3::conversion::FromPyObject<'a> for CffiMutBuf<'a> { fn extract_bound(pyobj: &pyo3::Bound<'a, pyo3::PyAny>) -> pyo3::PyResult { let (bufobj, ptrval) = _extract_buffer_length(pyobj, true)?; let len = bufobj.len()?; let buf = if len == 0 { &mut [] } else { // SAFETY: _extract_buffer_length ensures that we have a valid ptr // and length (and we ensure we meet slice's requirements for // 0-length slices above), we're keeping pyobj alive which ensures // the buffer is valid. But! There is no actually guarantee // against concurrent mutation. See // https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ // for details. This is the same as our cffi status quo ante, so // we're doing an unsound thing and living with it. unsafe { slice::from_raw_parts_mut(ptrval as *mut u8, len) } }; Ok(CffiMutBuf { _pyobj: pyobj.clone(), _bufobj: bufobj, buf, }) } } cryptography-43.0.0/src/rust/src/error.rs010064400017510000177000000220361464676315000166060ustar 00000000000000// 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. use pyo3::types::PyListMethods; use pyo3::ToPyObject; use crate::exceptions; pub enum CryptographyError { Asn1Parse(asn1::ParseError), Asn1Write(asn1::WriteError), KeyParsing(asn1::ParseError), Py(pyo3::PyErr), OpenSSL(openssl::error::ErrorStack), } impl From for CryptographyError { fn from(e: asn1::ParseError) -> CryptographyError { CryptographyError::Asn1Parse(e) } } impl From for CryptographyError { fn from(e: asn1::WriteError) -> CryptographyError { CryptographyError::Asn1Write(e) } } impl From for CryptographyError { fn from(e: pyo3::PyErr) -> CryptographyError { CryptographyError::Py(e) } } impl From> for CryptographyError { fn from(e: pyo3::DowncastError<'_, '_>) -> CryptographyError { CryptographyError::Py(e.into()) } } impl From for CryptographyError { fn from(e: openssl::error::ErrorStack) -> CryptographyError { CryptographyError::OpenSSL(e) } } impl From for CryptographyError { fn from(e: pem::PemError) -> CryptographyError { CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!( "Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. {e:?}" ))) } } impl From for CryptographyError { fn from(e: cryptography_key_parsing::KeyParsingError) -> CryptographyError { match e { cryptography_key_parsing::KeyParsingError::Parse(e) => CryptographyError::KeyParsing(e), cryptography_key_parsing::KeyParsingError::OpenSSL(e) => CryptographyError::OpenSSL(e), cryptography_key_parsing::KeyParsingError::InvalidKey => { CryptographyError::Py(pyo3::exceptions::PyValueError::new_err("Invalid key")) } cryptography_key_parsing::KeyParsingError::ExplicitCurveUnsupported => { CryptographyError::Py(pyo3::exceptions::PyValueError::new_err( "ECDSA keys with explicit parameters are unsupported at this time", )) } cryptography_key_parsing::KeyParsingError::UnsupportedKeyType(oid) => { CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!( "Unknown key type: {oid}" ))) } cryptography_key_parsing::KeyParsingError::UnsupportedEllipticCurve(oid) => { CryptographyError::Py(exceptions::UnsupportedAlgorithm::new_err(( format!("Curve {oid} is not supported"), exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE, ))) } } } } pub(crate) fn list_from_openssl_error( py: pyo3::Python<'_>, error_stack: openssl::error::ErrorStack, ) -> pyo3::Bound<'_, pyo3::types::PyList> { let errors = pyo3::types::PyList::empty_bound(py); for e in error_stack.errors() { errors .append( pyo3::Bound::new(py, OpenSSLError { e: e.clone() }) .expect("Failed to create OpenSSLError"), ) .expect("Failed to append to list"); } errors } impl From for pyo3::PyErr { fn from(e: CryptographyError) -> pyo3::PyErr { match e { CryptographyError::Asn1Parse(asn1_error) => pyo3::exceptions::PyValueError::new_err( format!("error parsing asn1 value: {asn1_error:?}"), ), CryptographyError::Asn1Write(asn1::WriteError::AllocationError) => { pyo3::exceptions::PyMemoryError::new_err( "failed to allocate memory while performing ASN.1 serialization", ) } CryptographyError::KeyParsing(asn1_error) => pyo3::exceptions::PyValueError::new_err( format!("Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters). Details: {asn1_error}"), ), CryptographyError::Py(py_error) => py_error, CryptographyError::OpenSSL(error_stack) => pyo3::Python::with_gil(|py| { let errors = list_from_openssl_error(py, error_stack); exceptions::InternalError::new_err(( format!( "Unknown OpenSSL error. This error is commonly encountered when another library is not cleaning up the OpenSSL error stack. If you are using cryptography with another library that uses OpenSSL try disabling it before reporting a bug. Otherwise please file an issue at https://github.com/pyca/cryptography/issues with information on how to reproduce this. ({errors:?})" ), errors.to_object(py), )) }), } } } impl CryptographyError { pub(crate) fn add_location(self, loc: asn1::ParseLocation) -> Self { match self { CryptographyError::Py(e) => CryptographyError::Py(e), CryptographyError::Asn1Parse(e) => CryptographyError::Asn1Parse(e.add_location(loc)), CryptographyError::KeyParsing(e) => CryptographyError::KeyParsing(e.add_location(loc)), CryptographyError::Asn1Write(e) => CryptographyError::Asn1Write(e), CryptographyError::OpenSSL(e) => CryptographyError::OpenSSL(e), } } } // The primary purpose of this alias is for brevity to keep function signatures // to a single-line as a work around for coverage issues. See // https://github.com/pyca/cryptography/pull/6173 pub(crate) type CryptographyResult = Result; #[pyo3::pyfunction] pub(crate) fn raise_openssl_error() -> crate::error::CryptographyResult<()> { Err(openssl::error::ErrorStack::get().into()) } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl")] pub(crate) struct OpenSSLError { e: openssl::error::Error, } #[pyo3::pymethods] impl OpenSSLError { #[getter] fn lib(&self) -> i32 { self.e.library_code() } #[getter] fn reason(&self) -> i32 { self.e.reason_code() } #[getter] fn reason_text(&self) -> &[u8] { self.e.reason().unwrap_or("").as_bytes() } fn __repr__(&self) -> pyo3::PyResult { Ok(format!( "", self.e.code(), self.e.library_code(), self.e.reason_code(), self.e.reason().unwrap_or("") )) } } #[pyo3::pyfunction] pub(crate) fn capture_error_stack( py: pyo3::Python<'_>, ) -> pyo3::PyResult> { let errs = pyo3::types::PyList::empty_bound(py); for e in openssl::error::ErrorStack::get().errors() { errs.append(pyo3::Bound::new(py, OpenSSLError { e: e.clone() })?)?; } Ok(errs) } #[cfg(test)] mod tests { use super::CryptographyError; #[test] fn test_cryptographyerror_from() { pyo3::prepare_freethreaded_python(); pyo3::Python::with_gil(|py| { let e: CryptographyError = asn1::WriteError::AllocationError.into(); assert!(matches!( e, CryptographyError::Asn1Write(asn1::WriteError::AllocationError) )); let py_e: pyo3::PyErr = e.into(); assert!(py_e.is_instance_of::(py)); let e: CryptographyError = pyo3::DowncastError::new(py.None().bind(py), "abc").into(); assert!(matches!(e, CryptographyError::Py(_))); let e = cryptography_key_parsing::KeyParsingError::OpenSSL( openssl::error::ErrorStack::get(), ) .into(); assert!(matches!(e, CryptographyError::OpenSSL(_))); }) } #[test] fn test_cryptographyerror_add_location() { let py_err = pyo3::PyErr::new::("Error!"); CryptographyError::Py(py_err).add_location(asn1::ParseLocation::Field("meh")); let asn1_write_err = asn1::WriteError::AllocationError; CryptographyError::Asn1Write(asn1_write_err) .add_location(asn1::ParseLocation::Field("meh")); let openssl_error = openssl::error::ErrorStack::get(); CryptographyError::from(openssl_error).add_location(asn1::ParseLocation::Field("meh")); let asn1_parse_error = asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue); CryptographyError::KeyParsing(asn1_parse_error) .add_location(asn1::ParseLocation::Field("meh")); } } cryptography-43.0.0/src/rust/src/exceptions.rs010064400017510000177000000031151464676315000176330ustar 00000000000000// 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. #[pyo3::pyclass( frozen, eq, module = "cryptography.hazmat.bindings._rust.exceptions", name = "_Reasons" )] #[allow(non_camel_case_types)] #[derive(PartialEq)] pub(crate) enum Reasons { BACKEND_MISSING_INTERFACE, UNSUPPORTED_HASH, UNSUPPORTED_CIPHER, UNSUPPORTED_PADDING, UNSUPPORTED_MGF, UNSUPPORTED_PUBLIC_KEY_ALGORITHM, UNSUPPORTED_ELLIPTIC_CURVE, UNSUPPORTED_SERIALIZATION, UNSUPPORTED_X509, UNSUPPORTED_EXCHANGE_ALGORITHM, UNSUPPORTED_DIFFIE_HELLMAN, UNSUPPORTED_MAC, } pyo3::import_exception_bound!(cryptography.exceptions, AlreadyUpdated); pyo3::import_exception_bound!(cryptography.exceptions, AlreadyFinalized); pyo3::import_exception_bound!(cryptography.exceptions, InternalError); pyo3::import_exception_bound!(cryptography.exceptions, InvalidSignature); pyo3::import_exception_bound!(cryptography.exceptions, InvalidTag); pyo3::import_exception_bound!(cryptography.exceptions, NotYetFinalized); pyo3::import_exception_bound!(cryptography.exceptions, UnsupportedAlgorithm); pyo3::import_exception_bound!(cryptography.x509, AttributeNotFound); pyo3::import_exception_bound!(cryptography.x509, DuplicateExtension); pyo3::import_exception_bound!(cryptography.x509, UnsupportedGeneralNameType); pyo3::import_exception_bound!(cryptography.x509, InvalidVersion); #[pyo3::pymodule] pub(crate) mod exceptions { #[pymodule_export] use super::Reasons; } cryptography-43.0.0/src/rust/src/lib.rs010064400017510000177000000177751464676315000162410ustar 00000000000000// 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. #![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)] #![allow(unknown_lints, non_local_definitions, clippy::result_large_err)] #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] use crate::error::CryptographyResult; #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] use openssl::provider; #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] use std::env; mod asn1; mod backend; mod buf; mod error; mod exceptions; pub(crate) mod oid; mod padding; mod pkcs12; mod pkcs7; mod test_support; pub(crate) mod types; mod x509; #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] #[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust")] struct LoadedProviders { legacy: Option, _default: provider::Provider, fips: Option, } #[pyo3::pyfunction] fn openssl_version() -> i64 { openssl::version::number() } #[pyo3::pyfunction] fn openssl_version_text() -> &'static str { openssl::version::version() } #[pyo3::pyfunction] fn is_fips_enabled() -> bool { cryptography_openssl::fips::is_enabled() } #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] fn _initialize_providers() -> CryptographyResult { // As of OpenSSL 3.0.0 we must register a legacy cipher provider // to get RC2 (needed for junk asymmetric private key // serialization), RC4, Blowfish, IDEA, SEED, etc. These things // are ugly legacy, but we aren't going to get rid of them // any time soon. let load_legacy = env::var("CRYPTOGRAPHY_OPENSSL_NO_LEGACY") .map(|v| v.is_empty() || v == "0") .unwrap_or(true); let legacy = if load_legacy { let legacy_result = provider::Provider::load(None, "legacy"); _legacy_provider_error(legacy_result.is_ok())?; Some(legacy_result?) } else { None }; let _default = provider::Provider::load(None, "default")?; Ok(LoadedProviders { legacy, _default, fips: None, }) } fn _legacy_provider_error(success: bool) -> pyo3::PyResult<()> { if !success { return Err(pyo3::exceptions::PyRuntimeError::new_err( "OpenSSL 3.0's legacy provider failed to load. This is a fatal error by default, but cryptography supports running without legacy algorithms by setting the environment variable CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error, you have likely made a mistake with your OpenSSL configuration." )); } Ok(()) } #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] #[pyo3::pyfunction] fn enable_fips(providers: &mut LoadedProviders) -> CryptographyResult<()> { providers.fips = Some(provider::Provider::load(None, "fips")?); cryptography_openssl::fips::enable()?; Ok(()) } #[pyo3::pymodule] mod _rust { use pyo3::types::PyModuleMethods; #[pymodule_export] use crate::asn1::asn1_mod; #[pymodule_export] use crate::exceptions::exceptions; #[pymodule_export] use crate::oid::ObjectIdentifier; #[pymodule_export] use crate::padding::{check_ansix923_padding, check_pkcs7_padding, PKCS7PaddingContext}; #[pymodule_export] use crate::pkcs12::pkcs12; #[pymodule_export] use crate::pkcs7::pkcs7_mod; #[pymodule_export] use crate::test_support::test_support; #[pyo3::pymodule] mod x509 { #[pymodule_export] use crate::x509::certificate::{ create_x509_certificate, load_der_x509_certificate, load_pem_x509_certificate, load_pem_x509_certificates, Certificate, }; #[pymodule_export] use crate::x509::common::{encode_extension_value, encode_name_bytes}; #[pymodule_export] use crate::x509::crl::{ create_x509_crl, load_der_x509_crl, load_pem_x509_crl, CertificateRevocationList, RevokedCertificate, }; #[pymodule_export] use crate::x509::csr::{ create_x509_csr, load_der_x509_csr, load_pem_x509_csr, CertificateSigningRequest, }; #[pymodule_export] use crate::x509::sct::Sct; #[pymodule_export] use crate::x509::verify::{ PolicyBuilder, PyClientVerifier, PyServerVerifier, PyStore, PyVerifiedClient, VerificationError, }; } #[pyo3::pymodule] mod ocsp { #[pymodule_export] use crate::x509::ocsp_req::{create_ocsp_request, load_der_ocsp_request, OCSPRequest}; #[pymodule_export] use crate::x509::ocsp_resp::{ create_ocsp_response, load_der_ocsp_response, OCSPResponse, OCSPSingleResponse, }; } #[pyo3::pymodule] mod openssl { use pyo3::prelude::PyModuleMethods; #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] #[pymodule_export] use super::super::enable_fips; #[pymodule_export] use super::super::{is_fips_enabled, openssl_version, openssl_version_text}; #[pymodule_export] use crate::backend::aead::aead; #[pymodule_export] use crate::backend::ciphers::ciphers; #[pymodule_export] use crate::backend::cmac::cmac; #[pymodule_export] use crate::backend::dh::dh; #[pymodule_export] use crate::backend::dsa::dsa; #[pymodule_export] use crate::backend::ec::ec; #[pymodule_export] use crate::backend::ed25519::ed25519; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] #[pymodule_export] use crate::backend::ed448::ed448; #[pymodule_export] use crate::backend::hashes::hashes; #[pymodule_export] use crate::backend::hmac::hmac; #[pymodule_export] use crate::backend::kdf::kdf; #[pymodule_export] use crate::backend::keys::keys; #[pymodule_export] use crate::backend::poly1305::poly1305; #[pymodule_export] use crate::backend::rsa::rsa; #[pymodule_export] use crate::backend::x25519::x25519; #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] #[pymodule_export] use crate::backend::x448::x448; #[pymodule_export] use crate::error::{capture_error_stack, raise_openssl_error, OpenSSLError}; #[pymodule_init] fn init(openssl_mod: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { openssl_mod.add( "CRYPTOGRAPHY_OPENSSL_300_OR_GREATER", cfg!(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), )?; openssl_mod.add( "CRYPTOGRAPHY_OPENSSL_320_OR_GREATER", cfg!(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER), )?; openssl_mod.add("CRYPTOGRAPHY_IS_LIBRESSL", cfg!(CRYPTOGRAPHY_IS_LIBRESSL))?; openssl_mod.add("CRYPTOGRAPHY_IS_BORINGSSL", cfg!(CRYPTOGRAPHY_IS_BORINGSSL))?; cfg_if::cfg_if! { if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] { let providers = super::super::_initialize_providers()?; if providers.legacy.is_some() { openssl_mod.add("_legacy_provider_loaded", true)?; } else { openssl_mod.add("_legacy_provider_loaded", false)?; } openssl_mod.add("_providers", providers)?; } else { // default value for non-openssl 3+ openssl_mod.add("_legacy_provider_loaded", false)?; } } Ok(()) } } #[pymodule_init] fn init(m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { m.add_submodule(&cryptography_cffi::create_module(m.py())?)?; Ok(()) } } #[cfg(test)] mod tests { use super::_legacy_provider_error; #[test] fn test_legacy_provider_error() { assert!(_legacy_provider_error(true).is_ok()); assert!(_legacy_provider_error(false).is_err()); } } cryptography-43.0.0/src/rust/src/oid.rs010064400017510000177000000035221464676315000162270ustar 00000000000000// 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. use crate::error::CryptographyResult; use crate::types; use pyo3::types::PyAnyMethods; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust")] pub(crate) struct ObjectIdentifier { pub(crate) oid: asn1::ObjectIdentifier, } #[pyo3::pymethods] impl ObjectIdentifier { #[new] fn new(value: &str) -> CryptographyResult { let oid = asn1::ObjectIdentifier::from_string(value) .ok_or_else(|| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?; Ok(ObjectIdentifier { oid }) } #[getter] fn dotted_string(&self) -> String { self.oid.to_string() } #[getter] fn _name<'p>( slf: pyo3::PyRef<'_, Self>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { types::OID_NAMES .get(py)? .call_method1(pyo3::intern!(py, "get"), (slf, "Unknown OID")) } fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { slf } fn __repr__(slf: &pyo3::Bound<'_, Self>, py: pyo3::Python<'_>) -> pyo3::PyResult { let name = Self::_name(slf.borrow(), py)?; Ok(format!( "", slf.get().oid, name.extract::()? )) } fn __eq__(&self, other: pyo3::PyRef<'_, ObjectIdentifier>) -> bool { self.oid == other.oid } fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.oid.hash(&mut hasher); hasher.finish() } } cryptography-43.0.0/src/rust/src/padding.rs010064400017510000177000000077551464676315000170760ustar 00000000000000// 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. use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::exceptions; /// Returns the value of the input with the most-significant-bit copied to all /// of the bits. fn duplicate_msb_to_all(a: u8) -> u8 { 0u8.wrapping_sub(a >> 7) } /// This returns 0xFF if a < b else 0x00, but does so in a constant time /// fashion. fn constant_time_lt(a: u8, b: u8) -> u8 { // Derived from: // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1i/include/internal/constant_time.h#L120 duplicate_msb_to_all(a ^ ((a ^ b) | (a.wrapping_sub(b) ^ b))) } #[pyo3::pyfunction] pub(crate) fn check_pkcs7_padding(data: &[u8]) -> bool { let mut mismatch = 0; let pad_size = *data.last().unwrap(); let len: u8 = data.len().try_into().expect("data too long"); for (i, b) in (0..len).zip(data.iter().rev()) { let mask = constant_time_lt(i, pad_size); mismatch |= mask & (pad_size ^ b); } // Check to make sure the pad_size was within the valid range. mismatch |= !constant_time_lt(0, pad_size); mismatch |= constant_time_lt(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 (mismatch & 1) == 0 } #[pyo3::pyfunction] pub(crate) fn check_ansix923_padding(data: &[u8]) -> bool { let mut mismatch = 0; let pad_size = *data.last().unwrap(); let len: u8 = data.len().try_into().expect("data too long"); // Skip the first one with the pad size for (i, b) in (1..len).zip(data[..data.len() - 1].iter().rev()) { let mask = constant_time_lt(i, pad_size); mismatch |= mask & b; } // Check to make sure the pad_size was within the valid range. mismatch |= !constant_time_lt(0, pad_size); mismatch |= constant_time_lt(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 (mismatch & 1) == 0 } #[pyo3::pyclass] pub(crate) struct PKCS7PaddingContext { block_size: usize, length_seen: Option, } #[pyo3::pymethods] impl PKCS7PaddingContext { #[new] pub(crate) fn new(block_size: usize) -> PKCS7PaddingContext { PKCS7PaddingContext { block_size: block_size / 8, length_seen: Some(0), } } pub(crate) fn update<'a>( &mut self, buf: CffiBuf<'a>, ) -> CryptographyResult> { match self.length_seen.as_mut() { Some(v) => { *v += buf.as_bytes().len(); Ok(buf.into_pyobj()) } None => Err(CryptographyError::from( exceptions::AlreadyFinalized::new_err("Context was already finalized."), )), } } pub(crate) fn finalize<'p>( &mut self, py: pyo3::Python<'p>, ) -> CryptographyResult> { match self.length_seen.take() { Some(v) => { let pad_size = self.block_size - (v % self.block_size); let pad = vec![pad_size as u8; pad_size]; Ok(pyo3::types::PyBytes::new_bound(py, &pad)) } None => Err(CryptographyError::from( exceptions::AlreadyFinalized::new_err("Context was already finalized."), )), } } } #[cfg(test)] mod tests { use super::constant_time_lt; #[test] fn test_constant_time_lt() { for a in 0..=255 { for b in 0..=255 { let expected = if a < b { 0xff } else { 0 }; assert_eq!(constant_time_lt(a, b), expected); } } } } cryptography-43.0.0/src/rust/src/pkcs12.rs010064400017510000177000001036271464676315000165660ustar 00000000000000// 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. use crate::backend::{ciphers, hashes, hmac, kdf, keys}; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::padding::PKCS7PaddingContext; use crate::x509::certificate::Certificate; use crate::{types, x509}; use cryptography_x509::common::Utf8StoredBMPString; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use pyo3::IntoPy; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; #[pyo3::pyclass(frozen)] struct PKCS12Certificate { #[pyo3(get)] certificate: pyo3::Py, #[pyo3(get)] friendly_name: Option>, } #[pyo3::pymethods] impl PKCS12Certificate { #[new] #[pyo3(signature = (cert, friendly_name=None))] fn new( cert: pyo3::Py, friendly_name: Option>, ) -> PKCS12Certificate { PKCS12Certificate { certificate: cert, friendly_name, } } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>, ) -> CryptographyResult { let friendly_name_eq = match (&self.friendly_name, &other.friendly_name) { (Some(a), Some(b)) => a.bind(py).as_bytes() == b.bind(py).as_bytes(), (None, None) => true, _ => false, }; Ok(friendly_name_eq && self.certificate.bind(py).eq(other.certificate.bind(py))?) } fn __hash__(&self, py: pyo3::Python<'_>) -> CryptographyResult { let mut hasher = DefaultHasher::new(); self.certificate.bind(py).hash()?.hash(&mut hasher); match &self.friendly_name { Some(v) => v.bind(py).hash()?.hash(&mut hasher), None => None::.hash(&mut hasher), }; Ok(hasher.finish()) } fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let py_friendly_name_repr; let friendly_name_repr = match &self.friendly_name { Some(v) => { py_friendly_name_repr = v .bind(py) .repr()? .extract::()?; &*py_friendly_name_repr } None => "None", }; Ok(format!( "", self.certificate.bind(py).str()?, friendly_name_repr )) } } pub(crate) fn symmetric_encrypt( py: pyo3::Python<'_>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, mode: pyo3::Bound<'_, pyo3::PyAny>, data: &[u8], ) -> CryptographyResult> { let block_size = algorithm .getattr(pyo3::intern!(py, "block_size"))? .extract()?; let mut cipher = ciphers::CipherContext::new(py, algorithm, mode, openssl::symm::Mode::Encrypt)?; let mut ciphertext = vec![0; data.len() + (block_size / 8 * 2)]; let n = cipher.update_into(py, data, &mut ciphertext)?; let mut padder = PKCS7PaddingContext::new(block_size); assert!(padder.update(CffiBuf::from_bytes(py, data))?.is_none()); let padding = padder.finalize(py)?; let pad_n = cipher.update_into(py, padding.as_bytes(), &mut ciphertext[n..])?; let final_block = cipher.finalize(py)?; assert!(final_block.as_bytes().is_empty()); ciphertext.truncate(n + pad_n); Ok(ciphertext) } enum EncryptionAlgorithm { PBESv1SHA1And3KeyTripleDESCBC, PBESv2SHA256AndAES256CBC, } impl EncryptionAlgorithm { fn salt_length(&self) -> usize { match self { EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => 8, EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => 16, } } fn algorithm_identifier<'a>( &self, cipher_kdf_iter: u64, salt: &'a [u8], iv: &'a [u8], ) -> cryptography_x509::common::AlgorithmIdentifier<'a> { match self { EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => { cryptography_x509::common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: cryptography_x509::common::AlgorithmParameters::Pbes1WithShaAnd3KeyTripleDesCbc(cryptography_x509::common::PBES1Params{ salt: salt[..8].try_into().unwrap(), iterations: cipher_kdf_iter, }), } } EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { let kdf_algorithm_identifier = cryptography_x509::common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: cryptography_x509::common::AlgorithmParameters::Pbkdf2( cryptography_x509::common::PBKDF2Params { salt, iteration_count: cipher_kdf_iter, key_length: None, prf: Box::new(cryptography_x509::common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: cryptography_x509::common::AlgorithmParameters::HmacWithSha256( (), ), }), }, ), }; let encryption_algorithm_identifier = cryptography_x509::common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: cryptography_x509::common::AlgorithmParameters::Aes256Cbc( iv[..16].try_into().unwrap(), ), }; cryptography_x509::common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: cryptography_x509::common::AlgorithmParameters::Pbes2( cryptography_x509::common::PBES2Params { key_derivation_func: Box::new(kdf_algorithm_identifier), encryption_scheme: Box::new(encryption_algorithm_identifier), }, ), } } } } fn encrypt( &self, py: pyo3::Python<'_>, password: &[u8], cipher_kdf_iter: u64, salt: &[u8], iv: &[u8], data: &[u8], ) -> CryptographyResult> { match self { EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC => { let key = pkcs12_kdf( password, salt, KDF_ENCRYPTION_KEY_ID, cipher_kdf_iter, 24, openssl::hash::MessageDigest::sha1(), )?; let iv = pkcs12_kdf( password, salt, KDF_IV_ID, cipher_kdf_iter, 8, openssl::hash::MessageDigest::sha1(), )?; let triple_des = types::TRIPLE_DES .get(py)? .call1((pyo3::types::PyBytes::new_bound(py, &key),))?; let cbc = types::CBC .get(py)? .call1((pyo3::types::PyBytes::new_bound(py, &iv),))?; symmetric_encrypt(py, triple_des, cbc, data) } EncryptionAlgorithm::PBESv2SHA256AndAES256CBC => { let pass_buf = CffiBuf::from_bytes(py, password); let sha256 = types::SHA256.get(py)?.call0()?; let key = kdf::derive_pbkdf2_hmac( py, pass_buf, &sha256, salt, cipher_kdf_iter.try_into().unwrap(), 32, )?; let aes256 = types::AES256.get(py)?.call1((key,))?; let cbc = types::CBC.get(py)?.call1((iv,))?; symmetric_encrypt(py, aes256, cbc, data) } } } } const KDF_ENCRYPTION_KEY_ID: u8 = 1; const KDF_IV_ID: u8 = 2; const KDF_MAC_KEY_ID: u8 = 3; fn pkcs12_kdf( pass: &[u8], salt: &[u8], id: u8, rounds: u64, key_len: usize, hash_alg: openssl::hash::MessageDigest, ) -> CryptographyResult> { // Encode the password as big-endian UTF-16 with NUL trailer let pass = std::str::from_utf8(pass) .map_err(|_| pyo3::exceptions::PyValueError::new_err("key must be valid UTF-8"))? .encode_utf16() .chain([0]) .flat_map(|v| v.to_be_bytes()) .collect::>(); // Comments are borrowed from BoringSSL. // In the spec, |block_size| is called "v", but measured in bits. let block_size = hash_alg.block_size(); // 1. Construct a string, D (the "diversifier"), by concatenating v/8 copies // of ID. let d = vec![id; block_size]; // 2. Concatenate copies of the salt together to create a string S of length // v(ceiling(s/v)) bits (the final copy of the salt may be truncated to // create S). Note that if the salt is the empty string, then so is S. // // 3. Concatenate copies of the password together to create a string P of // length v(ceiling(p/v)) bits (the final copy of the password may be // truncated to create P). Note that if the password is the empty string, // then so is P. // // 4. Set I=S||P to be the concatenation of S and P. let s_len = block_size * ((salt.len() + block_size - 1) / block_size); let p_len = block_size * ((pass.len() + block_size - 1) / block_size); let mut init_key = vec![0; s_len + p_len]; for i in 0..s_len { init_key[i] = salt[i % salt.len()]; } for i in 0..p_len { init_key[i + s_len] = pass[i % pass.len()]; } let mut result = vec![0; key_len]; let mut pos = 0; loop { // A. Set A_i=H^r(D||I). (i.e., the r-th hash of D||I, // H(H(H(... H(D||I)))) let mut h = openssl::hash::Hasher::new(hash_alg)?; h.update(&d)?; h.update(&init_key)?; let mut a = h.finish()?; for _ in 1..rounds { let mut h = openssl::hash::Hasher::new(hash_alg)?; h.update(&a)?; a = h.finish()?; } let to_add = a.len().min(result.len() - pos); result[pos..pos + to_add].copy_from_slice(&a[..to_add]); pos += to_add; if pos == result.len() { break; } // B. Concatenate copies of A_i to create a string B of length v bits (the // final copy of A_i may be truncated to create B). let mut b = vec![0; block_size]; for i in 0..block_size { b[i] = a[i % a.len()]; } // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit blocks, // where k=ceiling(s/v)+ceiling(p/v), modify I by setting I_j=(I_j+B+1) mod // 2^v for each j. assert!(init_key.len() % block_size == 0); let mut j = 0; while j < init_key.len() { let mut carry = 1u16; let mut k = block_size - 1; loop { carry += init_key[k + j] as u16 + b[k] as u16; init_key[j + k] = carry as u8; carry >>= 8; if k == 0 { break; } k -= 1; } j += block_size; } } Ok(result) } fn friendly_name_attributes( friendly_name: Option<&[u8]>, ) -> CryptographyResult< Option< asn1::SetOfWriter< '_, cryptography_x509::pkcs12::Attribute<'_>, Vec>, >, >, > { if let Some(name) = friendly_name { let name_str = std::str::from_utf8(name).map_err(|_| { pyo3::exceptions::PyValueError::new_err("friendly_name must be valid UTF-8") })?; Ok(Some(asn1::SetOfWriter::new(vec![ cryptography_x509::pkcs12::Attribute { _attr_id: asn1::DefinedByMarker::marker(), attr_values: cryptography_x509::pkcs12::AttributeSet::FriendlyName( asn1::SetOfWriter::new([Utf8StoredBMPString::new(name_str)]), ), }, ]))) } else { Ok(None) } } fn cert_to_bag<'a>( cert: &'a Certificate, friendly_name: Option<&'a [u8]>, ) -> CryptographyResult> { Ok(cryptography_x509::pkcs12::SafeBag { _bag_id: asn1::DefinedByMarker::marker(), bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::CertBag( cryptography_x509::pkcs12::CertBag { _cert_id: asn1::DefinedByMarker::marker(), cert_value: asn1::Explicit::new(cryptography_x509::pkcs12::CertType::X509( asn1::OctetStringEncoded::new(cert.raw.borrow_dependent().clone()), )), }, )), attributes: friendly_name_attributes(friendly_name)?, }) } #[allow(clippy::type_complexity)] fn decode_encryption_algorithm<'a>( py: pyo3::Python<'a>, encryption_algorithm: pyo3::Bound<'a, pyo3::PyAny>, ) -> CryptographyResult<( pyo3::pybacked::PyBackedBytes, pyo3::Bound<'a, pyo3::PyAny>, u64, u64, Option, )> { let default_hmac_alg = types::SHA256.get(py)?.call0()?; let default_hmac_kdf_iter = 2048; let default_cipher_kdf_iter = 20000; if encryption_algorithm.is_instance(&types::NO_ENCRYPTION.get(py)?)? { Ok(( pyo3::types::PyBytes::new_bound(py, b"").extract()?, default_hmac_alg, default_hmac_kdf_iter, default_cipher_kdf_iter, None, )) } else if encryption_algorithm.is_instance(&types::ENCRYPTION_BUILDER.get(py)?)? && encryption_algorithm .getattr(pyo3::intern!(py, "_format"))? .is(&types::PRIVATE_FORMAT_PKCS12.get(py)?) { let key_cert_alg = encryption_algorithm.getattr(pyo3::intern!(py, "_key_cert_algorithm"))?; let cipher = if key_cert_alg.is(&types::PBES_PBESV1SHA1AND3KEYTRIPLEDESCBC.get(py)?) { EncryptionAlgorithm::PBESv1SHA1And3KeyTripleDESCBC } else if key_cert_alg.is(&types::PBES_PBESV2SHA256ANDAES256CBC.get(py)?) { EncryptionAlgorithm::PBESv2SHA256AndAES256CBC } else { assert!(key_cert_alg.is_none()); EncryptionAlgorithm::PBESv2SHA256AndAES256CBC }; let hmac_alg = if let Some(v) = encryption_algorithm .getattr(pyo3::intern!(py, "_hmac_hash"))? .extract()? { v } else { default_hmac_alg }; let cipher_kdf_iter = if let Some(v) = encryption_algorithm .getattr(pyo3::intern!(py, "_kdf_rounds"))? .extract()? { v } else { default_cipher_kdf_iter }; Ok(( encryption_algorithm .getattr(pyo3::intern!(py, "password"))? .extract()?, hmac_alg, default_hmac_kdf_iter, cipher_kdf_iter, Some(cipher), )) } else if encryption_algorithm.is_instance(&types::BEST_AVAILABLE_ENCRYPTION.get(py)?)? { Ok(( encryption_algorithm .getattr(pyo3::intern!(py, "password"))? .extract()?, default_hmac_alg, default_hmac_kdf_iter, default_cipher_kdf_iter, Some(EncryptionAlgorithm::PBESv2SHA256AndAES256CBC), )) } else { Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Unsupported key encryption type"), )) } } #[derive(pyo3::FromPyObject)] enum CertificateOrPKCS12Certificate { Certificate(pyo3::Py), PKCS12Certificate(pyo3::Py), } #[pyo3::pyfunction] #[pyo3(signature = (name, key, cert, cas, encryption_algorithm))] fn serialize_key_and_certificates<'p>( py: pyo3::Python<'p>, name: Option<&[u8]>, key: Option>, cert: Option<&Certificate>, cas: Option>, encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let (password, mac_algorithm, mac_kdf_iter, cipher_kdf_iter, encryption_algorithm) = decode_encryption_algorithm(py, encryption_algorithm)?; let mut auth_safe_contents = vec![]; let ( cert_bag_contents, cert_salt, cert_iv, cert_ciphertext, key_bag_contents, key_salt, key_iv, key_ciphertext, ); let mut ca_certs = vec![]; if cert.is_some() || cas.is_some() { let mut cert_bags = vec![]; if let Some(cert) = cert { if let Some(ref key) = key { if !cert .public_key(py)? .into_bound(py) .eq(key.call_method0(pyo3::intern!(py, "public_key"))?)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Certificate public key and provided private key do not match", ), )); } } cert_bags.push(cert_to_bag(cert, name)?); } if let Some(cas) = cas { for cert in cas.iter()? { ca_certs.push(cert?.extract::()?); } for cert in &ca_certs { let bag = match cert { CertificateOrPKCS12Certificate::Certificate(c) => cert_to_bag(c.get(), None)?, CertificateOrPKCS12Certificate::PKCS12Certificate(c) => cert_to_bag( c.get().certificate.get(), c.get().friendly_name.as_ref().map(|v| v.as_bytes(py)), )?, }; cert_bags.push(bag); } } cert_bag_contents = asn1::write_single(&asn1::SequenceOfWriter::new(cert_bags))?; if let Some(e) = &encryption_algorithm { cert_salt = types::OS_URANDOM .get(py)? .call1((e.salt_length(),))? .extract::()?; cert_iv = types::OS_URANDOM .get(py)? .call1((16,))? .extract::()?; cert_ciphertext = e.encrypt( py, &password, cipher_kdf_iter, &cert_salt, &cert_iv, &cert_bag_contents, )?; auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: cryptography_x509::pkcs7::Content::EncryptedData(asn1::Explicit::new( cryptography_x509::pkcs7::EncryptedData { version: 0, encrypted_content_info: cryptography_x509::pkcs7::EncryptedContentInfo { content_type: cryptography_x509::pkcs7::PKCS7_DATA_OID, content_encryption_algorithm: e.algorithm_identifier( cipher_kdf_iter, &cert_salt, &cert_iv, ), encrypted_content: Some(&cert_ciphertext), }, }, )), }) } else { auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( &cert_bag_contents, ))), }); } } if let Some(key) = key { let der = types::ENCODING_DER.get(py)?; let pkcs8 = types::PRIVATE_FORMAT_PKCS8.get(py)?; let no_encryption = types::NO_ENCRYPTION.get(py)?.call0()?; let pkcs8_bytes = key .call_method1( pyo3::intern!(py, "private_bytes"), (der, pkcs8, no_encryption), )? .extract::()?; let key_bag = if let Some(e) = encryption_algorithm { key_salt = types::OS_URANDOM .get(py)? .call1((e.salt_length(),))? .extract::()?; key_iv = types::OS_URANDOM .get(py)? .call1((16,))? .extract::()?; key_ciphertext = e.encrypt( py, &password, cipher_kdf_iter, &key_salt, &key_iv, &pkcs8_bytes, )?; cryptography_x509::pkcs12::SafeBag { _bag_id: asn1::DefinedByMarker::marker(), bag_value: asn1::Explicit::new( cryptography_x509::pkcs12::BagValue::ShroudedKeyBag( cryptography_x509::pkcs12::EncryptedPrivateKeyInfo { encryption_algorithm: e.algorithm_identifier( cipher_kdf_iter, &key_salt, &key_iv, ), encrypted_data: &key_ciphertext, }, ), ), attributes: friendly_name_attributes(name)?, } } else { let pkcs8_tlv = asn1::parse_single(&pkcs8_bytes)?; cryptography_x509::pkcs12::SafeBag { _bag_id: asn1::DefinedByMarker::marker(), bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::KeyBag( pkcs8_tlv, )), attributes: friendly_name_attributes(name)?, } }; key_bag_contents = asn1::write_single(&asn1::SequenceOfWriter::new([key_bag]))?; auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( &key_bag_contents, ))), }); } let auth_safe_content = asn1::write_single(&asn1::SequenceOfWriter::new(auth_safe_contents))?; let salt = types::OS_URANDOM .get(py)? .call1((8,))? .extract::()?; let mac_algorithm_md = hashes::message_digest_from_algorithm(py, &mac_algorithm)?; let mac_key = pkcs12_kdf( &password, &salt, KDF_MAC_KEY_ID, mac_kdf_iter, mac_algorithm_md.size(), mac_algorithm_md, )?; let mac_digest = { let mut h = hmac::Hmac::new_bytes(py, &mac_key, &mac_algorithm)?; h.update_bytes(&auth_safe_content)?; h.finalize(py)? }; let mac_algorithm_identifier = crate::x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS [&*mac_algorithm .getattr(pyo3::intern!(py, "name"))? .extract::()?] .clone(); let p12 = cryptography_x509::pkcs12::Pfx { version: 3, auth_safe: cryptography_x509::pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new( &auth_safe_content, ))), }, mac_data: Some(cryptography_x509::pkcs12::MacData { mac: cryptography_x509::pkcs7::DigestInfo { algorithm: mac_algorithm_identifier, digest: mac_digest.as_bytes(), }, salt: &salt, iterations: mac_kdf_iter, }), }; Ok(pyo3::types::PyBytes::new_bound( py, &asn1::write_single(&p12)?, )) } fn decode_p12( data: CffiBuf<'_>, password: Option>, ) -> CryptographyResult { let p12 = openssl::pkcs12::Pkcs12::from_der(data.as_bytes()).map_err(|_| { pyo3::exceptions::PyValueError::new_err("Could not deserialize PKCS12 data") })?; let password = if let Some(p) = password.as_ref() { std::str::from_utf8(p.as_bytes()) .map_err(|_| pyo3::exceptions::PyUnicodeDecodeError::new_err(()))? } else { // Treat `password=None` the same as empty string. They're actually // not the same in PKCS#12, but OpenSSL transparently handles them the // same. "" }; let parsed = p12 .parse2(password) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Invalid password or PKCS12 data"))?; Ok(parsed) } #[pyo3::pyfunction] #[pyo3(signature = (data, password, backend=None))] fn load_key_and_certificates<'p>( py: pyo3::Python<'p>, data: CffiBuf<'_>, password: Option>, backend: Option>, ) -> CryptographyResult<( pyo3::PyObject, Option, pyo3::Bound<'p, pyo3::types::PyList>, )> { let _ = backend; let p12 = decode_p12(data, password)?; let private_key = if let Some(pkey) = p12.pkey { keys::private_key_from_pkey(py, &pkey, false)? } else { py.None() }; let cert = if let Some(ossl_cert) = p12.cert { let cert_der = pyo3::types::PyBytes::new_bound(py, &ossl_cert.to_der()?).unbind(); Some(x509::certificate::load_der_x509_certificate( py, cert_der, None, )?) } else { None }; let additional_certs = pyo3::types::PyList::empty_bound(py); if let Some(ossl_certs) = p12.ca { cfg_if::cfg_if! { if #[cfg(any( CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL ))] { let it = ossl_certs.iter(); } else { let it = ossl_certs.iter().rev(); } }; for ossl_cert in it { let cert_der = pyo3::types::PyBytes::new_bound(py, &ossl_cert.to_der()?).unbind(); let cert = x509::certificate::load_der_x509_certificate(py, cert_der, None)?; additional_certs.append(cert.into_py(py))?; } } Ok((private_key, cert, additional_certs)) } #[pyo3::pyfunction] #[pyo3(signature = (data, password, backend=None))] fn load_pkcs12<'p>( py: pyo3::Python<'p>, data: CffiBuf<'_>, password: Option>, backend: Option>, ) -> CryptographyResult> { let _ = backend; let p12 = decode_p12(data, password)?; let private_key = if let Some(pkey) = p12.pkey { keys::private_key_from_pkey(py, &pkey, false)? } else { py.None() }; let cert = if let Some(ossl_cert) = p12.cert { let cert_der = pyo3::types::PyBytes::new_bound(py, &ossl_cert.to_der()?).unbind(); let cert = x509::certificate::load_der_x509_certificate(py, cert_der, None)?; let alias = ossl_cert .alias() .map(|a| pyo3::types::PyBytes::new_bound(py, a).unbind()); PKCS12Certificate::new(pyo3::Py::new(py, cert)?, alias).into_py(py) } else { py.None() }; let additional_certs = pyo3::types::PyList::empty_bound(py); if let Some(ossl_certs) = p12.ca { cfg_if::cfg_if! { if #[cfg(any( CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, CRYPTOGRAPHY_IS_BORINGSSL ))] { let it = ossl_certs.iter(); } else { let it = ossl_certs.iter().rev(); } }; for ossl_cert in it { let cert_der = pyo3::types::PyBytes::new_bound(py, &ossl_cert.to_der()?).unbind(); let cert = x509::certificate::load_der_x509_certificate(py, cert_der, None)?; let alias = ossl_cert .alias() .map(|a| pyo3::types::PyBytes::new_bound(py, a).unbind()); let p12_cert = PKCS12Certificate::new(pyo3::Py::new(py, cert)?, alias).into_py(py); additional_certs.append(p12_cert)?; } } Ok(types::PKCS12KEYANDCERTIFICATES .get(py)? .call1((private_key, cert, additional_certs))?) } #[pyo3::pymodule] pub(crate) mod pkcs12 { #[pymodule_export] use super::{ load_key_and_certificates, load_pkcs12, serialize_key_and_certificates, PKCS12Certificate, }; } #[cfg(test)] mod tests { use super::{pkcs12_kdf, KDF_ENCRYPTION_KEY_ID, KDF_IV_ID, KDF_MAC_KEY_ID}; #[test] fn test_pkcs12_kdf() { for (password, salt, id, rounds, key_len, hash, expected_key) in [ // From https://github.com/RustCrypto/formats/blob/master/pkcs12/tests/kdf.rs ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7F\xa3\x17~[\x07h\xa3\x11\x8b\xf8c" as &[u8]), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97sI\xdbn&\xcc\xc9\x98\xd9\xe8\xf8=l"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84G\x02\xc2\xc1\xf3\xb4c!\xe2RJM"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9dO\xb7\x9c\x1e\x0c\x85y\xb7"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2\xfa\xda\x85\xb3 \x1a\x97s"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 20, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF\xd69V\xdb_\xf0k\x84"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xfa\xe4\xd4\x95z<\xc7\x81\xe1\x18\x0b\x9d"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\xe5\xff\x81;\xc6T}\xe5\x15[\x14\xd2"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 100, 12, openssl::hash::MessageDigest::sha256(), b"\x13cU\xed\x944Qf\x82SOF"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05'"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_IV_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"dr\xc0\xeb\xad?\xabA#\xe8\xb5\xedx4\xde!\xee\xb2\x01\x87\xb3\xef\xf7\x8a}\x1c\xdf\xfa@4\x85\x1d"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_MAC_KEY_ID, 1000, 32, openssl::hash::MessageDigest::sha256(), b"?\x91\x13\xf0\\0\xa9\x96\xc4\xa5\x16@\x9b\xda\xc9\xd0e\xf4B\x96\xcc\xd5+\xb7]\xe3\xfc\xfd\xbe+\xf10"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 100, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 1000, 200, openssl::hash::MessageDigest::sha256(), b"+\x95\xa0V\x9bc\xf6A\xfa\xe1\xef\xca2\xe8M\xb3i\x9a\xb7E@b\x8b\xa6b\x83\xb5\x8c\xf5@\x05\'\xd8\xd0\xeb\xe2\xcc\xbfv\x8cQ\xc4\xd8\xfb\xd1\xbb\x15k\xe0l\x1cY\xcb\xb6\x9eD\x05/\xfc77o\xdbG\xb2\xde\x7f\x9eT=\xe9\xd0\x96\xd8\xe5GK\"\x04\x10\xff\x1c]\x8b\xb7\xe5\xbc\x0fa\xba\xea\xa1/\xd0\xda\x1dz\x97\x01r\x9c\xea`\x14\xd7\xfeb\xa2\xed\x92m\xc3ka0\x7f\x11\x9dd\xed\xbc\xebZ\x9cX\x13;\xbfu\xba\x0b\xef\x00\n\x1aQ\x80\xe4\xb1\xde}\x89\xc8\x95(\xbc\xb7\x89\x9a\x1eF\xfdM\xa0\xd9\xde\x8f\x8ee\xe8\xd0\xd7u\xe3=\x12G\xe7mYj401a\xb2\x19\xf3\x9a\xfd\xa4H\xbfQ\x8a(5\xfc^(\xf0\xb5Z\x1ba7\xa2\xc7\x0c\xf7"), ("ge@äheim".as_bytes(), b"\x01\x02\x03\x04\x05\x06\x07\x08", KDF_ENCRYPTION_KEY_ID, 100, 32, openssl::hash::MessageDigest::sha512(), b"\xb1J\x9f\x01\xbf\xd9\xdc\xe4\xc9\xd6m/\xe9\x93~_\xd9\xf1\xaf\xa5\x9e7\no\xa4\xfc\x81\xc1\xcc\x8e\xc8\xee"), // From https://cs.opensource.google/go/x/crypto/+/master:pkcs12/pbkdf_test.go (b"sesame", b"\xff\xff\xff\xff\xff\xff\xff\xff", KDF_ENCRYPTION_KEY_ID, 2048, 24, openssl::hash::MessageDigest::sha1(), b"\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1"), ] { let result = pkcs12_kdf(password, salt, id, rounds, key_len, hash).map_err(|_| ()).unwrap(); assert_eq!(result, expected_key); } } #[test] fn test_pkcs12_kdf_error() { // Key is not valid UTF-8 let result = pkcs12_kdf( b"\x91\x82%\xa1", b"\x01\x02\x03\x04", KDF_ENCRYPTION_KEY_ID, 100, 8, openssl::hash::MessageDigest::sha256(), ); assert!(matches!(result, Err(_))); } } cryptography-43.0.0/src/rust/src/pkcs7.rs010064400017510000177000000532101464676315000165020ustar 00000000000000// 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. use std::borrow::Cow; use std::collections::HashMap; use std::ops::Deref; use cryptography_x509::common::{AlgorithmIdentifier, AlgorithmParameters}; use cryptography_x509::csr::Attribute; use cryptography_x509::pkcs7::PKCS7_DATA_OID; use cryptography_x509::{common, oid, pkcs7}; use once_cell::sync::Lazy; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use openssl::pkcs7::Pkcs7; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use pyo3::IntoPy; use crate::asn1::encode_der_data; use crate::buf::CffiBuf; use crate::error::{CryptographyError, CryptographyResult}; use crate::pkcs12::symmetric_encrypt; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use crate::x509::certificate::load_der_x509_certificate; use crate::{exceptions, types, x509}; const PKCS7_CONTENT_TYPE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 3); const PKCS7_MESSAGE_DIGEST_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 4); const PKCS7_SIGNING_TIME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 5); const PKCS7_SMIME_CAP_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 15); static OIDS_TO_MIC_NAME: Lazy> = Lazy::new(|| { let mut h = HashMap::new(); h.insert(&oid::SHA224_OID, "sha-224"); h.insert(&oid::SHA256_OID, "sha-256"); h.insert(&oid::SHA384_OID, "sha-384"); h.insert(&oid::SHA512_OID, "sha-512"); h }); #[pyo3::pyfunction] fn serialize_certificates<'p>( py: pyo3::Python<'p>, py_certs: Vec>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { if py_certs.is_empty() { return Err(pyo3::exceptions::PyTypeError::new_err( "certs must be a list of certs with length >= 1", ) .into()); } let raw_certs = py_certs .iter() .map(|c| c.raw.borrow_dependent()) .collect::>(); let signed_data = pkcs7::SignedData { version: 1, digest_algorithms: asn1::SetOfWriter::new(&[]), content_info: pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: pkcs7::Content::Data(None), }, certificates: Some(asn1::SetOfWriter::new(&raw_certs)), crls: None, signer_infos: asn1::SetOfWriter::new(&[]), }; let content_info = pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: pkcs7::Content::SignedData(asn1::Explicit::new(Box::new(signed_data))), }; let content_info_bytes = asn1::write_single(&content_info)?; encode_der_data(py, "PKCS7".to_string(), content_info_bytes, encoding) } #[pyo3::pyfunction] fn encrypt_and_serialize<'p>( py: pyo3::Python<'p>, builder: &pyo3::Bound<'p, pyo3::PyAny>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, options: &pyo3::Bound<'p, pyo3::types::PyList>, ) -> CryptographyResult> { let raw_data: CffiBuf<'p> = builder.getattr(pyo3::intern!(py, "_data"))?.extract()?; let text_mode = options.contains(types::PKCS7_TEXT.get(py)?)?; let data_with_header = if options.contains(types::PKCS7_BINARY.get(py)?)? { Cow::Borrowed(raw_data.as_bytes()) } else { smime_canonicalize(raw_data.as_bytes(), text_mode).0 }; // The message is encrypted with AES-128-CBC, which the S/MIME v3.2 RFC // specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7) let key = types::OS_URANDOM.get(py)?.call1((16,))?; let aes128_algorithm = types::AES128.get(py)?.call1((&key,))?; let iv = types::OS_URANDOM.get(py)?.call1((16,))?; let cbc_mode = types::CBC.get(py)?.call1((&iv,))?; let encrypted_content = symmetric_encrypt(py, aes128_algorithm, cbc_mode, &data_with_header)?; let py_recipients: Vec> = builder .getattr(pyo3::intern!(py, "_recipients"))? .extract()?; let mut recipient_infos = vec![]; let padding = types::PKCS1V15.get(py)?.call0()?; let ka_bytes = cryptography_keepalive::KeepAlive::new(); for cert in py_recipients.iter() { // Currently, keys are encrypted with RSA (PKCS #1 v1.5), which the S/MIME v3.2 RFC // specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.3) let encrypted_key = cert .call_method0(pyo3::intern!(py, "public_key"))? .call_method1(pyo3::intern!(py, "encrypt"), (&key, &padding))? .extract::()?; recipient_infos.push(pkcs7::RecipientInfo { version: 0, issuer_and_serial_number: pkcs7::IssuerAndSerialNumber { issuer: cert.get().raw.borrow_dependent().tbs_cert.issuer.clone(), serial_number: cert.get().raw.borrow_dependent().tbs_cert.serial, }, key_encryption_algorithm: AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Rsa(Some(())), }, encrypted_key: ka_bytes.add(encrypted_key), }); } let enveloped_data = pkcs7::EnvelopedData { version: 0, recipient_infos: asn1::SetOfWriter::new(&recipient_infos), encrypted_content_info: pkcs7::EncryptedContentInfo { content_type: PKCS7_DATA_OID, content_encryption_algorithm: AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: AlgorithmParameters::Aes128Cbc(iv.extract()?), }, encrypted_content: Some(&encrypted_content), }, }; let content_info = pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: pkcs7::Content::EnvelopedData(asn1::Explicit::new(Box::new(enveloped_data))), }; let ci_bytes = asn1::write_single(&content_info)?; if encoding.is(&types::ENCODING_SMIME.get(py)?) { Ok(types::SMIME_ENVELOPED_ENCODE .get(py)? .call1((&*ci_bytes,))? .extract()?) } else { // Handles the DER, PEM, and error cases encode_der_data(py, "PKCS7".to_string(), ci_bytes, encoding) } } #[pyo3::pyfunction] fn sign_and_serialize<'p>( py: pyo3::Python<'p>, builder: &pyo3::Bound<'p, pyo3::PyAny>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, options: &pyo3::Bound<'p, pyo3::types::PyList>, ) -> CryptographyResult> { let raw_data: CffiBuf<'p> = builder.getattr(pyo3::intern!(py, "_data"))?.extract()?; let text_mode = options.contains(types::PKCS7_TEXT.get(py)?)?; let (data_with_header, data_without_header) = if options.contains(types::PKCS7_BINARY.get(py)?)? { ( Cow::Borrowed(raw_data.as_bytes()), Cow::Borrowed(raw_data.as_bytes()), ) } else { smime_canonicalize(raw_data.as_bytes(), text_mode) }; let content_type_bytes = asn1::write_single(&pkcs7::PKCS7_DATA_OID)?; let now = x509::common::datetime_now(py)?; let signing_time_bytes = asn1::write_single(&x509::certificate::time_from_datetime(now)?)?; let smime_cap_bytes = asn1::write_single(&asn1::SequenceOfWriter::new([ // Subset of values OpenSSL provides: // https://github.com/openssl/openssl/blob/667a8501f0b6e5705fd611d5bb3ca24848b07154/crypto/pkcs7/pk7_smime.c#L150 // removing all the ones that are bad cryptography &asn1::SequenceOfWriter::new([oid::AES_256_CBC_OID]), &asn1::SequenceOfWriter::new([oid::AES_192_CBC_OID]), &asn1::SequenceOfWriter::new([oid::AES_128_CBC_OID]), ]))?; #[allow(clippy::type_complexity)] let py_signers: Vec<( pyo3::PyRef<'p, x509::certificate::Certificate>, pyo3::Bound<'_, pyo3::PyAny>, pyo3::Bound<'_, pyo3::PyAny>, pyo3::Bound<'_, pyo3::PyAny>, )> = builder.getattr(pyo3::intern!(py, "_signers"))?.extract()?; let py_certs: Vec> = builder .getattr(pyo3::intern!(py, "_additional_certs"))? .extract()?; let mut signer_infos = vec![]; let mut digest_algs = vec![]; let mut certs = py_certs .iter() .map(|p| p.raw.borrow_dependent()) .collect::>(); let ka_vec = cryptography_keepalive::KeepAlive::new(); let ka_bytes = cryptography_keepalive::KeepAlive::new(); for (cert, py_private_key, py_hash_alg, rsa_padding) in py_signers.iter() { let (authenticated_attrs, signature) = if options.contains(&types::PKCS7_NO_ATTRIBUTES.get(py)?)? { ( None, x509::sign::sign_data( py, py_private_key.clone(), py_hash_alg.clone(), rsa_padding.clone(), &data_with_header, )?, ) } else { let mut authenticated_attrs = vec![ Attribute { type_id: PKCS7_CONTENT_TYPE_OID, values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( [asn1::parse_single(&content_type_bytes).unwrap()], )), }, Attribute { type_id: PKCS7_SIGNING_TIME_OID, values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( [asn1::parse_single(&signing_time_bytes).unwrap()], )), }, ]; let digest = x509::ocsp::hash_data(py, py_hash_alg, &data_with_header)?; let digest_wrapped = ka_vec.add(asn1::write_single(&digest.as_bytes())?); authenticated_attrs.push(Attribute { type_id: PKCS7_MESSAGE_DIGEST_OID, values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ asn1::parse_single(digest_wrapped).unwrap(), ])), }); if !options.contains(types::PKCS7_NO_CAPABILITIES.get(py)?)? { authenticated_attrs.push(Attribute { type_id: PKCS7_SMIME_CAP_OID, values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new( [asn1::parse_single(&smime_cap_bytes).unwrap()], )), }); } let signed_data = asn1::write_single(&asn1::SetOfWriter::new(authenticated_attrs.as_slice()))?; ( Some(common::Asn1ReadableOrWritable::new_write( asn1::SetOfWriter::new(authenticated_attrs), )), x509::sign::sign_data( py, py_private_key.clone(), py_hash_alg.clone(), rsa_padding.clone(), &signed_data, )?, ) }; let digest_alg = x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[&*py_hash_alg .getattr(pyo3::intern!(py, "name"))? .extract::()?] .clone(); // Technically O(n^2), but no one will have that many signers. if !digest_algs.contains(&digest_alg) { digest_algs.push(digest_alg.clone()); } certs.push(cert.raw.borrow_dependent()); signer_infos.push(pkcs7::SignerInfo { version: 1, issuer_and_serial_number: pkcs7::IssuerAndSerialNumber { issuer: cert.raw.borrow_dependent().tbs_cert.issuer.clone(), serial_number: cert.raw.borrow_dependent().tbs_cert.serial, }, digest_algorithm: digest_alg, authenticated_attributes: authenticated_attrs, digest_encryption_algorithm: compute_pkcs7_signature_algorithm( py, py_private_key.clone(), py_hash_alg.clone(), rsa_padding.clone(), )?, encrypted_digest: ka_bytes.add(signature), unauthenticated_attributes: None, }); } let data_tlv_bytes; let content = if options.contains(types::PKCS7_DETACHED_SIGNATURE.get(py)?)? { None } else { data_tlv_bytes = asn1::write_single(&data_with_header.deref())?; Some(asn1::parse_single(&data_tlv_bytes).unwrap()) }; let signed_data = pkcs7::SignedData { version: 1, digest_algorithms: asn1::SetOfWriter::new(&digest_algs), content_info: pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: pkcs7::Content::Data(content.map(asn1::Explicit::new)), }, certificates: if options.contains(types::PKCS7_NO_CERTS.get(py)?)? { None } else { Some(asn1::SetOfWriter::new(&certs)) }, crls: None, signer_infos: asn1::SetOfWriter::new(&signer_infos), }; let content_info = pkcs7::ContentInfo { _content_type: asn1::DefinedByMarker::marker(), content: pkcs7::Content::SignedData(asn1::Explicit::new(Box::new(signed_data))), }; let ci_bytes = asn1::write_single(&content_info)?; if encoding.is(&types::ENCODING_SMIME.get(py)?) { let mic_algs = digest_algs .iter() .map(|d| OIDS_TO_MIC_NAME[&d.oid()]) .collect::>() .join(","); Ok(types::SMIME_SIGNED_ENCODE .get(py)? .call1((&*data_without_header, &*ci_bytes, mic_algs, text_mode))? .extract()?) } else { // Handles the DER, PEM, and error cases encode_der_data(py, "PKCS7".to_string(), ci_bytes, encoding) } } fn compute_pkcs7_signature_algorithm<'p>( py: pyo3::Python<'p>, private_key: pyo3::Bound<'p, pyo3::PyAny>, hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, ) -> pyo3::PyResult> { let key_type = x509::sign::identify_key_type(py, private_key.clone())?; let has_pss_padding = rsa_padding.is_instance(&types::PSS.get(py)?)?; // For RSA signatures (with no PSS padding), the OID is always the same no matter the // digest algorithm. See RFC 3370 (section 3.2). if key_type == x509::sign::KeyType::Rsa && !has_pss_padding { Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Rsa(Some(())), }) } else { x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding) } } fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [u8]>) { let mut new_data_with_header = vec![]; let mut new_data_without_header = vec![]; if text_mode { new_data_with_header.extend_from_slice(b"Content-Type: text/plain\r\n\r\n"); } let mut last_idx = 0; for (i, c) in data.iter().copied().enumerate() { if c == b'\n' && (i == 0 || data[i - 1] != b'\r') { new_data_with_header.extend_from_slice(&data[last_idx..i]); new_data_with_header.push(b'\r'); new_data_with_header.push(b'\n'); new_data_without_header.extend_from_slice(&data[last_idx..i]); new_data_without_header.push(b'\r'); new_data_without_header.push(b'\n'); last_idx = i + 1; } } // If there's stuff in new_data, that means we need to copy the rest of // data over. if !new_data_with_header.is_empty() { new_data_with_header.extend_from_slice(&data[last_idx..]); new_data_without_header.extend_from_slice(&data[last_idx..]); ( Cow::Owned(new_data_with_header), Cow::Owned(new_data_without_header), ) } else { (Cow::Borrowed(data), Cow::Borrowed(data)) } } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] fn load_pkcs7_certificates( py: pyo3::Python<'_>, pkcs7: Pkcs7, ) -> CryptographyResult> { let nid = pkcs7.type_().map(|t| t.nid()); if nid != Some(openssl::nid::Nid::PKCS7_SIGNED) { let nid_string = nid.map_or("empty".to_string(), |n| n.as_raw().to_string()); return Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( format!("Only basic signed structures are currently supported. NID for this data was {}", nid_string), exceptions::Reasons::UNSUPPORTED_SERIALIZATION, )), )); } let signed_certificates = pkcs7.signed().and_then(|x| x.certificates()); match signed_certificates { None => Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "The provided PKCS7 has no certificate data, but a cert loading method was called.", ), )), Some(certificates) => { let result = pyo3::types::PyList::empty_bound(py); for c in certificates { let cert_der = pyo3::types::PyBytes::new_bound(py, c.to_der()?.as_slice()).unbind(); let cert = load_der_x509_certificate(py, cert_der, None)?; result.append(cert.into_py(py))?; } Ok(result) } } } #[pyo3::pyfunction] fn load_pem_pkcs7_certificates<'p>( py: pyo3::Python<'p>, data: &[u8], ) -> CryptographyResult> { cfg_if::cfg_if! { if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_pem(data).map_err(|_| { CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Unable to parse PKCS7 data", )) })?; load_pkcs7_certificates(py, pkcs7_decoded) } else { let _ = py; let _ = data; Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "PKCS#7 is not supported by this backend.", exceptions::Reasons::UNSUPPORTED_SERIALIZATION, )), )) } } } #[pyo3::pyfunction] fn load_der_pkcs7_certificates<'p>( py: pyo3::Python<'p>, data: &[u8], ) -> CryptographyResult> { cfg_if::cfg_if! { if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] { let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_der(data).map_err(|_| { CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Unable to parse PKCS7 data", )) })?; load_pkcs7_certificates(py, pkcs7_decoded) } else { let _ = py; let _ = data; Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(( "PKCS#7 is not supported by this backend.", exceptions::Reasons::UNSUPPORTED_SERIALIZATION, )), )) } } } #[pyo3::pymodule] #[pyo3(name = "pkcs7")] pub(crate) mod pkcs7_mod { #[pymodule_export] use super::{ encrypt_and_serialize, load_der_pkcs7_certificates, load_pem_pkcs7_certificates, serialize_certificates, sign_and_serialize, }; } #[cfg(test)] mod tests { use std::borrow::Cow; use std::ops::Deref; use super::smime_canonicalize; #[test] fn test_smime_canonicalize() { for ( input, text_mode, expected_with_header, expected_without_header, expected_is_borrowed, ) in [ // Values with text_mode=false (b"" as &[u8], false, b"" as &[u8], b"" as &[u8], true), (b"\n", false, b"\r\n", b"\r\n", false), (b"abc", false, b"abc", b"abc", true), ( b"abc\r\ndef\n", false, b"abc\r\ndef\r\n", b"abc\r\ndef\r\n", false, ), (b"abc\r\n", false, b"abc\r\n", b"abc\r\n", true), ( b"abc\ndef\n", false, b"abc\r\ndef\r\n", b"abc\r\ndef\r\n", false, ), // Values with text_mode=true (b"", true, b"Content-Type: text/plain\r\n\r\n", b"", false), ( b"abc", true, b"Content-Type: text/plain\r\n\r\nabc", b"abc", false, ), ( b"abc\n", true, b"Content-Type: text/plain\r\n\r\nabc\r\n", b"abc\r\n", false, ), ] { let (result_with_header, result_without_header) = smime_canonicalize(input, text_mode); assert_eq!(result_with_header.deref(), expected_with_header); assert_eq!(result_without_header.deref(), expected_without_header); assert_eq!( matches!(result_with_header, Cow::Borrowed(_)), expected_is_borrowed ); assert_eq!( matches!(result_without_header, Cow::Borrowed(_)), expected_is_borrowed ); } } } cryptography-43.0.0/src/rust/src/test_support.rs010064400017510000177000000121341464676315000202260ustar 00000000000000// 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. #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use crate::buf::CffiBuf; use crate::error::CryptographyResult; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use crate::types; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use crate::x509::certificate::Certificate as PyCertificate; use asn1::SimpleAsn1Readable; use cryptography_x509::certificate::Certificate; use cryptography_x509::common::Time; use cryptography_x509::name::Name; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] use pyo3::prelude::PyAnyMethods; #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.test_support")] struct TestCertificate { #[pyo3(get)] not_before_tag: u8, #[pyo3(get)] not_after_tag: u8, #[pyo3(get)] issuer_value_tags: Vec, #[pyo3(get)] subject_value_tags: Vec, } fn parse_name_value_tags(rdns: &Name<'_>) -> Vec { let mut tags = vec![]; for rdn in rdns.unwrap_read().clone() { let mut attributes = rdn.collect::>(); assert_eq!(attributes.len(), 1); tags.push(attributes.pop().unwrap().value.tag().as_u8().unwrap()); } tags } fn time_tag(t: &Time) -> u8 { match t { Time::UtcTime(_) => asn1::UtcTime::TAG.as_u8().unwrap(), Time::GeneralizedTime(_) => asn1::GeneralizedTime::TAG.as_u8().unwrap(), } } #[pyo3::pyfunction] fn test_parse_certificate(data: &[u8]) -> CryptographyResult { let cert = asn1::parse_single::>(data)?; Ok(TestCertificate { not_before_tag: time_tag(&cert.tbs_cert.validity.not_before), not_after_tag: time_tag(&cert.tbs_cert.validity.not_after), issuer_value_tags: parse_name_value_tags(&cert.tbs_cert.issuer), subject_value_tags: parse_name_value_tags(&cert.tbs_cert.subject), }) } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] #[pyo3::pyfunction] #[pyo3(signature = (encoding, sig, msg, certs, options))] fn pkcs7_verify( py: pyo3::Python<'_>, encoding: pyo3::Bound<'_, pyo3::PyAny>, sig: &[u8], msg: Option>, certs: Vec>, options: pyo3::Bound<'_, pyo3::types::PyList>, ) -> CryptographyResult<()> { let p7 = if encoding.is(&types::ENCODING_DER.get(py)?) { openssl::pkcs7::Pkcs7::from_der(sig)? } else if encoding.is(&types::ENCODING_PEM.get(py)?) { openssl::pkcs7::Pkcs7::from_pem(sig)? } else { openssl::pkcs7::Pkcs7::from_smime(sig)?.0 }; let mut flags = openssl::pkcs7::Pkcs7Flags::empty(); if options.contains(types::PKCS7_TEXT.get(py)?)? { flags |= openssl::pkcs7::Pkcs7Flags::TEXT; } let store = { let mut b = openssl::x509::store::X509StoreBuilder::new()?; for cert in &certs { let der = asn1::write_single(cert.get().raw.borrow_dependent())?; b.add_cert(openssl::x509::X509::from_der(&der)?)?; } b.build() }; let certs = openssl::stack::Stack::new()?; p7.verify( &certs, &store, msg.as_ref().map(|m| m.as_bytes()), None, flags, )?; Ok(()) } #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] #[pyo3::pyfunction] #[pyo3(signature = (encoding, msg, pkey, cert_recipient, options))] fn pkcs7_decrypt<'p>( py: pyo3::Python<'p>, encoding: pyo3::Bound<'p, pyo3::PyAny>, msg: CffiBuf<'p>, pkey: pyo3::Bound<'p, pyo3::PyAny>, cert_recipient: pyo3::Bound<'p, PyCertificate>, options: pyo3::Bound<'p, pyo3::types::PyList>, ) -> CryptographyResult> { let p7 = if encoding.is(&types::ENCODING_DER.get(py)?) { openssl::pkcs7::Pkcs7::from_der(msg.as_bytes())? } else if encoding.is(&types::ENCODING_PEM.get(py)?) { openssl::pkcs7::Pkcs7::from_pem(msg.as_bytes())? } else { openssl::pkcs7::Pkcs7::from_smime(msg.as_bytes())?.0 }; let mut flags = openssl::pkcs7::Pkcs7Flags::empty(); if options.contains(types::PKCS7_TEXT.get(py)?)? { flags |= openssl::pkcs7::Pkcs7Flags::TEXT; } let cert_der = asn1::write_single(cert_recipient.get().raw.borrow_dependent())?; let cert_ossl = openssl::x509::X509::from_der(&cert_der)?; let der = types::ENCODING_DER.get(py)?; let pkcs8 = types::PRIVATE_FORMAT_PKCS8.get(py)?; let no_encryption = types::NO_ENCRYPTION.get(py)?.call0()?; let pkey_bytes = pkey .call_method1( pyo3::intern!(py, "private_bytes"), (der, pkcs8, no_encryption), )? .extract::()?; let pkey_ossl = openssl::pkey::PKey::private_key_from_der(&pkey_bytes)?; let result = p7.decrypt(&pkey_ossl, &cert_ossl, flags)?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } #[pyo3::pymodule] pub(crate) mod test_support { #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] #[pymodule_export] use super::pkcs7_decrypt; #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] #[pymodule_export] use super::pkcs7_verify; #[pymodule_export] use super::test_parse_certificate; } cryptography-43.0.0/src/rust/src/types.rs010064400017510000177000000603651464676315000166300ustar 00000000000000// 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. use pyo3::types::PyAnyMethods; pub struct LazyPyImport { module: &'static str, names: &'static [&'static str], value: pyo3::sync::GILOnceCell, } impl LazyPyImport { pub const fn new(module: &'static str, names: &'static [&'static str]) -> LazyPyImport { LazyPyImport { module, names, value: pyo3::sync::GILOnceCell::new(), } } pub fn get<'p>(&'p self, py: pyo3::Python<'p>) -> pyo3::PyResult> { let p = self.value.get_or_try_init(py, || { let mut obj = py.import_bound(self.module)?.into_any(); for name in self.names { obj = obj.getattr(*name)?; } Ok::<_, pyo3::PyErr>(obj.unbind()) })?; Ok(p.clone_ref(py).into_bound(py)) } } pub static DATETIME_DATETIME: LazyPyImport = LazyPyImport::new("datetime", &["datetime"]); pub static DATETIME_TIMEZONE_UTC: LazyPyImport = LazyPyImport::new("datetime", &["timezone", "utc"]); pub static IPADDRESS_IPADDRESS: LazyPyImport = LazyPyImport::new("ipaddress", &["ip_address"]); pub static IPADDRESS_IPNETWORK: LazyPyImport = LazyPyImport::new("ipaddress", &["ip_network"]); pub static OS_URANDOM: LazyPyImport = LazyPyImport::new("os", &["urandom"]); pub static DEPRECATED_IN_36: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn36"]); pub static DEPRECATED_IN_41: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn41"]); pub static DEPRECATED_IN_42: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn42"]); pub static DEPRECATED_IN_43: LazyPyImport = LazyPyImport::new("cryptography.utils", &["DeprecatedIn43"]); pub static ENCODING: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["Encoding"], ); pub static ENCODING_DER: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["Encoding", "DER"], ); pub static ENCODING_OPENSSH: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["Encoding", "OpenSSH"], ); pub static ENCODING_PEM: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["Encoding", "PEM"], ); pub static ENCODING_RAW: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["Encoding", "Raw"], ); pub static ENCODING_SMIME: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["Encoding", "SMIME"], ); pub static ENCODING_X962: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["Encoding", "X962"], ); pub static PRIVATE_FORMAT: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat"], ); pub static PRIVATE_FORMAT_OPENSSH: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat", "OpenSSH"], ); pub static PRIVATE_FORMAT_PKCS8: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat", "PKCS8"], ); pub static PRIVATE_FORMAT_PKCS12: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat", "PKCS12"], ); pub static PRIVATE_FORMAT_RAW: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat", "Raw"], ); pub static PRIVATE_FORMAT_TRADITIONAL_OPENSSL: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PrivateFormat", "TraditionalOpenSSL"], ); pub static PUBLIC_FORMAT: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PublicFormat"], ); pub static PUBLIC_FORMAT_COMPRESSED_POINT: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PublicFormat", "CompressedPoint"], ); pub static PUBLIC_FORMAT_OPENSSH: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PublicFormat", "OpenSSH"], ); pub static PUBLIC_FORMAT_PKCS1: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PublicFormat", "PKCS1"], ); pub static PUBLIC_FORMAT_RAW: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PublicFormat", "Raw"], ); pub static PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PublicFormat", "SubjectPublicKeyInfo"], ); pub static PUBLIC_FORMAT_UNCOMPRESSED_POINT: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["PublicFormat", "UncompressedPoint"], ); pub static PARAMETER_FORMAT_PKCS3: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["ParameterFormat", "PKCS3"], ); pub static KEY_SERIALIZATION_ENCRYPTION: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["KeySerializationEncryption"], ); pub static NO_ENCRYPTION: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["NoEncryption"], ); pub static BEST_AVAILABLE_ENCRYPTION: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["BestAvailableEncryption"], ); pub static ENCRYPTION_BUILDER: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization", &["_KeySerializationEncryption"], ); pub static PBES_PBESV1SHA1AND3KEYTRIPLEDESCBC: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs12", &["PBES", "PBESv1SHA1And3KeyTripleDESCBC"], ); pub static PBES_PBESV2SHA256ANDAES256CBC: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs12", &["PBES", "PBESv2SHA256AndAES256CBC"], ); pub static SERIALIZE_SSH_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.ssh", &["_serialize_ssh_private_key"], ); pub static SERIALIZE_SSH_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.ssh", &["serialize_ssh_public_key"], ); pub static SIG_OIDS_TO_HASH: LazyPyImport = LazyPyImport::new("cryptography.hazmat._oid", &["_SIG_OIDS_TO_HASH"]); pub static OID_NAMES: LazyPyImport = LazyPyImport::new("cryptography.hazmat._oid", &["_OID_NAMES"]); pub static REASON_FLAGS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["ReasonFlags"]); pub static ATTRIBUTE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attribute"]); pub static ATTRIBUTES: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Attributes"]); pub static CRL_NUMBER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLNumber"]); pub static DELTA_CRL_INDICATOR: LazyPyImport = LazyPyImport::new("cryptography.x509", &["DeltaCRLIndicator"]); pub static ISSUER_ALTERNATIVE_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["IssuerAlternativeName"]); pub static AUTHORITY_INFORMATION_ACCESS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["AuthorityInformationAccess"]); pub static ISSUING_DISTRIBUTION_POINT: LazyPyImport = LazyPyImport::new("cryptography.x509", &["IssuingDistributionPoint"]); pub static FRESHEST_CRL: LazyPyImport = LazyPyImport::new("cryptography.x509", &["FreshestCRL"]); pub static CRL_REASON: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLReason"]); pub static CERTIFICATE_ISSUER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CertificateIssuer"]); pub static INVALIDITY_DATE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["InvalidityDate"]); pub static OCSP_NONCE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OCSPNonce"]); pub static OCSP_ACCEPTABLE_RESPONSES: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OCSPAcceptableResponses"]); pub static SIGNED_CERTIFICATE_TIMESTAMPS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["SignedCertificateTimestamps"]); pub static PRECERT_POISON: LazyPyImport = LazyPyImport::new("cryptography.x509", &["PrecertPoison"]); pub static PRECERTIFICATE_SIGNED_CERTIFICATE_TIMESTAMPS: LazyPyImport = LazyPyImport::new( "cryptography.x509", &["PrecertificateSignedCertificateTimestamps"], ); pub static DISTRIBUTION_POINT: LazyPyImport = LazyPyImport::new("cryptography.x509", &["DistributionPoint"]); pub static ACCESS_DESCRIPTION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["AccessDescription"]); pub static AUTHORITY_KEY_IDENTIFIER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["AuthorityKeyIdentifier"]); pub static UNRECOGNIZED_EXTENSION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["UnrecognizedExtension"]); pub static EXTENSION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Extension"]); pub static EXTENSIONS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Extensions"]); pub static NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Name"]); pub static RELATIVE_DISTINGUISHED_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["RelativeDistinguishedName"]); pub static NAME_ATTRIBUTE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["NameAttribute"]); pub static NAME_CONSTRAINTS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["NameConstraints"]); pub static MS_CERTIFICATE_TEMPLATE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["MSCertificateTemplate"]); pub static CRL_DISTRIBUTION_POINTS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CRLDistributionPoints"]); pub static BASIC_CONSTRAINTS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["BasicConstraints"]); pub static INHIBIT_ANY_POLICY: LazyPyImport = LazyPyImport::new("cryptography.x509", &["InhibitAnyPolicy"]); pub static OCSP_NO_CHECK: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OCSPNoCheck"]); pub static POLICY_CONSTRAINTS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["PolicyConstraints"]); pub static CERTIFICATE_POLICIES: LazyPyImport = LazyPyImport::new("cryptography.x509", &["CertificatePolicies"]); pub static SUBJECT_INFORMATION_ACCESS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["SubjectInformationAccess"]); pub static KEY_USAGE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["KeyUsage"]); pub static EXTENDED_KEY_USAGE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["ExtendedKeyUsage"]); pub static SUBJECT_KEY_IDENTIFIER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["SubjectKeyIdentifier"]); pub static TLS_FEATURE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["TLSFeature"]); pub static SUBJECT_ALTERNATIVE_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["SubjectAlternativeName"]); pub static POLICY_INFORMATION: LazyPyImport = LazyPyImport::new("cryptography.x509", &["PolicyInformation"]); pub static USER_NOTICE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["UserNotice"]); pub static NOTICE_REFERENCE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["NoticeReference"]); pub static REGISTERED_ID: LazyPyImport = LazyPyImport::new("cryptography.x509", &["RegisteredID"]); pub static DIRECTORY_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["DirectoryName"]); pub static UNIFORM_RESOURCE_IDENTIFIER: LazyPyImport = LazyPyImport::new("cryptography.x509", &["UniformResourceIdentifier"]); pub static DNS_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["DNSName"]); pub static IP_ADDRESS: LazyPyImport = LazyPyImport::new("cryptography.x509", &["IPAddress"]); pub static RFC822_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["RFC822Name"]); pub static OTHER_NAME: LazyPyImport = LazyPyImport::new("cryptography.x509", &["OtherName"]); pub static CERTIFICATE_VERSION_V1: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Version", "v1"]); pub static CERTIFICATE_VERSION_V3: LazyPyImport = LazyPyImport::new("cryptography.x509", &["Version", "v3"]); pub static CRL_REASON_FLAGS: LazyPyImport = LazyPyImport::new("cryptography.x509.extensions", &["_CRLREASONFLAGS"]); pub static REASON_BIT_MAPPING: LazyPyImport = LazyPyImport::new("cryptography.x509.extensions", &["_REASON_BIT_MAPPING"]); pub static CRL_ENTRY_REASON_ENUM_TO_CODE: LazyPyImport = LazyPyImport::new( "cryptography.x509.extensions", &["_CRL_ENTRY_REASON_ENUM_TO_CODE"], ); pub static TLS_FEATURE_TYPE_TO_ENUM: LazyPyImport = LazyPyImport::new( "cryptography.x509.extensions", &["_TLS_FEATURE_TYPE_TO_ENUM"], ); pub static OCSP_RESPONSE_STATUS: LazyPyImport = LazyPyImport::new("cryptography.x509.ocsp", &["OCSPResponseStatus"]); pub static OCSP_CERT_STATUS: LazyPyImport = LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus"]); pub static OCSP_CERT_STATUS_GOOD: LazyPyImport = LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus", "GOOD"]); pub static OCSP_CERT_STATUS_UNKNOWN: LazyPyImport = LazyPyImport::new("cryptography.x509.ocsp", &["OCSPCertStatus", "UNKNOWN"]); pub static OCSP_RESPONDER_ENCODING_HASH: LazyPyImport = LazyPyImport::new("cryptography.x509.ocsp", &["OCSPResponderEncoding", "HASH"]); pub static CERTIFICATE_TRANSPARENCY_VERSION_V1: LazyPyImport = LazyPyImport::new( "cryptography.x509.certificate_transparency", &["Version", "v1"], ); pub static SIGNATURE_ALGORITHM: LazyPyImport = LazyPyImport::new( "cryptography.x509.certificate_transparency", &["SignatureAlgorithm"], ); pub static LOG_ENTRY_TYPE_X509_CERTIFICATE: LazyPyImport = LazyPyImport::new( "cryptography.x509.certificate_transparency", &["LogEntryType", "X509_CERTIFICATE"], ); pub static LOG_ENTRY_TYPE_PRE_CERTIFICATE: LazyPyImport = LazyPyImport::new( "cryptography.x509.certificate_transparency", &["LogEntryType", "PRE_CERTIFICATE"], ); pub static ASN1_TYPE_TO_ENUM: LazyPyImport = LazyPyImport::new("cryptography.x509.name", &["_ASN1_TYPE_TO_ENUM"]); pub static ASN1_TYPE_BIT_STRING: LazyPyImport = LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "BitString"]); pub static ASN1_TYPE_BMP_STRING: LazyPyImport = LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "BMPString"]); pub static ASN1_TYPE_UNIVERSAL_STRING: LazyPyImport = LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "UniversalString"]); pub static PKCS7_BINARY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["PKCS7Options", "Binary"], ); pub static PKCS7_TEXT: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["PKCS7Options", "Text"], ); pub static PKCS7_NO_ATTRIBUTES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["PKCS7Options", "NoAttributes"], ); pub static PKCS7_NO_CAPABILITIES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["PKCS7Options", "NoCapabilities"], ); pub static PKCS7_NO_CERTS: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["PKCS7Options", "NoCerts"], ); pub static PKCS7_DETACHED_SIGNATURE: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["PKCS7Options", "DetachedSignature"], ); pub static SMIME_ENVELOPED_ENCODE: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["_smime_enveloped_encode"], ); pub static SMIME_SIGNED_ENCODE: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs7", &["_smime_signed_encode"], ); pub static PKCS12KEYANDCERTIFICATES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.serialization.pkcs12", &["PKCS12KeyAndCertificates"], ); pub static HASHES_MODULE: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &[]); pub static HASH_ALGORITHM: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["HashAlgorithm"]); #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] pub static EXTENDABLE_OUTPUT_FUNCTION: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.hashes", &["ExtendableOutputFunction"], ); pub static SHA1: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA1"]); pub static SHA256: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA256"]); pub static PREHASHED: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.utils", &["Prehashed"], ); pub static ASYMMETRIC_PADDING: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["AsymmetricPadding"], ); pub static PADDING_AUTO: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["_Auto"], ); pub static PADDING_MAX_LENGTH: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["_MaxLength"], ); pub static PADDING_DIGEST_LENGTH: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["_DigestLength"], ); pub static PKCS1V15: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["PKCS1v15"], ); pub static PSS: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["PSS"], ); pub static OAEP: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["OAEP"], ); pub static MGF1: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["MGF1"], ); pub static CALCULATE_MAX_PSS_SALT_LENGTH: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.padding", &["calculate_max_pss_salt_length"], ); pub static RSA_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.rsa", &["RSAPrivateKey"], ); pub static RSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.rsa", &["RSAPublicKey"], ); pub static ELLIPTIC_CURVE: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ec", &["EllipticCurve"], ); pub static ELLIPTIC_CURVE_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ec", &["EllipticCurvePrivateKey"], ); pub static ELLIPTIC_CURVE_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ec", &["EllipticCurvePublicKey"], ); pub static CURVE_TYPES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ec", &["_CURVE_TYPES"], ); pub static ECDSA: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.asymmetric.ec", &["ECDSA"]); pub static ECDH: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.asymmetric.ec", &["ECDH"]); pub static ED25519_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ed25519", &["Ed25519PrivateKey"], ); pub static ED25519_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ed25519", &["Ed25519PublicKey"], ); pub static ED448_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ed448", &["Ed448PrivateKey"], ); pub static ED448_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.ed448", &["Ed448PublicKey"], ); pub static DSA_PRIVATE_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.dsa", &["DSAPrivateKey"], ); pub static DSA_PUBLIC_KEY: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.dsa", &["DSAPublicKey"], ); pub static FFI_FROM_BUFFER: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.bindings._rust", &["_openssl", "ffi", "from_buffer"], ); pub static FFI_CAST: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.bindings._rust", &["_openssl", "ffi", "cast"], ); pub static BLOCK_CIPHER_ALGORITHM: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers", &["BlockCipherAlgorithm"], ); pub static TRIPLE_DES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.decrepit.ciphers.algorithms", &["TripleDES"], ); pub static AES: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["AES"], ); pub static AES128: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["AES128"], ); pub static AES256: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["AES256"], ); pub static CHACHA20: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["ChaCha20"], ); #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SM4"))] pub static SM4: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["SM4"], ); #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_SEED"))] pub static SEED: LazyPyImport = LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["SEED"]); #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAMELLIA"))] pub static CAMELLIA: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.algorithms", &["Camellia"], ); #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_BF"))] pub static BLOWFISH: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.decrepit.ciphers.algorithms", &["Blowfish"], ); #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_CAST"))] pub static CAST5: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.decrepit.ciphers.algorithms", &["CAST5"], ); #[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_IDEA"))] pub static IDEA: LazyPyImport = LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["IDEA"]); pub static ARC4: LazyPyImport = LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["ARC4"]); pub static RC2: LazyPyImport = LazyPyImport::new("cryptography.hazmat.decrepit.ciphers.algorithms", &["RC2"]); pub static MODE_WITH_INITIALIZATION_VECTOR: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.modes", &["ModeWithInitializationVector"], ); pub static MODE_WITH_TWEAK: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.modes", &["ModeWithTweak"], ); pub static MODE_WITH_NONCE: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.modes", &["ModeWithNonce"], ); pub static MODE_WITH_AUTHENTICATION_TAG: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.ciphers.modes", &["ModeWithAuthenticationTag"], ); pub static CBC: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CBC"]); #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] pub static CFB: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CFB"]); #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] pub static CFB8: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CFB8"]); pub static OFB: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["OFB"]); pub static ECB: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["ECB"]); pub static CTR: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["CTR"]); pub static GCM: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["GCM"]); pub static XTS: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.ciphers.modes", &["XTS"]); pub static LEGACY_PROVIDER_LOADED: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.bindings._rust", &["openssl", "_legacy_provider_loaded"], ); #[cfg(test)] mod tests { use super::LazyPyImport; #[test] fn test_basic() { pyo3::prepare_freethreaded_python(); let v = LazyPyImport::new("foo", &["bar"]); pyo3::Python::with_gil(|py| { assert!(v.get(py).is_err()); }); } } cryptography-43.0.0/src/rust/src/x509/certificate.rs010064400017510000177000001050071464676315000204440ustar 00000000000000// 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. use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use cryptography_x509::certificate::Certificate as RawCertificate; use cryptography_x509::common::{AlgorithmParameters, Asn1ReadableOrWritable}; use cryptography_x509::extensions::{ AuthorityKeyIdentifier, BasicConstraints, DisplayText, DistributionPoint, DistributionPointName, DuplicateExtensionsError, IssuerAlternativeName, KeyUsage, MSCertificateTemplate, NameConstraints, PolicyConstraints, PolicyInformation, PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions, SequenceOfSubtrees, UserNotice, }; use cryptography_x509::extensions::{Extension, SubjectAlternativeName}; use cryptography_x509::{common, oid}; use cryptography_x509_verification::ops::CryptoOps; use pyo3::types::{PyAnyMethods, PyListMethods}; use pyo3::{IntoPy, ToPyObject}; use crate::asn1::{ big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, }; use crate::backend::{hashes, keys}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::verify::PyCryptoOps; use crate::x509::{extensions, sct, sign}; use crate::{exceptions, types, x509}; self_cell::self_cell!( pub(crate) struct OwnedCertificate { owner: pyo3::Py, #[covariant] dependent: RawCertificate, } ); #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Certificate { pub(crate) raw: OwnedCertificate, pub(crate) cached_extensions: pyo3::sync::GILOnceCell, } #[pyo3::pymethods] impl Certificate { fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.raw.borrow_dependent().hash(&mut hasher); hasher.finish() } fn __eq__(&self, other: pyo3::PyRef<'_, Certificate>) -> bool { self.raw.borrow_dependent() == other.raw.borrow_dependent() } fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let subject = self.subject(py)?; let subject_repr = subject.repr()?.extract::()?; Ok(format!("")) } fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { slf } pub(crate) fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { keys::load_der_public_key_bytes( py, self.raw.borrow_dependent().tbs_cert.spki.tlv().full_data(), ) } #[getter] fn public_key_algorithm_oid<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { oid_to_py_oid( py, self.raw.borrow_dependent().tbs_cert.spki.algorithm.oid(), ) } fn fingerprint<'p>( &self, py: pyo3::Python<'p>, algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let serialized = asn1::write_single(&self.raw.borrow_dependent())?; let mut h = hashes::Hash::new(py, algorithm, None)?; h.update_bytes(&serialized)?; Ok(h.finalize(py)?.into_any()) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let result = asn1::write_single(self.raw.borrow_dependent())?; encode_der_data(py, "CERTIFICATE".to_string(), result, encoding) } #[getter] fn serial_number<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let bytes = self.raw.borrow_dependent().tbs_cert.serial.as_bytes(); warn_if_negative_serial(py, bytes)?; Ok(big_byte_slice_to_py_int(py, bytes)?) } #[getter] fn version<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let version = &self.raw.borrow_dependent().tbs_cert.version; cert_version(py, *version) } #[getter] fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(x509::parse_name(py, self.raw.borrow_dependent().issuer()) .map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer")))?) } #[getter] fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(x509::parse_name(py, self.raw.borrow_dependent().subject()) .map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))?) } #[getter] fn tbs_certificate_bytes<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let result = asn1::write_single(&self.raw.borrow_dependent().tbs_cert)?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } #[getter] fn tbs_precertificate_bytes<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let val = self.raw.borrow_dependent(); let mut tbs_precert = val.tbs_cert.clone(); // Remove the SCT list extension match val.extensions() { Ok(extensions) => { let ext_count = extensions .as_raw() .as_ref() .map_or(0, |raw| raw.unwrap_read().len()); let filtered_extensions: Vec> = extensions .iter() .filter(|x| x.extn_id != oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID) .collect(); if filtered_extensions.len() == ext_count { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Could not find pre-certificate SCT list extension", ), )); } let filtered_extensions: RawExtensions<'_> = Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(filtered_extensions), ); tbs_precert.raw_extensions = Some(filtered_extensions); let result = asn1::write_single(&tbs_precert)?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } Err(DuplicateExtensionsError(oid)) => { let oid_obj = oid_to_py_oid(py, &oid)?; Err(exceptions::DuplicateExtension::new_err(( format!("Duplicate {} extension found", &oid), oid_obj.into_py(py), )) .into()) } } } #[getter] fn signature<'p>(&self, py: pyo3::Python<'p>) -> pyo3::Bound<'p, pyo3::types::PyBytes> { pyo3::types::PyBytes::new_bound(py, self.raw.borrow_dependent().signature.as_bytes()) } #[getter] fn not_valid_before<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_42.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to not_valid_before_utc.", 1, )?; let dt = &self .raw .borrow_dependent() .tbs_cert .validity .not_before .as_datetime(); x509::datetime_to_py(py, dt) } #[getter] fn not_valid_before_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let dt = &self .raw .borrow_dependent() .tbs_cert .validity .not_before .as_datetime(); x509::datetime_to_py_utc(py, dt) } #[getter] fn not_valid_after<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_42.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to not_valid_after_utc.", 1, )?; let dt = &self .raw .borrow_dependent() .tbs_cert .validity .not_after .as_datetime(); x509::datetime_to_py(py, dt) } #[getter] fn not_valid_after_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let dt = &self .raw .borrow_dependent() .tbs_cert .validity .not_after .as_datetime(); x509::datetime_to_py_utc(py, dt) } #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { sign::identify_signature_hash_algorithm(py, &self.raw.borrow_dependent().signature_alg) } #[getter] fn signature_algorithm_oid<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { oid_to_py_oid(py, self.raw.borrow_dependent().signature_alg.oid()) } #[getter] fn signature_algorithm_parameters<'p>( &'p self, py: pyo3::Python<'p>, ) -> CryptographyResult> { sign::identify_signature_algorithm_parameters( py, &self.raw.borrow_dependent().signature_alg, ) } #[getter] fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { x509::parse_and_cache_extensions( py, &self.cached_extensions, &self.raw.borrow_dependent().tbs_cert.raw_extensions, |ext| match ext.extn_id { oid::PRECERT_POISON_OID => { ext.value::<()>()?; Ok(Some(types::PRECERT_POISON.get(py)?.call0()?)) } oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID => { let contents = ext.value::<&[u8]>()?; let scts = sct::parse_scts(py, contents, sct::LogEntryType::PreCertificate)?; Ok(Some( types::PRECERTIFICATE_SIGNED_CERTIFICATE_TIMESTAMPS .get(py)? .call1((scts,))?, )) } _ => parse_cert_ext(py, ext), }, ) } fn verify_directly_issued_by( &self, issuer: pyo3::PyRef<'_, Certificate>, ) -> CryptographyResult<()> { if self.raw.borrow_dependent().tbs_cert.signature_alg != self.raw.borrow_dependent().signature_alg { return Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Inner and outer signature algorithms do not match. This is an invalid certificate." ))); }; if self.raw.borrow_dependent().tbs_cert.issuer != issuer.raw.borrow_dependent().tbs_cert.subject { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Issuer certificate subject does not match certificate issuer.", ), )); }; let ops = PyCryptoOps {}; let issuer_key = ops.public_key(issuer.raw.borrow_dependent())?; ops.verify_signed_by(self.raw.borrow_dependent(), &issuer_key) } } fn cert_version( py: pyo3::Python<'_>, version: u8, ) -> Result, CryptographyError> { match version { 0 => Ok(types::CERTIFICATE_VERSION_V1.get(py)?), 2 => Ok(types::CERTIFICATE_VERSION_V3.get(py)?), _ => Err(CryptographyError::from( exceptions::InvalidVersion::new_err(( format!("{version} is not a valid X509 version"), version, )), )), } } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] pub(crate) fn load_pem_x509_certificate( py: pyo3::Python<'_>, data: &[u8], backend: Option>, ) -> CryptographyResult { let _ = backend; // We support both PEM header strings that OpenSSL does // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L32-L33 let parsed = x509::find_in_pem( data, |p| p.tag() == "CERTIFICATE" || p.tag() == "X509 CERTIFICATE", "Valid PEM but no BEGIN CERTIFICATE/END CERTIFICATE delimiters. Are you sure this is a certificate?", )?; load_der_x509_certificate( py, pyo3::types::PyBytes::new_bound(py, parsed.contents()).unbind(), None, ) } #[pyo3::pyfunction] pub(crate) fn load_pem_x509_certificates( py: pyo3::Python<'_>, data: &[u8], ) -> CryptographyResult> { let certs = pem::parse_many(data)? .iter() .filter(|p| p.tag() == "CERTIFICATE" || p.tag() == "X509 CERTIFICATE") .map(|p| { load_der_x509_certificate( py, pyo3::types::PyBytes::new_bound(py, p.contents()).unbind(), None, ) }) .collect::, _>>()?; if certs.is_empty() { return Err(CryptographyError::from(pem::PemError::MalformedFraming)); } Ok(certs) } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] pub(crate) fn load_der_x509_certificate( py: pyo3::Python<'_>, data: pyo3::Py, backend: Option>, ) -> CryptographyResult { let _ = backend; let raw = OwnedCertificate::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; // Parse cert version immediately so we can raise error on parse if it is invalid. cert_version(py, raw.borrow_dependent().tbs_cert.version)?; // determine if the serial is negative and raise a warning if it is. We want to drop support // for this sort of invalid encoding eventually. warn_if_negative_serial(py, raw.borrow_dependent().tbs_cert.serial.as_bytes())?; // determine if the signature algorithm has incorrect parameters and raise a warning if it // does. this is a bug in the JDK and we want to drop support for it eventually. // ECDSA was fixed in Java 16, DSA in Java 21. warn_if_invalid_params(py, raw.borrow_dependent().signature_alg.params.clone())?; warn_if_invalid_params( py, raw.borrow_dependent().tbs_cert.signature_alg.params.clone(), )?; Ok(Certificate { raw, cached_extensions: pyo3::sync::GILOnceCell::new(), }) } fn warn_if_negative_serial(py: pyo3::Python<'_>, bytes: &'_ [u8]) -> pyo3::PyResult<()> { if bytes[0] & 0x80 != 0 { let warning_cls = types::DEPRECATED_IN_36.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Parsed a negative serial number, which is disallowed by RFC 5280. Loading this certificate will cause an exception in the next release of cryptography.", 1, )?; } Ok(()) } fn warn_if_invalid_params( py: pyo3::Python<'_>, params: AlgorithmParameters<'_>, ) -> pyo3::PyResult<()> { match params { AlgorithmParameters::EcDsaWithSha224(Some(..)) | AlgorithmParameters::EcDsaWithSha256(Some(..)) | AlgorithmParameters::EcDsaWithSha384(Some(..)) | AlgorithmParameters::EcDsaWithSha512(Some(..)) | AlgorithmParameters::DsaWithSha224(Some(..)) | AlgorithmParameters::DsaWithSha256(Some(..)) | AlgorithmParameters::DsaWithSha384(Some(..)) | AlgorithmParameters::DsaWithSha512(Some(..)) => { let warning_cls = types::DEPRECATED_IN_41.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "The parsed certificate contains a NULL parameter value in its signature algorithm parameters. This is invalid and will be rejected in a future version of cryptography. If this certificate was created via Java, please upgrade to JDK21+ or the latest JDK11/17 once a fix is issued. If this certificate was created in some other fashion please report the issue to the cryptography issue tracker. See https://github.com/pyca/cryptography/issues/8996 and https://github.com/pyca/cryptography/issues/9253 for more details.", 2, )?; } _ => {} } Ok(()) } fn parse_display_text( py: pyo3::Python<'_>, text: DisplayText<'_>, ) -> pyo3::PyResult { match text { DisplayText::IA5String(o) => { Ok(pyo3::types::PyString::new_bound(py, o.as_str()).to_object(py)) } DisplayText::Utf8String(o) => { Ok(pyo3::types::PyString::new_bound(py, o.as_str()).to_object(py)) } DisplayText::VisibleString(o) => { if asn1::VisibleString::new(o.as_str()).is_none() { let warning_cls = types::DEPRECATED_IN_41.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Invalid ASN.1 (UTF-8 characters in a VisibleString) in the explicit text and/or notice reference of the certificate policies extension. In a future version of cryptography, an exception will be raised.", 1, )?; } Ok(pyo3::types::PyString::new_bound(py, o.as_str()).to_object(py)) } DisplayText::BmpString(o) => { let py_bytes = pyo3::types::PyBytes::new_bound(py, o.as_utf16_be_bytes()); // TODO: do the string conversion in rust perhaps Ok(py_bytes .call_method1( pyo3::intern!(py, "decode"), (pyo3::intern!(py, "utf_16_be"),), )? .to_object(py)) } } } fn parse_user_notice( py: pyo3::Python<'_>, un: UserNotice<'_>, ) -> Result { let et = match un.explicit_text { Some(data) => parse_display_text(py, data)?, None => py.None(), }; let nr = match un.notice_ref { Some(data) => { let org = parse_display_text(py, data.organization)?; let numbers = pyo3::types::PyList::empty_bound(py); for num in data.notice_numbers.unwrap_read().clone() { numbers.append(big_byte_slice_to_py_int(py, num.as_bytes())?)?; } types::NOTICE_REFERENCE .get(py)? .call1((org, numbers))? .to_object(py) } None => py.None(), }; Ok(types::USER_NOTICE.get(py)?.call1((nr, et))?.to_object(py)) } fn parse_policy_qualifiers<'a>( py: pyo3::Python<'_>, policy_qualifiers: &asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, ) -> Result { let py_pq = pyo3::types::PyList::empty_bound(py); for pqi in policy_qualifiers.clone() { let qualifier = match pqi.qualifier { Qualifier::CpsUri(data) => { if pqi.policy_qualifier_id == oid::CP_CPS_URI_OID { pyo3::types::PyString::new_bound(py, data.as_str()).to_object(py) } else { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "CpsUri ASN.1 structure found but OID did not match", ), )); } } Qualifier::UserNotice(un) => { if pqi.policy_qualifier_id != oid::CP_USER_NOTICE_OID { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "UserNotice ASN.1 structure found but OID did not match", ), )); } parse_user_notice(py, un)? } }; py_pq.append(qualifier)?; } Ok(py_pq.to_object(py)) } fn parse_cp( py: pyo3::Python<'_>, ext: &Extension<'_>, ) -> Result { let cp = ext.value::>>()?; let certificate_policies = pyo3::types::PyList::empty_bound(py); for policyinfo in cp { let pi_oid = oid_to_py_oid(py, &policyinfo.policy_identifier)?; let py_pqis = match policyinfo.policy_qualifiers { Some(policy_qualifiers) => { parse_policy_qualifiers(py, policy_qualifiers.unwrap_read())? } None => py.None(), }; let pi = types::POLICY_INFORMATION .get(py)? .call1((pi_oid, py_pqis))?; certificate_policies.append(pi)?; } Ok(certificate_policies.to_object(py)) } fn parse_general_subtrees( py: pyo3::Python<'_>, subtrees: SequenceOfSubtrees<'_>, ) -> Result { let gns = pyo3::types::PyList::empty_bound(py); for gs in subtrees.unwrap_read().clone() { gns.append(x509::parse_general_name(py, gs.base)?)?; } Ok(gns.to_object(py)) } pub(crate) fn parse_distribution_point_name( py: pyo3::Python<'_>, dp: DistributionPointName<'_>, ) -> Result<(pyo3::PyObject, pyo3::PyObject), CryptographyError> { Ok(match dp { DistributionPointName::FullName(data) => ( x509::parse_general_names(py, data.unwrap_read())?, py.None(), ), DistributionPointName::NameRelativeToCRLIssuer(data) => { (py.None(), x509::parse_rdn(py, data.unwrap_read())?) } }) } fn parse_distribution_point( py: pyo3::Python<'_>, dp: DistributionPoint<'_>, ) -> Result { let (full_name, relative_name) = match dp.distribution_point { Some(data) => parse_distribution_point_name(py, data)?, None => (py.None(), py.None()), }; let reasons = parse_distribution_point_reasons(py, dp.reasons.as_ref().map(|v| v.unwrap_read()))?; let crl_issuer = match dp.crl_issuer { Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, None => py.None(), }; Ok(types::DISTRIBUTION_POINT .get(py)? .call1((full_name, relative_name, reasons, crl_issuer))? .to_object(py)) } pub(crate) fn parse_distribution_points( py: pyo3::Python<'_>, ext: &Extension<'_>, ) -> Result { let dps = ext.value::>>()?; let py_dps = pyo3::types::PyList::empty_bound(py); for dp in dps { let py_dp = parse_distribution_point(py, dp)?; py_dps.append(py_dp)?; } Ok(py_dps.to_object(py)) } pub(crate) fn parse_distribution_point_reasons( py: pyo3::Python<'_>, reasons: Option<&asn1::BitString<'_>>, ) -> Result { let reason_bit_mapping = types::REASON_BIT_MAPPING.get(py)?; Ok(match reasons { Some(bs) => { let mut vec = Vec::new(); for i in 1..=8 { if bs.has_bit_set(i) { vec.push(reason_bit_mapping.get_item(i)?); } } pyo3::types::PyFrozenSet::new_bound(py, &vec)?.to_object(py) } None => py.None(), }) } pub(crate) fn encode_distribution_point_reasons( py: pyo3::Python<'_>, py_reasons: &pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { let reason_flag_mapping = types::CRL_REASON_FLAGS.get(py)?; let mut bits = vec![0, 0]; for py_reason in py_reasons.iter()? { let bit = reason_flag_mapping .get_item(py_reason?)? .extract::()?; set_bit(&mut bits, bit, true); } if bits[1] == 0 { bits.truncate(1); } let unused_bits = bits.last().unwrap().trailing_zeros() as u8; Ok(asn1::OwnedBitString::new(bits, unused_bits).unwrap()) } pub(crate) fn parse_authority_key_identifier<'p>( py: pyo3::Python<'p>, ext: &Extension<'_>, ) -> Result, CryptographyError> { let aki = ext.value::>()?; let serial = match aki.authority_cert_serial_number { Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.to_object(py), None => py.None(), }; let issuer = match aki.authority_cert_issuer { Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, None => py.None(), }; Ok(types::AUTHORITY_KEY_IDENTIFIER .get(py)? .call1((aki.key_identifier, issuer, serial))?) } pub(crate) fn parse_access_descriptions( py: pyo3::Python<'_>, ext: &Extension<'_>, ) -> Result { let ads = pyo3::types::PyList::empty_bound(py); let parsed = ext.value::>()?; for access in parsed.unwrap_read().clone() { let py_oid = oid_to_py_oid(py, &access.access_method)?.to_object(py); let gn = x509::parse_general_name(py, access.access_location)?; let ad = types::ACCESS_DESCRIPTION.get(py)?.call1((py_oid, gn))?; ads.append(ad)?; } Ok(ads.to_object(py)) } pub fn parse_cert_ext<'p>( py: pyo3::Python<'p>, ext: &Extension<'_>, ) -> CryptographyResult>> { match ext.extn_id { oid::SUBJECT_ALTERNATIVE_NAME_OID => { let gn_seq = ext.value::>()?; let sans = x509::parse_general_names(py, &gn_seq)?; Ok(Some( types::SUBJECT_ALTERNATIVE_NAME.get(py)?.call1((sans,))?, )) } oid::ISSUER_ALTERNATIVE_NAME_OID => { let gn_seq = ext.value::>()?; let ians = x509::parse_general_names(py, &gn_seq)?; Ok(Some( types::ISSUER_ALTERNATIVE_NAME.get(py)?.call1((ians,))?, )) } oid::TLS_FEATURE_OID => { let tls_feature_type_to_enum = types::TLS_FEATURE_TYPE_TO_ENUM.get(py)?; let features = pyo3::types::PyList::empty_bound(py); for feature in ext.value::>()? { let py_feature = tls_feature_type_to_enum.get_item(feature)?; features.append(py_feature)?; } Ok(Some(types::TLS_FEATURE.get(py)?.call1((features,))?)) } oid::SUBJECT_KEY_IDENTIFIER_OID => { let identifier = ext.value::<&[u8]>()?; Ok(Some( types::SUBJECT_KEY_IDENTIFIER .get(py)? .call1((identifier,))?, )) } oid::EXTENDED_KEY_USAGE_OID => { let ekus = pyo3::types::PyList::empty_bound(py); for oid in ext.value::>()? { let oid_obj = oid_to_py_oid(py, &oid)?; ekus.append(oid_obj)?; } Ok(Some(types::EXTENDED_KEY_USAGE.get(py)?.call1((ekus,))?)) } oid::KEY_USAGE_OID => { let kus = ext.value::>()?; Ok(Some(types::KEY_USAGE.get(py)?.call1(( kus.digital_signature(), kus.content_commitment(), kus.key_encipherment(), kus.data_encipherment(), kus.key_agreement(), kus.key_cert_sign(), kus.crl_sign(), kus.encipher_only(), kus.decipher_only(), ))?)) } oid::AUTHORITY_INFORMATION_ACCESS_OID => { let ads = parse_access_descriptions(py, ext)?; Ok(Some( types::AUTHORITY_INFORMATION_ACCESS.get(py)?.call1((ads,))?, )) } oid::SUBJECT_INFORMATION_ACCESS_OID => { let ads = parse_access_descriptions(py, ext)?; Ok(Some( types::SUBJECT_INFORMATION_ACCESS.get(py)?.call1((ads,))?, )) } oid::CERTIFICATE_POLICIES_OID => { let cp = parse_cp(py, ext)?; Ok(Some(types::CERTIFICATE_POLICIES.get(py)?.call1((cp,))?)) } oid::POLICY_CONSTRAINTS_OID => { let pc = ext.value::()?; Ok(Some(types::POLICY_CONSTRAINTS.get(py)?.call1(( pc.require_explicit_policy, pc.inhibit_policy_mapping, ))?)) } oid::OCSP_NO_CHECK_OID => { ext.value::<()>()?; Ok(Some(types::OCSP_NO_CHECK.get(py)?.call0()?)) } oid::INHIBIT_ANY_POLICY_OID => { let bignum = ext.value::>()?; let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; Ok(Some(types::INHIBIT_ANY_POLICY.get(py)?.call1((pynum,))?)) } oid::BASIC_CONSTRAINTS_OID => { let bc = ext.value::()?; Ok(Some( types::BASIC_CONSTRAINTS .get(py)? .call1((bc.ca, bc.path_length))?, )) } oid::AUTHORITY_KEY_IDENTIFIER_OID => Ok(Some(parse_authority_key_identifier(py, ext)?)), oid::CRL_DISTRIBUTION_POINTS_OID => { let dp = parse_distribution_points(py, ext)?; Ok(Some(types::CRL_DISTRIBUTION_POINTS.get(py)?.call1((dp,))?)) } oid::FRESHEST_CRL_OID => { let dp = parse_distribution_points(py, ext)?; Ok(Some(types::FRESHEST_CRL.get(py)?.call1((dp,))?)) } oid::NAME_CONSTRAINTS_OID => { let nc = ext.value::>()?; let permitted_subtrees = match nc.permitted_subtrees { Some(data) => parse_general_subtrees(py, data)?, None => py.None(), }; let excluded_subtrees = match nc.excluded_subtrees { Some(data) => parse_general_subtrees(py, data)?, None => py.None(), }; Ok(Some( types::NAME_CONSTRAINTS .get(py)? .call1((permitted_subtrees, excluded_subtrees))?, )) } oid::MS_CERTIFICATE_TEMPLATE => { let ms_cert_tpl = ext.value::()?; let py_oid = oid_to_py_oid(py, &ms_cert_tpl.template_id)?; Ok(Some(types::MS_CERTIFICATE_TEMPLATE.get(py)?.call1(( py_oid, ms_cert_tpl.major_version, ms_cert_tpl.minor_version, ))?)) } _ => Ok(None), } } pub(crate) fn time_from_py( py: pyo3::Python<'_>, val: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let dt = x509::py_to_datetime(py, val.clone())?; time_from_datetime(dt) } pub(crate) fn time_from_datetime(dt: asn1::DateTime) -> CryptographyResult { if dt.year() >= 2050 { Ok(common::Time::GeneralizedTime(asn1::GeneralizedTime::new( dt, )?)) } else { Ok(common::Time::UtcTime(asn1::UtcTime::new(dt).unwrap())) } } #[pyo3::pyfunction] pub(crate) fn create_x509_certificate( py: pyo3::Python<'_>, builder: &pyo3::Bound<'_, pyo3::PyAny>, private_key: &pyo3::Bound<'_, pyo3::PyAny>, hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let sigalg = x509::sign::compute_signature_algorithm( py, private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), )?; let der = types::ENCODING_DER.get(py)?; let spki = types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?; let spki_bytes = builder .getattr(pyo3::intern!(py, "_public_key"))? .call_method1(pyo3::intern!(py, "public_bytes"), (der, spki))? .extract::()?; let py_serial = builder .getattr(pyo3::intern!(py, "_serial_number"))? .extract()?; let py_issuer_name = builder.getattr(pyo3::intern!(py, "_issuer_name"))?; let py_subject_name = builder.getattr(pyo3::intern!(py, "_subject_name"))?; let py_not_before = builder.getattr(pyo3::intern!(py, "_not_valid_before"))?; let py_not_after = builder.getattr(pyo3::intern!(py, "_not_valid_after"))?; let ka_vec = cryptography_keepalive::KeepAlive::new(); let ka_bytes = cryptography_keepalive::KeepAlive::new(); let serial_bytes = py_uint_to_big_endian_bytes(py, py_serial)?; let ka = cryptography_keepalive::KeepAlive::new(); let tbs_cert = cryptography_x509::certificate::TbsCertificate { version: builder .getattr(pyo3::intern!(py, "_version"))? .getattr(pyo3::intern!(py, "value"))? .extract()?, serial: asn1::BigInt::new(&serial_bytes).unwrap(), signature_alg: sigalg.clone(), issuer: x509::common::encode_name(py, &ka, &py_issuer_name)?, validity: cryptography_x509::certificate::Validity { not_before: time_from_py(py, &py_not_before)?, not_after: time_from_py(py, &py_not_after)?, }, subject: x509::common::encode_name(py, &ka, &py_subject_name)?, spki: asn1::parse_single(&spki_bytes)?, issuer_unique_id: None, subject_unique_id: None, raw_extensions: x509::common::encode_extensions( py, &ka_vec, &ka_bytes, &builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?, }; let tbs_bytes = asn1::write_single(&tbs_cert)?; let signature = x509::sign::sign_data( py, private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), &tbs_bytes, )?; let data = asn1::write_single(&cryptography_x509::certificate::Certificate { tbs_cert, signature_alg: sigalg, signature: asn1::BitString::new(&signature, 0).unwrap(), })?; load_der_x509_certificate( py, pyo3::types::PyBytes::new_bound(py, &data).unbind(), None, ) } pub(crate) fn set_bit(vals: &mut [u8], n: usize, set: bool) { let idx = n / 8; let v = 1 << (7 - (n & 0x07)); if set { vals[idx] |= v; } } cryptography-43.0.0/src/rust/src/x509/common.rs010064400017510000177000000465521464676315000174630ustar 00000000000000// 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. use cryptography_x509::common::{Asn1ReadableOrWritable, AttributeTypeValue, RawTlv}; use cryptography_x509::extensions::{ AccessDescription, DuplicateExtensionsError, Extension, Extensions, RawExtensions, }; use cryptography_x509::name::{GeneralName, Name, NameReadable, OtherName, UnvalidatedIA5String}; use pyo3::types::IntoPyDict; use pyo3::types::{PyAnyMethods, PyListMethods}; use pyo3::{IntoPy, ToPyObject}; use crate::asn1::{oid_to_py_oid, py_oid_to_oid}; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types, x509}; /// Parse all sections in a PEM file and return the first matching section. /// If no matching sections are found, return an error. pub(crate) fn find_in_pem( data: &[u8], filter_fn: fn(&pem::Pem) -> bool, no_match_err: &'static str, ) -> Result { let all_sections = pem::parse_many(data)?; if all_sections.is_empty() { return Err(CryptographyError::from(pem::PemError::MalformedFraming)); } all_sections.into_iter().find(filter_fn).ok_or_else(|| { CryptographyError::from(pyo3::exceptions::PyValueError::new_err(no_match_err)) }) } pub(crate) fn encode_name<'p>( py: pyo3::Python<'_>, ka: &'p cryptography_keepalive::KeepAlive, py_name: &pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult> { let mut rdns = vec![]; for py_rdn in py_name.getattr(pyo3::intern!(py, "rdns"))?.iter()? { let py_rdn = py_rdn?; let mut attrs = vec![]; for py_attr in py_rdn.iter()? { attrs.push(encode_name_entry(py, ka, &py_attr?)?); } rdns.push(asn1::SetOfWriter::new(attrs)); } Ok(Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(rdns), )) } pub(crate) fn encode_name_entry<'p>( py: pyo3::Python<'_>, ka: &'p cryptography_keepalive::KeepAlive, py_name_entry: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let attr_type = py_name_entry.getattr(pyo3::intern!(py, "_type"))?; let tag = attr_type .getattr(pyo3::intern!(py, "value"))? .extract::()?; let value: pyo3::pybacked::PyBackedBytes = if !attr_type.is(&types::ASN1_TYPE_BIT_STRING.get(py)?) { let encoding = if attr_type.is(&types::ASN1_TYPE_BMP_STRING.get(py)?) { "utf_16_be" } else if attr_type.is(&types::ASN1_TYPE_UNIVERSAL_STRING.get(py)?) { "utf_32_be" } else { "utf8" }; py_name_entry .getattr(pyo3::intern!(py, "value"))? .call_method1(pyo3::intern!(py, "encode"), (encoding,))? .extract()? } else { py_name_entry .getattr(pyo3::intern!(py, "value"))? .extract()? }; let py_oid = py_name_entry.getattr(pyo3::intern!(py, "oid"))?; let oid = py_oid_to_oid(py_oid)?; Ok(AttributeTypeValue { type_id: oid, value: RawTlv::new(asn1::Tag::from_bytes(&[tag])?.0, ka.add(value)), }) } #[pyo3::pyfunction] pub(crate) fn encode_name_bytes<'p>( py: pyo3::Python<'p>, py_name: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let ka = cryptography_keepalive::KeepAlive::new(); let name = encode_name(py, &ka, py_name)?; let result = asn1::write_single(&name)?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } pub(crate) fn encode_general_names<'a>( py: pyo3::Python<'_>, ka_bytes: &'a cryptography_keepalive::KeepAlive, ka_str: &'a cryptography_keepalive::KeepAlive, py_gns: &pyo3::Bound<'a, pyo3::PyAny>, ) -> Result>, CryptographyError> { let mut gns = vec![]; for el in py_gns.iter()? { let gn = encode_general_name(py, ka_bytes, ka_str, &el?)?; gns.push(gn); } Ok(gns) } pub(crate) fn encode_general_name<'a>( py: pyo3::Python<'_>, ka_bytes: &'a cryptography_keepalive::KeepAlive, ka_str: &'a cryptography_keepalive::KeepAlive, gn: &pyo3::Bound<'a, pyo3::PyAny>, ) -> Result, CryptographyError> { let gn_type = gn.get_type(); let gn_value = gn.getattr(pyo3::intern!(py, "value"))?; if gn_type.is(&types::DNS_NAME.get(py)?) { Ok(GeneralName::DNSName(UnvalidatedIA5String( ka_str.add(gn_value.extract()?), ))) } else if gn_type.is(&types::RFC822_NAME.get(py)?) { Ok(GeneralName::RFC822Name(UnvalidatedIA5String( ka_str.add(gn_value.extract()?), ))) } else if gn_type.is(&types::DIRECTORY_NAME.get(py)?) { let name = encode_name(py, ka_bytes, &gn_value)?; Ok(GeneralName::DirectoryName(name)) } else if gn_type.is(&types::OTHER_NAME.get(py)?) { let py_oid = gn.getattr(pyo3::intern!(py, "type_id"))?; Ok(GeneralName::OtherName(OtherName { type_id: py_oid_to_oid(py_oid)?, value: asn1::parse_single(ka_bytes.add(gn_value.extract()?)).map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( "OtherName value must be valid DER: {e:?}" )) })?, })) } else if gn_type.is(&types::UNIFORM_RESOURCE_IDENTIFIER.get(py)?) { Ok(GeneralName::UniformResourceIdentifier( UnvalidatedIA5String(ka_str.add(gn_value.extract()?)), )) } else if gn_type.is(&types::IP_ADDRESS.get(py)?) { Ok(GeneralName::IPAddress(ka_bytes.add( gn.call_method0(pyo3::intern!(py, "_packed"))?.extract()?, ))) } else if gn_type.is(&types::REGISTERED_ID.get(py)?) { let oid = py_oid_to_oid(gn_value)?; Ok(GeneralName::RegisteredID(oid)) } else { Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Unsupported GeneralName type"), )) } } pub(crate) fn encode_access_descriptions<'a>( py: pyo3::Python<'a>, py_ads: &pyo3::Bound<'a, pyo3::PyAny>, ) -> CryptographyResult> { let mut ads = vec![]; let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); for py_ad in py_ads.iter()? { let py_ad = py_ad?; let py_oid = py_ad.getattr(pyo3::intern!(py, "access_method"))?; let access_method = py_oid_to_oid(py_oid)?; let py_access_location = py_ad.getattr(pyo3::intern!(py, "access_location"))?; let access_location = encode_general_name(py, &ka_bytes, &ka_str, &py_access_location)?; ads.push(AccessDescription { access_method, access_location, }); } Ok(asn1::write_single(&asn1::SequenceOfWriter::new(ads))?) } pub(crate) fn parse_name<'p>( py: pyo3::Python<'p>, name: &NameReadable<'_>, ) -> Result, CryptographyError> { let py_rdns = pyo3::types::PyList::empty_bound(py); for rdn in name.clone() { let py_rdn = parse_rdn(py, &rdn)?; py_rdns.append(py_rdn)?; } Ok(types::NAME.get(py)?.call1((py_rdns,))?) } fn parse_name_attribute( py: pyo3::Python<'_>, attribute: AttributeTypeValue<'_>, ) -> Result { let oid = oid_to_py_oid(py, &attribute.type_id)?; let tag_val = attribute.value.tag().as_u8().ok_or_else(|| { CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Long-form tags are not supported in NameAttribute values", )) })?; let py_tag = types::ASN1_TYPE_TO_ENUM.get(py)?.get_item(tag_val)?; let py_data = match attribute.value.tag().as_u8() { // BitString tag value Some(3) => pyo3::types::PyBytes::new_bound(py, attribute.value.data()).into_any(), // BMPString tag value Some(30) => { let py_bytes = pyo3::types::PyBytes::new_bound(py, attribute.value.data()); py_bytes.call_method1(pyo3::intern!(py, "decode"), ("utf_16_be",))? } // UniversalString Some(28) => { let py_bytes = pyo3::types::PyBytes::new_bound(py, attribute.value.data()); py_bytes.call_method1(pyo3::intern!(py, "decode"), ("utf_32_be",))? } _ => { let parsed = std::str::from_utf8(attribute.value.data()) .map_err(|_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?; pyo3::types::PyString::new_bound(py, parsed).into_any() } }; let kwargs = [(pyo3::intern!(py, "_validate"), false)].into_py_dict_bound(py); Ok(types::NAME_ATTRIBUTE .get(py)? .call((oid, py_data, py_tag), Some(&kwargs))? .to_object(py)) } pub(crate) fn parse_rdn<'a>( py: pyo3::Python<'_>, rdn: &asn1::SetOf<'a, AttributeTypeValue<'a>>, ) -> Result { let py_attrs = pyo3::types::PyList::empty_bound(py); for attribute in rdn.clone() { let na = parse_name_attribute(py, attribute)?; py_attrs.append(na)?; } Ok(types::RELATIVE_DISTINGUISHED_NAME .get(py)? .call1((py_attrs,))? .to_object(py)) } pub(crate) fn parse_general_name( py: pyo3::Python<'_>, gn: GeneralName<'_>, ) -> Result { let py_gn = match gn { GeneralName::OtherName(data) => { let oid = oid_to_py_oid(py, &data.type_id)?; types::OTHER_NAME .get(py)? .call1((oid, data.value.full_data()))? .to_object(py) } GeneralName::RFC822Name(data) => types::RFC822_NAME .get(py)? .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::DNSName(data) => types::DNS_NAME .get(py)? .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::DirectoryName(data) => { let py_name = parse_name(py, data.unwrap_read())?; types::DIRECTORY_NAME .get(py)? .call1((py_name,))? .to_object(py) } GeneralName::UniformResourceIdentifier(data) => types::UNIFORM_RESOURCE_IDENTIFIER .get(py)? .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::IPAddress(data) => { if data.len() == 4 || data.len() == 16 { let addr = types::IPADDRESS_IPADDRESS.get(py)?.call1((data,))?; types::IP_ADDRESS.get(py)?.call1((addr,))?.to_object(py) } else { // if it's not an IPv4 or IPv6 we assume it's an IPNetwork and // verify length in this function. create_ip_network(py, data)? } } GeneralName::RegisteredID(data) => { let oid = oid_to_py_oid(py, &data)?; types::REGISTERED_ID.get(py)?.call1((oid,))?.to_object(py) } _ => { return Err(CryptographyError::from( exceptions::UnsupportedGeneralNameType::new_err( "x400Address/EDIPartyName are not supported types", ), )) } }; Ok(py_gn) } pub(crate) fn parse_general_names<'a>( py: pyo3::Python<'_>, gn_seq: &asn1::SequenceOf<'a, GeneralName<'a>>, ) -> Result { let gns = pyo3::types::PyList::empty_bound(py); for gn in gn_seq.clone() { let py_gn = parse_general_name(py, gn)?; gns.append(py_gn)?; } Ok(gns.to_object(py)) } fn create_ip_network( py: pyo3::Python<'_>, data: &[u8], ) -> Result { let prefix = match data.len() { 8 => { let num = u32::from_be_bytes(data[4..].try_into().unwrap()); ipv4_netmask(num) } 32 => { let num = u128::from_be_bytes(data[16..].try_into().unwrap()); ipv6_netmask(num) } _ => Err(CryptographyError::from(pyo3::exceptions::PyValueError::new_err( format!("Invalid IPNetwork, must be 8 bytes for IPv4 and 32 bytes for IPv6. Found length: {}", data.len()), ))), }; let base = types::IPADDRESS_IPADDRESS .get(py)? .call1((pyo3::types::PyBytes::new_bound(py, &data[..data.len() / 2]),))?; let net = format!( "{}/{}", base.getattr(pyo3::intern!(py, "exploded"))? .extract::()?, prefix? ); let addr = types::IPADDRESS_IPNETWORK.get(py)?.call1((net,))?; Ok(types::IP_ADDRESS.get(py)?.call1((addr,))?.to_object(py)) } fn ipv4_netmask(num: u32) -> Result { if num.leading_ones() + num.trailing_zeros() != 32 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid netmask"), )); } Ok((!num).leading_zeros()) } fn ipv6_netmask(num: u128) -> Result { if num.leading_ones() + num.trailing_zeros() != 128 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid netmask"), )); } Ok((!num).leading_zeros()) } pub(crate) fn parse_and_cache_extensions< 'p, F: Fn(&Extension<'_>) -> Result>, CryptographyError>, >( py: pyo3::Python<'p>, cached_extensions: &pyo3::sync::GILOnceCell, raw_extensions: &Option>, parse_ext: F, ) -> pyo3::PyResult { cached_extensions .get_or_try_init(py, || { let extensions = match Extensions::from_raw_extensions(raw_extensions.as_ref()) { Ok(extensions) => extensions, Err(DuplicateExtensionsError(oid)) => { let oid_obj = oid_to_py_oid(py, &oid)?; return Err(exceptions::DuplicateExtension::new_err(( format!("Duplicate {} extension found", &oid), oid_obj.into_py(py), ))); } }; let exts = pyo3::types::PyList::empty_bound(py); for raw_ext in extensions.iter() { let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?; let extn_value = match parse_ext(&raw_ext)? { Some(e) => e, None => types::UNRECOGNIZED_EXTENSION .get(py)? .call1((oid_obj.clone(), raw_ext.extn_value))?, }; let ext_obj = types::EXTENSION .get(py)? .call1((oid_obj, raw_ext.critical, extn_value))?; exts.append(ext_obj)?; } Ok(types::EXTENSIONS.get(py)?.call1((exts,))?.to_object(py)) }) .map(|p| p.clone_ref(py)) } pub(crate) fn encode_extensions< 'p, F: Fn( pyo3::Python<'_>, &asn1::ObjectIdentifier, &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult>>, >( py: pyo3::Python<'p>, ka_vec: &'p cryptography_keepalive::KeepAlive>, ka_bytes: &'p cryptography_keepalive::KeepAlive, py_exts: &pyo3::Bound<'p, pyo3::PyAny>, encode_ext: F, ) -> pyo3::PyResult>> { let mut exts = vec![]; for py_ext in py_exts.iter()? { let py_ext = py_ext?; let py_oid = py_ext.getattr(pyo3::intern!(py, "oid"))?; let oid = py_oid_to_oid(py_oid)?; let ext_val = py_ext.getattr(pyo3::intern!(py, "value"))?; if ext_val.is_instance(&types::UNRECOGNIZED_EXTENSION.get(py)?)? { exts.push(Extension { extn_id: oid, critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, extn_value: ka_bytes.add(ext_val.getattr(pyo3::intern!(py, "value"))?.extract()?), }); continue; } match encode_ext(py, &oid, &ext_val)? { Some(data) => { exts.push(Extension { extn_id: oid, critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, extn_value: ka_vec.add(data), }); } None => { return Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( "Extension not supported: {oid}" ))) } } } if exts.is_empty() { return Ok(None); } Ok(Some(Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(exts), ))) } #[pyo3::pyfunction] pub(crate) fn encode_extension_value<'p>( py: pyo3::Python<'p>, py_ext: pyo3::Bound<'p, pyo3::PyAny>, ) -> pyo3::PyResult> { let oid = py_oid_to_oid(py_ext.getattr(pyo3::intern!(py, "oid"))?)?; if let Some(data) = x509::extensions::encode_extension(py, &oid, &py_ext)? { // TODO: extra copy let py_data = pyo3::types::PyBytes::new_bound(py, &data); return Ok(py_data); } Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( "Extension not supported: {oid}" ))) } pub(crate) fn datetime_to_py<'p>( py: pyo3::Python<'p>, dt: &asn1::DateTime, ) -> pyo3::PyResult> { types::DATETIME_DATETIME.get(py)?.call1(( dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second(), )) } pub(crate) fn datetime_to_py_utc<'p>( py: pyo3::Python<'p>, dt: &asn1::DateTime, ) -> pyo3::PyResult> { let timezone = types::DATETIME_TIMEZONE_UTC.get(py)?; types::DATETIME_DATETIME.get(py)?.call1(( dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second(), 0, timezone, )) } pub(crate) fn py_to_datetime( py: pyo3::Python<'_>, val: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { // We treat naive datetimes as UTC times, while aware datetimes get // normalized to UTC before conversion. let val_utc = if val.getattr(pyo3::intern!(py, "tzinfo"))?.is_none() { val } else { let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; val.call_method1(pyo3::intern!(py, "astimezone"), (utc,))? }; Ok(asn1::DateTime::new( val_utc.getattr(pyo3::intern!(py, "year"))?.extract()?, val_utc.getattr(pyo3::intern!(py, "month"))?.extract()?, val_utc.getattr(pyo3::intern!(py, "day"))?.extract()?, val_utc.getattr(pyo3::intern!(py, "hour"))?.extract()?, val_utc.getattr(pyo3::intern!(py, "minute"))?.extract()?, val_utc.getattr(pyo3::intern!(py, "second"))?.extract()?, ) .unwrap()) } pub(crate) fn datetime_now(py: pyo3::Python<'_>) -> pyo3::PyResult { let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; py_to_datetime( py, types::DATETIME_DATETIME .get(py)? .call_method1(pyo3::intern!(py, "now"), (utc,))?, ) } cryptography-43.0.0/src/rust/src/x509/crl.rs010064400017510000177000000611651464676315000167500ustar 00000000000000// 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. use std::sync::Arc; use cryptography_x509::extensions::{Extension, IssuerAlternativeName}; use cryptography_x509::{ common, crl::{ self, CertificateRevocationList as RawCertificateRevocationList, RevokedCertificate as RawRevokedCertificate, }, name, oid, }; use pyo3::types::{PyAnyMethods, PyListMethods, PySliceMethods}; use pyo3::ToPyObject; use crate::asn1::{ big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, }; use crate::backend::hashes::Hash; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, extensions, sign}; use crate::{exceptions, types, x509}; #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] pub(crate) fn load_der_x509_crl( py: pyo3::Python<'_>, data: pyo3::Py, backend: Option>, ) -> Result { let _ = backend; let owned = OwnedCertificateRevocationList::try_new(data, |data| { asn1::parse_single(data.as_bytes(py)) })?; let version = owned.borrow_dependent().tbs_cert_list.version.unwrap_or(1); if version != 1 { return Err(CryptographyError::from( exceptions::InvalidVersion::new_err(( format!("{version} is not a valid CRL version"), version, )), )); } Ok(CertificateRevocationList { owned: Arc::new(owned), revoked_certs: pyo3::sync::GILOnceCell::new(), cached_extensions: pyo3::sync::GILOnceCell::new(), }) } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] pub(crate) fn load_pem_x509_crl( py: pyo3::Python<'_>, data: &[u8], backend: Option>, ) -> Result { let _ = backend; let block = x509::find_in_pem( data, |p| p.tag() == "X509 CRL", "Valid PEM but no BEGIN X509 CRL/END X509 delimiters. Are you sure this is a CRL?", )?; load_der_x509_crl( py, pyo3::types::PyBytes::new_bound(py, block.contents()).unbind(), None, ) } self_cell::self_cell!( struct OwnedCertificateRevocationList { owner: pyo3::Py, #[covariant] dependent: RawCertificateRevocationList, } ); #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct CertificateRevocationList { owned: Arc, revoked_certs: pyo3::sync::GILOnceCell>, cached_extensions: pyo3::sync::GILOnceCell, } impl CertificateRevocationList { fn public_bytes_der(&self) -> CryptographyResult> { Ok(asn1::write_single(&self.owned.borrow_dependent())?) } fn revoked_cert(&self, py: pyo3::Python<'_>, idx: usize) -> RevokedCertificate { RevokedCertificate { owned: self.revoked_certs.get(py).unwrap()[idx].clone(), cached_extensions: pyo3::sync::GILOnceCell::new(), } } fn len(&self) -> usize { self.owned .borrow_dependent() .tbs_cert_list .revoked_certificates .as_ref() .map_or(0, |v| v.unwrap_read().len()) } } #[pyo3::pymethods] impl CertificateRevocationList { fn __eq__(&self, other: pyo3::PyRef<'_, CertificateRevocationList>) -> bool { self.owned.borrow_dependent() == other.owned.borrow_dependent() } fn __len__(&self) -> usize { self.len() } fn __iter__(&self) -> CRLIterator { CRLIterator { contents: OwnedCRLIteratorData::try_new(Arc::clone(&self.owned), |v| { Ok::<_, ()>( v.borrow_dependent() .tbs_cert_list .revoked_certificates .as_ref() .map(|v| v.unwrap_read().clone()), ) }) .unwrap(), } } fn __getitem__( &self, py: pyo3::Python<'_>, idx: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { self.revoked_certs.get_or_init(py, || { let mut revoked_certs = vec![]; let mut it = self.__iter__(); while let Some(c) = it.__next__() { revoked_certs.push(c.owned); } revoked_certs }); if idx.is_instance_of::() { let indices = idx .downcast::()? .indices(self.len().try_into().unwrap())?; let result = pyo3::types::PyList::empty_bound(py); for i in (indices.start..indices.stop).step_by(indices.step.try_into().unwrap()) { let revoked_cert = pyo3::Bound::new(py, self.revoked_cert(py, i as usize))?; result.append(revoked_cert)?; } Ok(result.to_object(py)) } else { let mut idx = idx.extract::()?; if idx < 0 { idx += self.len() as isize; } if idx >= (self.len() as isize) || idx < 0 { return Err(pyo3::exceptions::PyIndexError::new_err(())); } Ok(pyo3::Bound::new(py, self.revoked_cert(py, idx as usize))?.to_object(py)) } } fn fingerprint<'p>( &self, py: pyo3::Python<'p>, algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult> { let data = self.public_bytes_der()?; let mut h = Hash::new(py, &algorithm, None)?; h.update_bytes(&data)?; Ok(h.finalize(py)?) } #[getter] fn signature_algorithm_oid<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { oid_to_py_oid(py, self.owned.borrow_dependent().signature_algorithm.oid()) } #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let oid = self.signature_algorithm_oid(py)?; match types::SIG_OIDS_TO_HASH.get(py)?.get_item(oid) { Ok(v) => Ok(v), Err(_) => Err(exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", self.owned.borrow_dependent().signature_algorithm.oid() ))), } } #[getter] fn signature_algorithm_parameters<'p>( &'p self, py: pyo3::Python<'p>, ) -> CryptographyResult> { sign::identify_signature_algorithm_parameters( py, &self.owned.borrow_dependent().signature_algorithm, ) } #[getter] fn signature(&self) -> &[u8] { self.owned.borrow_dependent().signature_value.as_bytes() } #[getter] fn tbs_certlist_bytes<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let b = asn1::write_single(&self.owned.borrow_dependent().tbs_cert_list)?; Ok(pyo3::types::PyBytes::new_bound(py, &b)) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, encoding: pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let result = asn1::write_single(&self.owned.borrow_dependent())?; encode_der_data(py, "X509 CRL".to_string(), result, &encoding) } #[getter] fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(x509::parse_name( py, self.owned .borrow_dependent() .tbs_cert_list .issuer .unwrap_read(), )?) } #[getter] fn next_update<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_42.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to next_update_utc.", 1, )?; match &self.owned.borrow_dependent().tbs_cert_list.next_update { Some(t) => x509::datetime_to_py(py, t.as_datetime()), None => Ok(py.None().into_bound(py)), } } #[getter] fn next_update_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { match &self.owned.borrow_dependent().tbs_cert_list.next_update { Some(t) => x509::datetime_to_py_utc(py, t.as_datetime()), None => Ok(py.None().into_bound(py)), } } #[getter] fn last_update<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_42.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to last_update_utc.", 1, )?; x509::datetime_to_py( py, self.owned .borrow_dependent() .tbs_cert_list .this_update .as_datetime(), ) } #[getter] fn last_update_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { x509::datetime_to_py_utc( py, self.owned .borrow_dependent() .tbs_cert_list .this_update .as_datetime(), ) } #[getter] fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let tbs_cert_list = &self.owned.borrow_dependent().tbs_cert_list; x509::parse_and_cache_extensions( py, &self.cached_extensions, &tbs_cert_list.raw_crl_extensions, |ext| match ext.extn_id { oid::CRL_NUMBER_OID => { let bignum = ext.value::>()?; let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; Ok(Some(types::CRL_NUMBER.get(py)?.call1((pynum,))?)) } oid::DELTA_CRL_INDICATOR_OID => { let bignum = ext.value::>()?; let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; Ok(Some(types::DELTA_CRL_INDICATOR.get(py)?.call1((pynum,))?)) } oid::ISSUER_ALTERNATIVE_NAME_OID => { let gn_seq = ext.value::>()?; let ians = x509::parse_general_names(py, &gn_seq)?; Ok(Some( types::ISSUER_ALTERNATIVE_NAME.get(py)?.call1((ians,))?, )) } oid::AUTHORITY_INFORMATION_ACCESS_OID => { let ads = certificate::parse_access_descriptions(py, ext)?; Ok(Some( types::AUTHORITY_INFORMATION_ACCESS.get(py)?.call1((ads,))?, )) } oid::AUTHORITY_KEY_IDENTIFIER_OID => { Ok(Some(certificate::parse_authority_key_identifier(py, ext)?)) } oid::ISSUING_DISTRIBUTION_POINT_OID => { let idp = ext.value::>()?; let (full_name, relative_name) = match idp.distribution_point { Some(data) => certificate::parse_distribution_point_name(py, data)?, None => (py.None(), py.None()), }; let py_reasons = if let Some(reasons) = idp.only_some_reasons { certificate::parse_distribution_point_reasons( py, Some(reasons.unwrap_read()), )? } else { py.None() }; Ok(Some(types::ISSUING_DISTRIBUTION_POINT.get(py)?.call1(( full_name, relative_name, idp.only_contains_user_certs, idp.only_contains_ca_certs, py_reasons, idp.indirect_crl, idp.only_contains_attribute_certs, ))?)) } oid::FRESHEST_CRL_OID => { let dp = certificate::parse_distribution_points(py, ext)?; Ok(Some(types::FRESHEST_CRL.get(py)?.call1((dp,))?)) } _ => Ok(None), }, ) } fn get_revoked_certificate_by_serial_number( &self, py: pyo3::Python<'_>, serial: pyo3::Bound<'_, pyo3::types::PyLong>, ) -> pyo3::PyResult> { let serial_bytes = py_uint_to_big_endian_bytes(py, serial)?; let owned = OwnedRevokedCertificate::try_new(Arc::clone(&self.owned), |v| { let certs = match &v.borrow_dependent().tbs_cert_list.revoked_certificates { Some(certs) => certs.unwrap_read().clone(), None => return Err(()), }; // TODO: linear scan. Make a hash or bisect! for cert in certs { if serial_bytes == cert.user_certificate.as_bytes() { return Ok(cert); } } Err(()) }); match owned { Ok(o) => Ok(Some(RevokedCertificate { owned: o, cached_extensions: pyo3::sync::GILOnceCell::new(), })), Err(()) => Ok(None), } } fn is_signature_valid<'p>( slf: pyo3::PyRef<'_, Self>, py: pyo3::Python<'p>, public_key: pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult { if slf.owned.borrow_dependent().tbs_cert_list.signature != slf.owned.borrow_dependent().signature_algorithm { return Ok(false); }; // Error on invalid public key -- below we treat any error as just // being an invalid signature. sign::identify_public_key_type(py, public_key.clone())?; Ok(sign::verify_signature_with_signature_algorithm( py, public_key, &slf.owned.borrow_dependent().signature_algorithm, slf.owned.borrow_dependent().signature_value.as_bytes(), &asn1::write_single(&slf.owned.borrow_dependent().tbs_cert_list)?, ) .is_ok()) } } type RawCRLIterator<'a> = Option>>; self_cell::self_cell!( struct OwnedCRLIteratorData { owner: Arc, #[covariant] dependent: RawCRLIterator, } ); #[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] struct CRLIterator { contents: OwnedCRLIteratorData, } // Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 fn try_map_arc_data_mut_crl_iterator( it: &mut OwnedCRLIteratorData, f: impl for<'this> FnOnce( &'this OwnedCertificateRevocationList, &mut Option>>, ) -> Result, E>, ) -> Result { OwnedRevokedCertificate::try_new(Arc::clone(it.borrow_owner()), |inner_it| { it.with_dependent_mut(|_, value| { // SAFETY: This is safe because `Arc::clone` ensures the data is // alive, but Rust doesn't understand the lifetime relationship it // produces. Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 f(inner_it, unsafe { std::mem::transmute::< &mut Option>>, &mut Option>>, >(value) }) }) }) } #[pyo3::pymethods] impl CRLIterator { fn __len__(&self) -> usize { self.contents .borrow_dependent() .clone() .map_or(0, |v| v.len()) } fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } fn __next__(&mut self) -> Option { let revoked = try_map_arc_data_mut_crl_iterator(&mut self.contents, |_data, v| match v { Some(v) => match v.next() { Some(revoked) => Ok(revoked), None => Err(()), }, None => Err(()), }) .ok()?; Some(RevokedCertificate { owned: revoked, cached_extensions: pyo3::sync::GILOnceCell::new(), }) } } self_cell::self_cell!( struct OwnedRevokedCertificate { owner: Arc, #[covariant] dependent: RawRevokedCertificate, } ); impl Clone for OwnedRevokedCertificate { fn clone(&self) -> OwnedRevokedCertificate { // SAFETY: This is safe because `Arc::clone` ensures the data is // alive, but Rust doesn't understand the lifetime relationship it // produces. Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 OwnedRevokedCertificate::new(Arc::clone(self.borrow_owner()), |_| unsafe { std::mem::transmute(self.borrow_dependent().clone()) }) } } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct RevokedCertificate { owned: OwnedRevokedCertificate, cached_extensions: pyo3::sync::GILOnceCell, } #[pyo3::pymethods] impl RevokedCertificate { #[getter] fn serial_number<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { big_byte_slice_to_py_int( py, self.owned.borrow_dependent().user_certificate.as_bytes(), ) } #[getter] fn revocation_date<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_42.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to revocation_date_utc.", 1, )?; x509::datetime_to_py( py, self.owned.borrow_dependent().revocation_date.as_datetime(), ) } #[getter] fn revocation_date_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { x509::datetime_to_py_utc( py, self.owned.borrow_dependent().revocation_date.as_datetime(), ) } #[getter] fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { x509::parse_and_cache_extensions( py, &self.cached_extensions, &self.owned.borrow_dependent().raw_crl_entry_extensions, |ext| parse_crl_entry_ext(py, ext), ) } } pub(crate) fn parse_crl_reason_flags<'p>( py: pyo3::Python<'p>, reason: &crl::CRLReason, ) -> CryptographyResult> { let flag_name = match reason.value() { 0 => "unspecified", 1 => "key_compromise", 2 => "ca_compromise", 3 => "affiliation_changed", 4 => "superseded", 5 => "cessation_of_operation", 6 => "certificate_hold", 8 => "remove_from_crl", 9 => "privilege_withdrawn", 10 => "aa_compromise", value => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "Unsupported reason code: {value}" )), )) } }; Ok(types::REASON_FLAGS.get(py)?.getattr(flag_name)?) } pub fn parse_crl_entry_ext<'p>( py: pyo3::Python<'p>, ext: &Extension<'_>, ) -> CryptographyResult>> { match ext.extn_id { oid::CRL_REASON_OID => { let flags = parse_crl_reason_flags(py, &ext.value::()?)?; Ok(Some(types::CRL_REASON.get(py)?.call1((flags,))?)) } oid::CERTIFICATE_ISSUER_OID => { let gn_seq = ext.value::>>()?; let gns = x509::parse_general_names(py, &gn_seq)?; Ok(Some(types::CERTIFICATE_ISSUER.get(py)?.call1((gns,))?)) } oid::INVALIDITY_DATE_OID => { let time = ext.value::()?; let py_dt = x509::datetime_to_py(py, time.as_datetime())?; Ok(Some(types::INVALIDITY_DATE.get(py)?.call1((py_dt,))?)) } _ => Ok(None), } } #[pyo3::pyfunction] pub(crate) fn create_x509_crl( py: pyo3::Python<'_>, builder: &pyo3::Bound<'_, pyo3::PyAny>, private_key: &pyo3::Bound<'_, pyo3::PyAny>, hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let sigalg = x509::sign::compute_signature_algorithm( py, private_key.to_owned(), hash_algorithm.to_owned(), rsa_padding.to_owned(), )?; let mut revoked_certs = vec![]; let ka_vec = cryptography_keepalive::KeepAlive::new(); let ka_bytes = cryptography_keepalive::KeepAlive::new(); for py_revoked_cert in builder .getattr(pyo3::intern!(py, "_revoked_certificates"))? .iter()? { let py_revoked_cert = py_revoked_cert?; let serial_number = py_revoked_cert .getattr(pyo3::intern!(py, "serial_number"))? .extract()?; let py_revocation_date = py_revoked_cert.getattr(pyo3::intern!(py, "revocation_date_utc"))?; let serial_bytes = ka_bytes.add(py_uint_to_big_endian_bytes(py, serial_number)?); revoked_certs.push(crl::RevokedCertificate { user_certificate: asn1::BigUint::new(serial_bytes).unwrap(), revocation_date: x509::certificate::time_from_py(py, &py_revocation_date)?, raw_crl_entry_extensions: x509::common::encode_extensions( py, &ka_vec, &ka_bytes, &py_revoked_cert.getattr(pyo3::intern!(py, "extensions"))?, extensions::encode_extension, )?, }); } let ka = cryptography_keepalive::KeepAlive::new(); let py_issuer_name = builder.getattr(pyo3::intern!(py, "_issuer_name"))?; let py_this_update = builder.getattr(pyo3::intern!(py, "_last_update"))?; let py_next_update = builder.getattr(pyo3::intern!(py, "_next_update"))?; let tbs_cert_list = crl::TBSCertList { version: Some(1), signature: sigalg.clone(), issuer: x509::common::encode_name(py, &ka, &py_issuer_name)?, this_update: x509::certificate::time_from_py(py, &py_this_update)?, next_update: Some(x509::certificate::time_from_py(py, &py_next_update)?), revoked_certificates: if revoked_certs.is_empty() { None } else { Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(revoked_certs), )) }, raw_crl_extensions: x509::common::encode_extensions( py, &ka_vec, &ka_bytes, &builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?, }; let tbs_bytes = asn1::write_single(&tbs_cert_list)?; let signature = x509::sign::sign_data( py, private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), &tbs_bytes, )?; let data = asn1::write_single(&crl::CertificateRevocationList { tbs_cert_list, signature_algorithm: sigalg, signature_value: asn1::BitString::new(&signature, 0).unwrap(), })?; load_der_x509_crl( py, pyo3::types::PyBytes::new_bound(py, &data).unbind(), None, ) } cryptography-43.0.0/src/rust/src/x509/csr.rs010064400017510000177000000321621464676315000167520ustar 00000000000000// 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. use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use asn1::SimpleAsn1Readable; use cryptography_x509::csr::{check_attribute_length, Attribute, CertificationRequestInfo, Csr}; use cryptography_x509::{common, oid}; use pyo3::types::{PyAnyMethods, PyListMethods}; use pyo3::IntoPy; use crate::asn1::{encode_der_data, oid_to_py_oid, py_oid_to_oid}; use crate::backend::keys; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, sign}; use crate::{exceptions, types, x509}; self_cell::self_cell!( struct OwnedCsr { owner: pyo3::Py, #[covariant] dependent: Csr, } ); #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct CertificateSigningRequest { raw: OwnedCsr, cached_extensions: pyo3::sync::GILOnceCell, } #[pyo3::pymethods] impl CertificateSigningRequest { fn __hash__(&self, py: pyo3::Python<'_>) -> u64 { let mut hasher = DefaultHasher::new(); self.raw.borrow_owner().as_bytes(py).hash(&mut hasher); hasher.finish() } fn __eq__( &self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, CertificateSigningRequest>, ) -> bool { self.raw.borrow_owner().as_bytes(py) == other.raw.borrow_owner().as_bytes(py) } fn public_key(&self, py: pyo3::Python<'_>) -> CryptographyResult { keys::load_der_public_key_bytes( py, self.raw.borrow_dependent().csr_info.spki.tlv().full_data(), ) } #[getter] fn public_key_algorithm_oid<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { oid_to_py_oid( py, self.raw.borrow_dependent().csr_info.spki.algorithm.oid(), ) } #[getter] fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(x509::parse_name( py, self.raw.borrow_dependent().csr_info.subject.unwrap_read(), )?) } #[getter] fn tbs_certrequest_bytes<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let result = asn1::write_single(&self.raw.borrow_dependent().csr_info)?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } #[getter] fn signature<'p>(&self, py: pyo3::Python<'p>) -> pyo3::Bound<'p, pyo3::types::PyBytes> { pyo3::types::PyBytes::new_bound(py, self.raw.borrow_dependent().signature.as_bytes()) } #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { sign::identify_signature_hash_algorithm(py, &self.raw.borrow_dependent().signature_alg) } #[getter] fn signature_algorithm_oid<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { oid_to_py_oid(py, self.raw.borrow_dependent().signature_alg.oid()) } #[getter] fn signature_algorithm_parameters<'p>( &'p self, py: pyo3::Python<'p>, ) -> CryptographyResult> { sign::identify_signature_algorithm_parameters( py, &self.raw.borrow_dependent().signature_alg, ) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let result = asn1::write_single(self.raw.borrow_dependent())?; encode_der_data(py, "CERTIFICATE REQUEST".to_string(), result, encoding) } fn get_attribute_for_oid<'p>( &self, py: pyo3::Python<'p>, oid: pyo3::Bound<'p, pyo3::PyAny>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_36.get(py)?; let warning_msg = "CertificateSigningRequest.get_attribute_for_oid has been deprecated. Please switch to request.attributes.get_attribute_for_oid."; pyo3::PyErr::warn_bound(py, &warning_cls, warning_msg, 1)?; let rust_oid = py_oid_to_oid(oid.clone())?; for attribute in self .raw .borrow_dependent() .csr_info .attributes .unwrap_read() .clone() { if rust_oid == attribute.type_id { check_attribute_length(attribute.values.unwrap_read().clone()).map_err(|_| { pyo3::exceptions::PyValueError::new_err( "Only single-valued attributes are supported", ) })?; let val = attribute.values.unwrap_read().clone().next().unwrap(); // We allow utf8string, printablestring, and ia5string at this time if val.tag() == asn1::Utf8String::TAG || val.tag() == asn1::PrintableString::TAG || val.tag() == asn1::IA5String::TAG { return Ok(pyo3::types::PyBytes::new_bound(py, val.data()).into_any()); } return Err(pyo3::exceptions::PyValueError::new_err(format!( "OID {} has a disallowed ASN.1 type: {:?}", oid, val.tag() ))); } } Err(exceptions::AttributeNotFound::new_err(( format!("No {oid} attribute was found"), oid.into_py(py), ))) } #[getter] fn attributes<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { let pyattrs = pyo3::types::PyList::empty_bound(py); for attribute in self .raw .borrow_dependent() .csr_info .attributes .unwrap_read() .clone() { check_attribute_length(attribute.values.unwrap_read().clone()).map_err(|_| { pyo3::exceptions::PyValueError::new_err( "Only single-valued attributes are supported", ) })?; let oid = oid_to_py_oid(py, &attribute.type_id)?; let val = attribute.values.unwrap_read().clone().next().unwrap(); let serialized = pyo3::types::PyBytes::new_bound(py, val.data()); let tag = val.tag().as_u8().ok_or_else(|| { CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Long-form tags are not supported in CSR attribute values", )) })?; let pyattr = types::ATTRIBUTE.get(py)?.call1((oid, serialized, tag))?; pyattrs.append(pyattr)?; } types::ATTRIBUTES.get(py)?.call1((pyattrs,)) } #[getter] fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let raw_exts = self .raw .borrow_dependent() .csr_info .get_extension_attribute() .map_err(|_| { pyo3::exceptions::PyValueError::new_err( "Only single-valued attributes are supported", ) })?; x509::parse_and_cache_extensions(py, &self.cached_extensions, &raw_exts, |ext| { certificate::parse_cert_ext(py, ext) }) } #[getter] fn is_signature_valid( slf: pyo3::PyRef<'_, Self>, py: pyo3::Python<'_>, ) -> CryptographyResult { let public_key = slf.public_key(py)?; Ok(sign::verify_signature_with_signature_algorithm( py, public_key.bind(py).clone(), &slf.raw.borrow_dependent().signature_alg, slf.raw.borrow_dependent().signature.as_bytes(), &asn1::write_single(&slf.raw.borrow_dependent().csr_info)?, ) .is_ok()) } } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] pub(crate) fn load_pem_x509_csr( py: pyo3::Python<'_>, data: &[u8], backend: Option>, ) -> CryptographyResult { let _ = backend; // We support both PEM header strings that OpenSSL does // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L35-L36 let parsed = x509::find_in_pem( data, |p| p.tag() == "CERTIFICATE REQUEST" || p.tag() == "NEW CERTIFICATE REQUEST", "Valid PEM but no BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters. Are you sure this is a CSR?", )?; load_der_x509_csr( py, pyo3::types::PyBytes::new_bound(py, parsed.contents()).unbind(), None, ) } #[pyo3::pyfunction] #[pyo3(signature = (data, backend=None))] pub(crate) fn load_der_x509_csr( py: pyo3::Python<'_>, data: pyo3::Py, backend: Option>, ) -> CryptographyResult { let _ = backend; let raw = OwnedCsr::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; let version = raw.borrow_dependent().csr_info.version; if version != 0 { return Err(CryptographyError::from( exceptions::InvalidVersion::new_err(( format!("{version} is not a valid CSR version"), version, )), )); } Ok(CertificateSigningRequest { raw, cached_extensions: pyo3::sync::GILOnceCell::new(), }) } #[pyo3::pyfunction] pub(crate) fn create_x509_csr( py: pyo3::Python<'_>, builder: &pyo3::Bound<'_, pyo3::PyAny>, private_key: &pyo3::Bound<'_, pyo3::PyAny>, hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, rsa_padding: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let sigalg = x509::sign::compute_signature_algorithm( py, private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), )?; let der = types::ENCODING_DER.get(py)?; let spki = types::PUBLIC_FORMAT_SUBJECT_PUBLIC_KEY_INFO.get(py)?; let spki_bytes = private_key .call_method0(pyo3::intern!(py, "public_key"))? .call_method1(pyo3::intern!(py, "public_bytes"), (der, spki))? .extract::()?; let ka_vec = cryptography_keepalive::KeepAlive::new(); let ka_bytes = cryptography_keepalive::KeepAlive::new(); let mut attrs = vec![]; let ext_bytes; if let Some(exts) = x509::common::encode_extensions( py, &ka_vec, &ka_bytes, &builder.getattr(pyo3::intern!(py, "_extensions"))?, x509::extensions::encode_extension, )? { ext_bytes = asn1::write_single(&exts)?; attrs.push(Attribute { type_id: (oid::EXTENSION_REQUEST).clone(), values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ asn1::parse_single(&ext_bytes)?, ])), }); } let mut attr_values = vec![]; for py_attr in builder.getattr(pyo3::intern!(py, "_attributes"))?.iter()? { let (py_oid, value, tag): ( pyo3::Bound<'_, pyo3::PyAny>, pyo3::pybacked::PyBackedBytes, Option, ) = py_attr?.extract()?; let oid = py_oid_to_oid(py_oid)?; let tag = if let Some(tag) = tag { asn1::Tag::from_bytes(&[tag])?.0 } else { if std::str::from_utf8(&value).is_err() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Attribute values must be valid utf-8.", ), )); } asn1::Utf8String::TAG }; attr_values.push((oid, tag, value)); } for (oid, tag, value) in &attr_values { attrs.push(Attribute { type_id: oid.clone(), values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ common::RawTlv::new(*tag, value), ])), }); } let py_subject_name = builder.getattr(pyo3::intern!(py, "_subject_name"))?; let ka = cryptography_keepalive::KeepAlive::new(); let csr_info = CertificationRequestInfo { version: 0, subject: x509::common::encode_name(py, &ka, &py_subject_name)?, spki: asn1::parse_single(&spki_bytes)?, attributes: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(attrs)), }; let tbs_bytes = asn1::write_single(&csr_info)?; let signature = x509::sign::sign_data( py, private_key.clone(), hash_algorithm.clone(), rsa_padding.clone(), &tbs_bytes, )?; let data = asn1::write_single(&Csr { csr_info, signature_alg: sigalg, signature: asn1::BitString::new(&signature, 0).unwrap(), })?; load_der_x509_csr( py, pyo3::types::PyBytes::new_bound(py, &data).clone().unbind(), None, ) } cryptography-43.0.0/src/rust/src/x509/extensions.rs010064400017510000177000000537451464676315000203740ustar 00000000000000// 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. use cryptography_x509::{common, crl, extensions, oid}; use crate::asn1::{py_oid_to_oid, py_uint_to_big_endian_bytes}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, sct}; use crate::{types, x509}; use pyo3::pybacked::PyBackedStr; use pyo3::types::PyAnyMethods; fn encode_general_subtrees<'a>( py: pyo3::Python<'_>, ka_bytes: &'a cryptography_keepalive::KeepAlive, ka_str: &'a cryptography_keepalive::KeepAlive, subtrees: &pyo3::Bound<'a, pyo3::PyAny>, ) -> Result>, CryptographyError> { if subtrees.is_none() { Ok(None) } else { let mut subtree_seq = vec![]; for name in subtrees.iter()? { let gn = x509::common::encode_general_name(py, ka_bytes, ka_str, &name?)?; subtree_seq.push(extensions::GeneralSubtree { base: gn, minimum: 0, maximum: None, }); } Ok(Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(subtree_seq), ))) } } pub(crate) fn encode_authority_key_identifier<'a>( py: pyo3::Python<'a>, py_aki: &pyo3::Bound<'a, pyo3::PyAny>, ) -> CryptographyResult> { #[derive(pyo3::FromPyObject)] struct PyAuthorityKeyIdentifier<'a> { key_identifier: Option, authority_cert_issuer: Option>, authority_cert_serial_number: Option>, } let aki = py_aki.extract::>()?; let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); let authority_cert_issuer = if let Some(authority_cert_issuer) = aki.authority_cert_issuer { let gns = x509::common::encode_general_names(py, &ka_bytes, &ka_str, &authority_cert_issuer)?; Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(gns), )) } else { None }; let serial_bytes; let authority_cert_serial_number = if let Some(authority_cert_serial_number) = aki.authority_cert_serial_number { serial_bytes = py_uint_to_big_endian_bytes(py, authority_cert_serial_number)?; Some(asn1::BigUint::new(&serial_bytes).unwrap()) } else { None }; Ok(asn1::write_single(&extensions::AuthorityKeyIdentifier { authority_cert_issuer, authority_cert_serial_number, key_identifier: aki.key_identifier.as_deref(), })?) } pub(crate) fn encode_distribution_points<'p>( py: pyo3::Python<'p>, py_dps: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { #[derive(pyo3::FromPyObject)] struct PyDistributionPoint<'a> { crl_issuer: Option>, full_name: Option>, relative_name: Option>, reasons: Option>, } let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); let mut dps = vec![]; for py_dp in py_dps.iter()? { let py_dp = py_dp?.extract::>()?; let crl_issuer = if let Some(py_crl_issuer) = py_dp.crl_issuer { let gns = x509::common::encode_general_names(py, &ka_bytes, &ka_str, &py_crl_issuer)?; Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(gns), )) } else { None }; let distribution_point = if let Some(py_full_name) = py_dp.full_name { let gns = x509::common::encode_general_names(py, &ka_bytes, &ka_str, &py_full_name)?; Some(extensions::DistributionPointName::FullName( common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), )) } else if let Some(py_relative_name) = py_dp.relative_name { let mut name_entries = vec![]; for py_name_entry in py_relative_name.iter()? { let ne = x509::common::encode_name_entry(py, &ka_bytes, &py_name_entry?)?; name_entries.push(ne); } Some(extensions::DistributionPointName::NameRelativeToCRLIssuer( common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), )) } else { None }; let reasons = if let Some(py_reasons) = py_dp.reasons { let reasons = certificate::encode_distribution_point_reasons(py, &py_reasons)?; Some(common::Asn1ReadableOrWritable::new_write(reasons)) } else { None }; dps.push(extensions::DistributionPoint { crl_issuer, distribution_point, reasons, }); } Ok(asn1::write_single(&asn1::SequenceOfWriter::new(dps))?) } fn encode_basic_constraints(ext: &pyo3::Bound<'_, pyo3::PyAny>) -> CryptographyResult> { #[derive(pyo3::FromPyObject)] struct PyBasicConstraints { ca: bool, path_length: Option, } let pybc = ext.extract::()?; let bc = extensions::BasicConstraints { ca: pybc.ca, path_length: pybc.path_length, }; Ok(asn1::write_single(&bc)?) } fn encode_key_usage( py: pyo3::Python<'_>, ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let mut bs = [0, 0]; certificate::set_bit( &mut bs, 0, ext.getattr(pyo3::intern!(py, "digital_signature"))? .is_truthy()?, ); certificate::set_bit( &mut bs, 1, ext.getattr(pyo3::intern!(py, "content_commitment"))? .is_truthy()?, ); certificate::set_bit( &mut bs, 2, ext.getattr(pyo3::intern!(py, "key_encipherment"))? .is_truthy()?, ); certificate::set_bit( &mut bs, 3, ext.getattr(pyo3::intern!(py, "data_encipherment"))? .is_truthy()?, ); certificate::set_bit( &mut bs, 4, ext.getattr(pyo3::intern!(py, "key_agreement"))? .is_truthy()?, ); certificate::set_bit( &mut bs, 5, ext.getattr(pyo3::intern!(py, "key_cert_sign"))? .is_truthy()?, ); certificate::set_bit( &mut bs, 6, ext.getattr(pyo3::intern!(py, "crl_sign"))?.is_truthy()?, ); if ext .getattr(pyo3::intern!(py, "key_agreement"))? .is_truthy()? { certificate::set_bit( &mut bs, 7, ext.getattr(pyo3::intern!(py, "encipher_only"))? .is_truthy()?, ); certificate::set_bit( &mut bs, 8, ext.getattr(pyo3::intern!(py, "decipher_only"))? .is_truthy()?, ); } let (bits, unused_bits) = if bs[1] == 0 { if bs[0] == 0 { (&[][..], 0) } else { (&bs[..1], bs[0].trailing_zeros() as u8) } } else { (&bs[..], bs[1].trailing_zeros() as u8) }; let v = asn1::BitString::new(bits, unused_bits).unwrap(); Ok(asn1::write_single(&v)?) } fn encode_certificate_policies( py: pyo3::Python<'_>, ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let mut policy_informations = vec![]; let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); for py_policy_info in ext.iter()? { let py_policy_info = py_policy_info?; let py_policy_qualifiers = py_policy_info.getattr(pyo3::intern!(py, "policy_qualifiers"))?; let qualifiers = if py_policy_qualifiers.is_truthy()? { let mut qualifiers = vec![]; for py_qualifier in py_policy_qualifiers.iter()? { let py_qualifier = py_qualifier?; let qualifier = if py_qualifier.is_instance_of::() { let py_qualifier_str = ka_str.add(py_qualifier.extract::()?); let cps_uri = match asn1::IA5String::new(py_qualifier_str) { Some(s) => s, None => { return Err(pyo3::exceptions::PyValueError::new_err( "Qualifier must be an ASCII-string.", ) .into()) } }; extensions::PolicyQualifierInfo { policy_qualifier_id: (oid::CP_CPS_URI_OID).clone(), qualifier: extensions::Qualifier::CpsUri(cps_uri), } } else { let py_notice = py_qualifier.getattr(pyo3::intern!(py, "notice_reference"))?; let notice_ref = if py_notice.is_truthy()? { let mut notice_numbers = vec![]; for py_num in py_notice .getattr(pyo3::intern!(py, "notice_numbers"))? .iter()? { let bytes = ka_bytes .add(py_uint_to_big_endian_bytes(ext.py(), py_num?.extract()?)?); notice_numbers.push(asn1::BigUint::new(bytes).unwrap()); } let py_notice_str = ka_str.add( py_notice .getattr(pyo3::intern!(py, "organization"))? .extract::()?, ); Some(extensions::NoticeReference { organization: extensions::DisplayText::Utf8String( asn1::Utf8String::new(py_notice_str), ), notice_numbers: common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(notice_numbers), ), }) } else { None }; let py_explicit_text = py_qualifier.getattr(pyo3::intern!(py, "explicit_text"))?; let explicit_text = if py_explicit_text.is_truthy()? { let py_explicit_text_str = ka_str.add(py_explicit_text.extract::()?); Some(extensions::DisplayText::Utf8String(asn1::Utf8String::new( py_explicit_text_str, ))) } else { None }; extensions::PolicyQualifierInfo { policy_qualifier_id: (oid::CP_USER_NOTICE_OID).clone(), qualifier: extensions::Qualifier::UserNotice(extensions::UserNotice { notice_ref, explicit_text, }), } }; qualifiers.push(qualifier); } Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(qualifiers), )) } else { None }; let py_policy_id = py_policy_info.getattr(pyo3::intern!(py, "policy_identifier"))?; policy_informations.push(extensions::PolicyInformation { policy_identifier: py_oid_to_oid(py_policy_id)?, policy_qualifiers: qualifiers, }); } Ok(asn1::write_single(&asn1::SequenceOfWriter::new( policy_informations, ))?) } fn encode_issuing_distribution_point( py: pyo3::Python<'_>, ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); let only_some_reasons = if ext .getattr(pyo3::intern!(py, "only_some_reasons"))? .is_truthy()? { let py_reasons = ext.getattr(pyo3::intern!(py, "only_some_reasons"))?; let reasons = certificate::encode_distribution_point_reasons(ext.py(), &py_reasons)?; Some(common::Asn1ReadableOrWritable::new_write(reasons)) } else { None }; let distribution_point = if ext.getattr(pyo3::intern!(py, "full_name"))?.is_truthy()? { let py_full_name = ext.getattr(pyo3::intern!(py, "full_name"))?; let gns = x509::common::encode_general_names(ext.py(), &ka_bytes, &ka_str, &py_full_name)?; Some(extensions::DistributionPointName::FullName( common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), )) } else if ext .getattr(pyo3::intern!(py, "relative_name"))? .is_truthy()? { let mut name_entries = vec![]; for py_name_entry in ext.getattr(pyo3::intern!(py, "relative_name"))?.iter()? { let name_entry = x509::common::encode_name_entry(ext.py(), &ka_bytes, &py_name_entry?)?; name_entries.push(name_entry); } Some(extensions::DistributionPointName::NameRelativeToCRLIssuer( common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), )) } else { None }; let idp = crl::IssuingDistributionPoint { distribution_point, indirect_crl: ext.getattr(pyo3::intern!(py, "indirect_crl"))?.extract()?, only_contains_attribute_certs: ext .getattr(pyo3::intern!(py, "only_contains_attribute_certs"))? .extract()?, only_contains_ca_certs: ext .getattr(pyo3::intern!(py, "only_contains_ca_certs"))? .extract()?, only_contains_user_certs: ext .getattr(pyo3::intern!(py, "only_contains_user_certs"))? .extract()?, only_some_reasons, }; Ok(asn1::write_single(&idp)?) } fn encode_oid_sequence(ext: &pyo3::Bound<'_, pyo3::PyAny>) -> CryptographyResult> { let mut oids = vec![]; for el in ext.iter()? { let oid = py_oid_to_oid(el?)?; oids.push(oid); } Ok(asn1::write_single(&asn1::SequenceOfWriter::new(oids))?) } fn encode_tls_features( py: pyo3::Python<'_>, ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { // Ideally we'd skip building up a vec and just write directly into the // writer. This isn't possible at the moment because the callback to write // an asn1::Sequence can't return an error, and we need to handle errors // from Python. let mut els = vec![]; for el in ext.iter()? { els.push(el?.getattr(pyo3::intern!(py, "value"))?.extract::()?); } Ok(asn1::write_single(&asn1::SequenceOfWriter::new(els))?) } fn encode_scts(ext: &pyo3::Bound<'_, pyo3::PyAny>) -> CryptographyResult> { let mut length = 0; for sct in ext.iter()? { let sct = sct?.downcast::()?.clone(); length += sct.get().sct_data.len() + 2; } let mut result = vec![]; result.extend_from_slice(&(length as u16).to_be_bytes()); for sct in ext.iter()? { let sct = sct?.downcast::()?.clone(); result.extend_from_slice(&(sct.get().sct_data.len() as u16).to_be_bytes()); result.extend_from_slice(&sct.get().sct_data); } Ok(asn1::write_single(&result.as_slice())?) } pub(crate) fn encode_extension( py: pyo3::Python<'_>, oid: &asn1::ObjectIdentifier, ext: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult>> { match oid { &oid::BASIC_CONSTRAINTS_OID => { let der = encode_basic_constraints(ext)?; Ok(Some(der)) } &oid::SUBJECT_KEY_IDENTIFIER_OID => { let digest = ext .getattr(pyo3::intern!(py, "digest"))? .extract::()?; Ok(Some(asn1::write_single(&digest.as_ref())?)) } &oid::KEY_USAGE_OID => { let der = encode_key_usage(py, ext)?; Ok(Some(der)) } &oid::AUTHORITY_INFORMATION_ACCESS_OID | &oid::SUBJECT_INFORMATION_ACCESS_OID => { let der = x509::common::encode_access_descriptions(ext.py(), ext)?; Ok(Some(der)) } &oid::EXTENDED_KEY_USAGE_OID | &oid::ACCEPTABLE_RESPONSES_OID => { let der = encode_oid_sequence(ext)?; Ok(Some(der)) } &oid::CERTIFICATE_POLICIES_OID => { let der = encode_certificate_policies(py, ext)?; Ok(Some(der)) } &oid::POLICY_CONSTRAINTS_OID => { let pc = extensions::PolicyConstraints { require_explicit_policy: ext .getattr(pyo3::intern!(py, "require_explicit_policy"))? .extract()?, inhibit_policy_mapping: ext .getattr(pyo3::intern!(py, "inhibit_policy_mapping"))? .extract()?, }; Ok(Some(asn1::write_single(&pc)?)) } &oid::NAME_CONSTRAINTS_OID => { let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); let permitted = ext.getattr(pyo3::intern!(py, "permitted_subtrees"))?; let excluded = ext.getattr(pyo3::intern!(py, "excluded_subtrees"))?; let nc = extensions::NameConstraints { permitted_subtrees: encode_general_subtrees( ext.py(), &ka_bytes, &ka_str, &permitted, )?, excluded_subtrees: encode_general_subtrees( ext.py(), &ka_bytes, &ka_str, &excluded, )?, }; Ok(Some(asn1::write_single(&nc)?)) } &oid::INHIBIT_ANY_POLICY_OID => { let intval = ext .getattr(pyo3::intern!(py, "skip_certs"))? .downcast::()? .clone(); let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; Ok(Some(asn1::write_single( &asn1::BigUint::new(&bytes).unwrap(), )?)) } &oid::ISSUER_ALTERNATIVE_NAME_OID | &oid::SUBJECT_ALTERNATIVE_NAME_OID => { let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); let gns = x509::common::encode_general_names(ext.py(), &ka_bytes, &ka_str, ext)?; Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) } &oid::AUTHORITY_KEY_IDENTIFIER_OID => { let der = encode_authority_key_identifier(ext.py(), ext)?; Ok(Some(der)) } &oid::FRESHEST_CRL_OID | &oid::CRL_DISTRIBUTION_POINTS_OID => { let der = encode_distribution_points(ext.py(), ext)?; Ok(Some(der)) } &oid::OCSP_NO_CHECK_OID => Ok(Some(asn1::write_single(&())?)), &oid::TLS_FEATURE_OID => { let der = encode_tls_features(py, ext)?; Ok(Some(der)) } &oid::PRECERT_POISON_OID => Ok(Some(asn1::write_single(&())?)), &oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID | &oid::SIGNED_CERTIFICATE_TIMESTAMPS_OID => { let der = encode_scts(ext)?; Ok(Some(der)) } &oid::CRL_REASON_OID => { let value = types::CRL_ENTRY_REASON_ENUM_TO_CODE .get(ext.py())? .get_item(ext.getattr(pyo3::intern!(py, "reason"))?)? .extract::()?; Ok(Some(asn1::write_single(&asn1::Enumerated::new(value))?)) } &oid::CERTIFICATE_ISSUER_OID => { let ka_bytes = cryptography_keepalive::KeepAlive::new(); let ka_str = cryptography_keepalive::KeepAlive::new(); let gns = x509::common::encode_general_names(ext.py(), &ka_bytes, &ka_str, ext)?; Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) } &oid::INVALIDITY_DATE_OID => { let py_dt = ext.getattr(pyo3::intern!(py, "invalidity_date_utc"))?; let dt = x509::py_to_datetime(py, py_dt)?; Ok(Some(asn1::write_single(&asn1::GeneralizedTime::new(dt)?)?)) } &oid::CRL_NUMBER_OID | &oid::DELTA_CRL_INDICATOR_OID => { let intval = ext .getattr(pyo3::intern!(py, "crl_number"))? .downcast::()? .clone(); let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; Ok(Some(asn1::write_single( &asn1::BigUint::new(&bytes).unwrap(), )?)) } &oid::ISSUING_DISTRIBUTION_POINT_OID => { let der = encode_issuing_distribution_point(py, ext)?; Ok(Some(der)) } &oid::NONCE_OID => { let nonce = ext .getattr(pyo3::intern!(py, "nonce"))? .extract::()?; Ok(Some(asn1::write_single(&nonce.as_ref())?)) } &oid::MS_CERTIFICATE_TEMPLATE => { let py_template_id = ext.getattr(pyo3::intern!(py, "template_id"))?; let mstpl = extensions::MSCertificateTemplate { template_id: py_oid_to_oid(py_template_id)?, major_version: ext.getattr(pyo3::intern!(py, "major_version"))?.extract()?, minor_version: ext.getattr(pyo3::intern!(py, "minor_version"))?.extract()?, }; Ok(Some(asn1::write_single(&mstpl)?)) } _ => Ok(None), } } cryptography-43.0.0/src/rust/src/x509/mod.rs010064400017510000177000000011701464676315000167350ustar 00000000000000// 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. pub(crate) mod certificate; pub(crate) mod common; pub(crate) mod crl; pub(crate) mod csr; pub(crate) mod extensions; pub(crate) mod ocsp; pub(crate) mod ocsp_req; pub(crate) mod ocsp_resp; pub(crate) mod sct; pub(crate) mod sign; pub(crate) mod verify; pub(crate) use common::{ datetime_to_py, datetime_to_py_utc, find_in_pem, parse_and_cache_extensions, parse_general_name, parse_general_names, parse_name, parse_rdn, py_to_datetime, }; cryptography-43.0.0/src/rust/src/x509/ocsp.rs010064400017510000177000000106531464676315000171300ustar 00000000000000// 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. use std::collections::HashMap; use cryptography_x509::common; use cryptography_x509::ocsp_req::CertID; use once_cell::sync::Lazy; use pyo3::types::PyAnyMethods; use crate::backend::hashes::Hash; use crate::error::CryptographyResult; use crate::x509::certificate::Certificate; pub(crate) static ALGORITHM_PARAMETERS_TO_HASH: Lazy< HashMap, &str>, > = Lazy::new(|| { let mut h = HashMap::new(); h.insert(common::AlgorithmParameters::Sha1(None), "SHA1"); h.insert(common::AlgorithmParameters::Sha1(Some(())), "SHA1"); h.insert(common::AlgorithmParameters::Sha224(None), "SHA224"); h.insert(common::AlgorithmParameters::Sha224(Some(())), "SHA224"); h.insert(common::AlgorithmParameters::Sha256(None), "SHA256"); h.insert(common::AlgorithmParameters::Sha256(Some(())), "SHA256"); h.insert(common::AlgorithmParameters::Sha384(None), "SHA384"); h.insert(common::AlgorithmParameters::Sha384(Some(())), "SHA384"); h.insert(common::AlgorithmParameters::Sha512(None), "SHA512"); h.insert(common::AlgorithmParameters::Sha512(Some(())), "SHA512"); h }); pub(crate) static HASH_NAME_TO_ALGORITHM_IDENTIFIERS: Lazy< HashMap<&str, common::AlgorithmIdentifier<'_>>, > = Lazy::new(|| { let mut h = HashMap::new(); h.insert( "sha1", common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Sha1(Some(())), }, ); h.insert( "sha224", common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Sha224(Some(())), }, ); h.insert( "sha256", common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Sha256(Some(())), }, ); h.insert( "sha384", common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Sha384(Some(())), }, ); h.insert( "sha512", common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Sha512(Some(())), }, ); h }); pub(crate) fn certid_new<'p>( py: pyo3::Python<'p>, ka: &'p cryptography_keepalive::KeepAlive, cert: &'p Certificate, issuer: &'p Certificate, hash_algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let issuer_der = asn1::write_single(&cert.raw.borrow_dependent().tbs_cert.issuer)?; let issuer_name_hash = pyo3::pybacked::PyBackedBytes::from(hash_data(py, hash_algorithm, &issuer_der)?); let issuer_key_hash = pyo3::pybacked::PyBackedBytes::from(hash_data( py, hash_algorithm, issuer .raw .borrow_dependent() .tbs_cert .spki .subject_public_key .as_bytes(), )?); Ok(CertID { hash_algorithm: HASH_NAME_TO_ALGORITHM_IDENTIFIERS[&*hash_algorithm .getattr(pyo3::intern!(py, "name"))? .extract::()?] .clone(), issuer_name_hash: ka.add(issuer_name_hash), issuer_key_hash: ka.add(issuer_key_hash), serial_number: cert.raw.borrow_dependent().tbs_cert.serial, }) } pub(crate) fn certid_new_from_hash<'p>( py: pyo3::Python<'p>, issuer_name_hash: &'p [u8], issuer_key_hash: &'p [u8], serial_number: asn1::BigInt<'p>, hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { let hash_name = hash_algorithm .getattr(pyo3::intern!(py, "name"))? .extract::()?; Ok(CertID { hash_algorithm: HASH_NAME_TO_ALGORITHM_IDENTIFIERS[&*hash_name].clone(), issuer_name_hash, issuer_key_hash, serial_number, }) } pub(crate) fn hash_data<'p>( py: pyo3::Python<'p>, py_hash_alg: &pyo3::Bound<'p, pyo3::PyAny>, data: &[u8], ) -> pyo3::PyResult> { let mut h = Hash::new(py, py_hash_alg, None)?; h.update_bytes(data)?; Ok(h.finalize(py)?) } cryptography-43.0.0/src/rust/src/x509/ocsp_req.rs010064400017510000177000000171541464676315000200020ustar 00000000000000// 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. use cryptography_x509::{ common, ocsp_req::{self, OCSPRequest as RawOCSPRequest}, oid, }; use pyo3::types::{PyAnyMethods, PyListMethods}; use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{extensions, ocsp}; use crate::{exceptions, types, x509}; self_cell::self_cell!( struct OwnedOCSPRequest { owner: pyo3::Py, #[covariant] dependent: RawOCSPRequest, } ); #[pyo3::pyfunction] pub(crate) fn load_der_ocsp_request( py: pyo3::Python<'_>, data: pyo3::Py, ) -> CryptographyResult { let raw = OwnedOCSPRequest::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; if raw .borrow_dependent() .tbs_request .request_list .unwrap_read() .len() != 1 { return Err(CryptographyError::from( pyo3::exceptions::PyNotImplementedError::new_err( "OCSP request contains more than one request", ), )); } Ok(OCSPRequest { raw, cached_extensions: pyo3::sync::GILOnceCell::new(), }) } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] pub(crate) struct OCSPRequest { raw: OwnedOCSPRequest, cached_extensions: pyo3::sync::GILOnceCell, } impl OCSPRequest { fn cert_id(&self) -> ocsp_req::CertID<'_> { self.raw .borrow_dependent() .tbs_request .request_list .unwrap_read() .clone() .next() .unwrap() .req_cert } } #[pyo3::pymethods] impl OCSPRequest { #[getter] fn issuer_name_hash(&self) -> &[u8] { self.cert_id().issuer_name_hash } #[getter] fn issuer_key_hash(&self) -> &[u8] { self.cert_id().issuer_key_hash } #[getter] fn hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let cert_id = self.cert_id(); match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&cert_id.hash_algorithm.params) { Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", cert_id.hash_algorithm.oid() )), )), } } #[getter] fn serial_number<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let bytes = self.cert_id().serial_number.as_bytes(); Ok(big_byte_slice_to_py_int(py, bytes)?) } #[getter] fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let tbs_request = &self.raw.borrow_dependent().tbs_request; x509::parse_and_cache_extensions( py, &self.cached_extensions, &tbs_request.raw_request_extensions, |ext| { match ext.extn_id { oid::NONCE_OID => { // This is a disaster. RFC 2560 says that the contents of the nonce is // just the raw extension value. This is nonsense, since they're always // supposed to be ASN.1 TLVs. RFC 6960 correctly specifies that the // nonce is an OCTET STRING, and so you should unwrap the TLV to get // the nonce. So we try parsing as a TLV and fall back to just using // the raw value. let nonce = ext.value::<&[u8]>().unwrap_or(ext.extn_value); Ok(Some(types::OCSP_NONCE.get(py)?.call1((nonce,))?)) } oid::ACCEPTABLE_RESPONSES_OID => { let oids = ext.value::>()?; let py_oids = pyo3::types::PyList::empty_bound(py); for oid in oids { py_oids.append(oid_to_py_oid(py, &oid)?)?; } Ok(Some( types::OCSP_ACCEPTABLE_RESPONSES .get(py)? .call1((py_oids,))?, )) } _ => Ok(None), } }, ) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, encoding: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { if !encoding.is(&types::ENCODING_DER.get(py)?) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) .into()); } let result = asn1::write_single(self.raw.borrow_dependent())?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } } #[pyo3::pyfunction] pub(crate) fn create_ocsp_request( py: pyo3::Python<'_>, builder: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let builder_request = builder.getattr(pyo3::intern!(py, "_request"))?; let serial_number_bytes; let ka_vec = cryptography_keepalive::KeepAlive::new(); let ka_bytes = cryptography_keepalive::KeepAlive::new(); // Declare outside the if-block so the lifetimes are right. let (py_cert, py_issuer, py_hash, issuer_name_hash, issuer_key_hash): ( pyo3::PyRef<'_, x509::certificate::Certificate>, pyo3::PyRef<'_, x509::certificate::Certificate>, pyo3::Bound<'_, pyo3::PyAny>, pyo3::pybacked::PyBackedBytes, pyo3::pybacked::PyBackedBytes, ); let req_cert = if !builder_request.is_none() { (py_cert, py_issuer, py_hash) = builder_request.extract()?; ocsp::certid_new(py, &ka_bytes, &py_cert, &py_issuer, &py_hash)? } else { let py_serial: pyo3::Bound<'_, pyo3::types::PyLong>; (issuer_name_hash, issuer_key_hash, py_serial, py_hash) = builder .getattr(pyo3::intern!(py, "_request_hash"))? .extract()?; serial_number_bytes = py_uint_to_big_endian_bytes(py, py_serial)?; let serial_number = asn1::BigInt::new(&serial_number_bytes).unwrap(); ocsp::certid_new_from_hash( py, &issuer_name_hash, &issuer_key_hash, serial_number, py_hash, )? }; let extensions = x509::common::encode_extensions( py, &ka_vec, &ka_bytes, &builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?; let reqs = [ocsp_req::Request { req_cert, single_request_extensions: None, }]; let ocsp_req = ocsp_req::OCSPRequest { tbs_request: ocsp_req::TBSRequest { version: 0, requestor_name: None, request_list: common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( &reqs, )), raw_request_extensions: extensions, }, optional_signature: None, }; let data = asn1::write_single(&ocsp_req)?; load_der_ocsp_request(py, pyo3::types::PyBytes::new_bound(py, &data).unbind()) } cryptography-43.0.0/src/rust/src/x509/ocsp_resp.rs010064400017510000177000001064021464676315000201570ustar 00000000000000// 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. use std::sync::Arc; use cryptography_x509::ocsp_resp::SingleResponse; use cryptography_x509::{ common, ocsp_resp::{self, OCSPResponse as RawOCSPResponse, SingleResponse as RawSingleResponse}, oid, }; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid}; use crate::error::{CryptographyError, CryptographyResult}; use crate::x509::{certificate, crl, extensions, ocsp, py_to_datetime, sct}; use crate::{exceptions, types, x509}; const BASIC_RESPONSE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 1); #[pyo3::pyfunction] pub(crate) fn load_der_ocsp_response( py: pyo3::Python<'_>, data: pyo3::Py, ) -> Result { let raw = OwnedOCSPResponse::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; let response = raw.borrow_dependent(); match response.response_status.value() { SUCCESSFUL_RESPONSE => match response.response_bytes { Some(ref bytes) => { if bytes.response_type != BASIC_RESPONSE_OID { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Successful OCSP response does not contain a BasicResponse", ), )); } } None => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Successful OCSP response does not contain a BasicResponse", ), )) } }, MALFORMED_REQUEST_RESPONSE | INTERNAL_ERROR_RESPONSE | TRY_LATER_RESPONSE | SIG_REQUIRED_RESPONSE | UNAUTHORIZED_RESPONSE => {} _ => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("OCSP response has an unknown status code"), )) } }; Ok(OCSPResponse { raw: Arc::new(raw), cached_extensions: pyo3::sync::GILOnceCell::new(), cached_single_extensions: pyo3::sync::GILOnceCell::new(), }) } self_cell::self_cell!( struct OwnedOCSPResponse { owner: pyo3::Py, #[covariant] dependent: RawOCSPResponse, } ); #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] pub(crate) struct OCSPResponse { raw: Arc, cached_extensions: pyo3::sync::GILOnceCell, cached_single_extensions: pyo3::sync::GILOnceCell, } impl OCSPResponse { fn requires_successful_response(&self) -> pyo3::PyResult<&ocsp_resp::BasicOCSPResponse<'_>> { match self.raw.borrow_dependent().response_bytes.as_ref() { Some(b) => Ok(b.response.get()), None => Err(pyo3::exceptions::PyValueError::new_err( "OCSP response status is not successful so the property has no value", )), } } } const SUCCESSFUL_RESPONSE: u32 = 0; const MALFORMED_REQUEST_RESPONSE: u32 = 1; const INTERNAL_ERROR_RESPONSE: u32 = 2; const TRY_LATER_RESPONSE: u32 = 3; // 4 is unused const SIG_REQUIRED_RESPONSE: u32 = 5; const UNAUTHORIZED_RESPONSE: u32 = 6; #[pyo3::pymethods] impl OCSPResponse { #[getter] fn responses(&self) -> Result { self.requires_successful_response()?; Ok(OCSPResponseIterator { contents: OwnedOCSPResponseIteratorData::try_new(Arc::clone(&self.raw), |v| { Ok::<_, ()>( v.borrow_dependent() .response_bytes .as_ref() .unwrap() .response .get() .tbs_response_data .responses .unwrap_read() .clone(), ) }) .unwrap(), }) } #[getter] fn response_status<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let status = self.raw.borrow_dependent().response_status.value(); let attr = if status == SUCCESSFUL_RESPONSE { "SUCCESSFUL" } else if status == MALFORMED_REQUEST_RESPONSE { "MALFORMED_REQUEST" } else if status == INTERNAL_ERROR_RESPONSE { "INTERNAL_ERROR" } else if status == TRY_LATER_RESPONSE { "TRY_LATER" } else if status == SIG_REQUIRED_RESPONSE { "SIG_REQUIRED" } else { assert_eq!(status, UNAUTHORIZED_RESPONSE); "UNAUTHORIZED" }; types::OCSP_RESPONSE_STATUS.get(py)?.getattr(attr) } #[getter] fn responder_name<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; match resp.tbs_response_data.responder_id { ocsp_resp::ResponderId::ByName(ref name) => { Ok(x509::parse_name(py, name.unwrap_read())?) } ocsp_resp::ResponderId::ByKey(_) => Ok(py.None().into_bound(py)), } } #[getter] fn responder_key_hash<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; match resp.tbs_response_data.responder_id { ocsp_resp::ResponderId::ByKey(key_hash) => { Ok(pyo3::types::PyBytes::new_bound(py, key_hash).into_any()) } ocsp_resp::ResponderId::ByName(_) => Ok(py.None().into_bound(py)), } } #[getter] fn produced_at<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_43.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to produced_at_utc.", 1, )?; let resp = self.requires_successful_response()?; x509::datetime_to_py(py, resp.tbs_response_data.produced_at.as_datetime()) } #[getter] fn produced_at_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; x509::datetime_to_py_utc(py, resp.tbs_response_data.produced_at.as_datetime()) } #[getter] fn signature_algorithm_oid<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; oid_to_py_oid(py, resp.signature_algorithm.oid()) } #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let hash_alg = types::SIG_OIDS_TO_HASH .get(py)? .get_item(self.signature_algorithm_oid(py)?); match hash_alg { Ok(data) => Ok(data), Err(_) => { let exc_message = format!( "Signature algorithm OID: {} not recognized", self.requires_successful_response()? .signature_algorithm .oid() ); Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(exc_message), )) } } } #[getter] fn signature<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; Ok(pyo3::types::PyBytes::new_bound( py, resp.signature.as_bytes(), )) } #[getter] fn tbs_response_bytes<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let resp = self.requires_successful_response()?; let result = asn1::write_single(&resp.tbs_response_data)?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } #[getter] fn certificates<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let resp = self.requires_successful_response()?; let py_certs = pyo3::types::PyList::empty_bound(py); let certs = match &resp.certs { Some(certs) => certs.unwrap_read(), None => return Ok(py_certs), }; for i in 0..certs.len() { // TODO: O(n^2), don't have too many certificates! let raw_cert = map_arc_data_ocsp_response(py, &self.raw, |_data, resp| { resp.response_bytes .as_ref() .unwrap() .response .get() .certs .as_ref() .unwrap() .unwrap_read() .clone() .nth(i) .unwrap() }); py_certs.append(pyo3::Bound::new( py, x509::certificate::Certificate { raw: raw_cert, cached_extensions: pyo3::sync::GILOnceCell::new(), }, )?)?; } Ok(py_certs) } #[getter] fn serial_number<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_serial_number(&single_resp, py) } #[getter] fn issuer_key_hash(&self) -> Result<&[u8], CryptographyError> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; Ok(single_resp.cert_id.issuer_key_hash) } #[getter] fn issuer_name_hash(&self) -> Result<&[u8], CryptographyError> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; Ok(single_resp.cert_id.issuer_name_hash) } #[getter] fn hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_hash_algorithm(&single_resp, py) } #[getter] fn certificate_status<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_certificate_status(&single_resp, py) } #[getter] fn revocation_time<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_43.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to revocation_time_utc.", 1, )?; let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_revocation_time(&single_resp, py) } #[getter] fn revocation_time_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_revocation_time_utc(&single_resp, py) } #[getter] fn revocation_reason<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_revocation_reason(&single_resp, py) } #[getter] fn this_update<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_43.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to this_update_utc.", 1, )?; let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_this_update(&single_resp, py) } #[getter] fn this_update_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_this_update_utc(&single_resp, py) } #[getter] fn next_update<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_43.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to next_update_utc.", 1, )?; let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_next_update(&single_resp, py) } #[getter] fn next_update_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let resp = self.requires_successful_response()?; let single_resp = single_response(resp)?; singleresp_py_next_update_utc(&single_resp, py) } #[getter] fn extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { self.requires_successful_response()?; let response_data = &self .raw .borrow_dependent() .response_bytes .as_ref() .unwrap() .response .get() .tbs_response_data; x509::parse_and_cache_extensions( py, &self.cached_extensions, &response_data.raw_response_extensions, |ext| { match &ext.extn_id { &oid::NONCE_OID => { // This is a disaster. RFC 2560 says that the contents of the nonce is // just the raw extension value. This is nonsense, since they're always // supposed to be ASN.1 TLVs. RFC 6960 correctly specifies that the // nonce is an OCTET STRING, and so you should unwrap the TLV to get // the nonce. So we try parsing as a TLV and fall back to just using // the raw value. let nonce = ext.value::<&[u8]>().unwrap_or(ext.extn_value); Ok(Some(types::OCSP_NONCE.get(py)?.call1((nonce,))?)) } _ => Ok(None), } }, ) } #[getter] fn single_extensions(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { self.requires_successful_response()?; let single_resp = single_response( self.raw .borrow_dependent() .response_bytes .as_ref() .unwrap() .response .get(), )?; x509::parse_and_cache_extensions( py, &self.cached_single_extensions, &single_resp.raw_single_extensions, |ext| match &ext.extn_id { &oid::SIGNED_CERTIFICATE_TIMESTAMPS_OID => { let contents = ext.value::<&[u8]>()?; let scts = sct::parse_scts(py, contents, sct::LogEntryType::Certificate)?; Ok(Some( types::SIGNED_CERTIFICATE_TIMESTAMPS .get(py)? .call1((scts,))?, )) } _ => crl::parse_crl_entry_ext(py, ext), }, ) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, encoding: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { if !encoding.is(&types::ENCODING_DER.get(py)?) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) .into()); } let result = asn1::write_single(self.raw.borrow_dependent())?; Ok(pyo3::types::PyBytes::new_bound(py, &result)) } } // Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 fn map_arc_data_ocsp_response( py: pyo3::Python<'_>, it: &OwnedOCSPResponse, f: impl for<'this> FnOnce( &'this [u8], &ocsp_resp::OCSPResponse<'this>, ) -> cryptography_x509::certificate::Certificate<'this>, ) -> certificate::OwnedCertificate { certificate::OwnedCertificate::new(it.borrow_owner().clone_ref(py), |inner_it| { it.with_dependent(|_, value| { // SAFETY: This is safe because `Arc::clone` ensures the data is // alive, but Rust doesn't understand the lifetime relationship it // produces. Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 f(inner_it.as_bytes(py), unsafe { std::mem::transmute::<&ocsp_resp::OCSPResponse<'_>, &ocsp_resp::OCSPResponse<'_>>( value, ) }) }) }) } fn try_map_arc_data_mut_ocsp_response_iterator( it: &mut OwnedOCSPResponseIteratorData, f: impl for<'this> FnOnce( &'this OwnedOCSPResponse, &mut asn1::SequenceOf<'this, ocsp_resp::SingleResponse<'this>>, ) -> Result, E>, ) -> Result { OwnedSingleResponse::try_new(Arc::clone(it.borrow_owner()), |inner_it| { it.with_dependent_mut(|_, value| { // SAFETY: This is safe because `Arc::clone` ensures the data is // alive, but Rust doesn't understand the lifetime relationship it // produces. Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 f(inner_it, unsafe { std::mem::transmute::< &mut asn1::SequenceOf<'_, ocsp_resp::SingleResponse<'_>>, &mut asn1::SequenceOf<'_, ocsp_resp::SingleResponse<'_>>, >(value) }) }) }) } fn single_response<'a>( resp: &ocsp_resp::BasicOCSPResponse<'a>, ) -> Result, CryptographyError> { let responses = resp.tbs_response_data.responses.unwrap_read(); let num_responses = responses.len(); if num_responses != 1 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "OCSP response contains {num_responses} SINGLERESP structures. Use .response_iter to iterate through them" )) )); } Ok(responses.clone().next().unwrap()) } fn singleresp_py_serial_number<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { big_byte_slice_to_py_int(py, resp.cert_id.serial_number.as_bytes()) } fn singleresp_py_certificate_status<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let attr = match resp.cert_status { ocsp_resp::CertStatus::Good(_) => pyo3::intern!(py, "GOOD"), ocsp_resp::CertStatus::Revoked(_) => pyo3::intern!(py, "REVOKED"), ocsp_resp::CertStatus::Unknown(_) => pyo3::intern!(py, "UNKNOWN"), }; types::OCSP_CERT_STATUS.get(py)?.getattr(attr) } fn singleresp_py_hash_algorithm<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&resp.cert_id.hash_algorithm.params) { Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", resp.cert_id.hash_algorithm.oid() )), )), } } fn singleresp_py_this_update<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { x509::datetime_to_py(py, resp.this_update.as_datetime()) } fn singleresp_py_this_update_utc<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { x509::datetime_to_py_utc(py, resp.this_update.as_datetime()) } fn singleresp_py_next_update<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { match &resp.next_update { Some(v) => x509::datetime_to_py(py, v.as_datetime()), None => Ok(py.None().into_bound(py)), } } fn singleresp_py_next_update_utc<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { match &resp.next_update { Some(v) => x509::datetime_to_py_utc(py, v.as_datetime()), None => Ok(py.None().into_bound(py)), } } fn singleresp_py_revocation_reason<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> CryptographyResult> { match &resp.cert_status { ocsp_resp::CertStatus::Revoked(revoked_info) => match revoked_info.revocation_reason { Some(ref v) => Ok(crl::parse_crl_reason_flags(py, v)?), None => Ok(py.None().into_bound(py)), }, ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { Ok(py.None().into_bound(py)) } } } fn singleresp_py_revocation_time<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { match &resp.cert_status { ocsp_resp::CertStatus::Revoked(revoked_info) => { x509::datetime_to_py(py, revoked_info.revocation_time.as_datetime()) } ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { Ok(py.None().into_bound(py)) } } } fn singleresp_py_revocation_time_utc<'p>( resp: &ocsp_resp::SingleResponse<'_>, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { match &resp.cert_status { ocsp_resp::CertStatus::Revoked(revoked_info) => { x509::datetime_to_py_utc(py, revoked_info.revocation_time.as_datetime()) } ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { Ok(py.None().into_bound(py)) } } } #[pyo3::pyfunction] pub(crate) fn create_ocsp_response( py: pyo3::Python<'_>, status: &pyo3::Bound<'_, pyo3::PyAny>, builder: &pyo3::Bound<'_, pyo3::PyAny>, private_key: &pyo3::Bound<'_, pyo3::PyAny>, hash_algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { let response_status = status .getattr(pyo3::intern!(py, "value"))? .extract::()?; let py_cert: pyo3::PyRef<'_, x509::certificate::Certificate>; let py_issuer: pyo3::PyRef<'_, x509::certificate::Certificate>; let borrowed_cert; let py_certs: Option>>; if response_status != SUCCESSFUL_RESPONSE { let resp = ocsp_resp::OCSPResponse { response_status: asn1::Enumerated::new(response_status), response_bytes: None, }; let data = asn1::write_single(&resp)?; return load_der_ocsp_response(py, pyo3::types::PyBytes::new_bound(py, &data).unbind()); } let py_single_resp = builder.getattr(pyo3::intern!(py, "_response"))?; py_cert = py_single_resp .getattr(pyo3::intern!(py, "_cert"))? .extract()?; py_issuer = py_single_resp .getattr(pyo3::intern!(py, "_issuer"))? .extract()?; let py_cert_hash_algorithm = py_single_resp.getattr(pyo3::intern!(py, "_algorithm"))?; let (responder_cert, responder_encoding): ( pyo3::Bound<'_, x509::certificate::Certificate>, pyo3::Bound<'_, pyo3::PyAny>, ) = builder .getattr(pyo3::intern!(py, "_responder_id"))? .extract()?; let py_cert_status = py_single_resp.getattr(pyo3::intern!(py, "_cert_status"))?; let cert_status = if py_cert_status.is(&types::OCSP_CERT_STATUS_GOOD.get(py)?) { ocsp_resp::CertStatus::Good(()) } else if py_cert_status.is(&types::OCSP_CERT_STATUS_UNKNOWN.get(py)?) { ocsp_resp::CertStatus::Unknown(()) } else { let revocation_reason = if !py_single_resp .getattr(pyo3::intern!(py, "_revocation_reason"))? .is_none() { let value = types::CRL_ENTRY_REASON_ENUM_TO_CODE .get(py)? .get_item(py_single_resp.getattr(pyo3::intern!(py, "_revocation_reason"))?)? .extract::()?; Some(asn1::Enumerated::new(value)) } else { None }; // REVOKED let py_revocation_time = py_single_resp.getattr(pyo3::intern!(py, "_revocation_time"))?; let revocation_time = asn1::GeneralizedTime::new(py_to_datetime(py, py_revocation_time)?)?; ocsp_resp::CertStatus::Revoked(ocsp_resp::RevokedInfo { revocation_time, revocation_reason, }) }; let next_update = if !py_single_resp .getattr(pyo3::intern!(py, "_next_update"))? .is_none() { let py_next_update = py_single_resp.getattr(pyo3::intern!(py, "_next_update"))?; Some(asn1::GeneralizedTime::new(py_to_datetime( py, py_next_update, )?)?) } else { None }; let py_this_update = py_single_resp.getattr(pyo3::intern!(py, "_this_update"))?; let this_update = asn1::GeneralizedTime::new(py_to_datetime(py, py_this_update)?)?; let ka_vec = cryptography_keepalive::KeepAlive::new(); let ka_bytes = cryptography_keepalive::KeepAlive::new(); let responses = vec![SingleResponse { cert_id: ocsp::certid_new(py, &ka_bytes, &py_cert, &py_issuer, &py_cert_hash_algorithm)?, cert_status, next_update, this_update, raw_single_extensions: None, }]; borrowed_cert = responder_cert.borrow(); let by_key_hash; let responder_id = if responder_encoding.is(&types::OCSP_RESPONDER_ENCODING_HASH.get(py)?) { let sha1 = types::SHA1.get(py)?.call0()?; by_key_hash = ocsp::hash_data( py, &sha1, borrowed_cert .raw .borrow_dependent() .tbs_cert .spki .subject_public_key .as_bytes(), )?; ocsp_resp::ResponderId::ByKey(by_key_hash.as_bytes()) } else { ocsp_resp::ResponderId::ByName( borrowed_cert .raw .borrow_dependent() .tbs_cert .subject .clone(), ) }; let tbs_response_data = ocsp_resp::ResponseData { version: 0, produced_at: asn1::GeneralizedTime::new(x509::common::datetime_now(py)?)?, responder_id, responses: common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( responses, )), raw_response_extensions: x509::common::encode_extensions( py, &ka_vec, &ka_bytes, &builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?, }; let sigalg = x509::sign::compute_signature_algorithm( py, private_key.clone(), hash_algorithm.clone(), py.None().into_bound(py), )?; let tbs_bytes = asn1::write_single(&tbs_response_data)?; let signature = x509::sign::sign_data( py, private_key.clone(), hash_algorithm.clone(), py.None().into_bound(py), &tbs_bytes, )?; if !responder_cert .call_method0(pyo3::intern!(py, "public_key"))? .eq(private_key.call_method0(pyo3::intern!(py, "public_key"))?)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Certificate public key and provided private key do not match", ), )); } py_certs = builder.getattr(pyo3::intern!(py, "_certs"))?.extract()?; let certs = py_certs.as_ref().map(|py_certs| { common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( py_certs .iter() .map(|c| c.raw.borrow_dependent().clone()) .collect(), )) }); let basic_resp = ocsp_resp::BasicOCSPResponse { tbs_response_data, signature: asn1::BitString::new(&signature, 0).unwrap(), signature_algorithm: sigalg, certs, }; let response_bytes = Some(ocsp_resp::ResponseBytes { response_type: (BASIC_RESPONSE_OID).clone(), response: asn1::OctetStringEncoded::new(basic_resp), }); let resp = ocsp_resp::OCSPResponse { response_status: asn1::Enumerated::new(SUCCESSFUL_RESPONSE), response_bytes, }; let data = asn1::write_single(&resp)?; load_der_ocsp_response(py, pyo3::types::PyBytes::new_bound(py, &data).unbind()) } type RawOCSPResponseIterator<'a> = asn1::SequenceOf<'a, SingleResponse<'a>>; self_cell::self_cell!( struct OwnedOCSPResponseIteratorData { owner: Arc, #[covariant] dependent: RawOCSPResponseIterator, } ); #[pyo3::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPResponseIterator { contents: OwnedOCSPResponseIteratorData, } #[pyo3::pymethods] impl OCSPResponseIterator { fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } fn __next__(&mut self) -> Option { let single_resp = try_map_arc_data_mut_ocsp_response_iterator(&mut self.contents, |_data, v| { match v.next() { Some(single_resp) => Ok(single_resp), None => Err(()), } }) .ok()?; Some(OCSPSingleResponse { raw: single_resp }) } } self_cell::self_cell!( struct OwnedSingleResponse { owner: Arc, #[covariant] dependent: RawSingleResponse, } ); #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.ocsp")] pub(crate) struct OCSPSingleResponse { raw: OwnedSingleResponse, } impl OCSPSingleResponse { fn single_response(&self) -> &SingleResponse<'_> { self.raw.borrow_dependent() } } #[pyo3::pymethods] impl OCSPSingleResponse { #[getter] fn serial_number<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { singleresp_py_serial_number(self.single_response(), py) } #[getter] fn issuer_key_hash(&self) -> &[u8] { let single_resp = self.single_response(); single_resp.cert_id.issuer_key_hash } #[getter] fn issuer_name_hash(&self) -> &[u8] { let single_resp = self.single_response(); single_resp.cert_id.issuer_name_hash } #[getter] fn hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> Result, CryptographyError> { let single_resp = self.single_response(); singleresp_py_hash_algorithm(single_resp, py) } #[getter] fn certificate_status<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let single_resp = self.single_response(); singleresp_py_certificate_status(single_resp, py) } #[getter] fn revocation_time<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_43.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to revocation_time_utc.", 1, )?; let single_resp = self.single_response(); singleresp_py_revocation_time(single_resp, py) } #[getter] fn revocation_time_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let single_resp = self.single_response(); singleresp_py_revocation_time_utc(single_resp, py) } #[getter] fn revocation_reason<'p>( &self, py: pyo3::Python<'p>, ) -> CryptographyResult> { let single_resp = self.single_response(); singleresp_py_revocation_reason(single_resp, py) } #[getter] fn this_update<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_43.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to this_update_utc.", 1, )?; let single_resp = self.single_response(); singleresp_py_this_update(single_resp, py) } #[getter] fn this_update_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let single_resp = self.single_response(); singleresp_py_this_update_utc(single_resp, py) } #[getter] fn next_update<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let warning_cls = types::DEPRECATED_IN_43.get(py)?; pyo3::PyErr::warn_bound( py, &warning_cls, "Properties that return a naïve datetime object have been deprecated. Please switch to next_update_utc.", 1, )?; let single_resp = self.single_response(); singleresp_py_next_update(single_resp, py) } #[getter] fn next_update_utc<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { let single_resp = self.single_response(); singleresp_py_next_update_utc(single_resp, py) } } cryptography-43.0.0/src/rust/src/x509/sct.rs010064400017510000177000000223171464676315000167550ustar 00000000000000// 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. use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use pyo3::types::{PyAnyMethods, PyDictMethods, PyListMethods}; use pyo3::ToPyObject; use crate::error::CryptographyError; use crate::types; struct TLSReader<'a> { data: &'a [u8], } impl<'a> TLSReader<'a> { fn new(data: &'a [u8]) -> TLSReader<'a> { TLSReader { data } } fn is_empty(&self) -> bool { self.data.is_empty() } fn read_byte(&mut self) -> Result { Ok(self.read_exact(1)?[0]) } fn read_exact(&mut self, length: usize) -> Result<&'a [u8], CryptographyError> { if length > self.data.len() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid SCT length"), )); } let (result, data) = self.data.split_at(length); self.data = data; Ok(result) } fn read_length_prefixed(&mut self) -> Result, CryptographyError> { let length = u16::from_be_bytes(self.read_exact(2)?.try_into().unwrap()); Ok(TLSReader::new(self.read_exact(length.into())?)) } } #[derive(Clone)] pub(crate) enum LogEntryType { Certificate, PreCertificate, } #[derive(Debug, PartialEq, Eq)] pub(crate) enum HashAlgorithm { Md5, Sha1, Sha224, Sha256, Sha384, Sha512, } impl TryFrom for HashAlgorithm { type Error = pyo3::PyErr; fn try_from(value: u8) -> Result { Ok(match value { 1 => HashAlgorithm::Md5, 2 => HashAlgorithm::Sha1, 3 => HashAlgorithm::Sha224, 4 => HashAlgorithm::Sha256, 5 => HashAlgorithm::Sha384, 6 => HashAlgorithm::Sha512, _ => { return Err(pyo3::exceptions::PyValueError::new_err(format!( "Invalid/unsupported hash algorithm for SCT: {value}" ))) } }) } } impl HashAlgorithm { fn to_attr(&self) -> &'static str { match self { HashAlgorithm::Md5 => "MD5", HashAlgorithm::Sha1 => "SHA1", HashAlgorithm::Sha224 => "SHA224", HashAlgorithm::Sha256 => "SHA256", HashAlgorithm::Sha384 => "SHA384", HashAlgorithm::Sha512 => "SHA512", } } } #[derive(Debug, PartialEq, Eq)] pub(crate) enum SignatureAlgorithm { Rsa, Dsa, Ecdsa, } impl SignatureAlgorithm { fn to_attr(&self) -> &'static str { match self { SignatureAlgorithm::Rsa => "RSA", SignatureAlgorithm::Dsa => "DSA", SignatureAlgorithm::Ecdsa => "ECDSA", } } } impl TryFrom for SignatureAlgorithm { type Error = pyo3::PyErr; fn try_from(value: u8) -> Result { Ok(match value { 1 => SignatureAlgorithm::Rsa, 2 => SignatureAlgorithm::Dsa, 3 => SignatureAlgorithm::Ecdsa, _ => { return Err(pyo3::exceptions::PyValueError::new_err(format!( "Invalid/unsupported signature algorithm for SCT: {value}" ))) } }) } } #[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Sct { log_id: [u8; 32], timestamp: u64, entry_type: LogEntryType, hash_algorithm: HashAlgorithm, signature_algorithm: SignatureAlgorithm, // TODO: These could be 'self references back into sct_data with ouroboros. signature: Vec, extension_bytes: Vec, pub(crate) sct_data: Vec, } #[pyo3::pymethods] impl Sct { fn __eq__(&self, other: pyo3::PyRef<'_, Sct>) -> bool { self.sct_data == other.sct_data } fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.sct_data.hash(&mut hasher); hasher.finish() } #[getter] fn version<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { types::CERTIFICATE_TRANSPARENCY_VERSION_V1.get(py) } #[getter] fn log_id(&self) -> &[u8] { &self.log_id } #[getter] fn timestamp<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; let kwargs = pyo3::types::PyDict::new_bound(py); kwargs.set_item("microsecond", self.timestamp % 1000 * 1000)?; kwargs.set_item("tzinfo", None::>)?; types::DATETIME_DATETIME .get(py)? .call_method1( pyo3::intern!(py, "fromtimestamp"), (self.timestamp / 1000, utc), )? .call_method("replace", (), Some(&kwargs)) } #[getter] fn entry_type<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { Ok(match self.entry_type { LogEntryType::Certificate => types::LOG_ENTRY_TYPE_X509_CERTIFICATE.get(py)?, LogEntryType::PreCertificate => types::LOG_ENTRY_TYPE_PRE_CERTIFICATE.get(py)?, }) } #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { types::HASHES_MODULE .get(py)? .call_method0(self.hash_algorithm.to_attr()) } #[getter] fn signature_algorithm<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { types::SIGNATURE_ALGORITHM .get(py)? .getattr(self.signature_algorithm.to_attr()) } #[getter] fn signature(&self) -> &[u8] { &self.signature } #[getter] fn extension_bytes(&self) -> &[u8] { &self.extension_bytes } } pub(crate) fn parse_scts( py: pyo3::Python<'_>, data: &[u8], entry_type: LogEntryType, ) -> Result { let mut reader = TLSReader::new(data).read_length_prefixed()?; let py_scts = pyo3::types::PyList::empty_bound(py); while !reader.is_empty() { let mut sct_data = reader.read_length_prefixed()?; let raw_sct_data = sct_data.data.to_vec(); let version = sct_data.read_byte()?; if version != 0 { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("Invalid SCT version"), )); } let log_id = sct_data.read_exact(32)?.try_into().unwrap(); let timestamp = u64::from_be_bytes(sct_data.read_exact(8)?.try_into().unwrap()); let extension_bytes = sct_data.read_length_prefixed()?.data.to_vec(); let hash_algorithm = sct_data.read_byte()?.try_into()?; let signature_algorithm = sct_data.read_byte()?.try_into()?; let signature = sct_data.read_length_prefixed()?.data.to_vec(); let sct = Sct { log_id, timestamp, entry_type: entry_type.clone(), hash_algorithm, signature_algorithm, signature, extension_bytes, sct_data: raw_sct_data, }; py_scts.append(pyo3::Bound::new(py, sct)?)?; } Ok(py_scts.to_object(py)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_hash_algorithm_try_from() { for (n, ha) in &[ (1_u8, HashAlgorithm::Md5), (2_u8, HashAlgorithm::Sha1), (3_u8, HashAlgorithm::Sha224), (4_u8, HashAlgorithm::Sha256), (5_u8, HashAlgorithm::Sha384), (6_u8, HashAlgorithm::Sha512), ] { let res = HashAlgorithm::try_from(*n).unwrap(); assert_eq!(&res, ha); } // We don't support "none" hash algorithms. assert!(HashAlgorithm::try_from(0).is_err()); assert!(HashAlgorithm::try_from(7).is_err()); } #[test] fn test_hash_algorithm_to_attr() { for (ha, attr) in &[ (HashAlgorithm::Md5, "MD5"), (HashAlgorithm::Sha1, "SHA1"), (HashAlgorithm::Sha224, "SHA224"), (HashAlgorithm::Sha256, "SHA256"), (HashAlgorithm::Sha384, "SHA384"), (HashAlgorithm::Sha512, "SHA512"), ] { assert_eq!(ha.to_attr(), *attr); } } #[test] fn test_signature_algorithm_try_from() { for (n, ha) in &[ (1_u8, SignatureAlgorithm::Rsa), (2_u8, SignatureAlgorithm::Dsa), (3_u8, SignatureAlgorithm::Ecdsa), ] { let res = SignatureAlgorithm::try_from(*n).unwrap(); assert_eq!(&res, ha); } // We don't support "anonymous" signature algorithms. assert!(SignatureAlgorithm::try_from(0).is_err()); assert!(SignatureAlgorithm::try_from(4).is_err()); } #[test] fn test_signature_algorithm_to_attr() { for (sa, attr) in &[ (SignatureAlgorithm::Rsa, "RSA"), (SignatureAlgorithm::Dsa, "DSA"), (SignatureAlgorithm::Ecdsa, "ECDSA"), ] { assert_eq!(sa.to_attr(), *attr); } } } cryptography-43.0.0/src/rust/src/x509/sign.rs010064400017510000177000000631321464676315000171240ustar 00000000000000// 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. use std::collections::HashMap; use cryptography_x509::{common, oid}; use once_cell::sync::Lazy; use pyo3::pybacked::PyBackedBytes; use pyo3::types::PyAnyMethods; use crate::asn1::oid_to_py_oid; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; // This is similar to a hashmap in ocsp.rs but contains more hash algorithms // that aren't allowable in OCSP static HASH_OIDS_TO_HASH: Lazy> = Lazy::new(|| { let mut h = HashMap::new(); h.insert(&oid::SHA1_OID, "SHA1"); h.insert(&oid::SHA224_OID, "SHA224"); h.insert(&oid::SHA256_OID, "SHA256"); h.insert(&oid::SHA384_OID, "SHA384"); h.insert(&oid::SHA512_OID, "SHA512"); h.insert(&oid::SHA3_224_OID, "SHA3_224"); h.insert(&oid::SHA3_256_OID, "SHA3_256"); h.insert(&oid::SHA3_384_OID, "SHA3_384"); h.insert(&oid::SHA3_512_OID, "SHA3_512"); h }); #[derive(Debug, PartialEq)] pub(crate) enum KeyType { Rsa, Dsa, Ec, Ed25519, Ed448, } enum HashType { None, Sha224, Sha256, Sha384, Sha512, Sha3_224, Sha3_256, Sha3_384, Sha3_512, } pub(crate) fn identify_key_type( py: pyo3::Python<'_>, private_key: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { if private_key.is_instance(&types::RSA_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Rsa) } else if private_key.is_instance(&types::DSA_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Dsa) } else if private_key.is_instance(&types::ELLIPTIC_CURVE_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ec) } else if private_key.is_instance(&types::ED25519_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ed25519) } else if private_key.is_instance(&types::ED448_PRIVATE_KEY.get(py)?)? { Ok(KeyType::Ed448) } else { Err(pyo3::exceptions::PyTypeError::new_err( "Key must be an rsa, dsa, ec, ed25519, or ed448 private key.", )) } } fn identify_hash_type( py: pyo3::Python<'_>, hash_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { if hash_algorithm.is_none() { return Ok(HashType::None); } if !hash_algorithm.is_instance(&types::HASH_ALGORITHM.get(py)?)? { return Err(pyo3::exceptions::PyTypeError::new_err( "Algorithm must be a registered hash algorithm.", )); } match &*hash_algorithm .getattr(pyo3::intern!(py, "name"))? .extract::()? { "sha224" => Ok(HashType::Sha224), "sha256" => Ok(HashType::Sha256), "sha384" => Ok(HashType::Sha384), "sha512" => Ok(HashType::Sha512), "sha3-224" => Ok(HashType::Sha3_224), "sha3-256" => Ok(HashType::Sha3_256), "sha3-384" => Ok(HashType::Sha3_384), "sha3-512" => Ok(HashType::Sha3_512), name => Err(exceptions::UnsupportedAlgorithm::new_err(format!( "Hash algorithm {name:?} not supported for signatures" ))), } } fn compute_pss_salt_length<'p>( py: pyo3::Python<'p>, private_key: pyo3::Bound<'p, pyo3::PyAny>, hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, ) -> pyo3::PyResult { let py_saltlen = rsa_padding.getattr(pyo3::intern!(py, "_salt_length"))?; if py_saltlen.is_instance(&types::PADDING_MAX_LENGTH.get(py)?)? { types::CALCULATE_MAX_PSS_SALT_LENGTH .get(py)? .call1((private_key, hash_algorithm))? .extract::() } else if py_saltlen.is_instance(&types::PADDING_DIGEST_LENGTH.get(py)?)? { hash_algorithm .getattr(pyo3::intern!(py, "digest_size"))? .extract::() } else if py_saltlen.is_instance_of::() { py_saltlen.extract::() } else { Err(pyo3::exceptions::PyTypeError::new_err( "salt_length must be an int, MaxLength, or DigestLength.", )) } } pub(crate) fn compute_signature_algorithm<'p>( py: pyo3::Python<'p>, private_key: pyo3::Bound<'p, pyo3::PyAny>, hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, ) -> pyo3::PyResult> { let key_type = identify_key_type(py, private_key.clone())?; let hash_type = identify_hash_type(py, hash_algorithm.clone())?; // If this is RSA-PSS we need to compute the signature algorithm from the // parameters provided in rsa_padding. if rsa_padding.is_instance(&types::PSS.get(py)?)? { let hash_alg_params = identify_alg_params_for_hash_type(hash_type)?; let hash_algorithm_id = common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: hash_alg_params, }; let salt_length = compute_pss_salt_length(py, private_key, hash_algorithm, rsa_padding.clone())?; let py_mgf_alg = rsa_padding .getattr(pyo3::intern!(py, "_mgf"))? .getattr(pyo3::intern!(py, "_algorithm"))?; let mgf_hash_type = identify_hash_type(py, py_mgf_alg)?; let mgf_alg = common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: identify_alg_params_for_hash_type(mgf_hash_type)?, }; let params = common::AlgorithmParameters::RsaPss(Some(Box::new(common::RsaPssParameters { hash_algorithm: hash_algorithm_id, mask_gen_algorithm: common::MaskGenAlgorithm { oid: oid::MGF1_OID, params: mgf_alg, }, salt_length, _trailer_field: None, }))); return Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params, }); } // It's not an RSA PSS signature, so we compute the signature algorithm from // the union of key type and hash type. match (key_type, hash_type) { (KeyType::Ed25519, HashType::None) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Ed25519, }), (KeyType::Ed448, HashType::None) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::Ed448, }), (KeyType::Ed25519 | KeyType::Ed448, _) => Err(pyo3::exceptions::PyValueError::new_err( "Algorithm must be None when signing via ed25519 or ed448", )), (KeyType::Ec, HashType::Sha224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha224(None), }), (KeyType::Ec, HashType::Sha256) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha256(None), }), (KeyType::Ec, HashType::Sha384) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha384(None), }), (KeyType::Ec, HashType::Sha512) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha512(None), }), (KeyType::Ec, HashType::Sha3_224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha3_224, }), (KeyType::Ec, HashType::Sha3_256) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha3_256, }), (KeyType::Ec, HashType::Sha3_384) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha3_384, }), (KeyType::Ec, HashType::Sha3_512) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::EcDsaWithSha3_512, }), (KeyType::Rsa, HashType::Sha224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha224(Some(())), }), (KeyType::Rsa, HashType::Sha256) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha256(Some(())), }), (KeyType::Rsa, HashType::Sha384) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha384(Some(())), }), (KeyType::Rsa, HashType::Sha512) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha512(Some(())), }), (KeyType::Rsa, HashType::Sha3_224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha3_224(Some(())), }), (KeyType::Rsa, HashType::Sha3_256) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha3_256(Some(())), }), (KeyType::Rsa, HashType::Sha3_384) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha3_384(Some(())), }), (KeyType::Rsa, HashType::Sha3_512) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::RsaWithSha3_512(Some(())), }), (KeyType::Dsa, HashType::Sha224) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::DsaWithSha224(None), }), (KeyType::Dsa, HashType::Sha256) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::DsaWithSha256(None), }), (KeyType::Dsa, HashType::Sha384) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::DsaWithSha384(None), }), (KeyType::Dsa, HashType::Sha512) => Ok(common::AlgorithmIdentifier { oid: asn1::DefinedByMarker::marker(), params: common::AlgorithmParameters::DsaWithSha512(None), }), ( KeyType::Dsa, HashType::Sha3_224 | HashType::Sha3_256 | HashType::Sha3_384 | HashType::Sha3_512, ) => Err(exceptions::UnsupportedAlgorithm::new_err( "SHA3 hashes are not supported with DSA keys", )), (_, HashType::None) => Err(pyo3::exceptions::PyTypeError::new_err( "Algorithm must be a registered hash algorithm, not None.", )), } } pub(crate) fn sign_data<'p>( py: pyo3::Python<'p>, private_key: pyo3::Bound<'p, pyo3::PyAny>, hash_algorithm: pyo3::Bound<'p, pyo3::PyAny>, rsa_padding: pyo3::Bound<'p, pyo3::PyAny>, data: &[u8], ) -> pyo3::PyResult { let key_type = identify_key_type(py, private_key.clone())?; let signature = match key_type { KeyType::Ed25519 | KeyType::Ed448 => { private_key.call_method1(pyo3::intern!(py, "sign"), (data,))? } KeyType::Ec => { let ecdsa = types::ECDSA.get(py)?.call1((hash_algorithm,))?; private_key.call_method1(pyo3::intern!(py, "sign"), (data, ecdsa))? } KeyType::Rsa => { let mut padding = rsa_padding; if padding.is_none() { padding = types::PKCS1V15.get(py)?.call0()?; } private_key.call_method1(pyo3::intern!(py, "sign"), (data, padding, hash_algorithm))? } KeyType::Dsa => { private_key.call_method1(pyo3::intern!(py, "sign"), (data, hash_algorithm))? } }; signature.extract() } pub(crate) fn verify_signature_with_signature_algorithm<'p>( py: pyo3::Python<'p>, issuer_public_key: pyo3::Bound<'p, pyo3::PyAny>, signature_algorithm: &common::AlgorithmIdentifier<'_>, signature: &[u8], data: &[u8], ) -> CryptographyResult<()> { let key_type = identify_public_key_type(py, issuer_public_key.clone())?; let sig_key_type = identify_key_type_for_algorithm_params(&signature_algorithm.params)?; if key_type != sig_key_type { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Signature algorithm does not match issuer key type", ), )); } let py_signature_algorithm_parameters = identify_signature_algorithm_parameters(py, signature_algorithm)?; let py_signature_hash_algorithm = identify_signature_hash_algorithm(py, signature_algorithm)?; match key_type { KeyType::Ed25519 | KeyType::Ed448 => { issuer_public_key.call_method1(pyo3::intern!(py, "verify"), (signature, data))? } KeyType::Ec => issuer_public_key.call_method1( pyo3::intern!(py, "verify"), (signature, data, py_signature_algorithm_parameters), )?, KeyType::Rsa => issuer_public_key.call_method1( pyo3::intern!(py, "verify"), ( signature, data, py_signature_algorithm_parameters, py_signature_hash_algorithm, ), )?, KeyType::Dsa => issuer_public_key.call_method1( pyo3::intern!(py, "verify"), (signature, data, py_signature_hash_algorithm), )?, }; Ok(()) } pub(crate) fn identify_public_key_type( py: pyo3::Python<'_>, public_key: pyo3::Bound<'_, pyo3::PyAny>, ) -> pyo3::PyResult { if public_key.is_instance(&types::RSA_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Rsa) } else if public_key.is_instance(&types::DSA_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Dsa) } else if public_key.is_instance(&types::ELLIPTIC_CURVE_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ec) } else if public_key.is_instance(&types::ED25519_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ed25519) } else if public_key.is_instance(&types::ED448_PUBLIC_KEY.get(py)?)? { Ok(KeyType::Ed448) } else { Err(pyo3::exceptions::PyTypeError::new_err( "Key must be an rsa, dsa, ec, ed25519, or ed448 public key.", )) } } fn identify_key_type_for_algorithm_params( params: &common::AlgorithmParameters<'_>, ) -> pyo3::PyResult { match params { common::AlgorithmParameters::RsaWithSha224(..) | common::AlgorithmParameters::RsaWithSha256(..) | common::AlgorithmParameters::RsaWithSha384(..) | common::AlgorithmParameters::RsaWithSha512(..) | common::AlgorithmParameters::RsaWithSha3_224(..) | common::AlgorithmParameters::RsaWithSha3_256(..) | common::AlgorithmParameters::RsaWithSha3_384(..) | common::AlgorithmParameters::RsaWithSha3_512(..) | common::AlgorithmParameters::RsaPss(..) => Ok(KeyType::Rsa), common::AlgorithmParameters::EcDsaWithSha224(..) | common::AlgorithmParameters::EcDsaWithSha256(..) | common::AlgorithmParameters::EcDsaWithSha384(..) | common::AlgorithmParameters::EcDsaWithSha512(..) | common::AlgorithmParameters::EcDsaWithSha3_224 | common::AlgorithmParameters::EcDsaWithSha3_256 | common::AlgorithmParameters::EcDsaWithSha3_384 | common::AlgorithmParameters::EcDsaWithSha3_512 => Ok(KeyType::Ec), common::AlgorithmParameters::Ed25519 => Ok(KeyType::Ed25519), common::AlgorithmParameters::Ed448 => Ok(KeyType::Ed448), common::AlgorithmParameters::DsaWithSha224(..) | common::AlgorithmParameters::DsaWithSha256(..) | common::AlgorithmParameters::DsaWithSha384(..) | common::AlgorithmParameters::DsaWithSha512(..) => Ok(KeyType::Dsa), _ => Err(pyo3::exceptions::PyValueError::new_err( "Unsupported signature algorithm", )), } } fn identify_alg_params_for_hash_type( hash_type: HashType, ) -> pyo3::PyResult> { match hash_type { HashType::Sha224 => Ok(common::AlgorithmParameters::Sha224(Some(()))), HashType::Sha256 => Ok(common::AlgorithmParameters::Sha256(Some(()))), HashType::Sha384 => Ok(common::AlgorithmParameters::Sha384(Some(()))), HashType::Sha512 => Ok(common::AlgorithmParameters::Sha512(Some(()))), HashType::Sha3_224 => Ok(common::AlgorithmParameters::Sha3_224(Some(()))), HashType::Sha3_256 => Ok(common::AlgorithmParameters::Sha3_256(Some(()))), HashType::Sha3_384 => Ok(common::AlgorithmParameters::Sha3_384(Some(()))), HashType::Sha3_512 => Ok(common::AlgorithmParameters::Sha3_512(Some(()))), HashType::None => Err(pyo3::exceptions::PyTypeError::new_err( "Algorithm must be a registered hash algorithm, not None.", )), } } fn hash_oid_py_hash( py: pyo3::Python<'_>, oid: asn1::ObjectIdentifier, ) -> CryptographyResult> { match HASH_OIDS_TO_HASH.get(&oid) { Some(alg_name) => Ok(types::HASHES_MODULE.get(py)?.getattr(*alg_name)?.call0()?), None => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", &oid )), )), } } pub(crate) fn identify_signature_hash_algorithm<'p>( py: pyo3::Python<'p>, signature_algorithm: &common::AlgorithmIdentifier<'_>, ) -> CryptographyResult> { let sig_oids_to_hash = types::SIG_OIDS_TO_HASH.get(py)?; match &signature_algorithm.params { common::AlgorithmParameters::RsaPss(opt_pss) => { let pss = opt_pss.as_ref().ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Invalid RSA PSS parameters") })?; hash_oid_py_hash(py, pss.hash_algorithm.oid().clone()) } _ => { let py_sig_alg_oid = oid_to_py_oid(py, signature_algorithm.oid())?; let hash_alg = sig_oids_to_hash.get_item(py_sig_alg_oid); match hash_alg { Ok(data) => Ok(data), Err(_) => Err(CryptographyError::from( exceptions::UnsupportedAlgorithm::new_err(format!( "Signature algorithm OID: {} not recognized", signature_algorithm.oid() )), )), } } } } pub(crate) fn identify_signature_algorithm_parameters<'p>( py: pyo3::Python<'p>, signature_algorithm: &common::AlgorithmIdentifier<'_>, ) -> CryptographyResult> { match &signature_algorithm.params { common::AlgorithmParameters::RsaPss(opt_pss) => { let pss = opt_pss.as_ref().ok_or_else(|| { pyo3::exceptions::PyValueError::new_err("Invalid RSA PSS parameters") })?; if pss.mask_gen_algorithm.oid != oid::MGF1_OID { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err(format!( "Unsupported mask generation OID: {}", pss.mask_gen_algorithm.oid )), )); } let py_mask_gen_hash_alg = hash_oid_py_hash(py, pss.mask_gen_algorithm.params.oid().clone())?; let py_mgf = types::MGF1.get(py)?.call1((py_mask_gen_hash_alg,))?; Ok(types::PSS.get(py)?.call1((py_mgf, pss.salt_length))?) } common::AlgorithmParameters::RsaWithSha1(_) | common::AlgorithmParameters::RsaWithSha1Alt(_) | common::AlgorithmParameters::RsaWithSha224(_) | common::AlgorithmParameters::RsaWithSha256(_) | common::AlgorithmParameters::RsaWithSha384(_) | common::AlgorithmParameters::RsaWithSha512(_) | common::AlgorithmParameters::RsaWithSha3_224(_) | common::AlgorithmParameters::RsaWithSha3_256(_) | common::AlgorithmParameters::RsaWithSha3_384(_) | common::AlgorithmParameters::RsaWithSha3_512(_) => { Ok(types::PKCS1V15.get(py)?.call0()?) } common::AlgorithmParameters::EcDsaWithSha224(_) | common::AlgorithmParameters::EcDsaWithSha256(_) | common::AlgorithmParameters::EcDsaWithSha384(_) | common::AlgorithmParameters::EcDsaWithSha512(_) | common::AlgorithmParameters::EcDsaWithSha3_224 | common::AlgorithmParameters::EcDsaWithSha3_256 | common::AlgorithmParameters::EcDsaWithSha3_384 | common::AlgorithmParameters::EcDsaWithSha3_512 => { let signature_hash_algorithm = identify_signature_hash_algorithm(py, signature_algorithm)?; Ok(types::ECDSA.get(py)?.call1((signature_hash_algorithm,))?) } _ => Ok(py.None().into_bound(py)), } } #[cfg(test)] mod tests { use cryptography_x509::{common, oid}; use super::{ identify_alg_params_for_hash_type, identify_key_type_for_algorithm_params, HashType, KeyType, }; #[test] fn test_identify_key_type_for_algorithm_params() { for (params, keytype) in [ ( &common::AlgorithmParameters::RsaWithSha224(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::RsaWithSha256(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::RsaWithSha384(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::RsaWithSha512(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::RsaWithSha3_224(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::RsaWithSha3_256(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::RsaWithSha3_384(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::RsaWithSha3_512(Some(())), KeyType::Rsa, ), ( &common::AlgorithmParameters::EcDsaWithSha224(None), KeyType::Ec, ), ( &common::AlgorithmParameters::EcDsaWithSha256(None), KeyType::Ec, ), ( &common::AlgorithmParameters::EcDsaWithSha384(None), KeyType::Ec, ), ( &common::AlgorithmParameters::EcDsaWithSha512(None), KeyType::Ec, ), (&common::AlgorithmParameters::EcDsaWithSha3_224, KeyType::Ec), (&common::AlgorithmParameters::EcDsaWithSha3_256, KeyType::Ec), (&common::AlgorithmParameters::EcDsaWithSha3_384, KeyType::Ec), (&common::AlgorithmParameters::EcDsaWithSha3_512, KeyType::Ec), (&common::AlgorithmParameters::Ed25519, KeyType::Ed25519), (&common::AlgorithmParameters::Ed448, KeyType::Ed448), ( &common::AlgorithmParameters::DsaWithSha224(None), KeyType::Dsa, ), ( &common::AlgorithmParameters::DsaWithSha256(None), KeyType::Dsa, ), ( &common::AlgorithmParameters::DsaWithSha384(None), KeyType::Dsa, ), ( &common::AlgorithmParameters::DsaWithSha512(None), KeyType::Dsa, ), ] { assert_eq!( identify_key_type_for_algorithm_params(params).unwrap(), keytype ); } assert!( identify_key_type_for_algorithm_params(&common::AlgorithmParameters::Other( oid::TLS_FEATURE_OID, None )) .is_err() ); } #[test] fn test_identify_alg_params_for_hash_type() { for (hash, params) in [ ( HashType::Sha224, common::AlgorithmParameters::Sha224(Some(())), ), ( HashType::Sha256, common::AlgorithmParameters::Sha256(Some(())), ), ( HashType::Sha384, common::AlgorithmParameters::Sha384(Some(())), ), ( HashType::Sha512, common::AlgorithmParameters::Sha512(Some(())), ), ( HashType::Sha3_224, common::AlgorithmParameters::Sha3_224(Some(())), ), ( HashType::Sha3_256, common::AlgorithmParameters::Sha3_256(Some(())), ), ( HashType::Sha3_384, common::AlgorithmParameters::Sha3_384(Some(())), ), ( HashType::Sha3_512, common::AlgorithmParameters::Sha3_512(Some(())), ), ] { assert_eq!(identify_alg_params_for_hash_type(hash).unwrap(), params); } } } cryptography-43.0.0/src/rust/src/x509/verify.rs010064400017510000177000000334011464676315000174640ustar 00000000000000// 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. use cryptography_x509::{ certificate::Certificate, extensions::SubjectAlternativeName, oid::SUBJECT_ALTERNATIVE_NAME_OID, }; use cryptography_x509_verification::{ ops::{CryptoOps, VerificationCertificate}, policy::{Policy, Subject}, trust_store::Store, types::{DNSName, IPAddress}, }; use pyo3::types::{PyAnyMethods, PyListMethods}; use crate::backend::keys; use crate::error::{CryptographyError, CryptographyResult}; use crate::types; use crate::x509::certificate::Certificate as PyCertificate; use crate::x509::common::{datetime_now, datetime_to_py, py_to_datetime}; use crate::x509::sign; use super::parse_general_names; pub(crate) struct PyCryptoOps {} impl CryptoOps for PyCryptoOps { type Key = pyo3::Py; type Err = CryptographyError; type CertificateExtra = pyo3::Py; fn public_key(&self, cert: &Certificate<'_>) -> Result { pyo3::Python::with_gil(|py| -> Result { keys::load_der_public_key_bytes(py, cert.tbs_cert.spki.tlv().full_data()) }) } fn verify_signed_by(&self, cert: &Certificate<'_>, key: &Self::Key) -> Result<(), Self::Err> { pyo3::Python::with_gil(|py| -> CryptographyResult<()> { sign::verify_signature_with_signature_algorithm( py, key.bind(py).clone(), &cert.signature_alg, cert.signature.as_bytes(), &asn1::write_single(&cert.tbs_cert)?, ) }) } } pyo3::create_exception!( cryptography.hazmat.bindings._rust.x509, VerificationError, pyo3::exceptions::PyException ); #[pyo3::pyclass(frozen, module = "cryptography.x509.verification")] pub(crate) struct PolicyBuilder { time: Option, store: Option>, max_chain_depth: Option, } #[pyo3::pymethods] impl PolicyBuilder { #[new] fn new() -> PolicyBuilder { PolicyBuilder { time: None, store: None, max_chain_depth: None, } } fn time( &self, py: pyo3::Python<'_>, new_time: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult { if self.time.is_some() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "The validation time may only be set once.", ), )); } Ok(PolicyBuilder { time: Some(py_to_datetime(py, new_time)?), store: self.store.as_ref().map(|s| s.clone_ref(py)), max_chain_depth: self.max_chain_depth, }) } fn store(&self, new_store: pyo3::Py) -> CryptographyResult { if self.store.is_some() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err("The trust store may only be set once."), )); } Ok(PolicyBuilder { time: self.time.clone(), store: Some(new_store), max_chain_depth: self.max_chain_depth, }) } fn max_chain_depth( &self, py: pyo3::Python<'_>, new_max_chain_depth: u8, ) -> CryptographyResult { if self.max_chain_depth.is_some() { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "The maximum chain depth may only be set once.", ), )); } Ok(PolicyBuilder { time: self.time.clone(), store: self.store.as_ref().map(|s| s.clone_ref(py)), max_chain_depth: Some(new_max_chain_depth), }) } fn build_client_verifier(&self, py: pyo3::Python<'_>) -> CryptographyResult { let store = match self.store.as_ref() { Some(s) => s.clone_ref(py), None => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "A client verifier must have a trust store.", ), )); } }; let time = match self.time.as_ref() { Some(t) => t.clone(), None => datetime_now(py)?, }; let policy = PyCryptoPolicy(Policy::client(PyCryptoOps {}, time, self.max_chain_depth)); Ok(PyClientVerifier { policy, store }) } fn build_server_verifier( &self, py: pyo3::Python<'_>, subject: pyo3::PyObject, ) -> CryptographyResult { let store = match self.store.as_ref() { Some(s) => s.clone_ref(py), None => { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "A server verifier must have a trust store.", ), )); } }; let time = match self.time.as_ref() { Some(t) => t.clone(), None => datetime_now(py)?, }; let subject_owner = build_subject_owner(py, &subject)?; let policy = OwnedPolicy::try_new(subject_owner, |subject_owner| { let subject = build_subject(py, subject_owner)?; Ok::, pyo3::PyErr>(PyCryptoPolicy(Policy::server( PyCryptoOps {}, subject, time, self.max_chain_depth, ))) })?; Ok(PyServerVerifier { py_subject: subject, policy, store, }) } } struct PyCryptoPolicy<'a>(Policy<'a, PyCryptoOps>); /// This enum exists solely to provide heterogeneously typed ownership for `OwnedPolicy`. enum SubjectOwner { // TODO: Switch this to `Py` once Pyo3's `to_str()` preserves a // lifetime relationship between an a `PyString` and its borrowed `&str` // reference in all limited API builds. PyO3 can't currently do that in // older limited API builds because it needs `PyUnicode_AsUTF8AndSize` to do // so, which was only stabilized with 3.10. DNSName(String), IPAddress(pyo3::Py), } self_cell::self_cell!( struct OwnedPolicy { owner: SubjectOwner, #[covariant] dependent: PyCryptoPolicy, } ); #[pyo3::pyclass( frozen, name = "VerifiedClient", module = "cryptography.hazmat.bindings._rust.x509" )] pub(crate) struct PyVerifiedClient { #[pyo3(get)] subjects: pyo3::Py, #[pyo3(get)] chain: pyo3::Py, } #[pyo3::pyclass( frozen, name = "ClientVerifier", module = "cryptography.hazmat.bindings._rust.x509" )] pub(crate) struct PyClientVerifier { policy: PyCryptoPolicy<'static>, #[pyo3(get)] store: pyo3::Py, } impl PyClientVerifier { fn as_policy(&self) -> &Policy<'_, PyCryptoOps> { &self.policy.0 } } #[pyo3::pymethods] impl PyClientVerifier { #[getter] fn validation_time<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { datetime_to_py(py, &self.as_policy().validation_time) } #[getter] fn max_chain_depth(&self) -> u8 { self.as_policy().max_chain_depth } fn verify( &self, py: pyo3::Python<'_>, leaf: pyo3::Py, intermediates: Vec>, ) -> CryptographyResult { let policy = self.as_policy(); let store = self.store.get(); let intermediates = intermediates .iter() .map(|i| { VerificationCertificate::new( i.get().raw.borrow_dependent().clone(), i.clone_ref(py), ) }) .collect::>(); let intermediate_refs = intermediates.iter().collect::>(); let v = VerificationCertificate::new( leaf.get().raw.borrow_dependent().clone(), leaf.clone_ref(py), ); let chain = cryptography_x509_verification::verify( &v, &intermediate_refs, policy, store.raw.borrow_dependent(), ) .map_err(|e| VerificationError::new_err(format!("validation failed: {e}")))?; let py_chain = pyo3::types::PyList::empty_bound(py); for c in &chain { py_chain.append(c.extra())?; } // NOTE: These `unwrap()`s cannot fail, since the underlying policy // enforces the presence of a SAN and the well-formedness of the // extension set. let leaf_san = &chain[0] .certificate() .extensions() .ok() .unwrap() .get_extension(&SUBJECT_ALTERNATIVE_NAME_OID) .unwrap(); let leaf_gns = leaf_san.value::>()?; let py_gns = parse_general_names(py, &leaf_gns)?; Ok(PyVerifiedClient { subjects: py_gns, chain: py_chain.unbind(), }) } } #[pyo3::pyclass( frozen, name = "ServerVerifier", module = "cryptography.hazmat.bindings._rust.x509" )] pub(crate) struct PyServerVerifier { #[pyo3(get, name = "subject")] py_subject: pyo3::Py, policy: OwnedPolicy, #[pyo3(get)] store: pyo3::Py, } impl PyServerVerifier { fn as_policy(&self) -> &Policy<'_, PyCryptoOps> { &self.policy.borrow_dependent().0 } } #[pyo3::pymethods] impl PyServerVerifier { #[getter] fn validation_time<'p>( &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult> { datetime_to_py(py, &self.as_policy().validation_time) } #[getter] fn max_chain_depth(&self) -> u8 { self.as_policy().max_chain_depth } fn verify<'p>( &self, py: pyo3::Python<'p>, leaf: pyo3::Py, intermediates: Vec>, ) -> CryptographyResult> { let policy = self.as_policy(); let store = self.store.get(); let intermediates = intermediates .iter() .map(|i| { VerificationCertificate::new( i.get().raw.borrow_dependent().clone(), i.clone_ref(py), ) }) .collect::>(); let intermediate_refs = intermediates.iter().collect::>(); let v = VerificationCertificate::new( leaf.get().raw.borrow_dependent().clone(), leaf.clone_ref(py), ); let chain = cryptography_x509_verification::verify( &v, &intermediate_refs, policy, store.raw.borrow_dependent(), ) .map_err(|e| VerificationError::new_err(format!("validation failed: {e:?}")))?; let result = pyo3::types::PyList::empty_bound(py); for c in chain { result.append(c.extra())?; } Ok(result) } } fn build_subject_owner( py: pyo3::Python<'_>, subject: &pyo3::Py, ) -> pyo3::PyResult { let subject = subject.bind(py); if subject.is_instance(&types::DNS_NAME.get(py)?)? { let value = subject .getattr(pyo3::intern!(py, "value"))? // TODO: switch this to borrowing the string (using Bound::to_str) once our // minimum Python version is 3.10 .extract::()?; Ok(SubjectOwner::DNSName(value)) } else if subject.is_instance(&types::IP_ADDRESS.get(py)?)? { let value = subject .getattr(pyo3::intern!(py, "_packed"))? .call0()? .downcast::()? .clone(); Ok(SubjectOwner::IPAddress(value.unbind())) } else { Err(pyo3::exceptions::PyTypeError::new_err( "unsupported subject type", )) } } fn build_subject<'a>( py: pyo3::Python<'_>, subject: &'a SubjectOwner, ) -> pyo3::PyResult> { match subject { SubjectOwner::DNSName(dns_name) => { let dns_name = DNSName::new(dns_name) .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("invalid domain name"))?; Ok(Subject::DNS(dns_name)) } SubjectOwner::IPAddress(ip_addr) => { let ip_addr = IPAddress::from_bytes(ip_addr.as_bytes(py)) .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("invalid IP address"))?; Ok(Subject::IP(ip_addr)) } } } type PyCryptoOpsStore<'a> = Store<'a, PyCryptoOps>; self_cell::self_cell!( struct RawPyStore { owner: Vec>, #[covariant] dependent: PyCryptoOpsStore, } ); #[pyo3::pyclass( frozen, name = "Store", module = "cryptography.hazmat.bindings._rust.x509" )] pub(crate) struct PyStore { raw: RawPyStore, } #[pyo3::pymethods] impl PyStore { #[new] fn new(py: pyo3::Python<'_>, certs: Vec>) -> pyo3::PyResult { if certs.is_empty() { return Err(pyo3::exceptions::PyValueError::new_err( "can't create an empty store", )); } Ok(Self { raw: RawPyStore::new(certs, |v| { Store::new(v.iter().map(|t| { VerificationCertificate::new( t.get().raw.borrow_dependent().clone(), t.clone_ref(py), ) })) }), }) } } cryptography-43.0.0/tests/__init__.py010064400017510000177000000002641464676315000160060ustar 00000000000000# 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. cryptography-43.0.0/tests/bench/__init__.py010064400017510000177000000002641464676315000170650ustar 00000000000000# 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. cryptography-43.0.0/tests/bench/test_aead.py010064400017510000177000000060131464676315000172550ustar 00000000000000# 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. import pytest from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, AESOCB3, AESSIV, ChaCha20Poly1305, ) def _aead_supported(cls): try: cls(b"0" * 32) return True except UnsupportedAlgorithm: return False @pytest.mark.skipif( not _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL with ChaCha20Poly1305 support", ) def test_chacha20poly1305_encrypt(benchmark): chacha = ChaCha20Poly1305(b"\x00" * 32) benchmark(chacha.encrypt, b"\x00" * 12, b"hello world plaintext", b"") @pytest.mark.skipif( not _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL with ChaCha20Poly1305 support", ) def test_chacha20poly1305_decrypt(benchmark): chacha = ChaCha20Poly1305(b"\x00" * 32) ct = chacha.encrypt(b"\x00" * 12, b"hello world plaintext", b"") benchmark(chacha.decrypt, b"\x00" * 12, ct, b"") def test_aesgcm_encrypt(benchmark): aes = AESGCM(b"\x00" * 32) benchmark(aes.encrypt, b"\x00" * 12, b"hello world plaintext", None) def test_aesgcm_decrypt(benchmark): aes = AESGCM(b"\x00" * 32) ct = aes.encrypt(b"\x00" * 12, b"hello world plaintext", None) benchmark(aes.decrypt, b"\x00" * 12, ct, None) @pytest.mark.skipif( not _aead_supported(AESSIV), reason="Requires OpenSSL with AES-SIV support", ) def test_aessiv_encrypt(benchmark): aes = AESSIV(b"\x00" * 32) benchmark(aes.encrypt, b"hello world plaintext", None) @pytest.mark.skipif( not _aead_supported(AESSIV), reason="Requires OpenSSL with AES-SIV support", ) def test_aessiv_decrypt(benchmark): aes = AESSIV(b"\x00" * 32) ct = aes.encrypt(b"hello world plaintext", None) benchmark(aes.decrypt, ct, None) @pytest.mark.skipif( not _aead_supported(AESOCB3), reason="Requires OpenSSL with AES-OCB3 support", ) def test_aesocb3_encrypt(benchmark): aes = AESOCB3(b"\x00" * 32) benchmark(aes.encrypt, b"\x00" * 12, b"hello world plaintext", None) @pytest.mark.skipif( not _aead_supported(AESOCB3), reason="Requires OpenSSL with AES-OCB3 support", ) def test_aesocb3_decrypt(benchmark): aes = AESOCB3(b"\x00" * 32) ct = aes.encrypt(b"\x00" * 12, b"hello world plaintext", None) benchmark(aes.decrypt, b"\x00" * 12, ct, None) @pytest.mark.skipif( not _aead_supported(AESCCM), reason="Requires OpenSSL with AES-CCM support", ) def test_aesccm_encrypt(benchmark): aes = AESCCM(b"\x00" * 32) benchmark(aes.encrypt, b"\x00" * 12, b"hello world plaintext", None) @pytest.mark.skipif( not _aead_supported(AESCCM), reason="Requires OpenSSL with AES-CCM support", ) def test_aesccm_decrypt(benchmark): aes = AESCCM(b"\x00" * 32) ct = aes.encrypt(b"\x00" * 12, b"hello world plaintext", None) benchmark(aes.decrypt, b"\x00" * 12, ct, None) cryptography-43.0.0/tests/bench/test_ec_load.py010064400017510000177000000006651464676315000177600ustar 00000000000000# 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. from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 def test_load_ec_public_numbers(benchmark): benchmark(EC_KEY_SECP256R1.public_numbers.public_key) def test_load_ec_private_numbers(benchmark): benchmark(EC_KEY_SECP256R1.private_key) cryptography-43.0.0/tests/bench/test_fernet.py010064400017510000177000000005271464676315000176520ustar 00000000000000# 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. from cryptography import fernet def test_fernet_encrypt(benchmark): f = fernet.Fernet(fernet.Fernet.generate_key()) benchmark(f.encrypt, b"\x00" * 256) cryptography-43.0.0/tests/bench/test_hashes.py010064400017510000177000000006511464676315000176400ustar 00000000000000# 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. from cryptography.hazmat.primitives import hashes def test_sha256(benchmark): def bench(): h = hashes.Hash(hashes.SHA256()) h.update(b"I love hashing. So much. The best.") return h.finalize() benchmark(bench) cryptography-43.0.0/tests/bench/test_hmac.py010064400017510000177000000007161464676315000172770ustar 00000000000000# 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. from cryptography.hazmat.primitives import hashes, hmac def test_hmac_sha256(benchmark): def bench(): h = hmac.HMAC(b"my extremely secure key", hashes.SHA256()) h.update(b"I love hashing. So much. The best.") return h.finalize() benchmark(bench) cryptography-43.0.0/tests/bench/test_x509.py010064400017510000177000000043641464676315000170770ustar 00000000000000# 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. import datetime import json import os import certifi from cryptography import x509 from ..utils import load_vectors_from_file def test_object_identifier_constructor(benchmark): benchmark(x509.ObjectIdentifier, "1.3.6.1.4.1.11129.2.4.5") def test_aki_public_bytes(benchmark): aki = x509.AuthorityKeyIdentifier( key_identifier=b"\x00" * 16, authority_cert_issuer=None, authority_cert_serial_number=None, ) benchmark(aki.public_bytes) def test_load_der_certificate(benchmark): cert_bytes = load_vectors_from_file( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), loader=lambda pemfile: pemfile.read(), mode="rb", ) benchmark(x509.load_der_x509_certificate, cert_bytes) def test_load_pem_certificate(benchmark): cert_bytes = load_vectors_from_file( os.path.join("x509", "cryptography.io.pem"), loader=lambda pemfile: pemfile.read(), mode="rb", ) benchmark(x509.load_pem_x509_certificate, cert_bytes) def test_verify_docs_python_org(benchmark, pytestconfig): limbo_root = pytestconfig.getoption("--x509-limbo-root", skip=True) with open(os.path.join(limbo_root, "limbo.json"), "rb") as f: [testcase] = [ tc for tc in json.load(f)["testcases"] if tc["id"] == "online::docs.python.org" ] with open(certifi.where(), "rb") as f: store = x509.verification.Store( x509.load_pem_x509_certificates(f.read()) ) leaf = x509.load_pem_x509_certificate( testcase["peer_certificate"].encode() ) intermediates = [ x509.load_pem_x509_certificate(c.encode()) for c in testcase["untrusted_intermediates"] ] time = datetime.datetime.fromisoformat(testcase["validation_time"]) def bench(): verifier = ( x509.verification.PolicyBuilder() .store(store) .time(time) .build_server_verifier(x509.DNSName("docs.python.org")) ) verifier.verify(leaf, intermediates) benchmark(bench) cryptography-43.0.0/tests/conftest.py010064400017510000177000000034301464676315000160720ustar 00000000000000# 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. import contextlib import pytest from cryptography.hazmat.backends.openssl import backend as openssl_backend from .utils import check_backend_support def pytest_configure(config): if config.getoption("--enable-fips"): openssl_backend._enable_fips() def pytest_report_header(config): return "\n".join( [ f"OpenSSL: {openssl_backend.openssl_version_text()}", f"FIPS Enabled: {openssl_backend._fips_enabled}", ] ) def pytest_addoption(parser): parser.addoption("--wycheproof-root", default=None) parser.addoption("--x509-limbo-root", default=None) parser.addoption("--enable-fips", default=False) def pytest_runtest_setup(item): if openssl_backend._fips_enabled: for marker in item.iter_markers(name="skip_fips"): pytest.skip(marker.kwargs["reason"]) @pytest.fixture(autouse=True) def backend(request): check_backend_support(openssl_backend, request) # Ensure the error stack is clear before the test errors = openssl_backend._consume_errors() assert not errors yield openssl_backend # Ensure the error stack is clear after the test errors = openssl_backend._consume_errors() assert not errors @pytest.fixture() def subtests(): # This is a miniature version of the pytest-subtests package, but # optimized for lower overhead. # # When tests are skipped, these are not logged in the final pytest output. yield SubTests() class SubTests: @contextlib.contextmanager def test(self): try: yield except pytest.skip.Exception: pass cryptography-43.0.0/tests/deprecated_module.py010064400017510000177000000006661464676315000177220ustar 00000000000000# 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. from cryptography import utils # This module exists to test `cryptography.utils.deprecated` DEPRECATED = 3 utils.deprecated( DEPRECATED, __name__, "Test Deprecated Object", DeprecationWarning, name="DEPRECATED", ) NOT_DEPRECATED = 12 cryptography-43.0.0/tests/doubles.py010064400017510000177000000023111464676315000156770ustar 00000000000000# 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. from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.modes import Mode class DummyCipherAlgorithm(CipherAlgorithm): name = "dummy-cipher" block_size = 128 key_size = 256 key_sizes = frozenset([256]) class DummyBlockCipherAlgorithm(DummyCipherAlgorithm, BlockCipherAlgorithm): def __init__(self, _: object) -> None: pass name = "dummy-block-cipher" class DummyMode(Mode): name = "dummy-mode" def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: pass class DummyHashAlgorithm(hashes.HashAlgorithm): name = "dummy-hash" block_size = None digest_size = 32 class DummyKeySerializationEncryption( serialization.KeySerializationEncryption ): pass class DummyAsymmetricPadding(padding.AsymmetricPadding): name = "dummy-padding" cryptography-43.0.0/tests/hazmat/__init__.py010064400017510000177000000002641464676315000172720ustar 00000000000000# 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. cryptography-43.0.0/tests/hazmat/backends/__init__.py010064400017510000177000000002641464676315000210440ustar 00000000000000# 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. cryptography-43.0.0/tests/hazmat/backends/test_openssl.py010064400017510000177000000201201464676315000220200ustar 00000000000000# 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. import itertools import os import pytest from cryptography.exceptions import InternalError, _Reasons from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from ...doubles import ( DummyAsymmetricPadding, DummyCipherAlgorithm, DummyHashAlgorithm, DummyMode, ) from ...hazmat.primitives.test_rsa import rsa_key_2048 from ...utils import ( load_vectors_from_file, raises_unsupported_algorithm, ) # Make ruff happy since we're importing fixtures that pytest patches in as # func args __all__ = ["rsa_key_2048"] class DummyMGF(padding.MGF): _salt_length = 0 _algorithm = hashes.SHA1() class TestOpenSSL: def test_backend_exists(self): assert backend def test_is_default_backend(self): assert backend is default_backend() def test_openssl_version_text(self): """ This test checks the value of OPENSSL_VERSION_TEXT. Unfortunately, this define does not appear to have a formal content definition, so for now we'll test to see if it starts with OpenSSL or LibreSSL as that appears to be true for every OpenSSL-alike. """ version = backend.openssl_version_text() assert version.startswith(("OpenSSL", "LibreSSL", "BoringSSL")) # Verify the correspondence between these two. And do it in a way that # ensures coverage. if version.startswith("LibreSSL"): assert rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL if rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL: assert version.startswith("LibreSSL") if version.startswith("BoringSSL"): assert rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL if rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert version.startswith("BoringSSL") def test_openssl_version_number(self): assert backend.openssl_version_number() > 0 def test_supports_cipher(self): assert ( backend.cipher_supported(DummyCipherAlgorithm(), DummyMode()) is False ) def test_openssl_assert(self): backend.openssl_assert(True) with pytest.raises(InternalError): backend.openssl_assert(False) def test_consume_errors(self): for i in range(10): backend._lib.ERR_put_error( backend._lib.ERR_LIB_EVP, 0, 0, b"test_openssl.py", -1 ) assert backend._lib.ERR_peek_error() != 0 errors = backend._consume_errors() assert backend._lib.ERR_peek_error() == 0 assert len(errors) == 10 def test_ssl_ciphers_registered(self): meth = backend._lib.TLS_method() ctx = backend._lib.SSL_CTX_new(meth) assert ctx != backend._ffi.NULL backend._lib.SSL_CTX_free(ctx) def test_evp_ciphers_registered(self): cipher = backend._lib.EVP_get_cipherbyname(b"aes-256-cbc") assert cipher != backend._ffi.NULL class TestOpenSSLRSA: def test_rsa_padding_unsupported_pss_mgf1_hash(self): assert ( backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(DummyHashAlgorithm()), salt_length=0 ) ) is False ) def test_rsa_padding_unsupported(self): assert backend.rsa_padding_supported(DummyAsymmetricPadding()) is False def test_rsa_padding_supported_pkcs1v15(self): assert backend.rsa_padding_supported(padding.PKCS1v15()) is True def test_rsa_padding_supported_pss(self): assert ( backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) ) is True ) def test_rsa_padding_supported_oaep(self): assert ( backend.rsa_padding_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ), ) is True ) def test_rsa_padding_supported_oaep_sha2_combinations(self): hashalgs = [ hashes.SHA1(), hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512(), ] for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): if backend._fips_enabled and ( isinstance(mgf1alg, hashes.SHA1) or isinstance(oaepalg, hashes.SHA1) ): continue assert ( backend.rsa_padding_supported( padding.OAEP( mgf=padding.MGF1(algorithm=mgf1alg), algorithm=oaepalg, label=None, ), ) is True ) def test_rsa_padding_unsupported_mgf(self): assert ( backend.rsa_padding_supported( padding.OAEP( mgf=DummyMGF(), algorithm=hashes.SHA1(), label=None, ), ) is False ) assert ( backend.rsa_padding_supported( padding.PSS(mgf=DummyMGF(), salt_length=0) ) is False ) def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_2048): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): rsa_key_2048.decrypt( b"0" * 256, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.MD5()), algorithm=hashes.MD5(), label=None, ), ) class TestOpenSSLSerializationWithOpenSSL: def test_very_long_pem_serialization_password(self): password = b"x" * 1025 with pytest.raises(ValueError, match="Passwords longer than"): load_vectors_from_file( os.path.join( "asymmetric", "Traditional_OpenSSL_Serialization", "key1.pem", ), lambda pemfile: ( serialization.load_pem_private_key( pemfile.read().encode(), password, unsafe_skip_rsa_key_validation=False, ) ), ) class TestRSAPEMSerialization: def test_password_length_limit(self, rsa_key_2048): password = b"x" * 1024 with pytest.raises(ValueError): rsa_key_2048.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(password), ) @pytest.mark.skipif( backend._lib.Cryptography_HAS_EVP_PKEY_DHX == 1, reason="Requires OpenSSL without EVP_PKEY_DHX", ) @pytest.mark.supported( only_if=lambda backend: backend.dh_supported(), skip_message="Requires DH support", ) class TestOpenSSLDHSerialization: @pytest.mark.parametrize( ("key_path", "loader_func"), [ ( os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.pem"), serialization.load_pem_private_key, ), ( os.path.join("asymmetric", "DH", "dhkey_rfc5114_2.der"), serialization.load_der_private_key, ), ], ) def test_private_load_dhx_unsupported( self, key_path, loader_func, backend ): key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) with pytest.raises(ValueError): loader_func(key_bytes, None, backend) cryptography-43.0.0/tests/hazmat/bindings/test_openssl.py010064400017510000177000000075251464676315000220610ustar 00000000000000# 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. import pytest from cryptography.exceptions import InternalError from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl.binding import ( Binding, _openssl_assert, _verify_package_version, ) class TestOpenSSL: def test_binding_loads(self): binding = Binding() assert binding assert binding.lib assert binding.ffi def test_ssl_ctx_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() # SSL_OP_ALL is 0 on BoringSSL if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b.lib.SSL_OP_ALL > 0 ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) current_options = b.lib.SSL_CTX_get_options(ctx) resp = b.lib.SSL_CTX_set_options(ctx, b.lib.SSL_OP_ALL) expected_options = current_options | b.lib.SSL_OP_ALL assert resp == expected_options assert b.lib.SSL_CTX_get_options(ctx) == expected_options def test_ssl_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. b = Binding() # SSL_OP_ALL is 0 on BoringSSL if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b.lib.SSL_OP_ALL > 0 ctx = b.lib.SSL_CTX_new(b.lib.TLS_method()) assert ctx != b.ffi.NULL ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) ssl = b.lib.SSL_new(ctx) ssl = b.ffi.gc(ssl, b.lib.SSL_free) current_options = b.lib.SSL_get_options(ssl) resp = b.lib.SSL_set_options(ssl, b.lib.SSL_OP_ALL) expected_options = current_options | b.lib.SSL_OP_ALL assert resp == expected_options assert b.lib.SSL_get_options(ssl) == expected_options def test_conditional_removal(self): b = Binding() if not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL: assert b.lib.TLS_ST_OK else: with pytest.raises(AttributeError): b.lib.TLS_ST_OK def test_openssl_assert_error_on_stack(self): b = Binding() b.lib.ERR_put_error( b.lib.ERR_LIB_EVP, b.lib.EVP_F_EVP_ENCRYPTFINAL_EX, b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, b"", -1, ) with pytest.raises(InternalError) as exc_info: _openssl_assert(False) error = exc_info.value.err_code[0] assert error.lib == b.lib.ERR_LIB_EVP assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b"data not multiple of block length" in error.reason_text def test_version_mismatch(self): with pytest.raises(ImportError): _verify_package_version("nottherightversion") def test_rust_internal_error(self): with pytest.raises(InternalError) as exc_info: rust_openssl.raise_openssl_error() assert len(exc_info.value.err_code) == 0 b = Binding() b.lib.ERR_put_error( b.lib.ERR_LIB_EVP, b.lib.EVP_F_EVP_ENCRYPTFINAL_EX, b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, b"", -1, ) with pytest.raises(InternalError) as exc_info: rust_openssl.raise_openssl_error() error = exc_info.value.err_code[0] assert error.lib == b.lib.ERR_LIB_EVP assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL: assert b"data not multiple of block length" in error.reason_text cryptography-43.0.0/tests/hazmat/primitives/__init__.py010064400017510000177000000002641464676315000214650ustar 00000000000000# 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. cryptography-43.0.0/tests/hazmat/primitives/decrepit/__init__.py010064400017510000177000000002641464676315000232640ustar 00000000000000# 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. cryptography-43.0.0/tests/hazmat/primitives/decrepit/test_3des.py010064400017510000177000000127651464676315000234330ustar 00000000000000# 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. """ Test using the NIST Test Vectors """ import binascii import os import pytest from cryptography.hazmat.decrepit.ciphers import algorithms from cryptography.hazmat.primitives.ciphers import modes from ....utils import load_nist_vectors from ..utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.TripleDES(b"\x00" * 8), modes.CBC(b"\x00" * 8) ), skip_message="Does not support TripleDES CBC", ) class TestTripleDESModeCBC: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), [ "TCBCinvperm.rsp", "TCBCpermop.rsp", "TCBCsubtab.rsp", "TCBCvarkey.rsp", "TCBCvartext.rsp", ], lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), ["TCBCMMT1.rsp", "TCBCMMT2.rsp", "TCBCMMT3.rsp"], lambda key1, key2, key3, **kwargs: algorithms.TripleDES( binascii.unhexlify(key1 + key2 + key3) ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.TripleDES(b"\x00" * 8), modes.OFB(b"\x00" * 8) ), skip_message="Does not support TripleDES OFB", ) class TestTripleDESModeOFB: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), [ "TOFBpermop.rsp", "TOFBsubtab.rsp", "TOFBvarkey.rsp", "TOFBvartext.rsp", "TOFBinvperm.rsp", ], lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), ["TOFBMMT1.rsp", "TOFBMMT2.rsp", "TOFBMMT3.rsp"], lambda key1, key2, key3, **kwargs: algorithms.TripleDES( binascii.unhexlify(key1 + key2 + key3) ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.TripleDES(b"\x00" * 8), modes.CFB(b"\x00" * 8) ), skip_message="Does not support TripleDES CFB", ) class TestTripleDESModeCFB: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ "TCFB64invperm.rsp", "TCFB64permop.rsp", "TCFB64subtab.rsp", "TCFB64varkey.rsp", "TCFB64vartext.rsp", ], lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), ["TCFB64MMT1.rsp", "TCFB64MMT2.rsp", "TCFB64MMT3.rsp"], lambda key1, key2, key3, **kwargs: algorithms.TripleDES( binascii.unhexlify(key1 + key2 + key3) ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.TripleDES(b"\x00" * 8), modes.CFB8(b"\x00" * 8) ), skip_message="Does not support TripleDES CFB8", ) class TestTripleDESModeCFB8: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ "TCFB8invperm.rsp", "TCFB8permop.rsp", "TCFB8subtab.rsp", "TCFB8varkey.rsp", "TCFB8vartext.rsp", ], lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), ) test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), ["TCFB8MMT1.rsp", "TCFB8MMT2.rsp", "TCFB8MMT3.rsp"], lambda key1, key2, key3, **kwargs: algorithms.TripleDES( binascii.unhexlify(key1 + key2 + key3) ), lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.TripleDES(b"\x00" * 8), modes.ECB() ), skip_message="Does not support TripleDES ECB", ) class TestTripleDESModeECB: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "ECB"), [ "TECBinvperm.rsp", "TECBpermop.rsp", "TECBsubtab.rsp", "TECBvarkey.rsp", "TECBvartext.rsp", ], lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), lambda **kwargs: modes.ECB(), ) test_mmt = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "3DES", "ECB"), ["TECBMMT1.rsp", "TECBMMT2.rsp", "TECBMMT3.rsp"], lambda key1, key2, key3, **kwargs: algorithms.TripleDES( binascii.unhexlify(key1 + key2 + key3) ), lambda **kwargs: modes.ECB(), ) cryptography-43.0.0/tests/hazmat/primitives/decrepit/test_algorithms.py010064400017510000177000000271161464676315000247420ustar 00000000000000# 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. import binascii import os import pytest from cryptography.exceptions import _Reasons from cryptography.hazmat.decrepit.ciphers.algorithms import ( ARC4, CAST5, IDEA, SEED, Blowfish, TripleDES, ) from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import modes from ....utils import load_nist_vectors, raises_unsupported_algorithm from ..utils import generate_encrypt_test class TestARC4: @pytest.mark.parametrize( ("key", "keysize"), [ (b"0" * 10, 40), (b"0" * 14, 56), (b"0" * 16, 64), (b"0" * 20, 80), (b"0" * 32, 128), (b"0" * 48, 192), (b"0" * 64, 256), ], ) def test_key_size(self, key, keysize): cipher = ARC4(binascii.unhexlify(key)) assert cipher.key_size == keysize def test_invalid_key_size(self): with pytest.raises(ValueError): ARC4(binascii.unhexlify(b"0" * 34)) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): ARC4("0" * 10) # type: ignore[arg-type] def test_invalid_mode_algorithm(): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): ciphers.Cipher( ARC4(b"\x00" * 16), modes.GCM(b"\x00" * 12), ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): ciphers.Cipher( ARC4(b"\x00" * 16), modes.CBC(b"\x00" * 12), ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): ciphers.Cipher( ARC4(b"\x00" * 16), modes.CTR(b"\x00" * 12), ) class TestTripleDES: @pytest.mark.parametrize("key", [b"0" * 16, b"0" * 32, b"0" * 48]) def test_key_size(self, key): cipher = TripleDES(binascii.unhexlify(key)) assert cipher.key_size == 192 def test_invalid_key_size(self): with pytest.raises(ValueError): TripleDES(binascii.unhexlify(b"0" * 12)) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): TripleDES("0" * 16) # type: ignore[arg-type] class TestBlowfish: @pytest.mark.parametrize( ("key", "keysize"), [(b"0" * (keysize // 4), keysize) for keysize in range(32, 449, 8)], ) def test_key_size(self, key, keysize): cipher = Blowfish(binascii.unhexlify(key)) assert cipher.key_size == keysize def test_invalid_key_size(self): with pytest.raises(ValueError): Blowfish(binascii.unhexlify(b"0" * 6)) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): Blowfish("0" * 8) # type: ignore[arg-type] @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( Blowfish(b"\x00" * 56), modes.ECB() ), skip_message="Does not support Blowfish ECB", ) class TestBlowfishModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ecb.txt"], lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( Blowfish(b"\x00" * 56), modes.CBC(b"\x00" * 8) ), skip_message="Does not support Blowfish CBC", ) class TestBlowfishModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cbc.txt"], lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( Blowfish(b"\x00" * 56), modes.OFB(b"\x00" * 8) ), skip_message="Does not support Blowfish OFB", ) class TestBlowfishModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ofb.txt"], lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( Blowfish(b"\x00" * 56), modes.CFB(b"\x00" * 8) ), skip_message="Does not support Blowfish CFB", ) class TestBlowfishModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cfb.txt"], lambda key, **kwargs: Blowfish(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) class TestCAST5: @pytest.mark.parametrize( ("key", "keysize"), [(b"0" * (keysize // 4), keysize) for keysize in range(40, 129, 8)], ) def test_key_size(self, key, keysize): cipher = CAST5(binascii.unhexlify(key)) assert cipher.key_size == keysize def test_invalid_key_size(self): with pytest.raises(ValueError): CAST5(binascii.unhexlify(b"0" * 34)) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): CAST5("0" * 10) # type: ignore[arg-type] @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( CAST5(b"\x00" * 16), modes.ECB() ), skip_message="Does not support CAST5 ECB", ) class TestCAST5ModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ecb.txt"], lambda key, **kwargs: CAST5(binascii.unhexlify(key)), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( CAST5(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support CAST5 CBC", ) class TestCAST5ModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cbc.txt"], lambda key, **kwargs: CAST5(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( CAST5(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support CAST5 OFB", ) class TestCAST5ModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-ofb.txt"], lambda key, **kwargs: CAST5(binascii.unhexlify(key)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( CAST5(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support CAST5 CFB", ) class TestCAST5ModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "CAST5"), ["cast5-cfb.txt"], lambda key, **kwargs: CAST5(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) class TestIDEA: def test_key_size(self): cipher = IDEA(b"\x00" * 16) assert cipher.key_size == 128 def test_invalid_key_size(self): with pytest.raises(ValueError): IDEA(b"\x00" * 17) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): IDEA("0" * 16) # type: ignore[arg-type] @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( IDEA(b"\x00" * 16), modes.ECB() ), skip_message="Does not support IDEA ECB", ) class TestIDEAModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ecb.txt"], lambda key, **kwargs: IDEA(binascii.unhexlify(key)), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( IDEA(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support IDEA CBC", ) class TestIDEAModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cbc.txt"], lambda key, **kwargs: IDEA(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( IDEA(b"\x00" * 16), modes.OFB(b"\x00" * 8) ), skip_message="Does not support IDEA OFB", ) class TestIDEAModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-ofb.txt"], lambda key, **kwargs: IDEA(binascii.unhexlify(key)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( IDEA(b"\x00" * 16), modes.CFB(b"\x00" * 8) ), skip_message="Does not support IDEA CFB", ) class TestIDEAModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "IDEA"), ["idea-cfb.txt"], lambda key, **kwargs: IDEA(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) class TestSEED: def test_key_size(self): cipher = SEED(b"\x00" * 16) assert cipher.key_size == 128 def test_invalid_key_size(self): with pytest.raises(ValueError): SEED(b"\x00" * 17) def test_invalid_key_type(self): with pytest.raises(TypeError, match="key must be bytes"): SEED("0" * 16) # type: ignore[arg-type] @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( SEED(b"\x00" * 16), modes.ECB() ), skip_message="Does not support SEED ECB", ) class TestSEEDModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4269.txt"], lambda key, **kwargs: SEED(binascii.unhexlify(key)), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( SEED(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support SEED CBC", ) class TestSEEDModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["rfc-4196.txt"], lambda key, **kwargs: SEED(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( SEED(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support SEED OFB", ) class TestSEEDModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-ofb.txt"], lambda key, **kwargs: SEED(binascii.unhexlify(key)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( SEED(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support SEED CFB", ) class TestSEEDModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SEED"), ["seed-cfb.txt"], lambda key, **kwargs: SEED(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) cryptography-43.0.0/tests/hazmat/primitives/decrepit/test_arc4.py010064400017510000177000000020031464676315000234060ustar 00000000000000# 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. import binascii import os import pytest from cryptography.hazmat.decrepit.ciphers import algorithms from ....utils import load_nist_vectors from ..utils import generate_stream_encryption_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.ARC4(b"\x00" * 16), None ), skip_message="Does not support ARC4", ) class TestARC4: test_rfc = generate_stream_encryption_test( load_nist_vectors, os.path.join("ciphers", "ARC4"), [ "rfc-6229-40.txt", "rfc-6229-56.txt", "rfc-6229-64.txt", "rfc-6229-80.txt", "rfc-6229-128.txt", "rfc-6229-192.txt", "rfc-6229-256.txt", "arc4.txt", ], lambda key, **kwargs: algorithms.ARC4(binascii.unhexlify(key)), ) cryptography-43.0.0/tests/hazmat/primitives/decrepit/test_rc2.py010064400017510000177000000016761464676315000232620ustar 00000000000000# 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. """ Test using the NIST Test Vectors """ import binascii import os import pytest from cryptography.hazmat.decrepit.ciphers.algorithms import RC2 from cryptography.hazmat.primitives.ciphers import modes from ....utils import load_nist_vectors from ..utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( RC2(b"\x00" * 16), modes.CBC(b"\x00" * 8) ), skip_message="Does not support RC2 CBC", ) class TestRC2ModeCBC: test_kat = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "RC2"), [ "rc2-cbc.txt", ], lambda key, **kwargs: RC2(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) cryptography-43.0.0/tests/hazmat/primitives/fixtures_dh.py010064400017510000177000000023021464676315000222450ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric import dh FFDH3072_P = dh.DHParameterNumbers( p=int( "ffffffffffffffffadf85458a2bb4a9aafdc5620273d3cf1d8b9c583ce2d3695a9e" "13641146433fbcc939dce249b3ef97d2fe363630c75d8f681b202aec4617ad3df1e" "d5d5fd65612433f51f5f066ed0856365553ded1af3b557135e7f57c935984f0c70e" "0e68b77e2a689daf3efe8721df158a136ade73530acca4f483a797abc0ab182b324" "fb61d108a94bb2c8e3fbb96adab760d7f4681d4f42a3de394df4ae56ede76372bb1" "90b07a7c8ee0a6d709e02fce1cdf7e2ecc03404cd28342f619172fe9ce98583ff8e" "4f1232eef28183c3fe3b1b4c6fad733bb5fcbc2ec22005c58ef1837d1683b2c6f34" "a26c1b2effa886b4238611fcfdcde355b3b6519035bbc34f4def99c023861b46fc9" "d6e6c9077ad91d2691f7f7ee598cb0fac186d91caefe130985139270b4130c93bc4" "37944f4fd4452e2d74dd364f2e21e71f54bff5cae82ab9c9df69ee86d2bc522363a" "0dabc521979b0deada1dbf9a42d5c4484e0abcd06bfa53ddef3c1b20ee3fd59d7c2" "5e41d2b66c62e37ffffffffffffffff", 16, ), g=2, ) cryptography-43.0.0/tests/hazmat/primitives/fixtures_dsa.py010064400017510000177000000173351464676315000224350ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric.dsa import ( DSAParameterNumbers, DSAPrivateNumbers, DSAPublicNumbers, ) DSA_KEY_1024 = DSAPrivateNumbers( public_numbers=DSAPublicNumbers( parameter_numbers=DSAParameterNumbers( p=int( "d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725ef34" "1eabb47cf8a7a8a41e792a156b7ce97206c4f9c5ce6fc5ae7912102b6" "b502e59050b5b21ce263dddb2044b652236f4d42ab4b5d6aa73189cef" "1ace778d7845a5c1c1c7147123188f8dc551054ee162b634d60f097f7" "19076640e20980a0093113a8bd73", 16, ), q=int("96c5390a8b612c0e422bb2b0ea194a3ec935a281", 16), g=int( "06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce499" "1d2b862259d6b4548a6495b195aa0e0b6137ca37eb23b94074d3c3d30" "0042bdf15762812b6333ef7b07ceba78607610fcc9ee68491dbc1e34c" "d12615474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3f4" "fd9f93cd6f4f17fc076341a7e7d9", 16, ), ), y=int( "6f26d98d41de7d871b6381851c9d91fa03942092ab6097e76422070edb71d" "b44ff568280fdb1709f8fc3feab39f1f824adaeb2a298088156ac31af1aa0" "4bf54f475bdcfdcf2f8a2dd973e922d83e76f016558617603129b21c70bf7" "d0e5dc9e68fe332e295b65876eb9a12fe6fca9f1a1ce80204646bf99b5771" "d249a6fea627", 16, ), ), x=int("8185fee9cc7c0e91fd85503274f1cd5a3fd15a49", 16), ) DSA_KEY_2048 = DSAPrivateNumbers( public_numbers=DSAPublicNumbers( parameter_numbers=DSAParameterNumbers( p=int( "ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace5e9c4" "1434c9cf0a8e9498acb0f4663c08b4484eace845f6fb17dac62c98e70" "6af0fc74e4da1c6c2b3fbf5a1d58ff82fc1a66f3e8b12252c40278fff" "9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d244e54561b0" "dca39b301de8c49da9fb23df33c6182e3f983208c560fb5119fbf78eb" "e3e6564ee235c6a15cbb9ac247baba5a423bc6582a1a9d8a2b4f0e9e3" "d9dbac122f750dd754325135257488b1f6ecabf21bff2947fe0d3b2cb" "7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a908c36e9" "5e60bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac5aa66ef7", 16, ), q=int( "8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b18f507" "192c19d", 16, ), g=int( "e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b1" "913413d344d1d8d84a333839d88eee431521f6e357c16e6a93be111a9" "8076739cd401bab3b9d565bf4fb99e9d185b1e14d61c93700133f908b" "ae03e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d555" "1b2fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c6156" "8f78d0706b10a26f23b4f197c322b825002284a0aca91807bba98ece9" "12b80e10cdf180cf99a35f210c1655fbfdd74f13b1b5046591f840387" "3d12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed27" "3b146ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302", 16, ), ), y=int( "6b32e31ab9031dc4dd0b5039a78d07826687ab087ae6de4736f5b0434e125" "3092e8a0b231f9c87f3fc8a4cb5634eb194bf1b638b7a7889620ce6711567" "e36aa36cda4604cfaa601a45918371d4ccf68d8b10a50a0460eb1dc0fff62" "ef5e6ee4d473e18ea4a66c196fb7e677a49b48241a0b4a97128eff30fa437" "050501a584f8771e7280d26d5af30784039159c11ebfea10b692fd0a58215" "eeb18bff117e13f08db792ed4151a218e4bed8dddfb0793225bd1e9773505" "166f4bd8cedbb286ea28232972da7bae836ba97329ba6b0a36508e50a52a7" "675e476d4d4137eae13f22a9d2fefde708ba8f34bf336c6e76331761e4b06" "17633fe7ec3f23672fb19d27", 16, ), ), x=int( "405772da6e90d809e77d5de796562a2dd4dfd10ef00a83a3aba6bd818a0348a1", 16 ), ) DSA_KEY_3072 = DSAPrivateNumbers( public_numbers=DSAPublicNumbers( parameter_numbers=DSAParameterNumbers( p=int( "f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d582" "8c352f593a9a787760ce34b789879941f2f01f02319f6ae0b756f1a84" "2ba54c85612ed632ee2d79ef17f06b77c641b7b080aff52a03fc2462e" "80abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a84e" "c389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea265" "1b372f8b58a0a65554db2561d706a63685000ac576b7e4562e262a142" "85a9c6370b290e4eb7757527d80b6c0fd5df831d36f3d1d35f12ab060" "548de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba" "9844d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1" "d54f90cce1a491a9c74fea88f7e7230b047d16b5a6027881d6f154818" "f06e513faf40c8814630e4e254f17a47bfe9cb519b98289935bf17673" "ae4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f747" "6cd715eaabb7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c1" "136f303f4b4d25ad5b692229957", 16, ), q=int( "d3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210f6169" "041653b", 16, ), g=int( "ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978db2104" "d7394b493c18332c64cec906a71c3778bd93341165dee8e6cd4ca6f13" "afff531191194ada55ecf01ff94d6cf7c4768b82dd29cd131aaf202ae" "fd40e564375285c01f3220af4d70b96f1395420d778228f1461f5d0b8" "e47357e87b1fe3286223b553e3fc9928f16ae3067ded6721bedf1d1a0" "1bfd22b9ae85fce77820d88cdf50a6bde20668ad77a707d1c60fcc5d5" "1c9de488610d0285eb8ff721ff141f93a9fb23c1d1f7654c07c46e588" "36d1652828f71057b8aff0b0778ef2ca934ea9d0f37daddade2d823a4" "d8e362721082e279d003b575ee59fd050d105dfd71cd63154efe431a0" "869178d9811f4f231dc5dcf3b0ec0f2b0f9896c32ec6c7ee7d60aa971" "09e09224907328d4e6acd10117e45774406c4c947da8020649c3168f6" "90e0bd6e91ac67074d1d436b58ae374523deaf6c93c1e6920db4a080b" "744804bb073cecfe83fa9398cf150afa286dc7eb7949750cf5001ce10" "4e9187f7e16859afa8fd0d775ae", 16, ), ), y=int( "814824e435e1e6f38daa239aad6dad21033afce6a3ebd35c1359348a0f241" "8871968c2babfc2baf47742148828f8612183178f126504da73566b6bab33" "ba1f124c15aa461555c2451d86c94ee21c3e3fc24c55527e01b1f03adcdd8" "ec5cb08082803a7b6a829c3e99eeb332a2cf5c035b0ce0078d3d414d31fa4" "7e9726be2989b8d06da2e6cd363f5a7d1515e3f4925e0b32adeae3025cc5a" "996f6fd27494ea408763de48f3bb39f6a06514b019899b312ec570851637b" "8865cff3a52bf5d54ad5a19e6e400a2d33251055d0a440b50d53f4791391d" "c754ad02b9eab74c46b4903f9d76f824339914db108057af7cde657d41766" "a99991ac8787694f4185d6f91d7627048f827b405ec67bf2fe56141c4c581" "d8c317333624e073e5879a82437cb0c7b435c0ce434e15965db1315d64895" "991e6bbe7dac040c42052408bbc53423fd31098248a58f8a67da3a39895cd" "0cc927515d044c1e3cb6a3259c3d0da354cce89ea3552c59609db10ee9899" "86527436af21d9485ddf25f90f7dff6d2bae", 16, ), ), x=int( "b2764c46113983777d3e7e97589f1303806d14ad9f2f1ef033097de954b17706", 16 ), ) cryptography-43.0.0/tests/hazmat/primitives/fixtures_ec.py010064400017510000177000000226031464676315000222470ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric import ec EC_KEY_SECT571R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "213997069697108634621868251335076179190383272087548888968788698953" "131928375431570122753130054966269038244076049869476736547896549201" "7388482714521707824160638375437887802901" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT571R1(), x=int( "42585672410900520895287019432267514156432686681290164230262278" "54789182447139054594501570747809649335533486119017169439209005" "883737780433424425566023654583165324498640038089" ), y=int( "13822523320209387572500458104799806851658024537477228250738334" "46977851514777531296572763848253279034733550774927720436494321" "97281333379623823457479233585424800362717541750" ), ), ) EC_KEY_SECT409R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "604993237916498765317587097853603474519114726157206838874832379003" "281871982139714656205843929472002062791572217653118715727" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT409R1(), x=int( "76237701339268928039087238870073679814646664010783544301589269" "2272579213400205907766385199643053767195204247826349822350081" ), y=int( "10056668929618383045204866060110626563392345494925302478351744" "01475129090774493235522729123877384838835703483224447476728811" ), ), ) EC_KEY_SECT283R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "589705077255658434962118789801402573495547207239917043241273753671" "0603230261342427657" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT283R1(), x=int( "10694213430317013187241490088760888472172922291550831393222973" "531614941756901942108493" ), y=int( "11461553100313943515373601367527399649593366728262918214942116" "4359557613202950705170" ), ), ) EC_KEY_SECT233R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "343470067105388144757135261232658742142830154753739648095101899829" "8288" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT233R1(), x=int( "74494951569151557692195071465128140646140765188698294062550374" "71118267" ), y=int( "48699150823022962508544923825876164485917001162461401797511748" "44872205" ), ), ) EC_KEY_SECT163R2 = ec.EllipticCurvePrivateNumbers( private_value=int("11788436193853888218177032687141056784083668635"), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT163R2(), x=int("5247234453330640212490501030772203801908103222463"), y=int("3172513801099088785224248292142866317754124455206"), ), ) EC_KEY_SECT571K1 = ec.EllipticCurvePrivateNumbers( private_value=int( "592811051234886966121888758661314648311634839499582476726008738218" "165015048237934517672316204181933804884636855291118594744334592153" "883208936227914544246799490897169723387" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT571K1(), x=int( "81362471461936552203898455874182916939857774872643607884250052" "29301336524105230729653881789373412990921493551253481866317181" "50644729351721577822595637058949405764944491655" ), y=int( "14058041260812945396067821061063618047896814719828637241661260" "31235681542401975593036630733881695595289523801041910183736211" "587294494888450327374439795428519848065589000434" ), ), ) EC_KEY_SECT409K1 = ec.EllipticCurvePrivateNumbers( private_value=int( "110321743150399087059465162400463719641470113494908091197354523708" "934106732952992153105338671368548199643686444619485307877" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT409K1(), x=int( "62280214209410363493525178797944995742119600145953755916426161" "0790364158569265348038207313261547476506319796469776797725796" ), y=int( "46653883749102474289095010108777579907422472804577185369332018" "7318642669590280811057512951467298158275464566214288556375885" ), ), ) EC_KEY_SECT283K1 = ec.EllipticCurvePrivateNumbers( private_value=int( "182508394415444014156574733141549331538128234395356466108310015130" "3868915489347291850" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT283K1(), x=int( "31141647206111886426350703123670451554123180910379592764773885" "2959123367428352287032" ), y=int( "71787460144483665964585187837283963089964760704065205376175384" "58957627834444017112582" ), ), ) EC_KEY_SECT233K1 = ec.EllipticCurvePrivateNumbers( private_value=int( "172670089647474613734091436081960550801254775902629891892394471086" "2070" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT233K1(), x=int( "55693911474339510991521579392202889561373678973929426354737048" "68129172" ), y=int( "11025856248546376145959939911850923631416718241836051344384802" "737277815" ), ), ) EC_KEY_SECT163K1 = ec.EllipticCurvePrivateNumbers( private_value=int("3699303791425402204035307605170569820290317991287"), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECT163K1(), x=int("4479755902310063321544063130576409926980094120721"), y=int("3051218481937171839039826690648109285113977745779"), ), ) EC_KEY_SECP521R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "662751235215460886290293902658128847495347691199214706697089140769" "672273950767961331442265530524063943548846724348048614239791498442" "5997823106818915698960565" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECP521R1(), x=int( "12944742826257420846659527752683763193401384271391513286022917" "29910013082920512632908350502247952686156279140016049549948975" "670668730618745449113644014505462" ), y=int( "10784108810271976186737587749436295782985563640368689081052886" "16296815984553198866894145509329328086635278430266482551941240" "591605833440825557820439734509311" ), ), ) EC_KEY_SECP384R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "280814107134858470598753916394807521398239633534281633982576099083" "35787109896602102090002196616273211495718603965098" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECP384R1(), x=int( "10036914308591746758780165503819213553101287571902957054148542" "504671046744460374996612408381962208627004841444205030" ), y=int( "17337335659928075994560513699823544906448896792102247714689323" "575406618073069185107088229463828921069465902299522926" ), ), ) EC_KEY_SECP256R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "271032978511595617649844168316234344656921218699414461240502635010" "25776962849" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECP256R1(), x=int( "49325986169170464532722748935508337546545346352733747948730305" "442770101441241" ), y=int( "51709162888529903487188595007092772817469799707382623884187518" "455962250433661" ), ), ) EC_KEY_SECP256K1 = ec.EllipticCurvePrivateNumbers( private_value=int( "683341569008473593765879222774207677458810362976327530563215318048" "64380736732" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECP256K1(), x=int( "59251322975795306609293064274738085741081547489119277536110995" "120127593127884" ), y=int( "10334192001480392039227801832201340147605940717841294644187071" "8261641142297801" ), ), ) EC_KEY_SECP224R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "234854340492774342642505519082413233282383066880756900834047566251" "50" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECP224R1(), x=int( "51165676638271204691095081341581621487998422645261573824239666" "1214" ), y=int( "14936601450555711309158397172719963843891926209168533453717969" "1265" ), ), ) EC_KEY_SECP192R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "4534766128536179420071447168915990251715442361606049349869" ), public_numbers=ec.EllipticCurvePublicNumbers( curve=ec.SECP192R1(), x=int("5415069751170397888083674339683360671310515485781457536999"), y=int("18671605334415960797751252911958331304288357195986572776"), ), ) cryptography-43.0.0/tests/hazmat/primitives/fixtures_rsa.py010064400017510000177000000711721464676315000224520ustar 00000000000000# 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. from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers, ) RSA_KEY_512 = RSAPrivateNumbers( p=int( "d57846898d5c0de249c08467586cb458fa9bc417cdf297f73cfc52281b787cd9", 16 ), q=int( "d10f71229e87e010eb363db6a85fd07df72d985b73c42786191f2ce9134afb2d", 16 ), d=int( "272869352cacf9c866c4e107acc95d4c608ca91460a93d28588d51cfccc07f449" "18bbe7660f9f16adc2b4ed36ca310ef3d63b79bd447456e3505736a45a6ed21", 16, ), dmp1=int( "addff2ec7564c6b64bc670d250b6f24b0b8db6b2810099813b7e7658cecf5c39", 16 ), dmq1=int( "463ae9c6b77aedcac1397781e50e4afc060d4b216dc2778494ebe42a6850c81", 16 ), iqmp=int( "54deef8548f65cad1d411527a32dcb8e712d3e128e4e0ff118663fae82a758f4", 16 ), public_numbers=RSAPublicNumbers( e=65537, n=int( "ae5411f963c50e3267fafcf76381c8b1e5f7b741fdb2a544bcf48bd607b10c991" "90caeb8011dc22cf83d921da55ec32bd05cac3ee02ca5e1dbef93952850b525", 16, ), ), ) RSA_KEY_522 = RSAPrivateNumbers( p=int( "1a8aab9a069f92b52fdf05824f2846223dc27adfc806716a247a77d4c36885e4bf", 16, ), q=int( "19e8d620d177ec54cdb733bb1915e72ef644b1202b889ceb524613efa49c07eb4f", 16, ), d=int( "10b8a7c0a92c1ae2d678097d69db3bfa966b541fb857468291d48d1b52397ea2bac0d" "4370c159015c7219e3806a01bbafaffdd46f86e3da1e2d1fe80a0369ccd745", 16, ), dmp1=int( "3eb6277f66e6e2dcf89f1b8529431f730839dbd9a3e49555159bc8470eee886e5", 16 ), dmq1=int( "184b4d74aa54c361e51eb23fee4eae5e4786b37b11b6e0447af9c0b9c4e4953c5b", 16, ), iqmp=int( "f80e9ab4fa7b35d0d232ef51c4736d1f2dcf2c7b1dd8716211b1bf1337e74f8ae", 16 ), public_numbers=RSAPublicNumbers( e=65537, n=int( "2afaea0e0bb6fca037da7d190b5270a6c665bc18e7a456f7e69beaac4433db748" "ba99acdd14697e453bca596eb35b47f2d48f1f85ef08ce5109dad557a9cf85ebf" "1", 16, ), ), ) RSA_KEY_599 = RSAPrivateNumbers( p=int( "cf95d20be0c7af69f4b3d909f65d858c26d1a7ef34da8e3977f4fa230580e58814b54" "24be99", 16, ), q=int( "6052be4b28debd4265fe12ace5aa4a0c4eb8d63ff8853c66824b35622161eb48a3bc8" "c3ada5", 16, ), d=int( "69d9adc465e61585d3142d7cc8dd30605e8d1cbbf31009bc2cd5538dc40528d5d68ee" "fe6a42d23674b6ec76e192351bf368c8968f0392110bf1c2825dbcff071270b80adcc" "fa1d19d00a1", 16, ), dmp1=int( "a86d10edde456687fba968b1f298d2e07226adb1221b2a466a93f3d83280f0bb46c20" "2b6811", 16, ), dmq1=int( "40d570e08611e6b1da94b95d46f8e7fe80be48f7a5ff8838375b08039514a399b11c2" "80735", 16, ), iqmp=int( "cd051cb0ea68b88765c041262ace2ec4db11dab14afd192742e34d5da3328637fabdf" "bae26e", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "4e1b470fe00642426f3808e74c959632dd67855a4c503c5b7876ccf4dc7f6a1a4" "9107b90d26daf0a7879a6858218345fbc6e59f01cd095ca5647c27c25265e6c47" "4fea89537191c7073d9d", 16, ), ), ) RSA_KEY_745 = RSAPrivateNumbers( p=int( "1c5a0cfe9a86debd19eca33ba961f15bc598aa7983a545ce775b933afc89eb51bcf90" "836257fdd060d4b383240241d", 16, ), q=int( "fb2634f657f82ee6b70553382c4e2ed26b947c97ce2f0016f1b282cf2998184ad0527" "a9eead826dd95fe06b57a025", 16, ), d=int( "402f30f976bc07d15ff0779abff127b20a8b6b1d0024cc2ad8b6762d38f174f81e792" "3b49d80bdbdd80d9675cbc7b2793ec199a0430eb5c84604dacfdb29259ae6a1a44676" "22f0b23d4cb0f5cb1db4b8173c8d9d3e57a74dbd200d2141", 16, ), dmp1=int( "e5e95b7751a6649f199be21bef7a51c9e49821d945b6fc5f538b4a670d8762c375b00" "8e70f31d52b3ea2bd14c3101", 16, ), dmq1=int( "12b85d5843645f72990fcf8d2f58408b34b3a3b9d9078dd527fceb5d2fb7839008092" "dd4aca2a1fb00542801dcef5", 16, ), iqmp=int( "5672740d947f621fc7969e3a44ec26736f3f819863d330e63e9409e139d20753551ac" "c16544dd2bdadb9dee917440", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "1bd085f92237774d34013b477ceebbb2f2feca71118db9b7429341477947e7b1d" "04e8c43ede3c52bb25781af58d4ff81289f301eac62dc3bcd7dafd7a4d5304e9f" "308e766952fbf2b62373e66611fa53189987dbef9f7243dcbbeb25831", 16, ), ), ) RSA_KEY_1024 = RSAPrivateNumbers( p=int( "ea4d9d9a1a068be44b9a5f8f6de0512b2c5ba1fb804a4655babba688e6e890b347c1a" "7426685a929337f513ae4256f0b7e5022d642237f960c5b24b96bee8e51", 16, ), q=int( "cffb33e400d6f08b410d69deb18a85cf0ed88fcca9f32d6f2f66c62143d49aff92c11" "4de937d4f1f62d4635ee89af99ce86d38a2b05310f3857c7b5d586ac8f9", 16, ), d=int( "3d12d46d04ce942fb99be7bf30587b8cd3e21d75a2720e7bda1b867f1d418d91d8b9f" "e1c00181fdde94f2faf33b4e6f800a1b3ae3b972ccb6d5079dcb6c794070ac8306d59" "c00b58b7a9a81122a6b055832de7c72334a07494d8e7c9fbeed2cc37e011d9e6bfc6e" "9bcddbef7f0f5771d9cf82cd4b268c97ec684575c24b6c881", 16, ), dmp1=int( "470f2b11257b7ec9ca34136f487f939e6861920ad8a9ae132a02e74af5dceaa5b4c98" "2949ccb44b67e2bcad2f58674db237fe250e0d62b47b28fa1dfaa603b41", 16, ), dmq1=int( "c616e8317d6b3ae8272973709b80e8397256697ff14ea03389de454f619f99915a617" "45319fefbe154ec1d49441a772c2f63f7d15c478199afc60469bfd0d561", 16, ), iqmp=int( "d15e7c9ad357dfcd5dbdc8427680daf1006761bcfba93a7f86589ad88832a8d564b1c" "d4291a658c96fbaea7ca588795820902d85caebd49c2d731e3fe0243130", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "be5aac07456d990133ebce69c06b48845b972ab1ad9f134bc5683c6b5489b5119" "ede07be3bed0e355d48e0dfab1e4fb5187adf42d7d3fb0401c082acb8481bf17f" "0e871f8877be04c3a1197d40aa260e2e0c48ed3fd2b93dc3fc0867591f67f3cd6" "0a77adee1d68a8c3730a5702485f6ac9ede7f0fd2918e037ee4cc1fc1b4c9", 16, ), ), ) RSA_KEY_1025 = RSAPrivateNumbers( p=int( "18e9bfb7071725da04d31c103fa3563648c69def43a204989214eb57b0c8b299f9ef3" "5dda79a62d8d67fd2a9b69fbd8d0490aa2edc1e111a2b8eb7c737bb691a5", 16, ), q=int( "d8eccaeeb95815f3079d13685f3f72ca2bf2550b349518049421375df88ca9bbb4ba8" "cb0e3502203c9eeae174112509153445d251313e4711a102818c66fcbb7", 16, ), d=int( "fe9ac54910b8b1bc948a03511c54cab206a1d36d50d591124109a48abb7480977ccb0" "47b4d4f1ce7b0805df2d4fa3fe425f49b78535a11f4b87a4eba0638b3340c23d4e6b2" "1ecebe9d5364ea6ead2d47b27836019e6ecb407000a50dc95a8614c9d0031a6e3a524" "d2345cfb76e15c1f69d5ba35bdfb6ec63bcb115a757ef79d9", 16, ), dmp1=int( "18537e81006a68ea76d590cc88e73bd26bc38d09c977959748e5265c0ce21c0b5fd26" "53d975f97ef759b809f791487a8fff1264bf561627fb4527a3f0bbb72c85", 16, ), dmq1=int( "c807eac5a1f1e1239f04b04dd16eff9a00565127a91046fa89e1eb5d6301cace85447" "4d1f47b0332bd35b4214b66e9166953241538f761f30d969272ee214f17", 16, ), iqmp=int( "133aa74dd41fe70fa244f07d0c4091a22f8c8f0134fe6aea9ec8b55383b758fefe358" "2beec36eca91715eee7d21931f24fa9e97e8e3a50f9cd0f731574a5eafcc", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "151c44fed756370fb2d4a0e6ec7dcac84068ca459b6aaf22daf902dca72c77563" "bf276fe3523f38f5ddaf3ea9aa88486a9d8760ff732489075862bee0e599de5c5" "f509b4519f4f446521bad15cd279a498fe1e89107ce0d237e3103d7c5eb801666" "42e2924b152aebff97b71fdd2d68ebb45034cc784e2e822ff6d1edf98af3f3", 16, ), ), ) RSA_KEY_1026 = RSAPrivateNumbers( p=int( "1fcbfb8719c5bdb5fe3eb0937c76bb096e750b9442dfe31d6a877a13aed2a6a4e9f79" "40f815f1c307dd6bc2b4b207bb6fe5be3a15bd2875a957492ce197cdedb1", 16, ), q=int( "1f704a0f6b8966dd52582fdc08227dd3dbaeaa781918b41144b692711091b4ca4eb62" "985c3513853828ce8739001dfba9a9a7f1a23cbcaf74280be925e2e7b50d", 16, ), d=int( "c67975e35a1d0d0b3ebfca736262cf91990cb31cf4ac473c0c816f3bc2720bcba2475" "e8d0de8535d257816c0fc53afc1b597eada8b229069d6ef2792fc23f59ffb4dc6c3d9" "0a3c462082025a4cba7561296dd3d8870c4440d779406f00879afe2c681e7f5ee055e" "ff829e6e55883ec20830c72300762e6e3a333d94b4dbe4501", 16, ), dmp1=int( "314730ca7066c55d086a9fbdf3670ef7cef816b9efea8b514b882ae9d647217cf41d7" "e9989269dc9893d02e315cb81f058c49043c2cac47adea58bdf5e20e841", 16, ), dmq1=int( "1da28a9d687ff7cfeebc2439240de7505a8796376968c8ec723a2b669af8ce53d9c88" "af18540bd78b2da429014923fa435f22697ac60812d7ca9c17a557f394cd", 16, ), iqmp=int( "727947b57b8a36acd85180522f1b381bce5fdbd962743b3b14af98a36771a80f58ddd" "62675d72a5935190da9ddc6fd6d6d5e9e9f805a2e92ab8d56b820493cdf", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "3e7a5e6483e55eb8b723f9c46732d21b0af9e06a4a1099962d67a35ee3f62e312" "9cfae6ab0446da18e26f33e1d753bc1cc03585c100cf0ab5ef056695706fc8b0c" "9c710cd73fe6e5beda70f515a96fabd3cc5ac49efcb2594b220ff3b603fcd927f" "6a0838ef04bf52f3ed9eab801f09e5aed1613ddeb946ed0fbb02060b3a36fd", 16, ), ), ) RSA_KEY_1027 = RSAPrivateNumbers( p=int( "30135e54cfb072c3d3eaf2000f3ed92ceafc85efc867b9d4bf5612f2978c432040093" "4829f741c0f002b54af2a4433ff872b6321ef00ff1e72cba4e0ced937c7d", 16, ), q=int( "1d01a8aead6f86b78c875f18edd74214e06535d65da054aeb8e1851d6f3319b4fb6d8" "6b01e07d19f8261a1ded7dc08116345509ab9790e3f13e65c037e5bb7e27", 16, ), d=int( "21cf4477df79561c7818731da9b9c88cd793f1b4b8e175bd0bfb9c0941a4dc648ecf1" "6d96b35166c9ea116f4c2eb33ce1c231e641a37c25e54c17027bdec08ddafcb83642e" "795a0dd133155ccc5eed03b6e745930d9ac7cfe91f9045149f33295af03a2198c660f" "08d8150d13ce0e2eb02f21ac75d63b55822f77bd5be8d07619", 16, ), dmp1=int( "173fb695931e845179511c18b546b265cb79b517c135902377281bdf9f34205e1f399" "4603ad63e9f6e7885ea73a929f03fa0d6bed943051ce76cddde2d89d434d", 16, ), dmq1=int( "10956b387b2621327da0c3c8ffea2af8be967ee25163222746c28115a406e632a7f12" "5a9397224f1fa5c116cd3a313e5c508d31db2deb83b6e082d213e33f7fcf", 16, ), iqmp=int( "234f833949f2c0d797bc6a0e906331e17394fa8fbc8449395766d3a8d222cf6167c48" "8e7fe1fe9721d3e3b699a595c8e6f063d92bd840dbc84d763b2b37002109", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "57281707d7f9b1369c117911758980e32c05b133ac52c225bcf68b79157ff47ea" "0a5ae9f579ef1fd7e42937f921eb3123c4a045cc47a2159fbbf904783e654954c" "42294c30a95c15db7c7b91f136244e548f62474b137087346c5522e54f226f49d" "6c93bc58cb39972e41bde452bb3ae9d60eb93e5e1ce91d222138d9890c7d0b", 16, ), ), ) RSA_KEY_1028 = RSAPrivateNumbers( p=int( "359d17378fae8e9160097daee78a206bd52efe1b757c12a6da8026cc4fc4bb2620f12" "b8254f4db6aed8228be8ee3e5a27ec7d31048602f01edb00befd209e8c75", 16, ), q=int( "33a2e70b93d397c46e63b273dcd3dcfa64291342a6ce896e1ec8f1c0edc44106550f3" "c06e7d3ca6ea29eccf3f6ab5ac6235c265313d6ea8e8767e6a343f616581", 16, ), d=int( "880640088d331aa5c0f4cf2887809a420a2bc086e671e6ffe4e47a8c80792c038a314" "9a8e45ef9a72816ab45b36e3af6800351067a6b2751843d4232413146bb575491463a" "8addd06ce3d1bcf7028ec6c5d938c545a20f0a40214b5c574ca7e840062b2b5f8ed49" "4b144bb2113677c4b10519177fee1d4f5fb8a1c159b0b47c01", 16, ), dmp1=int( "75f8c52dad2c1cea26b8bba63236ee4059489e3d2db766136098bcc6b67fde8f77cd3" "640035107bfb1ffc6480983cfb84fe0c3be008424ebc968a7db7e01f005", 16, ), dmq1=int( "3893c59469e4ede5cd0e6ff9837ca023ba9b46ff40c60ccf1bec10f7d38db5b1ba817" "6c41a3f750ec4203b711455aca06d1e0adffc5cffa42bb92c7cb77a6c01", 16, ), iqmp=int( "ad32aafae3c962ac25459856dc8ef1f733c3df697eced29773677f435d186cf759d1a" "5563dd421ec47b4d7e7f12f29647c615166d9c43fc49001b29089344f65", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "ad0696bef71597eb3a88e135d83c596930cac73868fbd7e6b2d64f34eea5c28cc" "e3510c68073954d3ba4deb38643e7a820a4cf06e75f7f82eca545d412bd637819" "45c28d406e95a6cced5ae924a8bfa4f3def3e0250d91246c269ec40c89c93a85a" "cd3770ba4d2e774732f43abe94394de43fb57f93ca25f7a59d75d400a3eff5", 16, ), ), ) RSA_KEY_1029 = RSAPrivateNumbers( p=int( "66f33e513c0b6b6adbf041d037d9b1f0ebf8de52812a3ac397a963d3f71ba64b3ad04" "e4d4b5e377e6fa22febcac292c907dc8dcfe64c807fd9a7e3a698850d983", 16, ), q=int( "3b47a89a19022461dcc2d3c05b501ee76955e8ce3cf821beb4afa85a21a26fd7203db" "deb8941f1c60ada39fd6799f6c07eb8554113f1020460ec40e93cd5f6b21", 16, ), d=int( "280c42af8b1c719821f2f6e2bf5f3dd53c81b1f3e1e7cc4fce6e2f830132da0665bde" "bc1e307106b112b52ad5754867dddd028116cf4471bc14a58696b99524b1ad8f05b31" "cf47256e54ab4399b6a073b2c0452441438dfddf47f3334c13c5ec86ece4d33409056" "139328fafa992fb5f5156f25f9b21d3e1c37f156d963d97e41", 16, ), dmp1=int( "198c7402a4ec10944c50ab8488d7b5991c767e75eb2817bd427dff10335ae141fa2e8" "7c016dc22d975cac229b9ffdf7d943ddfd3a04b8bf82e83c3b32c5698b11", 16, ), dmq1=int( "15fd30c7687b68ef7c2a30cdeb913ec56c4757c218cf9a04d995470797ee5f3a17558" "fbb6d00af245d2631d893b382da48a72bc8a613024289895952ab245b0c1", 16, ), iqmp=int( "4f8fde17e84557a3f4e242d889e898545ab55a1a8e075c9bb0220173ccffe84659abe" "a235104f82e32750309389d4a52af57dbb6e48d831917b6efeb190176570", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "17d6e0a09aa5b2d003e51f43b9c37ffde74688f5e3b709fd02ef375cb6b8d15e2" "99a9f74981c3eeaaf947d5c2d64a1a80f5c5108a49a715c3f7be95a016b8d3300" "965ead4a4df76e642d761526803e9434d4ec61b10cb50526d4dcaef02593085de" "d8c331c1b27b200a45628403065efcb2c0a0ca1f75d648d40a007fbfbf2cae3", 16, ), ), ) RSA_KEY_1030 = RSAPrivateNumbers( p=int( "6f4ac8a8172ef1154cf7f80b5e91de723c35a4c512860bfdbafcc3b994a2384bf7796" "3a2dd0480c7e04d5d418629651a0de8979add6f47b23da14c27a682b69c9", 16, ), q=int( "65a9f83e07dea5b633e036a9dccfb32c46bf53c81040a19c574c3680838fc6d28bde9" "55c0ff18b30481d4ab52a9f5e9f835459b1348bbb563ad90b15a682fadb3", 16, ), d=int( "290db707b3e1a96445ae8ea93af55a9f211a54ebe52995c2eb28085d1e3f09c986e73" "a00010c8e4785786eaaa5c85b98444bd93b585d0c24363ccc22c482e150a3fd900176" "86968e4fa20423ae72823b0049defceccb39bb34aa4ef64e6b14463b76d6a871c859e" "37285455b94b8e1527d1525b1682ac6f7c8fd79d576c55318c1", 16, ), dmp1=int( "23f7fa84010225dea98297032dac5d45745a2e07976605681acfe87e0920a8ab3caf5" "9d9602f3d63dc0584f75161fd8fff20c626c21c5e02a85282276a74628a9", 16, ), dmq1=int( "18ebb657765464a8aa44bf019a882b72a2110a77934c54915f70e6375088b10331982" "962bce1c7edd8ef9d3d95aa2566d2a99da6ebab890b95375919408d00f33", 16, ), iqmp=int( "3d59d208743c74054151002d77dcdfc55af3d41357e89af88d7eef2767be54c290255" "9258d85cf2a1083c035a33e65a1ca46dc8b706847c1c6434cef7b71a9dae", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "2c326574320818a6a8cb6b3328e2d6c1ba2a3f09b6eb2bc543c03ab18eb5efdaa" "8fcdbb6b4e12168304f587999f9d96a421fc80cb933a490df85d25883e6a88750" "d6bd8b3d4117251eee8f45e70e6daac7dbbd92a9103c623a09355cf00e3f16168" "e38b9c4cb5b368deabbed8df466bc6835eaba959bc1c2f4ec32a09840becc8b", 16, ), ), ) RSA_KEY_1031 = RSAPrivateNumbers( p=int( "c0958c08e50137db989fb7cc93abf1984543e2f955d4f43fb2967f40105e79274c852" "293fa06ce63ca8436155e475ed6d1f73fea4c8e2516cc79153e3dc83e897", 16, ), q=int( "78cae354ea5d6862e5d71d20273b7cddb8cdfab25478fe865180676b04250685c4d03" "30c216574f7876a7b12dfe69f1661d3b0cea6c2c0dcfb84050f817afc28d", 16, ), d=int( "1d55cc02b17a5d25bfb39f2bc58389004d0d7255051507f75ef347cdf5519d1a00f4b" "d235ce4171bfab7bdb7a6dcfae1cf41433fb7da5923cc84f15a675c0b83492c95dd99" "a9fc157aea352ffdcbb5d59dbc3662171d5838d69f130678ee27841a79ef64f679ce9" "3821fa69c03f502244c04b737edad8967def8022a144feaab29", 16, ), dmp1=int( "5b1c2504ec3a984f86b4414342b5bcf59a0754f13adf25b2a0edbc43f5ba8c3cc061d" "80b03e5866d059968f0d10a98deaeb4f7830436d76b22cf41f2914e13eff", 16, ), dmq1=int( "6c361e1819691ab5d67fb2a8f65c958d301cdf24d90617c68ec7005edfb4a7b638cde" "79d4b61cfba5c86e8c0ccf296bc7f611cb8d4ae0e072a0f68552ec2d5995", 16, ), iqmp=int( "b7d61945fdc8b92e075b15554bab507fa8a18edd0a18da373ec6c766c71eece61136a" "84b90b6d01741d40458bfad17a9bee9d4a8ed2f6e270782dc3bf5d58b56e", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "5adebaa926ea11fb635879487fdd53dcfbb391a11ac7279bb3b4877c9b811370a" "9f73da0690581691626d8a7cf5d972cced9c2091ccf999024b23b4e6dc6d99f80" "a454737dec0caffaebe4a3fac250ed02079267c8f39620b5ae3e125ca35338522" "dc9353ecac19cb2fe3b9e3a9291619dbb1ea3a7c388e9ee6469fbf5fb22892b", 16, ), ), ) RSA_KEY_1536 = RSAPrivateNumbers( p=int( "f1a65fa4e2aa6e7e2b560251e8a4cd65b625ad9f04f6571785782d1c213d91c961637" "0c572f2783caf2899f7fb690cf99a0184257fbd4b071b212c88fb348279a5387e61f1" "17e9c62980c45ea863fa9292087c0f66ecdcde6443d5a37268bf71", 16, ), q=int( "e54c2cbc3839b1da6ae6fea45038d986d6f523a3ae76051ba20583aab711ea5965cf5" "3cf54128cc9573f7460bba0fd6758a57aaf240c391790fb38ab473d83ef735510c53d" "1d10c31782e8fd7da42615e33565745c30a5e6ceb2a3ae0666cc35", 16, ), d=int( "7bcad87e23da2cb2a8c328883fabce06e1f8e9b776c8bf253ad9884e6200e3bd9bd3b" "a2cbe87d3854527bf005ba5d878c5b0fa20cfb0a2a42884ae95ca12bf7304285e9214" "5e992f7006c7c0ae839ad550da495b143bec0f4806c7f44caed45f3ccc6dc44cfaf30" "7abdb757e3d28e41c2d21366835c0a41e50a95af490ac03af061d2feb36ac0afb87be" "a13fb0f0c5a410727ebedb286c77f9469473fae27ef2c836da6071ef7efc1647f1233" "4009a89eecb09a8287abc8c2afd1ddd9a1b0641", 16, ), dmp1=int( "a845366cd6f9df1f34861bef7594ed025aa83a12759e245f58adaa9bdff9c3befb760" "75d3701e90038e888eec9bf092df63400152cb25fc07effc6c74c45f0654ccbde15cd" "90dd5504298a946fa5cf22a956072da27a6602e6c6e5c97f2db9c1", 16, ), dmq1=int( "28b0c1e78cdac03310717992d321a3888830ec6829978c048156152d805b4f8919c61" "70b5dd204e5ddf3c6c53bc6aff15d0bd09faff7f351b94abb9db980b31f150a6d7573" "08eb66938f89a5225cb4dd817a824c89e7a0293b58fc2eefb7e259", 16, ), iqmp=int( "6c1536c0e16e42a094b6caaf50231ba81916871497d73dcbbbd4bdeb9e60cae0413b3" "8143b5d680275b29ed7769fe5577e4f9b3647ddb064941120914526d64d80016d2eb7" "dc362da7c569623157f3d7cff8347f11494bf5c048d77e28d3f515", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "d871bb2d27672e54fc62c4680148cbdf848438da804e2c48b5a9c9f9daf6cc6e8" "ea7d2296f25064537a9a542aef3dd449ea75774238d4da02c353d1bee70013dcc" "c248ceef4050160705c188043c8559bf6dbfb6c4bb382eda4e9547575a8227d5b" "3c0a7088391364cf9f018d8bea053b226ec65e8cdbeaf48a071d0074860a734b1" "cb7d2146d43014b20776dea42f7853a54690e6cbbf3331a9f43763cfe2a51c329" "3bea3b2eebec0d8e43eb317a443afe541107d886e5243c096091543ae65", 16, ), ), ) RSA_KEY_2048 = RSAPrivateNumbers( p=int( "e14202e58c5f7446648d75e5dc465781f661f6b73000c080368afcfb21377f4ef19da" "845d4ef9bc6b151f6d9f34629103f2e57615f9ba0a3a2fbb035069e1d63b4bb0e78ad" "dad1ec3c6f87e25c877a1c4c1972098e09158ef7b9bc163852a18d44a70b7b31a03dc" "2614fd9ab7bf002cba79054544af3bfbdb6aed06c7b24e6ab", 16, ), q=int( "dbe2bea1ff92599bd19f9d045d6ce62250c05cfeac5117f3cf3e626cb696e3d886379" "557d5a57b7476f9cf886accfd40508a805fe3b45a78e1a8a125e516cda91640ee6398" "ec5a39d3e6b177ef12ab00d07907a17640e4ca454fd8487da3c4ffa0d5c2a5edb1221" "1c8e33c7ee9fa6753771fd111ec04b8317f86693eb2928c89", 16, ), d=int( "aef17f80f2653bc30539f26dd4c82ed6abc1d1b53bc0abcdbee47e9a8ab433abde865" "9fcfae1244d22de6ad333c95aee7d47f30b6815065ac3322744d3ea75058002cd1b29" "3141ee2a6dc682342432707080071bd2131d6262cab07871c28aa5238b87173fb78c3" "7f9c7bcd18c12e8971bb77fd9fa3e0792fec18d8d9bed0b03ba02b263606f24dbace1" "c8263ce2802a769a090e993fd49abc50c3d3c78c29bee2de0c98055d2f102f1c5684b" "8dddee611d5205392d8e8dd61a15bf44680972a87f040a611a149271eeb2573f8bf6f" "627dfa70e77def2ee6584914fa0290e041349ea0999cdff3e493365885b906cbcf195" "843345809a85098cca90fea014a21", 16, ), dmp1=int( "9ba56522ffcfa5244eae805c87cc0303461f82be29691b9a7c15a5a050df6c143c575" "7c288d3d7ab7f32c782e9d9fcddc10a604e6425c0e5d0e46069035d95a923646d276d" "d9d95b8696fa29ab0de18e53f6f119310f8dd9efca62f0679291166fed8cbd5f18fe1" "3a5f1ead1d71d8c90f40382818c18c8d069be793dbc094f69", 16, ), dmq1=int( "a8d4a0aaa2212ccc875796a81353da1fdf00d46676c88d2b96a4bfcdd924622d8e607" "f3ac1c01dda7ebfb0a97dd7875c2a7b2db6728fb827b89c519f5716fb3228f4121647" "04b30253c17de2289e9cce3343baa82eb404f789e094a094577a9b0c5314f1725fdf5" "8e87611ad20da331bd30b8aebc7dc97d0e9a9ba8579772c9", 16, ), iqmp=int( "17bd5ef638c49440d1853acb3fa63a5aca28cb7f94ed350db7001c8445da8943866a7" "0936e1ee2716c98b484e357cc054d82fbbd98d42f880695d38a1dd4eb096f629b9417" "aca47e6de5da9f34e60e8a0ffd7e35be74deeef67298d94b3e0db73fc4b7a4cb360c8" "9d2117a0bfd9434d37dc7c027d6b01e5295c875015510917d", 16, ), public_numbers=RSAPublicNumbers( e=65537, n=int( "c17afc7e77474caa5aa83036158a3ffbf7b5216851ba2230e5d6abfcc1c6cfef5" "9e923ea1330bc593b73802ab608a6e4a3306523a3116ba5aa3966145174e13b6c" "49e9b78062e449d72efb10fd49e91fa08b96d051e782e9f5abc5b5a6f7984827a" "db8e73da00f22b2efdcdb76eab46edad98ed65662743fdc6c0e336a5d0cdbaa7d" "c29e53635e24c87a5b2c4215968063cdeb68a972babbc1e3cff00fb9a80e372a4" "d0c2c920d1e8cee333ce470dc2e8145adb05bf29aee1d24f141e8cc784989c587" "fc6fbacd979f3f2163c1d7299b365bc72ffe2848e967aed1e48dcc515b3a50ed4" "de04fd053846ca10a223b10cc841cc80fdebee44f3114c13e886af583", 16, ), ), ) RSA_KEY_2048_ALT = RSAPrivateNumbers( d=int( "7522768467449591813737881904131688860626637897199391200040629" "8641018746450502628484395471408986929218353894683769457466923" "3079369551423094451013669595729568593462009746342148367797495" "5529909313614750246672441810743580455199636293179539903480635" "3091286716112931976896334411287175213124504134181121011488550" "5290054443979198998564749640800633368957384058700741073997703" "8877364695937023906368630297588990131009278072614118207348356" "4640244134189285070202534488517371577359510236833464698189075" "5160693085297816063285814039518178249628112908466649245545732" "5791532385553960363601827996980725025898649392004494256400884" "092073" ), dmp1=int( "5847872614112935747739644055317429405973942336206460017493394" "9737607778799766591021036792892472774720417920838206576785118" "8889624058962939702950175807073343659386156232294197300491647" "1029508414050591959344812347424476498076532682798598325230069" "0925827594762920534235575029199380552228825468180187156871965" "973" ), dmq1=int( "2949536259161239302081155875068405238857801001054083407704879" "8210876832264504685327766351157044892283801611558399025326793" "4131638001934454489864437565651739832511702151461257267169691" "6611992398459006200708626815153304591390855807749769768978152" "9854112656599931724820610358669306523835327459478374630794532" "167" ), iqmp=int( "7331180989818931535458916053540252830484856703208982675535284" "4613815808798190559315018094080936347757336989616401164752221" "8101156529898067044923499386460167055405998646366011838018441" "3678947694258190172377716154009305082091341215866326061721180" "3836418654472188816187630316821692982783286322262994892003058" "782" ), p=int( "1460007723851883695617573533155574746587863843382715314919865" "2434108956187429726002840717317310431378483921058946835896252" "7109559207437158778332364464259678946305487699031865937075508" "8616612925453842458055546540240601585731206561647892336916583" "0023641764106581040198845259766246869529221084602380669333021" "0819" ), q=int( "1433897765867889178402883410610177836503402597775250087462018" "4617952933433119527945447840336616357136736935069377619782227" "2822380830300262175671282877680573202309319960687756231128996" "9764855320953993690199846269451095044922353809602378616938811" "7513900906279873343591486841303392490561500301994171338761080" "4439" ), public_numbers=RSAPublicNumbers( e=65537, n=int( "209350181338107812610165420955871971489973659392253291327" "839812910252466502190690572476688311285621239204212139711" "207388949164851984253143698667018532039612470954223918242" "145976986600705122576087630525229796950722166468064721258" "490916138706756006902066136471049807637157890128560592039" "941717275079733754782848729566190631725183735944031456237" "089928120178187552521649483240599003240074352860189285952" "078970127554801074176375499583703254849309993132931268013" "715070507278514207864914944621214574162116786377990456375" "964817771730371110612100247262908550409785456157505694419" "00451152778245269283276012328748538414051025541" ), ), ) RSA_KEY_CORRUPTED = b""" -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAuYE4k09MAsi1yjMrXekMe6sT9bEt3ko47dnmN8YBgO8DiiCc 226TnQPvuX3FGxU+Y1zTJpcvVL3L37UOvh4CSb9zKyrFK9/x/UcCfK3Eq8JdS98P CVeGpkp5E+vwIKY72rc1RSSSCs0PtFdYbSn4trwf5BjPxIqXwIOS3R7zC7cLPHY4 YdsM4gLGVOP17uXJr/MPoAtWTBVm5zx4bHm6Xclzgf86sbPdL3LxNs0fz4HqJZgA 6EUtyl6Qypq2LjXbdmm2i3vC+MxW6nEPItPqgComhq0zBmVonsiEO87rEtD548Yq DKvxwHhlcODcVkAYebJ+W5L6PPJBNYA3t5wYyQIDAQABAoIBAAbHkg5msftpGt5Z Vb3yUuepem7hWTF5YFlIRw5l2wNcURNpbswEhOVNJbuG+KCple7Dw4TuDmhHs/zr BRqpDhXldhrUtb2uc3ihqWiVFJbieqE4jUbGvMJusvtXXeDwU6wGWzV/V4qndCrk u4PGypk4Cbbq6ZP2oufPryQ3D4Ff1TA06RSWdP3Cg673VqwLtkXwsRDhymAviiqU hxQg8bRNiD7mYoUKyLVeV7YRDLTBugfiFmy54yC99NJclLkYmzCgRt1EuoW0Hixx EIQFEOLftgpc+sKpbbiOileMsc/stytHXXqfgozhBxDNeSzdNYfwEpkLJpLZSUNV EhS4X1cCgYEAz+7DkXksWw9zLqYniMIcvcBnHQcy3Anqbcu8Zbw+I9wOwzNt44Bo f88i2idvWvMsRq/LX4WD4jjPB4Z3wAzGBCq+2cy0GrWByMu+VbpwCrntRBkS5huY IIf1nr1+BuySNt8TL6nZNKz0D8+5c8wT+VbVdPH//4MzfDrK81PPnesCgYEA5GMy ji4l+8zO33LFMlWQGYgfSMd4jGMQD0VCvfhlosK0Py0AfZj/GKEGHduo/37KVVvb 6XdJqYgB7OxPmdEqbMGeYPKv7pKkG1jXRuEtmXXJ9hS1t0oIvXJLHJnQrOOoRRAR +xJZbI7WjemY+ZCMOAPT1tm97pxjs81WgSJ6ExsCgYEAze5ADfEeNskkYAz6lnz4 jgzhkmQwwK+pVzgxy9g8brNkg3qJ2Iix9fKlJ71qkX7IWPF9z4qhxQhSMbfBHZkI +9OB1J7huJoOgVkXliwIbvcYvxq+Fts5XO6KGb699AmT/XgMvmXO0lbAGLC3kLGL DqQrH3kU+m9sLBrmKPrWYiUCgYEA3/8etW4zmMvd1jAFkoFyzGfCbyocZGxAcwm2 FQYMAN8/03p6sbSd9XTwv9YR4Uxke+WURkjVuW2IneuDgtQv6QCFKob74Jx4Uc4H jiAKDioFg9H6C6OUAOKZIpsFnJvIDLxfNkVf6WYKrrL+cz6/F61BVsbGTsGZ094/ ynWbDyMCgYEAh44C/wkebe0zz/llG+KTRGENsw1c7+pm0/l3wPYAlH02ewbyRjFf OKPfyyBtBkoD5rG3IbLyPxsbd3wWwyUzSYq02qRJq43XqyMZhRnNlYhEnNu/Gr5H sN1f13zqkKoRxxbIjyh4RDYlAv4Sehk27z2Q3gBe9bI5xKkoQ/VfF2w= -----END RSA PRIVATE KEY----- """ cryptography-43.0.0/tests/hazmat/primitives/test_aead.py010064400017510000177000001041461464676315000216630ustar 00000000000000# 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. import binascii import mmap import os import sys import pytest from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, AESGCMSIV, AESOCB3, AESSIV, ChaCha20Poly1305, ) from ...utils import ( load_nist_ccm_vectors, load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) from .utils import _load_all_params def _aead_supported(cls): try: cls(b"0" * 32) return True except UnsupportedAlgorithm: return False def large_mmap(): return mmap.mmap(-1, 2**32, prot=mmap.PROT_READ) @pytest.mark.skipif( _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL without ChaCha20Poly1305 support", ) def test_chacha20poly1305_unsupported_on_older_openssl(backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): ChaCha20Poly1305(ChaCha20Poly1305.generate_key()) @pytest.mark.skipif( not _aead_supported(ChaCha20Poly1305), reason="Does not support ChaCha20Poly1305", ) class TestChaCha20Poly1305: @pytest.mark.skipif( sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) nonce = b"0" * 12 large_data = large_mmap() with pytest.raises(OverflowError): chacha.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): chacha.encrypt(nonce, b"", large_data) def test_generate_key(self): key = ChaCha20Poly1305.generate_key() assert len(key) == 32 def test_bad_key(self, backend): with pytest.raises(TypeError): ChaCha20Poly1305(object()) # type:ignore[arg-type] with pytest.raises(ValueError): ChaCha20Poly1305(b"0" * 31) @pytest.mark.parametrize( ("nonce", "data", "associated_data"), [ [object(), b"data", b""], [b"0" * 12, object(), b""], [b"0" * 12, b"data", object()], ], ) def test_params_not_bytes_encrypt( self, nonce, data, associated_data, backend ): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) with pytest.raises(TypeError): chacha.encrypt(nonce, data, associated_data) with pytest.raises(TypeError): chacha.decrypt(nonce, data, associated_data) def test_nonce_not_12_bytes(self, backend): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) with pytest.raises(ValueError): chacha.encrypt(b"00", b"hello", b"") with pytest.raises(ValueError): chacha.decrypt(b"00", b"hello", b"") def test_decrypt_data_too_short(self, backend): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) with pytest.raises(InvalidTag): chacha.decrypt(b"0" * 12, b"0", None) def test_associated_data_none_equal_to_empty_bytestring(self, backend): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) nonce = os.urandom(12) ct1 = chacha.encrypt(nonce, b"some_data", None) ct2 = chacha.encrypt(nonce, b"some_data", b"") assert ct1 == ct2 pt1 = chacha.decrypt(nonce, ct1, None) pt2 = chacha.decrypt(nonce, ct2, b"") assert pt1 == pt2 def test_openssl_vectors(self, subtests, backend): vectors = load_vectors_from_file( os.path.join("ciphers", "ChaCha20Poly1305", "openssl.txt"), load_nist_vectors, ) for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) nonce = binascii.unhexlify(vector["iv"]) aad = binascii.unhexlify(vector["aad"]) tag = binascii.unhexlify(vector["tag"]) pt = binascii.unhexlify(vector["plaintext"]) ct = binascii.unhexlify(vector["ciphertext"]) chacha = ChaCha20Poly1305(key) if vector.get("result") == b"CIPHERFINAL_ERROR": with pytest.raises(InvalidTag): chacha.decrypt(nonce, ct + tag, aad) else: computed_pt = chacha.decrypt(nonce, ct + tag, aad) assert computed_pt == pt computed_ct = chacha.encrypt(nonce, pt, aad) assert computed_ct == ct + tag def test_boringssl_vectors(self, subtests, backend): vectors = load_vectors_from_file( os.path.join("ciphers", "ChaCha20Poly1305", "boringssl.txt"), load_nist_vectors, ) for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) nonce = binascii.unhexlify(vector["nonce"]) if vector["ad"].startswith(b'"'): aad = vector["ad"][1:-1] else: aad = binascii.unhexlify(vector["ad"]) tag = binascii.unhexlify(vector["tag"]) if vector["in"].startswith(b'"'): pt = vector["in"][1:-1] else: pt = binascii.unhexlify(vector["in"]) ct = binascii.unhexlify(vector["ct"].strip(b'"')) chacha = ChaCha20Poly1305(key) computed_pt = chacha.decrypt(nonce, ct + tag, aad) assert computed_pt == pt computed_ct = chacha.encrypt(nonce, pt, aad) assert computed_ct == ct + tag def test_buffer_protocol(self, backend): key = ChaCha20Poly1305.generate_key() chacha = ChaCha20Poly1305(key) pt = b"encrypt me" ad = b"additional" nonce = os.urandom(12) ct = chacha.encrypt(nonce, pt, ad) computed_pt = chacha.decrypt(nonce, ct, ad) assert computed_pt == pt chacha2 = ChaCha20Poly1305(bytearray(key)) ct2 = chacha2.encrypt(bytearray(nonce), pt, ad) assert ct2 == ct computed_pt2 = chacha2.decrypt(bytearray(nonce), ct2, ad) assert computed_pt2 == pt @pytest.mark.skipif( not _aead_supported(AESCCM), reason="Does not support AESCCM", ) class TestAESCCM: @pytest.mark.skipif( sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESCCM.generate_key(128) aesccm = AESCCM(key) nonce = b"0" * 12 large_data = large_mmap() with pytest.raises(OverflowError): aesccm.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): aesccm.encrypt(nonce, b"", large_data) def test_default_tag_length(self, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) nonce = os.urandom(12) pt = b"hello" ct = aesccm.encrypt(nonce, pt, None) assert len(ct) == len(pt) + 16 def test_invalid_tag_length(self, backend): key = AESCCM.generate_key(128) with pytest.raises(ValueError): AESCCM(key, tag_length=7) with pytest.raises(ValueError): AESCCM(key, tag_length=2) with pytest.raises(TypeError): AESCCM(key, tag_length="notanint") # type:ignore[arg-type] def test_invalid_nonce_length(self, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) pt = b"hello" nonce = os.urandom(14) with pytest.raises(ValueError): aesccm.encrypt(nonce, pt, None) with pytest.raises(ValueError): aesccm.encrypt(nonce[:6], pt, None) def test_vectors(self, subtests, backend): vectors = _load_all_params( os.path.join("ciphers", "AES", "CCM"), [ "DVPT128.rsp", "DVPT192.rsp", "DVPT256.rsp", "VADT128.rsp", "VADT192.rsp", "VADT256.rsp", "VNT128.rsp", "VNT192.rsp", "VNT256.rsp", "VPT128.rsp", "VPT192.rsp", "VPT256.rsp", ], load_nist_ccm_vectors, ) for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) nonce = binascii.unhexlify(vector["nonce"]) adata = binascii.unhexlify(vector["adata"])[: vector["alen"]] ct = binascii.unhexlify(vector["ct"]) pt = binascii.unhexlify(vector["payload"])[: vector["plen"]] aesccm = AESCCM(key, vector["tlen"]) if vector.get("fail"): with pytest.raises(InvalidTag): aesccm.decrypt(nonce, ct, adata) else: computed_pt = aesccm.decrypt(nonce, ct, adata) assert computed_pt == pt assert aesccm.encrypt(nonce, pt, adata) == ct def test_roundtrip(self, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) pt = b"encrypt me" ad = b"additional" nonce = os.urandom(12) ct = aesccm.encrypt(nonce, pt, ad) computed_pt = aesccm.decrypt(nonce, ct, ad) assert computed_pt == pt def test_nonce_too_long(self, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) pt = b"encrypt me" * 6600 # pt can be no more than 65536 bytes when nonce is 13 bytes nonce = os.urandom(13) with pytest.raises(ValueError): aesccm.encrypt(nonce, pt, None) with pytest.raises(ValueError): aesccm.decrypt(nonce, pt, None) @pytest.mark.parametrize( ("nonce", "data", "associated_data"), [ [object(), b"data", b""], [b"0" * 12, object(), b""], [b"0" * 12, b"data", object()], ], ) def test_params_not_bytes(self, nonce, data, associated_data, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) with pytest.raises(TypeError): aesccm.encrypt(nonce, data, associated_data) def test_bad_key(self, backend): with pytest.raises(TypeError): AESCCM(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESCCM(b"0" * 31) def test_bad_generate_key(self, backend): with pytest.raises(TypeError): AESCCM.generate_key(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESCCM.generate_key(129) def test_associated_data_none_equal_to_empty_bytestring(self, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) nonce = os.urandom(12) ct1 = aesccm.encrypt(nonce, b"some_data", None) ct2 = aesccm.encrypt(nonce, b"some_data", b"") assert ct1 == ct2 pt1 = aesccm.decrypt(nonce, ct1, None) pt2 = aesccm.decrypt(nonce, ct2, b"") assert pt1 == pt2 def test_decrypt_data_too_short(self, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) with pytest.raises(InvalidTag): aesccm.decrypt(b"0" * 12, b"0", None) def test_buffer_protocol(self, backend): key = AESCCM.generate_key(128) aesccm = AESCCM(key) pt = b"encrypt me" ad = b"additional" nonce = os.urandom(12) ct = aesccm.encrypt(nonce, pt, ad) computed_pt = aesccm.decrypt(nonce, ct, ad) assert computed_pt == pt aesccm2 = AESCCM(bytearray(key)) ct2 = aesccm2.encrypt(bytearray(nonce), pt, ad) assert ct2 == ct computed_pt2 = aesccm2.decrypt(bytearray(nonce), ct2, ad) assert computed_pt2 == pt def _load_gcm_vectors(): vectors = _load_all_params( os.path.join("ciphers", "AES", "GCM"), [ "gcmDecrypt128.rsp", "gcmDecrypt192.rsp", "gcmDecrypt256.rsp", "gcmEncryptExtIV128.rsp", "gcmEncryptExtIV192.rsp", "gcmEncryptExtIV256.rsp", ], load_nist_vectors, ) return [x for x in vectors if len(x["tag"]) == 32 and len(x["iv"]) >= 16] class TestAESGCM: @pytest.mark.skipif( sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESGCM.generate_key(128) aesgcm = AESGCM(key) nonce = b"0" * 12 large_data = large_mmap() with pytest.raises(OverflowError): aesgcm.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): aesgcm.encrypt(nonce, b"", large_data) def test_decrypt_data_too_short(self): key = AESGCM.generate_key(128) aesgcm = AESGCM(key) with pytest.raises(InvalidTag): aesgcm.decrypt(b"0" * 12, b"0", None) def test_vectors(self, backend, subtests): vectors = _load_gcm_vectors() for vector in vectors: with subtests.test(): nonce = binascii.unhexlify(vector["iv"]) if backend._fips_enabled and len(nonce) != 12: # Red Hat disables non-96-bit IV support as part of its # FIPS patches. pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") key = binascii.unhexlify(vector["key"]) aad = binascii.unhexlify(vector["aad"]) ct = binascii.unhexlify(vector["ct"]) pt = binascii.unhexlify(vector.get("pt", b"")) tag = binascii.unhexlify(vector["tag"]) aesgcm = AESGCM(key) if vector.get("fail") is True: with pytest.raises(InvalidTag): aesgcm.decrypt(nonce, ct + tag, aad) else: computed_ct = aesgcm.encrypt(nonce, pt, aad) assert computed_ct[:-16] == ct assert computed_ct[-16:] == tag computed_pt = aesgcm.decrypt(nonce, ct + tag, aad) assert computed_pt == pt @pytest.mark.parametrize( ("nonce", "data", "associated_data"), [ [object(), b"data", b""], [b"0" * 12, object(), b""], [b"0" * 12, b"data", object()], ], ) def test_params_not_bytes(self, nonce, data, associated_data, backend): key = AESGCM.generate_key(128) aesgcm = AESGCM(key) with pytest.raises(TypeError): aesgcm.encrypt(nonce, data, associated_data) with pytest.raises(TypeError): aesgcm.decrypt(nonce, data, associated_data) @pytest.mark.parametrize("length", [7, 129]) def test_invalid_nonce_length(self, length, backend): if backend._fips_enabled: # Red Hat disables non-96-bit IV support as part of its FIPS # patches. pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") key = AESGCM.generate_key(128) aesgcm = AESGCM(key) with pytest.raises(ValueError): aesgcm.encrypt(b"\x00" * length, b"hi", None) with pytest.raises(ValueError): aesgcm.decrypt(b"\x00" * length, b"hi", None) def test_bad_key(self, backend): with pytest.raises(TypeError): AESGCM(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESGCM(b"0" * 31) def test_bad_generate_key(self, backend): with pytest.raises(TypeError): AESGCM.generate_key(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESGCM.generate_key(129) def test_associated_data_none_equal_to_empty_bytestring(self, backend): key = AESGCM.generate_key(128) aesgcm = AESGCM(key) nonce = os.urandom(12) ct1 = aesgcm.encrypt(nonce, b"some_data", None) ct2 = aesgcm.encrypt(nonce, b"some_data", b"") assert ct1 == ct2 pt1 = aesgcm.decrypt(nonce, ct1, None) pt2 = aesgcm.decrypt(nonce, ct2, b"") assert pt1 == pt2 def test_buffer_protocol(self, backend): key = AESGCM.generate_key(128) aesgcm = AESGCM(key) pt = b"encrypt me" ad = b"additional" nonce = os.urandom(12) ct = aesgcm.encrypt(nonce, pt, ad) computed_pt = aesgcm.decrypt(nonce, ct, ad) assert computed_pt == pt aesgcm2 = AESGCM(bytearray(key)) ct2 = aesgcm2.encrypt(bytearray(nonce), bytearray(pt), bytearray(ad)) assert ct2 == ct b_nonce = bytearray(nonce) b_ct2 = bytearray(ct2) b_ad = bytearray(ad) computed_pt2 = aesgcm2.decrypt(b_nonce, b_ct2, b_ad) assert computed_pt2 == pt aesgcm3 = AESGCM(memoryview(key)) m_nonce = memoryview(nonce) m_pt = memoryview(pt) m_ad = memoryview(ad) ct3 = aesgcm3.encrypt(m_nonce, m_pt, m_ad) assert ct3 == ct m_ct3 = memoryview(ct3) computed_pt3 = aesgcm3.decrypt(m_nonce, m_ct3, m_ad) assert computed_pt3 == pt @pytest.mark.skipif( _aead_supported(AESOCB3), reason="Requires OpenSSL without AESOCB3 support", ) def test_aesocb3_unsupported_on_older_openssl(backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): AESOCB3(AESOCB3.generate_key(128)) @pytest.mark.skipif( not _aead_supported(AESOCB3), reason="Does not support AESOCB3", ) class TestAESOCB3: @pytest.mark.skipif( sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESOCB3.generate_key(128) aesocb3 = AESOCB3(key) nonce = b"0" * 12 large_data = large_mmap() with pytest.raises(OverflowError): aesocb3.encrypt(nonce, large_data, b"") with pytest.raises(OverflowError): aesocb3.encrypt(nonce, b"", large_data) def test_vectors(self, backend, subtests): vectors = [] for f in [ "rfc7253.txt", "openssl.txt", "test-vector-1-nonce104.txt", "test-vector-1-nonce112.txt", "test-vector-1-nonce120.txt", ]: vectors.extend( load_vectors_from_file( os.path.join("ciphers", "AES", "OCB3", f), load_nist_vectors, ) ) for vector in vectors: with subtests.test(): nonce = binascii.unhexlify(vector["nonce"]) key = binascii.unhexlify(vector["key"]) aad = binascii.unhexlify(vector["aad"]) ct = binascii.unhexlify(vector["ciphertext"]) pt = binascii.unhexlify(vector.get("plaintext", b"")) aesocb3 = AESOCB3(key) computed_ct = aesocb3.encrypt(nonce, pt, aad) assert computed_ct == ct computed_pt = aesocb3.decrypt(nonce, ct, aad) assert computed_pt == pt def test_vectors_invalid(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("ciphers", "AES", "OCB3", "rfc7253.txt"), load_nist_vectors, ) for vector in vectors: with subtests.test(): nonce = binascii.unhexlify(vector["nonce"]) key = binascii.unhexlify(vector["key"]) aad = binascii.unhexlify(vector["aad"]) ct = binascii.unhexlify(vector["ciphertext"]) aesocb3 = AESOCB3(key) with pytest.raises(InvalidTag): badkey = AESOCB3(AESOCB3.generate_key(128)) badkey.decrypt(nonce, ct, aad) with pytest.raises(InvalidTag): aesocb3.decrypt(nonce, b"nonsense", aad) with pytest.raises(InvalidTag): aesocb3.decrypt(b"\x00" * 12, ct, aad) with pytest.raises(InvalidTag): aesocb3.decrypt(nonce, ct, b"nonsense") @pytest.mark.parametrize( ("key_len", "expected"), [ (128, b"g\xe9D\xd22V\xc5\xe0\xb6\xc6\x1f\xa2/\xdf\x1e\xa2"), (192, b"\xf6s\xf2\xc3\xe7\x17J\xae{\xae\x98l\xa9\xf2\x9e\x17"), (256, b"\xd9\x0e\xb8\xe9\xc9w\xc8\x8by\xddy=\x7f\xfa\x16\x1c"), ], ) def test_rfc7253(self, backend, key_len, expected): # This is derived from page 18 of RFC 7253, with a tag length of # 128 bits. k = AESOCB3(b"\x00" * ((key_len - 8) // 8) + b"\x80") c = b"" for i in range(0, 128): s = b"\x00" * i n = (3 * i + 1).to_bytes(12, "big") c += k.encrypt(n, s, s) n = (3 * i + 2).to_bytes(12, "big") c += k.encrypt(n, s, b"") n = (3 * i + 3).to_bytes(12, "big") c += k.encrypt(n, b"", s) assert len(c) == 22400 n = (385).to_bytes(12, "big") output = k.encrypt(n, b"", c) assert output == expected @pytest.mark.parametrize( ("nonce", "data", "associated_data"), [ [object(), b"data", b""], [b"0" * 12, object(), b""], [b"0" * 12, b"data", object()], ], ) def test_params_not_bytes(self, nonce, data, associated_data, backend): key = AESOCB3.generate_key(128) aesocb3 = AESOCB3(key) with pytest.raises(TypeError): aesocb3.encrypt(nonce, data, associated_data) with pytest.raises(TypeError): aesocb3.decrypt(nonce, data, associated_data) def test_invalid_nonce_length(self, backend): key = AESOCB3.generate_key(128) aesocb3 = AESOCB3(key) with pytest.raises(ValueError): aesocb3.encrypt(b"\x00" * 11, b"hi", None) with pytest.raises(ValueError): aesocb3.encrypt(b"\x00" * 16, b"hi", None) with pytest.raises(ValueError): aesocb3.decrypt(b"\x00" * 11, b"hi", None) with pytest.raises(ValueError): aesocb3.decrypt(b"\x00" * 16, b"hi", None) def test_bad_key(self, backend): with pytest.raises(TypeError): AESOCB3(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESOCB3(b"0" * 31) def test_bad_generate_key(self, backend): with pytest.raises(TypeError): AESOCB3.generate_key(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESOCB3.generate_key(129) def test_associated_data_none_equal_to_empty_bytestring(self, backend): key = AESOCB3.generate_key(128) aesocb3 = AESOCB3(key) nonce = os.urandom(12) ct1 = aesocb3.encrypt(nonce, b"some_data", None) ct2 = aesocb3.encrypt(nonce, b"some_data", b"") assert ct1 == ct2 pt1 = aesocb3.decrypt(nonce, ct1, None) pt2 = aesocb3.decrypt(nonce, ct2, b"") assert pt1 == pt2 def test_buffer_protocol(self, backend): key = AESOCB3.generate_key(128) aesocb3 = AESOCB3(key) pt = b"encrypt me" ad = b"additional" nonce = os.urandom(12) ct = aesocb3.encrypt(nonce, pt, ad) computed_pt = aesocb3.decrypt(nonce, ct, ad) assert computed_pt == pt aesocb3_ = AESOCB3(bytearray(key)) ct2 = aesocb3_.encrypt(bytearray(nonce), pt, ad) assert ct2 == ct computed_pt2 = aesocb3_.decrypt(bytearray(nonce), ct2, ad) assert computed_pt2 == pt @pytest.mark.skipif( not _aead_supported(AESSIV), reason="Does not support AESSIV", ) class TestAESSIV: @pytest.mark.skipif( sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESSIV.generate_key(256) aessiv = AESSIV(key) large_data = large_mmap() with pytest.raises(OverflowError): aessiv.encrypt(large_data, None) with pytest.raises(OverflowError): aessiv.encrypt(b"irrelevant", [large_data]) with pytest.raises(OverflowError): aessiv.decrypt(b"very very irrelevant", [large_data]) def test_no_empty_encryption(self): key = AESSIV.generate_key(256) aessiv = AESSIV(key) with pytest.raises(ValueError): aessiv.encrypt(b"", None) with pytest.raises(InvalidTag): aessiv.decrypt(b"", None) def test_vectors(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("ciphers", "AES", "SIV", "openssl.txt"), load_nist_vectors, ) for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) aad1 = vector.get("aad", None) aad2 = vector.get("aad2", None) aad3 = vector.get("aad3", None) aad = [ binascii.unhexlify(a) for a in (aad1, aad2, aad3) if a is not None ] ct = binascii.unhexlify(vector["ciphertext"]) tag = binascii.unhexlify(vector["tag"]) pt = binascii.unhexlify(vector.get("plaintext", b"")) aessiv = AESSIV(key) computed_ct = aessiv.encrypt(pt, aad) assert computed_ct[:16] == tag assert computed_ct[16:] == ct computed_pt = aessiv.decrypt(computed_ct, aad) assert computed_pt == pt def test_vectors_invalid(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("ciphers", "AES", "SIV", "openssl.txt"), load_nist_vectors, ) for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) aad1 = vector.get("aad", None) aad2 = vector.get("aad2", None) aad3 = vector.get("aad3", None) aad = [ binascii.unhexlify(a) for a in (aad1, aad2, aad3) if a is not None ] ct = binascii.unhexlify(vector["ciphertext"]) aessiv = AESSIV(key) with pytest.raises(InvalidTag): badkey = AESSIV(AESSIV.generate_key(256)) badkey.decrypt(ct, aad) with pytest.raises(InvalidTag): aessiv.decrypt(ct, [*aad, b""]) with pytest.raises(InvalidTag): aessiv.decrypt(ct, [b"nonsense"]) with pytest.raises(InvalidTag): aessiv.decrypt(b"nonsense", aad) @pytest.mark.parametrize( ("data", "associated_data"), [ [object(), [b""]], [b"data" * 5, [object()]], [b"data" * 5, b""], ], ) def test_params_not_bytes(self, data, associated_data, backend): key = AESSIV.generate_key(256) aessiv = AESSIV(key) with pytest.raises(TypeError): aessiv.encrypt(data, associated_data) with pytest.raises(TypeError): aessiv.decrypt(data, associated_data) def test_bad_key(self, backend): with pytest.raises(TypeError): AESSIV(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESSIV(b"0" * 31) def test_bad_generate_key(self, backend): with pytest.raises(TypeError): AESSIV.generate_key(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESSIV.generate_key(128) def test_associated_data_none_equal_to_empty_list(self, backend): key = AESSIV.generate_key(256) aessiv = AESSIV(key) ct1 = aessiv.encrypt(b"some_data", None) ct2 = aessiv.encrypt(b"some_data", []) assert ct1 == ct2 pt1 = aessiv.decrypt(ct1, None) pt2 = aessiv.decrypt(ct2, []) assert pt1 == pt2 def test_buffer_protocol(self, backend): key = AESSIV.generate_key(256) aessiv = AESSIV(key) pt = b"encrypt me" ad = [b"additional"] ct = aessiv.encrypt(pt, ad) computed_pt = aessiv.decrypt(ct, ad) assert computed_pt == pt aessiv = AESSIV(bytearray(key)) ct2 = aessiv.encrypt(pt, ad) assert ct2 == ct computed_pt2 = aessiv.decrypt(ct2, ad) assert computed_pt2 == pt @pytest.mark.skipif( not _aead_supported(AESGCMSIV), reason="Does not support AESGCMSIV", ) class TestAESGCMSIV: @pytest.mark.skipif( sys.platform not in {"linux", "darwin"} or sys.maxsize < 2**31, reason="mmap and 64-bit platform required", ) def test_data_too_large(self): key = AESGCMSIV.generate_key(256) nonce = os.urandom(12) aesgcmsiv = AESGCMSIV(key) large_data = large_mmap() with pytest.raises(OverflowError): aesgcmsiv.encrypt(nonce, large_data, None) with pytest.raises(OverflowError): aesgcmsiv.encrypt(nonce, b"irrelevant", large_data) with pytest.raises(OverflowError): aesgcmsiv.decrypt(nonce, b"very very irrelevant", large_data) def test_invalid_nonce_length(self, backend): key = AESGCMSIV.generate_key(128) aesgcmsiv = AESGCMSIV(key) pt = b"hello" nonce = os.urandom(14) with pytest.raises(ValueError): aesgcmsiv.encrypt(nonce, pt, None) with pytest.raises(ValueError): aesgcmsiv.decrypt(nonce, pt, None) def test_no_empty_encryption(self): key = AESGCMSIV.generate_key(256) aesgcmsiv = AESGCMSIV(key) nonce = os.urandom(12) with pytest.raises(ValueError): aesgcmsiv.encrypt(nonce, b"", None) with pytest.raises(InvalidTag): aesgcmsiv.decrypt(nonce, b"", None) def test_vectors(self, backend, subtests): vectors = _load_all_params( os.path.join("ciphers", "AES", "GCM-SIV"), [ "openssl.txt", "aes-192-gcm-siv.txt", ], load_nist_vectors, ) for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) nonce = binascii.unhexlify(vector["iv"]) aad = binascii.unhexlify(vector.get("aad", b"")) ct = binascii.unhexlify(vector["ciphertext"]) tag = binascii.unhexlify(vector["tag"]) pt = binascii.unhexlify(vector.get("plaintext", b"")) aesgcmsiv = AESGCMSIV(key) computed_ct = aesgcmsiv.encrypt(nonce, pt, aad) assert computed_ct[:-16] == ct assert computed_ct[-16:] == tag computed_pt = aesgcmsiv.decrypt(nonce, computed_ct, aad) assert computed_pt == pt def test_vectors_invalid(self, backend, subtests): vectors = _load_all_params( os.path.join("ciphers", "AES", "GCM-SIV"), [ "openssl.txt", "aes-192-gcm-siv.txt", ], load_nist_vectors, ) for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) nonce = binascii.unhexlify(vector["iv"]) aad = binascii.unhexlify(vector.get("aad", b"")) ct = binascii.unhexlify(vector["ciphertext"]) aesgcmsiv = AESGCMSIV(key) with pytest.raises(InvalidTag): badkey = AESGCMSIV(AESGCMSIV.generate_key(256)) badkey.decrypt(nonce, ct, aad) with pytest.raises(InvalidTag): aesgcmsiv.decrypt(nonce, ct, b"nonsense") with pytest.raises(InvalidTag): aesgcmsiv.decrypt(nonce, b"nonsense", aad) @pytest.mark.parametrize( ("nonce", "data", "associated_data"), [ [object(), b"data", b""], [b"0" * 12, object(), b""], [b"0" * 12, b"data", object()], ], ) def test_params_not_bytes(self, nonce, data, associated_data, backend): key = AESGCMSIV.generate_key(256) aesgcmsiv = AESGCMSIV(key) with pytest.raises(TypeError): aesgcmsiv.encrypt(nonce, data, associated_data) with pytest.raises(TypeError): aesgcmsiv.decrypt(nonce, data, associated_data) def test_bad_key(self, backend): with pytest.raises(TypeError): AESGCMSIV(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESGCMSIV(b"0" * 31) def test_bad_generate_key(self, backend): with pytest.raises(TypeError): AESGCMSIV.generate_key(object()) # type:ignore[arg-type] with pytest.raises(ValueError): AESGCMSIV.generate_key(129) def test_associated_data_none_equal_to_empty_bytestring(self, backend): key = AESGCMSIV.generate_key(256) aesgcmsiv = AESGCMSIV(key) nonce = os.urandom(12) ct1 = aesgcmsiv.encrypt(nonce, b"some_data", None) ct2 = aesgcmsiv.encrypt(nonce, b"some_data", b"") assert ct1 == ct2 pt1 = aesgcmsiv.decrypt(nonce, ct1, None) pt2 = aesgcmsiv.decrypt(nonce, ct2, b"") assert pt1 == pt2 def test_buffer_protocol(self, backend): key = AESGCMSIV.generate_key(256) aesgcmsiv = AESGCMSIV(key) nonce = os.urandom(12) pt = b"encrypt me" ad = b"additional" ct = aesgcmsiv.encrypt(nonce, pt, ad) computed_pt = aesgcmsiv.decrypt(nonce, ct, ad) assert computed_pt == pt aesgcmsiv = AESGCMSIV(bytearray(key)) ct2 = aesgcmsiv.encrypt(nonce, pt, ad) assert ct2 == ct computed_pt2 = aesgcmsiv.decrypt(nonce, ct2, ad) assert computed_pt2 == pt cryptography-43.0.0/tests/hazmat/primitives/test_aes.py010064400017510000177000000275301464676315000215420ustar 00000000000000# 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. import binascii import os import pytest from cryptography.exceptions import AlreadyFinalized, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives.ciphers import algorithms, base, modes from ...doubles import DummyMode from ...utils import load_nist_vectors, raises_unsupported_algorithm from .utils import _load_all_params, generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 32), modes.XTS(b"\x00" * 16) ), skip_message="Does not support AES XTS", ) class TestAESModeXTS: def test_xts_vectors(self, backend, subtests): # This list comprehension excludes any vector that does not have a # data unit length that is divisible by 8. The NIST vectors include # tests for implementations that support encryption of data that is # not divisible modulo 8, but OpenSSL is not such an implementation. vectors = [ x for x in _load_all_params( os.path.join("ciphers", "AES", "XTS", "tweak-128hexstr"), ["XTSGenAES128.rsp", "XTSGenAES256.rsp"], load_nist_vectors, ) if int(x["dataunitlen"]) / 8.0 == int(x["dataunitlen"]) // 8 ] for vector in vectors: with subtests.test(): key = binascii.unhexlify(vector["key"]) tweak = binascii.unhexlify(vector["i"]) pt = binascii.unhexlify(vector["pt"]) ct = binascii.unhexlify(vector["ct"]) cipher = base.Cipher( algorithms.AES(key), modes.XTS(tweak), backend ) enc = cipher.encryptor() computed_ct = enc.update(pt) + enc.finalize() assert computed_ct == ct dec = cipher.decryptor() computed_pt = dec.update(ct) + dec.finalize() assert computed_pt == pt def test_xts_too_short(self, backend): key = b"thirty_two_byte_keys_are_great!!" tweak = b"\x00" * 16 cipher = base.Cipher(algorithms.AES(key), modes.XTS(tweak)) enc = cipher.encryptor() with pytest.raises(ValueError): enc.update(b"0" * 15) @pytest.mark.supported( only_if=lambda backend: not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL, skip_message="duplicate key encryption error added in OpenSSL 1.1.1d", ) def test_xts_no_duplicate_keys_encryption(self, backend): key = bytes(range(16)) * 2 tweak = b"\x00" * 16 cipher = base.Cipher(algorithms.AES(key), modes.XTS(tweak)) with pytest.raises(ValueError, match="duplicated keys"): cipher.encryptor() def test_xts_unsupported_with_aes128_aes256_classes(self): with pytest.raises(TypeError): base.Cipher(algorithms.AES128(b"0" * 16), modes.XTS(b"\x00" * 16)) with pytest.raises(TypeError): base.Cipher(algorithms.AES256(b"0" * 32), modes.XTS(b"\x00" * 16)) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) class TestAESModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CBC"), [ "CBCGFSbox128.rsp", "CBCGFSbox192.rsp", "CBCGFSbox256.rsp", "CBCKeySbox128.rsp", "CBCKeySbox192.rsp", "CBCKeySbox256.rsp", "CBCVarKey128.rsp", "CBCVarKey192.rsp", "CBCVarKey256.rsp", "CBCVarTxt128.rsp", "CBCVarTxt192.rsp", "CBCVarTxt256.rsp", "CBCMMT128.rsp", "CBCMMT192.rsp", "CBCMMT256.rsp", ], lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES ECB", ) class TestAESModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "ECB"), [ "ECBGFSbox128.rsp", "ECBGFSbox192.rsp", "ECBGFSbox256.rsp", "ECBKeySbox128.rsp", "ECBKeySbox192.rsp", "ECBKeySbox256.rsp", "ECBVarKey128.rsp", "ECBVarKey192.rsp", "ECBVarKey256.rsp", "ECBVarTxt128.rsp", "ECBVarTxt192.rsp", "ECBVarTxt256.rsp", "ECBMMT128.rsp", "ECBMMT192.rsp", "ECBMMT256.rsp", ], lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support AES OFB", ) class TestAESModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "OFB"), [ "OFBGFSbox128.rsp", "OFBGFSbox192.rsp", "OFBGFSbox256.rsp", "OFBKeySbox128.rsp", "OFBKeySbox192.rsp", "OFBKeySbox256.rsp", "OFBVarKey128.rsp", "OFBVarKey192.rsp", "OFBVarKey256.rsp", "OFBVarTxt128.rsp", "OFBVarTxt192.rsp", "OFBVarTxt256.rsp", "OFBMMT128.rsp", "OFBMMT192.rsp", "OFBMMT256.rsp", ], lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support AES CFB", ) class TestAESModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), [ "CFB128GFSbox128.rsp", "CFB128GFSbox192.rsp", "CFB128GFSbox256.rsp", "CFB128KeySbox128.rsp", "CFB128KeySbox192.rsp", "CFB128KeySbox256.rsp", "CFB128VarKey128.rsp", "CFB128VarKey192.rsp", "CFB128VarKey256.rsp", "CFB128VarTxt128.rsp", "CFB128VarTxt192.rsp", "CFB128VarTxt256.rsp", "CFB128MMT128.rsp", "CFB128MMT192.rsp", "CFB128MMT256.rsp", ], lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.CFB8(b"\x00" * 16) ), skip_message="Does not support AES CFB8", ) class TestAESModeCFB8: test_cfb8 = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), [ "CFB8GFSbox128.rsp", "CFB8GFSbox192.rsp", "CFB8GFSbox256.rsp", "CFB8KeySbox128.rsp", "CFB8KeySbox192.rsp", "CFB8KeySbox256.rsp", "CFB8VarKey128.rsp", "CFB8VarKey192.rsp", "CFB8VarKey256.rsp", "CFB8VarTxt128.rsp", "CFB8VarTxt192.rsp", "CFB8VarTxt256.rsp", "CFB8MMT128.rsp", "CFB8MMT192.rsp", "CFB8MMT256.rsp", ], lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.CTR(b"\x00" * 16) ), skip_message="Does not support AES CTR", ) class TestAESModeCTR: test_ctr = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "AES", "CTR"), ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"], lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)), ) @pytest.mark.parametrize( "mode", [ modes.CBC(bytearray(b"\x00" * 16)), modes.CTR(bytearray(b"\x00" * 16)), modes.OFB(bytearray(b"\x00" * 16)), modes.CFB(bytearray(b"\x00" * 16)), modes.CFB8(bytearray(b"\x00" * 16)), modes.XTS(bytearray(b"\x00" * 16)), # Add a dummy mode for coverage of the cipher_supported check. DummyMode(), ], ) def test_buffer_protocol_alternate_modes(mode, backend): data = bytearray(b"sixteen_byte_msg") key = algorithms.AES(bytearray(os.urandom(32))) if not backend.cipher_supported(key, mode): pytest.skip(f"AES in {mode.name} mode not supported") cipher = base.Cipher(key, mode, backend) enc = cipher.encryptor() ct = enc.update(data) + enc.finalize() dec = cipher.decryptor() pt = dec.update(ct) + dec.finalize() assert pt == data @pytest.mark.parametrize( "mode", [ modes.ECB(), modes.CBC(bytearray(b"\x00" * 16)), modes.CTR(bytearray(b"\x00" * 16)), modes.OFB(bytearray(b"\x00" * 16)), modes.CFB(bytearray(b"\x00" * 16)), modes.CFB8(bytearray(b"\x00" * 16)), ], ) @pytest.mark.parametrize("alg_cls", [algorithms.AES128, algorithms.AES256]) def test_alternate_aes_classes(mode, alg_cls, backend): alg = alg_cls(b"0" * (alg_cls.key_size // 8)) if not backend.cipher_supported(alg, mode): pytest.skip(f"AES in {mode.name} mode not supported") data = bytearray(b"sixteen_byte_msg") cipher = base.Cipher(alg, mode, backend) enc = cipher.encryptor() ct = enc.update(data) + enc.finalize() dec = cipher.decryptor() pt = dec.update(ct) + dec.finalize() assert pt == data def test_reset_nonce(backend): data = b"helloworld" * 10 nonce = b"\x00" * 16 nonce_alt = b"\xee" * 16 cipher = base.Cipher( algorithms.AES(b"\x00" * 16), modes.CTR(nonce), ) cipher_alt = base.Cipher( algorithms.AES(b"\x00" * 16), modes.CTR(nonce_alt), ) enc = cipher.encryptor() ct1 = enc.update(data) assert len(ct1) == len(data) for _ in range(2): enc.reset_nonce(nonce) assert enc.update(data) == ct1 # Reset the nonce to a different value # and check it matches with a different context enc_alt = cipher_alt.encryptor() ct2 = enc_alt.update(data) enc.reset_nonce(nonce_alt) assert enc.update(data) == ct2 enc_alt.finalize() enc.finalize() with pytest.raises(AlreadyFinalized): enc.reset_nonce(nonce) dec = cipher.decryptor() assert dec.update(ct1) == data for _ in range(2): dec.reset_nonce(nonce) assert dec.update(ct1) == data # Reset the nonce to a different value # and check it matches with a different context dec_alt = cipher_alt.decryptor() dec.reset_nonce(nonce_alt) assert dec.update(ct2) == dec_alt.update(ct2) dec_alt.finalize() dec.finalize() with pytest.raises(AlreadyFinalized): dec.reset_nonce(nonce) def test_reset_nonce_invalid_mode(backend): iv = b"\x00" * 16 c = base.Cipher( algorithms.AES(b"\x00" * 16), modes.CBC(iv), ) enc = c.encryptor() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): enc.reset_nonce(iv) dec = c.decryptor() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): dec.reset_nonce(iv) cryptography-43.0.0/tests/hazmat/primitives/test_aes_gcm.py010064400017510000177000000213551464676315000223670ustar 00000000000000# 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. import binascii import os import pytest from cryptography.exceptions import _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives.ciphers import algorithms, base, modes from ...utils import load_nist_vectors, raises_unsupported_algorithm from .utils import generate_aead_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) ), skip_message="Does not support AES GCM", ) class TestAESModeGCM: test_gcm = generate_aead_test( load_nist_vectors, os.path.join("ciphers", "AES", "GCM"), [ "gcmDecrypt128.rsp", "gcmDecrypt192.rsp", "gcmDecrypt256.rsp", "gcmEncryptExtIV128.rsp", "gcmEncryptExtIV192.rsp", "gcmEncryptExtIV256.rsp", ], algorithms.AES, modes.GCM, ) def test_gcm_tag_with_only_aad(self, backend): key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") tag = binascii.unhexlify(b"0f247e7f9c2505de374006738018493b") cipher = base.Cipher( algorithms.AES(key), modes.GCM(iv), backend=backend ) encryptor = cipher.encryptor() encryptor.authenticate_additional_data(aad) encryptor.finalize() assert encryptor.tag == tag def test_gcm_ciphertext_with_no_aad(self, backend): key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") tag = binascii.unhexlify(b"23c7ab0f952b7091cd324835043b5eb5") pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") cipher = base.Cipher( algorithms.AES(key), modes.GCM(iv), backend=backend ) encryptor = cipher.encryptor() computed_ct = encryptor.update(pt) + encryptor.finalize() assert computed_ct == ct assert encryptor.tag == tag def test_gcm_ciphertext_limit(self, backend): cipher = base.Cipher( algorithms.AES(b"\x00" * 16), modes.GCM(b"\x01" * 16), backend=backend, ) encryptor = cipher.encryptor() rust_openssl.ciphers._advance( encryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16 ) encryptor.update(b"0" * 16) with pytest.raises(ValueError): encryptor.update(b"0") with pytest.raises(ValueError): encryptor.update_into(b"0", bytearray(1)) decryptor = cipher.decryptor() rust_openssl.ciphers._advance( decryptor, modes.GCM._MAX_ENCRYPTED_BYTES - 16 ) decryptor.update(b"0" * 16) with pytest.raises(ValueError): decryptor.update(b"0") with pytest.raises(ValueError): decryptor.update_into(b"0", bytearray(1)) def test_gcm_aad_limit(self, backend): cipher = base.Cipher( algorithms.AES(b"\x00" * 16), modes.GCM(b"\x01" * 16), backend=backend, ) encryptor = cipher.encryptor() rust_openssl.ciphers._advance_aad( encryptor, modes.GCM._MAX_AAD_BYTES - 16 ) encryptor.authenticate_additional_data(b"0" * 16) with pytest.raises(ValueError): encryptor.authenticate_additional_data(b"0") decryptor = cipher.decryptor() rust_openssl.ciphers._advance_aad( decryptor, modes.GCM._MAX_AAD_BYTES - 16 ) decryptor.authenticate_additional_data(b"0" * 16) with pytest.raises(ValueError): decryptor.authenticate_additional_data(b"0") def test_gcm_tag_decrypt_none(self, backend): key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") encryptor = base.Cipher( algorithms.AES(key), modes.GCM(iv), backend=backend ).encryptor() encryptor.authenticate_additional_data(aad) encryptor.finalize() decryptor = base.Cipher( algorithms.AES(key), modes.GCM(iv), backend=backend ).decryptor() decryptor.authenticate_additional_data(aad) with pytest.raises(ValueError): decryptor.finalize() def test_gcm_tag_decrypt_mode(self, backend): key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") encryptor = base.Cipher( algorithms.AES(key), modes.GCM(iv), backend=backend ).encryptor() encryptor.authenticate_additional_data(aad) encryptor.finalize() tag = encryptor.tag decryptor = base.Cipher( algorithms.AES(key), modes.GCM(iv, tag), backend=backend ).decryptor() decryptor.authenticate_additional_data(aad) decryptor.finalize() def test_gcm_tag_decrypt_finalize(self, backend): key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") encryptor = base.Cipher( algorithms.AES(key), modes.GCM(iv), backend=backend ).encryptor() encryptor.authenticate_additional_data(aad) encryptor.finalize() tag = encryptor.tag decryptor = base.Cipher( algorithms.AES(key), modes.GCM(iv), backend=backend ).decryptor() decryptor.authenticate_additional_data(aad) decryptor.finalize_with_tag(tag) @pytest.mark.parametrize("tag", [b"tagtooshort", b"toolong" * 12]) def test_gcm_tag_decrypt_finalize_tag_length(self, tag, backend): decryptor = base.Cipher( algorithms.AES(b"0" * 16), modes.GCM(b"0" * 12), backend=backend ).decryptor() with pytest.raises(ValueError): decryptor.finalize_with_tag(tag) def test_buffer_protocol(self, backend): data = bytearray(b"helloworld") c = base.Cipher( algorithms.AES(bytearray(b"\x00" * 16)), modes.GCM(bytearray(b"\x00" * 12)), backend, ) enc = c.encryptor() enc.authenticate_additional_data(bytearray(b"foo")) ct = enc.update(data) + enc.finalize() dec = c.decryptor() dec.authenticate_additional_data(bytearray(b"foo")) pt = dec.update(ct) + dec.finalize_with_tag(enc.tag) assert pt == data enc = c.encryptor() with pytest.raises(ValueError): enc.update_into(b"abc123", bytearray(0)) @pytest.mark.parametrize("size", [8, 128]) def test_gcm_min_max_iv(self, size, backend): if backend._fips_enabled: # Red Hat disables non-96-bit IV support as part of its FIPS # patches. pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") key = os.urandom(16) iv = b"\x00" * size payload = b"data" encryptor = base.Cipher(algorithms.AES(key), modes.GCM(iv)).encryptor() ct = encryptor.update(payload) encryptor.finalize() tag = encryptor.tag decryptor = base.Cipher(algorithms.AES(key), modes.GCM(iv)).decryptor() pt = decryptor.update(ct) decryptor.finalize_with_tag(tag) assert pt == payload @pytest.mark.parametrize("alg", [algorithms.AES128, algorithms.AES256]) def test_alternate_aes_classes(self, alg, backend): data = bytearray(b"sixteen_byte_msg") cipher = base.Cipher( alg(b"0" * (alg.key_size // 8)), modes.GCM(b"\x00" * 12), backend ) enc = cipher.encryptor() ct = enc.update(data) + enc.finalize() dec = cipher.decryptor() pt = dec.update(ct) + dec.finalize_with_tag(enc.tag) assert pt == data def test_reset_nonce_invalid_mode(self, backend): nonce = b"\x00" * 12 c = base.Cipher( algorithms.AES(b"\x00" * 16), modes.GCM(nonce), ) enc = c.encryptor() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): enc.reset_nonce(nonce) dec = c.decryptor() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): dec.reset_nonce(nonce) cryptography-43.0.0/tests/hazmat/primitives/test_asym_utils.py010064400017510000177000000050421464676315000231550ustar 00000000000000# 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. import pytest from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import ( Prehashed, decode_dss_signature, encode_dss_signature, ) def test_dss_signature(): sig = encode_dss_signature(1, 1) assert sig == b"0\x06\x02\x01\x01\x02\x01\x01" assert decode_dss_signature(sig) == (1, 1) r_s1 = ( 1037234182290683143945502320610861668562885151617, 559776156650501990899426031439030258256861634312, ) sig2 = encode_dss_signature(*r_s1) assert sig2 == ( b"0-\x02\x15\x00\xb5\xaf0xg\xfb\x8bT9\x00\x13\xccg\x02\r\xdf\x1f,\x0b" b'\x81\x02\x14b\r;"\xabP1D\x0c>5\xea\xb6\xf4\x81)\x8f\x9e\x9f\x08' ) assert decode_dss_signature(sig2) == r_s1 sig3 = encode_dss_signature(0, 0) assert sig3 == b"0\x06\x02\x01\x00\x02\x01\x00" assert decode_dss_signature(sig3) == (0, 0) def test_encode_dss_non_integer(): with pytest.raises(TypeError): encode_dss_signature("h", 3) # type: ignore[arg-type] with pytest.raises(TypeError): encode_dss_signature("3", "2") # type: ignore[arg-type] with pytest.raises(TypeError): encode_dss_signature(3, "h") # type: ignore[arg-type] with pytest.raises(TypeError): encode_dss_signature(3.3, 1.2) # type: ignore[arg-type] with pytest.raises(TypeError): encode_dss_signature("hello", "world") # type: ignore[arg-type] def test_encode_dss_negative(): with pytest.raises(ValueError): encode_dss_signature(-1, 0) with pytest.raises(ValueError): encode_dss_signature(0, -1) def test_decode_dss_trailing_bytes(): with pytest.raises(ValueError): decode_dss_signature(b"0\x06\x02\x01\x01\x02\x01\x01\x00\x00\x00") def test_decode_dss_invalid_asn1(): with pytest.raises(ValueError): # This byte sequence has an invalid ASN.1 sequence length as well as # an invalid integer length for the second integer. decode_dss_signature(b"0\x07\x02\x01\x01\x02\x02\x01") with pytest.raises(ValueError): # This is the BER "end-of-contents octets". decode_dss_signature(b"\x00\x00") def test_pass_invalid_prehashed_arg(): with pytest.raises(TypeError): Prehashed(object()) # type: ignore[arg-type] def test_prehashed_digest_size(): p = Prehashed(hashes.SHA256()) assert p.digest_size == 32 cryptography-43.0.0/tests/hazmat/primitives/test_block.py010064400017510000177000000150111464676315000220530ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import AlreadyFinalized, _Reasons from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, base, modes, ) from ...doubles import DummyCipherAlgorithm, DummyMode from ...utils import raises_unsupported_algorithm from .utils import ( generate_aead_exception_test, generate_aead_tag_exception_test, ) class TestCipher: def test_creates_encryptor(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)), backend, ) assert isinstance(cipher.encryptor(), base.CipherContext) def test_creates_decryptor(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)), backend, ) assert isinstance(cipher.decryptor(), base.CipherContext) def test_instantiate_with_non_algorithm(self, backend): algorithm = object() with pytest.raises(TypeError): Cipher( algorithm, # type: ignore[arg-type] mode=None, backend=backend, ) class TestCipherContext: def test_use_after_finalize(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)), backend, ) encryptor = cipher.encryptor() encryptor.update(b"a" * 16) encryptor.finalize() with pytest.raises(AlreadyFinalized): encryptor.update(b"b" * 16) with pytest.raises(AlreadyFinalized): encryptor.finalize() decryptor = cipher.decryptor() decryptor.update(b"a" * 16) decryptor.finalize() with pytest.raises(AlreadyFinalized): decryptor.update(b"b" * 16) with pytest.raises(AlreadyFinalized): decryptor.finalize() def test_use_update_into_after_finalize(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)), backend, ) encryptor = cipher.encryptor() encryptor.update(b"a" * 16) encryptor.finalize() with pytest.raises(AlreadyFinalized): buf = bytearray(31) encryptor.update_into(b"b" * 16, buf) def test_unaligned_block_encryption(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), modes.ECB(), backend ) encryptor = cipher.encryptor() ct = encryptor.update(b"a" * 15) assert ct == b"" ct += encryptor.update(b"a" * 65) assert len(ct) == 80 ct += encryptor.finalize() decryptor = cipher.decryptor() pt = decryptor.update(ct[:3]) assert pt == b"" pt += decryptor.update(ct[3:]) assert len(pt) == 80 assert pt == b"a" * 80 decryptor.finalize() @pytest.mark.parametrize("mode", [DummyMode(), None]) def test_nonexistent_cipher(self, backend, mode): cipher = Cipher(DummyCipherAlgorithm(), mode, backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.encryptor() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.decryptor() def test_incorrectly_padded(self, backend): cipher = Cipher( algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16), backend ) encryptor = cipher.encryptor() encryptor.update(b"1") with pytest.raises(ValueError): encryptor.finalize() decryptor = cipher.decryptor() decryptor.update(b"1") with pytest.raises(ValueError): decryptor.finalize() @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) ), skip_message="Does not support AES GCM", ) class TestAEADCipherContext: test_aead_exceptions = generate_aead_exception_test( algorithms.AES, modes.GCM, ) test_aead_tag_exceptions = generate_aead_tag_exception_test( algorithms.AES, modes.GCM, ) class TestModeValidation: def test_cbc(self, backend): with pytest.raises(ValueError): Cipher( algorithms.AES(b"\x00" * 16), modes.CBC(b"abc"), backend, ) def test_ofb(self, backend): with pytest.raises(ValueError): Cipher( algorithms.AES(b"\x00" * 16), modes.OFB(b"abc"), backend, ) def test_cfb(self, backend): with pytest.raises(ValueError): Cipher( algorithms.AES(b"\x00" * 16), modes.CFB(b"abc"), backend, ) def test_cfb8(self, backend): with pytest.raises(ValueError): Cipher( algorithms.AES(b"\x00" * 16), modes.CFB8(b"abc"), backend, ) def test_ctr(self, backend): with pytest.raises(ValueError): Cipher( algorithms.AES(b"\x00" * 16), modes.CTR(b"abc"), backend, ) def test_gcm(self): with pytest.raises(ValueError): modes.GCM(b"") class TestModesRequireBytes: def test_cbc(self): with pytest.raises(TypeError): modes.CBC([1] * 16) # type:ignore[arg-type] def test_cfb(self): with pytest.raises(TypeError): modes.CFB([1] * 16) # type:ignore[arg-type] def test_cfb8(self): with pytest.raises(TypeError): modes.CFB8([1] * 16) # type:ignore[arg-type] def test_ofb(self): with pytest.raises(TypeError): modes.OFB([1] * 16) # type:ignore[arg-type] def test_ctr(self): with pytest.raises(TypeError): modes.CTR([1] * 16) # type:ignore[arg-type] def test_gcm_iv(self): with pytest.raises(TypeError): modes.GCM([1] * 16) # type:ignore[arg-type] def test_gcm_tag(self): with pytest.raises(TypeError): modes.GCM(b"\x00" * 16, [1] * 16) # type:ignore[arg-type] cryptography-43.0.0/tests/hazmat/primitives/test_camellia.py010064400017510000177000000047521464676315000225420ustar 00000000000000# 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. import binascii import os import pytest from cryptography.hazmat.primitives.ciphers import algorithms, modes from ...utils import load_cryptrec_vectors, load_nist_vectors from .utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.Camellia(b"\x00" * 16), modes.ECB() ), skip_message="Does not support Camellia ECB", ) class TestCamelliaModeECB: test_ecb = generate_encrypt_test( load_cryptrec_vectors, os.path.join("ciphers", "Camellia"), [ "camellia-128-ecb.txt", "camellia-192-ecb.txt", "camellia-256-ecb.txt", ], lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.Camellia(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support Camellia CBC", ) class TestCamelliaModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cbc.txt"], lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.Camellia(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support Camellia OFB", ) class TestCamelliaModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-ofb.txt"], lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.Camellia(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support Camellia CFB", ) class TestCamelliaModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cfb.txt"], lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) cryptography-43.0.0/tests/hazmat/primitives/test_chacha20.py010064400017510000177000000121601464676315000223340ustar 00000000000000# 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. import binascii import os import struct import pytest from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives.ciphers import Cipher, algorithms from ...utils import load_nist_vectors from .utils import _load_all_params @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.ChaCha20(b"\x00" * 32, b"0" * 16), None ), skip_message="Does not support ChaCha20", ) class TestChaCha20: @pytest.mark.parametrize( "vector", _load_all_params( os.path.join("ciphers", "ChaCha20"), ["counter-overflow.txt", "rfc7539.txt"], load_nist_vectors, ), ) def test_vectors(self, vector, backend): key = binascii.unhexlify(vector["key"]) nonce = binascii.unhexlify(vector["nonce"]) ibc = struct.pack(" None: if not backend.dsa_hash_supported(algorithm): pytest.skip( f"{backend} does not support the provided args. " f"p: {p.bit_length()}, hash: {algorithm.name}" ) def test_skip_if_dsa_not_supported(backend): with pytest.raises(pytest.skip.Exception): _skip_if_dsa_not_supported(backend, DummyHashAlgorithm(), 1, 1, 1) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) class TestDSA: def test_generate_dsa_parameters(self, backend): parameters = dsa.generate_parameters(2048, backend) assert isinstance(parameters, dsa.DSAParameters) def test_generate_invalid_dsa_parameters(self, backend): with pytest.raises(ValueError): dsa.generate_parameters(1, backend) @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("asymmetric", "DSA", "FIPS_186-3", "KeyPair.rsp"), load_fips_dsa_key_pair_vectors, ), ) def test_generate_dsa_keys(self, vector, backend): parameters = dsa.DSAParameterNumbers( p=vector["p"], q=vector["q"], g=vector["g"] ).parameters(backend) skey = parameters.generate_private_key() numbers = skey.private_numbers() skey_parameters = numbers.public_numbers.parameter_numbers pkey = skey.public_key() parameters = pkey.parameters() parameter_numbers = parameters.parameter_numbers() assert parameter_numbers.p == skey_parameters.p assert parameter_numbers.q == skey_parameters.q assert parameter_numbers.g == skey_parameters.g assert skey_parameters.p == vector["p"] assert skey_parameters.q == vector["q"] assert skey_parameters.g == vector["g"] assert skey.key_size == vector["p"].bit_length() assert pkey.key_size == skey.key_size public_numbers = pkey.public_numbers() assert numbers.public_numbers.y == public_numbers.y assert numbers.public_numbers.y == pow( skey_parameters.g, numbers.x, skey_parameters.p ) def test_generate_dsa_private_key_and_parameters(self, backend): skey = dsa.generate_private_key(2048, backend) assert skey numbers = skey.private_numbers() skey_parameters = numbers.public_numbers.parameter_numbers assert numbers.public_numbers.y == pow( skey_parameters.g, numbers.x, skey_parameters.p ) @pytest.mark.parametrize( ("p", "q", "g"), [ ( 2**1000, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, ), ( 2**2000, DSA_KEY_2048.public_numbers.parameter_numbers.q, DSA_KEY_2048.public_numbers.parameter_numbers.g, ), ( 2**3000, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, ), ( 2**3100, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, 2**150, DSA_KEY_1024.public_numbers.parameter_numbers.g, ), ( DSA_KEY_2048.public_numbers.parameter_numbers.p, 2**250, DSA_KEY_2048.public_numbers.parameter_numbers.g, ), ( DSA_KEY_3072.public_numbers.parameter_numbers.p, 2**260, DSA_KEY_3072.public_numbers.parameter_numbers.g, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 0, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 1, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 2**1200, ), ], ) def test_invalid_parameters_values(self, p, q, g, backend): with pytest.raises(ValueError): dsa.DSAParameterNumbers(p, q, g).parameters(backend) @pytest.mark.parametrize( ("p", "q", "g", "y", "x"), [ ( 2**1000, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), ( 2**2000, DSA_KEY_2048.public_numbers.parameter_numbers.q, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, DSA_KEY_2048.x, ), ( 2**3000, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, DSA_KEY_3072.x, ), ( 2**3100, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, DSA_KEY_3072.x, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, 2**150, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), ( DSA_KEY_2048.public_numbers.parameter_numbers.p, 2**250, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, DSA_KEY_2048.x, ), ( DSA_KEY_3072.public_numbers.parameter_numbers.p, 2**260, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, DSA_KEY_3072.x, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 0, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 1, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 2**1200, DSA_KEY_1024.public_numbers.y, DSA_KEY_1024.x, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, 0, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, -2, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, 2**159, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, 2**200, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, 2**100, DSA_KEY_1024.x, ), ], ) def test_invalid_dsa_private_key_arguments(self, p, q, g, y, x, backend): with pytest.raises(ValueError): dsa.DSAPrivateNumbers( public_numbers=dsa.DSAPublicNumbers( parameter_numbers=dsa.DSAParameterNumbers(p=p, q=q, g=g), y=y, ), x=x, ).private_key(backend) @pytest.mark.parametrize( ("p", "q", "g", "y"), [ ( 2**1000, DSA_KEY_1024.public_numbers.parameter_numbers.q, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, ), ( 2**2000, DSA_KEY_2048.public_numbers.parameter_numbers.q, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, ), ( 2**3000, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, ), ( 2**3100, DSA_KEY_3072.public_numbers.parameter_numbers.q, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, 2**150, DSA_KEY_1024.public_numbers.parameter_numbers.g, DSA_KEY_1024.public_numbers.y, ), ( DSA_KEY_2048.public_numbers.parameter_numbers.p, 2**250, DSA_KEY_2048.public_numbers.parameter_numbers.g, DSA_KEY_2048.public_numbers.y, ), ( DSA_KEY_3072.public_numbers.parameter_numbers.p, 2**260, DSA_KEY_3072.public_numbers.parameter_numbers.g, DSA_KEY_3072.public_numbers.y, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 0, DSA_KEY_1024.public_numbers.y, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 1, DSA_KEY_1024.public_numbers.y, ), ( DSA_KEY_1024.public_numbers.parameter_numbers.p, DSA_KEY_1024.public_numbers.parameter_numbers.q, 2**1200, DSA_KEY_1024.public_numbers.y, ), ], ) def test_invalid_dsa_public_key_arguments(self, p, q, g, y, backend): with pytest.raises(ValueError): dsa.DSAPublicNumbers( parameter_numbers=dsa.DSAParameterNumbers(p=p, q=q, g=g), y=y ).public_key(backend) def test_large_p(self, backend): key = load_vectors_from_file( os.path.join("asymmetric", "PEM_Serialization", "dsa_4096.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, backend ), mode="rb", ) assert isinstance(key, dsa.DSAPrivateKey) pn = key.private_numbers() assert pn.public_numbers.parameter_numbers.p.bit_length() == 4096 # Turn it back into a key to confirm that values this large pass # verification dsa.DSAPrivateNumbers( public_numbers=dsa.DSAPublicNumbers( parameter_numbers=dsa.DSAParameterNumbers( p=pn.public_numbers.parameter_numbers.p, q=pn.public_numbers.parameter_numbers.q, g=pn.public_numbers.parameter_numbers.g, ), y=pn.public_numbers.y, ), x=pn.x, ).private_key(backend) def test_public_key_equality(self, backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), lambda pemfile: pemfile.read().encode(), ) key1 = serialization.load_pem_private_key(key_bytes, None).public_key() key2 = serialization.load_pem_private_key(key_bytes, None).public_key() key3 = DSA_KEY_2048.private_key().public_key() assert key1 == key2 assert key1 != key3 assert key1 != object() with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] def test_public_key_copy(self): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), lambda pemfile: pemfile.read().encode(), ) key1 = serialization.load_pem_private_key(key_bytes, None).public_key() key2 = copy.copy(key1) assert key1 == key2 @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) class TestDSAVerification: def test_dsa_verification(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("asymmetric", "DSA", "FIPS_186-3", "SigVer.rsp"), load_fips_dsa_sig_vectors, ) for vector in vectors: with subtests.test(): digest_algorithm = vector["digest_algorithm"].replace("-", "") algorithm = _ALGORITHMS_DICT[digest_algorithm] _skip_if_dsa_not_supported( backend, algorithm, vector["p"], vector["q"], vector["g"] ) public_key = dsa.DSAPublicNumbers( parameter_numbers=dsa.DSAParameterNumbers( vector["p"], vector["q"], vector["g"] ), y=vector["y"], ).public_key(backend) sig = encode_dss_signature(vector["r"], vector["s"]) if vector["result"] == "F": with pytest.raises(InvalidSignature): public_key.verify(sig, vector["msg"], algorithm) else: public_key.verify(sig, vector["msg"], algorithm) def test_dsa_verify_invalid_asn1(self, backend): public_key = DSA_KEY_1024.public_numbers.public_key(backend) with pytest.raises(InvalidSignature): public_key.verify(b"fakesig", b"fakemsg", hashes.SHA1()) def test_verify(self, backend): message = b"one little message" algorithm = hashes.SHA1() private_key = DSA_KEY_1024.private_key(backend) signature = private_key.sign(message, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, algorithm) def test_prehashed_verify(self, backend): private_key = DSA_KEY_1024.private_key(backend) message = b"one little message" h = hashes.Hash(hashes.SHA1(), backend) h.update(message) digest = h.finalize() prehashed_alg = Prehashed(hashes.SHA1()) signature = private_key.sign(message, hashes.SHA1()) public_key = private_key.public_key() public_key.verify(signature, digest, prehashed_alg) def test_prehashed_digest_mismatch(self, backend): private_key = DSA_KEY_1024.private_key(backend) public_key = private_key.public_key() message = b"one little message" h = hashes.Hash(hashes.SHA1(), backend) h.update(message) digest = h.finalize() prehashed_alg = Prehashed(hashes.SHA224()) with pytest.raises(ValueError): public_key.verify(b"\x00" * 128, digest, prehashed_alg) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) class TestDSASignature: def test_dsa_signing(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("asymmetric", "DSA", "FIPS_186-3", "SigGen.txt"), load_fips_dsa_sig_vectors, ) for vector in vectors: with subtests.test(): digest_algorithm = vector["digest_algorithm"].replace("-", "") algorithm = _ALGORITHMS_DICT[digest_algorithm] _skip_if_dsa_not_supported( backend, algorithm, vector["p"], vector["q"], vector["g"] ) private_key = dsa.DSAPrivateNumbers( public_numbers=dsa.DSAPublicNumbers( parameter_numbers=dsa.DSAParameterNumbers( vector["p"], vector["q"], vector["g"] ), y=vector["y"], ), x=vector["x"], ).private_key(backend) signature = private_key.sign(vector["msg"], algorithm) assert signature private_key.public_key().verify( signature, vector["msg"], algorithm ) def test_sign(self, backend): private_key = DSA_KEY_1024.private_key(backend) message = b"one little message" algorithm = hashes.SHA1() signature = private_key.sign(message, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, algorithm) def test_sign_verify_buffer(self, backend): private_key = DSA_KEY_1024.private_key(backend) message = bytearray(b"one little message") algorithm = hashes.SHA1() signature = private_key.sign(message, algorithm) public_key = private_key.public_key() public_key.verify(bytearray(signature), message, algorithm) def test_prehashed_sign(self, backend): private_key = DSA_KEY_1024.private_key(backend) message = b"one little message" h = hashes.Hash(hashes.SHA1(), backend) h.update(message) digest = h.finalize() prehashed_alg = Prehashed(hashes.SHA1()) signature = private_key.sign(digest, prehashed_alg) public_key = private_key.public_key() public_key.verify(signature, message, hashes.SHA1()) def test_prehashed_digest_mismatch(self, backend): private_key = DSA_KEY_1024.private_key(backend) message = b"one little message" h = hashes.Hash(hashes.SHA1(), backend) h.update(message) digest = h.finalize() prehashed_alg = Prehashed(hashes.SHA224()) with pytest.raises(ValueError): private_key.sign(digest, prehashed_alg) class TestDSANumbers: def test_dsa_parameter_numbers(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) assert parameter_numbers.p == 1 assert parameter_numbers.q == 2 assert parameter_numbers.g == 3 def test_dsa_parameter_numbers_invalid_types(self): with pytest.raises(TypeError): dsa.DSAParameterNumbers(p=None, q=2, g=3) # type: ignore[arg-type] with pytest.raises(TypeError): dsa.DSAParameterNumbers(p=1, q=None, g=3) # type: ignore[arg-type] with pytest.raises(TypeError): dsa.DSAParameterNumbers(p=1, q=2, g=None) # type: ignore[arg-type] def test_dsa_public_numbers(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) public_numbers = dsa.DSAPublicNumbers( y=4, parameter_numbers=parameter_numbers ) assert public_numbers.y == 4 assert public_numbers.parameter_numbers == parameter_numbers def test_dsa_public_numbers_invalid_types(self): with pytest.raises(TypeError): dsa.DSAPublicNumbers( y=4, parameter_numbers=None, # type: ignore[arg-type] ) with pytest.raises(TypeError): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) dsa.DSAPublicNumbers( y=None, # type: ignore[arg-type] parameter_numbers=parameter_numbers, ) def test_dsa_private_numbers(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) public_numbers = dsa.DSAPublicNumbers( y=4, parameter_numbers=parameter_numbers ) private_numbers = dsa.DSAPrivateNumbers( x=5, public_numbers=public_numbers ) assert private_numbers.x == 5 assert private_numbers.public_numbers == public_numbers def test_dsa_private_numbers_invalid_types(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) public_numbers = dsa.DSAPublicNumbers( y=4, parameter_numbers=parameter_numbers ) with pytest.raises(TypeError): dsa.DSAPrivateNumbers( x=4, public_numbers=None, # type: ignore[arg-type] ) with pytest.raises(TypeError): dsa.DSAPrivateNumbers( x=None, # type: ignore[arg-type] public_numbers=public_numbers, ) def test_repr(self): parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) assert ( repr(parameter_numbers) == "" ) public_numbers = dsa.DSAPublicNumbers( y=4, parameter_numbers=parameter_numbers ) assert repr(public_numbers) == ( ")>" ) class TestDSANumberEquality: def test_parameter_numbers_eq(self): param = dsa.DSAParameterNumbers(1, 2, 3) assert param == dsa.DSAParameterNumbers(1, 2, 3) def test_parameter_numbers_ne(self): param = dsa.DSAParameterNumbers(1, 2, 3) assert param != dsa.DSAParameterNumbers(1, 2, 4) assert param != dsa.DSAParameterNumbers(1, 1, 3) assert param != dsa.DSAParameterNumbers(2, 2, 3) assert param != object() def test_public_numbers_eq(self): pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) assert pub == dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) def test_public_numbers_ne(self): pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) assert pub != dsa.DSAPublicNumbers(2, dsa.DSAParameterNumbers(1, 2, 3)) assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(2, 2, 3)) assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 3, 3)) assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 4)) assert pub != object() def test_private_numbers_eq(self): pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) priv = dsa.DSAPrivateNumbers(1, pub) assert priv == dsa.DSAPrivateNumbers( 1, dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) ) def test_private_numbers_ne(self): pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) priv = dsa.DSAPrivateNumbers(1, pub) assert priv != dsa.DSAPrivateNumbers( 2, dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) ) assert priv != dsa.DSAPrivateNumbers( 1, dsa.DSAPublicNumbers(2, dsa.DSAParameterNumbers(1, 2, 3)) ) assert priv != dsa.DSAPrivateNumbers( 1, dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(2, 2, 3)) ) assert priv != dsa.DSAPrivateNumbers( 1, dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 3, 3)) ) assert priv != dsa.DSAPrivateNumbers( 1, dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 4)) ) assert priv != object() @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) class TestDSASerialization: @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_fips_traditional_openssl(backend, fmt) 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) assert isinstance(key, dsa.DSAPrivateKey) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_pem_private_key( serialized, password, backend ) assert isinstance(loaded_key, dsa.DSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.parametrize( ("encoding", "fmt"), [ (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ( serialization.Encoding.SMIME, serialization.PrivateFormat.TraditionalOpenSSL, ), ], ) def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): key = DSA_KEY_1024.private_key(backend) with pytest.raises(ValueError): key.private_bytes(encoding, fmt, serialization.NoEncryption()) @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], [serialization.PrivateFormat.PKCS8, b"longerpassword"], [serialization.PrivateFormat.PKCS8, b"!*$&(@#$*&($T@%_somesymbol"], [serialization.PrivateFormat.PKCS8, b"\x01" * 1000], ], ) def test_private_bytes_encrypted_der(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) assert isinstance(key, dsa.DSAPrivateKey) serialized = key.private_bytes( serialization.Encoding.DER, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_der_private_key( serialized, password, backend ) assert isinstance(loaded_key, dsa.DSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.parametrize( ("encoding", "fmt", "loader_func"), [ [ serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.load_pem_private_key, ], [ serialization.Encoding.DER, serialization.PrivateFormat.TraditionalOpenSSL, serialization.load_der_private_key, ], [ serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.load_pem_private_key, ], [ serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.load_der_private_key, ], ], ) def test_private_bytes_unencrypted( self, backend, encoding, fmt, loader_func ): key = DSA_KEY_1024.private_key(backend) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) loaded_key = loader_func(serialized, None, backend) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.skip_fips( reason="Traditional OpenSSL key format is not supported in FIPS mode." ) @pytest.mark.parametrize( ("key_path", "encoding", "loader_func"), [ [ os.path.join( "asymmetric", "Traditional_OpenSSL_Serialization", "dsa.1024.pem", ), serialization.Encoding.PEM, serialization.load_pem_private_key, ], [ os.path.join( "asymmetric", "DER_Serialization", "dsa.1024.der" ), serialization.Encoding.DER, serialization.load_der_private_key, ], ], ) def test_private_bytes_traditional_openssl_unencrypted( self, backend, key_path, encoding, loader_func ): key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, None, backend) serialized = key.private_bytes( encoding, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption(), ) assert serialized == key_bytes def test_private_bytes_traditional_der_encrypted_invalid(self, backend): key = DSA_KEY_1024.private_key(backend) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, serialization.PrivateFormat.TraditionalOpenSSL, serialization.BestAvailableEncryption(b"password"), ) 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 ), ) with pytest.raises(TypeError): key.private_bytes( "notencoding", # type: ignore[arg-type] 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 ), ) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, "invalidformat", # type: ignore[arg-type] 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 ), ) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, "notanencalg", # type: ignore[arg-type] ) 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 ), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, DummyKeySerializationEncryption(), ) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) class TestDSAPEMPublicKeySerialization: @pytest.mark.parametrize( ("key_path", "loader_func", "encoding"), [ ( os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), serialization.load_pem_public_key, serialization.Encoding.PEM, ), ( os.path.join( "asymmetric", "DER_Serialization", "unenc-dsa-pkcs8.pub.der", ), serialization.load_der_public_key, serialization.Encoding.DER, ), ], ) def test_public_bytes_match( self, key_path, loader_func, encoding, backend ): key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) serialized = key.public_bytes( encoding, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert serialized == key_bytes def test_public_bytes_openssh(self, backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), lambda pemfile: pemfile.read(), mode="rb", ) key = serialization.load_pem_public_key(key_bytes, backend) with pytest.warns(utils.DeprecatedIn40): ssh_bytes = key.public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH, ) assert ssh_bytes == ( b"ssh-dss AAAAB3NzaC1kc3MAAACBAKoJMMwUWCUiHK/6KKwolBlqJ4M95ewhJweR" b"aJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5StOTzAik1K" b"2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmcGw8FlAyr" b"5dLeSaFnAAAAFQCtwOhps28KwBOmgf301ImdaYIEUQAAAIEAjGtFia+lOk0QSL/D" b"RtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QVvgPn" b"EUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q2ski" b"+ycTorCIfLoTubxozlz/8kHNMkYAAACAKyYOqX3GoSrpMsZA5989j/BKigWgMk+N" b"Xxsj8V+hcP8/QgYRJO/yWGyxG0moLc3BuQ/GqE+xAQnLZ9tdLalxrq8Xvl43KEVj" b"5MZNnl/ISAJYsxnw3inVTYNQcNnih5FNd9+BSR9EI7YtqYTrP0XrKin86l2uUlrG" b"q2vM4Ev99bY=" ) def test_public_bytes_invalid_encoding(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() with pytest.raises(TypeError): key.public_bytes( "notencoding", # type: ignore[arg-type] serialization.PublicFormat.SubjectPublicKeyInfo, ) def test_public_bytes_invalid_format(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() with pytest.raises(TypeError): key.public_bytes( serialization.Encoding.PEM, "invalidformat", # type: ignore[arg-type] ) def test_public_bytes_pkcs1_unsupported(self, backend): key = DSA_KEY_2048.private_key(backend).public_key() with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) @pytest.mark.parametrize( ("encoding", "fmt"), [ ( serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo, ), (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), *itertools.product( [ serialization.Encoding.Raw, serialization.Encoding.X962, serialization.Encoding.PEM, serialization.Encoding.DER, ], [ serialization.PublicFormat.Raw, serialization.PublicFormat.UncompressedPoint, serialization.PublicFormat.CompressedPoint, ], ), ], ) def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): key = DSA_KEY_2048.private_key(backend).public_key() with pytest.raises(ValueError): key.public_bytes(encoding, fmt) cryptography-43.0.0/tests/hazmat/primitives/test_ec.py010064400017510000177000001524461464676315000213660ustar 00000000000000# 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. import binascii import copy import itertools import os import textwrap import typing from binascii import hexlify import pytest from cryptography import exceptions, utils, x509 from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.ec import ( EllipticCurvePrivateKey, EllipticCurvePublicKey, ) from cryptography.hazmat.primitives.asymmetric.utils import ( Prehashed, encode_dss_signature, ) from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, load_kasvs_ecdh_vectors, load_nist_vectors, load_rfc6979_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) from .fixtures_ec import EC_KEY_SECP384R1 from .utils import skip_fips_traditional_openssl _HASH_TYPES: typing.Dict[str, typing.Type[hashes.HashAlgorithm]] = { "SHA-1": hashes.SHA1, "SHA-224": hashes.SHA224, "SHA-256": hashes.SHA256, "SHA-384": hashes.SHA384, "SHA-512": hashes.SHA512, } def _skip_ecdsa_vector(backend, curve: ec.EllipticCurve, hash_type): if not backend.elliptic_curve_signature_algorithm_supported( ec.ECDSA(hash_type()), curve ): pytest.skip( f"ECDSA not supported with this hash {hash_type().name} and " f"curve {curve.name}." ) def _skip_curve_unsupported(backend, curve: ec.EllipticCurve): if not backend.elliptic_curve_supported(curve): pytest.skip( f"Curve {curve.name} is not supported by this backend {backend}" ) def _skip_exchange_algorithm_unsupported(backend, algorithm, curve): if not backend.elliptic_curve_exchange_algorithm_supported( algorithm, curve ): pytest.skip( f"Exchange with {curve.name} curve is not supported by {backend}" ) def test_get_curve_for_oid(): assert ec.get_curve_for_oid(ec.EllipticCurveOID.SECP256R1) == ec.SECP256R1 with pytest.raises(LookupError): ec.get_curve_for_oid(x509.ObjectIdentifier("1.1.1.1")) class DummyCurve(ec.EllipticCurve): name = "dummy-curve" key_size = 1 class DummySignatureAlgorithm(ec.EllipticCurveSignatureAlgorithm): algorithm = hashes.SHA256() def test_skip_curve_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_curve_unsupported(backend, DummyCurve()) def test_skip_exchange_algorithm_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), DummyCurve()) def test_skip_ecdsa_vector(backend): with pytest.raises(pytest.skip.Exception): _skip_ecdsa_vector(backend, DummyCurve(), hashes.SHA256) def test_derive_private_key_success(backend): curve = ec.SECP256K1() _skip_curve_unsupported(backend, curve) private_numbers = ec.generate_private_key(curve, backend).private_numbers() derived_key = ec.derive_private_key( private_numbers.private_value, curve, backend ) assert private_numbers == derived_key.private_numbers() def test_derive_private_key_errors(backend): curve = ec.SECP256K1() _skip_curve_unsupported(backend, curve) with pytest.raises(TypeError): ec.derive_private_key("one", curve, backend) # type: ignore[arg-type] with pytest.raises(TypeError): ec.derive_private_key(10, "five", backend) # type: ignore[arg-type] with pytest.raises(ValueError): ec.derive_private_key(-7, curve, backend) def test_derive_point_at_infinity(backend): curve = ec.SECP256R1() _skip_curve_unsupported(backend, curve) # order of the curve q = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 # BoringSSL rejects infinity points before it ever gets to us, so it # uses a more generic error message. match = ( "infinity" if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL else "Invalid" ) with pytest.raises(ValueError, match=match): ec.derive_private_key(q, ec.SECP256R1()) def test_ec_numbers(): numbers = ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) ) assert numbers.private_value == 1 assert numbers.public_numbers.x == 2 assert numbers.public_numbers.y == 3 assert isinstance(numbers.public_numbers.curve, DummyCurve) @pytest.mark.parametrize( ("private_value", "x", "y", "curve"), [ (None, 2, 3, DummyCurve()), (1, None, 3, DummyCurve()), (1, 2, None, DummyCurve()), (1, 2, 3, None), ], ) def test_invalid_ec_numbers_args(private_value, x, y, curve): with pytest.raises(TypeError): ec.EllipticCurvePrivateNumbers( private_value, ec.EllipticCurvePublicNumbers(x, y, curve) ) def test_invalid_private_numbers_public_numbers(): with pytest.raises(TypeError): ec.EllipticCurvePrivateNumbers(1, None) # type: ignore[arg-type] def test_ec_public_numbers_repr(): pn = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) assert ( repr(pn) == "" ) def test_ec_public_numbers_hash(): pn1 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) pn2 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) pn3 = ec.EllipticCurvePublicNumbers(1, 3, ec.SECP256R1()) assert hash(pn1) == hash(pn2) assert hash(pn1) != hash(pn3) def test_ec_private_numbers_hash(): numbers1 = ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) ) numbers2 = ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) ) numbers3 = ec.EllipticCurvePrivateNumbers( 2, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) ) assert hash(numbers1) == hash(numbers2) assert hash(numbers1) != hash(numbers3) def test_ec_key_key_size(backend): curve = ec.SECP256R1() _skip_curve_unsupported(backend, curve) key = ec.generate_private_key(curve, backend) assert key.key_size == 256 assert key.public_key().key_size == 256 def test_deprecated_generate_private_key_with_curve_class(backend): # This test verifies that if you pass a curve _class_ instead of instance, # you get a warning and then `key.curve` is still an instance. _skip_curve_unsupported(backend, ec.SECP256R1()) with pytest.warns(utils.DeprecatedIn42): key = ec.generate_private_key(ec.SECP256R1) # type: ignore[arg-type] assert isinstance(key.curve, ec.SECP256R1) class TestECWithNumbers: def test_with_numbers(self, backend, subtests): vectors = itertools.product( load_vectors_from_file( os.path.join( "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp" ), load_fips_ecdsa_key_pair_vectors, ), _HASH_TYPES.values(), ) for vector, hash_type in vectors: with subtests.test(): curve = ec._CURVE_TYPES[vector["curve"]] _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePrivateNumbers( vector["d"], ec.EllipticCurvePublicNumbers( vector["x"], vector["y"], curve ), ).private_key(backend) assert key priv_num = key.private_numbers() assert priv_num.private_value == vector["d"] assert priv_num.public_numbers.x == vector["x"] assert priv_num.public_numbers.y == vector["y"] assert curve.name == priv_num.public_numbers.curve.name class TestECDSAVectors: def test_signing_with_example_keys(self, backend, subtests): vectors = itertools.product( load_vectors_from_file( os.path.join( "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp" ), load_fips_ecdsa_key_pair_vectors, ), _HASH_TYPES.values(), ) for vector, hash_type in vectors: with subtests.test(): curve = ec._CURVE_TYPES[vector["curve"]] _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePrivateNumbers( vector["d"], ec.EllipticCurvePublicNumbers( vector["x"], vector["y"], curve ), ).private_key(backend) assert key pkey = key.public_key() assert pkey signature = key.sign( b"YELLOW SUBMARINE", ec.ECDSA(hash_type()) ) pkey.verify( signature, b"YELLOW SUBMARINE", ec.ECDSA(hash_type()) ) @pytest.mark.parametrize("curve", ec._CURVE_TYPES.values()) def test_generate_vector_curves(self, backend, curve): _skip_curve_unsupported(backend, curve) key = ec.generate_private_key(curve, backend) assert key assert type(key.curve) is type(curve) assert key.curve.key_size pkey = key.public_key() assert pkey assert type(pkey.curve) is type(curve) assert key.curve.key_size == pkey.curve.key_size def test_generate_unknown_curve(self, backend): with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_ELLIPTIC_CURVE ): ec.generate_private_key(DummyCurve(), backend) assert ( backend.elliptic_curve_signature_algorithm_supported( ec.ECDSA(hashes.SHA256()), DummyCurve() ) is False ) def test_unknown_signature_algoritm(self, backend): _skip_curve_unsupported(backend, ec.SECP192R1()) key = ec.generate_private_key(ec.SECP192R1(), backend) with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): key.sign(b"somedata", DummySignatureAlgorithm()) with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): key.public_key().verify( b"signature", b"data", DummySignatureAlgorithm() ) assert ( backend.elliptic_curve_signature_algorithm_supported( DummySignatureAlgorithm(), ec.SECP192R1() ) is False ) def test_load_invalid_ec_key_from_numbers(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) numbers = ec.EllipticCurvePrivateNumbers( 357646505660320080863666618182642070958081774038609089496899025506, ec.EllipticCurvePublicNumbers( 47250808410327023131573602008345894927686381772325561185532964, 1120253292479243545483756778742719537373113335231773536789915, ec.SECP256R1(), ), ) with pytest.raises(ValueError): numbers.private_key(backend) numbers = ec.EllipticCurvePrivateNumbers( 357646505660320080863666618182642070958081774038609089496899025506, ec.EllipticCurvePublicNumbers( -4725080841032702313157360200834589492768638177232556118553296, 1120253292479243545483756778742719537373113335231773536789915, ec.SECP256R1(), ), ) with pytest.raises(ValueError): numbers.private_key(backend) numbers = ec.EllipticCurvePrivateNumbers( 357646505660320080863666618182642070958081774038609089496899025506, ec.EllipticCurvePublicNumbers( 47250808410327023131573602008345894927686381772325561185532964, -1120253292479243545483756778742719537373113335231773536789915, ec.SECP256R1(), ), ) with pytest.raises(ValueError): numbers.private_key(backend) def test_load_invalid_public_ec_key_from_numbers(self, backend): _skip_curve_unsupported(backend, ec.SECP521R1()) # Bad X coordinate numbers = ec.EllipticCurvePublicNumbers( int( "000003647356b91f8ace114c7247ecf4f4a622553fc025e04a178f179ef27" "9090c184af678a4c78f635483bdd8aa544851c6ef291c1f0d6a241ebfd145" "77d1d30d9903ce", 16, ), int( "000001499bc7e079322ea0fcfbd6b40103fa6a1536c2257b182db0df4b369" "6ec643adf100eb4f2025d1b873f82e5a475d6e4400ba777090eeb4563a115" "09e4c87319dc26", 16, ), ec.SECP521R1(), ) with pytest.raises(ValueError): numbers.public_key(backend) # Bad Y coordinate numbers = ec.EllipticCurvePublicNumbers( int( "0000019aadc221cc0525118ab6d5aa1f64720603de0be128cbfea0b381ad8" "02a2facc6370bb58cf88b3f0c692bc654ee19d6cad198f10d4b681b396f20" "d2e40603fa945b", 16, ), int( "0000025da392803a320717a08d4cb3dea932039badff363b71bdb8064e726" "6c7f4f4b748d4d425347fc33e3885d34b750fa7fcd5691f4d90c89522ce33" "feff5db10088a5", 16, ), ec.SECP521R1(), ) with pytest.raises(ValueError): numbers.public_key(backend) def test_load_invalid_ec_key_from_pem(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) # BoringSSL rejects infinity points before it ever gets to us, so it # uses a more generic error message. match = ( r"infinity|invalid form" if not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL else None ) with pytest.raises(ValueError, match=match): serialization.load_pem_public_key( textwrap.dedent( """ -----BEGIN PUBLIC KEY----- MBkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDAgAA -----END PUBLIC KEY----- """ ).encode(), backend=backend, ) with pytest.raises(ValueError, match=match): serialization.load_pem_private_key( textwrap.dedent( """ -----BEGIN PRIVATE KEY----- MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCD/////AAAAAP////// ////vOb6racXnoTzucrC/GMlUQ== -----END PRIVATE KEY----- """ ).encode(), password=None, backend=backend, ) def test_signatures(self, backend, subtests): vectors = itertools.chain( load_vectors_from_file( os.path.join( "asymmetric", "ECDSA", "FIPS_186-3", "SigGen.txt" ), load_fips_ecdsa_signing_vectors, ), load_vectors_from_file( os.path.join("asymmetric", "ECDSA", "SECP256K1", "SigGen.txt"), load_fips_ecdsa_signing_vectors, ), ) for vector in vectors: with subtests.test(): hash_type = _HASH_TYPES[vector["digest_algorithm"]] curve = ec._CURVE_TYPES[vector["curve"]] _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePublicNumbers( vector["x"], vector["y"], curve ).public_key(backend) signature = encode_dss_signature(vector["r"], vector["s"]) key.verify(signature, vector["message"], ec.ECDSA(hash_type())) def test_signature_failures(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("asymmetric", "ECDSA", "FIPS_186-3", "SigVer.rsp"), load_fips_ecdsa_signing_vectors, ) for vector in vectors: with subtests.test(): hash_type = _HASH_TYPES[vector["digest_algorithm"]] curve = ec._CURVE_TYPES[vector["curve"]] _skip_ecdsa_vector(backend, curve, hash_type) key = ec.EllipticCurvePublicNumbers( vector["x"], vector["y"], curve ).public_key(backend) signature = encode_dss_signature(vector["r"], vector["s"]) if vector["fail"] is True: with pytest.raises(exceptions.InvalidSignature): key.verify( signature, vector["message"], ec.ECDSA(hash_type()) ) else: key.verify( signature, vector["message"], ec.ECDSA(hash_type()) ) def test_unsupported_deterministic_nonce(self, backend): if backend.ecdsa_deterministic_supported(): pytest.skip( f"ECDSA deterministic signing is supported by this" f" backend {backend}" ) with pytest.raises(exceptions.UnsupportedAlgorithm): ec.ECDSA(hashes.SHA256(), deterministic_signing=True) def test_deterministic_nonce(self, backend, subtests): if not backend.ecdsa_deterministic_supported(): pytest.skip( f"ECDSA deterministic signing is not supported by this" f" backend {backend}" ) supported_hash_algorithms = { "SHA1": hashes.SHA1(), "SHA224": hashes.SHA224(), "SHA256": hashes.SHA256(), "SHA384": hashes.SHA384(), "SHA512": hashes.SHA512(), } curves = { "B-163": ec.SECT163R2(), "B-233": ec.SECT233R1(), "B-283": ec.SECT283R1(), "B-409": ec.SECT409R1(), "B-571": ec.SECT571R1(), "K-163": ec.SECT163K1(), "K-233": ec.SECT233K1(), "K-283": ec.SECT283K1(), "K-409": ec.SECT409K1(), "K-571": ec.SECT571K1(), "P-192": ec.SECP192R1(), "P-224": ec.SECP224R1(), "P-256": ec.SECP256R1(), "P-384": ec.SECP384R1(), "P-521": ec.SECP521R1(), } vectors = load_vectors_from_file( os.path.join( "asymmetric", "ECDSA", "RFC6979", "evppkey_ecdsa_rfc6979.txt" ), load_rfc6979_vectors, ) for vector in vectors: with subtests.test(): input = bytes(vector["input"], "utf-8") output = bytes.fromhex(vector["output"]) key = bytes("\n".join(vector["key"]), "utf-8") curve = curves[vector["key_name"].split("_")[0]] _skip_curve_unsupported(backend, curve) if "digest_sign" in vector: algorithm = vector["digest_sign"] hash_algorithm = supported_hash_algorithms[algorithm] algorithm = ec.ECDSA( hash_algorithm, deterministic_signing=vector["deterministic_nonce"], ) private_key = serialization.load_pem_private_key( key, password=None ) assert isinstance(private_key, EllipticCurvePrivateKey) signature = private_key.sign(input, algorithm) assert signature == output else: assert "digest_verify" in vector algorithm = vector["digest_verify"] assert algorithm in supported_hash_algorithms hash_algorithm = supported_hash_algorithms[algorithm] algorithm = ec.ECDSA(hash_algorithm) public_key = serialization.load_pem_public_key(key) assert isinstance(public_key, EllipticCurvePublicKey) if vector["verify_error"]: with pytest.raises(exceptions.InvalidSignature): public_key.verify(output, input, algorithm) else: public_key.verify(output, input, algorithm) def test_sign(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" algorithm = ec.ECDSA(hashes.SHA256()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(message, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, algorithm) def test_sign_verify_buffers(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = bytearray(b"one little message") algorithm = ec.ECDSA(hashes.SHA256()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(message, algorithm) public_key = private_key.public_key() public_key.verify(bytearray(signature), message, algorithm) def test_sign_prehashed(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" h = hashes.Hash(hashes.SHA256(), backend) h.update(message) data = h.finalize() algorithm = ec.ECDSA(Prehashed(hashes.SHA256())) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(data, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, ec.ECDSA(hashes.SHA256())) def test_sign_prehashed_digest_mismatch(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" h = hashes.Hash(hashes.SHA224(), backend) h.update(message) data = h.finalize() algorithm = ec.ECDSA(Prehashed(hashes.SHA256())) private_key = ec.generate_private_key(ec.SECP256R1(), backend) with pytest.raises(ValueError): private_key.sign(data, algorithm) def test_verify(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" algorithm = ec.ECDSA(hashes.SHA256()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(message, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, algorithm) def test_verify_prehashed(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" algorithm = ec.ECDSA(hashes.SHA256()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) signature = private_key.sign(message, algorithm) h = hashes.Hash(hashes.SHA256(), backend) h.update(message) data = h.finalize() public_key = private_key.public_key() public_key.verify( signature, data, ec.ECDSA(Prehashed(hashes.SHA256())) ) def test_verify_prehashed_digest_mismatch(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) message = b"one little message" private_key = ec.generate_private_key(ec.SECP256R1(), backend) h = hashes.Hash(hashes.SHA224(), backend) h.update(message) data = h.finalize() public_key = private_key.public_key() with pytest.raises(ValueError): public_key.verify( b"\x00" * 32, data, ec.ECDSA(Prehashed(hashes.SHA256())) ) class TestECEquality: def test_public_numbers_eq(self): pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) assert pub == ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) def test_public_numbers_ne(self): pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) assert pub != ec.EllipticCurvePublicNumbers(1, 2, ec.SECP384R1()) assert pub != ec.EllipticCurvePublicNumbers(1, 3, ec.SECP192R1()) assert pub != ec.EllipticCurvePublicNumbers(2, 2, ec.SECP192R1()) assert pub != object() def test_private_numbers_eq(self): pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) priv = ec.EllipticCurvePrivateNumbers(1, pub) assert priv == ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) ) def test_private_numbers_ne(self): pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) priv = ec.EllipticCurvePrivateNumbers(1, pub) assert priv != ec.EllipticCurvePrivateNumbers( 2, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) ) assert priv != ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(2, 2, ec.SECP192R1()) ) assert priv != ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(1, 3, ec.SECP192R1()) ) assert priv != ec.EllipticCurvePrivateNumbers( 1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP521R1()) ) assert priv != object() def test_public_key_equality(self, backend): _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(), ) key1 = serialization.load_pem_private_key(key_bytes, None).public_key() key2 = serialization.load_pem_private_key(key_bytes, None).public_key() key3 = ec.generate_private_key(ec.SECP256R1()).public_key() assert key1 == key2 assert key1 != key3 assert key1 != object() with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] def test_public_key_copy(self, backend): _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(), ) key1 = serialization.load_pem_private_key(key_bytes, None).public_key() key2 = copy.copy(key1) assert key1 == key2 class TestECSerialization: @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_fips_traditional_openssl(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) assert isinstance(key, ec.EllipticCurvePrivateKey) serialized = key.private_bytes( serialization.Encoding.PEM, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_pem_private_key( serialized, password, backend ) assert isinstance(loaded_key, ec.EllipticCurvePrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.supported( only_if=lambda backend: backend._fips_enabled, skip_message="Requires FIPS", ) def test_traditional_serialization_fips(self, backend): 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) assert isinstance(key, ec.EllipticCurvePrivateKey) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.BestAvailableEncryption(b"password"), ) @pytest.mark.parametrize( ("encoding", "fmt"), [ (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ], ) def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = ec.generate_private_key(ec.SECP256R1(), backend) with pytest.raises(ValueError): key.private_bytes(encoding, fmt, serialization.NoEncryption()) @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], [serialization.PrivateFormat.PKCS8, b"longerpassword"], [serialization.PrivateFormat.PKCS8, b"!*$&(@#$*&($T@%_somesymbol"], [serialization.PrivateFormat.PKCS8, b"\x01" * 1000], ], ) def test_private_bytes_encrypted_der(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) assert isinstance(key, ec.EllipticCurvePrivateKey) serialized = key.private_bytes( serialization.Encoding.DER, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_der_private_key( serialized, password, backend ) assert isinstance(loaded_key, ec.EllipticCurvePrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.parametrize( ("encoding", "fmt", "loader_func"), [ [ serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.load_pem_private_key, ], [ serialization.Encoding.DER, serialization.PrivateFormat.TraditionalOpenSSL, serialization.load_der_private_key, ], [ serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.load_pem_private_key, ], [ serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.load_der_private_key, ], ], ) def test_private_bytes_unencrypted( self, backend, encoding, fmt, loader_func ): _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) assert isinstance(key, ec.EllipticCurvePrivateKey) serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) loaded_key = loader_func(serialized, None, backend) assert isinstance(loaded_key, ec.EllipticCurvePrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.skip_fips( reason="Traditional OpenSSL key format is not supported in FIPS mode." ) @pytest.mark.parametrize( ("key_path", "encoding", "loader_func"), [ [ os.path.join( "asymmetric", "PEM_Serialization", "ec_private_key.pem" ), serialization.Encoding.PEM, serialization.load_pem_private_key, ], [ os.path.join( "asymmetric", "DER_Serialization", "ec_private_key.der" ), serialization.Encoding.DER, serialization.load_der_private_key, ], ], ) def test_private_bytes_traditional_openssl_unencrypted( self, backend, key_path, encoding, loader_func ): _skip_curve_unsupported(backend, ec.SECP256R1()) key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, None, backend) serialized = key.private_bytes( encoding, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption(), ) assert serialized == key_bytes def test_private_bytes_traditional_der_encrypted_invalid(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 ), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, serialization.PrivateFormat.TraditionalOpenSSL, serialization.BestAvailableEncryption(b"password"), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.SMIME, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption(), ) 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 ), ) with pytest.raises(TypeError): key.private_bytes( "notencoding", # type: ignore[arg-type] 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 ), ) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, "invalidformat", # type: ignore[arg-type] 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 ), ) with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, "notanencalg", # type: ignore[arg-type] ) 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 ), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, DummyKeySerializationEncryption(), ) def test_public_bytes_from_derived_public_key(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 ), ) public = key.public_key() pem = public.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ) parsed_public = serialization.load_pem_public_key(pem, backend) assert parsed_public def test_load_private_key_explicit_parameters(self): with pytest.raises(ValueError, match="explicit parameters"): load_vectors_from_file( os.path.join( "asymmetric", "EC", "explicit_parameters_private_key.pem" ), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), password=None ), mode="rb", ) with pytest.raises(ValueError, match="explicit parameters"): load_vectors_from_file( os.path.join( "asymmetric", "EC", "explicit_parameters_wap_wsg_idm_ecid_wtls11_private_key.pem", ), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), password=None ), mode="rb", ) def test_load_private_key_unsupported_curve(self): with pytest.raises((ValueError, exceptions.UnsupportedAlgorithm)): load_vectors_from_file( os.path.join("asymmetric", "EC", "secp128r1_private_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), password=None ), mode="rb", ) @pytest.mark.parametrize( ("key_file", "curve"), [ ("sect163k1-spki.pem", ec.SECT163K1), ("sect163r2-spki.pem", ec.SECT163R2), ("sect233k1-spki.pem", ec.SECT233K1), ("sect233r1-spki.pem", ec.SECT233R1), ], ) def test_load_public_keys(self, key_file, curve, backend): _skip_curve_unsupported(backend, curve()) key = load_vectors_from_file( os.path.join("asymmetric", "EC", key_file), lambda pemfile: serialization.load_pem_public_key( pemfile.read(), ), mode="rb", ) assert isinstance(key, ec.EllipticCurvePublicKey) assert isinstance(key.curve, curve) class TestEllipticCurvePEMPublicKeySerialization: @pytest.mark.parametrize( ("key_path", "loader_func", "encoding"), [ ( os.path.join( "asymmetric", "PEM_Serialization", "ec_public_key.pem" ), serialization.load_pem_public_key, serialization.Encoding.PEM, ), ( os.path.join( "asymmetric", "DER_Serialization", "ec_public_key.der" ), serialization.load_der_public_key, serialization.Encoding.DER, ), ], ) def test_public_bytes_match( self, key_path, loader_func, encoding, backend ): _skip_curve_unsupported(backend, ec.SECP256R1()) key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) serialized = key.public_bytes( encoding, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert serialized == key_bytes def test_public_bytes_openssh(self, backend): _skip_curve_unsupported(backend, ec.SECP192R1()) _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(), mode="rb", ) key = serialization.load_pem_public_key(key_bytes, backend) ssh_bytes = key.public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH ) assert ssh_bytes == ( b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" b"NTYAAABBBCS8827s9rUZyxZTi/um01+oIlWrwLHOjQxRU9CDAndom00zVAw5BRrI" b"KtHB+SWD4P+sVJTARSq1mHt8kOIWrPc=" ) key = ec.generate_private_key(ec.SECP192R1(), backend).public_key() with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH, ) 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 ), ) with pytest.raises(TypeError): key.public_bytes( "notencoding", # type: ignore[arg-type] serialization.PublicFormat.SubjectPublicKeyInfo, ) @pytest.mark.parametrize( ("encoding", "fmt"), list( itertools.product( [ serialization.Encoding.Raw, serialization.Encoding.X962, serialization.Encoding.PEM, serialization.Encoding.DER, ], [serialization.PublicFormat.Raw], ) ) + list( itertools.product( [serialization.Encoding.Raw], [ serialization.PublicFormat.SubjectPublicKeyInfo, serialization.PublicFormat.PKCS1, serialization.PublicFormat.UncompressedPoint, serialization.PublicFormat.CompressedPoint, ], ) ), ) def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = ec.generate_private_key(ec.SECP256R1(), backend).public_key() with pytest.raises(ValueError): key.public_bytes(encoding, fmt) 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 ), ) with pytest.raises(TypeError): key.public_bytes( serialization.Encoding.PEM, "invalidformat", # type: ignore[arg-type] ) 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 ), ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("asymmetric", "EC", "compressed_points.txt"), load_nist_vectors, ), ) def test_from_encoded_point_compressed(self, vector, backend): curve = {b"SECP256R1": ec.SECP256R1(), b"SECP256K1": ec.SECP256K1()}[ vector["curve"] ] _skip_curve_unsupported(backend, curve) point = binascii.unhexlify(vector["point"]) pn = ec.EllipticCurvePublicKey.from_encoded_point(curve, point) public_num = pn.public_numbers() assert public_num.x == int(vector["x"], 16) assert public_num.y == int(vector["y"], 16) def test_from_encoded_point_notoncurve(self): uncompressed_point = binascii.unhexlify( "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" "6e" ) with pytest.raises(ValueError): ec.EllipticCurvePublicKey.from_encoded_point( ec.SECP256R1(), uncompressed_point ) def test_from_encoded_point_uncompressed(self): uncompressed_point = binascii.unhexlify( "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" "6d" ) pn = ec.EllipticCurvePublicKey.from_encoded_point( ec.SECP256R1(), uncompressed_point ) assert pn.public_numbers().x == int( "7399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac68", 16, ) assert pn.public_numbers().y == int( "6699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f6d", 16, ) def test_from_encoded_point_invalid_length(self): bad_data = binascii.unhexlify( "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac" "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f" "6d" ) with pytest.raises(ValueError): ec.EllipticCurvePublicKey.from_encoded_point( ec.SECP384R1(), bad_data ) def test_from_encoded_point_empty_byte_string(self): with pytest.raises(ValueError): ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP384R1(), b"") def test_from_encoded_point_not_a_curve(self): with pytest.raises(TypeError): ec.EllipticCurvePublicKey.from_encoded_point( "notacurve", # type: ignore[arg-type] b"\x04data", ) def test_from_encoded_point_unsupported_encoding(self): unsupported_type = binascii.unhexlify( "057399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac6" "8" ) with pytest.raises(ValueError): ec.EllipticCurvePublicKey.from_encoded_point( ec.SECP256R1(), unsupported_type ) @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("asymmetric", "EC", "compressed_points.txt"), load_nist_vectors, ), ) def test_serialize_point(self, vector, backend): curve = {b"SECP256R1": ec.SECP256R1(), b"SECP256K1": ec.SECP256K1()}[ vector["curve"] ] _skip_curve_unsupported(backend, curve) point = binascii.unhexlify(vector["point"]) key = ec.EllipticCurvePublicKey.from_encoded_point(curve, point) key2 = ec.EllipticCurvePublicKey.from_encoded_point( curve, key.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint, ), ) assert ( key.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.CompressedPoint, ) == point ) assert ( key2.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.CompressedPoint, ) == point ) class TestECDH: def test_key_exchange_with_vectors(self, backend, subtests): vectors = load_vectors_from_file( os.path.join( "asymmetric", "ECDH", "KASValidityTest_ECCStaticUnified_NOKC_ZZOnly_init.fax", ), load_kasvs_ecdh_vectors, ) for vector in vectors: with subtests.test(): _skip_exchange_algorithm_unsupported( backend, ec.ECDH(), ec._CURVE_TYPES[vector["curve"]] ) key_numbers = vector["IUT"] private_numbers = ec.EllipticCurvePrivateNumbers( key_numbers["d"], ec.EllipticCurvePublicNumbers( key_numbers["x"], key_numbers["y"], ec._CURVE_TYPES[vector["curve"]], ), ) # Errno 5-7 indicates a bad public or private key, this # doesn't test the ECDH code at all if vector["fail"] and vector["errno"] in [5, 6, 7]: with pytest.raises(ValueError): private_numbers.private_key(backend) continue else: private_key = private_numbers.private_key(backend) peer_numbers = vector["CAVS"] public_numbers = ec.EllipticCurvePublicNumbers( peer_numbers["x"], peer_numbers["y"], ec._CURVE_TYPES[vector["curve"]], ) # Errno 1 and 2 indicates a bad public key, this doesn't test # the ECDH code at all if vector["fail"] and vector["errno"] in [1, 2]: with pytest.raises(ValueError): public_numbers.public_key(backend) continue else: peer_pubkey = public_numbers.public_key(backend) z = private_key.exchange(ec.ECDH(), peer_pubkey) zz = int(hexlify(z).decode("ascii"), 16) # At this point fail indicates that one of the underlying keys # was changed. This results in a non-matching derived key. if vector["fail"]: # Errno 8 indicates Z should be changed. assert vector["errno"] == 8 assert zz != vector["Z"] else: assert zz == vector["Z"] @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("asymmetric", "ECDH", "brainpool.txt"), load_nist_vectors, ), ) def test_brainpool_kex(self, backend, vector): curve = ec._CURVE_TYPES[vector["curve"].decode("ascii")] _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) key = ec.EllipticCurvePrivateNumbers( int(vector["da"], 16), ec.EllipticCurvePublicNumbers( int(vector["x_qa"], 16), int(vector["y_qa"], 16), curve ), ).private_key(backend) peer = ec.EllipticCurvePrivateNumbers( int(vector["db"], 16), ec.EllipticCurvePublicNumbers( int(vector["x_qb"], 16), int(vector["y_qb"], 16), curve ), ).private_key(backend) shared_secret = key.exchange(ec.ECDH(), peer.public_key()) assert shared_secret == binascii.unhexlify(vector["x_z"]) shared_secret_2 = peer.exchange(ec.ECDH(), key.public_key()) assert shared_secret_2 == binascii.unhexlify(vector["x_z"]) def test_exchange_unsupported_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 ), ) assert isinstance(key, ec.EllipticCurvePrivateKey) with raises_unsupported_algorithm( exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM ): key.exchange(None, key.public_key()) # type: ignore[arg-type] def test_exchange_non_matching_curve(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) _skip_curve_unsupported(backend, ec.SECP384R1()) 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 ), ) assert isinstance(key, ec.EllipticCurvePrivateKey) public_key = EC_KEY_SECP384R1.public_numbers.public_key(backend) with pytest.raises(ValueError): key.exchange(ec.ECDH(), public_key) cryptography-43.0.0/tests/hazmat/primitives/test_ed25519.py010064400017510000177000000260611464676315000217660ustar 00000000000000# 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. import binascii import copy import os import pytest from cryptography.exceptions import InvalidSignature, _Reasons from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import ( Ed25519PrivateKey, Ed25519PublicKey, ) from ...doubles import DummyKeySerializationEncryption from ...utils import load_vectors_from_file, raises_unsupported_algorithm def load_ed25519_vectors(vector_data): """ djb's ed25519 vectors are structured as a colon delimited array: 0: secret key (32 bytes) + public key (32 bytes) 1: public key (32 bytes) 2: message (0+ bytes) 3: signature + message (64+ bytes) """ data = [] for line in vector_data: secret_key, public_key, message, signature, _ = line.split(":") secret_key = secret_key[0:64] signature = signature[0:128] data.append( { "secret_key": secret_key, "public_key": public_key, "message": message, "signature": signature, } ) return data @pytest.mark.supported( only_if=lambda backend: not backend.ed25519_supported(), skip_message="Requires OpenSSL without Ed25519 support", ) def test_ed25519_unsupported(backend): with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): Ed25519PublicKey.from_public_bytes(b"0" * 32) with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): Ed25519PrivateKey.from_private_bytes(b"0" * 32) with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): Ed25519PrivateKey.generate() @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) class TestEd25519Signing: def test_sign_verify_input(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "sign.input"), load_ed25519_vectors, ) for vector in vectors: with subtests.test(): sk = binascii.unhexlify(vector["secret_key"]) pk = binascii.unhexlify(vector["public_key"]) message = binascii.unhexlify(vector["message"]) signature = binascii.unhexlify(vector["signature"]) private_key = Ed25519PrivateKey.from_private_bytes(sk) computed_sig = private_key.sign(message) assert computed_sig == signature public_key = private_key.public_key() assert ( public_key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw, ) == pk ) public_key.verify(signature, message) def test_pub_priv_bytes_raw(self, backend, subtests): vectors = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "sign.input"), load_ed25519_vectors, ) for vector in vectors: with subtests.test(): sk = binascii.unhexlify(vector["secret_key"]) pk = binascii.unhexlify(vector["public_key"]) private_key = Ed25519PrivateKey.from_private_bytes(sk) assert private_key.private_bytes_raw() == sk public_key = Ed25519PublicKey.from_public_bytes(pk) assert public_key.public_bytes_raw() == pk def test_invalid_signature(self, backend): key = Ed25519PrivateKey.generate() signature = key.sign(b"test data") with pytest.raises(InvalidSignature): key.public_key().verify(signature, b"wrong data") with pytest.raises(InvalidSignature): key.public_key().verify(b"0" * 64, b"test data") def test_sign_verify_buffer(self, backend): key = Ed25519PrivateKey.generate() data = bytearray(b"test data") signature = key.sign(data) key.public_key().verify(bytearray(signature), data) def test_generate(self, backend): key = Ed25519PrivateKey.generate() assert key assert key.public_key() def test_load_public_bytes(self, backend): public_key = Ed25519PrivateKey.generate().public_key() public_bytes = public_key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) public_key2 = Ed25519PublicKey.from_public_bytes(public_bytes) assert public_bytes == public_key2.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) def test_invalid_type_public_bytes(self, backend): with pytest.raises(TypeError): Ed25519PublicKey.from_public_bytes( object() # type: ignore[arg-type] ) def test_invalid_type_private_bytes(self, backend): with pytest.raises(TypeError): Ed25519PrivateKey.from_private_bytes( object() # type: ignore[arg-type] ) def test_invalid_length_from_public_bytes(self, backend): with pytest.raises(ValueError): Ed25519PublicKey.from_public_bytes(b"a" * 31) with pytest.raises(ValueError): Ed25519PublicKey.from_public_bytes(b"a" * 33) def test_invalid_length_from_private_bytes(self, backend): with pytest.raises(ValueError): Ed25519PrivateKey.from_private_bytes(b"a" * 31) with pytest.raises(ValueError): Ed25519PrivateKey.from_private_bytes(b"a" * 33) def test_invalid_private_bytes(self, backend): key = Ed25519PrivateKey.generate() with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, None, # type: ignore[arg-type] ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8, DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.Raw, serialization.NoEncryption(), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, serialization.PrivateFormat.OpenSSH, serialization.NoEncryption(), ) def test_invalid_public_bytes(self, backend): key = Ed25519PrivateKey.generate().public_key() with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo, ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.Raw ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.DER, serialization.PublicFormat.OpenSSH ) @pytest.mark.parametrize( ("encoding", "fmt", "encryption", "passwd", "load_func"), [ ( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(b"password"), b"password", serialization.load_pem_private_key, ), ( serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(b"password"), b"password", serialization.load_der_private_key, ), ( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), None, serialization.load_pem_private_key, ), ( serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), None, serialization.load_der_private_key, ), ( serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(b"\x00"), b"\x00", serialization.load_der_private_key, ), ], ) def test_round_trip_private_serialization( self, encoding, fmt, encryption, passwd, load_func, backend ): key = Ed25519PrivateKey.generate() serialized = key.private_bytes(encoding, fmt, encryption) loaded_key = load_func(serialized, passwd, backend) assert isinstance(loaded_key, Ed25519PrivateKey) def test_buffer_protocol(self, backend): private_bytes = os.urandom(32) key = Ed25519PrivateKey.from_private_bytes(bytearray(private_bytes)) assert ( key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption(), ) == private_bytes ) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_public_key_equality(backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key1 = serialization.load_der_private_key(key_bytes, None).public_key() key2 = serialization.load_der_private_key(key_bytes, None).public_key() key3 = Ed25519PrivateKey.generate().public_key() assert key1 == key2 assert key1 != key3 assert key1 != object() with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_public_key_copy(backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key1 = serialization.load_der_private_key(key_bytes, None).public_key() key2 = copy.copy(key1) assert key1 == key2 cryptography-43.0.0/tests/hazmat/primitives/test_ed448.py010064400017510000177000000252061464676315000216200ustar 00000000000000# 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. import binascii import copy import os import pytest from cryptography.exceptions import InvalidSignature, _Reasons from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed448 import ( Ed448PrivateKey, Ed448PublicKey, ) from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) @pytest.mark.supported( only_if=lambda backend: not backend.ed448_supported(), skip_message="Requires OpenSSL without Ed448 support", ) def test_ed448_unsupported(backend): with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): Ed448PublicKey.from_public_bytes(b"0" * 57) with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): Ed448PrivateKey.from_private_bytes(b"0" * 57) with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM ): Ed448PrivateKey.generate() @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) class TestEd448Signing: @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("asymmetric", "Ed448", "rfc8032.txt"), load_nist_vectors, ), ) def test_sign_input(self, vector, backend): if vector.get("context") is not None: pytest.skip("ed448 contexts are not currently supported") sk = binascii.unhexlify(vector["secret"]) pk = binascii.unhexlify(vector["public"]) message = binascii.unhexlify(vector["message"]) signature = binascii.unhexlify(vector["signature"]) private_key = Ed448PrivateKey.from_private_bytes(sk) computed_sig = private_key.sign(message) assert computed_sig == signature public_key = private_key.public_key() assert ( public_key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) == pk ) public_key.verify(signature, message) def test_invalid_signature(self, backend): key = Ed448PrivateKey.generate() signature = key.sign(b"test data") with pytest.raises(InvalidSignature): key.public_key().verify(signature, b"wrong data") with pytest.raises(InvalidSignature): key.public_key().verify(b"0" * 64, b"test data") def test_sign_verify_buffer(self, backend): key = Ed448PrivateKey.generate() data = bytearray(b"test data") signature = key.sign(data) key.public_key().verify(bytearray(signature), data) def test_generate(self, backend): key = Ed448PrivateKey.generate() assert key assert key.public_key() @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("asymmetric", "Ed448", "rfc8032.txt"), load_nist_vectors, ), ) def test_pub_priv_bytes_raw(self, vector, backend): sk = binascii.unhexlify(vector["secret"]) pk = binascii.unhexlify(vector["public"]) private_key = Ed448PrivateKey.from_private_bytes(sk) assert ( private_key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption(), ) == sk ) assert private_key.private_bytes_raw() == sk assert ( private_key.public_key().public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) == pk ) assert private_key.public_key().public_bytes_raw() == pk public_key = Ed448PublicKey.from_public_bytes(pk) assert ( public_key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ) == pk ) assert public_key.public_bytes_raw() == pk @pytest.mark.parametrize( ("encoding", "fmt", "encryption", "passwd", "load_func"), [ ( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(b"password"), b"password", serialization.load_pem_private_key, ), ( serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(b"password"), b"password", serialization.load_der_private_key, ), ( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), None, serialization.load_pem_private_key, ), ( serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), None, serialization.load_der_private_key, ), ], ) def test_round_trip_private_serialization( self, encoding, fmt, encryption, passwd, load_func, backend ): key = Ed448PrivateKey.generate() serialized = key.private_bytes(encoding, fmt, encryption) loaded_key = load_func(serialized, passwd, backend) assert isinstance(loaded_key, Ed448PrivateKey) def test_invalid_type_public_bytes(self, backend): with pytest.raises(TypeError): Ed448PublicKey.from_public_bytes( object() # type: ignore[arg-type] ) def test_invalid_type_private_bytes(self, backend): with pytest.raises(TypeError): Ed448PrivateKey.from_private_bytes( object() # type: ignore[arg-type] ) def test_invalid_length_from_public_bytes(self, backend): with pytest.raises(ValueError): Ed448PublicKey.from_public_bytes(b"a" * 56) with pytest.raises(ValueError): Ed448PublicKey.from_public_bytes(b"a" * 58) def test_invalid_length_from_private_bytes(self, backend): with pytest.raises(ValueError): Ed448PrivateKey.from_private_bytes(b"a" * 56) with pytest.raises(ValueError): Ed448PrivateKey.from_private_bytes(b"a" * 58) def test_invalid_private_bytes(self, backend): key = Ed448PrivateKey.generate() with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, None, # type: ignore[arg-type] ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8, DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.Raw, serialization.NoEncryption(), ) def test_invalid_public_bytes(self, backend): key = Ed448PrivateKey.generate().public_key() with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo, ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.Raw ) def test_buffer_protocol(self, backend): private_bytes = os.urandom(57) key = Ed448PrivateKey.from_private_bytes(bytearray(private_bytes)) assert ( key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption(), ) == private_bytes ) def test_malleability(self, backend): # This is a signature where r > the group order. It should be # rejected to prevent signature malleability issues. This test can # be removed when wycheproof grows ed448 vectors public_bytes = binascii.unhexlify( "fedb02a658d74990244d9d10cf338e977565cbbda6b24c716829ed6ee1e4f28cf" "2620c052db8d878f6243bffc22242816c1aaa67d2f3603600" ) signature = binascii.unhexlify( "0cc16ba24d69277f927c1554b0f08a2a711bbdd20b058ccc660d00ca13542a3ce" "f9e5c44c54ab23a2eb14f947e167b990b080863e28b399380f30db6e54d5d1406" "d23378ffde11b1fb81b2b438a3b8e8aa7f7f4e1befcc905023fab5a5465053844" "f04cf0c1b51d84760f869588687f57500" ) key = Ed448PublicKey.from_public_bytes(public_bytes) with pytest.raises(InvalidSignature): key.verify(signature, b"8") @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_public_key_equality(backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "Ed448", "ed448-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key1 = serialization.load_der_private_key(key_bytes, None).public_key() key2 = serialization.load_der_private_key(key_bytes, None).public_key() key3 = Ed448PrivateKey.generate().public_key() assert key1 == key2 assert key1 != key3 assert key1 != object() with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_public_key_copy(backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "Ed448", "ed448-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key1 = serialization.load_der_private_key(key_bytes, None).public_key() key2 = copy.copy(key1) assert key1 == key2 cryptography-43.0.0/tests/hazmat/primitives/test_hash_vectors.py010064400017510000177000000170551464676315000234630ustar 00000000000000# 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. import binascii import os import pytest from cryptography.hazmat.primitives import hashes from ...utils import load_hash_vectors, load_nist_vectors from .utils import _load_all_params, generate_hash_test @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) class TestSHA1: test_sha1 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA1"), ["SHA1LongMsg.rsp", "SHA1ShortMsg.rsp"], hashes.SHA1(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) class TestSHA224: test_sha224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), ["SHA224LongMsg.rsp", "SHA224ShortMsg.rsp"], hashes.SHA224(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) class TestSHA256: test_sha256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), ["SHA256LongMsg.rsp", "SHA256ShortMsg.rsp"], hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) class TestSHA384: test_sha384 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), ["SHA384LongMsg.rsp", "SHA384ShortMsg.rsp"], hashes.SHA384(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) class TestSHA512: test_sha512 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), ["SHA512LongMsg.rsp", "SHA512ShortMsg.rsp"], hashes.SHA512(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512_224()), skip_message="Does not support SHA512/224", ) class TestSHA512224: test_sha512_224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), ["SHA512_224LongMsg.rsp", "SHA512_224ShortMsg.rsp"], hashes.SHA512_224(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512_256()), skip_message="Does not support SHA512/256", ) class TestSHA512256: test_sha512_256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA2"), ["SHA512_256LongMsg.rsp", "SHA512_256ShortMsg.rsp"], hashes.SHA512_256(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) class TestMD5: test_md5 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "MD5"), ["rfc-1321.txt"], hashes.MD5(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.BLAKE2b(digest_size=64) ), skip_message="Does not support BLAKE2b", ) class TestBLAKE2b: test_b2b = generate_hash_test( load_hash_vectors, os.path.join("hashes", "blake2"), ["blake2b.txt"], hashes.BLAKE2b(digest_size=64), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.BLAKE2s(digest_size=32) ), skip_message="Does not support BLAKE2s", ) class TestBLAKE2s256: test_b2s = generate_hash_test( load_hash_vectors, os.path.join("hashes", "blake2"), ["blake2s.txt"], hashes.BLAKE2s(digest_size=32), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA3_224()), skip_message="Does not support SHA3_224", ) class TestSHA3224: test_sha3_224 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), ["SHA3_224LongMsg.rsp", "SHA3_224ShortMsg.rsp"], hashes.SHA3_224(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA3_256()), skip_message="Does not support SHA3_256", ) class TestSHA3256: test_sha3_256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), ["SHA3_256LongMsg.rsp", "SHA3_256ShortMsg.rsp"], hashes.SHA3_256(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA3_384()), skip_message="Does not support SHA3_384", ) class TestSHA3384: test_sha3_384 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), ["SHA3_384LongMsg.rsp", "SHA3_384ShortMsg.rsp"], hashes.SHA3_384(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA3_512()), skip_message="Does not support SHA3_512", ) class TestSHA3512: test_sha3_512 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHA3"), ["SHA3_512LongMsg.rsp", "SHA3_512ShortMsg.rsp"], hashes.SHA3_512(), ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.SHAKE128(digest_size=16) ), skip_message="Does not support SHAKE128", ) class TestSHAKE128: test_shake128 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHAKE"), ["SHAKE128LongMsg.rsp", "SHAKE128ShortMsg.rsp"], hashes.SHAKE128(digest_size=16), ) def test_shake128_variable(self, backend, subtests): vectors = _load_all_params( os.path.join("hashes", "SHAKE"), ["SHAKE128VariableOut.rsp"], load_nist_vectors, ) for vector in vectors: with subtests.test(): output_length = int(vector["outputlen"]) // 8 msg = binascii.unhexlify(vector["msg"]) shake = hashes.SHAKE128(digest_size=output_length) m = hashes.Hash(shake, backend=backend) m.update(msg) assert m.finalize() == binascii.unhexlify(vector["output"]) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.SHAKE256(digest_size=32) ), skip_message="Does not support SHAKE256", ) class TestSHAKE256: test_shake256 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SHAKE"), ["SHAKE256LongMsg.rsp", "SHAKE256ShortMsg.rsp"], hashes.SHAKE256(digest_size=32), ) def test_shake256_variable(self, backend, subtests): vectors = _load_all_params( os.path.join("hashes", "SHAKE"), ["SHAKE256VariableOut.rsp"], load_nist_vectors, ) for vector in vectors: with subtests.test(): output_length = int(vector["outputlen"]) // 8 msg = binascii.unhexlify(vector["msg"]) shake = hashes.SHAKE256(digest_size=output_length) m = hashes.Hash(shake, backend=backend) m.update(msg) assert m.finalize() == binascii.unhexlify(vector["output"]) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SM3()), skip_message="Does not support SM3", ) class TestSM3: test_sm3 = generate_hash_test( load_hash_vectors, os.path.join("hashes", "SM3"), ["oscca.txt"], hashes.SM3(), ) cryptography-43.0.0/tests/hazmat/primitives/test_hashes.py010064400017510000177000000117651464676315000222500ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import AlreadyFinalized, _Reasons from cryptography.hazmat.primitives import hashes from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm from .utils import generate_base_hash_test class TestHashContext: def test_hash_reject_unicode(self, backend): m = hashes.Hash(hashes.SHA1(), backend=backend) with pytest.raises(TypeError): m.update("\u00fc") # type: ignore[arg-type] def test_hash_algorithm_instance(self, backend): with pytest.raises(TypeError): hashes.Hash(hashes.SHA1, backend=backend) # type: ignore[arg-type] def test_raises_after_finalize(self, backend): h = hashes.Hash(hashes.SHA1(), backend=backend) h.finalize() with pytest.raises(AlreadyFinalized): h.update(b"foo") with pytest.raises(AlreadyFinalized): h.copy() with pytest.raises(AlreadyFinalized): h.finalize() def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): hashes.Hash(DummyHashAlgorithm(), backend) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) class TestSHA1: test_sha1 = generate_base_hash_test( hashes.SHA1(), digest_size=20, ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) class TestSHA224: test_sha224 = generate_base_hash_test( hashes.SHA224(), digest_size=28, ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) class TestSHA256: test_sha256 = generate_base_hash_test( hashes.SHA256(), digest_size=32, ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) class TestSHA384: test_sha384 = generate_base_hash_test( hashes.SHA384(), digest_size=48, ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) class TestSHA512: test_sha512 = generate_base_hash_test( hashes.SHA512(), digest_size=64, ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) class TestMD5: test_md5 = generate_base_hash_test( hashes.MD5(), digest_size=16, ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.BLAKE2b(digest_size=64) ), skip_message="Does not support BLAKE2b", ) class TestBLAKE2b: test_blake2b = generate_base_hash_test( hashes.BLAKE2b(digest_size=64), digest_size=64, ) def test_invalid_digest_size(self, backend): with pytest.raises(ValueError): hashes.BLAKE2b(digest_size=65) with pytest.raises(ValueError): hashes.BLAKE2b(digest_size=0) with pytest.raises(ValueError): hashes.BLAKE2b(digest_size=-1) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.BLAKE2s(digest_size=32) ), skip_message="Does not support BLAKE2s", ) class TestBLAKE2s: test_blake2s = generate_base_hash_test( hashes.BLAKE2s(digest_size=32), digest_size=32, ) def test_invalid_digest_size(self, backend): with pytest.raises(ValueError): hashes.BLAKE2s(digest_size=33) with pytest.raises(ValueError): hashes.BLAKE2s(digest_size=0) with pytest.raises(ValueError): hashes.BLAKE2s(digest_size=-1) def test_buffer_protocol_hash(backend): data = binascii.unhexlify(b"b4190e") h = hashes.Hash(hashes.SHA256(), backend) h.update(bytearray(data)) assert h.finalize() == binascii.unhexlify( b"dff2e73091f6c05e528896c4c831b9448653dc2ff043528f6769437bc7b975c2" ) class TestSHAKE: @pytest.mark.parametrize("xof", [hashes.SHAKE128, hashes.SHAKE256]) def test_invalid_digest_type(self, xof): with pytest.raises(TypeError): xof(digest_size=object()) @pytest.mark.parametrize("xof", [hashes.SHAKE128, hashes.SHAKE256]) def test_invalid_digest_size(self, xof): with pytest.raises(ValueError): xof(digest_size=-5) with pytest.raises(ValueError): xof(digest_size=0) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SM3()), skip_message="Does not support SM3", ) class TestSM3: test_sm3 = generate_base_hash_test( hashes.SM3(), digest_size=32, ) cryptography-43.0.0/tests/hazmat/primitives/test_hkdf.py010064400017510000177000000147611464676315000217100ustar 00000000000000# 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. import binascii import os import pytest from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand from ...utils import load_nist_vectors, load_vectors_from_file class TestHKDF: def test_length_limit(self, backend): big_length = 255 * hashes.SHA256().digest_size + 1 with pytest.raises(ValueError): HKDF( hashes.SHA256(), big_length, salt=None, info=None, backend=backend, ) def test_already_finalized(self, backend): hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend) hkdf.derive(b"\x01" * 16) with pytest.raises(AlreadyFinalized): hkdf.derive(b"\x02" * 16) hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend) hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") with pytest.raises(AlreadyFinalized): hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend) def test_verify(self, backend): hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend) hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") def test_verify_invalid(self, backend): hkdf = HKDF(hashes.SHA256(), 16, salt=None, info=None, backend=backend) with pytest.raises(InvalidKey): hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") def test_unicode_typeerror(self, backend): with pytest.raises(TypeError): HKDF( hashes.SHA256(), 16, salt="foo", # type: ignore[arg-type] info=None, backend=backend, ) with pytest.raises(TypeError): HKDF( hashes.SHA256(), 16, salt=None, info="foo", # type: ignore[arg-type] backend=backend, ) with pytest.raises(TypeError): hkdf = HKDF( hashes.SHA256(), 16, salt=None, info=None, backend=backend ) hkdf.derive("foo") # type: ignore[arg-type] with pytest.raises(TypeError): hkdf = HKDF( hashes.SHA256(), 16, salt=None, info=None, backend=backend ) hkdf.verify("foo", b"bar") # type: ignore[arg-type] with pytest.raises(TypeError): hkdf = HKDF( hashes.SHA256(), 16, salt=None, info=None, backend=backend ) hkdf.verify(b"foo", "bar") # type: ignore[arg-type] def test_derive_short_output(self, backend): hkdf = HKDF(hashes.SHA256(), 4, salt=None, info=None, backend=backend) assert hkdf.derive(b"\x01" * 16) == b"gJ\xfb{" def test_derive_long_output(self, backend): vector = load_vectors_from_file( os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors )[0] hkdf = HKDF( hashes.SHA256(), int(vector["l"]), salt=vector["salt"], info=vector["info"], backend=backend, ) ikm = binascii.unhexlify(vector["ikm"]) assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"]) def test_buffer_protocol(self, backend): vector = load_vectors_from_file( os.path.join("KDF", "hkdf-generated.txt"), load_nist_vectors )[0] hkdf = HKDF( hashes.SHA256(), int(vector["l"]), salt=vector["salt"], info=vector["info"], backend=backend, ) ikm = bytearray(binascii.unhexlify(vector["ikm"])) assert hkdf.derive(ikm) == binascii.unhexlify(vector["okm"]) class TestHKDFExpand: def test_derive(self, backend): prk = binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" ) okm = ( b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" b"5bf34007208d5b887185865" ) info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) assert binascii.hexlify(hkdf.derive(prk)) == okm def test_buffer_protocol(self, backend): prk = bytearray( binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2" b"b3e5" ) ) okm = ( b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" b"5bf34007208d5b887185865" ) info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) assert binascii.hexlify(hkdf.derive(prk)) == okm def test_verify(self, backend): prk = binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" ) okm = ( b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" b"5bf34007208d5b887185865" ) info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) hkdf.verify(prk, binascii.unhexlify(okm)) def test_invalid_verify(self, backend): prk = binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" ) info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) with pytest.raises(InvalidKey): hkdf.verify(prk, b"wrong key") def test_already_finalized(self, backend): info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) hkdf.derive(b"first") with pytest.raises(AlreadyFinalized): hkdf.derive(b"second") def test_unicode_error(self, backend): info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) with pytest.raises(TypeError): hkdf.derive("first") # type: ignore[arg-type] cryptography-43.0.0/tests/hazmat/primitives/test_hkdf_vectors.py010064400017510000177000000017321464676315000234470ustar 00000000000000# 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. import os import pytest from cryptography.hazmat.primitives import hashes from ...utils import load_nist_vectors from .utils import generate_hkdf_test @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1.", ) class TestHKDFSHA1: test_hkdfsha1 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA1.txt"], hashes.SHA1(), ) @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), skip_message="Does not support SHA256.", ) class TestHKDFSHA256: test_hkdfsha256 = generate_hkdf_test( load_nist_vectors, os.path.join("KDF"), ["rfc-5869-HKDF-SHA256.txt"], hashes.SHA256(), ) cryptography-43.0.0/tests/hazmat/primitives/test_hmac.py010064400017510000177000000060371464676315000217010ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons, ) from cryptography.hazmat.primitives import hashes, hmac from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm from .utils import generate_base_hmac_test @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) class TestHMACCopy: test_copy = generate_base_hmac_test( hashes.MD5(), ) class TestHMAC: def test_hmac_reject_unicode(self, backend): h = hmac.HMAC(b"mykey", hashes.SHA1(), backend=backend) with pytest.raises(TypeError): h.update("\u00fc") # type: ignore[arg-type] def test_hmac_algorithm_instance(self, backend): with pytest.raises(TypeError): hmac.HMAC( b"key", hashes.SHA1, # type: ignore[arg-type] backend=backend, ) def test_raises_after_finalize(self, backend): h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend) h.finalize() with pytest.raises(AlreadyFinalized): h.update(b"foo") with pytest.raises(AlreadyFinalized): h.copy() with pytest.raises(AlreadyFinalized): h.finalize() def test_verify(self, backend): h = hmac.HMAC(b"", hashes.SHA1(), backend=backend) digest = h.finalize() h = hmac.HMAC(b"", hashes.SHA1(), backend=backend) h.verify(digest) with pytest.raises(AlreadyFinalized): h.verify(b"") def test_invalid_verify(self, backend): h = hmac.HMAC(b"", hashes.SHA1(), backend=backend) with pytest.raises(InvalidSignature): h.verify(b"") with pytest.raises(AlreadyFinalized): h.verify(b"") def test_verify_reject_unicode(self, backend): h = hmac.HMAC(b"", hashes.SHA1(), backend=backend) with pytest.raises(TypeError): h.verify("") # type: ignore[arg-type] def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): hmac.HMAC(b"key", DummyHashAlgorithm(), backend) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): hmac.HMAC(b"key", hashes.SHAKE256(digest_size=256), backend) def test_buffer_protocol(self, backend): key = bytearray(b"2b7e151628aed2a6abf7158809cf4f3c") h = hmac.HMAC(key, hashes.SHA256(), backend) h.update(bytearray(b"6bc1bee22e409f96e93d7e117393172a")) assert h.finalize() == binascii.unhexlify( b"a1bf7169c56a501c6585190ff4f07cad6e492a3ee187c0372614fb444b9fc3f0" ) def test_algorithm(self): alg = hashes.SHA256() h = hmac.HMAC(b"123456", alg) assert h.algorithm is alg cryptography-43.0.0/tests/hazmat/primitives/test_hmac_vectors.py010064400017510000177000000060011464676315000234350ustar 00000000000000# 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. import binascii import pytest from cryptography.hazmat.primitives import hashes, hmac from ...utils import load_hash_vectors from .utils import generate_hmac_test @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) class TestHMACMD5: test_hmac_md5 = generate_hmac_test( load_hash_vectors, "HMAC", ["rfc-2202-md5.txt"], hashes.MD5(), ) @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) class TestHMACSHA1: test_hmac_sha1 = generate_hmac_test( load_hash_vectors, "HMAC", ["rfc-2202-sha1.txt"], hashes.SHA1(), ) @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) class TestHMACSHA224: test_hmac_sha224 = generate_hmac_test( load_hash_vectors, "HMAC", ["rfc-4231-sha224.txt"], hashes.SHA224(), ) @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) class TestHMACSHA256: test_hmac_sha256 = generate_hmac_test( load_hash_vectors, "HMAC", ["rfc-4231-sha256.txt"], hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) class TestHMACSHA384: test_hmac_sha384 = generate_hmac_test( load_hash_vectors, "HMAC", ["rfc-4231-sha384.txt"], hashes.SHA384(), ) @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) class TestHMACSHA512: test_hmac_sha512 = generate_hmac_test( load_hash_vectors, "HMAC", ["rfc-4231-sha512.txt"], hashes.SHA512(), ) @pytest.mark.supported( only_if=lambda backend: backend.hmac_supported( hashes.BLAKE2b(digest_size=64) ), skip_message="Does not support BLAKE2", ) class TestHMACBLAKE2: def test_blake2b(self, backend): h = hmac.HMAC(b"0" * 64, hashes.BLAKE2b(digest_size=64), backend) h.update(b"test") digest = h.finalize() assert digest == binascii.unhexlify( b"b5319122f8a24ba134a0c9851922448104e25be5d1b91265c0c68b22722f0f29" b"87dba4aeaa69e6bed7edc44f48d6b1be493a3ce583f9c737c53d6bacc09e2f32" ) def test_blake2s(self, backend): h = hmac.HMAC(b"0" * 32, hashes.BLAKE2s(digest_size=32), backend) h.update(b"test") digest = h.finalize() assert digest == binascii.unhexlify( b"51477cc5bdf1faf952cf97bb934ee936de1f4d5d7448a84eeb6f98d23b392166" ) cryptography-43.0.0/tests/hazmat/primitives/test_kbkdf.py010064400017510000177000000647721464676315000220640ustar 00000000000000# 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. import re import pytest from cryptography.exceptions import AlreadyFinalized, InvalidKey, _Reasons from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.kdf.kbkdf import ( KBKDFCMAC, KBKDFHMAC, CounterLocation, Mode, ) from ...doubles import ( DummyBlockCipherAlgorithm, DummyCipherAlgorithm, DummyHashAlgorithm, ) from ...utils import raises_unsupported_algorithm class TestKBKDFHMAC: def test_invalid_key(self, backend): kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) key = kdf.derive(b"material") kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with pytest.raises(InvalidKey): kdf.verify(b"material2", key) def test_already_finalized(self, backend): kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) kdf.derive(b"material") with pytest.raises(AlreadyFinalized): kdf.derive(b"material2") kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) key = kdf.derive(b"material") with pytest.raises(AlreadyFinalized): kdf.verify(b"material", key) kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) kdf.verify(b"material", key) with pytest.raises(AlreadyFinalized): kdf.verify(b"material", key) def test_key_length(self, backend): kdf = KBKDFHMAC( hashes.SHA1(), Mode.CounterMode, 85899345920, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with pytest.raises(ValueError): kdf.derive(b"material") def test_rlen(self, backend): with pytest.raises(ValueError): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 5, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_r_type(self, backend): with pytest.raises(TypeError): KBKDFHMAC( hashes.SHA1(), Mode.CounterMode, 32, b"r", # type: ignore[arg-type] 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_zero_llen(self, backend): with pytest.raises(ValueError): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 0, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_l_type(self, backend): with pytest.raises(TypeError): KBKDFHMAC( hashes.SHA1(), Mode.CounterMode, 32, 4, b"l", # type: ignore[arg-type] CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_l(self, backend): with pytest.raises(ValueError): KBKDFHMAC( hashes.SHA1(), Mode.CounterMode, 32, 4, None, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_unsupported_mode(self, backend): with pytest.raises(TypeError): KBKDFHMAC( hashes.SHA256(), None, # type: ignore[arg-type] 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_unsupported_location(self, backend): with pytest.raises(TypeError): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, None, # type: ignore[arg-type] b"label", b"context", None, backend=backend, ) def test_unsupported_parameters(self, backend): with pytest.raises(ValueError): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", b"fixed", backend=backend, ) def test_missing_break_location(self, backend): with pytest.raises( ValueError, match=re.escape("Please specify a break_location") ): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, ) with pytest.raises( ValueError, match=re.escape("Please specify a break_location") ): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location=None, ) def test_keyword_only_break_location(self, backend): with pytest.raises( TypeError, match=r"\d+ positional arguments but \d+ were given\Z" ): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend, 0, # break_location ) # type: ignore def test_invalid_break_location(self, backend): with pytest.raises( TypeError, match=re.escape("break_location must be an integer") ): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location="0", # type: ignore[arg-type] ) with pytest.raises( ValueError, match=re.escape("break_location must be a positive integer"), ): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location=-1, ) with pytest.raises( ValueError, match=re.escape("break_location offset > len(fixed)") ): kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location=18, ) kdf.derive(b"input key") def test_ignored_break_location_before(self, backend): with pytest.raises( ValueError, match=re.escape( "break_location is ignored when location is not" " CounterLocation.MiddleFixed" ), ): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, break_location=0, ) def test_ignored_break_location_after(self, backend): with pytest.raises( ValueError, match=re.escape( "break_location is ignored when location is not" " CounterLocation.MiddleFixed" ), ): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.AfterFixed, b"label", b"context", None, backend=backend, break_location=0, ) def test_unsupported_hash(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): KBKDFHMAC( object(), # type: ignore[arg-type] Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_unsupported_algorithm(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): KBKDFHMAC( DummyHashAlgorithm(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_unicode_error_label(self, backend): with pytest.raises(TypeError): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, "label", # type: ignore[arg-type] b"context", None, backend=backend, ) def test_unicode_error_context(self, backend): with pytest.raises(TypeError): KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", "context", # type: ignore[arg-type] None, backend=backend, ) def test_unicode_error_key_material(self, backend): with pytest.raises(TypeError): kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) kdf.derive("material") # type: ignore[arg-type] def test_buffer_protocol(self, backend): kdf = KBKDFHMAC( hashes.SHA256(), Mode.CounterMode, 10, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) key = kdf.derive(bytearray(b"material")) assert key == b"\xb7\x01\x05\x98\xf5\x1a\x12L\xc7." class TestKBKDFCMAC: _KEY_MATERIAL = bytes(32) _KEY_MATERIAL2 = _KEY_MATERIAL.replace(b"\x00", b"\x01", 1) def test_invalid_key(self, backend): kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) key = kdf.derive(self._KEY_MATERIAL) kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with pytest.raises(InvalidKey): kdf.verify(self._KEY_MATERIAL2, key) def test_already_finalized(self, backend): kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) kdf.derive(self._KEY_MATERIAL) with pytest.raises(AlreadyFinalized): kdf.derive(self._KEY_MATERIAL2) kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) key = kdf.derive(self._KEY_MATERIAL) with pytest.raises(AlreadyFinalized): kdf.verify(self._KEY_MATERIAL, key) kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) kdf.verify(self._KEY_MATERIAL, key) with pytest.raises(AlreadyFinalized): kdf.verify(self._KEY_MATERIAL, key) def test_key_length(self, backend): kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 85899345920, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with pytest.raises(ValueError): kdf.derive(self._KEY_MATERIAL) def test_rlen(self, backend): with pytest.raises(ValueError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 5, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_r_type(self, backend): with pytest.raises(TypeError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, b"r", # type: ignore[arg-type] 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_zero_llen(self, backend): with pytest.raises(ValueError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 0, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_l_type(self, backend): with pytest.raises(TypeError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, b"l", # type: ignore[arg-type] CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_l(self, backend): with pytest.raises(ValueError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, None, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_unsupported_mode(self, backend): with pytest.raises(TypeError): KBKDFCMAC( algorithms.AES, None, # type: ignore[arg-type] 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_unsupported_location(self, backend): with pytest.raises(TypeError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, None, # type: ignore[arg-type] b"label", b"context", None, backend=backend, ) def test_unsupported_parameters(self, backend): with pytest.raises(ValueError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", b"fixed", backend=backend, ) def test_missing_break_location(self, backend): with pytest.raises( ValueError, match=re.escape("Please specify a break_location") ): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, ) with pytest.raises( ValueError, match=re.escape("Please specify a break_location") ): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location=None, ) def test_keyword_only_break_location(self, backend): with pytest.raises( TypeError, match=r"\d+ positional arguments but \d+ were given\Z" ): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend, 0, # break_location ) # type: ignore def test_invalid_break_location(self, backend): with pytest.raises( TypeError, match=re.escape("break_location must be an integer") ): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location="0", # type: ignore[arg-type] ) with pytest.raises( ValueError, match=re.escape("break_location must be a positive integer"), ): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location=-1, ) with pytest.raises( ValueError, match=re.escape("break_location offset > len(fixed)") ): kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.MiddleFixed, b"label", b"context", None, backend=backend, break_location=18, ) kdf.derive(b"32 bytes long input key material") def test_ignored_break_location_before(self, backend): with pytest.raises( ValueError, match=re.escape( "break_location is ignored when location is not" " CounterLocation.MiddleFixed" ), ): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, break_location=0, ) def test_ignored_break_location_after(self, backend): with pytest.raises( ValueError, match=re.escape( "break_location is ignored when location is not" " CounterLocation.MiddleFixed" ), ): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.AfterFixed, b"label", b"context", None, backend=backend, break_location=0, ) def test_unsupported_algorithm(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): KBKDFCMAC( object, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): KBKDFCMAC( DummyCipherAlgorithm, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): KBKDFCMAC( algorithms.ChaCha20, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) def test_unicode_error_label(self, backend): with pytest.raises(TypeError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, "label", # type: ignore[arg-type] b"context", None, backend=backend, ) def test_unicode_error_context(self, backend): with pytest.raises(TypeError): KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", "context", # type: ignore[arg-type] None, backend=backend, ) def test_unsupported_cipher(self, backend): kdf = KBKDFCMAC( DummyBlockCipherAlgorithm, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): kdf.derive(self._KEY_MATERIAL) def test_unicode_error_key_material(self, backend): kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with pytest.raises(TypeError): kdf.derive("material") # type: ignore[arg-type] def test_wrong_key_material_length(self, backend): kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 32, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) with pytest.raises(ValueError): kdf.derive(b"material") def test_buffer_protocol(self, backend): kdf = KBKDFCMAC( algorithms.AES, Mode.CounterMode, 10, 4, 4, CounterLocation.BeforeFixed, b"label", b"context", None, backend=backend, ) key = kdf.derive(bytearray(self._KEY_MATERIAL)) assert key == b"\x19\xcd\xbe\x17Lb\x115<\xd0" cryptography-43.0.0/tests/hazmat/primitives/test_kbkdf_vectors.py010064400017510000177000000007471464676315000236210ustar 00000000000000# 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. import os from ...utils import load_nist_kbkdf_vectors from .utils import generate_kbkdf_counter_mode_test class TestCounterKDFCounterMode: test_kbkdfctr = generate_kbkdf_counter_mode_test( load_nist_kbkdf_vectors, os.path.join("KDF"), ["nist-800-108-KBKDF-CTR.txt"], ) cryptography-43.0.0/tests/hazmat/primitives/test_keywrap.py010064400017510000177000000201741464676315000224510ustar 00000000000000# 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. import binascii import os import pytest from cryptography.hazmat.primitives import keywrap from cryptography.hazmat.primitives.ciphers import algorithms, modes from ...utils import load_nist_vectors from .utils import _load_all_params class TestAESKeyWrap: @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" " is unsupported", ) def test_wrap(self, backend, subtests): params = _load_all_params( os.path.join("keywrap", "kwtestvectors"), ["KW_AE_128.txt", "KW_AE_192.txt", "KW_AE_256.txt"], load_nist_vectors, ) for param in params: with subtests.test(): wrapping_key = binascii.unhexlify(param["k"]) key_to_wrap = binascii.unhexlify(param["p"]) wrapped_key = keywrap.aes_key_wrap( wrapping_key, key_to_wrap, backend ) assert param["c"] == binascii.hexlify(wrapped_key) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" " is unsupported", ) def test_unwrap(self, backend, subtests): params = _load_all_params( os.path.join("keywrap", "kwtestvectors"), ["KW_AD_128.txt", "KW_AD_192.txt", "KW_AD_256.txt"], load_nist_vectors, ) for param in params: with subtests.test(): wrapping_key = binascii.unhexlify(param["k"]) wrapped_key = binascii.unhexlify(param["c"]) if param.get("fail") is True: with pytest.raises(keywrap.InvalidUnwrap): keywrap.aes_key_unwrap( wrapping_key, wrapped_key, backend ) else: unwrapped_key = keywrap.aes_key_unwrap( wrapping_key, wrapped_key, backend ) assert param["p"] == binascii.hexlify(unwrapped_key) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" " is unsupported", ) def test_wrap_invalid_key_length(self, backend): # The wrapping key must be of length [16, 24, 32] with pytest.raises(ValueError): keywrap.aes_key_wrap(b"badkey", b"sixteen_byte_key", backend) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" " is unsupported", ) def test_unwrap_invalid_key_length(self, backend): with pytest.raises(ValueError): keywrap.aes_key_unwrap(b"badkey", b"\x00" * 24, backend) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" " is unsupported", ) def test_wrap_invalid_key_to_wrap_length(self, backend): # Keys to wrap must be at least 16 bytes long with pytest.raises(ValueError): keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 15, backend) # Keys to wrap must be a multiple of 8 bytes with pytest.raises(ValueError): keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 23, backend) def test_unwrap_invalid_wrapped_key_length(self, backend): # Keys to unwrap must be at least 24 bytes with pytest.raises(keywrap.InvalidUnwrap): keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 16, backend) # Keys to unwrap must be a multiple of 8 bytes with pytest.raises(keywrap.InvalidUnwrap): keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 27, backend) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 16), modes.ECB() ), skip_message="Does not support AES key wrap (RFC 5649) because AES-ECB" " is unsupported", ) class TestAESKeyWrapWithPadding: def test_wrap(self, backend, subtests): params = _load_all_params( os.path.join("keywrap", "kwtestvectors"), ["KWP_AE_128.txt", "KWP_AE_192.txt", "KWP_AE_256.txt"], load_nist_vectors, ) for param in params: with subtests.test(): wrapping_key = binascii.unhexlify(param["k"]) key_to_wrap = binascii.unhexlify(param["p"]) wrapped_key = keywrap.aes_key_wrap_with_padding( wrapping_key, key_to_wrap, backend ) assert param["c"] == binascii.hexlify(wrapped_key) def test_wrap_additional_vectors(self, backend, subtests): params = _load_all_params( "keywrap", ["kwp_botan.txt"], load_nist_vectors ) for param in params: with subtests.test(): wrapping_key = binascii.unhexlify(param["key"]) key_to_wrap = binascii.unhexlify(param["input"]) wrapped_key = keywrap.aes_key_wrap_with_padding( wrapping_key, key_to_wrap, backend ) assert wrapped_key == binascii.unhexlify(param["output"]) def test_unwrap(self, backend, subtests): params = _load_all_params( os.path.join("keywrap", "kwtestvectors"), ["KWP_AD_128.txt", "KWP_AD_192.txt", "KWP_AD_256.txt"], load_nist_vectors, ) for param in params: with subtests.test(): wrapping_key = binascii.unhexlify(param["k"]) wrapped_key = binascii.unhexlify(param["c"]) if param.get("fail") is True: with pytest.raises(keywrap.InvalidUnwrap): keywrap.aes_key_unwrap_with_padding( wrapping_key, wrapped_key, backend ) else: unwrapped_key = keywrap.aes_key_unwrap_with_padding( wrapping_key, wrapped_key, backend ) assert param["p"] == binascii.hexlify(unwrapped_key) def test_unwrap_additional_vectors(self, backend, subtests): params = _load_all_params( "keywrap", ["kwp_botan.txt"], load_nist_vectors ) for param in params: with subtests.test(): wrapping_key = binascii.unhexlify(param["key"]) wrapped_key = binascii.unhexlify(param["output"]) unwrapped_key = keywrap.aes_key_unwrap_with_padding( wrapping_key, wrapped_key, backend ) assert unwrapped_key == binascii.unhexlify(param["input"]) def test_unwrap_invalid_wrapped_key_length(self, backend): # Keys to unwrap must be at least 16 bytes with pytest.raises( keywrap.InvalidUnwrap, match="Must be at least 16 bytes" ): keywrap.aes_key_unwrap_with_padding( b"sixteen_byte_key", b"\x00" * 15, backend ) def test_wrap_invalid_key_length(self, backend): with pytest.raises(ValueError, match="must be a valid AES key length"): keywrap.aes_key_wrap_with_padding(b"badkey", b"\x00", backend) def test_unwrap_invalid_key_length(self, backend): with pytest.raises(ValueError, match="must be a valid AES key length"): keywrap.aes_key_unwrap_with_padding( b"badkey", b"\x00" * 16, backend ) cryptography-43.0.0/tests/hazmat/primitives/test_padding.py010064400017510000177000000173231464676315000223770ustar 00000000000000# 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. import pytest from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import padding class TestPKCS7: @pytest.mark.parametrize("size", [127, 4096, -2]) def test_invalid_block_size(self, size): with pytest.raises(ValueError): padding.PKCS7(size) @pytest.mark.parametrize( ("size", "padded"), [ (128, b"1111"), (128, b"1111111111111111"), (128, b"111111111111111\x06"), (128, b""), (128, b"\x06" * 6), (128, b"\x00" * 16), ], ) def test_invalid_padding(self, size, padded): unpadder = padding.PKCS7(size).unpadder() with pytest.raises(ValueError): unpadder.update(padded) unpadder.finalize() def test_non_bytes(self): padder = padding.PKCS7(128).padder() with pytest.raises(TypeError): padder.update("abc") # type: ignore[arg-type] unpadder = padding.PKCS7(128).unpadder() with pytest.raises(TypeError): unpadder.update("abc") # type: ignore[arg-type] def test_zany_py2_bytes_subclass(self): class mybytes(bytes): # noqa: N801 def __str__(self): return "broken" str(mybytes()) padder = padding.PKCS7(128).padder() data = padder.update(mybytes(b"abc")) + padder.finalize() unpadder = padding.PKCS7(128).unpadder() unpadder.update(mybytes(data)) assert unpadder.finalize() == b"abc" @pytest.mark.parametrize( ("size", "unpadded", "padded"), [ (128, b"1111111111", b"1111111111\x06\x06\x06\x06\x06\x06"), ( 128, b"111111111111111122222222222222", b"111111111111111122222222222222\x02\x02", ), (128, b"1" * 16, b"1" * 16 + b"\x10" * 16), (128, b"1" * 17, b"1" * 17 + b"\x0f" * 15), ], ) def test_pad(self, size, unpadded, padded): padder = padding.PKCS7(size).padder() result = padder.update(unpadded) result += padder.finalize() assert result == padded @pytest.mark.parametrize( ("size", "unpadded", "padded"), [ (128, b"1111111111", b"1111111111\x06\x06\x06\x06\x06\x06"), ( 128, b"111111111111111122222222222222", b"111111111111111122222222222222\x02\x02", ), ], ) def test_unpad(self, size, unpadded, padded): unpadder = padding.PKCS7(size).unpadder() result = unpadder.update(padded) result += unpadder.finalize() assert result == unpadded def test_use_after_finalize(self): padder = padding.PKCS7(128).padder() b = padder.finalize() with pytest.raises(AlreadyFinalized): padder.update(b"") with pytest.raises(AlreadyFinalized): padder.finalize() unpadder = padding.PKCS7(128).unpadder() unpadder.update(b) unpadder.finalize() with pytest.raises(AlreadyFinalized): unpadder.update(b"") with pytest.raises(AlreadyFinalized): unpadder.finalize() def test_large_padding(self): padder = padding.PKCS7(2040).padder() padded_data = padder.update(b"") padded_data += padder.finalize() for i in padded_data: assert i == 255 unpadder = padding.PKCS7(2040).unpadder() data = unpadder.update(padded_data) data += unpadder.finalize() assert data == b"" def test_bytearray(self): padder = padding.PKCS7(128).padder() unpadded = bytearray(b"t" * 38) padded = ( padder.update(unpadded) + padder.update(unpadded) + padder.finalize() ) unpadder = padding.PKCS7(128).unpadder() final = unpadder.update(padded) + unpadder.finalize() assert final == unpadded + unpadded class TestANSIX923: @pytest.mark.parametrize("size", [127, 4096, -2]) def test_invalid_block_size(self, size): with pytest.raises(ValueError): padding.ANSIX923(size) @pytest.mark.parametrize( ("size", "padded"), [ (128, b"1111"), (128, b"1111111111111111"), (128, b"111111111111111\x06"), (128, b"1111111111\x06\x06\x06\x06\x06\x06"), (128, b""), (128, b"\x06" * 6), (128, b"\x00" * 16), ], ) def test_invalid_padding(self, size, padded): unpadder = padding.ANSIX923(size).unpadder() with pytest.raises(ValueError): unpadder.update(padded) unpadder.finalize() def test_non_bytes(self): padder = padding.ANSIX923(128).padder() with pytest.raises(TypeError): padder.update("abc") # type: ignore[arg-type] unpadder = padding.ANSIX923(128).unpadder() with pytest.raises(TypeError): unpadder.update("abc") # type: ignore[arg-type] def test_zany_py2_bytes_subclass(self): class mybytes(bytes): # noqa: N801 def __str__(self): return "broken" str(mybytes()) padder = padding.ANSIX923(128).padder() padder.update(mybytes(b"abc")) unpadder = padding.ANSIX923(128).unpadder() unpadder.update(mybytes(padder.finalize())) assert unpadder.finalize() == b"abc" @pytest.mark.parametrize( ("size", "unpadded", "padded"), [ (128, b"1111111111", b"1111111111\x00\x00\x00\x00\x00\x06"), ( 128, b"111111111111111122222222222222", b"111111111111111122222222222222\x00\x02", ), (128, b"1" * 16, b"1" * 16 + b"\x00" * 15 + b"\x10"), (128, b"1" * 17, b"1" * 17 + b"\x00" * 14 + b"\x0f"), ], ) def test_pad(self, size, unpadded, padded): padder = padding.ANSIX923(size).padder() result = padder.update(unpadded) result += padder.finalize() assert result == padded @pytest.mark.parametrize( ("size", "unpadded", "padded"), [ (128, b"1111111111", b"1111111111\x00\x00\x00\x00\x00\x06"), ( 128, b"111111111111111122222222222222", b"111111111111111122222222222222\x00\x02", ), ], ) def test_unpad(self, size, unpadded, padded): unpadder = padding.ANSIX923(size).unpadder() result = unpadder.update(padded) result += unpadder.finalize() assert result == unpadded def test_use_after_finalize(self): padder = padding.ANSIX923(128).padder() b = padder.finalize() with pytest.raises(AlreadyFinalized): padder.update(b"") with pytest.raises(AlreadyFinalized): padder.finalize() unpadder = padding.ANSIX923(128).unpadder() unpadder.update(b) unpadder.finalize() with pytest.raises(AlreadyFinalized): unpadder.update(b"") with pytest.raises(AlreadyFinalized): unpadder.finalize() def test_bytearray(self): padder = padding.ANSIX923(128).padder() unpadded = bytearray(b"t" * 38) padded = ( padder.update(unpadded) + padder.update(unpadded) + padder.finalize() ) unpadder = padding.ANSIX923(128).unpadder() final = unpadder.update(padded) + unpadder.finalize() assert final == unpadded + unpadded cryptography-43.0.0/tests/hazmat/primitives/test_pbkdf2hmac.py010064400017510000177000000044331464676315000227700ustar 00000000000000# 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. import pytest from cryptography.exceptions import AlreadyFinalized, InvalidKey, _Reasons from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm class TestPBKDF2HMAC: def test_already_finalized(self, backend): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) kdf.derive(b"password") with pytest.raises(AlreadyFinalized): kdf.derive(b"password2") kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) key = kdf.derive(b"password") with pytest.raises(AlreadyFinalized): kdf.verify(b"password", key) kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) kdf.verify(b"password", key) with pytest.raises(AlreadyFinalized): kdf.verify(b"password", key) def test_unsupported_algorithm(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): PBKDF2HMAC(DummyHashAlgorithm(), 20, b"salt", 10, backend) def test_invalid_key(self, backend): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) key = kdf.derive(b"password") kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) with pytest.raises(InvalidKey): kdf.verify(b"password2", key) def test_unicode_error_with_salt(self, backend): with pytest.raises(TypeError): PBKDF2HMAC( hashes.SHA1(), 20, "salt", # type: ignore[arg-type] 10, backend, ) def test_unicode_error_with_key_material(self, backend): kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, backend) with pytest.raises(TypeError): kdf.derive("unicode here") # type: ignore[arg-type] def test_buffer_protocol(self, backend): kdf = PBKDF2HMAC(hashes.SHA1(), 10, b"salt", 10, backend) data = bytearray(b"data") assert kdf.derive(data) == b"\xe9n\xaa\x81\xbbt\xa4\xf6\x08\xce" cryptography-43.0.0/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py010064400017510000177000000023461464676315000245360ustar 00000000000000# 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. import binascii import os import pytest from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from ...utils import load_nist_vectors, load_vectors_from_file @pytest.mark.supported( only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1 for PBKDF2HMAC", ) def test_pbkdf2_hmacsha1_vectors(subtests, backend): params = load_vectors_from_file( os.path.join("KDF", "rfc-6070-PBKDF2-SHA1.txt"), load_nist_vectors, ) for param in params: with subtests.test(): iterations = int(param["iterations"]) if iterations > 1_000_000: pytest.skip("Skipping test due to iteration count") kdf = PBKDF2HMAC( hashes.SHA1(), int(param["length"]), param["salt"], iterations, ) derived_key = kdf.derive(param["password"]) assert binascii.hexlify(derived_key) == param["derived_key"] cryptography-43.0.0/tests/hazmat/primitives/test_pkcs12.py010064400017510000177000001046261464676315000220770ustar 00000000000000# 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. import os from datetime import datetime, timezone import pytest from cryptography import x509 from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.decrepit.ciphers.algorithms import RC2 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed448, ed25519, rsa, ) from cryptography.hazmat.primitives.ciphers.modes import CBC from cryptography.hazmat.primitives.serialization import ( Encoding, PublicFormat, load_pem_private_key, ) from cryptography.hazmat.primitives.serialization.pkcs12 import ( PBES, PKCS12Certificate, PKCS12KeyAndCertificates, load_key_and_certificates, load_pkcs12, serialize_key_and_certificates, ) from ...doubles import DummyKeySerializationEncryption from ...utils import load_vectors_from_file def _skip_curve_unsupported(backend, curve): if not backend.elliptic_curve_supported(curve): pytest.skip( f"Curve {curve.name} is not supported by this backend {backend}" ) @pytest.mark.skip_fips( reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." ) class TestPKCS12Loading: def _test_load_pkcs12_ec_keys(self, filename, password, backend): cert, key = _load_ca(backend) assert isinstance(key, ec.EllipticCurvePrivateKey) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", filename), lambda derfile: load_key_and_certificates( derfile.read(), password, backend ), mode="rb", ) assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_cert == cert assert parsed_key.private_numbers() == key.private_numbers() assert parsed_more_certs == [] @pytest.mark.parametrize( ("filename", "password"), [ ("cert-key-aes256cbc.p12", b"cryptography"), ("cert-none-key-none.p12", b"cryptography"), ], ) def test_load_pkcs12_ec_keys(self, filename, password, backend): self._test_load_pkcs12_ec_keys(filename, password, backend) @pytest.mark.parametrize( ("filename", "password"), [ ("cert-rc2-key-3des.p12", b"cryptography"), ("no-password.p12", None), ], ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( RC2(b"0" * 16), CBC(b"0" * 8) ), skip_message="Does not support RC2", ) def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): self._test_load_pkcs12_ec_keys(filename, password, backend) def test_load_key_and_cert_cert_only(self, backend): cert, _ = _load_ca(backend) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", "cert-aes256cbc-no-key.p12"), lambda data: load_key_and_certificates( data.read(), b"cryptography", backend ), mode="rb", ) assert parsed_cert is None assert parsed_key is None assert parsed_more_certs == [cert] def test_load_key_and_certificates_key_only(self, backend): _, key = _load_ca(backend) assert isinstance(key, ec.EllipticCurvePrivateKey) parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file( os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"), lambda data: load_key_and_certificates( data.read(), b"cryptography", backend ), mode="rb", ) assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_key.private_numbers() == key.private_numbers() assert parsed_cert is None assert parsed_more_certs == [] def test_load_pkcs12_key_only(self, backend): _, key = _load_ca(backend) assert isinstance(key, ec.EllipticCurvePrivateKey) p12 = load_vectors_from_file( os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"), lambda data: load_pkcs12(data.read(), b"cryptography", backend), mode="rb", ) assert isinstance(p12.key, ec.EllipticCurvePrivateKey) assert p12.key.private_numbers() == key.private_numbers() assert p12.cert is None assert p12.additional_certs == [] def test_non_bytes(self, backend): with pytest.raises(TypeError): load_key_and_certificates( b"irrelevant", object(), # type: ignore[arg-type] backend, ) def test_not_a_pkcs12(self, backend): with pytest.raises(ValueError): load_key_and_certificates(b"invalid", b"pass", backend) def test_invalid_password(self, backend): with pytest.raises(ValueError): load_vectors_from_file( os.path.join("pkcs12", "cert-key-aes256cbc.p12"), lambda derfile: load_key_and_certificates( derfile.read(), b"invalid", backend ), mode="rb", ) def test_buffer_protocol(self, backend): p12 = load_vectors_from_file( os.path.join("pkcs12", "cert-key-aes256cbc.p12"), lambda derfile: derfile.read(), mode="rb", ) p12buffer = bytearray(p12) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12buffer, bytearray(b"cryptography"), backend ) assert parsed_key is not None assert parsed_cert is not None assert parsed_more_certs == [] @pytest.mark.parametrize( ("name", "name2", "name3", "filename", "password"), [ (None, None, None, "no-name-no-pwd.p12", None), (b"name", b"name2", b"name3", "name-all-no-pwd.p12", None), (b"name", None, None, "name-1-no-pwd.p12", None), (None, b"name2", b"name3", "name-2-3-no-pwd.p12", None), (None, b"name2", None, "name-2-no-pwd.p12", None), (None, None, b"name3", "name-3-no-pwd.p12", None), ( "☺".encode(), "ä".encode(), "ç".encode(), "name-unicode-no-pwd.p12", None, ), (None, None, None, "no-name-pwd.p12", b"password"), (b"name", b"name2", b"name3", "name-all-pwd.p12", b"password"), (b"name", None, None, "name-1-pwd.p12", b"password"), (None, b"name2", b"name3", "name-2-3-pwd.p12", b"password"), (None, b"name2", None, "name-2-pwd.p12", b"password"), (None, None, b"name3", "name-3-pwd.p12", b"password"), ( "☺".encode(), "ä".encode(), "ç".encode(), "name-unicode-pwd.p12", b"password", ), ], ) def test_load_object( self, filename, name, name2, name3, password, backend ): cert, key = _load_ca(backend) cert2 = _load_cert( backend, os.path.join("x509", "cryptography.io.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) pkcs12 = load_vectors_from_file( os.path.join("pkcs12", filename), lambda derfile: load_pkcs12(derfile.read(), password, backend), mode="rb", ) assert pkcs12.cert is not None assert pkcs12.cert.certificate == cert assert pkcs12.cert.friendly_name == name assert isinstance(pkcs12.key, ec.EllipticCurvePrivateKey) assert pkcs12.key.private_numbers() == key.private_numbers() assert len(pkcs12.additional_certs) == 2 assert pkcs12.additional_certs[0].certificate == cert2 assert pkcs12.additional_certs[0].friendly_name == name2 assert pkcs12.additional_certs[1].certificate == cert3 assert pkcs12.additional_certs[1].friendly_name == name3 @pytest.mark.parametrize( ("name2", "name3", "filename", "password"), [ (None, None, "no-cert-no-name-no-pwd.p12", None), (b"name2", b"name3", "no-cert-name-all-no-pwd.p12", None), (b"name2", None, "no-cert-name-2-no-pwd.p12", None), (None, b"name3", "no-cert-name-3-no-pwd.p12", None), ( "☹".encode(), "ï".encode(), "no-cert-name-unicode-no-pwd.p12", None, ), (None, None, "no-cert-no-name-pwd.p12", b"password"), (b"name2", b"name3", "no-cert-name-all-pwd.p12", b"password"), (b"name2", None, "no-cert-name-2-pwd.p12", b"password"), (None, b"name3", "no-cert-name-3-pwd.p12", b"password"), ( "☹".encode(), "ï".encode(), "no-cert-name-unicode-pwd.p12", b"password", ), ], ) def test_load_object_no_cert_key( self, filename, name2, name3, password, backend ): cert2 = _load_cert( backend, os.path.join("x509", "cryptography.io.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) pkcs12 = load_vectors_from_file( os.path.join("pkcs12", filename), lambda derfile: load_pkcs12(derfile.read(), password, backend), mode="rb", ) assert pkcs12.cert is None assert pkcs12.key is None assert len(pkcs12.additional_certs) == 2 assert pkcs12.additional_certs[0].certificate == cert2 assert pkcs12.additional_certs[0].friendly_name == name2 assert pkcs12.additional_certs[1].certificate == cert3 assert pkcs12.additional_certs[1].friendly_name == name3 def _load_cert(backend, path): return load_vectors_from_file( path, lambda pemfile: x509.load_pem_x509_certificate( pemfile.read(), backend ), mode="rb", ) def _load_ca(backend): cert = _load_cert(backend, os.path.join("pkcs12", "ca", "ca.pem")) key = load_vectors_from_file( os.path.join("pkcs12", "ca", "ca_key.pem"), lambda pemfile: load_pem_private_key(pemfile.read(), None, backend), mode="rb", ) return cert, key @pytest.mark.skip_fips( reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." ) class TestPKCS12Creation: @pytest.mark.parametrize( ( "kgenerator", "ktype", "kparam", ), [ pytest.param( ed448.Ed448PrivateKey.generate, ed448.Ed448PrivateKey, [], marks=pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ), ), pytest.param( ed25519.Ed25519PrivateKey.generate, ed25519.Ed25519PrivateKey, [], marks=pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ), ), (rsa.generate_private_key, rsa.RSAPrivateKey, [65537, 1024]), (dsa.generate_private_key, dsa.DSAPrivateKey, [1024]), ] + [ pytest.param( ec.generate_private_key, ec.EllipticCurvePrivateKey, [curve] ) for curve in ec._CURVE_TYPES.values() ], ) @pytest.mark.parametrize("name", [None, b"name"]) @pytest.mark.parametrize( ("algorithm", "password"), [ (serialization.BestAvailableEncryption(b"password"), b"password"), (serialization.NoEncryption(), None), ], ) def test_generate_each_supported_keytype( self, backend, kgenerator, ktype, kparam, name, algorithm, password ): if ktype == ec.EllipticCurvePrivateKey: _skip_curve_unsupported(backend, *kparam) key = kgenerator(*kparam) assert isinstance(key, ktype) cacert, cakey = _load_ca(backend) now = datetime.now(timezone.utc).replace(tzinfo=None) cert = ( x509.CertificateBuilder() .subject_name(cacert.subject) .issuer_name(cacert.subject) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now) .not_valid_after(now) .sign(cakey, hashes.SHA256()) ) assert isinstance(cert, x509.Certificate) p12 = serialize_key_and_certificates( name, key, cert, [cacert], algorithm ) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, password, backend ) assert parsed_cert == cert assert isinstance(parsed_key, ktype) assert parsed_key.public_key().public_bytes( Encoding.PEM, PublicFormat.SubjectPublicKeyInfo ) == key.public_key().public_bytes( Encoding.PEM, PublicFormat.SubjectPublicKeyInfo ) assert parsed_more_certs == [cacert] def test_generate_with_cert_key_ca(self, backend): cert, key = _load_ca(backend) cert2 = _load_cert( backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) encryption = serialization.NoEncryption() p12 = serialize_key_and_certificates( None, key, cert, [cert2, cert3], encryption ) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, None, backend ) assert parsed_cert == cert assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_key.private_numbers() == key.private_numbers() assert parsed_more_certs == [cert2, cert3] def test_generate_cas_friendly_names(self, backend): cert, key = _load_ca(backend) cert2 = _load_cert( backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) encryption = serialization.NoEncryption() p12 = serialize_key_and_certificates( b"test", key, cert, [ PKCS12Certificate(cert2, b"cert2"), PKCS12Certificate(cert3, None), ], encryption, ) p12_cert = load_pkcs12(p12, None, backend) cas = p12_cert.additional_certs assert cas[0].certificate == cert2 assert cas[0].friendly_name == b"cert2" assert cas[1].certificate == cert3 assert cas[1].friendly_name is None @pytest.mark.parametrize( ("encryption_algorithm", "password"), [ (serialization.BestAvailableEncryption(b"password"), b"password"), ( serialization.PrivateFormat.PKCS12.encryption_builder().build( b"not a password" ), b"not a password", ), (serialization.NoEncryption(), None), ], ) def test_generate_cas_friendly_names_no_key( self, backend, encryption_algorithm, password ): cert2 = _load_cert( backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) p12 = serialize_key_and_certificates( None, None, None, [ PKCS12Certificate(cert2, b"cert2"), PKCS12Certificate(cert3, None), ], encryption_algorithm, ) p12_cert = load_pkcs12(p12, password, backend) cas = p12_cert.additional_certs assert cas[0].certificate == cert2 assert cas[0].friendly_name == b"cert2" assert cas[1].certificate == cert3 assert cas[1].friendly_name is None def test_generate_wrong_types(self, backend): cert, key = _load_ca(backend) cert2 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) encryption = serialization.NoEncryption() with pytest.raises(TypeError) as exc: serialize_key_and_certificates( b"name", cert, cert, None, encryption ) assert str(exc.value) == ( "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" " private key, or None." ) with pytest.raises(TypeError) as exc: serialize_key_and_certificates(b"name", key, key, None, encryption) assert "object cannot be converted to 'Certificate'" in str(exc.value) with pytest.raises(TypeError) as exc: serialize_key_and_certificates(b"name", key, cert, None, key) assert str(exc.value) == ( "Key encryption algorithm must be a " "KeySerializationEncryption instance" ) with pytest.raises(TypeError) as exc: serialize_key_and_certificates(None, key, cert, cert2, encryption) with pytest.raises(TypeError) as exc: serialize_key_and_certificates(None, key, cert, [key], encryption) assert "failed to extract enum CertificateOrPKCS12Certificate" in str( exc.value ) def test_generate_no_cert(self, backend): _, key = _load_ca(backend) p12 = serialize_key_and_certificates( None, key, None, None, serialization.NoEncryption() ) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, None, backend ) assert parsed_cert is None assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_key.private_numbers() == key.private_numbers() assert parsed_more_certs == [] @pytest.mark.parametrize( ("encryption_algorithm", "password"), [ (serialization.BestAvailableEncryption(b"password"), b"password"), (serialization.NoEncryption(), None), ], ) def test_generate_cas_only(self, encryption_algorithm, password, backend): cert, _ = _load_ca(backend) p12 = serialize_key_and_certificates( None, None, None, [cert], encryption_algorithm ) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, password, backend ) assert parsed_cert is None assert parsed_key is None assert parsed_more_certs == [cert] @pytest.mark.parametrize( ("encryption_algorithm", "password"), [ (serialization.BestAvailableEncryption(b"password"), b"password"), (serialization.NoEncryption(), None), ], ) def test_generate_cert_only(self, encryption_algorithm, password, backend): # This test is a bit weird, but when passing *just* a cert # with no corresponding key it will be encoded in the cas # list. We have external consumers relying on this behavior # (and the underlying structure makes no real distinction # anyway) so this test ensures we don't break them. cert, _ = _load_ca(backend) p12 = serialize_key_and_certificates( None, None, cert, [], encryption_algorithm ) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, password, backend ) assert parsed_cert is None assert parsed_key is None assert parsed_more_certs == [cert] def test_generate_cert_only_none_cas(self, backend): # Same as test_generate_cert_only, but passing None instead of an # empty list for cas. cert, _ = _load_ca(backend) p12 = serialize_key_and_certificates( None, None, cert, None, serialization.NoEncryption() ) parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, None ) assert parsed_cert is None assert parsed_key is None assert parsed_more_certs == [cert] def test_invalid_utf8_friendly_name(self, backend): cert, _ = _load_ca(backend) with pytest.raises(ValueError): serialize_key_and_certificates( b"\xc9", None, cert, None, serialization.NoEncryption() ) def test_must_supply_something(self): with pytest.raises(ValueError) as exc: serialize_key_and_certificates( None, None, None, None, serialization.NoEncryption() ) assert str(exc.value) == ( "You must supply at least one of key, cert, or cas" ) def test_generate_unsupported_encryption_type(self, backend): cert, key = _load_ca(backend) with pytest.raises(ValueError) as exc: serialize_key_and_certificates( None, key, cert, None, DummyKeySerializationEncryption(), ) assert str(exc.value) == "Unsupported key encryption type" @pytest.mark.parametrize( ("enc_alg", "enc_alg_der"), [ ( PBES.PBESv2SHA256AndAES256CBC, [ b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x05\x0d", # PBESv2 b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x2a", # AES ], ), ( PBES.PBESv1SHA1And3KeyTripleDESCBC, [b"\x06\x0a\x2a\x86\x48\x86\xf7\x0d\x01\x0c\x01\x03"], ), ( None, [], ), ], ) @pytest.mark.parametrize( ("mac_alg", "mac_alg_der"), [ (hashes.SHA1(), b"\x06\x05\x2b\x0e\x03\x02\x1a"), (hashes.SHA256(), b"\x06\t`\x86H\x01e\x03\x04\x02\x01"), (None, None), ], ) @pytest.mark.parametrize( ("iters", "iter_der"), [ (420, b"\x02\x02\x01\xa4"), (22222, b"\x02\x02\x56\xce"), (None, None), ], ) def test_key_serialization_encryption( self, backend, enc_alg, enc_alg_der, mac_alg, mac_alg_der, iters, iter_der, ): if ( enc_alg is PBES.PBESv2SHA256AndAES256CBC ) and not rust_openssl.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: pytest.skip("PBESv2 is not supported on OpenSSL < 3.0") builder = serialization.PrivateFormat.PKCS12.encryption_builder() if enc_alg is not None: builder = builder.key_cert_algorithm(enc_alg) if mac_alg is not None: builder = builder.hmac_hash(mac_alg) if iters is not None: builder = builder.kdf_rounds(iters) encryption = builder.build(b"password") key = ec.generate_private_key(ec.SECP256R1()) cacert, cakey = _load_ca(backend) now = datetime.now(timezone.utc).replace(tzinfo=None) cert = ( x509.CertificateBuilder() .subject_name(cacert.subject) .issuer_name(cacert.subject) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now) .not_valid_after(now) .sign(cakey, hashes.SHA256()) ) assert isinstance(cert, x509.Certificate) p12 = serialize_key_and_certificates( b"name", key, cert, [cacert], encryption ) # We want to know if we've serialized something that has the parameters # we expect, so we match on specific byte strings of OIDs & DER values. for der in enc_alg_der: assert der in p12 if mac_alg_der is not None: assert mac_alg_der in p12 if iter_der is not None: assert iter_der in p12 parsed_key, parsed_cert, parsed_more_certs = load_key_and_certificates( p12, b"password", backend ) assert parsed_cert == cert assert isinstance(parsed_key, ec.EllipticCurvePrivateKey) assert parsed_key.public_key().public_bytes( Encoding.PEM, PublicFormat.SubjectPublicKeyInfo ) == key.public_key().public_bytes( Encoding.PEM, PublicFormat.SubjectPublicKeyInfo ) assert parsed_more_certs == [cacert] def test_set_mac_key_certificate_mismatch(self, backend): cacert, _ = _load_ca(backend) key = ec.generate_private_key(ec.SECP256R1()) encryption = ( serialization.PrivateFormat.PKCS12.encryption_builder() .hmac_hash(hashes.SHA256()) .build(b"password") ) with pytest.raises(ValueError): serialize_key_and_certificates( b"name", key, cacert, [], encryption ) @pytest.mark.skip_fips( reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." ) def test_pkcs12_ordering(): """ In OpenSSL < 3.0.0 PKCS12 parsing reverses the order. However, we accidentally thought it was **encoding** that did it, leading to bug https://github.com/pyca/cryptography/issues/5872 This test ensures our ordering is correct going forward. """ def make_cert(name): key = ec.generate_private_key(ec.SECP256R1()) subject = x509.Name( [ x509.NameAttribute(x509.NameOID.COMMON_NAME, name), ] ) now = datetime.now(timezone.utc).replace(tzinfo=None) cert = ( x509.CertificateBuilder() .subject_name(subject) .issuer_name(subject) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now) .not_valid_after(now) .sign(key, hashes.SHA256()) ) return (key, cert) # Make some certificates with distinct names. a_name = "A" * 20 b_name = "B" * 20 c_name = "C" * 20 a_key, a_cert = make_cert(a_name) _, b_cert = make_cert(b_name) _, c_cert = make_cert(c_name) # Bundle them in a PKCS#12 file in order A, B, C. p12 = serialize_key_and_certificates( b"p12", a_key, a_cert, [b_cert, c_cert], serialization.NoEncryption() ) # Parse them out. The API should report them in the same order. (_, cert, certs) = load_key_and_certificates(p12, None) assert cert == a_cert assert certs == [b_cert, c_cert] # The ordering in the PKCS#12 file itself should also match. a_idx = p12.index(a_name.encode("utf-8")) b_idx = p12.index(b_name.encode("utf-8")) c_idx = p12.index(c_name.encode("utf-8")) assert a_idx < b_idx < c_idx class TestPKCS12Objects: def test_certificate_constructor(self, backend): with pytest.raises(TypeError): PKCS12Certificate(None, None) # type:ignore[arg-type] with pytest.raises(TypeError): PKCS12Certificate("hello", None) # type:ignore[arg-type] cert = _load_cert(backend, os.path.join("x509", "cryptography.io.pem")) with pytest.raises(TypeError): PKCS12Certificate(cert, "hello") # type:ignore[arg-type] with pytest.raises(TypeError): PKCS12Certificate(cert, 42) # type:ignore[arg-type] def test_certificate_equality(self, backend): cert2 = _load_cert( backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) c2n = PKCS12Certificate(cert2, None) c2a = PKCS12Certificate(cert2, b"a") c2b = PKCS12Certificate(cert2, b"b") c3n = PKCS12Certificate(cert3, None) c3a = PKCS12Certificate(cert3, b"a") assert c2n == c2n assert c2a == c2a assert c2n != c2a assert c2n != c3n assert c2a != c2b assert c2a != c3a assert c2n != "test" # type: ignore[comparison-overlap] def test_certificate_hash(self, backend): cert2 = _load_cert( backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) c2n = PKCS12Certificate(cert2, None) c2a = PKCS12Certificate(cert2, b"a") c2b = PKCS12Certificate(cert2, b"b") c3n = PKCS12Certificate(cert3, None) c3a = PKCS12Certificate(cert3, b"a") assert hash(c2n) == hash(c2n) assert hash(c2a) == hash(c2a) assert hash(c2n) != hash(c2a) assert hash(c2n) != hash(c3n) assert hash(c2a) != hash(c2b) assert hash(c2a) != hash(c3a) def test_certificate_repr(self, backend): cert = _load_cert(backend, os.path.join("x509", "cryptography.io.pem")) assert ( repr(PKCS12Certificate(cert, None)) == f"" ) assert ( repr(PKCS12Certificate(cert, b"a")) == f"" ) def test_key_and_certificates_constructor(self, backend): with pytest.raises(TypeError): PKCS12KeyAndCertificates( "hello", # type:ignore[arg-type] None, [], ) with pytest.raises(TypeError): PKCS12KeyAndCertificates( None, "hello", # type:ignore[arg-type] [], ) with pytest.raises(TypeError): PKCS12KeyAndCertificates( None, None, ["hello"], # type:ignore[list-item] ) def test_key_and_certificates_equality(self, backend): cert, key = _load_ca(backend) cert2 = _load_cert( backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) p12a = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12b = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert, b"name"), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12c = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert2, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12d = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert, None), [PKCS12Certificate(cert3, None), PKCS12Certificate(cert2, None)], ) p12e = PKCS12KeyAndCertificates( None, PKCS12Certificate(cert, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12f = PKCS12KeyAndCertificates( None, PKCS12Certificate(cert2, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12g = PKCS12KeyAndCertificates( key, None, [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12h = PKCS12KeyAndCertificates(None, None, []) assert p12a == p12a assert p12h == p12h assert p12a != p12b assert p12a != p12c assert p12a != p12d assert p12a != p12e assert p12a != p12g assert p12a != p12h assert p12e != p12f assert p12e != p12g assert p12e != p12h assert p12e != "test" def test_key_and_certificates_hash(self, backend): cert, key = _load_ca(backend) cert2 = _load_cert( backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") ) cert3 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) p12a = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12b = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert, b"name"), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12c = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert2, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12d = PKCS12KeyAndCertificates( key, PKCS12Certificate(cert, None), [PKCS12Certificate(cert3, None), PKCS12Certificate(cert2, None)], ) p12e = PKCS12KeyAndCertificates( None, PKCS12Certificate(cert, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12f = PKCS12KeyAndCertificates( None, PKCS12Certificate(cert2, None), [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12g = PKCS12KeyAndCertificates( key, None, [PKCS12Certificate(cert2, None), PKCS12Certificate(cert3, None)], ) p12h = PKCS12KeyAndCertificates(None, None, []) assert hash(p12a) == hash(p12a) assert hash(p12h) == hash(p12h) assert hash(p12a) != hash(p12b) assert hash(p12a) != hash(p12c) assert hash(p12a) != hash(p12d) assert hash(p12a) != hash(p12e) assert hash(p12a) != hash(p12g) assert hash(p12a) != hash(p12h) assert hash(p12e) != hash(p12f) assert hash(p12e) != hash(p12g) assert hash(p12e) != hash(p12h) def test_key_and_certificates_repr(self, backend): cert, key = _load_ca(backend) cert2 = _load_cert( backend, os.path.join("x509", "cryptography.io.pem") ) assert repr( PKCS12KeyAndCertificates( key, PKCS12Certificate(cert, None), [PKCS12Certificate(cert2, b"name2")], ) ) == ( f", " f"additional_certs=[" f"])>" ) cryptography-43.0.0/tests/hazmat/primitives/test_pkcs7.py010064400017510000177000001226571464676315000220270ustar 00000000000000# 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. import email.parser import os import typing import pytest from cryptography import x509 from cryptography.exceptions import _Reasons from cryptography.hazmat.bindings._rust import test_support from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa from cryptography.hazmat.primitives.serialization import pkcs7 from ...utils import load_vectors_from_file, raises_unsupported_algorithm @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported(), skip_message="Requires OpenSSL with PKCS7 support", ) class TestPKCS7Loading: def test_load_invalid_der_pkcs7(self, backend): with pytest.raises(ValueError): pkcs7.load_der_pkcs7_certificates(b"nonsense") def test_load_invalid_pem_pkcs7(self, backend): with pytest.raises(ValueError): pkcs7.load_pem_pkcs7_certificates(b"nonsense") def test_not_bytes_der(self, backend): with pytest.raises(TypeError): pkcs7.load_der_pkcs7_certificates(38) # type: ignore[arg-type] def test_not_bytes_pem(self, backend): with pytest.raises(TypeError): pkcs7.load_pem_pkcs7_certificates(38) # type: ignore[arg-type] def test_load_pkcs7_pem(self, backend): certs = load_vectors_from_file( os.path.join("pkcs7", "isrg.pem"), lambda pemfile: pkcs7.load_pem_pkcs7_certificates(pemfile.read()), mode="rb", ) assert len(certs) == 1 assert certs[0].subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME ) == [x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "ISRG Root X1")] @pytest.mark.parametrize( "filepath", [ os.path.join("pkcs7", "amazon-roots.der"), os.path.join("pkcs7", "amazon-roots.p7b"), ], ) def test_load_pkcs7_der(self, filepath, backend): certs = load_vectors_from_file( filepath, lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), mode="rb", ) assert len(certs) == 2 assert certs[0].subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME ) == [ x509.NameAttribute( x509.oid.NameOID.COMMON_NAME, "Amazon Root CA 3" ) ] assert certs[1].subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME ) == [ x509.NameAttribute( x509.oid.NameOID.COMMON_NAME, "Amazon Root CA 2" ) ] def test_load_pkcs7_unsupported_type(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): load_vectors_from_file( os.path.join("pkcs7", "enveloped.pem"), lambda pemfile: pkcs7.load_pem_pkcs7_certificates( pemfile.read() ), mode="rb", ) def test_load_pkcs7_empty_certificates(self): der = b"\x30\x0b\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02" with pytest.raises(ValueError): pkcs7.load_der_pkcs7_certificates(der) def _load_cert_key(): key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "ca_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate(pemfile.read()), mode="rb", ) return cert, key @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported(), skip_message="Requires OpenSSL with PKCS7 support", ) class TestPKCS7SignatureBuilder: def test_invalid_data(self, backend): builder = pkcs7.PKCS7SignatureBuilder() with pytest.raises(TypeError): builder.set_data("not bytes") # type: ignore[arg-type] def test_set_data_twice(self, backend): builder = pkcs7.PKCS7SignatureBuilder().set_data(b"test") with pytest.raises(ValueError): builder.set_data(b"test") def test_sign_no_signer(self, backend): builder = pkcs7.PKCS7SignatureBuilder().set_data(b"test") with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, []) def test_sign_no_data(self, backend): cert, key = _load_cert_key() builder = pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, hashes.SHA256() ) with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, []) def test_unsupported_hash_alg(self, backend): cert, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, hashes.SHA512_256(), # type: ignore[arg-type] ) def test_not_a_cert(self, backend): _, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( b"notacert", # type: ignore[arg-type] key, hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Does not support ed25519.", ) def test_unsupported_key_type(self, backend): cert, _ = _load_cert_key() key = ed25519.Ed25519PrivateKey.generate() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, # type: ignore[arg-type] hashes.SHA256(), ) def test_sign_invalid_options(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(b"test") .add_signer(cert, key, hashes.SHA256()) ) with pytest.raises(ValueError): builder.sign( serialization.Encoding.SMIME, [b"invalid"], # type: ignore[list-item] ) def test_sign_invalid_encoding(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(b"test") .add_signer(cert, key, hashes.SHA256()) ) with pytest.raises(ValueError): builder.sign(serialization.Encoding.Raw, []) def test_sign_invalid_options_text_no_detached(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(b"test") .add_signer(cert, key, hashes.SHA256()) ) options = [pkcs7.PKCS7Options.Text] with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, options) def test_sign_invalid_options_text_der_encoding(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(b"test") .add_signer(cert, key, hashes.SHA256()) ) options = [ pkcs7.PKCS7Options.Text, pkcs7.PKCS7Options.DetachedSignature, ] with pytest.raises(ValueError): builder.sign(serialization.Encoding.DER, options) def test_sign_invalid_options_no_attrs_and_no_caps(self, backend): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(b"test") .add_signer(cert, key, hashes.SHA256()) ) options = [ pkcs7.PKCS7Options.NoAttributes, pkcs7.PKCS7Options.NoCapabilities, ] with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, options) def test_smime_sign_detached(self, backend): data = b"hello world" cert, key = _load_cert_key() options = [pkcs7.PKCS7Options.DetachedSignature] builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) sig = builder.sign(serialization.Encoding.SMIME, options) sig_binary = builder.sign(serialization.Encoding.DER, options) assert b"text/plain" not in sig # We don't have a generic ASN.1 parser available to us so we instead # will assert on specific byte sequences being present based on the # parameters chosen above. assert b"sha-256" in sig # Detached signature means that the signed data is *not* embedded into # the PKCS7 structure itself, but is present in the SMIME serialization # as a separate section before the PKCS7 data. So we should expect to # have data in sig but not in sig_binary assert data in sig # Parse the message to get the signed data, which is the # first payload in the message message = email.parser.BytesParser().parsebytes(sig) payload = message.get_payload() assert isinstance(payload, list) assert isinstance(payload[0], email.message.Message) signed_data = payload[0].get_payload() assert isinstance(signed_data, str) test_support.pkcs7_verify( serialization.Encoding.SMIME, sig, signed_data.encode(), [cert], options, ) assert data not in sig_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, data, [cert], options, ) def test_sign_byteslike(self, backend): data = bytearray(b"hello world") cert, key = _load_cert_key() options = [pkcs7.PKCS7Options.DetachedSignature] builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) sig = builder.sign(serialization.Encoding.SMIME, options) assert bytes(data) in sig test_support.pkcs7_verify( serialization.Encoding.SMIME, sig, data, [cert], options, ) data = bytearray(b"") builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) sig = builder.sign(serialization.Encoding.SMIME, options) test_support.pkcs7_verify( serialization.Encoding.SMIME, sig, data, [cert], options, ) def test_sign_pem(self, backend): data = b"hello world" cert, key = _load_cert_key() options: typing.List[pkcs7.PKCS7Options] = [] builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) sig = builder.sign(serialization.Encoding.PEM, options) test_support.pkcs7_verify( serialization.Encoding.PEM, sig, None, [cert], options, ) @pytest.mark.parametrize( ("hash_alg", "expected_value"), [ (hashes.SHA256(), b"\x06\t`\x86H\x01e\x03\x04\x02\x01"), (hashes.SHA384(), b"\x06\t`\x86H\x01e\x03\x04\x02\x02"), (hashes.SHA512(), b"\x06\t`\x86H\x01e\x03\x04\x02\x03"), ], ) def test_sign_alternate_digests_der( self, hash_alg, expected_value, backend ): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hash_alg) ) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert expected_value in sig test_support.pkcs7_verify( serialization.Encoding.DER, sig, None, [cert], options ) @pytest.mark.parametrize( ("hash_alg", "expected_value"), [ (hashes.SHA256(), b"sha-256"), (hashes.SHA384(), b"sha-384"), (hashes.SHA512(), b"sha-512"), ], ) def test_sign_alternate_digests_detached( self, hash_alg, expected_value, backend ): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hash_alg) ) options = [pkcs7.PKCS7Options.DetachedSignature] sig = builder.sign(serialization.Encoding.SMIME, options) # When in detached signature mode the hash algorithm is stored as a # byte string like "sha-384". assert expected_value in sig def test_sign_attached(self, backend): data = b"hello world" cert, key = _load_cert_key() options: typing.List[pkcs7.PKCS7Options] = [] builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) sig_binary = builder.sign(serialization.Encoding.DER, options) # When not passing detached signature the signed data is embedded into # the PKCS7 structure itself assert data in sig_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, ) def test_sign_binary(self, backend): data = b"hello\nworld" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) options: typing.List[pkcs7.PKCS7Options] = [] sig_no_binary = builder.sign(serialization.Encoding.DER, options) sig_binary = builder.sign( serialization.Encoding.DER, [pkcs7.PKCS7Options.Binary] ) # Binary prevents translation of LF to CR+LF (SMIME canonical form) # so data should not be present in sig_no_binary, but should be present # in sig_binary assert data not in sig_no_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_no_binary, None, [cert], options, ) assert data in sig_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, ) def test_sign_smime_canonicalization(self, backend): data = b"hello\nworld" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) options: typing.List[pkcs7.PKCS7Options] = [] sig_binary = builder.sign(serialization.Encoding.DER, options) # LF gets converted to CR+LF (SMIME canonical form) # so data should not be present in the sig assert data not in sig_binary assert b"hello\r\nworld" in sig_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, ) def test_sign_text(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) options = [ pkcs7.PKCS7Options.Text, pkcs7.PKCS7Options.DetachedSignature, ] sig_pem = builder.sign(serialization.Encoding.SMIME, options) # The text option adds text/plain headers to the S/MIME message # These headers are only relevant in SMIME mode, not binary, which is # just the PKCS7 structure itself. assert sig_pem.count(b"text/plain") == 1 assert b"Content-Type: text/plain\r\n\r\nhello world\r\n" in sig_pem # Parse the message to get the signed data, which is the # first payload in the message message = email.parser.BytesParser().parsebytes(sig_pem) payload = message.get_payload() assert isinstance(payload, list) assert isinstance(payload[0], email.message.Message) signed_data = payload[0].as_bytes( policy=message.policy.clone(linesep="\r\n") ) test_support.pkcs7_verify( serialization.Encoding.SMIME, sig_pem, signed_data, [cert], options, ) def test_smime_capabilities(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) sig_binary = builder.sign(serialization.Encoding.DER, []) # 1.2.840.113549.1.9.15 (SMIMECapabilities) as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" in sig_binary # 2.16.840.1.101.3.4.1.42 (aes256-CBC-PAD) as an ASN.1 DER encoded OID aes256_cbc_pad_oid = b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x2a" # 2.16.840.1.101.3.4.1.22 (aes192-CBC-PAD) as an ASN.1 DER encoded OID aes192_cbc_pad_oid = b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x16" # 2.16.840.1.101.3.4.1.2 (aes128-CBC-PAD) as an ASN.1 DER encoded OID aes128_cbc_pad_oid = b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x02" # Each algorithm in SMIMECapabilities should be inside its own # SEQUENCE. # This is encoded as SEQUENCE_IDENTIFIER + LENGTH + ALGORITHM_OID. # This tests that each algorithm is indeed encoded inside its own # sequence. See RFC 2633, Appendix A for more details. sequence_identifier = b"\x30" for oid in [ aes256_cbc_pad_oid, aes192_cbc_pad_oid, aes128_cbc_pad_oid, ]: len_oid = len(oid).to_bytes(length=1, byteorder="big") assert sequence_identifier + len_oid + oid in sig_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], [], ) def test_sign_no_capabilities(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) options = [pkcs7.PKCS7Options.NoCapabilities] sig_binary = builder.sign(serialization.Encoding.DER, options) # NoCapabilities removes the SMIMECapabilities attribute from the # PKCS7 structure. This is an ASN.1 sequence with the # OID 1.2.840.113549.1.9.15. It does NOT remove all authenticated # attributes, so we verify that by looking for the signingTime OID. # 1.2.840.113549.1.9.15 SMIMECapabilities as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" not in sig_binary # 1.2.840.113549.1.9.5 signingTime as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x05" in sig_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, ) def test_sign_no_attributes(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) options = [pkcs7.PKCS7Options.NoAttributes] sig_binary = builder.sign(serialization.Encoding.DER, options) # NoAttributes removes all authenticated attributes, so we shouldn't # find SMIMECapabilities or signingTime. # 1.2.840.113549.1.9.15 SMIMECapabilities as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" not in sig_binary # 1.2.840.113549.1.9.5 signingTime as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x05" not in sig_binary test_support.pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, ) def test_sign_no_certs(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert sig.count(cert.public_bytes(serialization.Encoding.DER)) == 1 options = [pkcs7.PKCS7Options.NoCerts] sig_no = builder.sign(serialization.Encoding.DER, options) assert sig_no.count(cert.public_bytes(serialization.Encoding.DER)) == 0 @pytest.mark.parametrize( "pad", [ padding.PKCS1v15(), None, padding.PSS( mgf=padding.MGF1(hashes.SHA512()), salt_length=padding.PSS.DIGEST_LENGTH, ), ], ) def test_rsa_pkcs_padding_options(self, pad, backend): data = b"hello world" rsa_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) assert isinstance(rsa_key, rsa.RSAPrivateKey) rsa_cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate( pemfile.read() ), mode="rb", ) builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(rsa_cert, rsa_key, hashes.SHA512(), rsa_padding=pad) ) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) # This should be a pkcs1 sha512 signature if isinstance(pad, padding.PSS): # PKCS7_verify can't verify a PSS sig and we don't bind CMS so # we instead just check that a few things are present in the # output. # There should be four SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 4 # There should be one MGF1 OID in this structure assert ( sig.count(b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x08") == 1 ) else: # This should be a pkcs1 RSA signature, which uses the # `rsaEncryption` OID (1.2.840.113549.1.1.1) no matter which # digest algorithm is used. # See RFC 3370 section 3.2 for more details. # This OID appears twice, once in the certificate itself and # another in the SignerInfo data structure in the # `digest_encryption_algorithm` field. assert ( sig.count(b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01") == 2 ) test_support.pkcs7_verify( serialization.Encoding.DER, sig, None, [rsa_cert], options, ) def test_not_rsa_key_with_padding(self, backend): cert, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, hashes.SHA512(), rsa_padding=padding.PKCS1v15() ) def test_rsa_invalid_padding(self, backend): rsa_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) assert isinstance(rsa_key, rsa.RSAPrivateKey) rsa_cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate( pemfile.read() ), mode="rb", ) with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( rsa_cert, rsa_key, hashes.SHA512(), rsa_padding=object(), # type: ignore[arg-type] ) def test_multiple_signers(self, backend): data = b"hello world" cert, key = _load_cert_key() rsa_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) assert isinstance(rsa_key, rsa.RSAPrivateKey) rsa_cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate( pemfile.read() ), mode="rb", ) builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA512()) .add_signer(rsa_cert, rsa_key, hashes.SHA512()) ) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) # There should be three SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 3 test_support.pkcs7_verify( serialization.Encoding.DER, sig, None, [cert, rsa_cert], options, ) def test_multiple_signers_different_hash_algs(self, backend): data = b"hello world" cert, key = _load_cert_key() rsa_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) rsa_cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate( pemfile.read() ), mode="rb", ) assert isinstance(rsa_key, rsa.RSAPrivateKey) builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA384()) .add_signer(rsa_cert, rsa_key, hashes.SHA512()) ) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) # There should be two SHA384 and two SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x02") == 2 assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 2 test_support.pkcs7_verify( serialization.Encoding.DER, sig, None, [cert, rsa_cert], options, ) def test_add_additional_cert_not_a_cert(self, backend): with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_certificate( b"notacert" # type: ignore[arg-type] ) def test_add_additional_cert(self, backend): data = b"hello world" cert, key = _load_cert_key() rsa_cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate( pemfile.read() ), mode="rb", ) builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA384()) .add_certificate(rsa_cert) ) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert ( sig.count(rsa_cert.public_bytes(serialization.Encoding.DER)) == 1 ) def test_add_multiple_additional_certs(self, backend): data = b"hello world" cert, key = _load_cert_key() rsa_cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate( pemfile.read() ), mode="rb", ) builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA384()) .add_certificate(rsa_cert) .add_certificate(rsa_cert) ) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert ( sig.count(rsa_cert.public_bytes(serialization.Encoding.DER)) == 2 ) def _load_rsa_cert_key(): key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) cert = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate(pemfile.read()), mode="rb", ) return cert, key @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported() and backend.rsa_encryption_supported(padding.PKCS1v15()), skip_message="Requires OpenSSL with PKCS7 support and PKCS1 v1.5 padding " "support", ) class TestPKCS7EnvelopeBuilder: def test_invalid_data(self, backend): builder = pkcs7.PKCS7EnvelopeBuilder() with pytest.raises(TypeError): builder.set_data("not bytes") # type: ignore[arg-type] def test_set_data_twice(self, backend): builder = pkcs7.PKCS7EnvelopeBuilder().set_data(b"test") with pytest.raises(ValueError): builder.set_data(b"test") def test_encrypt_no_recipient(self, backend): builder = pkcs7.PKCS7EnvelopeBuilder().set_data(b"test") with pytest.raises(ValueError): builder.encrypt(serialization.Encoding.SMIME, []) def test_encrypt_no_data(self, backend): cert, _ = _load_rsa_cert_key() builder = pkcs7.PKCS7EnvelopeBuilder().add_recipient(cert) with pytest.raises(ValueError): builder.encrypt(serialization.Encoding.SMIME, []) def test_unsupported_encryption(self, backend): cert_non_rsa, _ = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7EnvelopeBuilder().add_recipient(cert_non_rsa) def test_not_a_cert(self, backend): with pytest.raises(TypeError): pkcs7.PKCS7EnvelopeBuilder().add_recipient( b"notacert", # type: ignore[arg-type] ) def test_encrypt_invalid_options(self, backend): cert, _ = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(b"test").add_recipient(cert) ) with pytest.raises(ValueError): builder.encrypt( serialization.Encoding.SMIME, [b"invalid"], # type: ignore[list-item] ) def test_encrypt_invalid_encoding(self, backend): cert, _ = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(b"test").add_recipient(cert) ) with pytest.raises(ValueError): builder.encrypt(serialization.Encoding.Raw, []) @pytest.mark.parametrize( "invalid_options", [ [pkcs7.PKCS7Options.NoAttributes], [pkcs7.PKCS7Options.NoCapabilities], [pkcs7.PKCS7Options.NoCerts], [pkcs7.PKCS7Options.DetachedSignature], [pkcs7.PKCS7Options.Binary, pkcs7.PKCS7Options.Text], ], ) def test_encrypt_invalid_encryption_options( self, backend, invalid_options ): cert, _ = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(b"test").add_recipient(cert) ) with pytest.raises(ValueError): builder.encrypt(serialization.Encoding.DER, invalid_options) @pytest.mark.parametrize( "options", [ [pkcs7.PKCS7Options.Text], [pkcs7.PKCS7Options.Binary], ], ) def test_smime_encrypt_smime_encoding(self, backend, options): data = b"hello world\n" cert, private_key = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) ) enveloped = builder.encrypt(serialization.Encoding.SMIME, options) assert b"MIME-Version: 1.0\n" in enveloped assert b"Content-Transfer-Encoding: base64\n" in enveloped message = email.parser.BytesParser().parsebytes(enveloped) assert message.get_content_disposition() == "attachment" assert message.get_filename() == "smime.p7m" assert message.get_content_type() == "application/pkcs7-mime" assert message.get_param("smime-type") == "enveloped-data" assert message.get_param("name") == "smime.p7m" payload = message.get_payload(decode=True) assert isinstance(payload, bytes) # We want to know if we've serialized something that has the parameters # we expect, so we match on specific byte strings of OIDs & DER values. # OID 2.16.840.1.101.3.4.1.2 (aes128-CBC) assert b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x02" in payload # OID 1.2.840.113549.1.1.1 (rsaEncryption (PKCS #1)) assert b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01" in payload # cryptography CA (the recipient's Common Name) assert ( b"\x0c\x0f\x63\x72\x79\x70\x74\x6f\x67\x72\x61\x70\x68\x79" b"\x20\x43\x41" ) in payload decrypted_bytes = test_support.pkcs7_decrypt( serialization.Encoding.SMIME, enveloped, private_key, cert, options, ) # New lines are canonicalized to '\r\n' when not using Binary expected_data = ( data if pkcs7.PKCS7Options.Binary in options else data.replace(b"\n", b"\r\n") ) assert decrypted_bytes == expected_data @pytest.mark.parametrize( "options", [ [pkcs7.PKCS7Options.Text], [pkcs7.PKCS7Options.Binary], ], ) def test_smime_encrypt_der_encoding(self, backend, options): data = b"hello world\n" cert, private_key = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) ) enveloped = builder.encrypt(serialization.Encoding.DER, options) # We want to know if we've serialized something that has the parameters # we expect, so we match on specific byte strings of OIDs & DER values. # OID 2.16.840.1.101.3.4.1.2 (aes128-CBC) assert b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x02" in enveloped # OID 1.2.840.113549.1.1.1 (rsaEncryption (PKCS #1)) assert b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01" in enveloped # cryptography CA (the recipient's Common Name) assert ( b"\x0c\x0f\x63\x72\x79\x70\x74\x6f\x67\x72\x61\x70\x68\x79" b"\x20\x43\x41" ) in enveloped decrypted_bytes = test_support.pkcs7_decrypt( serialization.Encoding.DER, enveloped, private_key, cert, options, ) # New lines are canonicalized to '\r\n' when not using Binary expected_data = ( data if pkcs7.PKCS7Options.Binary in options else data.replace(b"\n", b"\r\n") ) assert decrypted_bytes == expected_data @pytest.mark.parametrize( "options", [ [pkcs7.PKCS7Options.Text], [pkcs7.PKCS7Options.Binary], ], ) def test_smime_encrypt_pem_encoding(self, backend, options): data = b"hello world\n" cert, private_key = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) ) enveloped = builder.encrypt(serialization.Encoding.PEM, options) decrypted_bytes = test_support.pkcs7_decrypt( serialization.Encoding.PEM, enveloped, private_key, cert, options, ) # New lines are canonicalized to '\r\n' when not using Binary expected_data = ( data if pkcs7.PKCS7Options.Binary in options else data.replace(b"\n", b"\r\n") ) assert decrypted_bytes == expected_data def test_smime_encrypt_multiple_recipients(self, backend): data = b"hello world\n" cert, _ = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder() .set_data(data) .add_recipient(cert) .add_recipient(cert) ) enveloped = builder.encrypt(serialization.Encoding.DER, []) # cryptography CA (the recipient's Common Name) common_name_bytes = ( b"\x0c\x0f\x63\x72\x79\x70\x74\x6f\x67\x72\x61" b"\x70\x68\x79\x20\x43\x41" ) assert enveloped.count(common_name_bytes) == 2 @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported(), skip_message="Requires OpenSSL with PKCS7 support", ) class TestPKCS7SerializeCerts: @pytest.mark.parametrize( ("encoding", "loader"), [ (serialization.Encoding.PEM, pkcs7.load_pem_pkcs7_certificates), (serialization.Encoding.DER, pkcs7.load_der_pkcs7_certificates), ], ) def test_roundtrip(self, encoding, loader, backend): certs = load_vectors_from_file( os.path.join("pkcs7", "amazon-roots.der"), lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), mode="rb", ) p7 = pkcs7.serialize_certificates(certs, encoding) certs2 = loader(p7) assert certs == certs2 def test_ordering(self, backend): certs = load_vectors_from_file( os.path.join("pkcs7", "amazon-roots.der"), lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), mode="rb", ) p7 = pkcs7.serialize_certificates( list(reversed(certs)), serialization.Encoding.DER ) certs2 = pkcs7.load_der_pkcs7_certificates(p7) assert certs == certs2 def test_pem_matches_vector(self, backend): p7_pem = load_vectors_from_file( os.path.join("pkcs7", "isrg.pem"), lambda p: p.read(), mode="rb", ) certs = pkcs7.load_pem_pkcs7_certificates(p7_pem) p7 = pkcs7.serialize_certificates(certs, serialization.Encoding.PEM) assert p7 == p7_pem def test_der_matches_vector(self, backend): p7_der = load_vectors_from_file( os.path.join("pkcs7", "amazon-roots.der"), lambda p: p.read(), mode="rb", ) certs = pkcs7.load_der_pkcs7_certificates(p7_der) p7 = pkcs7.serialize_certificates(certs, serialization.Encoding.DER) assert p7 == p7_der def test_invalid_types(self): certs = load_vectors_from_file( os.path.join("pkcs7", "amazon-roots.der"), lambda derfile: pkcs7.load_der_pkcs7_certificates(derfile.read()), mode="rb", ) with pytest.raises(TypeError): pkcs7.serialize_certificates( object(), # type: ignore[arg-type] serialization.Encoding.PEM, ) with pytest.raises(TypeError): pkcs7.serialize_certificates([], serialization.Encoding.PEM) with pytest.raises(TypeError): pkcs7.serialize_certificates( certs, "not an encoding", # type: ignore[arg-type] ) @pytest.mark.supported( only_if=lambda backend: not backend.pkcs7_supported(), skip_message="Requires OpenSSL without PKCS7 support (BoringSSL)", ) class TestPKCS7Unsupported: def test_pkcs7_functions_unsupported(self): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): pkcs7.load_der_pkcs7_certificates(b"nonsense") with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): pkcs7.load_pem_pkcs7_certificates(b"nonsense") @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported() and not backend.rsa_encryption_supported(padding.PKCS1v15()), skip_message="Requires OpenSSL with no PKCS1 v1.5 padding support", ) class TestPKCS7EnvelopeBuilderUnsupported: def test_envelope_builder_unsupported(self, backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): pkcs7.PKCS7EnvelopeBuilder() cryptography-43.0.0/tests/hazmat/primitives/test_poly1305.py010064400017510000177000000113171464676315000222620ustar 00000000000000# 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. import binascii import os import pytest from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons, ) from cryptography.hazmat.primitives.poly1305 import Poly1305 from ...utils import ( load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) @pytest.mark.supported( only_if=lambda backend: not backend.poly1305_supported(), skip_message="Requires OpenSSL without poly1305 support", ) def test_poly1305_unsupported(backend): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MAC): Poly1305(b"0" * 32) @pytest.mark.supported( only_if=lambda backend: backend.poly1305_supported(), skip_message="Requires OpenSSL with poly1305 support", ) class TestPoly1305: @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("poly1305", "rfc7539.txt"), load_nist_vectors ), ) def test_vectors(self, vector, backend): key = binascii.unhexlify(vector["key"]) msg = binascii.unhexlify(vector["msg"]) tag = binascii.unhexlify(vector["tag"]) poly = Poly1305(key) poly.update(msg) assert poly.finalize() == tag assert Poly1305.generate_tag(key, msg) == tag Poly1305.verify_tag(key, msg, tag) def test_key_with_no_additional_references(self, backend): poly = Poly1305(os.urandom(32)) assert len(poly.finalize()) == 16 def test_raises_after_finalize(self, backend): poly = Poly1305(b"0" * 32) poly.finalize() with pytest.raises(AlreadyFinalized): poly.update(b"foo") with pytest.raises(AlreadyFinalized): poly.finalize() def test_reject_unicode(self, backend): poly = Poly1305(b"0" * 32) with pytest.raises(TypeError): poly.update("") # type:ignore[arg-type] with pytest.raises(TypeError): Poly1305.generate_tag(b"0" * 32, "") # type:ignore[arg-type] def test_verify(self, backend): poly = Poly1305(b"0" * 32) poly.update(b"msg") tag = poly.finalize() with pytest.raises(AlreadyFinalized): poly.verify(b"") poly2 = Poly1305(b"0" * 32) poly2.update(b"msg") poly2.verify(tag) Poly1305.verify_tag(b"0" * 32, b"msg", tag) def test_invalid_verify(self, backend): poly = Poly1305(b"0" * 32) poly.update(b"msg") with pytest.raises(InvalidSignature): poly.verify(b"") p2 = Poly1305(b"0" * 32) p2.update(b"msg") with pytest.raises(InvalidSignature): p2.verify(b"\x00" * 16) with pytest.raises(InvalidSignature): Poly1305.verify_tag(b"0" * 32, b"msg", b"\x00" * 16) def test_verify_reject_unicode(self, backend): poly = Poly1305(b"0" * 32) with pytest.raises(TypeError): poly.verify("") # type:ignore[arg-type] with pytest.raises(TypeError): Poly1305.verify_tag(b"0" * 32, b"msg", "") # type:ignore[arg-type] def test_invalid_key_type(self, backend): with pytest.raises(TypeError): Poly1305(object()) # type:ignore[arg-type] with pytest.raises(TypeError): Poly1305.generate_tag(object(), b"msg") # type:ignore[arg-type] def test_invalid_key_length(self, backend): with pytest.raises(ValueError): Poly1305(b"0" * 31) with pytest.raises(ValueError): Poly1305.generate_tag(b"0" * 31, b"msg") with pytest.raises(ValueError): Poly1305(b"0" * 33) with pytest.raises(ValueError): Poly1305.generate_tag(b"0" * 33, b"msg") def test_buffer_protocol(self, backend): key = binascii.unhexlify( b"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cb" b"c207075c0" ) msg = binascii.unhexlify( b"2754776173206272696c6c69672c20616e642074686520736c69746" b"87920746f7665730a446964206779726520616e642067696d626c65" b"20696e2074686520776162653a0a416c6c206d696d7379207765726" b"52074686520626f726f676f7665732c0a416e6420746865206d6f6d" b"65207261746873206f757467726162652e" ) key = bytearray(key) poly = Poly1305(key) poly.update(bytearray(msg)) assert poly.finalize() == binascii.unhexlify( b"4541669a7eaaee61e708dc7cbcc5eb62" ) assert Poly1305.generate_tag(key, msg) == binascii.unhexlify( b"4541669a7eaaee61e708dc7cbcc5eb62" ) cryptography-43.0.0/tests/hazmat/primitives/test_rsa.py010064400017510000177000003031221464676315000215510ustar 00000000000000# 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. import binascii import copy import itertools import os import pytest from cryptography.exceptions import ( InvalidSignature, UnsupportedAlgorithm, _Reasons, ) from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers, ) from ...doubles import ( DummyAsymmetricPadding, DummyHashAlgorithm, DummyKeySerializationEncryption, ) from ...utils import ( load_nist_vectors, load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) from .fixtures_rsa import ( RSA_KEY_512, RSA_KEY_522, RSA_KEY_599, RSA_KEY_745, RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, RSA_KEY_2048_ALT, RSA_KEY_CORRUPTED, ) from .utils import ( _check_rsa_private_numbers, generate_rsa_verification_test, skip_fips_traditional_openssl, ) @pytest.fixture(scope="session") def rsa_key_512() -> rsa.RSAPrivateKey: return RSA_KEY_512.private_key(unsafe_skip_rsa_key_validation=True) @pytest.fixture(scope="session") def rsa_key_2048() -> rsa.RSAPrivateKey: return RSA_KEY_2048.private_key(unsafe_skip_rsa_key_validation=True) class DummyMGF(padding.MGF): _salt_length = 0 _algorithm = hashes.SHA256() def _check_fips_key_length(backend, private_key): if ( backend._fips_enabled and private_key.key_size < backend._fips_rsa_min_key_size ): pytest.skip(f"Key size not FIPS compliant: {private_key.key_size}") def _flatten_pkcs1_examples(vectors): flattened_vectors = [] for vector in vectors: examples = vector[0].pop("examples") for example in examples: merged_vector = (vector[0], vector[1], example) flattened_vectors.append(merged_vector) return flattened_vectors def _build_oaep_sha2_vectors(): base_path = os.path.join("asymmetric", "RSA", "oaep-custom") vectors = [] hashalgs = [ hashes.SHA1(), hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512(), ] for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): if mgf1alg.name == "sha1" and oaepalg.name == "sha1": # We need to generate the cartesian product of the permutations # of all the SHAs above, but SHA1/SHA1 is something we already # tested previously and thus did not generate custom vectors for. continue examples = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( base_path, f"oaep-{mgf1alg.name}-{oaepalg.name}.txt", ), load_pkcs1_vectors, ) ) # We've loaded the files, but the loaders don't give us any information # about the mgf1 or oaep hash algorithms. We know this info so we'll # just add that to the end of the tuple for private, public, vector in examples: vectors.append((private, public, vector, mgf1alg, oaepalg)) return vectors def _skip_pss_hash_algorithm_unsupported(backend, hash_alg): if not backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH ) ): pytest.skip(f"Does not support {hash_alg.name} in MGF1 using PSS.") def test_skip_pss_hash_algorithm_unsupported(backend): with pytest.raises(pytest.skip.Exception): _skip_pss_hash_algorithm_unsupported(backend, DummyHashAlgorithm()) def test_modular_inverse(): p = int( "d1f9f6c09fd3d38987f7970247b85a6da84907753d42ec52bc23b745093f4fff5cff3" "617ce43d00121a9accc0051f519c76e08cf02fc18acfe4c9e6aea18da470a2b611d2e" "56a7b35caa2c0239bc041a53cc5875ca0b668ae6377d4b23e932d8c995fd1e58ecfd8" "c4b73259c0d8a54d691cca3f6fb85c8a5c1baf588e898d481", 16, ) q = int( "d1519255eb8f678c86cfd06802d1fbef8b664441ac46b73d33d13a8404580a33a8e74" "cb2ea2e2963125b3d454d7a922cef24dd13e55f989cbabf64255a736671f4629a47b5" "b2347cfcd669133088d1c159518531025297c2d67c9da856a12e80222cd03b4c6ec0f" "86c957cb7bb8de7a127b645ec9e820aa94581e4762e209f01", 16, ) assert rsa._modinv(q, p) == int( "0275e06afa722999315f8f322275483e15e2fb46d827b17800f99110b269a6732748f" "624a382fa2ed1ec68c99f7fc56fb60e76eea51614881f497ba7034c17dde955f92f15" "772f8b2b41f3e56d88b1e096cdd293eba4eae1e82db815e0fadea0c4ec971bc6fd875" "c20e67e48c31a611e98d32c6213ae4c4d7b53023b2f80c538", 16, ) class TestRSA: @pytest.mark.parametrize( ("public_exponent", "key_size"), itertools.product( (3, 65537), (1024, 1536, 2048), ), ) def test_generate_rsa_keys(self, backend, public_exponent, key_size): if backend._fips_enabled: if key_size < backend._fips_rsa_min_key_size: pytest.skip(f"Key size not FIPS compliant: {key_size}") if public_exponent < backend._fips_rsa_min_public_exponent: pytest.skip(f"Exponent not FIPS compliant: {public_exponent}") skey = rsa.generate_private_key(public_exponent, key_size, backend) assert skey.key_size == key_size _check_rsa_private_numbers(skey.private_numbers()) pkey = skey.public_key() assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) def test_generate_bad_public_exponent(self, backend): with pytest.raises(ValueError): rsa.generate_private_key( public_exponent=1, key_size=2048, backend=backend ) with pytest.raises(ValueError): rsa.generate_private_key( public_exponent=4, key_size=2048, backend=backend ) with pytest.raises(ValueError): rsa.generate_private_key( public_exponent=65535, key_size=2048, backend=backend ) def test_cant_generate_insecure_tiny_key(self, backend): with pytest.raises(ValueError): rsa.generate_private_key( public_exponent=65537, key_size=511, backend=backend ) with pytest.raises(ValueError): rsa.generate_private_key( public_exponent=65537, key_size=256, backend=backend ) @pytest.mark.parametrize( "pkcs1_example", load_vectors_from_file( os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt" ), load_pkcs1_vectors, ), ) def test_load_pss_vect_example_keys(self, pkcs1_example): secret, public = pkcs1_example private_num = rsa.RSAPrivateNumbers( p=secret["p"], q=secret["q"], d=secret["private_exponent"], dmp1=secret["dmp1"], dmq1=secret["dmq1"], iqmp=secret["iqmp"], public_numbers=rsa.RSAPublicNumbers( e=secret["public_exponent"], n=secret["modulus"] ), ) _check_rsa_private_numbers(private_num) public_num = rsa.RSAPublicNumbers( e=public["public_exponent"], n=public["modulus"] ) assert public_num public_num2 = private_num.public_numbers assert public_num2 assert public_num.n == public_num2.n assert public_num.e == public_num2.e @pytest.mark.supported( only_if=lambda backend: not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL, skip_message="Does not support RSA PSS loading", ) @pytest.mark.parametrize( "path", [ os.path.join("asymmetric", "PKCS8", "rsa_pss_2048.pem"), os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_hash.pem"), os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_hash_mask.pem"), os.path.join( "asymmetric", "PKCS8", "rsa_pss_2048_hash_mask_diff.pem" ), os.path.join( "asymmetric", "PKCS8", "rsa_pss_2048_hash_mask_salt.pem" ), ], ) def test_load_pss_keys_strips_constraints(self, path, backend): key = load_vectors_from_file( filename=path, loader=lambda p: serialization.load_pem_private_key( p.read(), password=None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) # These keys have constraints that prohibit PKCS1v15 signing, # but for now we load them without the constraint and test that # it's truly removed by performing a disallowed signature. assert isinstance(key, rsa.RSAPrivateKey) signature = key.sign(b"whatever", padding.PKCS1v15(), hashes.SHA224()) key.public_key().verify( signature, b"whatever", padding.PKCS1v15(), hashes.SHA224() ) def test_load_pss_pub_keys_strips_constraints(self, backend): key = load_vectors_from_file( filename=os.path.join( "asymmetric", "PKCS8", "rsa_pss_2048_pub.der" ), loader=lambda p: serialization.load_der_public_key( p.read(), ), mode="rb", ) assert isinstance(key, rsa.RSAPublicKey) with pytest.raises(InvalidSignature): key.verify( b"badsig", b"whatever", padding.PKCS1v15(), hashes.SHA256() ) @pytest.mark.supported( only_if=lambda backend: rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL, skip_message="Test requires a backend without RSA-PSS key support", ) def test_load_pss_unsupported(self, backend): # Key loading errors unfortunately have multiple paths so # we need to allow ValueError and UnsupportedAlgorithm with pytest.raises((UnsupportedAlgorithm, ValueError)): load_vectors_from_file( filename=os.path.join( "asymmetric", "PKCS8", "rsa_pss_2048.pem" ), loader=lambda p: serialization.load_pem_private_key( p.read(), password=None ), mode="rb", ) @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("asymmetric", "RSA", "oaep-label.txt"), load_nist_vectors, ), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=b"label", ) ), skip_message="Does not support RSA OAEP labels", ) def test_oaep_label_decrypt(self, vector, backend): private_key = serialization.load_der_private_key( binascii.unhexlify(vector["key"]), None, backend, unsafe_skip_rsa_key_validation=True, ) assert isinstance(private_key, rsa.RSAPrivateKey) assert vector["oaepdigest"] == b"SHA512" decrypted = private_key.decrypt( binascii.unhexlify(vector["input"]), padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA512()), algorithm=hashes.SHA512(), label=binascii.unhexlify(vector["oaeplabel"]), ), ) assert vector["output"][1:-1] == decrypted @pytest.mark.parametrize( ("msg", "label"), [ (b"amazing encrypted msg", b"some label"), (b"amazing encrypted msg", b""), ], ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=b"label", ) ), skip_message="Does not support RSA OAEP labels", ) def test_oaep_label_roundtrip(self, rsa_key_2048, msg, label, backend): private_key = rsa_key_2048 ct = private_key.public_key().encrypt( msg, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=label, ), ) pt = private_key.decrypt( ct, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=label, ), ) assert pt == msg @pytest.mark.parametrize( ("enclabel", "declabel"), [(b"label1", b"label2"), (b"label3", b""), (b"", b"label4")], ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=b"label", ) ), skip_message="Does not support RSA OAEP labels", ) def test_oaep_wrong_label(self, rsa_key_2048, enclabel, declabel, backend): private_key = rsa_key_2048 msg = b"test" ct = private_key.public_key().encrypt( msg, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=enclabel, ), ) with pytest.raises(ValueError): private_key.decrypt( ct, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=declabel, ), ) class TestRSASignature: @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported( hashes.SHA1() ), skip_message="Does not support SHA1 signature.", ) def test_pkcs1v15_signing(self, backend, subtests): vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), load_pkcs1_vectors, ) ) for private, public, example in vectors: with subtests.test(): private_key = rsa.RSAPrivateNumbers( p=private["p"], q=private["q"], d=private["private_exponent"], dmp1=private["dmp1"], dmq1=private["dmq1"], iqmp=private["iqmp"], public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), ).private_key(backend, unsafe_skip_rsa_key_validation=True) signature = private_key.sign( binascii.unhexlify(example["message"]), padding.PKCS1v15(), hashes.SHA1(), ) assert binascii.hexlify(signature) == example["signature"] @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported( hashes.SHA1() ), skip_message="Does not support SHA1 signature.", ) def test_pss_signing(self, subtests, backend): for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt" ), load_pkcs1_vectors, ) ): with subtests.test(): private_key = rsa.RSAPrivateNumbers( p=private["p"], q=private["q"], d=private["private_exponent"], dmp1=private["dmp1"], dmq1=private["dmq1"], iqmp=private["iqmp"], public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), ).private_key(backend, unsafe_skip_rsa_key_validation=True) public_key = rsa.RSAPublicNumbers( e=public["public_exponent"], n=public["modulus"] ).public_key(backend) signature = private_key.sign( binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA1(), ) assert len(signature) == (private_key.key_size + 7) // 8 # PSS signatures contain randomness so we can't do an exact # signature check. Instead we'll verify that the signature # created successfully verifies. public_key.verify( signature, binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA1(), ) @pytest.mark.parametrize( "hash_alg", [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()], ) def test_pss_signing_sha2(self, rsa_key_2048, hash_alg, backend): _skip_pss_hash_algorithm_unsupported(backend, hash_alg) private_key = rsa_key_2048 public_key = private_key.public_key() pss = padding.PSS( mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH ) msg = b"testing signature" signature = private_key.sign(msg, pss, hash_alg) public_key.verify(signature, msg, pss, hash_alg) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH, ) ), skip_message="Does not support PSS.", ) def test_pss_digest_length(self, rsa_key_2048, backend): private_key = rsa_key_2048 signature = private_key.sign( b"some data", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH, ), hashes.SHA256(), ) public = private_key.public_key() public.verify( signature, b"some data", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH, ), hashes.SHA256(), ) public.verify( signature, b"some data", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=32, ), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: ( backend.hash_supported(hashes.SHA512()) and backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ) ), skip_message="Does not support SHA512.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pss_minimum_key_size_for_digest(self, backend): private_key = RSA_KEY_522.private_key( backend, unsafe_skip_rsa_key_validation=True ) private_key.sign( b"no failure", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA512(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pss_signing_digest_too_large_for_key_size( self, rsa_key_512: rsa.RSAPrivateKey, backend ): private_key = rsa_key_512 with pytest.raises(ValueError): private_key.sign( b"msg", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA512(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) def test_pss_signing_salt_length_too_long( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with pytest.raises(ValueError): private_key.sign( b"failure coming", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=1000000 ), hashes.SHA256(), ) def test_unsupported_padding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.sign(b"msg", DummyAsymmetricPadding(), hashes.SHA256()) def test_padding_incorrect_type( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with pytest.raises(TypeError): private_key.sign( b"msg", "notpadding", # type: ignore[arg-type] hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) def test_unsupported_pss_mgf( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.sign( b"msg", padding.PSS( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO, ) ), skip_message="Does not support PSS.", ) def test_pss_sign_unsupported_auto( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with pytest.raises(ValueError): private_key.sign( b"some data", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO, ), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pkcs1_digest_too_large_for_key_size(self, backend): private_key = RSA_KEY_599.private_key( backend, unsafe_skip_rsa_key_validation=True ) with pytest.raises(ValueError): private_key.sign( b"failure coming", padding.PKCS1v15(), hashes.SHA512() ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pkcs1_minimum_key_size(self, backend): private_key = RSA_KEY_745.private_key( backend, unsafe_skip_rsa_key_validation=True ) private_key.sign(b"no failure", padding.PKCS1v15(), hashes.SHA512()) @pytest.mark.parametrize( "message", [ b"one little message", bytearray(b"one little message"), ], ) def test_sign(self, rsa_key_2048: rsa.RSAPrivateKey, message, backend): private_key = rsa_key_2048 pkcs = padding.PKCS1v15() algorithm = hashes.SHA256() signature = private_key.sign(message, pkcs, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, pkcs, algorithm) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) def test_prehashed_sign(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 message = b"one little message" h = hashes.Hash(hashes.SHA256(), backend) h.update(message) digest = h.finalize() pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) signature = private_key.sign(digest, pss, prehashed_alg) public_key = private_key.public_key() public_key.verify(signature, message, pss, hashes.SHA256()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH, ) ), skip_message="Does not support PSS.", ) def test_prehashed_digest_length( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 message = b"one little message" h = hashes.Hash(hashes.SHA256(), backend) h.update(message) digest = h.finalize() pss = padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH, ) prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) signature = private_key.sign(digest, pss, prehashed_alg) public_key = private_key.public_key() public_key.verify(signature, message, pss, hashes.SHA256()) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.BLAKE2s(digest_size=32) ), skip_message="Does not support BLAKE2s", ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) def test_unsupported_hash(self, rsa_key_512: rsa.RSAPrivateKey, backend): private_key = rsa_key_512 message = b"one little message" pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): private_key.sign(message, pss, hashes.BLAKE2s(32)) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) def test_unsupported_hash_pss_mgf1(self, rsa_key_2048: rsa.RSAPrivateKey): private_key = rsa_key_2048 message = b"my message" pss = padding.PSS( mgf=padding.MGF1(DummyHashAlgorithm()), salt_length=0 ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): private_key.sign(message, pss, hashes.SHA256()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) def test_prehashed_digest_mismatch( self, rsa_key_512: rsa.RSAPrivateKey, backend ): private_key = rsa_key_512 message = b"one little message" h = hashes.Hash(hashes.SHA512(), backend) h.update(message) digest = h.finalize() pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) with pytest.raises(ValueError): private_key.sign(digest, pss, prehashed_alg) def test_prehashed_unsupported_in_signature_recover( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() signature = private_key.sign( b"sign me", padding.PKCS1v15(), hashes.SHA256() ) prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) with pytest.raises(TypeError): public_key.recover_data_from_signature( signature, padding.PKCS1v15(), prehashed_alg, # type: ignore[arg-type] ) def test_corrupted_private_key(self, backend): with pytest.raises(ValueError): serialization.load_pem_private_key( RSA_KEY_CORRUPTED, password=None, backend=backend ) class TestRSAVerification: @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported( hashes.SHA1() ), skip_message="Does not support SHA1 signature.", ) def test_pkcs1v15_verification(self, backend, subtests): vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), load_pkcs1_vectors, ) ) for private, public, example in vectors: with subtests.test(): public_key = rsa.RSAPublicNumbers( e=public["public_exponent"], n=public["modulus"] ).public_key(backend) signature = binascii.unhexlify(example["signature"]) message = binascii.unhexlify(example["message"]) public_key.verify( signature, message, padding.PKCS1v15(), hashes.SHA1() ) # Test digest recovery by providing hash digest = hashes.Hash(hashes.SHA1()) digest.update(message) msg_digest = digest.finalize() rec_msg_digest = public_key.recover_data_from_signature( signature, padding.PKCS1v15(), hashes.SHA1() ) assert msg_digest == rec_msg_digest # Test recovery of all data (full DigestInfo) with hash alg. as # None rec_sig_data = public_key.recover_data_from_signature( signature, padding.PKCS1v15(), None ) assert len(rec_sig_data) > len(msg_digest) assert msg_digest == rec_sig_data[-len(msg_digest) :] @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) def test_invalid_pkcs1v15_signature_wrong_data( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() signature = private_key.sign( b"sign me", padding.PKCS1v15(), hashes.SHA256() ) with pytest.raises(InvalidSignature): public_key.verify( signature, b"incorrect data", padding.PKCS1v15(), hashes.SHA256(), ) def test_invalid_pkcs1v15_signature_recover_wrong_hash_alg( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() signature = private_key.sign( b"sign me", padding.PKCS1v15(), hashes.SHA256() ) with pytest.raises(InvalidSignature): public_key.recover_data_from_signature( signature, padding.PKCS1v15(), hashes.SHA512() ) def test_invalid_signature_sequence_removed(self, backend): """ This test comes from wycheproof """ key_der = binascii.unhexlify( b"30820122300d06092a864886f70d01010105000382010f003082010a02820101" b"00a2b451a07d0aa5f96e455671513550514a8a5b462ebef717094fa1fee82224" b"e637f9746d3f7cafd31878d80325b6ef5a1700f65903b469429e89d6eac88450" b"97b5ab393189db92512ed8a7711a1253facd20f79c15e8247f3d3e42e46e48c9" b"8e254a2fe9765313a03eff8f17e1a029397a1fa26a8dce26f490ed81299615d9" b"814c22da610428e09c7d9658594266f5c021d0fceca08d945a12be82de4d1ece" b"6b4c03145b5d3495d4ed5411eb878daf05fd7afc3e09ada0f1126422f590975a" b"1969816f48698bcbba1b4d9cae79d460d8f9f85e7975005d9bc22c4e5ac0f7c1" b"a45d12569a62807d3b9a02e5a530e773066f453d1f5b4c2e9cf7820283f742b9" b"d50203010001" ) sig = binascii.unhexlify( b"498209f59a0679a1f926eccf3056da2cba553d7ab3064e7c41ad1d739f038249" b"f02f5ad12ee246073d101bc3cdb563e8b6be61562056422b7e6c16ad53deb12a" b"f5de744197753a35859833f41bb59c6597f3980132b7478fd0b95fd27dfad64a" b"20fd5c25312bbd41a85286cd2a83c8df5efa0779158d01b0747ff165b055eb28" b"80ea27095700a295593196d8c5922cf6aa9d7e29b5056db5ded5eb20aeb31b89" b"42e26b15a5188a4934cd7e39cfe379a197f49a204343a493452deebca436ee61" b"4f4daf989e355544489f7e69ffa8ccc6a1e81cf0ab33c3e6d7591091485a6a31" b"bda3b33946490057b9a3003d3fd9daf7c4778b43fd46144d945d815f12628ff4" ) public_key = serialization.load_der_public_key(key_der, backend) assert isinstance(public_key, rsa.RSAPublicKey) with pytest.raises(InvalidSignature): public_key.verify( sig, binascii.unhexlify(b"313233343030"), padding.PKCS1v15(), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) def test_invalid_pkcs1v15_signature_wrong_key( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 private_key2 = RSA_KEY_2048_ALT.private_key( backend, unsafe_skip_rsa_key_validation=True ) public_key = private_key2.public_key() msg = b"sign me" signature = private_key.sign(msg, padding.PKCS1v15(), hashes.SHA256()) with pytest.raises(InvalidSignature): public_key.verify( signature, msg, padding.PKCS1v15(), hashes.SHA256() ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=20) ), skip_message="Does not support PSS.", ) @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported( hashes.SHA1() ), skip_message="Does not support SHA1 signature.", ) def test_pss_verification(self, subtests, backend): for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt" ), load_pkcs1_vectors, ) ): with subtests.test(): public_key = rsa.RSAPublicNumbers( e=public["public_exponent"], n=public["modulus"] ).public_key(backend) public_key.verify( binascii.unhexlify(example["signature"]), binascii.unhexlify(example["message"]), padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA1()), salt_length=20, ), hashes.SHA1(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO, ) ), skip_message="Does not support PSS.", ) def test_pss_verify_auto_salt_length( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 signature = private_key.sign( b"some data", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(), ) private_key.public_key().verify( signature, b"some data", padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO, ), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_wrong_data(self, backend): public_key = rsa.RSAPublicNumbers( n=int( b"dffc2137d5e810cde9e4b4612f5796447218bab913b3fa98bdf7982e4fa6" b"ec4d6653ef2b29fb1642b095befcbea6decc178fb4bed243d3c3592c6854" b"6af2d3f3", 16, ), e=65537, ).public_key(backend) signature = binascii.unhexlify( b"0e68c3649df91c5bc3665f96e157efa75b71934aaa514d91e94ca8418d100f45" b"6f05288e58525f99666bab052adcffdf7186eb40f583bd38d98c97d3d524808b" ) with pytest.raises(InvalidSignature): public_key.verify( signature, b"incorrect data", padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_wrong_key(self, backend): signature = binascii.unhexlify( b"3a1880165014ba6eb53cc1449d13e5132ebcc0cfd9ade6d7a2494a0503bd0826" b"f8a46c431e0d7be0ca3e453f8b2b009e2733764da7927cc6dbe7a021437a242e" ) public_key = rsa.RSAPublicNumbers( n=int( b"381201f4905d67dfeb3dec131a0fbea773489227ec7a1448c3109189ac68" b"5a95441be90866a14c4d2e139cd16db540ec6c7abab13ffff91443fd46a8" b"960cbb7658ded26a5c95c86f6e40384e1c1239c63e541ba221191c4dd303" b"231b42e33c6dbddf5ec9a746f09bf0c25d0f8d27f93ee0ae5c0d723348f4" b"030d3581e13522e1", 16, ), e=65537, ).public_key(backend) with pytest.raises(InvalidSignature): public_key.verify( signature, b"sign me", padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): # 2048 bit PSS signature signature = binascii.unhexlify( b"58750fc3d2f560d1f3e37c8e28bc8da6d3e93f5d58f8becd25b1c931eea30fea" b"54cb17d44b90104a0aacb7fe9ffa2a59c5788435911d63de78178d21eb875ccd" b"0b07121b641ed4fe6bcb1ca5060322765507b4f24bdba8a698a8e4e07e6bf2c4" b"7a736abe5a912e85cd32f648f3e043b4385e8b612dcce342c5fddf18c524deb5" b"6295b95f6dfa759b2896b793628a90f133e74c1ff7d3af43e3f7ee792df2e5b6" b"a19e996ac3676884354899a437b3ae4e3ac91976c336c332a3b1db0d172b19cb" b"40ad3d871296cfffb3c889ce74a179a3e290852c35d59525afe4b39dc907fad2" b"ac462c50a488dca486031a3dc8c4cdbbc53e9f71d64732e1533a5d1249b833ce" ) # 1024 bit key public_key = RSA_KEY_1024.private_key( unsafe_skip_rsa_key_validation=True ).public_key() with pytest.raises(InvalidSignature): public_key.verify( signature, b"sign me", padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(), ) def test_invalid_pss_signature_recover( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() pss_padding = padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) signature = private_key.sign(b"sign me", pss_padding, hashes.SHA256()) # Hash algorithm cannot be absent for PSS padding with pytest.raises(TypeError): public_key.recover_data_from_signature( signature, pss_padding, None ) # Signature data recovery not supported with PSS with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): public_key.recover_data_from_signature( signature, pss_padding, hashes.SHA256() ) def test_unsupported_padding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): public_key.verify( b"sig", b"msg", DummyAsymmetricPadding(), hashes.SHA256() ) def test_padding_incorrect_type( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() with pytest.raises(TypeError): public_key.verify( b"sig", b"msg", "notpadding", # type: ignore[arg-type] hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=0) ), skip_message="Does not support PSS.", ) def test_unsupported_pss_mgf( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): public_key.verify( b"sig", b"msg", padding.PSS( mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA512()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pss_verify_digest_too_large_for_key_size( self, rsa_key_512: rsa.RSAPrivateKey, backend ): private_key = rsa_key_512 signature = binascii.unhexlify( b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" b"534c050ef6b19b1bdc6eb4da422e89161106a6f5b5cc16135b11eb6439b646bd" ) public_key = private_key.public_key() with pytest.raises(ValueError): public_key.verify( signature, b"msg doesn't matter", padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA512()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA512(), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS.", ) @pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.") def test_pss_verify_salt_length_too_long(self, backend): signature = binascii.unhexlify( b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" b"534c050ef6b19b1bdc6eb4da422e89161106a6f5b5cc16135b11eb6439b646bd" ) public_key = rsa.RSAPublicNumbers( n=int( b"d309e4612809437548b747d7f9eb9cd3340f54fe42bb3f84a36933b0839c" b"11b0c8b7f67e11f7252370161e31159c49c784d4bc41c42a78ce0f0b40a3" b"ca8ffb91", 16, ), e=65537, ).public_key(backend) with pytest.raises(InvalidSignature): public_key.verify( signature, b"sign me", padding.PSS( mgf=padding.MGF1( algorithm=hashes.SHA256(), ), salt_length=1000000, ), hashes.SHA256(), ) @pytest.mark.parametrize( "message", [ b"one little message", bytearray(b"one little message"), ], ) def test_verify(self, rsa_key_2048: rsa.RSAPrivateKey, message, backend): private_key = rsa_key_2048 pkcs = padding.PKCS1v15() algorithm = hashes.SHA256() signature = private_key.sign(message, pkcs, algorithm) public_key = private_key.public_key() public_key.verify(signature, message, pkcs, algorithm) def test_prehashed_verify(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 message = b"one little message" h = hashes.Hash(hashes.SHA256(), backend) h.update(message) digest = h.finalize() prehashed_alg = asym_utils.Prehashed(hashes.SHA256()) pkcs = padding.PKCS1v15() signature = private_key.sign(message, pkcs, hashes.SHA256()) public_key = private_key.public_key() public_key.verify(signature, digest, pkcs, prehashed_alg) def test_prehashed_digest_mismatch( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): public_key = rsa_key_2048.public_key() message = b"one little message" h = hashes.Hash(hashes.SHA256(), backend) h.update(message) data = h.finalize() prehashed_alg = asym_utils.Prehashed(hashes.SHA512()) pkcs = padding.PKCS1v15() with pytest.raises(ValueError): public_key.verify(b"\x00" * 64, data, pkcs, prehashed_alg) class TestRSAPSSMGF1Verification: test_rsa_pss_mgf1_sha1 = pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA1()), salt_length=padding.PSS.MAX_LENGTH, ) ) and backend.signature_hash_supported(hashes.SHA1()), skip_message=( "Does not support PSS using MGF1 with SHA1 or SHA1 signature." ), )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGenPSS_186-2.rsp", "SigGenPSS_186-3.rsp", "SigVerPSS_186-3.rsp", ], hashes.SHA1(), lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, ), salt_length=params["salt_length"], ), ) ) test_rsa_pss_mgf1_sha224 = pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA224()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS using MGF1 with SHA224.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGenPSS_186-2.rsp", "SigGenPSS_186-3.rsp", "SigVerPSS_186-3.rsp", ], hashes.SHA224(), lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, ), salt_length=params["salt_length"], ), ) ) test_rsa_pss_mgf1_sha256 = pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS using MGF1 with SHA256.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGenPSS_186-2.rsp", "SigGenPSS_186-3.rsp", "SigVerPSS_186-3.rsp", ], hashes.SHA256(), lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, ), salt_length=params["salt_length"], ), ) ) test_rsa_pss_mgf1_sha384 = pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA384()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS using MGF1 with SHA384.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGenPSS_186-2.rsp", "SigGenPSS_186-3.rsp", "SigVerPSS_186-3.rsp", ], hashes.SHA384(), lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, ), salt_length=params["salt_length"], ), ) ) test_rsa_pss_mgf1_sha512 = pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( mgf=padding.MGF1(hashes.SHA512()), salt_length=padding.PSS.MAX_LENGTH, ) ), skip_message="Does not support PSS using MGF1 with SHA512.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGenPSS_186-2.rsp", "SigGenPSS_186-3.rsp", "SigVerPSS_186-3.rsp", ], hashes.SHA512(), lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, ), salt_length=params["salt_length"], ), ) ) class TestRSAPKCS1Verification: test_rsa_pkcs1v15_verify_sha1 = pytest.mark.supported( only_if=lambda backend: ( backend.signature_hash_supported(hashes.SHA1()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA1 and PKCS1v1.5.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGen15_186-2.rsp", "SigGen15_186-3.rsp", "SigVer15_186-3.rsp", ], hashes.SHA1(), lambda params, hash_alg: padding.PKCS1v15(), ) ) test_rsa_pkcs1v15_verify_sha224 = pytest.mark.supported( only_if=lambda backend: ( backend.signature_hash_supported(hashes.SHA224()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA224 and PKCS1v1.5.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGen15_186-2.rsp", "SigGen15_186-3.rsp", "SigVer15_186-3.rsp", ], hashes.SHA224(), lambda params, hash_alg: padding.PKCS1v15(), ) ) test_rsa_pkcs1v15_verify_sha256 = pytest.mark.supported( only_if=lambda backend: ( backend.signature_hash_supported(hashes.SHA256()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA256 and PKCS1v1.5.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGen15_186-2.rsp", "SigGen15_186-3.rsp", "SigVer15_186-3.rsp", ], hashes.SHA256(), lambda params, hash_alg: padding.PKCS1v15(), ) ) test_rsa_pkcs1v15_verify_sha384 = pytest.mark.supported( only_if=lambda backend: ( backend.signature_hash_supported(hashes.SHA384()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA384 and PKCS1v1.5.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGen15_186-2.rsp", "SigGen15_186-3.rsp", "SigVer15_186-3.rsp", ], hashes.SHA384(), lambda params, hash_alg: padding.PKCS1v15(), ) ) test_rsa_pkcs1v15_verify_sha512 = pytest.mark.supported( only_if=lambda backend: ( backend.signature_hash_supported(hashes.SHA512()) and backend.rsa_padding_supported(padding.PKCS1v15()) ), skip_message="Does not support SHA512 and PKCS1v1.5.", )( generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), [ "SigGen15_186-2.rsp", "SigGen15_186-3.rsp", "SigVer15_186-3.rsp", ], hashes.SHA512(), lambda params, hash_alg: padding.PKCS1v15(), ) ) class TestPSS: def test_calculate_max_pss_salt_length(self): with pytest.raises(TypeError): padding.calculate_max_pss_salt_length( object(), # type:ignore[arg-type] hashes.SHA256(), ) def test_invalid_salt_length_not_integer(self): with pytest.raises(TypeError): padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=b"not_a_length", # type:ignore[arg-type] ) def test_invalid_salt_length_negative_integer(self): with pytest.raises(ValueError): padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=-1) def test_valid_pss_parameters(self): algorithm = hashes.SHA256() salt_length = algorithm.digest_size mgf = padding.MGF1(algorithm) pss = padding.PSS(mgf=mgf, salt_length=salt_length) assert pss._mgf == mgf assert pss._salt_length == salt_length def test_valid_pss_parameters_maximum(self): algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) pss = padding.PSS(mgf=mgf, salt_length=padding.PSS.MAX_LENGTH) assert pss._mgf == mgf assert pss._salt_length == padding.PSS.MAX_LENGTH def test_mgf_property(self): algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) pss = padding.PSS(mgf=mgf, salt_length=padding.PSS.MAX_LENGTH) assert pss.mgf == mgf assert pss.mgf == pss._mgf class TestMGF1: def test_invalid_hash_algorithm(self): with pytest.raises(TypeError): padding.MGF1(b"not_a_hash") # type:ignore[arg-type] def test_valid_mgf1_parameters(self): algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) assert mgf._algorithm == algorithm class TestOAEP: def test_invalid_algorithm(self): mgf = padding.MGF1(hashes.SHA256()) with pytest.raises(TypeError): padding.OAEP( mgf=mgf, algorithm=b"", # type:ignore[arg-type] label=None, ) def test_algorithm_property(self): algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) oaep = padding.OAEP(mgf=mgf, algorithm=algorithm, label=None) assert oaep.algorithm == algorithm assert oaep.algorithm == oaep._algorithm def test_mgf_property(self): algorithm = hashes.SHA256() mgf = padding.MGF1(algorithm) oaep = padding.OAEP(mgf=mgf, algorithm=algorithm, label=None) assert oaep.mgf == mgf assert oaep.mgf == oaep._mgf class TestRSADecryption: @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) def test_decrypt_pkcs1v15_vectors(self, backend, subtests): vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"), load_pkcs1_vectors, ) ) for private, public, example in vectors: with subtests.test(): skey = rsa.RSAPrivateNumbers( p=private["p"], q=private["q"], d=private["private_exponent"], dmp1=private["dmp1"], dmq1=private["dmq1"], iqmp=private["iqmp"], public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), ).private_key(backend, unsafe_skip_rsa_key_validation=True) ciphertext = binascii.unhexlify(example["encryption"]) assert len(ciphertext) == (skey.key_size + 7) // 8 message = skey.decrypt(ciphertext, padding.PKCS1v15()) assert message == binascii.unhexlify(example["message"]) def test_unsupported_padding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.decrypt(b"0" * 256, DummyAsymmetricPadding()) @pytest.mark.supported( only_if=lambda backend: ( backend.rsa_encryption_supported(padding.PKCS1v15()) and not backend._lib.Cryptography_HAS_IMPLICIT_RSA_REJECTION ), skip_message="Does not support PKCS1v1.5.", ) def test_decrypt_invalid_decrypt( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with pytest.raises(ValueError): private_key.decrypt(b"\x00" * 256, padding.PKCS1v15()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) def test_decrypt_ciphertext_too_large( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with pytest.raises(ValueError): private_key.decrypt(b"\x00" * 257, padding.PKCS1v15()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) def test_decrypt_ciphertext_too_small( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 ct = binascii.unhexlify( b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1" b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0" ) with pytest.raises(ValueError): private_key.decrypt(ct, padding.PKCS1v15()) @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None, ) ), skip_message="Does not support OAEP.", ) def test_decrypt_oaep_sha1_vectors(self, subtests, backend): for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt" ), load_pkcs1_vectors, ) ): with subtests.test(): skey = rsa.RSAPrivateNumbers( p=private["p"], q=private["q"], d=private["private_exponent"], dmp1=private["dmp1"], dmq1=private["dmq1"], iqmp=private["iqmp"], public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), ).private_key(backend, unsafe_skip_rsa_key_validation=True) message = skey.decrypt( binascii.unhexlify(example["encryption"]), padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None, ), ) assert message == binascii.unhexlify(example["message"]) def test_decrypt_oaep_sha2_vectors(self, backend, subtests): vectors = _build_oaep_sha2_vectors() for private, public, example, mgf1_alg, hash_alg in vectors: with subtests.test(): pad = padding.OAEP( mgf=padding.MGF1(algorithm=mgf1_alg), algorithm=hash_alg, label=None, ) if not backend.rsa_encryption_supported(pad): pytest.skip( f"Does not support OAEP using {mgf1_alg.name} MGF1 " f"or {hash_alg.name} hash." ) skey = rsa.RSAPrivateNumbers( p=private["p"], q=private["q"], d=private["private_exponent"], dmp1=private["dmp1"], dmq1=private["dmq1"], iqmp=private["iqmp"], public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), ).private_key(backend, unsafe_skip_rsa_key_validation=True) message = skey.decrypt( binascii.unhexlify(example["encryption"]), pad, ) assert message == binascii.unhexlify(example["message"]) @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ) ), skip_message="Does not support OAEP.", ) def test_invalid_oaep_decryption( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): # More recent versions of OpenSSL may raise different errors. # This test triggers a failure and confirms that we properly handle # it. private_key = rsa_key_2048 ciphertext = private_key.public_key().encrypt( b"secure data", padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ), ) private_key_alt = RSA_KEY_2048_ALT.private_key( backend, unsafe_skip_rsa_key_validation=True ) with pytest.raises(ValueError): private_key_alt.decrypt( ciphertext, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ), ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None, ) ), skip_message="Does not support OAEP.", ) def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): key = RSA_KEY_2048_ALT.private_key( backend, unsafe_skip_rsa_key_validation=True ) ciphertext = ( b"\xb1ph\xc0\x0b\x1a|\xe6\xda\xea\xb5\xd7%\x94\x07\xf96\xfb\x96" b"\x11\x9b\xdc4\xea.-\x91\x80\x13S\x94\x04m\xe9\xc5/F\x1b\x9b:\\" b"\x1d\x04\x16ML\xae\xb32J\x01yuA\xbb\x83\x1c\x8f\xf6\xa5\xdbp\xcd" b"\nx\xc7\xf6\x15\xb2/\xdcH\xae\xe7\x13\x13by\r4t\x99\x0fc\x1f\xc1" b"\x1c\xb1\xdd\xc5\x08\xd1\xee\xa1XQ\xb8H@L5v\xc3\xaf\xf2\r\x97" b"\xed\xaa\xe7\xf1\xd4xai\xd3\x83\xd9\xaa9\xbfx\xe1\x87F \x01\xff" b"L\xccv}ae\xb3\xfa\xf2B\xb8\xf9\x04H\x94\x85\xcb\x86\xbb\\ghx!W31" b"\xc7;t\na_E\xc2\x16\xb0;\xa1\x18\t\x1b\xe1\xdb\x80>)\x15\xc6\x12" b"\xcb\xeeg`\x8b\x9b\x1b\x05y4\xb0\x84M6\xcd\xa1\x827o\xfd\x96\xba" b"Z#\x8d\xae\x01\xc9\xf2\xb6\xde\x89{8&eQ\x1e8\x03\x01#?\xb66\\" b"\xad.\xe9\xfa!\x95 c{\xcaz\xe0*\tP\r\x91\x9a)B\xb5\xadN\xf4$\x83" b"\t\xb5u\xab\x19\x99" ) with pytest.raises(ValueError): key.decrypt( ciphertext, padding.OAEP( algorithm=hashes.SHA1(), mgf=padding.MGF1(hashes.SHA1()), label=None, ), ) def test_unsupported_oaep_hash(self, rsa_key_2048: rsa.RSAPrivateKey): private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): private_key.decrypt( b"0" * 256, padding.OAEP( mgf=padding.MGF1(DummyHashAlgorithm()), algorithm=hashes.SHA256(), label=None, ), ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): private_key.decrypt( b"0" * 256, padding.OAEP( mgf=padding.MGF1(hashes.SHA256()), algorithm=DummyHashAlgorithm(), label=None, ), ) def test_unsupported_oaep_mgf( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.decrypt( b"0" * 256, padding.OAEP( mgf=DummyMGF(), algorithm=hashes.SHA256(), label=None, ), ) class TestRSAEncryption: @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ) ), skip_message="Does not support OAEP.", ) @pytest.mark.parametrize( ("key_data", "pad"), itertools.product( ( RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, ), [ padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ) ], ), ) def test_rsa_encrypt_oaep(self, key_data, pad, backend): private_key = key_data.private_key(unsafe_skip_rsa_key_validation=True) _check_fips_key_length(backend, private_key) pt = b"encrypt me!" public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) assert ct != pt assert len(ct) == (public_key.key_size + 7) // 8 recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt @pytest.mark.parametrize( ("mgf1hash", "oaephash"), itertools.product( [ hashes.SHA1(), hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512(), ], [ hashes.SHA1(), hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512(), ], ), ) def test_rsa_encrypt_oaep_sha2( self, rsa_key_2048: rsa.RSAPrivateKey, mgf1hash, oaephash, backend ): pad = padding.OAEP( mgf=padding.MGF1(algorithm=mgf1hash), algorithm=oaephash, label=None, ) if not backend.rsa_encryption_supported(pad): pytest.skip( f"Does not support OAEP using {mgf1hash.name} MGF1 " f"or {oaephash.name} hash." ) private_key = rsa_key_2048 pt = b"encrypt me using sha2 hashes!" public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) assert ct != pt assert len(ct) == (public_key.key_size + 7) // 8 recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", ) @pytest.mark.parametrize( ("key_data", "pad"), itertools.product( ( RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, ), [padding.PKCS1v15()], ), ) def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): private_key = key_data.private_key(unsafe_skip_rsa_key_validation=True) _check_fips_key_length(backend, private_key) pt = b"encrypt me!" public_key = private_key.public_key() ct = public_key.encrypt(pt, pad) assert ct != pt assert len(ct) == (public_key.key_size + 7) // 8 recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt @pytest.mark.parametrize( ("key_data", "pad"), itertools.product( ( RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, ), ( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None, ), padding.PKCS1v15(), ), ), ) def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): private_key = key_data.private_key(unsafe_skip_rsa_key_validation=True) if not backend.rsa_encryption_supported(pad): pytest.skip("PKCS1v15 padding not allowed in FIPS") _check_fips_key_length(backend, private_key) public_key = private_key.public_key() # Slightly smaller than the key size but not enough for padding. with pytest.raises(ValueError): public_key.encrypt(b"\x00" * (private_key.key_size // 8 - 1), pad) # Larger than the key size. with pytest.raises(ValueError): public_key.encrypt(b"\x00" * (private_key.key_size // 8 + 5), pad) @pytest.mark.supported( only_if=lambda backend: backend._fips_enabled, skip_message="Requires FIPS", ) def test_rsa_fips_small_key(self, rsa_key_512: rsa.RSAPrivateKey, backend): with pytest.raises(ValueError): rsa_key_512.sign(b"somedata", padding.PKCS1v15(), hashes.SHA512()) def test_unsupported_padding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): public_key.encrypt(b"somedata", DummyAsymmetricPadding()) with pytest.raises(TypeError): public_key.encrypt( b"somedata", padding=object(), # type: ignore[arg-type] ) def test_unsupported_oaep_mgf( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): public_key.encrypt( b"ciphertext", padding.OAEP( mgf=DummyMGF(), algorithm=hashes.SHA256(), label=None, ), ) class TestRSANumbers: def test_rsa_public_numbers(self): public_numbers = rsa.RSAPublicNumbers(e=1, n=15) assert public_numbers.e == 1 assert public_numbers.n == 15 def test_rsa_private_numbers(self): public_numbers = rsa.RSAPublicNumbers(e=1, n=15) private_numbers = rsa.RSAPrivateNumbers( p=3, q=5, d=1, dmp1=1, dmq1=1, iqmp=2, public_numbers=public_numbers, ) assert private_numbers.p == 3 assert private_numbers.q == 5 assert private_numbers.d == 1 assert private_numbers.dmp1 == 1 assert private_numbers.dmq1 == 1 assert private_numbers.iqmp == 2 assert private_numbers.public_numbers == public_numbers def test_rsa_private_numbers_create_key(self, backend): private_key = RSA_KEY_1024.private_key( backend, unsafe_skip_rsa_key_validation=True ) assert private_key def test_rsa_public_numbers_create_key(self, backend): public_key = RSA_KEY_1024.public_numbers.public_key(backend) assert public_key public_key = rsa.RSAPublicNumbers(n=10, e=3).public_key(backend) assert public_key def test_public_numbers_invalid_types(self): with pytest.raises(TypeError): rsa.RSAPublicNumbers(e=None, n=15) # type: ignore[arg-type] with pytest.raises(TypeError): rsa.RSAPublicNumbers(e=1, n=None) # type: ignore[arg-type] @pytest.mark.parametrize( ("p", "q", "d", "dmp1", "dmq1", "iqmp", "public_numbers"), [ (None, 5, 1, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), (3, None, 1, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), (3, 5, None, 1, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), (3, 5, 1, None, 1, 2, rsa.RSAPublicNumbers(e=1, n=15)), (3, 5, 1, 1, None, 2, rsa.RSAPublicNumbers(e=1, n=15)), (3, 5, 1, 1, 1, None, rsa.RSAPublicNumbers(e=1, n=15)), (3, 5, 1, 1, 1, 2, None), ], ) def test_private_numbers_invalid_types( self, p, q, d, dmp1, dmq1, iqmp, public_numbers ): with pytest.raises(TypeError): rsa.RSAPrivateNumbers( p=p, q=q, d=d, dmp1=dmp1, dmq1=dmq1, iqmp=iqmp, public_numbers=public_numbers, ) @pytest.mark.parametrize( ("e", "n"), [ (7, 2), # modulus < 3 (1, 15), # public_exponent < 3 (17, 15), # public_exponent > modulus (14, 15), # public_exponent not odd ], ) def test_invalid_public_numbers_argument_values(self, e, n, backend): # Start with public_exponent=7, modulus=15. Then change one value at a # time to test the bounds. with pytest.raises(ValueError): rsa.RSAPublicNumbers(e=e, n=n).public_key(backend) @pytest.mark.parametrize( ("p", "q", "d", "dmp1", "dmq1", "iqmp", "e", "n"), [ (3, 11, 3, 1, 3, 2, 7, 2), # modulus < 3 (3, 11, 3, 1, 3, 2, 7, 35), # modulus != p * q (37, 11, 3, 1, 3, 2, 7, 33), # p > modulus (3, 37, 3, 1, 3, 2, 7, 33), # q > modulus (3, 11, 3, 35, 3, 2, 7, 33), # dmp1 > modulus (3, 11, 3, 1, 35, 2, 7, 33), # dmq1 > modulus (3, 11, 3, 1, 3, 35, 7, 33), # iqmp > modulus (3, 11, 37, 1, 3, 2, 7, 33), # d > modulus (3, 11, 3, 1, 3, 2, 1, 33), # public_exponent < 3 (3, 11, 3, 1, 3, 35, 65537, 33), # public_exponent > modulus (3, 11, 3, 1, 3, 2, 6, 33), # public_exponent is not odd (3, 11, 3, 2, 3, 2, 7, 33), # dmp1 is not odd (3, 11, 3, 1, 4, 2, 7, 33), # dmq1 is not odd ], ) def test_invalid_private_numbers_argument_values( self, p, q, d, dmp1, dmq1, iqmp, e, n, backend ): # Start with p=3, q=11, private_exponent=3, public_exponent=7, # modulus=33, dmp1=1, dmq1=3, iqmp=2. Then change one value at # a time to test the bounds. with pytest.raises(ValueError): rsa.RSAPrivateNumbers( p=p, q=q, d=d, dmp1=dmp1, dmq1=dmq1, iqmp=iqmp, public_numbers=rsa.RSAPublicNumbers(e=e, n=n), ).private_key(backend) def test_public_number_repr(self): num = RSAPublicNumbers(1, 1) assert repr(num) == "" class TestRSANumbersEquality: def test_public_numbers_eq(self): num = RSAPublicNumbers(1, 2) num2 = RSAPublicNumbers(1, 2) assert num == num2 def test_public_numbers_ne(self): num = RSAPublicNumbers(1, 2) assert num != RSAPublicNumbers(2, 2) assert num != RSAPublicNumbers(1, 3) assert num != object() def test_private_numbers_eq(self): pub = RSAPublicNumbers(1, 2) num = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub) pub2 = RSAPublicNumbers(1, 2) num2 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub2) assert num == num2 def test_private_numbers_ne(self): pub = RSAPublicNumbers(1, 2) num = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub) assert num != RSAPrivateNumbers( 1, 2, 3, 4, 5, 7, RSAPublicNumbers(1, 2) ) assert num != RSAPrivateNumbers( 1, 2, 3, 4, 4, 6, RSAPublicNumbers(1, 2) ) assert num != RSAPrivateNumbers( 1, 2, 3, 5, 5, 6, RSAPublicNumbers(1, 2) ) assert num != RSAPrivateNumbers( 1, 2, 4, 4, 5, 6, RSAPublicNumbers(1, 2) ) assert num != RSAPrivateNumbers( 1, 3, 3, 4, 5, 6, RSAPublicNumbers(1, 2) ) assert num != RSAPrivateNumbers( 2, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2) ) assert num != RSAPrivateNumbers( 1, 2, 3, 4, 5, 6, RSAPublicNumbers(2, 2) ) assert num != RSAPrivateNumbers( 1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3) ) assert num != object() def test_public_numbers_hash(self): pub1 = RSAPublicNumbers(3, 17) pub2 = RSAPublicNumbers(3, 17) pub3 = RSAPublicNumbers(7, 21) assert hash(pub1) == hash(pub2) assert hash(pub1) != hash(pub3) def test_private_numbers_hash(self): priv1 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2)) priv2 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2)) priv3 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3)) assert hash(priv1) == hash(priv2) assert hash(priv1) != hash(priv3) class TestRSAPrimeFactorRecovery: def test_recover_prime_factors(self, subtests): for key in [ RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, ]: with subtests.test(): p, q = rsa.rsa_recover_prime_factors( key.public_numbers.n, key.public_numbers.e, key.d, ) # Unfortunately there is no convention on which prime should be # p and which one q. The function we use always makes p > q, # but the NIST vectors are not so consistent. Accordingly, we # verify we've recovered the proper (p, q) by sorting them and # asserting on that. assert sorted([p, q]) == sorted([key.p, key.q]) assert p > q def test_invalid_recover_prime_factors(self): with pytest.raises(ValueError): rsa.rsa_recover_prime_factors(34, 3, 7) class TestRSAPrivateKeySerialization: @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, rsa_key_2048: rsa.RSAPrivateKey, backend, fmt, password ): skip_fips_traditional_openssl(backend, fmt) key = rsa_key_2048 serialized = key.private_bytes( serialization.Encoding.PEM, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_pem_private_key( serialized, password, backend, unsafe_skip_rsa_key_validation=True ) assert isinstance(loaded_key, rsa.RSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.supported( only_if=lambda backend: backend._fips_enabled, skip_message="Requires FIPS", ) def test_traditional_serialization_fips( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048 with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.BestAvailableEncryption(b"password"), ) @pytest.mark.parametrize( ("encoding", "fmt"), [ (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), (serialization.Encoding.DER, serialization.PrivateFormat.Raw), (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ], ) def test_private_bytes_rejects_invalid( self, rsa_key_2048: rsa.RSAPrivateKey, encoding, fmt, backend ): key = rsa_key_2048 with pytest.raises(ValueError): key.private_bytes(encoding, fmt, serialization.NoEncryption()) @pytest.mark.parametrize( ("fmt", "password"), [ [serialization.PrivateFormat.PKCS8, b"s"], [serialization.PrivateFormat.PKCS8, b"longerpassword"], [serialization.PrivateFormat.PKCS8, b"!*$&(@#$*&($T@%_somesymbol"], [serialization.PrivateFormat.PKCS8, b"\x01" * 1000], ], ) def test_private_bytes_encrypted_der( self, rsa_key_2048: rsa.RSAPrivateKey, backend, fmt, password ): key = rsa_key_2048 serialized = key.private_bytes( serialization.Encoding.DER, fmt, serialization.BestAvailableEncryption(password), ) loaded_key = serialization.load_der_private_key( serialized, password, backend, unsafe_skip_rsa_key_validation=True ) assert isinstance(loaded_key, rsa.RSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.parametrize( ("encoding", "fmt", "loader_func"), [ [ serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.load_pem_private_key, ], [ serialization.Encoding.DER, serialization.PrivateFormat.TraditionalOpenSSL, serialization.load_der_private_key, ], [ serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.load_pem_private_key, ], [ serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.load_der_private_key, ], ], ) def test_private_bytes_unencrypted( self, rsa_key_2048: rsa.RSAPrivateKey, backend, encoding, fmt, loader_func, ): key = rsa_key_2048 serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) loaded_key = loader_func( serialized, None, backend, unsafe_skip_rsa_key_validation=True ) loaded_priv_num = loaded_key.private_numbers() priv_num = key.private_numbers() assert loaded_priv_num == priv_num @pytest.mark.skip_fips( reason="Traditional OpenSSL key format is not supported in FIPS mode." ) @pytest.mark.parametrize( ("key_path", "encoding", "loader_func"), [ [ os.path.join( "asymmetric", "Traditional_OpenSSL_Serialization", "testrsa.pem", ), serialization.Encoding.PEM, serialization.load_pem_private_key, ], [ os.path.join("asymmetric", "DER_Serialization", "testrsa.der"), serialization.Encoding.DER, serialization.load_der_private_key, ], ], ) def test_private_bytes_traditional_openssl_unencrypted( self, backend, key_path, encoding, loader_func ): key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func( key_bytes, None, backend, unsafe_skip_rsa_key_validation=True ) serialized = key.private_bytes( encoding, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption(), ) assert serialized == key_bytes def test_private_bytes_traditional_der_encrypted_invalid( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048 with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.DER, serialization.PrivateFormat.TraditionalOpenSSL, serialization.BestAvailableEncryption(b"password"), ) def test_private_bytes_invalid_encoding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048 with pytest.raises(TypeError): key.private_bytes( "notencoding", # type: ignore[arg-type] serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) def test_private_bytes_invalid_format( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048 with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, "invalidformat", # type: ignore[arg-type] serialization.NoEncryption(), ) def test_private_bytes_invalid_encryption_algorithm( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048 with pytest.raises(TypeError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, "notanencalg", # type: ignore[arg-type] ) def test_private_bytes_unsupported_encryption_type( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048 with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, DummyKeySerializationEncryption(), ) class TestRSAPEMPublicKeySerialization: @pytest.mark.parametrize( ("key_path", "loader_func", "encoding", "format"), [ ( os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), serialization.load_pem_public_key, serialization.Encoding.PEM, serialization.PublicFormat.PKCS1, ), ( os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.der"), serialization.load_der_public_key, serialization.Encoding.DER, serialization.PublicFormat.PKCS1, ), ( os.path.join("asymmetric", "PKCS8", "unenc-rsa-pkcs8.pub.pem"), serialization.load_pem_public_key, serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ), ( os.path.join( "asymmetric", "DER_Serialization", "unenc-rsa-pkcs8.pub.der", ), serialization.load_der_public_key, serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo, ), ], ) def test_public_bytes_match( self, key_path, loader_func, encoding, format, backend ): key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) key = loader_func(key_bytes, backend) serialized = key.public_bytes(encoding, format) assert serialized == key_bytes def test_public_bytes_openssh(self, backend): key_bytes = load_vectors_from_file( os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), lambda pemfile: pemfile.read(), mode="rb", ) key = serialization.load_pem_public_key(key_bytes, backend) ssh_bytes = key.public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH ) assert ssh_bytes == ( b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7JHoJfg6yNzLMOWet8Z49a4KD" b"0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkkFPZk/7x0" b"xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAvSKAFKEvy" b"D43si00DQnXWrYHAEQ==" ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.OpenSSH ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.DER, serialization.PublicFormat.OpenSSH ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.PKCS1, ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.SubjectPublicKeyInfo, ) def test_public_bytes_invalid_encoding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048.public_key() with pytest.raises(TypeError): key.public_bytes( "notencoding", # type: ignore[arg-type] serialization.PublicFormat.PKCS1, ) def test_public_bytes_invalid_format( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): key = rsa_key_2048.public_key() with pytest.raises(TypeError): key.public_bytes( serialization.Encoding.PEM, "invalidformat", # type: ignore[arg-type] ) @pytest.mark.parametrize( ("encoding", "fmt"), [ ( serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo, ), (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), *itertools.product( [ serialization.Encoding.Raw, serialization.Encoding.X962, serialization.Encoding.PEM, serialization.Encoding.DER, ], [ serialization.PublicFormat.Raw, serialization.PublicFormat.UncompressedPoint, serialization.PublicFormat.CompressedPoint, ], ), ], ) def test_public_bytes_rejects_invalid( self, rsa_key_2048: rsa.RSAPrivateKey, encoding, fmt, backend ): key = rsa_key_2048.public_key() with pytest.raises(ValueError): key.public_bytes(encoding, fmt) def test_public_key_equality(self, rsa_key_2048: rsa.RSAPrivateKey): key1 = rsa_key_2048.public_key() key2 = RSA_KEY_2048.private_key( unsafe_skip_rsa_key_validation=True ).public_key() key3 = RSA_KEY_2048_ALT.private_key( unsafe_skip_rsa_key_validation=True ).public_key() assert key1 == key2 assert key1 != key3 assert key1 != object() with pytest.raises(TypeError): key1 < key2 # type: ignore[operator] def test_public_key_copy(self, rsa_key_2048: rsa.RSAPrivateKey): key1 = rsa_key_2048.public_key() key2 = copy.copy(key1) assert key1 == key2 cryptography-43.0.0/tests/hazmat/primitives/test_scrypt.py010064400017510000177000000150501464676315000223100ustar 00000000000000# 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. import binascii import os import pytest from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives.kdf.scrypt import _MEM_LIMIT, Scrypt from tests.utils import ( load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) vectors = load_vectors_from_file( os.path.join("KDF", "scrypt.txt"), load_nist_vectors ) def _skip_if_memory_limited(memory_limit, params): # Memory calc adapted from OpenSSL (URL split over 2 lines, thanks PEP8) # https://github.com/openssl/openssl/blob/6286757141a8c6e14d647ec733634a # e0c83d9887/crypto/evp/scrypt.c#L189-L221 blen = int(params["p"]) * 128 * int(params["r"]) vlen = 32 * int(params["r"]) * (int(params["n"]) + 2) * 4 memory_required = blen + vlen if memory_limit < memory_required: pytest.skip( "Test exceeds Scrypt memory limit. " "This is likely a 32-bit platform." ) def test_memory_limit_skip(): with pytest.raises(pytest.skip.Exception): _skip_if_memory_limited(1000, {"p": 16, "r": 64, "n": 1024}) _skip_if_memory_limited(2**31, {"p": 16, "r": 64, "n": 1024}) @pytest.mark.supported( only_if=lambda backend: not backend.scrypt_supported(), skip_message="Supports scrypt so can't test unsupported path", ) def test_unsupported_backend(backend): # This test is currently exercised by LibreSSL, which does # not support scrypt with raises_unsupported_algorithm(None): Scrypt(b"NaCl", 64, 1024, 8, 16) @pytest.mark.supported( only_if=lambda backend: backend.scrypt_supported(), skip_message="Does not support Scrypt", ) class TestScrypt: @pytest.mark.parametrize("params", vectors) def test_derive(self, backend, params): _skip_if_memory_limited(_MEM_LIMIT, params) password = params["password"] work_factor = int(params["n"]) block_size = int(params["r"]) parallelization_factor = int(params["p"]) length = int(params["length"]) salt = params["salt"] derived_key = params["derived_key"] scrypt = Scrypt( salt, length, work_factor, block_size, parallelization_factor, backend, ) assert binascii.hexlify(scrypt.derive(password)) == derived_key def test_salt_not_bytes(self, backend): work_factor = 1024 block_size = 8 parallelization_factor = 16 length = 64 salt = 1 with pytest.raises(TypeError): Scrypt( salt, # type: ignore[arg-type] length, work_factor, block_size, parallelization_factor, backend, ) def test_scrypt_malloc_failure(self, backend): password = b"NaCl" work_factor = 1024**3 block_size = 589824 parallelization_factor = 16 length = 64 salt = b"NaCl" scrypt = Scrypt( salt, length, work_factor, block_size, parallelization_factor, backend, ) with pytest.raises(MemoryError): scrypt.derive(password) def test_password_not_bytes(self, backend): password = 1 work_factor = 1024 block_size = 8 parallelization_factor = 16 length = 64 salt = b"NaCl" scrypt = Scrypt( salt, length, work_factor, block_size, parallelization_factor, backend, ) with pytest.raises(TypeError): scrypt.derive(password) # type: ignore[arg-type] def test_buffer_protocol(self, backend): password = bytearray(b"password") work_factor = 256 block_size = 8 parallelization_factor = 16 length = 10 salt = b"NaCl" scrypt = Scrypt( salt, length, work_factor, block_size, parallelization_factor, backend, ) assert scrypt.derive(password) == b"\xf4\x92\x86\xb2\x06\x0c\x848W\x87" @pytest.mark.parametrize("params", vectors) def test_verify(self, backend, params): _skip_if_memory_limited(_MEM_LIMIT, params) password = params["password"] work_factor = int(params["n"]) block_size = int(params["r"]) parallelization_factor = int(params["p"]) length = int(params["length"]) salt = params["salt"] derived_key = params["derived_key"] scrypt = Scrypt( salt, length, work_factor, block_size, parallelization_factor, backend, ) scrypt.verify(password, binascii.unhexlify(derived_key)) def test_invalid_verify(self, backend): password = b"password" work_factor = 1024 block_size = 8 parallelization_factor = 16 length = 64 salt = b"NaCl" derived_key = b"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773" scrypt = Scrypt( salt, length, work_factor, block_size, parallelization_factor, backend, ) with pytest.raises(InvalidKey): scrypt.verify(password, binascii.unhexlify(derived_key)) def test_already_finalized(self, backend): password = b"password" work_factor = 1024 block_size = 8 parallelization_factor = 16 length = 64 salt = b"NaCl" scrypt = Scrypt( salt, length, work_factor, block_size, parallelization_factor, backend, ) scrypt.derive(password) with pytest.raises(AlreadyFinalized): scrypt.derive(password) def test_invalid_n(self, backend): # n is less than 2 with pytest.raises(ValueError): Scrypt(b"NaCl", 64, 1, 8, 16, backend) # n is not a power of 2 with pytest.raises(ValueError): Scrypt(b"NaCl", 64, 3, 8, 16, backend) def test_invalid_r(self, backend): with pytest.raises(ValueError): Scrypt(b"NaCl", 64, 2, 0, 16, backend) def test_invalid_p(self, backend): with pytest.raises(ValueError): Scrypt(b"NaCl", 64, 2, 8, 0, backend) cryptography-43.0.0/tests/hazmat/primitives/test_serialization.py010064400017510000177000001544621464676315000236540ustar 00000000000000# 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. import base64 import itertools import os import textwrap import pytest from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed448, ed25519, rsa, x448, x25519, ) from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.hazmat.primitives.serialization import ( BestAvailableEncryption, Encoding, NoEncryption, PrivateFormat, PublicFormat, load_der_parameters, load_der_private_key, load_der_public_key, load_pem_parameters, load_pem_private_key, load_pem_public_key, ) from cryptography.hazmat.primitives.serialization.pkcs12 import PBES from ...utils import load_vectors_from_file from .test_ec import _skip_curve_unsupported from .test_rsa import rsa_key_2048 from .utils import _check_dsa_private_numbers, _check_rsa_private_numbers # Make ruff happy since we're importing fixtures that pytest patches in as # func args __all__ = ["rsa_key_2048"] def _skip_fips_format(key_path, password, backend): if backend._fips_enabled: if key_path[0] == "Traditional_OpenSSL_Serialization": pytest.skip("Traditional OpenSSL format blocked in FIPS mode") if ( key_path[0] in ("PEM_Serialization", "PKCS8") and password is not None ): pytest.skip( "The encrypted PEM vectors currently have encryption " "that is not FIPS approved in the 3.0 provider" ) if key_path[0] == "DER_Serialization" and password is not None: pytest.skip( "The encrypted PKCS8 DER vectors currently have encryption " "that is not FIPS approved in the 3.0 provider" ) class TestBufferProtocolSerialization: @pytest.mark.parametrize( ("key_path", "password"), [ (["DER_Serialization", "enc-rsa-pkcs8.der"], bytearray(b"foobar")), (["DER_Serialization", "enc2-rsa-pkcs8.der"], bytearray(b"baz")), (["DER_Serialization", "unenc-rsa-pkcs8.der"], None), (["DER_Serialization", "testrsa.der"], None), ], ) def test_load_der_rsa_private_key(self, key_path, password, backend): _skip_fips_format(key_path, password, backend) data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: derfile.read(), mode="rb", ) key = load_der_private_key( bytearray(data), password, unsafe_skip_rsa_key_validation=True ) assert key assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( ("key_path", "password"), [ ( ["PEM_Serialization", "rsa_private_key.pem"], bytearray(b"123456"), ), (["PKCS8", "unenc-rsa-pkcs8.pem"], None), (["PKCS8", "enc-rsa-pkcs8.pem"], bytearray(b"foobar")), (["PKCS8", "enc2-rsa-pkcs8.pem"], bytearray(b"baz")), ( ["Traditional_OpenSSL_Serialization", "key1.pem"], bytearray(b"123456"), ), ], ) def test_load_pem_rsa_private_key(self, key_path, password, backend): _skip_fips_format(key_path, password, backend) data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: pemfile.read(), mode="rb", ) key = load_pem_private_key( bytearray(data), password, unsafe_skip_rsa_key_validation=True ) assert key assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) class TestDERSerialization: @pytest.mark.parametrize( ("key_path", "password"), [ (["DER_Serialization", "enc-rsa-pkcs8.der"], b"foobar"), (["DER_Serialization", "enc2-rsa-pkcs8.der"], b"baz"), (["DER_Serialization", "unenc-rsa-pkcs8.der"], None), (["DER_Serialization", "testrsa.der"], None), ], ) def test_load_der_rsa_private_key(self, key_path, password, backend): _skip_fips_format(key_path, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: load_der_private_key( derfile.read(), password, unsafe_skip_rsa_key_validation=True ), mode="rb", ) assert key assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.parametrize( ("key_path", "password"), [ (["DER_Serialization", "unenc-dsa-pkcs8.der"], None), (["DER_Serialization", "dsa.1024.der"], None), (["DER_Serialization", "dsa.2048.der"], None), (["DER_Serialization", "dsa.3072.der"], None), ], ) def test_load_der_dsa_private_key(self, key_path, password, backend): key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: load_der_private_key( derfile.read(), password, backend ), mode="rb", ) assert key assert isinstance(key, dsa.DSAPrivateKey) _check_dsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( "key_path", [["DER_Serialization", "enc-rsa-pkcs8.der"]] ) def test_password_not_bytes(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = "this password is not bytes" with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda derfile: load_der_private_key( derfile.read(), password, # type:ignore[arg-type] backend, ), mode="rb", ) @pytest.mark.parametrize( ("key_path", "password"), [ (["DER_Serialization", "ec_private_key.der"], None), (["DER_Serialization", "ec_private_key_encrypted.der"], b"123456"), ], ) def test_load_der_ec_private_key(self, key_path, password, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: load_der_private_key( derfile.read(), password, backend ), mode="rb", ) assert key assert isinstance(key, ec.EllipticCurvePrivateKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @pytest.mark.parametrize( "key_path", [["DER_Serialization", "enc-rsa-pkcs8.der"]] ) def test_wrong_password(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = b"this password is wrong" with pytest.raises(ValueError): load_vectors_from_file( key_file, lambda derfile: load_der_private_key( derfile.read(), password, backend ), mode="rb", ) @pytest.mark.parametrize( "key_path", [["DER_Serialization", "unenc-rsa-pkcs8.der"]] ) def test_unused_password(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = b"this password will not be used" with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda derfile: load_der_private_key( derfile.read(), password, backend ), mode="rb", ) @pytest.mark.parametrize( ("key_path", "password"), itertools.product( [["DER_Serialization", "enc-rsa-pkcs8.der"]], [b"", None] ), ) def test_missing_password(self, key_path, password, backend): key_file = os.path.join("asymmetric", *key_path) with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda derfile: load_der_private_key( derfile.read(), password, backend ), mode="rb", ) def test_wrong_format(self, backend): key_data = b"---- NOT A KEY ----\n" with pytest.raises(ValueError): load_der_private_key(key_data, None, backend) with pytest.raises(ValueError): load_der_private_key( key_data, b"this password will not be used", backend ) def test_invalid_rsa_even_q(self, backend): data = load_vectors_from_file( os.path.join( "asymmetric", "PEM_Serialization", "rsa-bad-1025-q-is-2.pem" ), lambda pemfile: pemfile.read(), mode="rb", ) with pytest.raises(ValueError): load_pem_private_key(data, None) def test_corrupt_der_pkcs8(self, backend): # unenc-rsa-pkcs8 with a bunch of data missing. key_data = textwrap.dedent( """\ MIICdQIBADALBgkqhkiG9w0BAQEEggJhMIICXQIBAAKBgQC7JHoJfg6yNzLMOWet 8Z49a4KD0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkk FPZk/7x0xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAv FiRC0Cgz+frQPFQEBsAV9RuasyQxqzxrR0Ow0qncBeGBWbYE6WZhqtcLAI895b+i +F4lbB4iD7T9QeIDMU/aIMXA81UO4cns1z4qDAHKeyLLrPQrJ/B4X7XC+egUWm5+ hr1qmyAMusyXIBECQQDJWZ8piluf4yrYfsJAn6hF5T4RjTztbqvO0GVG2McHY7Uj NPSffhzHx/ll0fQEQji+OgydCCX8o3HZrgw5YfSJAkEA7e+rqdU5nO5ZG//PSEQb tjLnRiTzBH/elQhtdZ5nF7pcpNTi4k13zutmKcWW4GK75azcRGJUhu1kDM7QYAOd SQJAVNkYcifkvna7GmooL5VYEsQsqLbM4v0NF2TIGNfG3z1MGp75KrC5LhL97MNR we2p/bd2k0HYyCKUGnf2nMPDiQJBAI75pwittSoE240EobUGIDTSz8CJsXIxuDmL z+KOpdpPRR5TQmbEMEspjsFpFymMiuYPgmihQbO2cJl1qScY5OkCQQCJ6m5tcN8l Xxg/SNpjEIv+qAyUD96XVlOJlOIeLHQ8kYE0C6ZA+MsqYIzgAreJk88Yn0lU/X0/ mu/UpE/BRZmR """ ).encode() bad_der = base64.b64decode(b"".join(key_data.splitlines())) with pytest.raises(ValueError): load_der_private_key(bad_der, None, backend) with pytest.raises(ValueError): load_der_private_key( bad_der, b"this password will not be used", backend ) def test_corrupt_traditional_format_der(self, backend): # privkey with a bunch of data missing. key_data = textwrap.dedent( """\ MIIBPAIBAAJBAKrbeqkuRk8VcRmWFmtP+LviMB3+6dizWW3DwaffznyHGAFwUJ/I Tv0XtbsCyl3QoyKGhrOAy3RvPK5M38iuXT0CAwEAAQJAZ3cnzaHXM/bxGaR5CR1R rD1qFBAVfoQFiOH9uPJgMaoAuoQEisPHVcZDKcOv4wEg6/TInAIXBnEigtqvRzuy mvcpHZwQJdmdHHkGKAs37Dfxi67HbkUCIQCeZGliHXFa071Fp06ZeWlR2ADonTZz rJBhdTe0v5pCeQIhAIZfkiGgGBX4cIuuckzEm43g9WMUjxP/0GlK39vIyihxAiEA mymehFRT0MvqW5xAKAx7Pgkt8HVKwVhc2LwGKHE0DZM= """ ).encode() bad_der = base64.b64decode(b"".join(key_data.splitlines())) with pytest.raises(ValueError): load_pem_private_key(bad_der, None, backend) with pytest.raises(ValueError): load_pem_private_key( bad_der, b"this password will not be used", backend ) @pytest.mark.parametrize( "key_file", [ os.path.join( "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"), ], ) def test_load_der_rsa_public_key(self, key_file, backend): key = load_vectors_from_file( key_file, lambda derfile: load_der_public_key(derfile.read(), backend), mode="rb", ) assert key assert isinstance(key, rsa.RSAPublicKey) numbers = key.public_numbers() assert numbers.e == 65537 def test_load_der_invalid_public_key(self, backend): with pytest.raises(ValueError): load_der_public_key(b"invalid data", backend) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.parametrize( "key_file", [ os.path.join( "asymmetric", "DER_Serialization", "unenc-dsa-pkcs8.pub.der" ), os.path.join( "asymmetric", "DER_Serialization", "dsa_public_key.der" ), ], ) def test_load_der_dsa_public_key(self, key_file, backend): key = load_vectors_from_file( key_file, lambda derfile: load_der_public_key(derfile.read(), backend), mode="rb", ) assert key assert isinstance(key, dsa.DSAPublicKey) def test_load_ec_public_key(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( os.path.join( "asymmetric", "DER_Serialization", "ec_public_key.der" ), lambda derfile: load_der_public_key(derfile.read(), backend), mode="rb", ) assert key assert isinstance(key, ec.EllipticCurvePublicKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @pytest.mark.supported( only_if=lambda backend: backend.dh_supported(), skip_message="DH not supported", ) def test_wrong_parameters_format(self, backend): param_data = b"---- NOT A KEY ----\n" with pytest.raises(ValueError): load_der_parameters(param_data, backend) class TestPEMSerialization: @pytest.mark.parametrize( ("key_file", "password"), [ (["PEM_Serialization", "rsa_private_key.pem"], b"123456"), (["PKCS8", "unenc-rsa-pkcs8.pem"], None), (["PKCS8", "enc-rsa-pkcs8.pem"], b"foobar"), (["PKCS8", "enc2-rsa-pkcs8.pem"], b"baz"), (["PKCS8", "pkcs12_s2k_pem-X_9607.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9671.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9925.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9926.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9927.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9928.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9929.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9930.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9931.pem"], b"123456"), (["PKCS8", "pkcs12_s2k_pem-X_9932.pem"], b"123456"), (["Traditional_OpenSSL_Serialization", "key1.pem"], b"123456"), (["Traditional_OpenSSL_Serialization", "key2.pem"], b"a123456"), (["Traditional_OpenSSL_Serialization", "testrsa.pem"], None), ( ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], b"password", ), ], ) def test_load_pem_rsa_private_key(self, key_file, password, backend): _skip_fips_format(key_file, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_file), lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, unsafe_skip_rsa_key_validation=True, ), ) assert key assert isinstance(key, rsa.RSAPrivateKey) _check_rsa_private_numbers(key.private_numbers()) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.parametrize( ("key_path", "password"), [ (["Traditional_OpenSSL_Serialization", "dsa.1024.pem"], None), (["Traditional_OpenSSL_Serialization", "dsa.2048.pem"], None), (["Traditional_OpenSSL_Serialization", "dsa.3072.pem"], None), (["PKCS8", "unenc-dsa-pkcs8.pem"], None), (["PEM_Serialization", "dsa_private_key.pem"], b"123456"), ], ) def test_load_dsa_private_key(self, key_path, password, backend): _skip_fips_format(key_path, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, backend ), ) assert key assert isinstance(key, dsa.DSAPrivateKey) _check_dsa_private_numbers(key.private_numbers()) @pytest.mark.parametrize( ("key_path", "password"), [ (["PKCS8", "ec_private_key.pem"], None), (["PKCS8", "ec_private_key_encrypted.pem"], b"123456"), (["PEM_Serialization", "ec_private_key.pem"], None), (["PEM_Serialization", "ec_private_key_encrypted.pem"], b"123456"), ], ) def test_load_pem_ec_private_key(self, key_path, password, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) _skip_fips_format(key_path, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, backend ), ) assert key assert isinstance(key, ec.EllipticCurvePrivateKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @pytest.mark.parametrize( ("key_file"), [ 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"), os.path.join( "asymmetric", "PEM_Serialization", "rsa_wrong_delimiter_public_key.pem", ), ], ) def test_load_pem_rsa_public_key(self, key_file, backend): key = load_vectors_from_file( key_file, lambda pemfile: load_pem_public_key( pemfile.read().encode(), backend ), ) assert key assert isinstance(key, rsa.RSAPublicKey) numbers = key.public_numbers() assert numbers.e == 65537 def test_load_pem_public_fails_with_ec_key_with_rsa_delimiter(self): with pytest.raises(ValueError): load_vectors_from_file( os.path.join( "asymmetric", "PEM_Serialization", "ec_public_key_rsa_delimiter.pem", ), lambda pemfile: load_pem_public_key(pemfile.read().encode()), ) def test_load_priv_key_with_public_key_api_fails( self, rsa_key_2048, backend ): # In OpenSSL 3.0.x the PEM_read_bio_PUBKEY function will invoke # the default password callback if you pass an encrypted private # key. This is very, very, very bad as the default callback can # trigger an interactive console prompt, which will hang the # Python process. This test makes sure we don't do that. priv_key_serialized = rsa_key_2048.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, BestAvailableEncryption(b"password"), ) with pytest.raises(ValueError): load_pem_public_key(priv_key_serialized) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.parametrize( ("key_file"), [ os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), os.path.join( "asymmetric", "PEM_Serialization", "dsa_public_key.pem" ), ], ) def test_load_pem_dsa_public_key(self, key_file, backend): key = load_vectors_from_file( key_file, lambda pemfile: load_pem_public_key( pemfile.read().encode(), backend ), ) assert key assert isinstance(key, dsa.DSAPublicKey) def test_load_ec_public_key(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: load_pem_public_key( pemfile.read().encode(), backend ), ) assert key assert isinstance(key, ec.EllipticCurvePublicKey) assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 @pytest.mark.skip_fips( reason="Traditional OpenSSL format blocked in FIPS mode" ) def test_rsa_traditional_encrypted_values(self, backend): pkey = load_vectors_from_file( os.path.join( "asymmetric", "Traditional_OpenSSL_Serialization", "key1.pem" ), lambda pemfile: load_pem_private_key( pemfile.read().encode(), b"123456", unsafe_skip_rsa_key_validation=True, ), ) assert isinstance(pkey, rsa.RSAPrivateKey) numbers = pkey.private_numbers() assert numbers.p == int( "fb7d316fc51531b36d93adaefaf52db6ad5beb793d37c4cf9dfc1ddd17cfbafb", 16, ) assert numbers.q == int( "df98264e646de9a0fbeab094e31caad5bc7adceaaae3c800ca0275dd4bb307f5", 16, ) assert numbers.d == int( "db4848c36f478dd5d38f35ae519643b6b810d404bcb76c00e44015e56ca1cab0" "7bb7ae91f6b4b43fcfc82a47d7ed55b8c575152116994c2ce5325ec24313b911", 16, ) assert numbers.dmp1 == int( "ce997f967192c2bcc3853186f1559fd355c190c58ddc15cbf5de9b6df954c727", 16, ) assert numbers.dmq1 == int( "b018a57ab20ffaa3862435445d863369b852cf70a67c55058213e3fe10e3848d", 16, ) assert numbers.iqmp == int( "6a8d830616924f5cf2d1bc1973f97fde6b63e052222ac7be06aa2532d10bac76", 16, ) assert numbers.public_numbers.e == 65537 assert numbers.public_numbers.n == int( "dba786074f2f0350ce1d99f5aed5b520cfe0deb5429ec8f2a88563763f566e77" "9814b7c310e5326edae31198eed439b845dd2db99eaa60f5c16a43f4be6bcf37", 16, ) @pytest.mark.parametrize( "key_path", [ ["Traditional_OpenSSL_Serialization", "testrsa.pem"], ["PKCS8", "unenc-rsa-pkcs8.pem"], ], ) def test_unused_password(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = b"this password will not be used" with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, backend ), ) def test_invalid_encoding_with_traditional(self, backend): key_file = os.path.join( "asymmetric", "Traditional_OpenSSL_Serialization", "testrsa.pem" ) key = load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) for enc in (Encoding.OpenSSH, Encoding.Raw, Encoding.X962): with pytest.raises(ValueError): key.private_bytes( enc, PrivateFormat.TraditionalOpenSSL, NoEncryption() ) @pytest.mark.parametrize( "key_path", [ ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], ["PKCS8", "enc-rsa-pkcs8.pem"], ], ) def test_password_not_bytes(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = "this password is not bytes" with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, # type:ignore[arg-type] backend, ), ) @pytest.mark.parametrize( "key_path", [ ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], ["PKCS8", "enc-rsa-pkcs8.pem"], ], ) def test_wrong_password(self, key_path, backend): key_file = os.path.join("asymmetric", *key_path) password = b"this password is wrong" with pytest.raises(ValueError): load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, backend ), ) @pytest.mark.parametrize( ("key_path", "password"), itertools.product( [ ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], ["PKCS8", "enc-rsa-pkcs8.pem"], ], [b"", None], ), ) def test_missing_password(self, key_path, password, backend): key_file = os.path.join("asymmetric", *key_path) with pytest.raises(TypeError): load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, backend ), ) def test_wrong_private_format(self, backend): key_data = b"---- NOT A KEY ----\n" with pytest.raises(ValueError): load_pem_private_key(key_data, None, backend) with pytest.raises(ValueError): load_pem_private_key( 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) @pytest.mark.supported( only_if=lambda backend: backend.dh_supported(), skip_message="DH not supported", ) def test_wrong_parameters_format(self, backend): param_data = b"---- NOT A KEY ----\n" with pytest.raises(ValueError): load_pem_parameters(param_data, backend) def test_corrupt_traditional_format(self, backend): # privkey.pem with a bunch of data missing. key_data = textwrap.dedent( """\ -----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAKrbeqkuRk8VcRmWFmtP+LviMB3+6dizWW3DwaffznyHGAFwUJ/I Tv0XtbsCyl3QoyKGhrOAy3RvPK5M38iuXT0CAwEAAQJAZ3cnzaHXM/bxGaR5CR1R rD1qFBAVfoQFiOH9uPJgMaoAuoQEisPHVcZDKcOv4wEg6/TInAIXBnEigtqvRzuy mvcpHZwQJdmdHHkGKAs37Dfxi67HbkUCIQCeZGliHXFa071Fp06ZeWlR2ADonTZz rJBhdTe0v5pCeQIhAIZfkiGgGBX4cIuuckzEm43g9WMUjxP/0GlK39vIyihxAiEA mymehFRT0MvqW5xAKAx7Pgkt8HVKwVhc2LwGKHE0DZM= -----END RSA PRIVATE KEY----- """ ).encode() with pytest.raises(ValueError): load_pem_private_key(key_data, None, backend) with pytest.raises(ValueError): load_pem_private_key( key_data, b"this password will not be used", backend ) def test_traditional_encrypted_corrupt_format(self, backend): # privkey.pem with a single bit flipped key_data = textwrap.dedent( """\ -----BEGIN RSA PRIVATE KEY----- Proc-Type: <,ENCRYPTED DEK-Info: AES-128-CBC,5E22A2BD85A653FB7A3ED20DE84F54CD hAqtb5ZkTMGcs4BBDQ1SKZzdQThWRDzEDxM3qBfjvYa35KxZ54aic013mW/lwj2I v5bbpOjrHYHNAiZYZ7RNb+ztbF6F/g5PA5g7mFwEq+LFBY0InIplYBSv9QtE+lot Dy4AlZa/+NzJwgdKDb+JVfk5SddyD4ywnyeORnMPy4xXKvjXwmW+iLibZVKsjIgw H8hSxcD+FhWyJm9h9uLtmpuqhQo0jTUYpnTezZx2xeVPB53Ev7YCxR9Nsgj5GsVf 9Z/hqLB7IFgM3pa0z3PQeUIZF/cEf72fISWIOBwwkzVrPUkXWfbuWeJXQXSs3amE 5A295jD9BQp9CY0nNFSsy+qiXWToq2xT3y5zVNEStmN0SCGNaIlUnJzL9IHW+oMI kPmXZMnAYBWeeCF1gf3J3aE5lZInegHNfEI0+J0LazC2aNU5Dg/BNqrmRqKWEIo/ -----END RSA PRIVATE KEY----- """ ).encode() password = b"this password is wrong" with pytest.raises(ValueError): load_pem_private_key(key_data, None, backend) with pytest.raises(ValueError): load_pem_private_key(key_data, password, backend) def test_unsupported_key_encryption(self, backend): key_data = textwrap.dedent( """\ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: FAKE-123,5E22A2BD85A653FB7A3ED20DE84F54CD hAqtb5ZkTMGcs4BBDQ1SKZzdQThWRDzEDxM3qBfjvYa35KxZ54aic013mW/lwj2I v5bbpOjrHYHNAiZYZ7RNb+ztbF6F/g5PA5g7mFwEq+LFBY0InIplYBSv9QtE+lot Dy4AlZa/+NzJwgdKDb+JVfk5SddyD4ywnyeORnMPy4xXKvjXwmW+iLibZVKsjIgw H8hSxcD+FhWyJm9h9uLtmpuqhQo0jTUYpnTezZx2xeVPB53Ev7YCxR9Nsgj5GsVf 9Z/hqLB7IFgM3pa0z3PQeUIZF/cEf72fISWIOBwwkzVrPUkXWfbuWeJXQXSs3amE 5A295jD9BQp9CY0nNFSsy+qiXWToq2xT3y5zVNEStmN0SCGNaIlUnJzL9IHW+oMI kPmXZMnAYBWeeCF1gf3J3aE5lZInegHNfEI0+J0LazC2aNU5Dg/BNqrmRqKWEIo/ -----END RSA PRIVATE KEY----- """ ).encode() password = b"password" with pytest.raises(ValueError): load_pem_private_key(key_data, password, backend) def test_corrupt_pkcs8_format(self, backend): # unenc-rsa-pkcs8.pem with a bunch of data missing. key_data = textwrap.dedent( """\ -----BEGIN PRIVATE KEY----- MIICdQIBADALBgkqhkiG9w0BAQEEggJhMIICXQIBAAKBgQC7JHoJfg6yNzLMOWet 8Z49a4KD0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkk FPZk/7x0xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAv FiRC0Cgz+frQPFQEBsAV9RuasyQxqzxrR0Ow0qncBeGBWbYE6WZhqtcLAI895b+i +F4lbB4iD7T9QeIDMU/aIMXA81UO4cns1z4qDAHKeyLLrPQrJ/B4X7XC+egUWm5+ hr1qmyAMusyXIBECQQDJWZ8piluf4yrYfsJAn6hF5T4RjTztbqvO0GVG2McHY7Uj NPSffhzHx/ll0fQEQji+OgydCCX8o3HZrgw5YfSJAkEA7e+rqdU5nO5ZG//PSEQb tjLnRiTzBH/elQhtdZ5nF7pcpNTi4k13zutmKcWW4GK75azcRGJUhu1kDM7QYAOd SQJAVNkYcifkvna7GmooL5VYEsQsqLbM4v0NF2TIGNfG3z1MGp75KrC5LhL97MNR we2p/bd2k0HYyCKUGnf2nMPDiQJBAI75pwittSoE240EobUGIDTSz8CJsXIxuDmL z+KOpdpPRR5TQmbEMEspjsFpFymMiuYPgmihQbO2cJl1qScY5OkCQQCJ6m5tcN8l Xxg/SNpjEIv+qAyUD96XVlOJlOIeLHQ8kYE0C6ZA+MsqYIzgAreJk88Yn0lU/X0/ mu/UpE/BRZmR -----END PRIVATE KEY----- """ ).encode() with pytest.raises(ValueError): load_pem_private_key(key_data, None, backend) with pytest.raises(ValueError): load_pem_private_key( key_data, b"this password will not be used", backend ) def test_pks8_encrypted_corrupt_format(self, backend): # enc-rsa-pkcs8.pem with some bits flipped. key_data = textwrap.dedent( """\ -----BEGIN ENCRYPTED PRIVATE KEY----- MIICojAcBgoqhkiG9w0BDAEDMA4ECHK0M0+QuEL9AgIBIcSCAoDRq+KRY+0XP0tO lwBTzViiXSXoyNnKAZKt5r5K/fGNntv22g/1s/ZNCetrqsJDC5eMUPPacz06jFq/ Ipsep4/OgjQ9UAOzXNrWEoNyrHnWDo7usgD3CW0mKyqER4+wG0adVMbt3N+CJHGB 85jzRmQTfkdx1rSWeSx+XyswHn8ER4+hQ+omKWMVm7AFkjjmP/KnhUnLT98J8rhU ArQoFPHz/6HVkypFccNaPPNg6IA4aS2A+TU9vJYOaXSVfFB2yf99hfYYzC+ukmuU 5Lun0cysK5s/5uSwDueUmDQKspnaNyiaMGDxvw8hilJc7vg0fGObfnbIpizhxJwq gKBfR7Zt0Hv8OYi1He4MehfMGdbHskztF+yQ40LplBGXQrvAqpU4zShga1BoQ98T 0ekbBmqj7hg47VFsppXR7DKhx7G7rpMmdKbFhAZVCjae7rRGpUtD52cpFdPhMyAX huhMkoczwUW8B/rM4272lkHo6Br0yk/TQfTEGkvryflNVu6lniPTV151WV5U1M3o 3G3a44eDyt7Ln+WSOpWtbPQMTrpKhur6WXgJvrpa/m02oOGdvOlDsoOCgavgQMWg 7xKKL7620pHl7p7f/8tlE8q6vLXVvyNtAOgt/JAr2rgvrHaZSzDE0DwgCjBXEm+7 cVMVNkHod7bLQefVanVtWqPzbmr8f7gKeuGwWSG9oew/lN2hxcLEPJHAQlnLgx3P 0GdGjK9NvwA0EP2gYIeE4+UtSder7xQ7bVh25VB20R4TTIIs4aXXCVOoQPagnzaT 6JLgl8FrvdfjHwIvmSOO1YMNmILBq000Q8WDqyErBDs4hsvtO6VQ4LeqJj6gClX3 qeJNaJFu -----END ENCRYPTED PRIVATE KEY----- """ ).encode() password = b"this password is wrong" with pytest.raises(ValueError): load_pem_private_key(key_data, None, backend) with pytest.raises(ValueError): load_pem_private_key(key_data, password, backend) @pytest.mark.skip_fips(reason="non-FIPS parameters") def test_rsa_pkcs8_encrypted_values(self, backend): pkey = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "enc-rsa-pkcs8.pem"), lambda pemfile: load_pem_private_key( pemfile.read().encode(), b"foobar", unsafe_skip_rsa_key_validation=True, ), ) assert isinstance(pkey, rsa.RSAPrivateKey) numbers = pkey.private_numbers() assert numbers.public_numbers.n == int( "00beec64d6db5760ac2fd4c971145641b9bd7f5c56558ece608795c79807" "376a7fe5b19f95b35ca358ea5c8abd7ae051d49cd2f1e45969a1ae945460" "3c14b278664a0e414ebc8913acb6203626985525e17a600611b028542dd0" "562aad787fb4f1650aa318cdcff751e1b187cbf6785fbe164e9809491b95" "dd68480567c99b1a57", 16, ) assert numbers.public_numbers.e == 65537 assert numbers.d == int( "0cfe316e9dc6b8817f4fcfd5ae38a0886f68f773b8a6db4c9e6d8703c599" "f3d9785c3a2c09e4c8090909fb3721e19a3009ec21221523a729265707a5" "8f13063671c42a4096cad378ef2510cb59e23071489d8893ac4934dd149f" "34f2d094bea57f1c8027c3a77248ac9b91218737d0c3c3dfa7d7829e6977" "cf7d995688c86c81", 16, ) assert numbers.p == int( "00db122ac857b2c0437d7616daa98e597bb75ca9ad3a47a70bec10c10036" "03328794b225c8e3eee6ffd3fd6d2253d28e071fe27d629ab072faa14377" "ce6118cb67", 16, ) assert numbers.q == int( "00df1b8aa8506fcbbbb9d00257f2975e38b33d2698fd0f37e82d7ef38c56" "f21b6ced63c825383782a7115cfcc093300987dbd2853b518d1c8f26382a" "2d2586d391", 16, ) assert numbers.dmp1 == int( "00be18aca13e60712fdf5daa85421eb10d86d654b269e1255656194fb0c4" "2dd01a1070ea12c19f5c39e09587af02f7b1a1030d016a9ffabf3b36d699" "ceaf38d9bf", 16, ) assert numbers.dmq1 == int( "71aa8978f90a0c050744b77cf1263725b203ac9f730606d8ae1d289dce4a" "28b8d534e9ea347aeb808c73107e583eb80c546d2bddadcdb3c82693a4c1" "3d863451", 16, ) assert numbers.iqmp == int( "136b7b1afac6e6279f71b24217b7083485a5e827d156024609dae39d48a6" "bdb55af2f062cc4a3b077434e6fffad5faa29a2b5dba2bed3e4621e478c0" "97ccfe7f", 16, ) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) def test_load_pem_dsa_private_key(self, backend): key = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), lambda pemfile: load_pem_private_key( pemfile.read().encode(), None, backend ), ) assert key assert isinstance(key, dsa.DSAPrivateKey) params = key.parameters() assert isinstance(params, dsa.DSAParameters) num = key.private_numbers() pub = num.public_numbers parameter_numbers = pub.parameter_numbers assert num.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", 16) assert pub.y == int( "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" "ab6bcce04bfdf5b6", 16, ) assert parameter_numbers.p == int( "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" "940cabe5d2de49a167", 16, ) assert parameter_numbers.q == int( "00adc0e869b36f0ac013a681fdf4d4899d69820451", 16 ) assert parameter_numbers.g == int( "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" "68ce5cfff241cd3246", 16, ) @pytest.mark.parametrize( ("key_file", "password"), [("bad-oid-dsa-key.pem", None)] ) def test_load_bad_oid_key(self, key_file, password, backend): with pytest.raises(ValueError): load_vectors_from_file( os.path.join("asymmetric", "PKCS8", key_file), lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, backend ), ) @pytest.mark.parametrize( ("key_file", "password"), [("bad-encryption-oid.pem", b"password")] ) def test_load_bad_encryption_oid_key(self, key_file, password, backend): with pytest.raises(ValueError): load_vectors_from_file( os.path.join("asymmetric", "PKCS8", key_file), lambda pemfile: load_pem_private_key( pemfile.read().encode(), password, backend ), ) class TestKeySerializationEncryptionTypes: def test_non_bytes_password(self): with pytest.raises(ValueError): BestAvailableEncryption(object()) # type:ignore[arg-type] def test_encryption_with_zero_length_password(self): with pytest.raises(ValueError): BestAvailableEncryption(b"") @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) class TestEd25519Serialization: def test_load_der_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8-enc.der"), lambda derfile: derfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key = load_der_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.DER, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) def test_load_pem_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8-enc.pem"), lambda pemfile: pemfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.pem"), lambda pemfile: pemfile.read(), mode="rb", ) key = load_pem_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) @pytest.mark.parametrize( ("key_path", "encoding", "loader"), [ ( ["Ed25519", "ed25519-pub.pem"], Encoding.PEM, load_pem_public_key, ), ( ["Ed25519", "ed25519-pub.der"], Encoding.DER, load_der_public_key, ), ], ) def test_load_public_key(self, key_path, encoding, loader, backend): data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: pemfile.read(), mode="rb", ) public_key = loader(data, backend) assert ( public_key.public_bytes( encoding, PublicFormat.SubjectPublicKeyInfo ) == data ) def test_openssl_serialization_unsupported(self, backend): key = ed25519.Ed25519PrivateKey.generate() with pytest.raises(ValueError): key.private_bytes( Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) with pytest.raises(ValueError): key.private_bytes( Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) @pytest.mark.supported( only_if=lambda backend: backend.x448_supported(), skip_message="Requires OpenSSL with X448 support", ) class TestX448Serialization: def test_load_der_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "X448", "x448-pkcs8-enc.der"), lambda derfile: derfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "X448", "x448-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key = load_der_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.DER, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) def test_load_pem_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "X448", "x448-pkcs8-enc.pem"), lambda pemfile: pemfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "X448", "x448-pkcs8.pem"), lambda pemfile: pemfile.read(), mode="rb", ) key = load_pem_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) @pytest.mark.parametrize( ("key_path", "encoding", "loader"), [ (["X448", "x448-pub.pem"], Encoding.PEM, load_pem_public_key), (["X448", "x448-pub.der"], Encoding.DER, load_der_public_key), ], ) def test_load_public_key(self, key_path, encoding, loader, backend): data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: pemfile.read(), mode="rb", ) public_key = loader(data, backend) assert ( public_key.public_bytes( encoding, PublicFormat.SubjectPublicKeyInfo ) == data ) def test_openssl_serialization_unsupported(self, backend): key = x448.X448PrivateKey.generate() with pytest.raises(ValueError): key.private_bytes( Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) with pytest.raises(ValueError): key.private_bytes( Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) def test_openssh_serialization_unsupported(self, backend): key = x448.X448PrivateKey.generate() with pytest.raises(ValueError): key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) with pytest.raises(ValueError): key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption() ) @pytest.mark.supported( only_if=lambda backend: backend.x25519_supported(), skip_message="Requires OpenSSL with X25519 support", ) class TestX25519Serialization: def test_load_der_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "X25519", "x25519-pkcs8-enc.der"), lambda derfile: derfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "X25519", "x25519-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key = load_der_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.DER, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) def test_load_pem_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "X25519", "x25519-pkcs8-enc.pem"), lambda pemfile: pemfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "X25519", "x25519-pkcs8.pem"), lambda pemfile: pemfile.read(), mode="rb", ) key = load_pem_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) @pytest.mark.parametrize( ("key_path", "encoding", "loader"), [ (["X25519", "x25519-pub.pem"], Encoding.PEM, load_pem_public_key), (["X25519", "x25519-pub.der"], Encoding.DER, load_der_public_key), ], ) def test_load_public_key(self, key_path, encoding, loader, backend): data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: pemfile.read(), mode="rb", ) public_key = loader(data, backend) assert ( public_key.public_bytes( encoding, PublicFormat.SubjectPublicKeyInfo ) == data ) def test_openssl_serialization_unsupported(self, backend): key = x25519.X25519PrivateKey.generate() with pytest.raises(ValueError): key.private_bytes( Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) with pytest.raises(ValueError): key.private_bytes( Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) def test_openssh_serialization_unsupported(self, backend): key = x25519.X25519PrivateKey.generate() with pytest.raises(ValueError): key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) with pytest.raises(ValueError): key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption() ) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) class TestEd448Serialization: def test_load_der_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.der"), lambda derfile: derfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "Ed448", "ed448-pkcs8.der"), lambda derfile: derfile.read(), mode="rb", ) key = load_der_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.DER, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) def test_load_pem_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "Ed448", "ed448-pkcs8-enc.pem"), lambda pemfile: pemfile.read(), mode="rb", ) unencrypted = load_vectors_from_file( os.path.join("asymmetric", "Ed448", "ed448-pkcs8.pem"), lambda pemfile: pemfile.read(), mode="rb", ) key = load_pem_private_key(data, b"password", backend) assert ( key.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() ) == unencrypted ) @pytest.mark.parametrize( ("key_path", "encoding", "loader"), [ (["Ed448", "ed448-pub.pem"], Encoding.PEM, load_pem_public_key), (["Ed448", "ed448-pub.der"], Encoding.DER, load_der_public_key), ], ) def test_load_public_key(self, key_path, encoding, loader, backend): data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: pemfile.read(), mode="rb", ) public_key = loader(data, backend) assert ( public_key.public_bytes( encoding, PublicFormat.SubjectPublicKeyInfo ) == data ) def test_openssl_serialization_unsupported(self, backend): key = ed448.Ed448PrivateKey.generate() with pytest.raises(ValueError): key.private_bytes( Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) with pytest.raises(ValueError): key.private_bytes( Encoding.DER, PrivateFormat.TraditionalOpenSSL, NoEncryption(), ) def test_openssh_serialization_unsupported(self, backend): key = ed448.Ed448PrivateKey.generate() with pytest.raises(ValueError): key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH, ) with pytest.raises(ValueError): key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption(), ) @pytest.mark.supported( only_if=lambda backend: backend.dh_supported(), skip_message="DH not supported", ) class TestDHSerialization: """Test all options with least-supported key type.""" @pytest.mark.skip_fips(reason="non-FIPS parameters") def test_dh_public_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "DH", "dhkey.pem"), lambda pemfile: pemfile.read(), mode="rb", ) public_key = load_pem_private_key(data, None, backend).public_key() for enc in ( Encoding.PEM, Encoding.DER, Encoding.OpenSSH, Encoding.Raw, Encoding.X962, ): for fmt in ( PublicFormat.SubjectPublicKeyInfo, PublicFormat.PKCS1, PublicFormat.OpenSSH, PublicFormat.Raw, PublicFormat.CompressedPoint, PublicFormat.UncompressedPoint, ): if ( enc in (Encoding.PEM, Encoding.DER) and fmt == PublicFormat.SubjectPublicKeyInfo ): # tested elsewhere continue with pytest.raises(ValueError): public_key.public_bytes(enc, fmt) @pytest.mark.skip_fips(reason="non-FIPS parameters") def test_dh_private_key(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "DH", "dhkey.pem"), lambda pemfile: pemfile.read(), mode="rb", ) private_key = load_pem_private_key(data, None, backend) for enc in ( Encoding.PEM, Encoding.DER, Encoding.OpenSSH, Encoding.Raw, Encoding.X962, ): for fmt in ( PrivateFormat.PKCS8, PrivateFormat.TraditionalOpenSSL, PrivateFormat.Raw, ): if ( enc in (Encoding.PEM, Encoding.DER) and fmt is PrivateFormat.PKCS8 ): # tested elsewhere continue with pytest.raises(ValueError): private_key.private_bytes(enc, fmt, NoEncryption()) class TestEncryptionBuilder: def test_unsupported_format(self): f = PrivateFormat.PKCS8 with pytest.raises(ValueError): f.encryption_builder() def test_duplicate_kdf_rounds(self): b = PrivateFormat.OpenSSH.encryption_builder().kdf_rounds(12) with pytest.raises(ValueError): b.kdf_rounds(12) def test_invalid_kdf_rounds(self): b = PrivateFormat.OpenSSH.encryption_builder() with pytest.raises(ValueError): b.kdf_rounds(0) with pytest.raises(ValueError): b.kdf_rounds(-1) with pytest.raises(TypeError): b.kdf_rounds("string") # type: ignore[arg-type] def test_invalid_password(self): b = PrivateFormat.OpenSSH.encryption_builder() with pytest.raises(ValueError): b.build(12) # type: ignore[arg-type] with pytest.raises(ValueError): b.build(b"") def test_unsupported_type_for_methods(self): b = PrivateFormat.OpenSSH.encryption_builder() with pytest.raises(TypeError): b.key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) with pytest.raises(TypeError): b.hmac_hash(SHA1()) def test_duplicate_hmac_hash(self): b = PrivateFormat.PKCS12.encryption_builder().hmac_hash(SHA1()) with pytest.raises(ValueError): b.hmac_hash(SHA1()) def test_duplicate_key_cert_algorithm(self): b = PrivateFormat.PKCS12.encryption_builder().key_cert_algorithm( PBES.PBESv1SHA1And3KeyTripleDESCBC ) with pytest.raises(ValueError): b.key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) cryptography-43.0.0/tests/hazmat/primitives/test_sm4.py010064400017510000177000000143011464676315000214650ustar 00000000000000# 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. import binascii import os import pytest from cryptography.exceptions import InvalidTag from cryptography.hazmat.primitives.ciphers import algorithms, base, modes from ...utils import load_nist_vectors, load_vectors_from_file from .utils import generate_encrypt_test @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.SM4(b"\x00" * 16), modes.ECB() ), skip_message="Does not support SM4 ECB", ) class TestSM4ModeECB: test_ecb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SM4"), ["draft-ribose-cfrg-sm4-10-ecb.txt"], lambda key, **kwargs: algorithms.SM4(binascii.unhexlify(key)), lambda **kwargs: modes.ECB(), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.SM4(b"\x00" * 16), modes.CBC(b"\x00" * 16) ), skip_message="Does not support SM4 CBC", ) class TestSM4ModeCBC: test_cbc = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SM4"), ["draft-ribose-cfrg-sm4-10-cbc.txt"], lambda key, **kwargs: algorithms.SM4(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.SM4(b"\x00" * 16), modes.OFB(b"\x00" * 16) ), skip_message="Does not support SM4 OFB", ) class TestSM4ModeOFB: test_ofb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SM4"), ["draft-ribose-cfrg-sm4-10-ofb.txt"], lambda key, **kwargs: algorithms.SM4(binascii.unhexlify(key)), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.SM4(b"\x00" * 16), modes.CFB(b"\x00" * 16) ), skip_message="Does not support SM4 CFB", ) class TestSM4ModeCFB: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SM4"), ["draft-ribose-cfrg-sm4-10-cfb.txt"], lambda key, **kwargs: algorithms.SM4(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.SM4(b"\x00" * 16), modes.CTR(b"\x00" * 16) ), skip_message="Does not support SM4 CTR", ) class TestSM4ModeCTR: test_cfb = generate_encrypt_test( load_nist_vectors, os.path.join("ciphers", "SM4"), ["draft-ribose-cfrg-sm4-10-ctr.txt"], lambda key, **kwargs: algorithms.SM4(binascii.unhexlify(key)), lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)), ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.SM4(b"\x00" * 16), modes.GCM(b"\x00" * 16) ), skip_message="Does not support SM4 GCM", ) class TestSM4ModeGCM: @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("ciphers", "SM4", "rfc8998.txt"), load_nist_vectors, ), ) def test_encryption(self, vector, backend): key = binascii.unhexlify(vector["key"]) iv = binascii.unhexlify(vector["iv"]) associated_data = binascii.unhexlify(vector["aad"]) tag = binascii.unhexlify(vector["tag"]) plaintext = binascii.unhexlify(vector["plaintext"]) ciphertext = binascii.unhexlify(vector["ciphertext"]) cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv)) encryptor = cipher.encryptor() encryptor.authenticate_additional_data(associated_data) computed_ct = encryptor.update(plaintext) + encryptor.finalize() assert computed_ct == ciphertext assert encryptor.tag == tag @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("ciphers", "SM4", "rfc8998.txt"), load_nist_vectors, ), ) def test_decryption(self, vector, backend): key = binascii.unhexlify(vector["key"]) iv = binascii.unhexlify(vector["iv"]) associated_data = binascii.unhexlify(vector["aad"]) tag = binascii.unhexlify(vector["tag"]) plaintext = binascii.unhexlify(vector["plaintext"]) ciphertext = binascii.unhexlify(vector["ciphertext"]) cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv, tag)) decryptor = cipher.decryptor() decryptor.authenticate_additional_data(associated_data) computed_pt = decryptor.update(ciphertext) + decryptor.finalize() assert computed_pt == plaintext cipher_no_tag = base.Cipher(algorithms.SM4(key), modes.GCM(iv)) decryptor = cipher_no_tag.decryptor() decryptor.authenticate_additional_data(associated_data) computed_pt = decryptor.update( ciphertext ) + decryptor.finalize_with_tag(tag) assert computed_pt == plaintext @pytest.mark.parametrize( "vector", load_vectors_from_file( os.path.join("ciphers", "SM4", "rfc8998.txt"), load_nist_vectors, ), ) def test_invalid_tag(self, vector, backend): key = binascii.unhexlify(vector["key"]) iv = binascii.unhexlify(vector["iv"]) associated_data = binascii.unhexlify(vector["aad"]) tag = binascii.unhexlify(vector["tag"]) ciphertext = binascii.unhexlify(vector["ciphertext"]) cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv, tag)) decryptor = cipher.decryptor() decryptor.authenticate_additional_data(associated_data) decryptor.update(ciphertext[:-1]) with pytest.raises(InvalidTag): decryptor.finalize() cipher_no_tag = base.Cipher(algorithms.SM4(key), modes.GCM(iv)) decryptor = cipher_no_tag.decryptor() decryptor.authenticate_additional_data(associated_data) decryptor.update(ciphertext[:-1]) with pytest.raises(InvalidTag): decryptor.finalize_with_tag(tag) cryptography-43.0.0/tests/hazmat/primitives/test_ssh.py010064400017510000177000002144231464676315000215660ustar 00000000000000# 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. import base64 import datetime import os import pytest from cryptography import utils from cryptography.exceptions import InvalidSignature, InvalidTag from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed25519, rsa, ) from cryptography.hazmat.primitives.serialization import ( BestAvailableEncryption, Encoding, KeySerializationEncryption, NoEncryption, PrivateFormat, PublicFormat, SSHCertificate, SSHCertificateBuilder, SSHCertificateType, load_pem_private_key, load_ssh_private_key, load_ssh_public_identity, load_ssh_public_key, ssh, ) from ...doubles import DummyKeySerializationEncryption from ...utils import load_vectors_from_file, raises_unsupported_algorithm from .fixtures_rsa import RSA_KEY_2048 from .test_ec import _skip_curve_unsupported from .test_rsa import rsa_key_2048 __all__ = ["rsa_key_2048"] class TestOpenSSHSerialization: @pytest.mark.parametrize( ("key_file", "cert_file"), [ ("rsa-psw.key.pub", None), ("rsa-nopsw.key.pub", "rsa-nopsw.key-cert.pub"), ("dsa-psw.key.pub", None), ("dsa-nopsw.key.pub", "dsa-nopsw.key-cert.pub"), ("ecdsa-psw.key.pub", None), ("ecdsa-nopsw.key.pub", "ecdsa-nopsw.key-cert.pub"), ("ed25519-psw.key.pub", None), ("ed25519-nopsw.key.pub", "ed25519-nopsw.key-cert.pub"), ("sk-ecdsa-psw.key.pub", None), ("sk-ecdsa-nopsw.key.pub", None), ("sk-ed25519-psw.key.pub", None), ("sk-ed25519-nopsw.key.pub", None), ], ) def test_load_ssh_public_key(self, key_file, cert_file, backend): if "ed25519" in key_file and not backend.ed25519_supported(): pytest.skip("Requires OpenSSL with Ed25519 support") # normal public key pub_data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", key_file), lambda f: f.read(), mode="rb", ) nocomment_data = b" ".join(pub_data.split()[:2]) if key_file.startswith("dsa"): with pytest.warns(utils.DeprecatedIn40): public_key = load_ssh_public_key(pub_data, backend) with pytest.warns(utils.DeprecatedIn40): assert ( public_key.public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) else: public_key = load_ssh_public_key(pub_data, backend) if not key_file.startswith("sk-"): # SK keys do not round-trip assert ( public_key.public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) self.run_partial_pubkey(pub_data, backend) # parse public key with ssh certificate if cert_file: cert_data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", cert_file), lambda f: f.read(), mode="rb", ) if cert_file.startswith("dsa"): with pytest.warns(utils.DeprecatedIn40): cert_key = load_ssh_public_key(cert_data, backend) with pytest.warns(utils.DeprecatedIn40): assert ( cert_key.public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) else: cert_key = load_ssh_public_key(cert_data, backend) assert ( cert_key.public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) # try with more spaces cert_data = b" \t ".join(cert_data.split()) if cert_file.startswith("dsa"): with pytest.warns(utils.DeprecatedIn40): cert_key = load_ssh_public_key(cert_data, backend) with pytest.warns(utils.DeprecatedIn40): assert ( cert_key.public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) else: cert_key = load_ssh_public_key(cert_data, backend) assert ( cert_key.public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) self.run_partial_pubkey(cert_data, backend) def run_partial_pubkey(self, pubdata, backend): parts = pubdata.split() raw = base64.b64decode(parts[1]) for i in range(1, len(raw)): frag = base64.b64encode(raw[:i]) new_pub = b" ".join([parts[0], frag]) with pytest.raises(ValueError): load_ssh_public_key(new_pub, backend) @pytest.mark.parametrize( ("key_file",), [ ("rsa-nopsw.key",), ("rsa-psw.key",), ("dsa-nopsw.key",), ("dsa-psw.key",), ("ecdsa-nopsw.key",), ("ecdsa-psw.key",), ("ed25519-nopsw.key",), ("ed25519-psw.key",), ("ed25519-aesgcm-psw.key",), ], ) def test_load_ssh_private_key(self, key_file, backend): if "ed25519" in key_file and not backend.ed25519_supported(): pytest.skip("Requires OpenSSL with Ed25519 support") if "-psw" in key_file and not ssh._bcrypt_supported: pytest.skip("Requires bcrypt module") # read public and private key from ssh-keygen priv_data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", key_file), lambda f: f.read(), mode="rb", ) pub_data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", key_file + ".pub"), lambda f: f.read(), mode="rb", ) nocomment_data = b" ".join(pub_data.split()[:2]) # load and compare password = None if "-psw" in key_file: password = b"password" for data in [ priv_data, bytearray(priv_data), memoryview(priv_data), memoryview(bytearray(priv_data)), ]: if key_file.startswith("dsa"): with pytest.warns(utils.DeprecatedIn40): private_key = load_ssh_private_key(data, password, backend) with pytest.warns(utils.DeprecatedIn40): assert ( private_key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) else: private_key = load_ssh_private_key(data, password, backend) assert ( private_key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) # serialize with own code and reload encryption: KeySerializationEncryption = NoEncryption() if password: encryption = BestAvailableEncryption(password) if key_file.startswith("dsa"): with pytest.warns(utils.DeprecatedIn40): priv_data2 = private_key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, encryption, ) with pytest.warns(utils.DeprecatedIn40): private_key2 = load_ssh_private_key( priv_data2, password, backend ) with pytest.warns(utils.DeprecatedIn40): assert ( private_key2.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) else: priv_data2 = private_key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, encryption, ) private_key2 = load_ssh_private_key(priv_data2, password, backend) assert ( private_key2.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) == nocomment_data ) # make sure multi-line base64 is used maxline = max(map(len, priv_data2.split(b"\n"))) assert maxline < 80 @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires Ed25519 support", ) @pytest.mark.supported( only_if=lambda backend: ssh._bcrypt_supported, skip_message="Requires that bcrypt exists", ) def test_load_ssh_private_key_invalid_tag(self, backend): priv_data = bytearray( load_vectors_from_file( os.path.join( "asymmetric", "OpenSSH", "ed25519-aesgcm-psw.key" ), lambda f: f.read(), mode="rb", ) ) # mutate one byte to break the tag priv_data[-38] = 82 with pytest.raises(InvalidTag): load_ssh_private_key(priv_data, b"password") @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires Ed25519 support", ) @pytest.mark.supported( only_if=lambda backend: ssh._bcrypt_supported, skip_message="Requires that bcrypt exists", ) def test_load_ssh_private_key_tag_incorrect_length(self, backend): priv_data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", "ed25519-aesgcm-psw.key"), lambda f: f.read(), mode="rb", ) # clip out a byte broken_data = priv_data[:-37] + priv_data[-38:] with pytest.raises(ValueError): load_ssh_private_key(broken_data, b"password") @pytest.mark.supported( only_if=lambda backend: ssh._bcrypt_supported, skip_message="Requires that bcrypt exists", ) def test_bcrypt_encryption(self, backend): private_key = ec.generate_private_key(ec.SECP256R1(), backend) pub1 = private_key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) for psw in ( b"1", b"1234", b"1234" * 4, b"x" * 72, ): # BestAvailableEncryption does not handle bytes-like? best = BestAvailableEncryption(psw) encdata = private_key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, best ) decoded_key = load_ssh_private_key(encdata, psw, backend) pub2 = decoded_key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) assert pub1 == pub2 # bytearray decoded_key2 = load_ssh_private_key( bytearray(encdata), psw, backend ) pub2 = decoded_key2.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) assert pub1 == pub2 # memoryview(bytes) decoded_key2 = load_ssh_private_key( memoryview(encdata), psw, backend ) pub2 = decoded_key2.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) assert pub1 == pub2 # memoryview(bytearray) decoded_key2 = load_ssh_private_key( memoryview(bytearray(encdata)), psw, backend ) pub2 = decoded_key2.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) assert pub1 == pub2 with pytest.raises(ValueError): decoded_key = load_ssh_private_key(encdata, None, backend) with pytest.raises(ValueError): decoded_key = load_ssh_private_key(encdata, b"wrong", backend) @pytest.mark.supported( only_if=lambda backend: not ssh._bcrypt_supported, skip_message="Requires that bcrypt is missing", ) def test_missing_bcrypt(self, backend): priv_data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", "ecdsa-psw.key"), lambda f: f.read(), mode="rb", ) with raises_unsupported_algorithm(None): load_ssh_private_key(priv_data, b"password", backend) private_key = ec.generate_private_key(ec.SECP256R1(), backend) with raises_unsupported_algorithm(None): private_key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, BestAvailableEncryption(b"x"), ) def test_fraglist_corners(self): f = ssh._FragList() with pytest.raises(ValueError): f.put_mpint(-1) f.put_mpint(0) f.put_mpint(0x80) assert f.tobytes() == b"\0\0\0\0" + b"\0\0\0\x02" + b"\0\x80" def make_file( self, magic=b"openssh-key-v1\0", ciphername=b"none", kdfname=b"none", kdfoptions=b"", nkeys=1, pub_type=b"ecdsa-sha2-nistp256", pub_fields=( b"nistp256", b"\x04" * 65, ), priv_type=None, priv_fields=(b"nistp256", b"\x04" * 65, b"\x7f" * 32), comment=b"comment", checkval1=b"1234", checkval2=b"1234", pad=None, header=b"-----BEGIN OPENSSH PRIVATE KEY-----\n", footer=b"-----END OPENSSH PRIVATE KEY-----\n", cut=8192, ): """Create private key file""" if not priv_type: priv_type = pub_type pub = ssh._FragList() for elem in (pub_type, *pub_fields): pub.put_sshstr(elem) secret = ssh._FragList([checkval1, checkval2]) for i in range(nkeys): for elem in (priv_type, *priv_fields, comment): secret.put_sshstr(elem) if pad is None: pad_len = 8 - (secret.size() % 8) pad = bytearray(range(1, 1 + pad_len)) secret.put_raw(pad) main = ssh._FragList([magic]) main.put_sshstr(ciphername) main.put_sshstr(kdfname) main.put_sshstr(kdfoptions) main.put_u32(nkeys) for i in range(nkeys): main.put_sshstr(pub) main.put_sshstr(secret) res = main.tobytes() return ssh._ssh_pem_encode(res[:cut], header, footer) def test_ssh_make_file(self, backend): # check if works by default data = self.make_file() key = load_ssh_private_key(data, None, backend) assert isinstance(key, ec.EllipticCurvePrivateKey) def test_load_ssh_private_key_errors(self, backend): # bad kdf data = self.make_file(kdfname=b"unknown", ciphername=b"aes256-ctr") with raises_unsupported_algorithm(None): load_ssh_private_key(data, None, backend) # bad cipher data = self.make_file(ciphername=b"unknown", kdfname=b"bcrypt") with raises_unsupported_algorithm(None): load_ssh_private_key(data, None, backend) # bad magic data = self.make_file(magic=b"unknown") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # too few keys data = self.make_file(nkeys=0) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # too many keys data = self.make_file(nkeys=2) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) def test_ssh_errors_bad_values(self, backend): # bad curve data = self.make_file(pub_type=b"ecdsa-sha2-nistp444") with raises_unsupported_algorithm(None): load_ssh_private_key(data, None, backend) # curve mismatch data = self.make_file(priv_type=b"ecdsa-sha2-nistp384") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # invalid bigint data = self.make_file( priv_fields=(b"nistp256", b"\x04" * 65, b"\x80" * 32) ) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) def test_ssh_errors_pubpriv_mismatch(self, backend): # ecdsa public-private mismatch data = self.make_file( pub_fields=( b"nistp256", b"\x04" + b"\x05" * 64, ) ) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # rsa public-private mismatch data = self.make_file( pub_type=b"ssh-rsa", pub_fields=(b"x" * 32,) * 2, priv_fields=(b"z" * 32,) * 6, ) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # dsa public-private mismatch data = self.make_file( pub_type=b"ssh-dss", pub_fields=(b"x" * 32,) * 4, priv_fields=(b"z" * 32,) * 5, ) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # ed25519 public-private mismatch sk = b"x" * 32 pk1 = b"y" * 32 pk2 = b"z" * 32 data = self.make_file( pub_type=b"ssh-ed25519", pub_fields=(pk1,), priv_fields=( pk1, sk + pk2, ), ) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) data = self.make_file( pub_type=b"ssh-ed25519", pub_fields=(pk1,), priv_fields=( pk2, sk + pk1, ), ) with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) def test_ssh_errors_bad_wrapper(self, backend): # wrong header data = self.make_file(header=b"-----BEGIN RSA PRIVATE KEY-----\n") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # wring footer data = self.make_file(footer=b"-----END RSA PRIVATE KEY-----\n") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) def test_ssh_no_padding(self, backend): # no padding must work, if data is on block boundary data = self.make_file(pad=b"", comment=b"") key = load_ssh_private_key(data, None, backend) assert isinstance(key, ec.EllipticCurvePrivateKey) # no padding with right last byte data = self.make_file(pad=b"", comment=b"\x08" * 8) key = load_ssh_private_key(data, None, backend) assert isinstance(key, ec.EllipticCurvePrivateKey) # avoid unexpected padding removal data = self.make_file(pad=b"", comment=b"1234\x01\x02\x03\x04") key = load_ssh_private_key(data, None, backend) assert isinstance(key, ec.EllipticCurvePrivateKey) # bad padding with right size data = self.make_file(pad=b"\x08" * 8, comment=b"") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) def test_ssh_errors_bad_secrets(self, backend): # checkval mismatch data = self.make_file(checkval2=b"4321") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) # bad padding, correct=1 data = self.make_file(pad=b"\x01\x02") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) data = self.make_file(pad=b"") with pytest.raises(ValueError): load_ssh_private_key(data, None, backend) @pytest.mark.supported( only_if=lambda backend: backend.elliptic_curve_supported( ec.SECP192R1() ), skip_message="Requires backend support for ec.SECP192R1", ) def test_serialize_ssh_private_key_errors_bad_curve(self, backend): private_key = ec.generate_private_key(ec.SECP192R1(), backend) with pytest.raises(ValueError): private_key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption() ) def test_serialize_ssh_private_key_errors( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): # bad encoding private_key = ec.generate_private_key(ec.SECP256R1(), backend) with pytest.raises(ValueError): private_key.private_bytes( Encoding.DER, PrivateFormat.OpenSSH, NoEncryption() ) # bad object type with pytest.raises(ValueError): ssh._serialize_ssh_private_key( object(), # type:ignore[arg-type] b"", NoEncryption(), ) private_key = ec.generate_private_key(ec.SECP256R1(), backend) # unknown encryption class with pytest.raises(ValueError): private_key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): rsa_key_2048.private_bytes( Encoding.DER, PrivateFormat.OpenSSH, NoEncryption() ) @pytest.mark.supported( only_if=lambda backend: ssh._bcrypt_supported, skip_message="Requires that bcrypt exists", ) @pytest.mark.parametrize( "password", ( b"1234", b"p@ssw0rd", b"x" * 100, ), ) @pytest.mark.parametrize( "kdf_rounds", [ 1, 10, 30, ], ) def test_serialize_ssh_private_key_with_password( self, password, kdf_rounds, rsa_key_2048: rsa.RSAPrivateKey, backend ): for original_key in [ ec.generate_private_key(ec.SECP256R1(), backend), rsa_key_2048, ]: assert isinstance( original_key, (ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey) ) encoded_key_data = original_key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, ( PrivateFormat.OpenSSH.encryption_builder() .kdf_rounds(kdf_rounds) .build(password) ), ) decoded_key = load_ssh_private_key( data=encoded_key_data, password=password, backend=backend, ) original_public_key = original_key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) decoded_public_key = decoded_key.public_key().public_bytes( Encoding.OpenSSH, PublicFormat.OpenSSH ) assert original_public_key == decoded_public_key @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.parametrize( ("key_path", "supported"), [ (["Traditional_OpenSSL_Serialization", "dsa.1024.pem"], True), (["Traditional_OpenSSL_Serialization", "dsa.2048.pem"], False), (["Traditional_OpenSSL_Serialization", "dsa.3072.pem"], False), ], ) def test_dsa_private_key_sizes(self, key_path, supported, backend): key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: load_pem_private_key( pemfile.read(), None, backend ), mode="rb", ) assert isinstance(key, dsa.DSAPrivateKey) if supported: with pytest.warns(utils.DeprecatedIn40): res = key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption() ) assert isinstance(res, bytes) else: with pytest.raises(ValueError): with pytest.warns(utils.DeprecatedIn40): key.private_bytes( Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption() ) class TestRSASSHSerialization: def test_load_ssh_public_key_unsupported(self, backend): ssh_key = b"ecdsa-sha2-junk AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY=" with raises_unsupported_algorithm(None): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_bad_format(self, backend): ssh_key = b"ssh-rsa not-a-real-key" with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa_too_short(self, backend): ssh_key = b"ssh-rsa" with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_truncated_int(self, backend): ssh_key = b"ssh-rsa AAAAB3NzaC1yc2EAAAA=" with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) ssh_key = b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAACKr+IHXo" with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa_comment_with_spaces(self, backend): ssh_key = ( b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" # Extra section appended b"2MzHvnbv testkey@localhost extra" ) load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa_extra_data_after_modulo(self, backend): ssh_key = ( b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" b"2MzHvnbvAQ== testkey@localhost" ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa_different_string(self, backend): ssh_key = ( # "AAAAB3NzA" the final A is capitalized here to cause the string # ssh-rsa inside the base64 encoded blob to be incorrect. It should # be a lower case 'a'. b"ssh-rsa AAAAB3NzAC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" b"2MzHvnbvAQ== testkey@localhost" ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_rsa(self, backend): ssh_key = ( b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" b"2MzHvnbv testkey@localhost" ) key = load_ssh_public_key(ssh_key, backend) assert key is not None assert isinstance(key, rsa.RSAPublicKey) numbers = key.public_numbers() expected_e = 0x10001 expected_n = int( "00C3BBF5D13F59322BA0A0B77EA0B6CF570241628AE24B5BA454D" "23DCA295652B3523B67752653DFFD69587FAD9578DD6406F23691" "EA491C3F8B2D391D0312D9653C303B651067ADF887A5241843CEF" "8019680A088E092FEC305FB04EA070340BB9BD0F1635B2AD84142" "61B4E2D010ABD8FC6D2FB768912F78EE6B05A60857532B75B75EF" "C007601A4EF58BA947B7E75E38F3443CDD87E7C138A1DAD9D9FB3" "19FF69DA43A9F6F6B0CD243F042CD1A5AFAEB286BD46AEB2D922B" "D01385D6892167074A0907F94A2BF08A54ABB2FFFFC89920861D0" "46F8706AB88DDADBD9E8204D48B87789081E074024C8996783B31" "7076A98ABF0A2D8550EAF2097D8CCC7BE76EF", 16, ) expected = rsa.RSAPublicNumbers(expected_e, expected_n) assert numbers == expected class TestDSSSSHSerialization: def test_load_ssh_public_key_dss_too_short(self, backend): ssh_key = b"ssh-dss" with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_dss_comment_with_spaces(self, backend): ssh_key = ( b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost extra" ) with pytest.warns(utils.DeprecatedIn40): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_dss_extra_data_after_modulo(self, backend): ssh_key = ( b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" b"z53N7tPF/IhHTjBHb1Ol7IFu9p9AAwMD== testkey@localhost" ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_dss_different_string(self, backend): ssh_key = ( # "AAAAB3NzA" the final A is capitalized here to cause the string # ssh-dss inside the base64 encoded blob to be incorrect. It should # be a lower case 'a'. b"ssh-dss AAAAB3NzAC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost" ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_dss(self, backend): ssh_key = ( b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost" ) with pytest.warns(utils.DeprecatedIn40): key = load_ssh_public_key(ssh_key, backend) assert key is not None assert isinstance(key, dsa.DSAPublicKey) numbers = key.public_numbers() expected_y = int( "d143cf92901f936fa258e9a11422460c8f8f1597884eef8cb1252a3e2ff0aae" "96a7032c01cdd8485b5cbfb73a46bb04708f98a18bc88d4c7812b284da8f900" "6e473e89897f9bc9125c69bbfd8ef691c0e76c1c34e6c843b8fe240e6e5aeb3" "13486e5fa917ab1288ff1a6ebcf9dcdeed3c5fc88474e30476f53a5ec816ef6" "9f4", 16, ) expected_p = int( "b9b052d7f07630148d4d838b17790ef4f43437238ebebd5032ea483fd7b7902" "5ec3dc65ebd563ab586a633b4344f6acd10af31353bcf29111fa5e3b8d5c1e8" "7befe3c65f9b8be69c740716698c8366c8ef925b9cec1dcd69e73d926b554e2" "b4b6ddd1453eab39ba0f846e1555adcc33c5a8637128c9ed61104a45505a748" "f6db", 16, ) expected_q = 1230879958723280233885494314531920096931919647917 expected_g = int( "7f6c9170b2cfb67e78267c6fcb8b93b22fb03d895a0676451a15ac44511393a" "7bc249b6cf8f5f5c5022afefd4df5bf9d13bbdf182df5af2a5c5d1dc7604185" "7d5b0e4b22b856c300f850a3b00bac394b728755b8b7a56522eefc491573967" "debb5982fc94d6a8c291f758feae63ad769a5621947221522a2dc31d18ede6f" "b656", 16, ) expected = dsa.DSAPublicNumbers( expected_y, dsa.DSAParameterNumbers(expected_p, expected_q, expected_g), ) assert numbers == expected class TestECDSASSHSerialization: def test_load_ssh_public_key_ecdsa_nist_p256(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) ssh_key = ( b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" ) key = load_ssh_public_key(ssh_key, backend) assert isinstance(key, ec.EllipticCurvePublicKey) expected_x = int( "44196257377740326295529888716212621920056478823906609851236662550" "785814128027", 10, ) expected_y = int( "12257763433170736656417248739355923610241609728032203358057767672" "925775019611", 10, ) assert key.public_numbers() == ec.EllipticCurvePublicNumbers( expected_x, expected_y, ec.SECP256R1() ) def test_load_ssh_public_key_byteslike(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) ssh_key = ( b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" ) assert load_ssh_public_key(bytearray(ssh_key), backend) assert load_ssh_public_key(memoryview(ssh_key), backend) assert load_ssh_public_key(memoryview(bytearray(ssh_key)), backend) def test_load_ssh_public_key_ecdsa_nist_p384(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) ssh_key = ( b"ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAz" b"ODQAAABhBMzucOm9wbwg4iMr5QL0ya0XNQGXpw4wM5f12E3tWhdcrzyGHyel71t1" b"4bvF9JZ2/WIuSxUr33XDl8jYo+lMQ5N7Vanc7f7i3AR1YydatL3wQfZStQ1I3rBa" b"qQtRSEU8Tg== root@cloud-server-01" ) key = load_ssh_public_key(ssh_key, backend) assert isinstance(key, ec.EllipticCurvePublicKey) expected_x = int( "31541830871345183397582554827482786756220448716666815789487537666" "592636882822352575507883817901562613492450642523901", 10, ) expected_y = int( "15111413269431823234030344298767984698884955023183354737123929430" "995703524272335782455051101616329050844273733614670", 10, ) assert key.public_numbers() == ec.EllipticCurvePublicNumbers( expected_x, expected_y, ec.SECP384R1() ) def test_load_ssh_public_key_ecdsa_nist_p521(self, backend): _skip_curve_unsupported(backend, ec.SECP521R1()) ssh_key = ( b"ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1" b"MjEAAACFBAGTrRhMSEgF6Ni+PXNz+5fjS4lw3ypUILVVQ0Av+0hQxOx+MyozELon" b"I8NKbrbBjijEs1GuImsmkTmWsMXS1j2A7wB4Kseh7W9KA9IZJ1+TMrzWUEwvOOXi" b"wT23pbaWWXG4NaM7vssWfZBnvz3S174TCXnJ+DSccvWBFnKP0KchzLKxbg== " b"root@cloud-server-01" ) key = load_ssh_public_key(ssh_key, backend) assert isinstance(key, ec.EllipticCurvePublicKey) expected_x = int( "54124123120178189598842622575230904027376313369742467279346415219" "77809037378785192537810367028427387173980786968395921877911964629" "142163122798974160187785455", 10, ) expected_y = int( "16111775122845033200938694062381820957441843014849125660011303579" "15284560361402515564433711416776946492019498546572162801954089916" "006665939539407104638103918", 10, ) assert key.public_numbers() == ec.EllipticCurvePublicNumbers( expected_x, expected_y, ec.SECP521R1() ) def test_load_ssh_public_key_ecdsa_nist_p256_trailing_data(self, backend): ssh_key = ( b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" b"teIg1TO03/FD9hbpBFgBeix3NrCFPltB= root@cloud-server-01" ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_ecdsa_nist_p256_missing_data(self, backend): ssh_key = ( b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" b"teIg1TO03/FD9hbpBFgBeix3NrCF= root@cloud-server-01" ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_ecdsa_nist_p256_compressed(self, backend): # If we ever implement compressed points, note that this is not a valid # one, it just has the compressed marker in the right place. ssh_key = ( b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" b"NTYAAABBAWG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" ) with pytest.raises(NotImplementedError): load_ssh_public_key(ssh_key, backend) def test_load_ssh_public_key_ecdsa_nist_p256_bad_curve_name(self, backend): ssh_key = ( # The curve name in here is changed to be "nistp255". b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" b"NTUAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" ) with pytest.raises(ValueError): load_ssh_public_key(ssh_key, backend) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) class TestEd25519SSHSerialization: def test_load_ssh_public_key(self, backend): ssh_key = ( b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4" b"GR+xWvBmvxjxrB1vG user@chiron.local" ) key = load_ssh_public_key(ssh_key, backend) assert isinstance(key, ed25519.Ed25519PublicKey) assert key.public_bytes(Encoding.Raw, PublicFormat.Raw) == ( b"m\x9f\x82\x99\xa9`\xee\xb5\xa9\xe01\x19\xdd0\x81\x16\x8d\xfc" b"N\x06G\xecV\xbc\x19\xaf\xc6= 16] for params in all_params: with subtests.test(): aead_test(backend, cipher_factory, mode_factory, params) return test_aead def aead_test(backend, cipher_factory, mode_factory, params): if ( mode_factory is GCM and backend._fips_enabled and len(params["iv"]) != 24 ): # Red Hat disables non-96-bit IV support as part of its FIPS # patches. The check is for a byte length of 24 because the value is # hex encoded. pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") tag = binascii.unhexlify(params["tag"]) mode = mode_factory( binascii.unhexlify(params["iv"]), tag, len(tag), ) assert isinstance(mode, GCM) if params.get("pt") is not None: plaintext = binascii.unhexlify(params["pt"]) ciphertext = binascii.unhexlify(params["ct"]) aad = binascii.unhexlify(params["aad"]) if params.get("fail") is True: cipher = Cipher( cipher_factory(binascii.unhexlify(params["key"])), mode, backend, ) decryptor = cipher.decryptor() decryptor.authenticate_additional_data(aad) actual_plaintext = decryptor.update(ciphertext) with pytest.raises(InvalidTag): decryptor.finalize() else: cipher = Cipher( cipher_factory(binascii.unhexlify(params["key"])), mode_factory(binascii.unhexlify(params["iv"]), None), backend, ) encryptor = cipher.encryptor() encryptor.authenticate_additional_data(aad) actual_ciphertext = encryptor.update(plaintext) actual_ciphertext += encryptor.finalize() assert encryptor.tag[: len(tag)] == tag cipher = Cipher( cipher_factory(binascii.unhexlify(params["key"])), mode_factory( binascii.unhexlify(params["iv"]), tag, min_tag_length=len(tag), ), backend, ) decryptor = cipher.decryptor() decryptor.authenticate_additional_data(aad) actual_plaintext = decryptor.update(ciphertext) actual_plaintext += decryptor.finalize() assert actual_plaintext == plaintext def generate_stream_encryption_test( param_loader, path, file_names, cipher_factory ): def test_stream_encryption(self, backend, subtests): for params in _load_all_params(path, file_names, param_loader): with subtests.test(): stream_encryption_test(backend, cipher_factory, params) return test_stream_encryption def stream_encryption_test(backend, cipher_factory, params): plaintext = params["plaintext"] ciphertext = params["ciphertext"] offset = params["offset"] cipher = Cipher(cipher_factory(**params), None, backend=backend) encryptor = cipher.encryptor() # throw away offset bytes encryptor.update(b"\x00" * int(offset)) actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) actual_ciphertext += encryptor.finalize() assert actual_ciphertext == binascii.unhexlify(ciphertext) decryptor = cipher.decryptor() decryptor.update(b"\x00" * int(offset)) actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) actual_plaintext += decryptor.finalize() assert actual_plaintext == binascii.unhexlify(plaintext) def generate_hash_test(param_loader, path, file_names, hash_cls): def test_hash(self, backend, subtests): for params in _load_all_params(path, file_names, param_loader): with subtests.test(): hash_test(backend, hash_cls, params) return test_hash def hash_test(backend, algorithm, params): msg, md = params m = hashes.Hash(algorithm, backend=backend) m.update(binascii.unhexlify(msg)) expected_md = md.replace(" ", "").lower().encode("ascii") assert m.finalize() == binascii.unhexlify(expected_md) def generate_base_hash_test(algorithm, digest_size): def test_base_hash(self, backend): base_hash_test(backend, algorithm, digest_size) return test_base_hash def base_hash_test(backend, algorithm, digest_size): m = hashes.Hash(algorithm, backend=backend) assert m.algorithm.digest_size == digest_size m_copy = m.copy() assert m != m_copy m.update(b"abc") copy = m.copy() copy.update(b"123") m.update(b"123") assert copy.finalize() == m.finalize() def generate_base_hmac_test(hash_cls): def test_base_hmac(self, backend): base_hmac_test(backend, hash_cls) return test_base_hmac def base_hmac_test(backend, algorithm): key = b"ab" h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) h_copy = h.copy() assert h != h_copy def generate_hmac_test(param_loader, path, file_names, algorithm): def test_hmac(self, backend, subtests): for params in _load_all_params(path, file_names, param_loader): with subtests.test(): hmac_test(backend, algorithm, params) return test_hmac def hmac_test(backend, algorithm, params): msg, md, key = params h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) h.update(binascii.unhexlify(msg)) assert h.finalize() == binascii.unhexlify(md.encode("ascii")) def generate_aead_exception_test(cipher_factory, mode_factory): def test_aead_exception(self, backend): aead_exception_test(backend, cipher_factory, mode_factory) return test_aead_exception def aead_exception_test(backend, cipher_factory, mode_factory): mode = mode_factory(binascii.unhexlify(b"0" * 24)) assert isinstance(mode, GCM) cipher = Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), mode, backend, ) encryptor = cipher.encryptor() encryptor.update(b"a" * 16) with pytest.raises(NotYetFinalized): encryptor.tag with pytest.raises(AlreadyUpdated): encryptor.authenticate_additional_data(b"b" * 16) encryptor.finalize() with pytest.raises(AlreadyFinalized): encryptor.authenticate_additional_data(b"b" * 16) with pytest.raises(AlreadyFinalized): encryptor.update(b"b" * 16) with pytest.raises(AlreadyFinalized): encryptor.finalize() mode2 = mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16) assert isinstance(mode2, GCM) cipher = Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), mode2, backend, ) decryptor = cipher.decryptor() decryptor.update(b"a" * 16) with pytest.raises(AlreadyUpdated): decryptor.authenticate_additional_data(b"b" * 16) with pytest.raises(AttributeError): decryptor.tag # type: ignore[attr-defined] def generate_aead_tag_exception_test(cipher_factory, mode_factory): def test_aead_tag_exception(self, backend): aead_tag_exception_test(backend, cipher_factory, mode_factory) return test_aead_tag_exception def aead_tag_exception_test(backend, cipher_factory, mode_factory): cipher = Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), mode_factory(binascii.unhexlify(b"0" * 24)), backend, ) with pytest.raises(ValueError): mode_factory(binascii.unhexlify(b"0" * 24), b"000") with pytest.raises(ValueError): Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), mode_factory(binascii.unhexlify(b"0" * 24), b"toolong" * 12), backend, ) with pytest.raises(ValueError): mode_factory(binascii.unhexlify(b"0" * 24), b"000000", 2) cipher = Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), backend, ) with pytest.raises(ValueError): cipher.encryptor() def hkdf_derive_test(backend, algorithm, params): hkdf = HKDF( algorithm, int(params["l"]), salt=binascii.unhexlify(params["salt"]) or None, info=binascii.unhexlify(params["info"]) or None, backend=backend, ) okm = hkdf.derive(binascii.unhexlify(params["ikm"])) assert okm == binascii.unhexlify(params["okm"]) def hkdf_extract_test(backend, algorithm, params): hkdf = HKDF( algorithm, int(params["l"]), salt=binascii.unhexlify(params["salt"]) or None, info=binascii.unhexlify(params["info"]) or None, backend=backend, ) prk = hkdf._extract(binascii.unhexlify(params["ikm"])) assert prk == binascii.unhexlify(params["prk"]) def hkdf_expand_test(backend, algorithm, params): hkdf = HKDFExpand( algorithm, int(params["l"]), info=binascii.unhexlify(params["info"]) or None, backend=backend, ) okm = hkdf.derive(binascii.unhexlify(params["prk"])) assert okm == binascii.unhexlify(params["okm"]) def generate_hkdf_test(param_loader, path, file_names, algorithm): def test_hkdf(self, backend, subtests): for params in _load_all_params(path, file_names, param_loader): with subtests.test(): hkdf_extract_test(backend, algorithm, params) with subtests.test(): hkdf_expand_test(backend, algorithm, params) with subtests.test(): hkdf_derive_test(backend, algorithm, params) return test_hkdf def generate_kbkdf_counter_mode_test(param_loader, path, file_names): def test_kbkdf(self, backend, subtests): for params in _load_all_params(path, file_names, param_loader): with subtests.test(): kbkdf_counter_mode_test(backend, params) return test_kbkdf def _kbkdf_hmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params): supported_hash_algorithms: typing.Dict[ str, typing.Type[hashes.HashAlgorithm] ] = { "hmac_sha1": hashes.SHA1, "hmac_sha224": hashes.SHA224, "hmac_sha256": hashes.SHA256, "hmac_sha384": hashes.SHA384, "hmac_sha512": hashes.SHA512, } algorithm = supported_hash_algorithms.get(prf) assert algorithm is not None assert backend.hmac_supported(algorithm()) ctrkdf = KBKDFHMAC( algorithm(), Mode.CounterMode, params["l"] // 8, params["rlen"] // 8, None, ctr_loc, None, None, binascii.unhexlify(params["fixedinputdata"]), backend=backend, break_location=brk_loc, ) ko = ctrkdf.derive(binascii.unhexlify(params["ki"])) assert binascii.hexlify(ko) == params["ko"] def _kbkdf_cmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params): supported_cipher_algorithms: typing.Dict[ str, typing.Type[BlockCipherAlgorithm] ] = { "cmac_aes128": algorithms.AES, "cmac_aes192": algorithms.AES, "cmac_aes256": algorithms.AES, "cmac_tdes2": decrepit_algorithms.TripleDES, "cmac_tdes3": decrepit_algorithms.TripleDES, } algorithm = supported_cipher_algorithms.get(prf) assert algorithm is not None # TripleDES is disallowed in FIPS mode. if backend._fips_enabled and algorithm is decrepit_algorithms.TripleDES: pytest.skip("TripleDES is not supported in FIPS mode.") ctrkdf = KBKDFCMAC( algorithm, Mode.CounterMode, params["l"] // 8, params["rlen"] // 8, None, ctr_loc, None, None, binascii.unhexlify(params["fixedinputdata"]), backend=backend, break_location=brk_loc, ) ko = ctrkdf.derive(binascii.unhexlify(params["ki"])) assert binascii.hexlify(ko) == params["ko"] def kbkdf_counter_mode_test(backend, params): supported_counter_locations = { "before_fixed": CounterLocation.BeforeFixed, "after_fixed": CounterLocation.AfterFixed, "middle_fixed": CounterLocation.MiddleFixed, } ctr_loc = supported_counter_locations[params.pop("ctrlocation")] brk_loc = None if ctr_loc == CounterLocation.MiddleFixed: assert "fixedinputdata" not in params params["fixedinputdata"] = params.pop( "databeforectrdata" ) + params.pop("dataafterctrdata") brk_loc = params.pop("databeforectrlen") assert isinstance(brk_loc, int) prf = params.get("prf") assert prf is not None assert isinstance(prf, str) del params["prf"] if prf.startswith("hmac"): _kbkdf_hmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params) else: assert prf.startswith("cmac") _kbkdf_cmac_counter_mode_test(backend, prf, ctr_loc, brk_loc, params) def generate_rsa_verification_test( param_loader, path, file_names, hash_alg, pad_factory ): def test_rsa_verification(self, backend, subtests): all_params = _load_all_params(path, file_names, param_loader) all_params = [ i for i in all_params if i["algorithm"] == hash_alg.name.upper() ] for params in all_params: with subtests.test(): rsa_verification_test(backend, params, hash_alg, pad_factory) return test_rsa_verification def rsa_verification_test(backend, params, hash_alg, pad_factory): public_numbers = rsa.RSAPublicNumbers( e=params["public_exponent"], n=params["modulus"] ) public_key = public_numbers.public_key(backend) pad = pad_factory(params, hash_alg) signature = binascii.unhexlify(params["s"]) msg = binascii.unhexlify(params["msg"]) if params["fail"]: with pytest.raises(InvalidSignature): public_key.verify(signature, msg, pad, hash_alg) else: public_key.verify(signature, msg, pad, hash_alg) def _rsa_recover_euler_private_exponent(e: int, p: int, q: int) -> int: """ Compute the RSA private_exponent (d) given the public exponent (e) and the RSA primes p and q, following the usage of the original RSA paper. As in the original RSA paper, this uses the Euler totient function instead of the Carmichael totient function, and thus may generate a larger value of the private exponent than necessary. See cryptography.hazmat.primitives.asymmetric.rsa_recover_private_exponent for the public-facing version of this function, which uses the preferred Carmichael totient function. """ phi_n = (p - 1) * (q - 1) return rsa._modinv(e, phi_n) def _check_rsa_private_numbers(skey): assert skey pkey = skey.public_numbers assert pkey assert pkey.e assert pkey.n # Historically there have been two ways to calculate valid values of the # private_exponent (d) given the public exponent (e): # - using the Carmichael totient function (gives smaller and more # computationally-efficient values, and is required by some standards) # - using the Euler totient function (matching the original RSA paper) # Allow for either here. assert skey.d in ( rsa.rsa_recover_private_exponent(pkey.e, skey.p, skey.q), _rsa_recover_euler_private_exponent(pkey.e, skey.p, skey.q), ) assert skey.p * skey.q == pkey.n assert skey.dmp1 == rsa.rsa_crt_dmp1(skey.d, skey.p) assert skey.dmq1 == rsa.rsa_crt_dmq1(skey.d, skey.q) assert skey.iqmp == rsa.rsa_crt_iqmp(skey.p, skey.q) def _check_dsa_private_numbers(skey): assert skey pkey = skey.public_numbers params = pkey.parameter_numbers assert pow(params.g, skey.x, params.p) == pkey.y def skip_fips_traditional_openssl(backend, fmt): if ( fmt is serialization.PrivateFormat.TraditionalOpenSSL and backend._fips_enabled ): pytest.skip( "Traditional OpenSSL key format is not supported in FIPS mode." ) cryptography-43.0.0/tests/hazmat/test_oid.py010064400017510000177000000022441464676315000173450ustar 00000000000000# 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. import copy import pytest from cryptography.hazmat._oid import ObjectIdentifier def test_basic_oid(): assert ObjectIdentifier("1.2.3.4").dotted_string == "1.2.3.4" def test_oid_equal(): assert ObjectIdentifier("1.2.3.4") == ObjectIdentifier("1.2.3.4") def test_oid_deepcopy(): oid = ObjectIdentifier("1.2.3.4") assert oid == copy.deepcopy(oid) def test_oid_constraint(): # Too short with pytest.raises(ValueError): ObjectIdentifier("1") # First node too big with pytest.raises(ValueError): ObjectIdentifier("3.2.1") # Outside range with pytest.raises(ValueError): ObjectIdentifier("1.40") with pytest.raises(ValueError): ObjectIdentifier("0.42") # non-decimal oid with pytest.raises(ValueError): ObjectIdentifier("1.2.foo.bar") with pytest.raises(ValueError): ObjectIdentifier("1.2.0xf00.0xba4") # negative oid with pytest.raises(ValueError): ObjectIdentifier("1.2.-3.-4") cryptography-43.0.0/tests/test_cryptography_utils.py010064400017510000177000000032031464676315000212550ustar 00000000000000# 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. import enum import typing import pytest from cryptography import utils class TestCachedProperty: def test_simple(self): class T: @utils.cached_property def t(self): accesses.append(None) return 14 accesses: typing.List[typing.Optional[T]] = [] assert T.t t = T() assert t.t == 14 assert len(accesses) == 1 assert t.t == 14 assert len(accesses) == 1 t = T() assert t.t == 14 assert len(accesses) == 2 assert t.t == 14 assert len(accesses) == 2 def test_set(self): class T: @utils.cached_property def t(self): accesses.append(None) return 14 accesses: typing.List[typing.Optional[T]] = [] t = T() with pytest.raises(AttributeError): t.t = None assert len(accesses) == 0 assert t.t == 14 assert len(accesses) == 1 with pytest.raises(AttributeError): t.t = None assert len(accesses) == 1 assert t.t == 14 assert len(accesses) == 1 def test_enum(): class TestEnum(utils.Enum): something = "something" assert issubclass(TestEnum, enum.Enum) assert isinstance(TestEnum.something, enum.Enum) assert repr(TestEnum.something) == "" assert str(TestEnum.something) == "TestEnum.something" cryptography-43.0.0/tests/test_fernet.py010064400017510000177000000235601464676315000165750ustar 00000000000000# 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. import base64 import datetime import json import os import time import pretend import pytest import cryptography_vectors from cryptography.fernet import Fernet, InvalidToken, MultiFernet from cryptography.hazmat.primitives.ciphers import algorithms, modes def json_parametrize(keys, filename): vector_file = cryptography_vectors.open_vector_file( os.path.join("fernet", filename), "r" ) with vector_file: data = json.load(vector_file) return pytest.mark.parametrize( keys, [tuple([entry[k] for k in keys]) for entry in data], ids=[f"{filename}[{i}]" for i in range(len(data))], ) @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) class TestFernet: @json_parametrize( ("secret", "now", "iv", "src", "token"), "generate.json", ) def test_generate(self, secret, now, iv, src, token, backend): f = Fernet(secret.encode("ascii"), backend=backend) actual_token = f._encrypt_from_parts( src.encode("ascii"), int(datetime.datetime.fromisoformat(now).timestamp()), bytes(iv), ) assert actual_token == token.encode("ascii") @json_parametrize( ("secret", "now", "src", "ttl_sec", "token"), "verify.json", ) def test_verify( self, secret, now, src, ttl_sec, token, backend, monkeypatch ): # secret & token are both str f = Fernet(secret.encode("ascii"), backend=backend) current_time = int(datetime.datetime.fromisoformat(now).timestamp()) payload = f.decrypt_at_time( token, # str ttl=ttl_sec, current_time=current_time, ) assert payload == src.encode("ascii") payload = f.decrypt_at_time( token.encode("ascii"), # bytes ttl=ttl_sec, current_time=current_time, ) assert payload == src.encode("ascii") monkeypatch.setattr(time, "time", lambda: current_time) payload = f.decrypt(token, ttl=ttl_sec) # str assert payload == src.encode("ascii") payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) # bytes assert payload == src.encode("ascii") @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch): f = Fernet(secret.encode("ascii"), backend=backend) current_time = int(datetime.datetime.fromisoformat(now).timestamp()) with pytest.raises(InvalidToken): f.decrypt_at_time( token.encode("ascii"), ttl=ttl_sec, current_time=current_time, ) monkeypatch.setattr(time, "time", lambda: current_time) with pytest.raises(InvalidToken): f.decrypt(token.encode("ascii"), ttl=ttl_sec) def test_invalid_start_byte(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(InvalidToken): f.decrypt(base64.urlsafe_b64encode(b"\x81")) def test_timestamp_too_short(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(InvalidToken): f.decrypt(base64.urlsafe_b64encode(b"\x80abc")) def test_non_base64_token(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(InvalidToken): f.decrypt(b"\x00") with pytest.raises(InvalidToken): f.decrypt("nonsensetoken") def test_invalid_types(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(TypeError): f.encrypt("") # type: ignore[arg-type] with pytest.raises(TypeError): f.decrypt(12345) # type: ignore[arg-type] def test_timestamp_ignored_no_ttl(self, monkeypatch, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) pt = b"encrypt me" token = f.encrypt(pt) monkeypatch.setattr(time, "time", pretend.raiser(ValueError)) assert f.decrypt(token, ttl=None) == pt def test_ttl_required_in_decrypt_at_time(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) pt = b"encrypt me" token = f.encrypt(pt) with pytest.raises(ValueError): f.decrypt_at_time( token, ttl=None, # type: ignore[arg-type] current_time=int(time.time()), ) @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xff\x00\x80"]) def test_roundtrips(self, message, backend): f = Fernet(Fernet.generate_key(), backend=backend) assert f.decrypt(f.encrypt(message)) == message @pytest.mark.parametrize("key", [base64.urlsafe_b64encode(b"abc"), b"abc"]) def test_bad_key(self, backend, key): with pytest.raises(ValueError): Fernet(key, backend=backend) def test_extract_timestamp(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) current_time = 1526138327 token = f.encrypt_at_time(b"encrypt me", current_time) assert f.extract_timestamp(token) == current_time assert f.extract_timestamp(token.decode("ascii")) == current_time with pytest.raises(InvalidToken): f.extract_timestamp(b"nonsensetoken") @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) ), skip_message="Does not support AES CBC", ) class TestMultiFernet: def test_encrypt(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) f = MultiFernet([f1, f2]) assert f1.decrypt(f.encrypt(b"abc")) == b"abc" def test_decrypt(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) f = MultiFernet([f1, f2]) # token as bytes assert f.decrypt(f1.encrypt(b"abc")) == b"abc" assert f.decrypt(f2.encrypt(b"abc")) == b"abc" # token as str assert f.decrypt(f1.encrypt(b"abc").decode("ascii")) == b"abc" assert f.decrypt(f2.encrypt(b"abc").decode("ascii")) == b"abc" with pytest.raises(InvalidToken): f.decrypt(b"\x00" * 16) def test_decrypt_at_time(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f = MultiFernet([f1]) pt = b"encrypt me" token = f.encrypt_at_time(pt, current_time=100) assert f.decrypt_at_time(token, ttl=1, current_time=100) == pt with pytest.raises(InvalidToken): f.decrypt_at_time(token, ttl=1, current_time=102) with pytest.raises(ValueError): f.decrypt_at_time( token, ttl=None, # type: ignore[arg-type] current_time=100, ) def test_no_fernets(self, backend): with pytest.raises(ValueError): MultiFernet([]) def test_non_iterable_argument(self, backend): with pytest.raises(TypeError): MultiFernet(None) # type: ignore[arg-type] def test_rotate_bytes(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) mf1 = MultiFernet([f1]) mf2 = MultiFernet([f2, f1]) plaintext = b"abc" mf1_ciphertext = mf1.encrypt(plaintext) assert mf2.decrypt(mf1_ciphertext) == plaintext rotated = mf2.rotate(mf1_ciphertext) assert rotated != mf1_ciphertext assert mf2.decrypt(rotated) == plaintext with pytest.raises(InvalidToken): mf1.decrypt(rotated) def test_rotate_str(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) mf1 = MultiFernet([f1]) mf2 = MultiFernet([f2, f1]) plaintext = b"abc" mf1_ciphertext = mf1.encrypt(plaintext).decode("ascii") assert mf2.decrypt(mf1_ciphertext) == plaintext rotated = mf2.rotate(mf1_ciphertext).decode("ascii") assert rotated != mf1_ciphertext assert mf2.decrypt(rotated) == plaintext with pytest.raises(InvalidToken): mf1.decrypt(rotated) def test_rotate_preserves_timestamp(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) mf1 = MultiFernet([f1]) mf2 = MultiFernet([f2, f1]) plaintext = b"abc" original_time = int(time.time()) - 5 * 60 mf1_ciphertext = mf1.encrypt_at_time(plaintext, original_time) rotated_time, _ = Fernet._get_unverified_token_data( mf2.rotate(mf1_ciphertext) ) assert int(time.time()) != rotated_time assert original_time == rotated_time def test_rotate_decrypt_no_shared_keys(self, backend): f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) mf1 = MultiFernet([f1]) mf2 = MultiFernet([f2]) with pytest.raises(InvalidToken): mf2.rotate(mf1.encrypt(b"abc")) cryptography-43.0.0/tests/test_meta.py010064400017510000177000000021101464676315000162240ustar 00000000000000# 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. import os import pkgutil import subprocess import sys import typing import cryptography def find_all_modules() -> typing.List[str]: return sorted( mod for _, mod, _ in pkgutil.walk_packages( cryptography.__path__, prefix=cryptography.__name__ + ".", ) ) def test_no_circular_imports(subtests): env = os.environ.copy() env["PYTHONPATH"] = os.pathsep.join(sys.path) # When using pytest-cov it attempts to instrument subprocesses. This # causes the memleak tests to raise exceptions. # we don't need coverage so we remove the env vars. env.pop("COV_CORE_CONFIG", None) env.pop("COV_CORE_DATAFILE", None) env.pop("COV_CORE_SOURCE", None) for module in find_all_modules(): with subtests.test(): argv = [sys.executable, "-c", f"__import__({module!r})"] subprocess.check_call(argv, env=env) cryptography-43.0.0/tests/test_utils.py010064400017510000177000005604011464676315000164520ustar 00000000000000# 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. import binascii import inspect import os import textwrap import pretend import pytest import cryptography import cryptography.utils import cryptography_vectors from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from . import deprecated_module from .utils import ( check_backend_support, load_cryptrec_vectors, load_ed25519_vectors, load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, load_hash_vectors, load_kasvs_dh_vectors, load_kasvs_ecdh_vectors, load_nist_ccm_vectors, load_nist_kbkdf_vectors, load_nist_vectors, load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, load_x963_vectors, raises_unsupported_algorithm, ) def test_int_to_bytes_rejects_zero_length(): with pytest.raises(ValueError): cryptography.utils.int_to_bytes(123, 0) with pytest.raises(ValueError): cryptography.utils.int_to_bytes(0, 0) def test_check_backend_support_skip(): supported = pretend.stub( kwargs={"only_if": lambda backend: False, "skip_message": "Nope"} ) node = pretend.stub(iter_markers=lambda x: [supported]) item = pretend.stub(node=node) with pytest.raises(pytest.skip.Exception) as exc_info: check_backend_support(True, item) assert exc_info.value.args[0] == "Nope (True)" def test_check_backend_support_no_skip(): supported = pretend.stub( kwargs={"only_if": lambda backend: True, "skip_message": "Nope"} ) node = pretend.stub(iter_markers=lambda x: [supported]) item = pretend.stub(node=node) assert check_backend_support(None, item) is None def test_load_nist_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.1 # Config info for aes_values # AESVS GFSbox test data for CBC # State : Encrypt and Decrypt # Key Length : 128 # Generated on Fri Apr 22 15:11:33 2011 [ENCRYPT] COUNT = 0 KEY = 00000000000000000000000000000000 IV = 00000000000000000000000000000000 PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6 CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e COUNT = 1 KEY = 00000000000000000000000000000000 IV = 00000000000000000000000000000000 PLAINTEXT = 9798c4640bad75c7c3227db910174e72 CIPHERTEXT = a9a1631bf4996954ebc093957b234589 [DECRYPT] COUNT = 0 KEY = 00000000000000000000000000000000 IV = 00000000000000000000000000000000 CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6 COUNT = 1 KEY = 00000000000000000000000000000000 IV = 00000000000000000000000000000000 CIPHERTEXT = a9a1631bf4996954ebc093957b234589 PLAINTEXT = 9798c4640bad75c7c3227db910174e72 """ ).splitlines() assert load_nist_vectors(vector_data) == [ { "key": b"00000000000000000000000000000000", "iv": b"00000000000000000000000000000000", "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6", "ciphertext": b"0336763e966d92595a567cc9ce537f5e", }, { "key": b"00000000000000000000000000000000", "iv": b"00000000000000000000000000000000", "plaintext": b"9798c4640bad75c7c3227db910174e72", "ciphertext": b"a9a1631bf4996954ebc093957b234589", }, { "key": b"00000000000000000000000000000000", "iv": b"00000000000000000000000000000000", "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6", "ciphertext": b"0336763e966d92595a567cc9ce537f5e", }, { "key": b"00000000000000000000000000000000", "iv": b"00000000000000000000000000000000", "plaintext": b"9798c4640bad75c7c3227db910174e72", "ciphertext": b"a9a1631bf4996954ebc093957b234589", }, ] def test_load_nist_vectors_with_null_chars(): vector_data = textwrap.dedent( """ COUNT = 0 KEY = thing\\0withnulls COUNT = 1 KEY = 00000000000000000000000000000000 """ ).splitlines() assert load_nist_vectors(vector_data) == [ {"key": b"thing\x00withnulls"}, {"key": b"00000000000000000000000000000000"}, ] def test_load_ed25519_vectors(): vector_data = ( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a9" "80182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a:d75a98018" "2b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a::e5564300c360" "ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc" "61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b:\n" "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb3d401" "7c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:3d4017c3e" "843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:72:92a009a9f0" "d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996" "e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c0072:\n" "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7fc51c" "d8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:fc51cd8e6" "218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:af82:6291d657" "deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f" "290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40aaf82:\n" "0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9e61a1" "85bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:e61a185bc" "ef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:cbc77b:d9868d" "52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9" "758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00ccbc77b:\n" ).splitlines() assert load_ed25519_vectors(vector_data) == [ { "secret_key": ( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7" "f60" ), "public_key": ( "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f7075" "11a" ), "message": "", "signature": ( "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490" "1555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e" "7a100b" ), }, { "secret_key": ( "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a" "6fb" ), "public_key": ( "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af46" "60c" ), "message": "72", "signature": ( "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb6" "9da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612" "bb0c00" ), }, { "secret_key": ( "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b445" "8f7" ), "public_key": ( "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908" "025" ), "message": "af82", "signature": ( "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac" "3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea" "1ec40a" ), }, { "secret_key": ( "0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed97" "8e9" ), "public_key": ( "e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57" "057" ), "message": "cbc77b", "signature": ( "d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b" "3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db" "19b00c" ), }, ] def test_load_cryptrec_vectors(): vector_data = textwrap.dedent( """ # Vectors taken from https://info.isl.ntt.co.jp/crypt/eng/camellia/ # Download is t_camelia.txt # Camellia with 128-bit key K No.001 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 P No.001 : 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C No.001 : 07 92 3A 39 EB 0A 81 7D 1C 4D 87 BD B8 2D 1F 1C P No.002 : 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C No.002 : 48 CD 64 19 80 96 72 D2 34 92 60 D8 9A 08 D3 D3 K No.002 : 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 P No.001 : 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C No.001 : 07 92 3A 39 EB 0A 81 7D 1C 4D 87 BD B8 2D 1F 1C """ ).splitlines() assert load_cryptrec_vectors(vector_data) == [ { "key": b"00000000000000000000000000000000", "plaintext": b"80000000000000000000000000000000", "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", }, { "key": b"00000000000000000000000000000000", "plaintext": b"40000000000000000000000000000000", "ciphertext": b"48CD6419809672D2349260D89A08D3D3", }, { "key": b"10000000000000000000000000000000", "plaintext": b"80000000000000000000000000000000", "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", }, ] def test_load_cryptrec_vectors_invalid(): vector_data = textwrap.dedent( """ # Vectors taken from https://info.isl.ntt.co.jp/crypt/eng/camellia/ # Download is t_camelia.txt # Camellia with 128-bit key E No.001 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 """ ).splitlines() with pytest.raises(ValueError): load_cryptrec_vectors(vector_data) def test_load_hash_vectors(): vector_data = textwrap.dedent( """ # https://tools.ietf.org/html/rfc1321 [irrelevant] Len = 0 Msg = 00 MD = d41d8cd98f00b204e9800998ecf8427e Len = 8 Msg = 61 MD = 0cc175b9c0f1b6a831c399e269772661 Len = 24 Msg = 616263 MD = 900150983cd24fb0d6963f7d28e17f72 Len = 112 Msg = 6d65737361676520646967657374 MD = f96b697d7cb7938d525a2f31aaf161d0 """ ).splitlines() assert load_hash_vectors(vector_data) == [ (b"", "d41d8cd98f00b204e9800998ecf8427e"), (b"61", "0cc175b9c0f1b6a831c399e269772661"), (b"616263", "900150983cd24fb0d6963f7d28e17f72"), (b"6d65737361676520646967657374", "f96b697d7cb7938d525a2f31aaf161d0"), ] def test_load_hmac_vectors(): vector_data = textwrap.dedent( """ Len = 224 # "Jefe" Key = 4a656665 # "what do ya want for nothing?" Msg = 7768617420646f2079612077616e7420666f72206e6f7468696e673f MD = 750c783e6ab0b503eaa86e310a5db738 """ ).splitlines() assert load_hash_vectors(vector_data) == [ ( b"7768617420646f2079612077616e7420666f72206e6f7468696e673f", "750c783e6ab0b503eaa86e310a5db738", b"4a656665", ), ] def test_load_hash_vectors_bad_data(): vector_data = textwrap.dedent( """ # https://tools.ietf.org/html/rfc1321 Len = 0 Msg = 00 UNKNOWN=Hello World """ ).splitlines() with pytest.raises(ValueError): load_hash_vectors(vector_data) def test_load_vectors_from_file(): vectors = load_vectors_from_file( os.path.join("ciphers", "Blowfish", "bf-cfb.txt"), load_nist_vectors, ) assert vectors == [ { "key": b"0123456789ABCDEFF0E1D2C3B4A59687", "iv": b"FEDCBA9876543210", "plaintext": ( b"37363534333231204E6F77206973207468652074696D6520666F722000" ), "ciphertext": ( b"E73214A2822139CAF26ECF6D2EB9E76E3DA3DE04D1517200519D57A6C3" ), } ] def test_load_nist_gcm_vectors(): vector_data = textwrap.dedent( """ [Keylen = 128] [IVlen = 96] [PTlen = 0] [AADlen = 0] [Taglen = 128] Count = 0 Key = 11754cd72aec309bf52f7687212e8957 IV = 3c819d9a9bed087615030b65 PT = AAD = CT = Tag = 250327c674aaf477aef2675748cf6971 Count = 1 Key = 272f16edb81a7abbea887357a58c1917 IV = 794ec588176c703d3d2a7a07 PT = AAD = CT = Tag = b6e6f197168f5049aeda32dafbdaeb Count = 2 Key = a49a5e26a2f8cb63d05546c2a62f5343 IV = 907763b19b9b4ab6bd4f0281 CT = AAD = Tag = a2be08210d8c470a8df6e8fbd79ec5cf FAIL Count = 3 Key = 5c1155084cc0ede76b3bc22e9f7574ef IV = 9549e4ba69a61cad7856efc1 PT = d1448fa852b84408e2dad8381f363de7 AAD = e98e9d9c618e46fef32660976f854ee3 CT = f78b60ca125218493bea1c50a2e12ef4 Tag = d72da7f5c6cf0bca7242c71835809449 [Keylen = 128] [IVlen = 96] [PTlen = 0] [AADlen = 0] [Taglen = 120] Count = 0 Key = eac258e99c55e6ae8ef1da26640613d7 IV = 4e8df20faaf2c8eebe922902 CT = AAD = Tag = e39aeaebe86aa309a4d062d6274339 PT = Count = 1 Key = 3726cf02fcc6b8639a5497652c94350d IV = 55fef82cde693ce76efcc193 CT = AAD = Tag = 3d68111a81ed22d2ef5bccac4fc27f FAIL Count = 2 Key = f202299d5fd74f03b12d2119a6c4c038 IV = eec51e7958c3f20a1bb71815 CT = AAD = Tag = a81886b3fb26e51fca87b267e1e157 FAIL Count = 3 Key = fd52925f39546b4c55ffb6b20c59898c IV = f5cf3227444afd905a5f6dba CT = AAD = Tag = 1665b0f1a0b456e1664cfd3de08ccd PT = [Keylen = 128] [IVlen = 8] [PTlen = 104] [AADlen = 0] [Taglen = 128] Count = 0 Key = 58fab7632bcf10d2bcee58520bf37414 IV = 3c CT = 15c4db4cbb451211179d57017f AAD = Tag = eae841d4355feeb3f786bc86625f1e5b FAIL """ ).splitlines() assert load_nist_vectors(vector_data) == [ { "aad": b"", "pt": b"", "iv": b"3c819d9a9bed087615030b65", "tag": b"250327c674aaf477aef2675748cf6971", "key": b"11754cd72aec309bf52f7687212e8957", "ct": b"", }, { "aad": b"", "pt": b"", "iv": b"794ec588176c703d3d2a7a07", "tag": b"b6e6f197168f5049aeda32dafbdaeb", "key": b"272f16edb81a7abbea887357a58c1917", "ct": b"", }, { "aad": b"", "iv": b"907763b19b9b4ab6bd4f0281", "tag": b"a2be08210d8c470a8df6e8fbd79ec5cf", "key": b"a49a5e26a2f8cb63d05546c2a62f5343", "ct": b"", "fail": True, }, { "aad": b"e98e9d9c618e46fef32660976f854ee3", "pt": b"d1448fa852b84408e2dad8381f363de7", "iv": b"9549e4ba69a61cad7856efc1", "tag": b"d72da7f5c6cf0bca7242c71835809449", "key": b"5c1155084cc0ede76b3bc22e9f7574ef", "ct": b"f78b60ca125218493bea1c50a2e12ef4", }, { "aad": b"", "pt": b"", "iv": b"4e8df20faaf2c8eebe922902", "tag": b"e39aeaebe86aa309a4d062d6274339", "key": b"eac258e99c55e6ae8ef1da26640613d7", "ct": b"", }, { "aad": b"", "iv": b"55fef82cde693ce76efcc193", "tag": b"3d68111a81ed22d2ef5bccac4fc27f", "key": b"3726cf02fcc6b8639a5497652c94350d", "ct": b"", "fail": True, }, { "aad": b"", "iv": b"eec51e7958c3f20a1bb71815", "tag": b"a81886b3fb26e51fca87b267e1e157", "key": b"f202299d5fd74f03b12d2119a6c4c038", "ct": b"", "fail": True, }, { "aad": b"", "pt": b"", "iv": b"f5cf3227444afd905a5f6dba", "tag": b"1665b0f1a0b456e1664cfd3de08ccd", "key": b"fd52925f39546b4c55ffb6b20c59898c", "ct": b"", }, { "aad": b"", "iv": b"3c", "tag": b"eae841d4355feeb3f786bc86625f1e5b", "key": b"58fab7632bcf10d2bcee58520bf37414", "ct": b"15c4db4cbb451211179d57017f", "fail": True, }, ] def test_load_pkcs1_vectors(): vector_data = textwrap.dedent( """ Test vectors for RSA-PSS ======================== This file contains an extract of the original pss-vect.txt Key lengths: Key 8: 1031 bits Key 9: 1536 bits =========================================================================== # Example 8: A 1031-bit RSA key pair # ----------------------------------- # Public key # ---------- # Modulus: 49 53 70 a1 fb 18 54 3c 16 d3 63 1e 31 63 25 5d f6 2b e6 ee e8 90 d5 f2 55 09 e4 f7 78 a8 ea 6f bb bc df 85 df f6 4e 0d 97 20 03 ab 36 81 fb ba 6d d4 1f d5 41 82 9b 2e 58 2d e9 f2 a4 a4 e0 a2 d0 90 0b ef 47 53 db 3c ee 0e e0 6c 7d fa e8 b1 d5 3b 59 53 21 8f 9c ce ea 69 5b 08 66 8e de aa dc ed 94 63 b1 d7 90 d5 eb f2 7e 91 15 b4 6c ad 4d 9a 2b 8e fa b0 56 1b 08 10 34 47 39 ad a0 73 3f # Exponent: 01 00 01 # Private key # ----------- # Modulus: 49 53 70 a1 fb 18 54 3c 16 d3 63 1e 31 63 25 5d f6 2b e6 ee e8 90 d5 f2 55 09 e4 f7 78 a8 ea 6f bb bc df 85 df f6 4e 0d 97 20 03 ab 36 81 fb ba 6d d4 1f d5 41 82 9b 2e 58 2d e9 f2 a4 a4 e0 a2 d0 90 0b ef 47 53 db 3c ee 0e e0 6c 7d fa e8 b1 d5 3b 59 53 21 8f 9c ce ea 69 5b 08 66 8e de aa dc ed 94 63 b1 d7 90 d5 eb f2 7e 91 15 b4 6c ad 4d 9a 2b 8e fa b0 56 1b 08 10 34 47 39 ad a0 73 3f # Public exponent: 01 00 01 # Exponent: 6c 66 ff e9 89 80 c3 8f cd ea b5 15 98 98 83 61 65 f4 b4 b8 17 c4 f6 a8 d4 86 ee 4e a9 13 0f e9 b9 09 2b d1 36 d1 84 f9 5f 50 4a 60 7e ac 56 58 46 d2 fd d6 59 7a 89 67 c7 39 6e f9 5a 6e ee bb 45 78 a6 43 96 6d ca 4d 8e e3 de 84 2d e6 32 79 c6 18 15 9c 1a b5 4a 89 43 7b 6a 61 20 e4 93 0a fb 52 a4 ba 6c ed 8a 49 47 ac 64 b3 0a 34 97 cb e7 01 c2 d6 26 6d 51 72 19 ad 0e c6 d3 47 db e9 # Prime 1: 08 da d7 f1 13 63 fa a6 23 d5 d6 d5 e8 a3 19 32 8d 82 19 0d 71 27 d2 84 6c 43 9b 0a b7 26 19 b0 a4 3a 95 32 0e 4e c3 4f c3 a9 ce a8 76 42 23 05 bd 76 c5 ba 7b e9 e2 f4 10 c8 06 06 45 a1 d2 9e db # Prime 2: 08 47 e7 32 37 6f c7 90 0f 89 8e a8 2e b2 b0 fc 41 85 65 fd ae 62 f7 d9 ec 4c e2 21 7b 97 99 0d d2 72 db 15 7f 99 f6 3c 0d cb b9 fb ac db d4 c4 da db 6d f6 77 56 35 8c a4 17 48 25 b4 8f 49 70 6d # Prime exponent 1: 05 c2 a8 3c 12 4b 36 21 a2 aa 57 ea 2c 3e fe 03 5e ff 45 60 f3 3d de bb 7a da b8 1f ce 69 a0 c8 c2 ed c1 65 20 dd a8 3d 59 a2 3b e8 67 96 3a c6 5f 2c c7 10 bb cf b9 6e e1 03 de b7 71 d1 05 fd 85 # Prime exponent 2: 04 ca e8 aa 0d 9f aa 16 5c 87 b6 82 ec 14 0b 8e d3 b5 0b 24 59 4b 7a 3b 2c 22 0b 36 69 bb 81 9f 98 4f 55 31 0a 1a e7 82 36 51 d4 a0 2e 99 44 79 72 59 51 39 36 34 34 e5 e3 0a 7e 7d 24 15 51 e1 b9 # Coefficient: 07 d3 e4 7b f6 86 60 0b 11 ac 28 3c e8 8d bb 3f 60 51 e8 ef d0 46 80 e4 4c 17 1e f5 31 b8 0b 2b 7c 39 fc 76 63 20 e2 cf 15 d8 d9 98 20 e9 6f f3 0d c6 96 91 83 9c 4b 40 d7 b0 6e 45 30 7d c9 1f 3f # RSA-PSS signing of 6 random messages with random salts # ------------------------------------------------------- # PSS Example 8.1 # ----------------- # Message to be signed: 81 33 2f 4b e6 29 48 41 5e a1 d8 99 79 2e ea cf 6c 6e 1d b1 da 8b e1 3b 5c ea 41 db 2f ed 46 70 92 e1 ff 39 89 14 c7 14 25 97 75 f5 95 f8 54 7f 73 56 92 a5 75 e6 92 3a f7 8f 22 c6 99 7d db 90 fb 6f 72 d7 bb 0d d5 74 4a 31 de cd 3d c3 68 58 49 83 6e d3 4a ec 59 63 04 ad 11 84 3c 4f 88 48 9f 20 97 35 f5 fb 7f da f7 ce c8 ad dc 58 18 16 8f 88 0a cb f4 90 d5 10 05 b7 a8 e8 4e 43 e5 42 87 97 75 71 dd 99 ee a4 b1 61 eb 2d f1 f5 10 8f 12 a4 14 2a 83 32 2e db 05 a7 54 87 a3 43 5c 9a 78 ce 53 ed 93 bc 55 08 57 d7 a9 fb # Salt: 1d 65 49 1d 79 c8 64 b3 73 00 9b e6 f6 f2 46 7b ac 4c 78 fa # Signature: 02 62 ac 25 4b fa 77 f3 c1 ac a2 2c 51 79 f8 f0 40 42 2b 3c 5b af d4 0a 8f 21 cf 0f a5 a6 67 cc d5 99 3d 42 db af b4 09 c5 20 e2 5f ce 2b 1e e1 e7 16 57 7f 1e fa 17 f3 da 28 05 2f 40 f0 41 9b 23 10 6d 78 45 aa f0 11 25 b6 98 e7 a4 df e9 2d 39 67 bb 00 c4 d0 d3 5b a3 55 2a b9 a8 b3 ee f0 7c 7f ec db c5 42 4a c4 db 1e 20 cb 37 d0 b2 74 47 69 94 0e a9 07 e1 7f bb ca 67 3b 20 52 23 80 c5 # PSS Example 8.2 # ----------------- # Message to be signed: e2 f9 6e af 0e 05 e7 ba 32 6e cc a0 ba 7f d2 f7 c0 23 56 f3 ce de 9d 0f aa bf 4f cc 8e 60 a9 73 e5 59 5f d9 ea 08 # Salt: 43 5c 09 8a a9 90 9e b2 37 7f 12 48 b0 91 b6 89 87 ff 18 38 # Signature: 27 07 b9 ad 51 15 c5 8c 94 e9 32 e8 ec 0a 28 0f 56 33 9e 44 a1 b5 8d 4d dc ff 2f 31 2e 5f 34 dc fe 39 e8 9c 6a 94 dc ee 86 db bd ae 5b 79 ba 4e 08 19 a9 e7 bf d9 d9 82 e7 ee 6c 86 ee 68 39 6e 8b 3a 14 c9 c8 f3 4b 17 8e b7 41 f9 d3 f1 21 10 9b f5 c8 17 2f ad a2 e7 68 f9 ea 14 33 03 2c 00 4a 8a a0 7e b9 90 00 0a 48 dc 94 c8 ba c8 aa be 2b 09 b1 aa 46 c0 a2 aa 0e 12 f6 3f bb a7 75 ba 7e # # ============================================= # Example 9: A 1536-bit RSA key pair # ----------------------------------- # Public key # ---------- # Modulus: e6 bd 69 2a c9 66 45 79 04 03 fd d0 f5 be b8 b9 bf 92 ed 10 00 7f c3 65 04 64 19 dd 06 c0 5c 5b 5b 2f 48 ec f9 89 e4 ce 26 91 09 97 9c bb 40 b4 a0 ad 24 d2 24 83 d1 ee 31 5a d4 cc b1 53 42 68 35 26 91 c5 24 f6 dd 8e 6c 29 d2 24 cf 24 69 73 ae c8 6c 5b f6 b1 40 1a 85 0d 1b 9a d1 bb 8c bc ec 47 b0 6f 0f 8c 7f 45 d3 fc 8f 31 92 99 c5 43 3d db c2 b3 05 3b 47 de d2 ec d4 a4 ca ef d6 14 83 3d c8 bb 62 2f 31 7e d0 76 b8 05 7f e8 de 3f 84 48 0a d5 e8 3e 4a 61 90 4a 4f 24 8f b3 97 02 73 57 e1 d3 0e 46 31 39 81 5c 6f d4 fd 5a c5 b8 17 2a 45 23 0e cb 63 18 a0 4f 14 55 d8 4e 5a 8b # Exponent: 01 00 01 # Private key # ----------- # Modulus: e6 bd 69 2a c9 66 45 79 04 03 fd d0 f5 be b8 b9 bf 92 ed 10 00 7f c3 65 04 64 19 dd 06 c0 5c 5b 5b 2f 48 ec f9 89 e4 ce 26 91 09 97 9c bb 40 b4 a0 ad 24 d2 24 83 d1 ee 31 5a d4 cc b1 53 42 68 35 26 91 c5 24 f6 dd 8e 6c 29 d2 24 cf 24 69 73 ae c8 6c 5b f6 b1 40 1a 85 0d 1b 9a d1 bb 8c bc ec 47 b0 6f 0f 8c 7f 45 d3 fc 8f 31 92 99 c5 43 3d db c2 b3 05 3b 47 de d2 ec d4 a4 ca ef d6 14 83 3d c8 bb 62 2f 31 7e d0 76 b8 05 7f e8 de 3f 84 48 0a d5 e8 3e 4a 61 90 4a 4f 24 8f b3 97 02 73 57 e1 d3 0e 46 31 39 81 5c 6f d4 fd 5a c5 b8 17 2a 45 23 0e cb 63 18 a0 4f 14 55 d8 4e 5a 8b # Public exponent: 01 00 01 # Exponent: 6a 7f d8 4f b8 5f ad 07 3b 34 40 6d b7 4f 8d 61 a6 ab c1 21 96 a9 61 dd 79 56 5e 9d a6 e5 18 7b ce 2d 98 02 50 f7 35 95 75 35 92 70 d9 15 90 bb 0e 42 7c 71 46 0b 55 d5 14 10 b1 91 bc f3 09 fe a1 31 a9 2c 8e 70 27 38 fa 71 9f 1e 00 41 f5 2e 40 e9 1f 22 9f 4d 96 a1 e6 f1 72 e1 55 96 b4 51 0a 6d ae c2 61 05 f2 be bc 53 31 6b 87 bd f2 13 11 66 60 70 e8 df ee 69 d5 2c 71 a9 76 ca ae 79 c7 2b 68 d2 85 80 dc 68 6d 9f 51 29 d2 25 f8 2b 3d 61 55 13 a8 82 b3 db 91 41 6b 48 ce 08 88 82 13 e3 7e eb 9a f8 00 d8 1c ab 32 8c e4 20 68 99 03 c0 0c 7b 5f d3 1b 75 50 3a 6d 41 96 84 d6 29 # Prime 1: f8 eb 97 e9 8d f1 26 64 ee fd b7 61 59 6a 69 dd cd 0e 76 da ec e6 ed 4b f5 a1 b5 0a c0 86 f7 92 8a 4d 2f 87 26 a7 7e 51 5b 74 da 41 98 8f 22 0b 1c c8 7a a1 fc 81 0c e9 9a 82 f2 d1 ce 82 1e dc ed 79 4c 69 41 f4 2c 7a 1a 0b 8c 4d 28 c7 5e c6 0b 65 22 79 f6 15 4a 76 2a ed 16 5d 47 de e3 67 # Prime 2: ed 4d 71 d0 a6 e2 4b 93 c2 e5 f6 b4 bb e0 5f 5f b0 af a0 42 d2 04 fe 33 78 d3 65 c2 f2 88 b6 a8 da d7 ef e4 5d 15 3e ef 40 ca cc 7b 81 ff 93 40 02 d1 08 99 4b 94 a5 e4 72 8c d9 c9 63 37 5a e4 99 65 bd a5 5c bf 0e fe d8 d6 55 3b 40 27 f2 d8 62 08 a6 e6 b4 89 c1 76 12 80 92 d6 29 e4 9d 3d # Prime exponent 1: 2b b6 8b dd fb 0c 4f 56 c8 55 8b ff af 89 2d 80 43 03 78 41 e7 fa 81 cf a6 1a 38 c5 e3 9b 90 1c 8e e7 11 22 a5 da 22 27 bd 6c de eb 48 14 52 c1 2a d3 d6 1d 5e 4f 77 6a 0a b5 56 59 1b ef e3 e5 9e 5a 7f dd b8 34 5e 1f 2f 35 b9 f4 ce e5 7c 32 41 4c 08 6a ec 99 3e 93 53 e4 80 d9 ee c6 28 9f # Prime exponent 2: 4f f8 97 70 9f ad 07 97 46 49 45 78 e7 0f d8 54 61 30 ee ab 56 27 c4 9b 08 0f 05 ee 4a d9 f3 e4 b7 cb a9 d6 a5 df f1 13 a4 1c 34 09 33 68 33 f1 90 81 6d 8a 6b c4 2e 9b ec 56 b7 56 7d 0f 3c 9c 69 6d b6 19 b2 45 d9 01 dd 85 6d b7 c8 09 2e 77 e9 a1 cc cd 56 ee 4d ba 42 c5 fd b6 1a ec 26 69 # Coefficient: 77 b9 d1 13 7b 50 40 4a 98 27 29 31 6e fa fc 7d fe 66 d3 4e 5a 18 26 00 d5 f3 0a 0a 85 12 05 1c 56 0d 08 1d 4d 0a 18 35 ec 3d 25 a6 0f 4e 4d 6a a9 48 b2 bf 3d bb 5b 12 4c bb c3 48 92 55 a3 a9 48 37 2f 69 78 49 67 45 f9 43 e1 db 4f 18 38 2c ea a5 05 df c6 57 57 bb 3f 85 7a 58 dc e5 21 56 # PKCS#1 v1.5 Signature Example 2.17 # ----------------- # Message to be signed: 06 ad d7 5a b6 89 de 06 77 44 e6 9a 2e bd 4b 90 fa 93 83 00 3c d0 5f f5 36 cb f2 94 cd 21 5f 09 23 b7 fc 90 04 f0 aa 18 52 71 a1 d0 06 1f d0 e9 77 7a d1 ec 0c 71 59 1f 57 8b f7 b8 e5 a1 # Signature: 45 14 21 0e 54 1d 5b ad 7d d6 0a e5 49 b9 43 ac c4 4f 21 39 0d f5 b6 13 18 45 5a 17 61 0d f5 b7 4d 84 ae d2 32 f1 7e 59 d9 1d d2 65 99 22 f8 12 db d4 96 81 69 03 84 b9 54 e9 ad fb 9b 1a 96 8c 0c bf f7 63 ec ee d6 27 50 c5 91 64 b5 e0 80 a8 fe f3 d5 5b fe 2a cf ad 27 52 a6 a8 45 9f a1 fa b4 9a d3 78 c6 96 4b 23 ee 97 fd 10 34 61 0c 5c c1 4c 61 e0 eb fb 17 11 f8 ad e9 6f e6 55 7b 38 # # ============================================= # """ ).splitlines() vectors = tuple(load_pkcs1_vectors(vector_data)) expected = ( ( { "modulus": int( "495370a1fb18543c16d3631e3163255df62be6eee890d5f25509e4f77" "8a8ea6fbbbcdf85dff64e0d972003ab3681fbba6dd41fd541829b2e58" "2de9f2a4a4e0a2d0900bef4753db3cee0ee06c7dfae8b1d53b5953218" "f9cceea695b08668edeaadced9463b1d790d5ebf27e9115b46cad4d9a" "2b8efab0561b0810344739ada0733f", 16, ), "public_exponent": int("10001", 16), "private_exponent": int( "6c66ffe98980c38fcdeab5159898836165f4b4b817c4f6a8d486ee4ea" "9130fe9b9092bd136d184f95f504a607eac565846d2fdd6597a8967c7" "396ef95a6eeebb4578a643966dca4d8ee3de842de63279c618159c1ab" "54a89437b6a6120e4930afb52a4ba6ced8a4947ac64b30a3497cbe701" "c2d6266d517219ad0ec6d347dbe9", 16, ), "p": int( "8dad7f11363faa623d5d6d5e8a319328d82190d7127d2846c439b0ab7" "2619b0a43a95320e4ec34fc3a9cea876422305bd76c5ba7be9e2f410c" "8060645a1d29edb", 16, ), "q": int( "847e732376fc7900f898ea82eb2b0fc418565fdae62f7d9ec4ce2217b" "97990dd272db157f99f63c0dcbb9fbacdbd4c4dadb6df67756358ca41" "74825b48f49706d", 16, ), "dmp1": int( "05c2a83c124b3621a2aa57ea2c3efe035eff4560f33ddebb7adab81fc" "e69a0c8c2edc16520dda83d59a23be867963ac65f2cc710bbcfb96ee1" "03deb771d105fd85", 16, ), "dmq1": int( "04cae8aa0d9faa165c87b682ec140b8ed3b50b24594b7a3b2c220b366" "9bb819f984f55310a1ae7823651d4a02e99447972595139363434e5e3" "0a7e7d241551e1b9", 16, ), "iqmp": int( "07d3e47bf686600b11ac283ce88dbb3f6051e8efd04680e44c171ef53" "1b80b2b7c39fc766320e2cf15d8d99820e96ff30dc69691839c4b40d7" "b06e45307dc91f3f", 16, ), "examples": [ { "message": b"81332f4be62948415ea1d899792eeacf6c6e1db1d" b"a8be13b5cea41db2fed467092e1ff398914c71425" b"9775f595f8547f735692a575e6923af78f22c6997" b"ddb90fb6f72d7bb0dd5744a31decd3dc368584983" b"6ed34aec596304ad11843c4f88489f209735f5fb7" b"fdaf7cec8addc5818168f880acbf490d51005b7a8" b"e84e43e54287977571dd99eea4b161eb2df1f5108" b"f12a4142a83322edb05a75487a3435c9a78ce53ed" b"93bc550857d7a9fb", "salt": b"1d65491d79c864b373009be6f6f2467bac4c78fa", "signature": b"0262ac254bfa77f3c1aca22c5179f8f040422b3" b"c5bafd40a8f21cf0fa5a667ccd5993d42dbafb4" b"09c520e25fce2b1ee1e716577f1efa17f3da280" b"52f40f0419b23106d7845aaf01125b698e7a4df" b"e92d3967bb00c4d0d35ba3552ab9a8b3eef07c7" b"fecdbc5424ac4db1e20cb37d0b2744769940ea9" b"07e17fbbca673b20522380c5", }, { "message": b"e2f96eaf0e05e7ba326ecca0ba7fd2f7c02356f3c" b"ede9d0faabf4fcc8e60a973e5595fd9ea08", "salt": b"435c098aa9909eb2377f1248b091b68987ff1838", "signature": b"2707b9ad5115c58c94e932e8ec0a280f56339e4" b"4a1b58d4ddcff2f312e5f34dcfe39e89c6a94dc" b"ee86dbbdae5b79ba4e0819a9e7bfd9d982e7ee6" b"c86ee68396e8b3a14c9c8f34b178eb741f9d3f1" b"21109bf5c8172fada2e768f9ea1433032c004a8" b"aa07eb990000a48dc94c8bac8aabe2b09b1aa46" b"c0a2aa0e12f63fbba775ba7e", }, ], }, { "modulus": int( "495370a1fb18543c16d3631e3163255df62be6eee890d5f25509e4f77" "8a8ea6fbbbcdf85dff64e0d972003ab3681fbba6dd41fd541829b2e58" "2de9f2a4a4e0a2d0900bef4753db3cee0ee06c7dfae8b1d53b5953218" "f9cceea695b08668edeaadced9463b1d790d5ebf27e9115b46cad4d9a" "2b8efab0561b0810344739ada0733f", 16, ), "public_exponent": int("10001", 16), }, ), ( { "modulus": int( "e6bd692ac96645790403fdd0f5beb8b9bf92ed10007fc365046419dd0" "6c05c5b5b2f48ecf989e4ce269109979cbb40b4a0ad24d22483d1ee31" "5ad4ccb1534268352691c524f6dd8e6c29d224cf246973aec86c5bf6b" "1401a850d1b9ad1bb8cbcec47b06f0f8c7f45d3fc8f319299c5433ddb" "c2b3053b47ded2ecd4a4caefd614833dc8bb622f317ed076b8057fe8d" "e3f84480ad5e83e4a61904a4f248fb397027357e1d30e463139815c6f" "d4fd5ac5b8172a45230ecb6318a04f1455d84e5a8b", 16, ), "public_exponent": int("10001", 16), "private_exponent": int( "6a7fd84fb85fad073b34406db74f8d61a6abc12196a961dd79565e9da" "6e5187bce2d980250f7359575359270d91590bb0e427c71460b55d514" "10b191bcf309fea131a92c8e702738fa719f1e0041f52e40e91f229f4" "d96a1e6f172e15596b4510a6daec26105f2bebc53316b87bdf2131166" "6070e8dfee69d52c71a976caae79c72b68d28580dc686d9f5129d225f" "82b3d615513a882b3db91416b48ce08888213e37eeb9af800d81cab32" "8ce420689903c00c7b5fd31b75503a6d419684d629", 16, ), "p": int( "f8eb97e98df12664eefdb761596a69ddcd0e76daece6ed4bf5a1b50ac" "086f7928a4d2f8726a77e515b74da41988f220b1cc87aa1fc810ce99a" "82f2d1ce821edced794c6941f42c7a1a0b8c4d28c75ec60b652279f61" "54a762aed165d47dee367", 16, ), "q": int( "ed4d71d0a6e24b93c2e5f6b4bbe05f5fb0afa042d204fe3378d365c2f" "288b6a8dad7efe45d153eef40cacc7b81ff934002d108994b94a5e472" "8cd9c963375ae49965bda55cbf0efed8d6553b4027f2d86208a6e6b48" "9c176128092d629e49d3d", 16, ), "dmp1": int( "2bb68bddfb0c4f56c8558bffaf892d8043037841e7fa81cfa61a38c5e" "39b901c8ee71122a5da2227bd6cdeeb481452c12ad3d61d5e4f776a0a" "b556591befe3e59e5a7fddb8345e1f2f35b9f4cee57c32414c086aec9" "93e9353e480d9eec6289f", 16, ), "dmq1": int( "4ff897709fad079746494578e70fd8546130eeab5627c49b080f05ee4" "ad9f3e4b7cba9d6a5dff113a41c3409336833f190816d8a6bc42e9bec" "56b7567d0f3c9c696db619b245d901dd856db7c8092e77e9a1cccd56e" "e4dba42c5fdb61aec2669", 16, ), "iqmp": int( "77b9d1137b50404a982729316efafc7dfe66d34e5a182600d5f30a0a8" "512051c560d081d4d0a1835ec3d25a60f4e4d6aa948b2bf3dbb5b124c" "bbc3489255a3a948372f6978496745f943e1db4f18382ceaa505dfc65" "757bb3f857a58dce52156", 16, ), "examples": [ { "message": b"06add75ab689de067744e69a2ebd4b90fa9383003" b"cd05ff536cbf294cd215f0923b7fc9004f0aa1852" b"71a1d0061fd0e9777ad1ec0c71591f578bf7b8e5a" b"1", "signature": b"4514210e541d5bad7dd60ae549b943acc44f213" b"90df5b61318455a17610df5b74d84aed232f17e" b"59d91dd2659922f812dbd49681690384b954e9a" b"dfb9b1a968c0cbff763eceed62750c59164b5e0" b"80a8fef3d55bfe2acfad2752a6a8459fa1fab49" b"ad378c6964b23ee97fd1034610c5cc14c61e0eb" b"fb1711f8ade96fe6557b38", } ], }, { "modulus": int( "e6bd692ac96645790403fdd0f5beb8b9bf92ed10007fc365046419dd0" "6c05c5b5b2f48ecf989e4ce269109979cbb40b4a0ad24d22483d1ee31" "5ad4ccb1534268352691c524f6dd8e6c29d224cf246973aec86c5bf6b" "1401a850d1b9ad1bb8cbcec47b06f0f8c7f45d3fc8f319299c5433ddb" "c2b3053b47ded2ecd4a4caefd614833dc8bb622f317ed076b8057fe8d" "e3f84480ad5e83e4a61904a4f248fb397027357e1d30e463139815c6f" "d4fd5ac5b8172a45230ecb6318a04f1455d84e5a8b", 16, ), "public_exponent": int("10001", 16), }, ), ) assert vectors == expected def test_load_pkcs1_oaep_vectors(): vector_data = textwrap.dedent( """ Test vectors for RSA-OAEP ========================= This file contains test vectors for the RSA-OAEP encryption Key lengths: Key 1: 1024 bits # =========================================================================== # Example 1: A 1024-bit RSA key pair # ----------------------------------- # Public key # ---------- # Modulus: a8 b3 b2 84 af 8e b5 0b 38 70 34 a8 60 f1 46 c4 91 9f 31 87 63 cd 6c 55 98 c8 ae 48 11 a1 e0 ab c4 c7 e0 b0 82 d6 93 a5 e7 fc ed 67 5c f4 66 85 12 77 2c 0c bc 64 a7 42 c6 c6 30 f5 33 c8 cc 72 f6 2a e8 33 c4 0b f2 58 42 e9 84 bb 78 bd bf 97 c0 10 7d 55 bd b6 62 f5 c4 e0 fa b9 84 5c b5 14 8e f7 39 2d d3 aa ff 93 ae 1e 6b 66 7b b3 d4 24 76 16 d4 f5 ba 10 d4 cf d2 26 de 88 d3 9f 16 fb # Exponent: 01 00 01 # Private key # ----------- # Modulus: a8 b3 b2 84 af 8e b5 0b 38 70 34 a8 60 f1 46 c4 91 9f 31 87 63 cd 6c 55 98 c8 ae 48 11 a1 e0 ab c4 c7 e0 b0 82 d6 93 a5 e7 fc ed 67 5c f4 66 85 12 77 2c 0c bc 64 a7 42 c6 c6 30 f5 33 c8 cc 72 f6 2a e8 33 c4 0b f2 58 42 e9 84 bb 78 bd bf 97 c0 10 7d 55 bd b6 62 f5 c4 e0 fa b9 84 5c b5 14 8e f7 39 2d d3 aa ff 93 ae 1e 6b 66 7b b3 d4 24 76 16 d4 f5 ba 10 d4 cf d2 26 de 88 d3 9f 16 fb # Public exponent: 01 00 01 # Exponent: 53 33 9c fd b7 9f c8 46 6a 65 5c 73 16 ac a8 5c 55 fd 8f 6d d8 98 fd af 11 95 17 ef 4f 52 e8 fd 8e 25 8d f9 3f ee 18 0f a0 e4 ab 29 69 3c d8 3b 15 2a 55 3d 4a c4 d1 81 2b 8b 9f a5 af 0e 7f 55 fe 73 04 df 41 57 09 26 f3 31 1f 15 c4 d6 5a 73 2c 48 31 16 ee 3d 3d 2d 0a f3 54 9a d9 bf 7c bf b7 8a d8 84 f8 4d 5b eb 04 72 4d c7 36 9b 31 de f3 7d 0c f5 39 e9 cf cd d3 de 65 37 29 ea d5 d1 # Prime 1: d3 27 37 e7 26 7f fe 13 41 b2 d5 c0 d1 50 a8 1b 58 6f b3 13 2b ed 2f 8d 52 62 86 4a 9c b9 f3 0a f3 8b e4 48 59 8d 41 3a 17 2e fb 80 2c 21 ac f1 c1 1c 52 0c 2f 26 a4 71 dc ad 21 2e ac 7c a3 9d # Prime 2: cc 88 53 d1 d5 4d a6 30 fa c0 04 f4 71 f2 81 c7 b8 98 2d 82 24 a4 90 ed be b3 3d 3e 3d 5c c9 3c 47 65 70 3d 1d d7 91 64 2f 1f 11 6a 0d d8 52 be 24 19 b2 af 72 bf e9 a0 30 e8 60 b0 28 8b 5d 77 # Prime exponent 1: 0e 12 bf 17 18 e9 ce f5 59 9b a1 c3 88 2f e8 04 6a 90 87 4e ef ce 8f 2c cc 20 e4 f2 74 1f b0 a3 3a 38 48 ae c9 c9 30 5f be cb d2 d7 68 19 96 7d 46 71 ac c6 43 1e 40 37 96 8d b3 78 78 e6 95 c1 # Prime exponent 2: 95 29 7b 0f 95 a2 fa 67 d0 07 07 d6 09 df d4 fc 05 c8 9d af c2 ef 6d 6e a5 5b ec 77 1e a3 33 73 4d 92 51 e7 90 82 ec da 86 6e fe f1 3c 45 9e 1a 63 13 86 b7 e3 54 c8 99 f5 f1 12 ca 85 d7 15 83 # Coefficient: 4f 45 6c 50 24 93 bd c0 ed 2a b7 56 a3 a6 ed 4d 67 35 2a 69 7d 42 16 e9 32 12 b1 27 a6 3d 54 11 ce 6f a9 8d 5d be fd 73 26 3e 37 28 14 27 43 81 81 66 ed 7d d6 36 87 dd 2a 8c a1 d2 f4 fb d8 e1 # RSA-OAEP encryption of 6 random messages with random seeds # ----------------------------------------------------------- # OAEP Example 1.1 # ------------------ # Message: 66 28 19 4e 12 07 3d b0 3b a9 4c da 9e f9 53 23 97 d5 0d ba 79 b9 87 00 4a fe fe 34 # Seed: 18 b7 76 ea 21 06 9d 69 77 6a 33 e9 6b ad 48 e1 dd a0 a5 ef # Encryption: 35 4f e6 7b 4a 12 6d 5d 35 fe 36 c7 77 79 1a 3f 7b a1 3d ef 48 4e 2d 39 08 af f7 22 fa d4 68 fb 21 69 6d e9 5d 0b e9 11 c2 d3 17 4f 8a fc c2 01 03 5f 7b 6d 8e 69 40 2d e5 45 16 18 c2 1a 53 5f a9 d7 bf c5 b8 dd 9f c2 43 f8 cf 92 7d b3 13 22 d6 e8 81 ea a9 1a 99 61 70 e6 57 a0 5a 26 64 26 d9 8c 88 00 3f 84 77 c1 22 70 94 a0 d9 fa 1e 8c 40 24 30 9c e1 ec cc b5 21 00 35 d4 7a c7 2e 8a # OAEP Example 1.2 # ------------------ # Message: 75 0c 40 47 f5 47 e8 e4 14 11 85 65 23 29 8a c9 ba e2 45 ef af 13 97 fb e5 6f 9d d5 # Seed: 0c c7 42 ce 4a 9b 7f 32 f9 51 bc b2 51 ef d9 25 fe 4f e3 5f # Encryption: 64 0d b1 ac c5 8e 05 68 fe 54 07 e5 f9 b7 01 df f8 c3 c9 1e 71 6c 53 6f c7 fc ec 6c b5 b7 1c 11 65 98 8d 4a 27 9e 15 77 d7 30 fc 7a 29 93 2e 3f 00 c8 15 15 23 6d 8d 8e 31 01 7a 7a 09 df 43 52 d9 04 cd eb 79 aa 58 3a dc c3 1e a6 98 a4 c0 52 83 da ba 90 89 be 54 91 f6 7c 1a 4e e4 8d c7 4b bb e6 64 3a ef 84 66 79 b4 cb 39 5a 35 2d 5e d1 15 91 2d f6 96 ff e0 70 29 32 94 6d 71 49 2b 44 # ============================================= """ ).splitlines() vectors = load_pkcs1_vectors(vector_data) expected = [ ( { "modulus": int( "a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae481" "1a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6" "c630f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb" "662f5c4e0fab9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616" "d4f5ba10d4cfd226de88d39f16fb", 16, ), "public_exponent": int("10001", 16), "private_exponent": int( "53339cfdb79fc8466a655c7316aca85c55fd8f6dd898fdaf119517ef4" "f52e8fd8e258df93fee180fa0e4ab29693cd83b152a553d4ac4d1812b" "8b9fa5af0e7f55fe7304df41570926f3311f15c4d65a732c483116ee3" "d3d2d0af3549ad9bf7cbfb78ad884f84d5beb04724dc7369b31def37d" "0cf539e9cfcdd3de653729ead5d1", 16, ), "p": int( "d32737e7267ffe1341b2d5c0d150a81b586fb3132bed2f8d5262864a9" "cb9f30af38be448598d413a172efb802c21acf1c11c520c2f26a471dc" "ad212eac7ca39d", 16, ), "q": int( "cc8853d1d54da630fac004f471f281c7b8982d8224a490edbeb33d3e3" "d5cc93c4765703d1dd791642f1f116a0dd852be2419b2af72bfe9a030" "e860b0288b5d77", 16, ), "dmp1": int( "0e12bf1718e9cef5599ba1c3882fe8046a90874eefce8f2ccc20e4f27" "41fb0a33a3848aec9c9305fbecbd2d76819967d4671acc6431e403796" "8db37878e695c1", 16, ), "dmq1": int( "95297b0f95a2fa67d00707d609dfd4fc05c89dafc2ef6d6ea55bec771" "ea333734d9251e79082ecda866efef13c459e1a631386b7e354c899f5" "f112ca85d71583", 16, ), "iqmp": int( "4f456c502493bdc0ed2ab756a3a6ed4d67352a697d4216e93212b127a" "63d5411ce6fa98d5dbefd73263e3728142743818166ed7dd63687dd2a" "8ca1d2f4fbd8e1", 16, ), "examples": [ { "message": b"6628194e12073db03ba94cda9ef9532397d50dba7" b"9b987004afefe34", "seed": b"18b776ea21069d69776a33e96bad48e1dda0a5ef", "encryption": b"354fe67b4a126d5d35fe36c777791a3f7ba13d" b"ef484e2d3908aff722fad468fb21696de95d0b" b"e911c2d3174f8afcc201035f7b6d8e69402de5" b"451618c21a535fa9d7bfc5b8dd9fc243f8cf92" b"7db31322d6e881eaa91a996170e657a05a2664" b"26d98c88003f8477c1227094a0d9fa1e8c4024" b"309ce1ecccb5210035d47ac72e8a", }, { "message": b"750c4047f547e8e41411856523298ac9bae245efa" b"f1397fbe56f9dd5", "seed": b"0cc742ce4a9b7f32f951bcb251efd925fe4fe35f", "encryption": b"640db1acc58e0568fe5407e5f9b701dff8c3c9" b"1e716c536fc7fcec6cb5b71c1165988d4a279e" b"1577d730fc7a29932e3f00c81515236d8d8e31" b"017a7a09df4352d904cdeb79aa583adcc31ea6" b"98a4c05283daba9089be5491f67c1a4ee48dc7" b"4bbbe6643aef846679b4cb395a352d5ed11591" b"2df696ffe0702932946d71492b44", }, ], }, { "modulus": int( "a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae481" "1a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6" "c630f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb" "662f5c4e0fab9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616" "d4f5ba10d4cfd226de88d39f16fb", 16, ), "public_exponent": int("10001", 16), }, ) ] assert vectors == expected def test_load_hotp_vectors(): vector_data = textwrap.dedent( """ # HOTP Test Vectors # RFC 4226 Appendix D COUNT = 0 COUNTER = 0 INTERMEDIATE = cc93cf18508d94934c64b65d8ba7667fb7cde4b0 TRUNCATED = 4c93cf18 HOTP = 755224 SECRET = 12345678901234567890 COUNT = 1 COUNTER = 1 INTERMEDIATE = 75a48a19d4cbe100644e8ac1397eea747a2d33ab TRUNCATED = 41397eea HOTP = 287082 SECRET = 12345678901234567890 COUNT = 2 COUNTER = 2 INTERMEDIATE = 0bacb7fa082fef30782211938bc1c5e70416ff44 TRUNCATED = 82fef30 HOTP = 359152 SECRET = 12345678901234567890 COUNT = 3 COUNTER = 3 INTERMEDIATE = 66c28227d03a2d5529262ff016a1e6ef76557ece TRUNCATED = 66ef7655 HOTP = 969429 SECRET = 12345678901234567890 """ ).splitlines() assert load_nist_vectors(vector_data) == [ { "counter": b"0", "intermediate": b"cc93cf18508d94934c64b65d8ba7667fb7cde4b0", "truncated": b"4c93cf18", "hotp": b"755224", "secret": b"12345678901234567890", }, { "counter": b"1", "intermediate": b"75a48a19d4cbe100644e8ac1397eea747a2d33ab", "truncated": b"41397eea", "hotp": b"287082", "secret": b"12345678901234567890", }, { "counter": b"2", "intermediate": b"0bacb7fa082fef30782211938bc1c5e70416ff44", "truncated": b"82fef30", "hotp": b"359152", "secret": b"12345678901234567890", }, { "counter": b"3", "intermediate": b"66c28227d03a2d5529262ff016a1e6ef76557ece", "truncated": b"66ef7655", "hotp": b"969429", "secret": b"12345678901234567890", }, ] def test_load_totp_vectors(): vector_data = textwrap.dedent( """ # TOTP Test Vectors # RFC 6238 Appendix B COUNT = 0 TIME = 59 TOTP = 94287082 MODE = SHA1 SECRET = 12345678901234567890 COUNT = 1 TIME = 59 TOTP = 46119246 MODE = SHA256 SECRET = 12345678901234567890 COUNT = 2 TIME = 59 TOTP = 90693936 MODE = SHA512 SECRET = 12345678901234567890 """ ).splitlines() assert load_nist_vectors(vector_data) == [ { "time": b"59", "totp": b"94287082", "mode": b"SHA1", "secret": b"12345678901234567890", }, { "time": b"59", "totp": b"46119246", "mode": b"SHA256", "secret": b"12345678901234567890", }, { "time": b"59", "totp": b"90693936", "mode": b"SHA512", "secret": b"12345678901234567890", }, ] def test_load_rsa_nist_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.4 # "SigGen PKCS#1 RSASSA-PSS" information # Mod sizes selected: 1024 1536 2048 3072 4096 # SHA Algorithm selected:SHA1 SHA224 SHA256 SHA384 SHA512 # Salt len: 20 [mod = 1024] n = bcb47b2e0dafcba81ff2a2b5cb115ca7e757184c9d72bcdcda707a146b3b4e29989d e = 00000000000000000000000000000000000000000000000000000000000000000010001 SHAAlg = SHA1 Msg = 1248f62a4389f42f7b4bb131053d6c88a994db2075b912ccbe3ea7dc611714f14e S = 682cf53c1145d22a50caa9eb1a9ba70670c5915e0fdfde6457a765de2a8fe12de97 SHAAlg = SHA384 Msg = e511903c2f1bfba245467295ac95413ac4746c984c3750a728c388aa628b0ebf S = 9c748702bbcc1f9468864cd360c8c39d007b2d8aaee833606c70f7593cf0d1519 [mod = 1024] n = 1234567890 e = 0010001 SHAAlg = SHA512 Msg = 3456781293fab829 S = deadbeef0000 """ ).splitlines() vectors = load_rsa_nist_vectors(vector_data) assert vectors == [ { "modulus": int( "bcb47b2e0dafcba81ff2a2b5cb115ca7e757184c9d72bcdcda" "707a146b3b4e29989d", 16, ), "public_exponent": 65537, "algorithm": "SHA1", "salt_length": 20, "msg": b"1248f62a4389f42f7b4bb131053d6c88a994db2075b912ccbe3ea7dc6" b"11714f14e", "s": b"682cf53c1145d22a50caa9eb1a9ba70670c5915e0fdfde6457a765de2a8" b"fe12de97", "fail": False, }, { "modulus": int( "bcb47b2e0dafcba81ff2a2b5cb115ca7e757184c9d72bcdcda" "707a146b3b4e29989d", 16, ), "public_exponent": 65537, "algorithm": "SHA384", "salt_length": 20, "msg": b"e511903c2f1bfba245467295ac95413ac4746c984c3750a728c388aa6" b"28b0ebf", "s": b"9c748702bbcc1f9468864cd360c8c39d007b2d8aaee833606c70f7593cf" b"0d1519", "fail": False, }, { "modulus": 78187493520, "public_exponent": 65537, "algorithm": "SHA512", "salt_length": 20, "msg": b"3456781293fab829", "s": b"deadbeef0000", "fail": False, }, ] def test_load_rsa_nist_pkcs1v15_verification_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.0 # "SigVer PKCS#1 Ver 1.5" information # Mod sizes selected: 1024 1536 2048 3072 4096 # SHA Algorithm selected:SHA1 SHA224 SHA256 SHA384 SHA512 # Generated on Wed Mar 02 00:13:02 2011 [mod = 1024] n = be499b5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db126411 p = e7a80c5d211c06acb900939495f26d365fc2b4825b75e356f89003eaa5931e6be5c3 q = d248aa248000f720258742da67b711940c8f76e1ecd52b67a6ffe1e49354d66ff84f SHAAlg = SHA1 e = 00000000000000000000000000000000000000000000000000000000000000000011 d = 0d0f17362bdad181db4e1fe03e8de1a3208989914e14bf269558826bfa20faf4b68d Msg = 6b9cfac0ba1c7890b13e381ce752195cc1375237db2afcf6a9dcd1f95ec733a80c S = 562d87b5781c01d166fef3972669a0495c145b898a17df4743fbefb0a1582bd6ba9d SaltVal = 11223344555432167890 Result = F (3 - Signature changed ) SHAAlg = SHA1 e = 0000000000003 d = bfa20faf4b68d Msg = 2a67c70ff14f9b34ddb42e6f89d5971057a0da980fc9ae70c81a84da0c0ac42737 S = 2b91c6ae2b3c46ff18d5b7abe239634cb752d0acb53eea0ccd8ea8483036a50e8faf SaltVal = 11223344555432167890 Result = P """ ).splitlines() vectors = load_rsa_nist_vectors(vector_data) assert vectors == [ { "modulus": int( "be499b5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b" "9bfeb7aa72db126411", 16, ), "p": int( "e7a80c5d211c06acb900939495f26d365fc2b4825b75e356f89003ea" "a5931e6be5c3", 16, ), "q": int( "d248aa248000f720258742da67b711940c8f76e1ecd52b67a6ffe1e4" "9354d66ff84f", 16, ), "public_exponent": 17, "algorithm": "SHA1", "private_exponent": int( "0d0f17362bdad181db4e1fe03e8de1a3208989914" "e14bf269558826bfa20faf4b68d", 16, ), "msg": b"6b9cfac0ba1c7890b13e381ce752195cc1375237db2afcf6a9dcd1f95" b"ec733a80c", "s": b"562d87b5781c01d166fef3972669a0495c145b898a17df4743fbefb0a15" b"82bd6ba9d", "saltval": b"11223344555432167890", "fail": True, }, { "modulus": int( "be499b5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b" "9bfeb7aa72db126411", 16, ), "p": int( "e7a80c5d211c06acb900939495f26d365fc2b4825b75e356f89003ea" "a5931e6be5c3", 16, ), "q": int( "d248aa248000f720258742da67b711940c8f76e1ecd52b67a6ffe1e4" "9354d66ff84f", 16, ), "public_exponent": 3, "algorithm": "SHA1", "private_exponent": int("bfa20faf4b68d", 16), "msg": b"2a67c70ff14f9b34ddb42e6f89d5971057a0da980fc9ae70c81a84da0" b"c0ac42737", "s": b"2b91c6ae2b3c46ff18d5b7abe239634cb752d0acb53eea0ccd8ea848303" b"6a50e8faf", "saltval": b"11223344555432167890", "fail": False, }, ] def test_load_rsa_nist_pss_verification_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.0 # "SigVer PKCS#1 RSASSA-PSS" information # Mod sizes selected: 1024 1536 2048 3072 4096 # SHA Algorithm selected:SHA1 SHA224 SHA256 SHA384 SHA512 # Salt len: 10 # Generated on Wed Mar 02 00:25:22 2011 [mod = 1024] n = be499b5e7f06c83fa0293e31465c8eb6b5 p = e7a80c5d211c06acb900939495f26d365f q = d248aa248000f720258742da67b711940c SHAAlg = SHA1 e = 00000000000000011 d = c8e26a88239672cf49b3422a07c4d834ba Msg = 6b9cfac0ba1c7890b13e381ce752195c S = 562d87b5781c01d166fef3972669a0495c SaltVal = 11223344555432167890 Result = F (3 - Signature changed ) SHAAlg = SHA384 e = 000003 d = 0d0f17362bdad181db4e1fe03e8de1a320 Msg = 2a67c70ff14f9b34ddb42e6f89d59710 S = 2b91c6ae2b3c46ff18d5b7abe239634cb7 SaltVal = 11223344555432167890 Result = P """ ).splitlines() vectors = load_rsa_nist_vectors(vector_data) assert vectors == [ { "modulus": int("be499b5e7f06c83fa0293e31465c8eb6b5", 16), "p": int("e7a80c5d211c06acb900939495f26d365f", 16), "q": int("d248aa248000f720258742da67b711940c", 16), "public_exponent": 17, "algorithm": "SHA1", "private_exponent": int("c8e26a88239672cf49b3422a07c4d834ba", 16), "msg": b"6b9cfac0ba1c7890b13e381ce752195c", "s": b"562d87b5781c01d166fef3972669a0495c", "saltval": b"11223344555432167890", "salt_length": 10, "fail": True, }, { "modulus": int("be499b5e7f06c83fa0293e31465c8eb6b5", 16), "p": int("e7a80c5d211c06acb900939495f26d365f", 16), "q": int("d248aa248000f720258742da67b711940c", 16), "public_exponent": 3, "algorithm": "SHA384", "private_exponent": int("0d0f17362bdad181db4e1fe03e8de1a320", 16), "msg": b"2a67c70ff14f9b34ddb42e6f89d59710", "s": b"2b91c6ae2b3c46ff18d5b7abe239634cb7", "saltval": b"11223344555432167890", "salt_length": 10, "fail": False, }, ] def test_load_fips_dsa_key_pair_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.1 # "KeyPair" information # Mod sizes selected: L=1024, N=160:: L=2048, N=224 :: L=2048, N=256 :: L =3072, N=256 # Generated on Wed May 04 08:50:52 2011 [mod = L=1024, N=160] P = d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725ef341eabb47cf8a7a\ 8a41e792a156b7ce97206c4f9c5ce6fc5ae7912102b6b502e59050b5b21ce263dddb2044b65223\ 6f4d42ab4b5d6aa73189cef1ace778d7845a5c1c1c7147123188f8dc551054ee162b634d60f097\ f719076640e20980a0093113a8bd73 Q = 96c5390a8b612c0e422bb2b0ea194a3ec935a281 G = 06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce4991d2b862259d6b\ 4548a6495b195aa0e0b6137ca37eb23b94074d3c3d300042bdf15762812b6333ef7b07ceba7860\ 7610fcc9ee68491dbc1e34cd12615474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3\ f4fd9f93cd6f4f17fc076341a7e7d9 X = 8185fee9cc7c0e91fd85503274f1cd5a3fd15a49 Y = 6f26d98d41de7d871b6381851c9d91fa03942092ab6097e76422070edb71db44ff5682\ 80fdb1709f8fc3feab39f1f824adaeb2a298088156ac31af1aa04bf54f475bdcfdcf2f8a2dd973\ e922d83e76f016558617603129b21c70bf7d0e5dc9e68fe332e295b65876eb9a12fe6fca9f1a1c\ e80204646bf99b5771d249a6fea627 X = 85322d6ea73083064376099ca2f65f56e8522d9b Y = 21f8690f717c9f4dcb8f4b6971de2f15b9231fcf41b7eeb997d781f240bfdddfd2090d\ 22083c26cca39bf37c9caf1ec89518ea64845a50d747b49131ffff6a2fd11ea7bacbb93c7d0513\ 7383a06365af82225dd3713ca5a45006316f53bd12b0e260d5f79795e5a4c9f353f12867a1d320\ 2394673ada8563b71555e53f415254 [mod = L=2048, N=256] P = ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace5e9c41434c9cf0a8e9\ 498acb0f4663c08b4484eace845f6fb17dac62c98e706af0fc74e4da1c6c2b3fbf5a1d58ff82fc\ 1a66f3e8b12252c40278fff9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d244e54561\ b0dca39b301de8c49da9fb23df33c6182e3f983208c560fb5119fbf78ebe3e6564ee235c6a15cb\ b9ac247baba5a423bc6582a1a9d8a2b4f0e9e3d9dbac122f750dd754325135257488b1f6ecabf2\ 1bff2947fe0d3b2cb7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a908c36e95e60\ bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac5aa66ef7 Q = 8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b18f507192c19d G = e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b1913413d344d1d\ 8d84a333839d88eee431521f6e357c16e6a93be111a98076739cd401bab3b9d565bf4fb99e9d18\ 5b1e14d61c93700133f908bae03e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d5\ 551b2fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c61568f78d0706b10a26f23b\ 4f197c322b825002284a0aca91807bba98ece912b80e10cdf180cf99a35f210c1655fbfdd74f13\ b1b5046591f8403873d12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed273b14\ 6ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302 X = 405772da6e90d809e77d5de796562a2dd4dfd10ef00a83a3aba6bd818a0348a1 Y = 6b32e31ab9031dc4dd0b5039a78d07826687ab087ae6de4736f5b0434e1253092e8a0b\ 231f9c87f3fc8a4cb5634eb194bf1b638b7a7889620ce6711567e36aa36cda4604cfaa601a4591\ 8371d4ccf68d8b10a50a0460eb1dc0fff62ef5e6ee4d473e18ea4a66c196fb7e677a49b48241a0\ b4a97128eff30fa437050501a584f8771e7280d26d5af30784039159c11ebfea10b692fd0a5821\ 5eeb18bff117e13f08db792ed4151a218e4bed8dddfb0793225bd1e9773505166f4bd8cedbb286\ ea28232972da7bae836ba97329ba6b0a36508e50a52a7675e476d4d4137eae13f22a9d2fefde70\ 8ba8f34bf336c6e76331761e4b0617633fe7ec3f23672fb19d27 X = 0e0b95e31fda3f888059c46c3002ef8f2d6be112d0209aeb9e9545da67aeea80 Y = 778082b77ddba6f56597cc74c3a612abf2ddbd85cc81430c99ab843c1f630b9db01399\ 65f563978164f9bf3a8397256be714625cd41cd7fa0067d94ea66d7e073f7125af692ad01371d4\ a17f4550590378f2b074030c20e36911598a1018772f61be3b24de4be5a388ccc09e15a92819c3\ 1dec50de9fde105b49eaa097b9d13d9219eeb33b628facfd1c78a7159c8430d0647c506e7e3de7\ 4763cb351eada72c00bef3c9641881e6254870c1e6599f8ca2f1bbb74f39a905e3a34e4544168e\ 6e50c9e3305fd09cab6ed4aff6fda6e0d5bf375c81ac9054406d9193b003c89272f1bd83d48250\ 134b65c77c2b6332d38d34d9016f0e8975536ad6c348a1faedb0 [mod = L=3072, N=256] P = f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d5828c352f593a9a7\ 87760ce34b789879941f2f01f02319f6ae0b756f1a842ba54c85612ed632ee2d79ef17f06b77c6\ 41b7b080aff52a03fc2462e80abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a8\ 4ec389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea2651b372f8b58a0a65554d\ b2561d706a63685000ac576b7e4562e262a14285a9c6370b290e4eb7757527d80b6c0fd5df831d\ 36f3d1d35f12ab060548de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba9844\ d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1d54f90cce1a491a9c74fea88f\ 7e7230b047d16b5a6027881d6f154818f06e513faf40c8814630e4e254f17a47bfe9cb519b9828\ 9935bf17673ae4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f7476cd715eaab\ b7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c1136f303f4b4d25ad5b692229957 Q = d3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210f6169041653b G = ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978db2104d7394b493c183\ 32c64cec906a71c3778bd93341165dee8e6cd4ca6f13afff531191194ada55ecf01ff94d6cf7c4\ 768b82dd29cd131aaf202aefd40e564375285c01f3220af4d70b96f1395420d778228f1461f5d0\ b8e47357e87b1fe3286223b553e3fc9928f16ae3067ded6721bedf1d1a01bfd22b9ae85fce7782\ 0d88cdf50a6bde20668ad77a707d1c60fcc5d51c9de488610d0285eb8ff721ff141f93a9fb23c1\ d1f7654c07c46e58836d1652828f71057b8aff0b0778ef2ca934ea9d0f37daddade2d823a4d8e3\ 62721082e279d003b575ee59fd050d105dfd71cd63154efe431a0869178d9811f4f231dc5dcf3b\ 0ec0f2b0f9896c32ec6c7ee7d60aa97109e09224907328d4e6acd10117e45774406c4c947da802\ 0649c3168f690e0bd6e91ac67074d1d436b58ae374523deaf6c93c1e6920db4a080b744804bb07\ 3cecfe83fa9398cf150afa286dc7eb7949750cf5001ce104e9187f7e16859afa8fd0d775ae X = b2764c46113983777d3e7e97589f1303806d14ad9f2f1ef033097de954b17706 Y = 814824e435e1e6f38daa239aad6dad21033afce6a3ebd35c1359348a0f2418871968c2\ babfc2baf47742148828f8612183178f126504da73566b6bab33ba1f124c15aa461555c2451d86\ c94ee21c3e3fc24c55527e01b1f03adcdd8ec5cb08082803a7b6a829c3e99eeb332a2cf5c035b0\ ce0078d3d414d31fa47e9726be2989b8d06da2e6cd363f5a7d1515e3f4925e0b32adeae3025cc5\ a996f6fd27494ea408763de48f3bb39f6a06514b019899b312ec570851637b8865cff3a52bf5d5\ 4ad5a19e6e400a2d33251055d0a440b50d53f4791391dc754ad02b9eab74c46b4903f9d76f8243\ 39914db108057af7cde657d41766a99991ac8787694f4185d6f91d7627048f827b405ec67bf2fe\ 56141c4c581d8c317333624e073e5879a82437cb0c7b435c0ce434e15965db1315d64895991e6b\ be7dac040c42052408bbc53423fd31098248a58f8a67da3a39895cd0cc927515d044c1e3cb6a32\ 59c3d0da354cce89ea3552c59609db10ee989986527436af21d9485ddf25f90f7dff6d2bae X = 52e3e040efb30e1befd909a0bdbcfd140d005b1bff094af97186080262f1904d Y = a5ae6e8f9b7a68ab0516dad4d7b7d002126f811d5a52e3d35c6d387fcb43fd19bf7792\ 362f9c98f8348aa058bb62376685f3d0c366c520d697fcd8416947151d4bbb6f32b53528a01647\ 9e99d2cd48d1fc679027c15f0042f207984efe05c1796bca8eba678dfdd00b80418e3ea840557e\ 73b09e003882f9a68edba3431d351d1ca07a8150b018fdbdf6c2f1ab475792a3ccaa6594472a45\ f8dc777b60bf67de3e0f65c20d11b7d59faedf83fbce52617f500d9e514947c455274c6e900464\ 767fb56599b81344cf6d12c25cb2b7d038d7b166b6cf30534811c15d0e8ab880a2ac06786ae2dd\ de61329a78d526f65245380ce877e979c5b50de66c9c30d66382c8f254653d25a1eb1d3a4897d7\ 623399b473ce712a2184cf2da1861706c41466806aefe41b497db82aca6c31c8f4aa68c17d1d9e\ 380b57998917655783ec96e5234a131f7299398d36f1f5f84297a55ff292f1f060958c358fed34\ 6db2de45127ca728a9417b2c54203e33e53b9a061d924395b09afab8daf3e8dd7eedcec3ac """ ).splitlines() expected = [ { "g": int( "06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce499" "1d2b862259d6b4548a6495b195aa0e0b6137ca37eb23b94074d3c3d3000" "42bdf15762812b6333ef7b07ceba78607610fcc9ee68491dbc1e34cd12" "615474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3f" "4fd9f93cd6f4f17fc076341a7e7d9", 16, ), "p": int( "d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725e" "f341eabb47cf8a7a8a41e792a156b7ce97206c4f9c5ce6fc5ae791210" "2b6b502e59050b5b21ce263dddb2044b652236f4d42ab4b5d6aa73189c" "ef1ace778d7845a5c1c1c7147123188f8dc551054ee162b634d60f097f7" "19076640e20980a0093113a8bd73", 16, ), "q": int("96c5390a8b612c0e422bb2b0ea194a3ec935a281", 16), "x": int("8185fee9cc7c0e91fd85503274f1cd5a3fd15a49", 16), "y": int( "6f26d98d41de7d871b6381851c9d91fa03942092ab6097e76422" "070edb71db44ff568280fdb1709f8fc3feab39f1f824adaeb2a29808815" "6ac31af1aa04bf54f475bdcfdcf2f8a2dd973e922d83e76f01655861760" "3129b21c70bf7d0e5dc9e68fe332e295b65876eb9a12fe6fca9f1a1ce80" "204646bf99b5771d249a6fea627", 16, ), }, { "g": int( "06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce4991d" "2b862259d6b4548a6495b195aa0e0b6137ca37eb23b94074d3c3d30004" "2bdf15762812b6333ef7b07ceba78607610fcc9ee68491dbc1e34cd126" "15474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3f4fd9" "f93cd6f4f17fc076341a7e7d9", 16, ), "p": int( "d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725ef341e" "abb47cf8a7a8a41e792a156b7ce97206c4f9c5ce6fc5ae7912102b6b50" "2e59050b5b21ce263dddb2044b652236f4d42ab4b5d6aa73189cef1a" "ce778d7845a5c1c1c7147123188f8dc551054ee162b634d6" "0f097f719076640e20980a0093113a8bd73", 16, ), "q": int("96c5390a8b612c0e422bb2b0ea194a3ec935a281", 16), "x": int("85322d6ea73083064376099ca2f65f56e8522d9b", 16), "y": int( "21f8690f717c9f4dcb8f4b6971de2f15b9231fcf41b7eeb997d781f240" "bfdddfd2090d22083c26cca39bf37c9caf1ec89518ea64845a50d747b49" "131ffff6a2fd11ea7bacbb93c7d05137383a06365af82225dd3713c" "a5a45006316f53bd12b0e260d5f79795e5a4c9f353f12867a1d3" "202394673ada8563b71555e53f415254", 16, ), }, { "g": int( "e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b191" "3413d344d1d8d84a333839d88eee431521f6e357c16e6a93be111a9807" "6739cd401bab3b9d565bf4fb99e9d185b1e14d61c93700133f908bae0" "3e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d5551b2" "fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c61568f78" "d0706b10a26f23b4f197c322b825002284a0aca91807bba98ece912" "b80e10cdf180cf99a35f210c1655fbfdd74f13b1b5046591f8403873d" "12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed273b1" "46ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302", 16, ), "p": int( "ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace" "5e9c41434c9cf0a8e9498acb0f4663c08b4484eace845f6fb17d" "ac62c98e706af0fc74e4da1c6c2b3fbf5a1d58ff82fc1a66f3e8b122" "52c40278fff9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d2" "44e54561b0dca39b301de8c49da9fb23df33c6182e3f983208c560fb5" "119fbf78ebe3e6564ee235c6a15cbb9ac247baba5a423bc6582a1a9d8a" "2b4f0e9e3d9dbac122f750dd754325135257488b1f6ecabf21bff2947" "fe0d3b2cb7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a" "908c36e95e60bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac" "5aa66ef7", 16, ), "q": int( "8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b1" "8f507192c19d", 16, ), "x": int( "405772da6e90d809e77d5de796562a2dd4dfd10ef00a83a3aba6" "bd818a0348a1", 16, ), "y": int( "6b32e31ab9031dc4dd0b5039a78d07826687ab087ae6de4736f5" "b0434e1253092e8a0b231f9c87f3fc8a4cb5634eb194bf1b638" "b7a7889620ce6711567e36aa36cda4604cfaa601a45918371d" "4ccf68d8b10a50a0460eb1dc0fff62ef5e6ee4d473e18ea4a6" "6c196fb7e677a49b48241a0b4a97128eff30fa437050501a584" "f8771e7280d26d5af30784039159c11ebfea10b692fd0a58215ee" "b18bff117e13f08db792ed4151a218e4bed8dddfb0793225bd1e97" "73505166f4bd8cedbb286ea28232972da7bae836ba97329ba6b0a36508" "e50a52a7675e476d4d4137eae13f22a9d2fefde708ba8f34bf336c6e7" "6331761e4b0617633fe7ec3f23672fb19d27", 16, ), }, { "g": int( "e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b191" "3413d344d1d8d84a333839d88eee431521f6e357c16e6a93be111a9807" "6739cd401bab3b9d565bf4fb99e9d185b1e14d61c93700133f908bae0" "3e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d5551b2" "fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c61568f78" "d0706b10a26f23b4f197c322b825002284a0aca91807bba98ece912" "b80e10cdf180cf99a35f210c1655fbfdd74f13b1b5046591f8403873d" "12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed273b1" "46ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302", 16, ), "p": int( "ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace" "5e9c41434c9cf0a8e9498acb0f4663c08b4484eace845f6fb17d" "ac62c98e706af0fc74e4da1c6c2b3fbf5a1d58ff82fc1a66f3e8b122" "52c40278fff9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d2" "44e54561b0dca39b301de8c49da9fb23df33c6182e3f983208c560fb5" "119fbf78ebe3e6564ee235c6a15cbb9ac247baba5a423bc6582a1a9d8a" "2b4f0e9e3d9dbac122f750dd754325135257488b1f6ecabf21bff2947" "fe0d3b2cb7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a" "908c36e95e60bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac" "5aa66ef7", 16, ), "q": int( "8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b1" "8f507192c19d", 16, ), "x": int( "0e0b95e31fda3f888059c46c3002ef8f2d6be112d0209aeb9e95" "45da67aeea80", 16, ), "y": int( "778082b77ddba6f56597cc74c3a612abf2ddbd85cc81430c99ab" "843c1f630b9db0139965f563978164f9bf3a8397256be714625" "cd41cd7fa0067d94ea66d7e073f7125af692ad01371d4a17f45" "50590378f2b074030c20e36911598a1018772f61be3b24de4be" "5a388ccc09e15a92819c31dec50de9fde105b49eaa097b9d13d" "9219eeb33b628facfd1c78a7159c8430d0647c506e7e3de74763c" "b351eada72c00bef3c9641881e6254870c1e6599f8ca2f1bbb74f" "39a905e3a34e4544168e6e50c9e3305fd09cab6ed4aff6fda6e0d" "5bf375c81ac9054406d9193b003c89272f1bd83d48250134b65c77" "c2b6332d38d34d9016f0e8975536ad6c348a1faedb0", 16, ), }, { "g": int( "ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978d" "b2104d7394b493c18332c64cec906a71c3778bd93341165dee8" "e6cd4ca6f13afff531191194ada55ecf01ff94d6cf7c4768b82" "dd29cd131aaf202aefd40e564375285c01f3220af4d70b96f1" "395420d778228f1461f5d0b8e47357e87b1fe3286223b553e3" "fc9928f16ae3067ded6721bedf1d1a01bfd22b9ae85fce77820d88cdf" "50a6bde20668ad77a707d1c60fcc5d51c9de488610d0285eb8ff721f" "f141f93a9fb23c1d1f7654c07c46e58836d1652828f71057b8aff0b077" "8ef2ca934ea9d0f37daddade2d823a4d8e362721082e279d003b575ee" "59fd050d105dfd71cd63154efe431a0869178d9811f4f231dc5dcf3b" "0ec0f2b0f9896c32ec6c7ee7d60aa97109e09224907328d4e6acd1011" "7e45774406c4c947da8020649c3168f690e0bd6e91ac67074d1d436b" "58ae374523deaf6c93c1e6920db4a080b744804bb073cecfe83fa939" "8cf150afa286dc7eb7949750cf5001ce104e9187f7e16859afa8fd0d" "775ae", 16, ), "p": int( "f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d5828" "c352f593a9a787760ce34b789879941f2f01f02319f6ae0b756f1a842" "ba54c85612ed632ee2d79ef17f06b77c641b7b080aff52a03fc2462e8" "0abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a84ec" "389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea2651" "b372f8b58a0a65554db2561d706a63685000ac576b7e4562e262a1428" "5a9c6370b290e4eb7757527d80b6c0fd5df831d36f3d1d35f12ab0605" "48de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba9" "844d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1d" "54f90cce1a491a9c74fea88f7e7230b047d16b5a6027881d6f154818f" "06e513faf40c8814630e4e254f17a47bfe9cb519b98289935bf17673a" "e4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f7476" "cd715eaabb7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c11" "36f303f4b4d25ad5b692229957", 16, ), "q": int( "d3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210" "f6169041653b", 16, ), "x": int( "b2764c46113983777d3e7e97589f1303806d14ad9f2f1ef03309" "7de954b17706", 16, ), "y": int( "814824e435e1e6f38daa239aad6dad21033afce6a3ebd35c1359348a0f2" "418871968c2babfc2baf47742148828f8612183178f126504da73566b6" "bab33ba1f124c15aa461555c2451d86c94ee21c3e3fc24c55527e" "01b1f03adcdd8ec5cb08082803a7b6a829c3e99eeb332a2cf5c035b0c" "e0078d3d414d31fa47e9726be2989b8d06da2e6cd363f5a7d1515e3f4" "925e0b32adeae3025cc5a996f6fd27494ea408763de48f3bb39f6a06" "514b019899b312ec570851637b8865cff3a52bf5d54ad5a19e6e400" "a2d33251055d0a440b50d53f4791391dc754ad02b9eab74c46b4903" "f9d76f824339914db108057af7cde657d41766a99991ac8787694f" "4185d6f91d7627048f827b405ec67bf2fe56141c4c581d8c317333" "624e073e5879a82437cb0c7b435c0ce434e15965db1315d648959" "91e6bbe7dac040c42052408bbc53423fd31098248a58f8a67da3a" "39895cd0cc927515d044c1e3cb6a3259c3d0da354cce89ea3552c" "59609db10ee989986527436af21d9485ddf25f90f7dff6d2bae", 16, ), }, { "g": int( "ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978d" "b2104d7394b493c18332c64cec906a71c3778bd93341165dee8" "e6cd4ca6f13afff531191194ada55ecf01ff94d6cf7c4768b82" "dd29cd131aaf202aefd40e564375285c01f3220af4d70b96f1" "395420d778228f1461f5d0b8e47357e87b1fe3286223b553e3" "fc9928f16ae3067ded6721bedf1d1a01bfd22b9ae85fce77820d88cdf" "50a6bde20668ad77a707d1c60fcc5d51c9de488610d0285eb8ff721f" "f141f93a9fb23c1d1f7654c07c46e58836d1652828f71057b8aff0b077" "8ef2ca934ea9d0f37daddade2d823a4d8e362721082e279d003b575ee" "59fd050d105dfd71cd63154efe431a0869178d9811f4f231dc5dcf3b" "0ec0f2b0f9896c32ec6c7ee7d60aa97109e09224907328d4e6acd1011" "7e45774406c4c947da8020649c3168f690e0bd6e91ac67074d1d436b" "58ae374523deaf6c93c1e6920db4a080b744804bb073cecfe83fa939" "8cf150afa286dc7eb7949750cf5001ce104e9187f7e16859afa8fd0d" "775ae", 16, ), "p": int( "f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d5828" "c352f593a9a787760ce34b789879941f2f01f02319f6ae0b756f1a842" "ba54c85612ed632ee2d79ef17f06b77c641b7b080aff52a03fc2462e8" "0abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a84ec" "389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea2651" "b372f8b58a0a65554db2561d706a63685000ac576b7e4562e262a1428" "5a9c6370b290e4eb7757527d80b6c0fd5df831d36f3d1d35f12ab0605" "48de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba9" "844d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1d" "54f90cce1a491a9c74fea88f7e7230b047d16b5a6027881d6f154818f" "06e513faf40c8814630e4e254f17a47bfe9cb519b98289935bf17673a" "e4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f7476" "cd715eaabb7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c11" "36f303f4b4d25ad5b692229957", 16, ), "q": int( "d3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210" "f6169041653b", 16, ), "x": int( "52e3e040efb30e1befd909a0bdbcfd140d005b1bff094af97186" "080262f1904d", 16, ), "y": int( "a5ae6e8f9b7a68ab0516dad4d7b7d002126f811d5a52e3d35c6d" "387fcb43fd19bf7792362f9c98f8348aa058bb62376685f3d0c3" "66c520d697fcd8416947151d4bbb6f32b53528a016479e99d2cd" "48d1fc679027c15f0042f207984efe05c1796bca8eba678dfdd0" "0b80418e3ea840557e73b09e003882f9a68edba3431d351d1ca0" "7a8150b018fdbdf6c2f1ab475792a3ccaa6594472a45f8dc777b" "60bf67de3e0f65c20d11b7d59faedf83fbce52617f500d9e5149" "47c455274c6e900464767fb56599b81344cf6d12c25cb2b7d038" "d7b166b6cf30534811c15d0e8ab880a2ac06786ae2ddde61329a" "78d526f65245380ce877e979c5b50de66c9c30d66382c8f25465" "3d25a1eb1d3a4897d7623399b473ce712a2184cf2da1861706c4" "1466806aefe41b497db82aca6c31c8f4aa68c17d1d9e380b5799" "8917655783ec96e5234a131f7299398d36f1f5f84297a55ff292" "f1f060958c358fed346db2de45127ca728a9417b2c54203e33e5" "3b9a061d924395b09afab8daf3e8dd7eedcec3ac", 16, ), }, ] assert expected == load_fips_dsa_key_pair_vectors(vector_data) def test_load_fips_dsa_sig_ver_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.0 # "SigVer" information # Mod sizes selected: SHA-1 L=1024, N=160,SHA-384 L=2048, N=256 # Generated on Fri Apr 01 08:37:15 2011 [mod = L=1024, N=160, SHA-1] P = dc5bf3a88b2d99e4c95cdd7a0501cc38630d425cf5c390af3429cff1f35147b795cae\ a923f0d3577158f8a0c89dabd1962c2c453306b5d70cacfb01430aceb54e5a5fa6f93\ 40d3bd2da612fceeb76b0ec1ebfae635a56ab141b108e00dc76eefe2edd0c514c21c4\ 57457c39065dba9d0ecb7569c247172d8438ad2827b60435b Q = e956602b83d195dbe945b3ac702fc61f81571f1d G = d7eb9ca20a3c7a079606bafc4c9261ccaba303a5dc9fe9953f197dfe548c234895baa\ 77f441ee6a2d97b909cbbd26ff7b869d24cae51b5c6edb127a4b5d75cd8b46608bfa1\ 48249dffdb59807c5d7dde3fe3080ca3a2d28312142becb1fa8e24003e21c72871081\ 74b95d5bc711e1c8d9b1076784f5dc37a964a5e51390da713 Msg = 0fe1bfee500bdb76026099b1d37553f6bdfe48c82094ef98cb309dd777330bedfaa\ 2f94c823ef74ef4074b50d8706041ac0e371c7c22dcf70263b8d60e17a86c7c379c\ fda8f22469e0df9d49d59439fc99891873628fff25dda5fac5ac794e948babdde96\ 8143ba05f1128f34fdad5875edc4cd71c6c24ba2060ffbd439ce2b3 X = 1d93010c29ecfc432188942f46f19f44f0e1bb5d Y = 6240ea0647117c38fe705106d56db578f3e10130928452d4f3587881b8a2bc6873a8b\ efc3237f20914e2a91c7f07a928ee22adeed23d74ab7f82ea11f70497e578f7a9b4cb\ d6f10226222b0b4da2ea1e49813d6bb9882fbf675c0846bb80cc891857b89b0ef1beb\ 6cce3378a9aab5d66ad4cb9277cf447dfe1e64434749432fb R = b5af307867fb8b54390013cc67020ddf1f2c0b81 S = 620d3b22ab5031440c3e35eab6f481298f9e9f08 Result = P Msg = 97d50898025d2f9ba633866e968ca75e969d394edba6517204cb3dd537c2ba38778\ a2dc9dbc685a915e5676fcd43bc3726bc59ce3d7a9fae35565082a069c139fa37c9\ 0d922b126933db3fa6c5ef6b1edf00d174a51887bb76909c6a94fe994ecc7b7fc8f\ 26113b17f30f9d01693df99a125b4f17e184331c6b6e8ca00f54f3a X = 350e13534692a7e0c4b7d58836046c436fbb2322 Y = 69974de550fe6bd3099150faea1623ad3fb6d9bf23a07215093f319725ad0877accff\ d291b6da18eb0cbe51676ceb0977504eb97c27c0b191883f72fb2710a9fbd8bcf13be\ 0bf854410b32f42b33ec89d3cc1cf892bcd536c4195ca9ada302ad600c3408739935d\ 77dc247529ca47f844cc86f5016a2fe962c6e20ca7c4d4e8f R = b5d05faa7005764e8dae0327c5bf1972ff7681b9 S = 18ea15bd9f00475b25204cbc23f8c23e01588015 Result = F (3 - R changed ) [mod = L=2048, N=256, SHA-384] P = e7c1c86125db9ef417da1ced7ea0861bdad629216a3f3c745df42a46b989e59f4d984\ 25ee3c932fa3c2b6f637bdb6545bec526faa037e11f5578a4363b9fca5eba60d6a9cb\ aa2befd04141d989c7356285132c2eaf74f2d868521cdc0a17ae9a2546ef863027d3f\ 8cc7949631fd0e2971417a912c8b8c5c989730db6ea6e8baee0e667850429038093c8\ 51ccb6fb173bb081e0efe0bd7450e0946888f89f75e443ab93ef2da293a01622cf43c\ 6dd79625d41ba8f9ef7e3086ab39134283d8e96c89249488120fd061e4a87d34af410\ 69c0b4fd3934c31b589cbe85b68b912718d5dab859fda7082511fad1d152044905005\ 546e19b14aa96585a55269bf2b831 Q = 8e056ec9d4b7acb580087a6ed9ba3478711bb025d5b8d9c731ef9b38bd43db2f G = dc2bfb9776786ad310c8b0cdcbba3062402613c67e6959a8d8d1b05aab636528b7b1f\ e9cd33765f853d6dbe13d09f2681f8c7b1ed7886aaed70c7bd76dbe858ffb8bd86235\ ddf759244678f428c6519af593dc94eeadbd9852ba2b3d61664e8d58c29d2039af3c3\ d6d16f90988f6a8c824569f3d48050e30896a9e17cd0232ef01ab8790008f6973b84c\ 763a72f4ae8b485abfb7e8efeb86808fa2b281d3e5d65d28f5992a34c077c5aa8026c\ b2fbc34a45f7e9bd216b10e6f12ecb172e9a6eb8f2e91316905b6add1fd22e83bc2f0\ 89f1d5e6a6e6707c18ff55ddcb7954e8bceaf0efc4e8314910c03b0e51175f344faaf\ ee476a373ac95743cec712b72cf2e Msg = 6cd6ccfd66bcd832189c5f0c77994210e3bf2c43416f0fe77c4e92f31c5369538dc\ 2c003f146c5ac79df43194ccf3c44d470d9f1083bd15b99b5bcf88c32d8a9021f09\ ea2288d7b3bf345a12aef3949c1e121b9fb371a67c2d1377364206ac839dd784835\ 61426bda0303f285aa12e9c45d3cdfc6beae3549703b187deeb3296 X = 56c897b5938ad5b3d437d7e4826da586a6b3be15e893fa1aaa946f20a028b6b3 Y = 38ad44489e1a5778b9689f4dcf40e2acf23840fb954e987d6e8cb629106328ac64e1f\ 3c3eba48b21176ad4afe3b733bead382ee1597e1b83e4b43424f2daaba04e5bd79e14\ 36693ac2bddb79a298f026e57e200a252efd1e848a4a2e90be6e78f5242b468b9c0c6\ d2615047a5a40b9ae7e57a519114db55bf3bed65e580f894b094630ca9c217f6accd0\ 91e72d2f22da620044ff372d7273f9445017fad492959e59600b7494dbe766a03e401\ 25d4e6747c76f68a5b0cdc0e7d7cee12d08c6fb7d0fb049e420a33405075ed4463296\ 345ca695fb7feab7c1b5333ae519fcd4bb6a043f4555378969114743d4face96cad31\ c0e0089da4e3f61b6d7dabc088ab7 R = 3b85b17be240ed658beb3652c9d93e8e9eea160d35ee2459614305802963374e S = 726800a5174a53b56dce86064109c0273cd11fcfa3c92c5cd6aa910260c0e3c7 Result = F (1 - Message changed) Msg = 3ad6b0884f358dea09c31a9abc40c45a6000611fc2b907b30eac00413fd2819de70\ 15488a411609d46c499b8f7afa1b78b352ac7f8535bd805b8ff2a5eae557098c668\ f7ccd73af886d6823a6d456c29931ee864ed46d767382785728c2a83fcff5271007\ d2a67d06fa205fd7b9d1a42ea5d6dc76e5e18a9eb148cd1e8b262ae X = 2faf566a9f057960f1b50c69508f483d9966d6e35743591f3a677a9dc40e1555 Y = 926425d617babe87c442b03903e32ba5bbf0cd9d602b59c4df791a4d64a6d4333ca0c\ 0d370552539197d327dcd1bbf8c454f24b03fc7805f862db34c7b066ddfddbb11dbd0\ 10b27123062d028fe041cb56a2e77488348ae0ab6705d87aac4d4e9e6600e9e706326\ d9979982cffa839beb9eacc3963bcca455a507e80c1c37ad4e765b2c9c0477a075e9b\ c584feacdf3a35a9391d4711f14e197c54022282bfed9a191213d64127f17a9c5affe\ c26e0c71f15d3a5b16098fec118c45bf8bb2f3b1560df0949254c1c0aeb0a16d5a95a\ 40fab8521fbe8ea77c51169b587cc3360e5733e6a23b9fded8c40724ea1f9e93614b3\ a6c9b4f8dbbe915b794497227ba62 R = 343ea0a9e66277380f604d5880fca686bffab69ca97bfba015a102a7e23dce0e S = 6258488c770e0f5ad7b9da8bade5023fc0d17c6ec517bd08d53e6dc01ac5c2b3 Result = P """ ).splitlines() expected = [ { "p": int( "dc5bf3a88b2d99e4c95cdd7a0501cc38630d425cf5c390af3429cff1" "f35147b795caea923f0d3577158f8a0c89dabd1962c2c453306b5d70" "cacfb01430aceb54e5a5fa6f9340d3bd2da612fceeb76b0ec1ebfae6" "35a56ab141b108e00dc76eefe2edd0c514c21c457457c39065dba9d0" "ecb7569c247172d8438ad2827b60435b", 16, ), "q": int("e956602b83d195dbe945b3ac702fc61f81571f1d", 16), "g": int( "d7eb9ca20a3c7a079606bafc4c9261ccaba303a5dc9fe9953f197dfe" "548c234895baa77f441ee6a2d97b909cbbd26ff7b869d24cae51b5c6" "edb127a4b5d75cd8b46608bfa148249dffdb59807c5d7dde3fe3080c" "a3a2d28312142becb1fa8e24003e21c7287108174b95d5bc711e1c8d" "9b1076784f5dc37a964a5e51390da713", 16, ), "digest_algorithm": "SHA-1", "msg": binascii.unhexlify( b"0fe1bfee500bdb76026099b1d37553f6bdfe48c82094ef98cb309dd77733" b"0bedfaa2f94c823ef74ef4074b50d8706041ac0e371c7c22dcf70263b8d6" b"0e17a86c7c379cfda8f22469e0df9d49d59439fc99891873628fff25dda5" b"fac5ac794e948babdde968143ba05f1128f34fdad5875edc4cd71c6c24ba" b"2060ffbd439ce2b3" ), "x": int("1d93010c29ecfc432188942f46f19f44f0e1bb5d", 16), "y": int( "6240ea0647117c38fe705106d56db578f3e10130928452d4f3587881" "b8a2bc6873a8befc3237f20914e2a91c7f07a928ee22adeed23d74ab" "7f82ea11f70497e578f7a9b4cbd6f10226222b0b4da2ea1e49813d6b" "b9882fbf675c0846bb80cc891857b89b0ef1beb6cce3378a9aab5d66" "ad4cb9277cf447dfe1e64434749432fb", 16, ), "r": int("b5af307867fb8b54390013cc67020ddf1f2c0b81", 16), "s": int("620d3b22ab5031440c3e35eab6f481298f9e9f08", 16), "result": "P", }, { "p": int( "dc5bf3a88b2d99e4c95cdd7a0501cc38630d425cf5c390af3429cff1" "f35147b795caea923f0d3577158f8a0c89dabd1962c2c453306b5d70" "cacfb01430aceb54e5a5fa6f9340d3bd2da612fceeb76b0ec1ebfae6" "35a56ab141b108e00dc76eefe2edd0c514c21c457457c39065dba9d0" "ecb7569c247172d8438ad2827b60435b", 16, ), "q": int("e956602b83d195dbe945b3ac702fc61f81571f1d", 16), "g": int( "d7eb9ca20a3c7a079606bafc4c9261ccaba303a5dc9fe9953f197dfe" "548c234895baa77f441ee6a2d97b909cbbd26ff7b869d24cae51b5c6" "edb127a4b5d75cd8b46608bfa148249dffdb59807c5d7dde3fe3080c" "a3a2d28312142becb1fa8e24003e21c7287108174b95d5bc711e1c8d" "9b1076784f5dc37a964a5e51390da713", 16, ), "digest_algorithm": "SHA-1", "msg": binascii.unhexlify( b"97d50898025d2f9ba633866e968ca75e969d394edba6517204cb3dd537c2" b"ba38778a2dc9dbc685a915e5676fcd43bc3726bc59ce3d7a9fae35565082" b"a069c139fa37c90d922b126933db3fa6c5ef6b1edf00d174a51887bb7690" b"9c6a94fe994ecc7b7fc8f26113b17f30f9d01693df99a125b4f17e184331" b"c6b6e8ca00f54f3a" ), "x": int("350e13534692a7e0c4b7d58836046c436fbb2322", 16), "y": int( "69974de550fe6bd3099150faea1623ad3fb6d9bf23a07215093f3197" "25ad0877accffd291b6da18eb0cbe51676ceb0977504eb97c27c0b19" "1883f72fb2710a9fbd8bcf13be0bf854410b32f42b33ec89d3cc1cf8" "92bcd536c4195ca9ada302ad600c3408739935d77dc247529ca47f84" "4cc86f5016a2fe962c6e20ca7c4d4e8f", 16, ), "r": int("b5d05faa7005764e8dae0327c5bf1972ff7681b9", 16), "s": int("18ea15bd9f00475b25204cbc23f8c23e01588015", 16), "result": "F", }, { "p": int( "e7c1c86125db9ef417da1ced7ea0861bdad629216a3f3c745df42a4" "6b989e59f4d98425ee3c932fa3c2b6f637bdb6545bec526faa037e1" "1f5578a4363b9fca5eba60d6a9cbaa2befd04141d989c7356285132" "c2eaf74f2d868521cdc0a17ae9a2546ef863027d3f8cc7949631fd0" "e2971417a912c8b8c5c989730db6ea6e8baee0e667850429038093c" "851ccb6fb173bb081e0efe0bd7450e0946888f89f75e443ab93ef2d" "a293a01622cf43c6dd79625d41ba8f9ef7e3086ab39134283d8e96c" "89249488120fd061e4a87d34af41069c0b4fd3934c31b589cbe85b6" "8b912718d5dab859fda7082511fad1d152044905005546e19b14aa9" "6585a55269bf2b831", 16, ), "q": int( "8e056ec9d4b7acb580087a6ed9ba3478711bb025d5b8d9c731ef9b3" "8bd43db2f", 16, ), "g": int( "dc2bfb9776786ad310c8b0cdcbba3062402613c67e6959a8d8d1b05" "aab636528b7b1fe9cd33765f853d6dbe13d09f2681f8c7b1ed7886a" "aed70c7bd76dbe858ffb8bd86235ddf759244678f428c6519af593d" "c94eeadbd9852ba2b3d61664e8d58c29d2039af3c3d6d16f90988f6" "a8c824569f3d48050e30896a9e17cd0232ef01ab8790008f6973b84" "c763a72f4ae8b485abfb7e8efeb86808fa2b281d3e5d65d28f5992a" "34c077c5aa8026cb2fbc34a45f7e9bd216b10e6f12ecb172e9a6eb8" "f2e91316905b6add1fd22e83bc2f089f1d5e6a6e6707c18ff55ddcb" "7954e8bceaf0efc4e8314910c03b0e51175f344faafee476a373ac9" "5743cec712b72cf2e", 16, ), "digest_algorithm": "SHA-384", "msg": binascii.unhexlify( b"6cd6ccfd66bcd832189c5f0c77994210e3bf2c43416f0fe77c4e92f31c5" b"369538dc2c003f146c5ac79df43194ccf3c44d470d9f1083bd15b99b5bc" b"f88c32d8a9021f09ea2288d7b3bf345a12aef3949c1e121b9fb371a67c2" b"d1377364206ac839dd78483561426bda0303f285aa12e9c45d3cdfc6bea" b"e3549703b187deeb3296" ), "x": int( "56c897b5938ad5b3d437d7e4826da586a6b3be15e893fa1aaa946f2" "0a028b6b3", 16, ), "y": int( "38ad44489e1a5778b9689f4dcf40e2acf23840fb954e987d6e8cb62" "9106328ac64e1f3c3eba48b21176ad4afe3b733bead382ee1597e1b" "83e4b43424f2daaba04e5bd79e1436693ac2bddb79a298f026e57e2" "00a252efd1e848a4a2e90be6e78f5242b468b9c0c6d2615047a5a40" "b9ae7e57a519114db55bf3bed65e580f894b094630ca9c217f6accd" "091e72d2f22da620044ff372d7273f9445017fad492959e59600b74" "94dbe766a03e40125d4e6747c76f68a5b0cdc0e7d7cee12d08c6fb7" "d0fb049e420a33405075ed4463296345ca695fb7feab7c1b5333ae5" "19fcd4bb6a043f4555378969114743d4face96cad31c0e0089da4e3" "f61b6d7dabc088ab7", 16, ), "r": int( "3b85b17be240ed658beb3652c9d93e8e9eea160d35ee24596143058" "02963374e", 16, ), "s": int( "726800a5174a53b56dce86064109c0273cd11fcfa3c92c5cd6aa910" "260c0e3c7", 16, ), "result": "F", }, { "p": int( "e7c1c86125db9ef417da1ced7ea0861bdad629216a3f3c745df42a4" "6b989e59f4d98425ee3c932fa3c2b6f637bdb6545bec526faa037e1" "1f5578a4363b9fca5eba60d6a9cbaa2befd04141d989c7356285132" "c2eaf74f2d868521cdc0a17ae9a2546ef863027d3f8cc7949631fd0" "e2971417a912c8b8c5c989730db6ea6e8baee0e667850429038093c" "851ccb6fb173bb081e0efe0bd7450e0946888f89f75e443ab93ef2d" "a293a01622cf43c6dd79625d41ba8f9ef7e3086ab39134283d8e96c" "89249488120fd061e4a87d34af41069c0b4fd3934c31b589cbe85b6" "8b912718d5dab859fda7082511fad1d152044905005546e19b14aa9" "6585a55269bf2b831", 16, ), "q": int( "8e056ec9d4b7acb580087a6ed9ba3478711bb025d5b8d9c731ef9b3" "8bd43db2f", 16, ), "g": int( "dc2bfb9776786ad310c8b0cdcbba3062402613c67e6959a8d8d1b05" "aab636528b7b1fe9cd33765f853d6dbe13d09f2681f8c7b1ed7886a" "aed70c7bd76dbe858ffb8bd86235ddf759244678f428c6519af593d" "c94eeadbd9852ba2b3d61664e8d58c29d2039af3c3d6d16f90988f6" "a8c824569f3d48050e30896a9e17cd0232ef01ab8790008f6973b84" "c763a72f4ae8b485abfb7e8efeb86808fa2b281d3e5d65d28f5992a" "34c077c5aa8026cb2fbc34a45f7e9bd216b10e6f12ecb172e9a6eb8" "f2e91316905b6add1fd22e83bc2f089f1d5e6a6e6707c18ff55ddcb" "7954e8bceaf0efc4e8314910c03b0e51175f344faafee476a373ac9" "5743cec712b72cf2e", 16, ), "digest_algorithm": "SHA-384", "msg": binascii.unhexlify( b"3ad6b0884f358dea09c31a9abc40c45a6000611fc2b907b30eac00413fd" b"2819de7015488a411609d46c499b8f7afa1b78b352ac7f8535bd805b8ff" b"2a5eae557098c668f7ccd73af886d6823a6d456c29931ee864ed46d7673" b"82785728c2a83fcff5271007d2a67d06fa205fd7b9d1a42ea5d6dc76e5e" b"18a9eb148cd1e8b262ae" ), "x": int( "2faf566a9f057960f1b50c69508f483d9966d6e35743591f3a677a9" "dc40e1555", 16, ), "y": int( "926425d617babe87c442b03903e32ba5bbf0cd9d602b59c4df791a4d" "64a6d4333ca0c0d370552539197d327dcd1bbf8c454f24b03fc7805f" "862db34c7b066ddfddbb11dbd010b27123062d028fe041cb56a2e774" "88348ae0ab6705d87aac4d4e9e6600e9e706326d9979982cffa839be" "b9eacc3963bcca455a507e80c1c37ad4e765b2c9c0477a075e9bc584" "feacdf3a35a9391d4711f14e197c54022282bfed9a191213d64127f1" "7a9c5affec26e0c71f15d3a5b16098fec118c45bf8bb2f3b1560df09" "49254c1c0aeb0a16d5a95a40fab8521fbe8ea77c51169b587cc3360e" "5733e6a23b9fded8c40724ea1f9e93614b3a6c9b4f8dbbe915b79449" "7227ba62", 16, ), "r": int( "343ea0a9e66277380f604d5880fca686bffab69ca97bfba015a102a" "7e23dce0e", 16, ), "s": int( "6258488c770e0f5ad7b9da8bade5023fc0d17c6ec517bd08d53e6dc" "01ac5c2b3", 16, ), "result": "P", }, ] assert expected == load_fips_dsa_sig_vectors(vector_data) def test_load_fips_dsa_sig_gen_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.2 # "SigGen" information for "dsa2_values" # Mod sizes selected: SHA-1 L=1024, N=160, SHA-256 L=2048, N=256 [mod = L=1024, N=160, SHA-1] P = a8f9cd201e5e35d892f85f80e4db2599a5676a3b1d4f190330ed3256b26d0e80a0e49\ a8fffaaad2a24f472d2573241d4d6d6c7480c80b4c67bb4479c15ada7ea8424d2502fa01472e7\ 60241713dab025ae1b02e1703a1435f62ddf4ee4c1b664066eb22f2e3bf28bb70a2a76e4fd5eb\ e2d1229681b5b06439ac9c7e9d8bde283 Q = f85f0f83ac4df7ea0cdf8f469bfeeaea14156495 G = 2b3152ff6c62f14622b8f48e59f8af46883b38e79b8c74deeae9df131f8b856e3ad6c\ 8455dab87cc0da8ac973417ce4f7878557d6cdf40b35b4a0ca3eb310c6a95d68ce284ad4e25ea\ 28591611ee08b8444bd64b25f3f7c572410ddfb39cc728b9c936f85f419129869929cdb909a6a\ 3a99bbe089216368171bd0ba81de4fe33 Msg = 3b46736d559bd4e0c2c1b2553a33ad3c6cf23cac998d3d0c0e8fa4b19bca06f2f38\ 6db2dcff9dca4f40ad8f561ffc308b46c5f31a7735b5fa7e0f9e6cb512e63d7eea05538d66a75\ cd0d4234b5ccf6c1715ccaaf9cdc0a2228135f716ee9bdee7fc13ec27a03a6d11c5c5b3685f51\ 900b1337153bc6c4e8f52920c33fa37f4e7 Y = 313fd9ebca91574e1c2eebe1517c57e0c21b0209872140c5328761bbb2450b33f1b18\ b409ce9ab7c4cd8fda3391e8e34868357c199e16a6b2eba06d6749def791d79e95d3a4d09b24c\ 392ad89dbf100995ae19c01062056bb14bce005e8731efde175f95b975089bdcdaea562b32786\ d96f5a31aedf75364008ad4fffebb970b R = 50ed0e810e3f1c7cb6ac62332058448bd8b284c0 S = c6aded17216b46b7e4b6f2a97c1ad7cc3da83fde Msg = d2bcb53b044b3e2e4b61ba2f91c0995fb83a6a97525e66441a3b489d9594238bc74\ 0bdeea0f718a769c977e2de003877b5d7dc25b182ae533db33e78f2c3ff0645f2137abc137d4e\ 7d93ccf24f60b18a820bc07c7b4b5fe08b4f9e7d21b256c18f3b9d49acc4f93e2ce6f3754c780\ 7757d2e1176042612cb32fc3f4f70700e25 Y = 29bdd759aaa62d4bf16b4861c81cf42eac2e1637b9ecba512bdbc13ac12a80ae8de25\ 26b899ae5e4a231aef884197c944c732693a634d7659abc6975a773f8d3cd5a361fe2492386a3\ c09aaef12e4a7e73ad7dfc3637f7b093f2c40d6223a195c136adf2ea3fbf8704a675aa7817aa7\ ec7f9adfb2854d4e05c3ce7f76560313b R = a26c00b5750a2d27fe7435b93476b35438b4d8ab S = 61c9bfcb2938755afa7dad1d1e07c6288617bf70 [mod = L=2048, N=256, SHA-256] P = a8adb6c0b4cf9588012e5deff1a871d383e0e2a85b5e8e03d814fe13a059705e66323\ 0a377bf7323a8fa117100200bfd5adf857393b0bbd67906c081e585410e38480ead51684dac3a\ 38f7b64c9eb109f19739a4517cd7d5d6291e8af20a3fbf17336c7bf80ee718ee087e322ee4104\ 7dabefbcc34d10b66b644ddb3160a28c0639563d71993a26543eadb7718f317bf5d9577a61565\ 61b082a10029cd44012b18de6844509fe058ba87980792285f2750969fe89c2cd6498db354563\ 8d5379d125dccf64e06c1af33a6190841d223da1513333a7c9d78462abaab31b9f96d5f34445c\ eb6309f2f6d2c8dde06441e87980d303ef9a1ff007e8be2f0be06cc15f Q = e71f8567447f42e75f5ef85ca20fe557ab0343d37ed09edc3f6e68604d6b9dfb G = 5ba24de9607b8998e66ce6c4f812a314c6935842f7ab54cd82b19fa104abfb5d84579\ a623b2574b37d22ccae9b3e415e48f5c0f9bcbdff8071d63b9bb956e547af3a8df99e5d306197\ 9652ff96b765cb3ee493643544c75dbe5bb39834531952a0fb4b0378b3fcbb4c8b5800a533039\ 2a2a04e700bb6ed7e0b85795ea38b1b962741b3f33b9dde2f4ec1354f09e2eb78e95f037a5804\ b6171659f88715ce1a9b0cc90c27f35ef2f10ff0c7c7a2bb0154d9b8ebe76a3d764aa879af372\ f4240de8347937e5a90cec9f41ff2f26b8da9a94a225d1a913717d73f10397d2183f1ba3b7b45\ a68f1ff1893caf69a827802f7b6a48d51da6fbefb64fd9a6c5b75c4561 Msg = 4e3a28bcf90d1d2e75f075d9fbe55b36c5529b17bc3a9ccaba6935c9e20548255b3\ dfae0f91db030c12f2c344b3a29c4151c5b209f5e319fdf1c23b190f64f1fe5b330cb7c8fa952\ f9d90f13aff1cb11d63181da9efc6f7e15bfed4862d1a62c7dcf3ba8bf1ff304b102b1ec3f149\ 7dddf09712cf323f5610a9d10c3d9132659 Y = 5a55dceddd1134ee5f11ed85deb4d634a3643f5f36dc3a70689256469a0b651ad2288\ 0f14ab85719434f9c0e407e60ea420e2a0cd29422c4899c416359dbb1e592456f2b3cce233259\ c117542fd05f31ea25b015d9121c890b90e0bad033be1368d229985aac7226d1c8c2eab325ef3\ b2cd59d3b9f7de7dbc94af1a9339eb430ca36c26c46ecfa6c5481711496f624e188ad7540ef5d\ f26f8efacb820bd17a1f618acb50c9bc197d4cb7ccac45d824a3bf795c234b556b06aeb929173\ 453252084003f69fe98045fe74002ba658f93475622f76791d9b2623d1b5fff2cc16844746efd\ 2d30a6a8134bfc4c8cc80a46107901fb973c28fc553130f3286c1489da R = 633055e055f237c38999d81c397848c38cce80a55b649d9e7905c298e2a51447 S = 2bbf68317660ec1e4b154915027b0bc00ee19cfc0bf75d01930504f2ce10a8b0 Msg = a733b3f588d5ac9b9d4fe2f804df8c256403a9f8eef6f191fc48e1267fb5b4d546b\ a11e77b667844e489bf0d5f72990aeb061d01ccd7949a23def74a803b7d92d51abfadeb4885ff\ d8ffd58ab87548a15c087a39b8993b2fa64c9d31a594eeb7512da16955834336a234435c5a9d0\ dd9b15a94e116154dea63fdc8dd7a512181 Y = 356ed47537fbf02cb30a8cee0537f300dff1d0c467399ce70b87a8758d5ec9dd25624\ 6fccaeb9dfe109f2a984f2ddaa87aad54ce0d31f907e504521baf4207d7073b0a4a9fc67d8ddd\ a99f87aed6e0367cec27f9c608af743bf1ee6e11d55a182d43b024ace534029b866f6422828bb\ 81a39aae9601ee81c7f81dd358e69f4e2edfa4654d8a65bc64311dc86aac4abc1fc7a3f651596\ 61a0d8e288eb8d665cb0adf5ac3d6ba8e9453facf7542393ae24fd50451d3828086558f7ec528\ e284935a53f67a1aa8e25d8ad5c4ad55d83aef883a4d9eeb6297e6a53f65049ba9e2c6b7953a7\ 60bc1dc46f78ceaaa2c02f5375dd82e708744aa40b15799eb81d7e5b1a R = bcd490568c0a89ba311bef88ea4f4b03d273e793722722327095a378dd6f3522 S = 74498fc43091fcdd2d1ef0775f8286945a01cd72b805256b0451f9cbd943cf82 """ ).splitlines() expected = [ { "p": int( "a8f9cd201e5e35d892f85f80e4db2599a5676a3b1d4f190330ed325" "6b26d0e80a0e49a8fffaaad2a24f472d2573241d4d6d6c7480c80b4" "c67bb4479c15ada7ea8424d2502fa01472e760241713dab025ae1b0" "2e1703a1435f62ddf4ee4c1b664066eb22f2e3bf28bb70a2a76e4fd" "5ebe2d1229681b5b06439ac9c7e9d8bde283", 16, ), "q": int("f85f0f83ac4df7ea0cdf8f469bfeeaea14156495", 16), "g": int( "2b3152ff6c62f14622b8f48e59f8af46883b38e79b8c74deeae9df1" "31f8b856e3ad6c8455dab87cc0da8ac973417ce4f7878557d6cdf40" "b35b4a0ca3eb310c6a95d68ce284ad4e25ea28591611ee08b8444bd" "64b25f3f7c572410ddfb39cc728b9c936f85f419129869929cdb909" "a6a3a99bbe089216368171bd0ba81de4fe33", 16, ), "digest_algorithm": "SHA-1", "msg": binascii.unhexlify( b"3b46736d559bd4e0c2c1b2553a33ad3c6cf23cac998d3d0c0e8fa4b19bc" b"a06f2f386db2dcff9dca4f40ad8f561ffc308b46c5f31a7735b5fa7e0f9" b"e6cb512e63d7eea05538d66a75cd0d4234b5ccf6c1715ccaaf9cdc0a222" b"8135f716ee9bdee7fc13ec27a03a6d11c5c5b3685f51900b1337153bc6c" b"4e8f52920c33fa37f4e7" ), "y": int( "313fd9ebca91574e1c2eebe1517c57e0c21b0209872140c5328761b" "bb2450b33f1b18b409ce9ab7c4cd8fda3391e8e34868357c199e16a" "6b2eba06d6749def791d79e95d3a4d09b24c392ad89dbf100995ae1" "9c01062056bb14bce005e8731efde175f95b975089bdcdaea562b32" "786d96f5a31aedf75364008ad4fffebb970b", 16, ), "r": int("50ed0e810e3f1c7cb6ac62332058448bd8b284c0", 16), "s": int("c6aded17216b46b7e4b6f2a97c1ad7cc3da83fde", 16), }, { "p": int( "a8f9cd201e5e35d892f85f80e4db2599a5676a3b1d4f190330ed325" "6b26d0e80a0e49a8fffaaad2a24f472d2573241d4d6d6c7480c80b4" "c67bb4479c15ada7ea8424d2502fa01472e760241713dab025ae1b0" "2e1703a1435f62ddf4ee4c1b664066eb22f2e3bf28bb70a2a76e4fd" "5ebe2d1229681b5b06439ac9c7e9d8bde283", 16, ), "q": int("f85f0f83ac4df7ea0cdf8f469bfeeaea14156495", 16), "g": int( "2b3152ff6c62f14622b8f48e59f8af46883b38e79b8c74deeae9df1" "31f8b856e3ad6c8455dab87cc0da8ac973417ce4f7878557d6cdf40" "b35b4a0ca3eb310c6a95d68ce284ad4e25ea28591611ee08b8444bd" "64b25f3f7c572410ddfb39cc728b9c936f85f419129869929cdb909" "a6a3a99bbe089216368171bd0ba81de4fe33", 16, ), "digest_algorithm": "SHA-1", "msg": binascii.unhexlify( b"d2bcb53b044b3e2e4b61ba2f91c0995fb83a6a97525e66441a3b489d959" b"4238bc740bdeea0f718a769c977e2de003877b5d7dc25b182ae533db33e" b"78f2c3ff0645f2137abc137d4e7d93ccf24f60b18a820bc07c7b4b5fe08" b"b4f9e7d21b256c18f3b9d49acc4f93e2ce6f3754c7807757d2e11760426" b"12cb32fc3f4f70700e25" ), "y": int( "29bdd759aaa62d4bf16b4861c81cf42eac2e1637b9ecba512bdbc13" "ac12a80ae8de2526b899ae5e4a231aef884197c944c732693a634d7" "659abc6975a773f8d3cd5a361fe2492386a3c09aaef12e4a7e73ad7" "dfc3637f7b093f2c40d6223a195c136adf2ea3fbf8704a675aa7817" "aa7ec7f9adfb2854d4e05c3ce7f76560313b", 16, ), "r": int("a26c00b5750a2d27fe7435b93476b35438b4d8ab", 16), "s": int("61c9bfcb2938755afa7dad1d1e07c6288617bf70", 16), }, { "p": int( "a8adb6c0b4cf9588012e5deff1a871d383e0e2a85b5e8e03d814fe1" "3a059705e663230a377bf7323a8fa117100200bfd5adf857393b0bb" "d67906c081e585410e38480ead51684dac3a38f7b64c9eb109f1973" "9a4517cd7d5d6291e8af20a3fbf17336c7bf80ee718ee087e322ee4" "1047dabefbcc34d10b66b644ddb3160a28c0639563d71993a26543e" "adb7718f317bf5d9577a6156561b082a10029cd44012b18de684450" "9fe058ba87980792285f2750969fe89c2cd6498db3545638d5379d1" "25dccf64e06c1af33a6190841d223da1513333a7c9d78462abaab31" "b9f96d5f34445ceb6309f2f6d2c8dde06441e87980d303ef9a1ff00" "7e8be2f0be06cc15f", 16, ), "q": int( "e71f8567447f42e75f5ef85ca20fe557ab0343d37ed09edc3f6e686" "04d6b9dfb", 16, ), "g": int( "5ba24de9607b8998e66ce6c4f812a314c6935842f7ab54cd82b19fa" "104abfb5d84579a623b2574b37d22ccae9b3e415e48f5c0f9bcbdff" "8071d63b9bb956e547af3a8df99e5d3061979652ff96b765cb3ee49" "3643544c75dbe5bb39834531952a0fb4b0378b3fcbb4c8b5800a533" "0392a2a04e700bb6ed7e0b85795ea38b1b962741b3f33b9dde2f4ec" "1354f09e2eb78e95f037a5804b6171659f88715ce1a9b0cc90c27f3" "5ef2f10ff0c7c7a2bb0154d9b8ebe76a3d764aa879af372f4240de8" "347937e5a90cec9f41ff2f26b8da9a94a225d1a913717d73f10397d" "2183f1ba3b7b45a68f1ff1893caf69a827802f7b6a48d51da6fbefb" "64fd9a6c5b75c4561", 16, ), "digest_algorithm": "SHA-256", "msg": binascii.unhexlify( b"4e3a28bcf90d1d2e75f075d9fbe55b36c5529b17bc3a9ccaba6935c9e20" b"548255b3dfae0f91db030c12f2c344b3a29c4151c5b209f5e319fdf1c23" b"b190f64f1fe5b330cb7c8fa952f9d90f13aff1cb11d63181da9efc6f7e1" b"5bfed4862d1a62c7dcf3ba8bf1ff304b102b1ec3f1497dddf09712cf323" b"f5610a9d10c3d9132659" ), "y": int( "5a55dceddd1134ee5f11ed85deb4d634a3643f5f36dc3a706892564" "69a0b651ad22880f14ab85719434f9c0e407e60ea420e2a0cd29422" "c4899c416359dbb1e592456f2b3cce233259c117542fd05f31ea25b" "015d9121c890b90e0bad033be1368d229985aac7226d1c8c2eab325" "ef3b2cd59d3b9f7de7dbc94af1a9339eb430ca36c26c46ecfa6c548" "1711496f624e188ad7540ef5df26f8efacb820bd17a1f618acb50c9" "bc197d4cb7ccac45d824a3bf795c234b556b06aeb92917345325208" "4003f69fe98045fe74002ba658f93475622f76791d9b2623d1b5fff" "2cc16844746efd2d30a6a8134bfc4c8cc80a46107901fb973c28fc5" "53130f3286c1489da", 16, ), "r": int( "633055e055f237c38999d81c397848c38cce80a55b649d9e7905c29" "8e2a51447", 16, ), "s": int( "2bbf68317660ec1e4b154915027b0bc00ee19cfc0bf75d01930504f" "2ce10a8b0", 16, ), }, { "p": int( "a8adb6c0b4cf9588012e5deff1a871d383e0e2a85b5e8e03d814fe1" "3a059705e663230a377bf7323a8fa117100200bfd5adf857393b0bb" "d67906c081e585410e38480ead51684dac3a38f7b64c9eb109f1973" "9a4517cd7d5d6291e8af20a3fbf17336c7bf80ee718ee087e322ee4" "1047dabefbcc34d10b66b644ddb3160a28c0639563d71993a26543e" "adb7718f317bf5d9577a6156561b082a10029cd44012b18de684450" "9fe058ba87980792285f2750969fe89c2cd6498db3545638d5379d1" "25dccf64e06c1af33a6190841d223da1513333a7c9d78462abaab31" "b9f96d5f34445ceb6309f2f6d2c8dde06441e87980d303ef9a1ff00" "7e8be2f0be06cc15f", 16, ), "q": int( "e71f8567447f42e75f5ef85ca20fe557ab0343d37ed09edc3f6e686" "04d6b9dfb", 16, ), "g": int( "5ba24de9607b8998e66ce6c4f812a314c6935842f7ab54cd82b19fa" "104abfb5d84579a623b2574b37d22ccae9b3e415e48f5c0f9bcbdff" "8071d63b9bb956e547af3a8df99e5d3061979652ff96b765cb3ee49" "3643544c75dbe5bb39834531952a0fb4b0378b3fcbb4c8b5800a533" "0392a2a04e700bb6ed7e0b85795ea38b1b962741b3f33b9dde2f4ec" "1354f09e2eb78e95f037a5804b6171659f88715ce1a9b0cc90c27f3" "5ef2f10ff0c7c7a2bb0154d9b8ebe76a3d764aa879af372f4240de8" "347937e5a90cec9f41ff2f26b8da9a94a225d1a913717d73f10397d" "2183f1ba3b7b45a68f1ff1893caf69a827802f7b6a48d51da6fbefb" "64fd9a6c5b75c4561", 16, ), "digest_algorithm": "SHA-256", "msg": binascii.unhexlify( b"a733b3f588d5ac9b9d4fe2f804df8c256403a9f8eef6f191fc48e1267fb" b"5b4d546ba11e77b667844e489bf0d5f72990aeb061d01ccd7949a23def7" b"4a803b7d92d51abfadeb4885ffd8ffd58ab87548a15c087a39b8993b2fa" b"64c9d31a594eeb7512da16955834336a234435c5a9d0dd9b15a94e11615" b"4dea63fdc8dd7a512181" ), "y": int( "356ed47537fbf02cb30a8cee0537f300dff1d0c467399ce70b87a87" "58d5ec9dd256246fccaeb9dfe109f2a984f2ddaa87aad54ce0d31f9" "07e504521baf4207d7073b0a4a9fc67d8ddda99f87aed6e0367cec2" "7f9c608af743bf1ee6e11d55a182d43b024ace534029b866f642282" "8bb81a39aae9601ee81c7f81dd358e69f4e2edfa4654d8a65bc6431" "1dc86aac4abc1fc7a3f65159661a0d8e288eb8d665cb0adf5ac3d6b" "a8e9453facf7542393ae24fd50451d3828086558f7ec528e284935a" "53f67a1aa8e25d8ad5c4ad55d83aef883a4d9eeb6297e6a53f65049" "ba9e2c6b7953a760bc1dc46f78ceaaa2c02f5375dd82e708744aa40" "b15799eb81d7e5b1a", 16, ), "r": int( "bcd490568c0a89ba311bef88ea4f4b03d273e793722722327095a37" "8dd6f3522", 16, ), "s": int( "74498fc43091fcdd2d1ef0775f8286945a01cd72b805256b0451f9c" "bd943cf82", 16, ), }, ] assert expected == load_fips_dsa_sig_vectors(vector_data) def test_load_fips_ecdsa_key_pair_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.0 # "Key Pair" information # Curves selected: P-192 K-233 B-571 # Generated on Wed Mar 16 16:16:42 2011 [P-192] [B.4.2 Key Pair Generation by Testing Candidates] N = 2 d = e5ce89a34adddf25ff3bf1ffe6803f57d0220de3118798ea Qx = 8abf7b3ceb2b02438af19543d3e5b1d573fa9ac60085840f Qy = a87f80182dcd56a6a061f81f7da393e7cffd5e0738c6b245 d = 7d14435714ad13ff23341cb567cc91198ff8617cc39751b2 Qx = 39dc723b19527daa1e80425209c56463481b9b47c51f8cbd Qy = 432a3e84f2a16418834fabaf6b7d2341669512951f1672ad [K-233] [B.4.2 Key Pair Generation by Testing Candidates] N = 2 d = 01da7422b50e3ff051f2aaaed10acea6cbf6110c517da2f4eaca8b5b87 Qx = 01c7475da9a161e4b3f7d6b086494063543a979e34b8d7ac44204d47bf9f Qy = 0131cbd433f112871cc175943991b6a1350bf0cdd57ed8c831a2a7710c92 d = 530951158f7b1586978c196603c12d25607d2cb0557efadb23cd0ce8 Qx = d37500a0391d98d3070d493e2b392a2c79dc736c097ed24b7dd5ddec44 Qy = 01d996cc79f37d8dba143d4a8ad9a8a60ed7ea760aae1ddba34d883f65d9 [B-571] [B.4.2 Key Pair Generation by Testing Candidates] N = 2 d = 01443e93c7ef6802655f641ecbe95e75f1f15b02d2e172f49a32e22047d5c00ebe1b3f\ f0456374461360667dbf07bc67f7d6135ee0d1d46a226a530fefe8ebf3b926e9fbad8d57a6 Qx = 053e3710d8e7d4138db0a369c97e5332c1be38a20a4a84c36f5e55ea9fd6f34545b86\ 4ea64f319e74b5ee9e4e1fa1b7c5b2db0e52467518f8c45b658824871d5d4025a6320ca06f8 Qy = 03a22cfd370c4a449b936ae97ab97aab11c57686cca99d14ef184f9417fad8bedae4d\ f8357e3710bcda1833b30e297d4bf637938b995d231e557d13f062e81e830af5ab052208ead d = 03d2bd44ca9eeee8c860a4873ed55a54bdfdf5dab4060df7292877960b85d1fd496aa3\ 3c587347213d7f6bf208a6ab4b430546e7b6ffbc3135bd12f44a28517867ca3c83a821d6f8 Qx = 07a7af10f6617090bade18b2e092d0dfdc87cd616db7f2db133477a82bfe3ea421ebb\ 7d6289980819292a719eb247195529ea60ad62862de0a26c72bfc49ecc81c2f9ed704e3168f Qy = 0721496cf16f988b1aabef3368450441df8439a0ca794170f270ead56203d675b57f5\ a4090a3a2f602a77ff3bac1417f7e25a683f667b3b91f105016a47afad46a0367b18e2bdf0c """ ).splitlines() expected = [ { "curve": "secp192r1", "d": int("e5ce89a34adddf25ff3bf1ffe6803f57d0220de3118798ea", 16), "x": int("8abf7b3ceb2b02438af19543d3e5b1d573fa9ac60085840f", 16), "y": int("a87f80182dcd56a6a061f81f7da393e7cffd5e0738c6b245", 16), }, { "curve": "secp192r1", "d": int("7d14435714ad13ff23341cb567cc91198ff8617cc39751b2", 16), "x": int("39dc723b19527daa1e80425209c56463481b9b47c51f8cbd", 16), "y": int("432a3e84f2a16418834fabaf6b7d2341669512951f1672ad", 16), }, { "curve": "sect233k1", "d": int( "1da7422b50e3ff051f2aaaed10acea6cbf6110c517da2f4eaca8b5b87", 16, ), "x": int( "1c7475da9a161e4b3f7d6b086494063543a979e34b8d7ac4" "4204d47bf9f", 16, ), "y": int( "131cbd433f112871cc175943991b6a1350bf0cdd57ed8c83" "1a2a7710c92", 16, ), }, { "curve": "sect233k1", "d": int( "530951158f7b1586978c196603c12d25607d2cb0557efadb23cd0ce8", 16, ), "x": int( "d37500a0391d98d3070d493e2b392a2c79dc736c097ed24b" "7dd5ddec44", 16, ), "y": int( "1d996cc79f37d8dba143d4a8ad9a8a60ed7ea760aae1ddba" "34d883f65d9", 16, ), }, { "curve": "sect571r1", "d": int( "1443e93c7ef6802655f641ecbe95e75f1f15b02d2e172f49" "a32e22047d5c00ebe1b3ff0456374461360667dbf07bc67f" "7d6135ee0d1d46a226a530fefe8ebf3b926e9fbad8d57a6", 16, ), "x": int( "53e3710d8e7d4138db0a369c97e5332c1be38a20a4a84c36" "f5e55ea9fd6f34545b864ea64f319e74b5ee9e4e1fa1b7c5" "b2db0e52467518f8c45b658824871d5d4025a6320ca06f8", 16, ), "y": int( "3a22cfd370c4a449b936ae97ab97aab11c57686cca99d14e" "f184f9417fad8bedae4df8357e3710bcda1833b30e297d4b" "f637938b995d231e557d13f062e81e830af5ab052208ead", 16, ), }, { "curve": "sect571r1", "d": int( "3d2bd44ca9eeee8c860a4873ed55a54bdfdf5dab4060df72" "92877960b85d1fd496aa33c587347213d7f6bf208a6ab4b4" "30546e7b6ffbc3135bd12f44a28517867ca3c83a821d6f8", 16, ), "x": int( "7a7af10f6617090bade18b2e092d0dfdc87cd616db7f2db1" "33477a82bfe3ea421ebb7d6289980819292a719eb2471955" "29ea60ad62862de0a26c72bfc49ecc81c2f9ed704e3168f", 16, ), "y": int( "721496cf16f988b1aabef3368450441df8439a0ca794170f" "270ead56203d675b57f5a4090a3a2f602a77ff3bac1417f7" "e25a683f667b3b91f105016a47afad46a0367b18e2bdf0c", 16, ), }, ] assert expected == load_fips_ecdsa_key_pair_vectors(vector_data) def test_load_fips_ecdsa_signing_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.2 # "SigVer" information for "ecdsa_values" # Curves/SHAs selected: P-192, B-571,SHA-512 # Generated on Tue Aug 16 15:27:42 2011 [P-192,SHA-1] Msg = ebf748d748ebbca7d29fb473698a6e6b4fb10c865d4af024cc39ae3df3464ba4f1d6\ d40f32bf9618a91bb5986fa1a2af048a0e14dc51e5267eb05e127d689d0ac6f1a7f156ce066316\ b971cc7a11d0fd7a2093e27cf2d08727a4e6748cc32fd59c7810c5b9019df21cdcc0bca432c0a3\ eed0785387508877114359cee4a071cf d = e14f37b3d1374ff8b03f41b9b3fdd2f0ebccf275d660d7f3 Qx = 07008ea40b08dbe76432096e80a2494c94982d2d5bcf98e6 Qy = 76fab681d00b414ea636ba215de26d98c41bd7f2e4d65477 k = cb0abc7043a10783684556fb12c4154d57bc31a289685f25 R = 6994d962bdd0d793ffddf855ec5bf2f91a9698b46258a63e S = 02ba6465a234903744ab02bc8521405b73cf5fc00e1a9f41 Result = F (3 - S changed) Msg = 0dcb3e96d77ee64e9d0a350d31563d525755fc675f0c833504e83fc69c030181b42f\ e80c378e86274a93922c570d54a7a358c05755ec3ae91928e02236e81b43e596e4ccbf6a910488\ 9c388072bec4e1faeae11fe4eb24fa4f9573560dcf2e3abc703c526d46d502c7a7222583431cc8\ 178354ae7dbb84e3479917707bce0968 d = 7a0235bea3d70445f14d56f9b7fb80ec8ff4eb2f76865244 Qx = 0ea3c1fa1f124f26530cbfddeb831eecc67df31e08889d1d Qy = 7215a0cce0501b47903bd8fe1179c2dfe07bd076f89f5225 k = 3c646b0f03f5575e5fd463d4319817ce8bd3022eaf551cef R = a3ba51c39c43991d87dff0f34d0bec7c883299e04f60f95e S = 8a7f9c59c6d65ad390e4c19636ba92b53be5d0f848b4e1f7 [B-571,SHA-512] Msg = 10d2e00ae57176c79cdfc746c0c887abe799ee445b151b008e3d9f81eb69be40298d\ df37b5c45a9b6e5ff83785d8c140cf11e6a4c3879a2845796872363da24b10f1f8d9cc48f8af20\ 681dceb60dd62095d6d3b1779a4a805de3d74e38983b24c0748618e2f92ef7cac257ff4bd1f411\ 13f2891eb13c47930e69ddbe91f270fb d = 03e1b03ffca4399d5b439fac8f87a5cb06930f00d304193d7daf83d5947d0c1e293f74\ aef8e56849f16147133c37a6b3d1b1883e5d61d6b871ea036c5291d9a74541f28878cb986 Qx = 3b236fc135d849d50140fdaae1045e6ae35ef61091e98f5059b30eb16acdd0deb2bc0\ d3544bc3a666e0014e50030134fe5466a9e4d3911ed580e28851f3747c0010888e819d3d1f Qy = 3a8b6627a587d289032bd76374d16771188d7ff281c39542c8977f6872fa932e5daa1\ 4e13792dea9ffe8e9f68d6b525ec99b81a5a60cfb0590cc6f297cfff8d7ba1a8bb81fe2e16 k = 2e56a94cfbbcd293e242f0c2a2e9df289a9480e6ba52e0f00fa19bcf2a7769bd155e6b\ 79ddbd6a8646b0e69c8baea27f8034a18796e8eb4fe6e0e2358c383521d9375d2b6b437f9 R = 2eb1c5c1fc93cf3c8babed12c031cf1504e094174fd335104cbe4a2abd210b5a14b1c3\ a455579f1ed0517c31822340e4dd3c1f967e1b4b9d071a1072afc1a199f8c548cd449a634 S = 22f97bb48641235826cf4e597fa8de849402d6bd6114ad2d7fbcf53a08247e5ee921f1\ bd5994dffee36eedff5592bb93b8bb148214da3b7baebffbd96b4f86c55b3f6bbac142442 Result = P (0 ) Msg = b61a0849a28672cb536fcf61ea2eb389d02ff7a09aa391744cae6597bd56703c40c5\ 0ca2dee5f7ee796acfd47322f03d8dbe4d99dc8eec588b4e5467f123075b2d74b2a0b0bbfd3ac5\ 487a905fad6d6ac1421c2e564c0cf15e1f0f10bc31c249b7b46edd2462a55f85560d99bde9d5b0\ 6b97817d1dbe0a67c701d6e6e7878272 d = 2e09ffd8b434bb7f67d1d3ccf482164f1653c6e4ec64dec2517aa21b7a93b2b21ea1ee\ bb54734882f29303e489f02e3b741a87287e2dcdf3858eb6d2ec668f8b5b26f442ce513a2 Qx = 36f1be8738dd7dae4486b86a08fe90424f3673e76b10e739442e15f3bfafaf841842a\ c98e490521b7e7bb94c127529f6ec6a42cc6f06fc80606f1210fe020ff508148f93301c9d3 Qy = 4d39666ebe99fe214336ad440d776c88eb916f2f4a3433548b87d2aebed840b424d15\ c8341b4a0a657bf6a234d4fe78631c8e07ac1f4dc7474cd6b4545d536b7b17c160db4562d9 k = 378e7801566d7b77db7a474717ab2195b02957cc264a9449d4126a7cc574728ed5a476\ 9abd5dde987ca66cfe3d45b5fc52ffd266acb8a8bb3fcb4b60f7febbf48aebe33bd3efbdd R = 3d8105f87fe3166046c08e80a28acc98a80b8b7a729623053c2a9e80afd06756edfe09\ bdcf3035f6829ede041b745955d219dc5d30ddd8b37f6ba0f6d2857504cdc68a1ed812a10 S = 34db9998dc53527114518a7ce3783d674ca8cced823fa05e2942e7a0a20b3cc583dcd9\ 30c43f9b93079c5ee18a1f5a66e7c3527c18610f9b47a4da7e245ef803e0662e4d2ad721c """ ).splitlines() expected = [ { "curve": "secp192r1", "digest_algorithm": "SHA-1", "message": binascii.unhexlify( b"ebf748d748ebbca7d29fb473698a6e6b4fb10c865d4af024cc39ae3df346" b"4ba4f1d6d40f32bf9618a91bb5986fa1a2af048a0e14dc51e5267eb05e12" b"7d689d0ac6f1a7f156ce066316b971cc7a11d0fd7a2093e27cf2d08727a4" b"e6748cc32fd59c7810c5b9019df21cdcc0bca432c0a3eed0785387508877" b"114359cee4a071cf" ), "d": int("e14f37b3d1374ff8b03f41b9b3fdd2f0ebccf275d660d7f3", 16), "x": int("7008ea40b08dbe76432096e80a2494c94982d2d5bcf98e6", 16), "y": int("76fab681d00b414ea636ba215de26d98c41bd7f2e4d65477", 16), "r": int("6994d962bdd0d793ffddf855ec5bf2f91a9698b46258a63e", 16), "s": int("02ba6465a234903744ab02bc8521405b73cf5fc00e1a9f41", 16), "fail": True, }, { "curve": "secp192r1", "digest_algorithm": "SHA-1", "message": binascii.unhexlify( b"0dcb3e96d77ee64e9d0a350d31563d525755fc675f0c833504e83fc69c03" b"0181b42fe80c378e86274a93922c570d54a7a358c05755ec3ae91928e022" b"36e81b43e596e4ccbf6a9104889c388072bec4e1faeae11fe4eb24fa4f95" b"73560dcf2e3abc703c526d46d502c7a7222583431cc8178354ae7dbb84e3" b"479917707bce0968" ), "d": int("7a0235bea3d70445f14d56f9b7fb80ec8ff4eb2f76865244", 16), "x": int("ea3c1fa1f124f26530cbfddeb831eecc67df31e08889d1d", 16), "y": int("7215a0cce0501b47903bd8fe1179c2dfe07bd076f89f5225", 16), "r": int("a3ba51c39c43991d87dff0f34d0bec7c883299e04f60f95e", 16), "s": int("8a7f9c59c6d65ad390e4c19636ba92b53be5d0f848b4e1f7", 16), }, { "curve": "sect571r1", "digest_algorithm": "SHA-512", "message": binascii.unhexlify( b"10d2e00ae57176c79cdfc746c0c887abe799ee445b151b008e3d9f81eb69" b"be40298ddf37b5c45a9b6e5ff83785d8c140cf11e6a4c3879a2845796872" b"363da24b10f1f8d9cc48f8af20681dceb60dd62095d6d3b1779a4a805de3" b"d74e38983b24c0748618e2f92ef7cac257ff4bd1f41113f2891eb13c4793" b"0e69ddbe91f270fb" ), "d": int( "3e1b03ffca4399d5b439fac8f87a5cb06930f00d304193d7daf83d59" "47d0c1e293f74aef8e56849f16147133c37a6b3d1b1883e5d61d6b87" "1ea036c5291d9a74541f28878cb986", 16, ), "x": int( "3b236fc135d849d50140fdaae1045e6ae35ef61091e98f5059b30eb1" "6acdd0deb2bc0d3544bc3a666e0014e50030134fe5466a9e4d3911ed" "580e28851f3747c0010888e819d3d1f", 16, ), "y": int( "3a8b6627a587d289032bd76374d16771188d7ff281c39542c8977f68" "72fa932e5daa14e13792dea9ffe8e9f68d6b525ec99b81a5a60cfb05" "90cc6f297cfff8d7ba1a8bb81fe2e16", 16, ), "r": int( "2eb1c5c1fc93cf3c8babed12c031cf1504e094174fd335104cbe4a2a" "bd210b5a14b1c3a455579f1ed0517c31822340e4dd3c1f967e1b4b9d" "071a1072afc1a199f8c548cd449a634", 16, ), "s": int( "22f97bb48641235826cf4e597fa8de849402d6bd6114ad2d7fbcf53a" "08247e5ee921f1bd5994dffee36eedff5592bb93b8bb148214da3b7b" "aebffbd96b4f86c55b3f6bbac142442", 16, ), "fail": False, }, { "curve": "sect571r1", "digest_algorithm": "SHA-512", "message": binascii.unhexlify( b"b61a0849a28672cb536fcf61ea2eb389d02ff7a09aa391744cae6597bd56" b"703c40c50ca2dee5f7ee796acfd47322f03d8dbe4d99dc8eec588b4e5467" b"f123075b2d74b2a0b0bbfd3ac5487a905fad6d6ac1421c2e564c0cf15e1f" b"0f10bc31c249b7b46edd2462a55f85560d99bde9d5b06b97817d1dbe0a67" b"c701d6e6e7878272" ), "d": int( "2e09ffd8b434bb7f67d1d3ccf482164f1653c6e4ec64dec2517aa21b" "7a93b2b21ea1eebb54734882f29303e489f02e3b741a87287e2dcdf3" "858eb6d2ec668f8b5b26f442ce513a2", 16, ), "x": int( "36f1be8738dd7dae4486b86a08fe90424f3673e76b10e739442e15f3" "bfafaf841842ac98e490521b7e7bb94c127529f6ec6a42cc6f06fc80" "606f1210fe020ff508148f93301c9d3", 16, ), "y": int( "4d39666ebe99fe214336ad440d776c88eb916f2f4a3433548b87d2ae" "bed840b424d15c8341b4a0a657bf6a234d4fe78631c8e07ac1f4dc74" "74cd6b4545d536b7b17c160db4562d9", 16, ), "r": int( "3d8105f87fe3166046c08e80a28acc98a80b8b7a729623053c2a9e80" "afd06756edfe09bdcf3035f6829ede041b745955d219dc5d30ddd8b3" "7f6ba0f6d2857504cdc68a1ed812a10", 16, ), "s": int( "34db9998dc53527114518a7ce3783d674ca8cced823fa05e2942e7a0" "a20b3cc583dcd930c43f9b93079c5ee18a1f5a66e7c3527c18610f9b" "47a4da7e245ef803e0662e4d2ad721c", 16, ), }, ] assert expected == load_fips_ecdsa_signing_vectors(vector_data) def test_load_kasvs_dh_vectors(): vector_data = textwrap.dedent( """ [SHA(s) supported (Used for hashing Z): SHA256 ] # Generated on Thu Mar 17 20:44:26 2011 [FA - SHA1] P = da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a06848bf7a\ 42f18895c97402e81118e01a00d0855d51922f434c022350861d58ddf60d65bc6941fc6064b147\ 071a4c30426d82fc90d888f94990267c64beef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8\ b59afb62cfe9ea3ba042c73a6ade35 Q = f2ca7621eb250aa5f22cef1907011295defc50a7 G = a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5e3e5af586\ 3a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc623f648fd6d95e9cefcb0745763\ cddf564c87bcf4ba7928e74fd6a3080481f588d535e4c026b58a21e1e5ec412ff241b436043e29\ 173f1dc6cb943c09742de989547288 COUNT = 0 XstatCAVS = 42c6ee70beb7465928a1efe692d2281b8f7b53d6 YstatCAVS = 5a7890f6d20ee9c7162cd84222cb0c7cb5b4f29244a58fc95327fc41045f47\ 6fb3da42fca76a1dd59222a7a7c3872d5af7d8dc254e003eccdb38f291619c51911df2b6ed67d0\ b459f4bc25819c0078777b9a1a24c72e7c037a3720a1edad5863ef5ac75ce816869c820859558d\ 5721089ddbe331f55bef741396a3bbf85c6c1a XstatIUT = 54081a8fef2127a1f22ed90440b1b09c331d0614 YstatIUT = 0b92af0468b841ea5de4ca91d895b5e922245421de57ed7a88d2de41610b208\ e8e233705f17b2e9eb91914bad2fa87f0a58519a7da2980bc06e7411c925a6050526bd86e62150\ 5e6f610b63fdcd9afcfaa96bd087afca44d9197cc35b559f731357a5b979250c0f3a254bb8165f\ 5072156e3fd6f9a6e69bcf4b4578f78b3bde7 Z = 8d8f4175e16e15a42eb9099b11528af88741cc206a088971d3064bb291eda608d1600b\ ff829624db258fd15e95d96d3e74c6be3232afe5c855b9c59681ce13b7aea9ff2b16707e4c02f0\ e82bf6dadf2149ac62630f6c62dea0e505e3279404da5ffd5a088e8474ae0c8726b8189cb3d2f0\ 4baffe700be849df9f91567fc2ebb8 CAVSHashZZ = eb99e77ac2272c7a2ee70c59375ac4d167312c20 Result = P (0 - Correct) COUNT = 2 XstatCAVS = 32e642683d745a23dccf4f12f989d8dfd1fd9894c422930950cb4c71 YstatCAVS = 8cd371363b32fcc2e936e345f2278b77001f2efdf78512c3ee75c12f88507e\ 2d5c0e5cdded3bb78435506c8028a3f4d6f028c0f49a0d61f1285795197e56deac80279e723f2b\ 3746e213ac8ec60f1cefc2308ff17a7e9e2efab537e17406d2829fd85e0c54dda2d9f0b4fcda3d\ 2776110e096a817588e19588b77be8b41bafdd41ad91b0edf629333bd6ac1e461208ead124c31b\ 8a7935c723e1c450c5798dc05f8265ad9e35095ff112af9e889f00315fa337a76a450670866eca\ 12cc6ad0778576962eb9cdc12721d3c15e4d87b67488a145d400240670eb26695a42879cd3940a\ 55087f6527667277e1212a202dbe455c45c64b9be4a38153557bbb8fd755 XstatIUT = 7d8ae93df3bc09d399a4157ec562126acf51092c3269ab27f60a3a2b YstatIUT = 22127e9728e906ea4b1512c8b1e80474b58446210c23ccfc800f83c2c15da81\ 59940e494b235266f6a9d5f80529067794f1a9edd566755d23d0a3060fe074c5a10122df3e4729\ 73bba39ea3a988e8387f5f0491e590b6b5edc299b4598ab1e79b72681a0be8cd8735a5adb85fa3\ 1310f29ec407c9654f1bb83bcdf7f771b68d176817f662e8d798b53ebb4e5dd407b7b1d8fdb62e\ a9e1b60d6c3d75d9bcf83f4b8d1ed39408bd8d973b4ea81e8e832eac361dcd530713388a60971e\ a9f8b1e69c1e99df1cca12bdaf293dacfa1419c5692ceffa91988aef3321ac8cbc2efae6c4337c\ 8808310fb5a240395a98e6004fe613c39e84f4177341746d9e388dcb2e8 Z = 0efeaa399a182e0a603baf0dd95aa0fae5289ebd47d5f0f60c86bc936839c31c9f7f37\ bf04f76ab02f4094a8ab10ed907ec7291585cc085c3e8981df2bd46a01c19ec9a2f66709df1d4f\ efbeb48c8263554e46890f59eb642bf95ff7f0de70138621c22c4cc32be6c3d5c82c0c9a76a9f5\ a65bffe0c096a350f96a9da945d7e5095b15b566ce3cb8b0377cd9375b6c046afa9ea0bc084677\ 3445f16566b2c84cae4f6d212e89ee539a1ce7ea325273fd228053efce2a585eb9e8f308b48cf4\ e29593b6f7a02e8625e1e8bff1ea1405f8c8c34b8339a9a99c7c9de4eb9895df7719ccda9394f5\ 3080eff1226f6b9c7ae0a38941e18b1a137aabbb62308eb35ba2 CAVSHashZZ = 76dedc997d5113573bbeeaf991f62b257511b7d9aa83270dfc4fec40 Result = P (10 - Z value should have leading 0 nibble ) COUNT = 3 XstatCAVS = 66502429aba271e2f2ee2197a2b336e5f0467f192aa28b60dcbf1194 YstatCAVS = dfb001294215423d7146a2453cdb8598ccef01e1d931a913c3e4ed4a3cf38a\ 912066c28e4eaf77dd80ff07183a6160bd95932f513402f864dcf7a70cbedc9b60bbfbc67f72a8\ 3d5f6463a2b5a4fc906d3e921f5e1069126113265b440e15ccf2d7164bad7131f1613fec35df7f\ 470d45888e0c91be091f3f9552d670b8b7f479853193cb3c39f35fc7bd547ccb1bc579a67302b4\ ba948e6db51043d351bb74a952e6a694e6e7456f714c47d7c8eeeb4fd83ad93c86b78445f9393f\ dfd65c7dbd7fd6eba9794ddf183901b1d213321fd0ab3f7588ab0f6b3692f365a87131eda0e062\ 505861988f6ce63150207545ecf9678e0971330253dfb7cfd546c5346fec XstatIUT = 106b358be4f068348ac240ecbb454e5c39ca80b078cb0fafd856e9c5 YstatIUT = 715d0781975b7b03162f4401c1eda343fd9bf1140006034573b31828a618c35\ 6163554cd27da956f7179a69e860fb6efeaa2e2aa9f1261506a8344c4929953621381b13d6426e\ 152c0f2f94bfcd2b758eca24923596d427ed8f957e8bc9b1c7d21a87ef02222a1477cf3bfaadc6\ 8106456ab9706026006eccd290b21543de6bb97d5b8cf4ccee1c081a6d1dd27aaef060fa93888a\ 47a4a416ad5c5bd490ea600e04379232fb1077fbf394f4579accdbe352714e25b88916dca8d8f7\ e0c4ed9594f7693f656a235a2e88ebda48b0d557e32da9f12d2a4c3180f05b16b4fba9bec79278\ a3971b77f9223b5ab78b857e0376c5008211592c8c72d521373ee3b22b8 Z = cf879ebd107bb877457809c3fc410218b7acba3c5967495a8f1c3370d57f038a48dd69\ f9f69b9f4dd855e7c58a1e4ec32646a978266eb314db468ea1dfcee8a85a1644a5732498c4fbcd\ f85098c6ed0ce12e431e99142fd2335369b3f56620ada21aa69d883e82a0b5e35484dde32d17c2\ dc873f2cc5518eb7fc19695dff9fc94c9d9432bb4b09d8180323cfc561ebc2d6eff8dd5f8496f2\ b22377700a22bbfe61a6969c198129397454843e4fc3540026986039665095490056287e4fc49e\ 6cb3181cb2bf06444fd0040150271c9ce1f61c13ecd5dd022194a2dbf3e1c7fbc6bd19497c7b88\ 8b4da613d28fa6f378a43369cb8795a1c823f7d6cf4d84bba578 CAVSHashZZ = ebac4fb70699224f85d9e3c799b1f3a56dab268b882aba49525df02d Result = F (5 - Z changed ) [FB - SHA224] P = f3722b9b911c6aede9eaeeaa406283de66a097f39a7225df6c3c916e57920d356e5047\ 8d307dbfd146bfb91b6f68ecbbcf54b3d19c33a4b17293fea3e3d6bff8ac4cca93a805386f062a\ 8a27ae906ef5da94d279fd7b3d7289e00956f76bae9c0d2b8d11742ca5809630632aae58f9c6dc\ e00c7380581deffde2187b022f83c6ceaeaadb0844a17fcbb04039ca6843c91f0c9058b22434b2\ 63c3dfda8de8429e087c5be97fc5c9db9526031ad3a218bd9916fb4a3c27966d208b1e360014c0\ 1e95530c148fb3cd27e6a7250d3c3b81dcd220ca14548dbccf99ebb9e334db6bcd14e632c98dd3\ f9860af7ae450f1b7809b45f0ec10e6f27672beebc9963befc73 Q = a9a17de95a29091bf8e07dab53ea1aba9403be3c61027c6c8f48bac5 G = 035513ec441402b78353ab1bba550b21c76c89973885a627170262ef52497d5d137b89\ 27a212aaab2f051198c90bb81dffd9eb10b36b7ca3b63565b4c1025aea3b5e9c4a348c9cfa17f3\ 907a1e4469701c0dedb8a4b9e96c5965b1fb8c229b0c34baac774bf9dda4fc5ee8764358b3c848\ 12878aab7464bc09e97aecab7d7e3fbb4870e2a3b89667a4158bf1ed1a90dfaf47019fbb52b1b9\ 6365bb4e1e9474993fe382fd23480dc875861be152997a621fdb7aef977ea5b4d3d74486b162dc\ 28f95a64cf65587a919a57eef92934fc9410df7f09fa82f975328ed82ff29cc3e15a971f56f4ac\ 2dcb289252575e02a6cdb7fcc6cddd7b0dca9c422e63eb2b8f05 COUNT = 0 XstatCAVS = 1610eaa4e0ccc8857e2b53149e008492b1fbd9025a6e8d95aaee9c0f YstatCAVS = 51ee21cd9f97015180f258fad5c94ff5a458806b1412087236bf77fe87aae1\ a36735816ed6e2160a731159814b6ae1f3f52c478dd9207094adfb62f7667d5c366327e66d2309\ 6395e938504db330953a708015f861fe9d9487611093b9fe7327518a7cc15994ab573313e15411\ 7c1a3ae88b8bdd1e316748249e4a9cbd1947f159836d13613d1f9449fc3442171d1970bc28958c\ 1cafa2776a6f14ccdb29db02f64911bd83bfdcdfc843dd14a4cab9acb0bda8b293d2f5f7050768\ e57533cbc415a29e6f31cc365e107f91ae3722484e2c7329a85af69055a5a104da37e810878896\ d1b247b02b75234ecff82b1958f42d7b031622e9394c98b5229112f7f620 XstatIUT = 0c4c83d75b27864b052cadc556e500e25aabf0c9d1bc01f0e1fe3862 YstatIUT = 467a857337a82472a1307a64dccc8e9994c5c63ec4312936885d17be419051a\ 5f037fbb052d7010ebe01634d9e8b8b522d9ab4749fdc274f465369b89e360df8f70b7865a3c71\ d2dbcd2df19e9293dab1153d3d63fcb7deb559b684dde6c6eed63214444807041c9a0ce3f52ca4\ 39ec16dd231995b5dc6f18e6801b6bd6454babccf9abbfacffb49c71e6494a4779cbfa550c5d71\ 44114e6fc193f460dcd0be7e6e06e546da7653770dc5859df87029e722dbe81361030569148d16\ 36988926bf0dcfe47c9d8a54698c08b3b5c70afe86b5c6f643463f8f34889d27d6cfd2d478c2d7\ b3d008a985c7380f0b43f10024b59c3543880883c42d0e7e0a07326ba3a Z = 10a30bacab82e652415376baffdbc008c7eb2e5a3aa68bc10ce486ca84983fd89b1b02\ 7bb40e75333406361005f5e756526a95fe01202df9217d81b1713d5187c368fdd4c9c2433d9e6c\ 18844769479b725c4140c92a304ee1bc5726d8f5321b5b1c54a1a6b67c527e6817c0ed613a0d4e\ 60db55de898788b7e8d4aa9a81ab5ed7f6282962c433d246ed640555bdd76d29c2874551264d74\ c76373f8a88871b41b041c98041b16f94f983ddf00f5bc7d2416d19168c90178974a0602436cd1\ 86748bcc63a629edc3a0db59415cccd37a65130ea477c89da92d41371f5972891cf41f9c7f0e75\ ccbff9893225384db30daa5e310f08e3e0fad98bcdf8ecf35fe5 CAVSHashZZ = 014f5daea733d0e9e100f852e74d64a319f741cfbdb47975ab9dd3d0 Result = F (3 - IUT's Static public key fails PKV 5.6.2.4) COUNT = 1 XstatCAVS = 9ee22ac51664e40e0a24dbb94142dba40605e2b6eeaaa0268a0f6847 YstatCAVS = c2630c9d38ed5c825d1c6a3eba7143f3fc8a049c8bcd1efc212d2af64eca99\ 4308208691d330aa8f27fc4a1e55de4e512113996d21375a667f8c26d76dee2f6809b15432a33f\ b735aca5c2263940f58712bded08f55443dee300b9489589e0462bd6bce19deaec4adc12fa61a6\ 94c8c5c999b28211d7835bac0ffd2b316850823e2dc1d1f58e05cbf75c673036d116b3f03b9687\ c89f9c2a0d43c4ffc9a605addbdcce0cb3790c6db846156bb857a7b3df40dc6ed04d19cc9eaebb\ 6bbc034e77c3d882a1a62317cce25b6130f0803e3bc49b5e36768260073a617034872be0b50bed\ 32740224beaf582d67fbcfef3b3ecc18f9c71c782e9a68495ef31dc7986e XstatIUT = 438093a468236658821bf64eb08456139963d4fb27121c3ed6c55876 YstatIUT = e192da8e1244e27221c1765344a5bb379dce741d427a734b4bdb6c4d16b2490\ bd37564d745008e63ae46ef332331d79887ac63298ce143e125f8b320c0f859b7f5f2c1e0053e4\ a7a16997e6143ff702300c9863ae7caef5c1dfca0ecf5197c557745b793f0790a4fe678aeb93fd\ b52490d4f273a5553944dda3ac8b9b792c9b67f8d7b9496398e432a423ae87ebeba688be3ed67e\ ddd7575fa56431cd48579bf53c903bbe066dd78b23c0996ef3a880f0d91315104366a82f01abde\ cce96fd371f94e8420f8bc5b896c801df573554f749b03d0d28b1e1a990bc61c7e9659342ac7e2\ 68e9c0b7c40fdaab394f29cf0a54f780022f9a03b0bd28eb7db8b0b1b47 Z = 56f8f40fa4b8f3580f9014b30d60a42933a53a62182a690142f458dc275c3b2f0e721b\ c5ee6e890b14516419110f5252ff1cceea8e274b2987aa78e3bae90c1935b276b7a1f1c944f79d\ 4774b7a85b3355bdf25cb02bddfbda4ee7918bc93a5c9ca6d7e8fdedbda8e6c8a6ca794bad055a\ 52b19c148958227344cbddd70271d4610316cfea1e559b0bc3a12d15023b30d9f2db602053a056\ 9c3bd2ce1faf59280ecd339f845dbcaaf2e883c5cc6263996f866b18b75d049d4c82097af8a5ce\ 353e14416b3eeb31ba9bc4f6f3dbd846c5299fb5c0043a1b95b9149b39d14df9e6a69547abf8a4\ d518475576730ed528779366568e46b7dd4ed787cb72d0733c93 CAVSHashZZ = 17dbbaa7a20c1390cd8cb3d31ee947bf9dde87739e067b9861ffeea9 Result = P (0 - Correct) """ ).splitlines() expected = [ { "fail_agree": False, "fail_z": False, "g": int( "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5" "e3e5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc6" "23f648fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f" "588d535e4c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c0974" "2de989547288", 16, ), "p": int( "da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a" "06848bf7a42f18895c97402e81118e01a00d0855d51922f434c022350861d" "58ddf60d65bc6941fc6064b147071a4c30426d82fc90d888f94990267c64b" "eef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba0" "42c73a6ade35", 16, ), "q": 1386090807861091316803998193774751098153687863463, "x1": 381229709512864262422021151581620734547375903702, "x2": 479735944608461101114916716909067001453470352916, "y1": int( "5a7890f6d20ee9c7162cd84222cb0c7cb5b4f29244a58fc95327fc41045f4" "76fb3da42fca76a1dd59222a7a7c3872d5af7d8dc254e003eccdb38f29161" "9c51911df2b6ed67d0b459f4bc25819c0078777b9a1a24c72e7c037a3720a" "1edad5863ef5ac75ce816869c820859558d5721089ddbe331f55bef741396" "a3bbf85c6c1a", 16, ), "y2": int( "b92af0468b841ea5de4ca91d895b5e922245421de57ed7a88d2de41610b20" "8e8e233705f17b2e9eb91914bad2fa87f0a58519a7da2980bc06e7411c925" "a6050526bd86e621505e6f610b63fdcd9afcfaa96bd087afca44d9197cc35" "b559f731357a5b979250c0f3a254bb8165f5072156e3fd6f9a6e69bcf4b45" "78f78b3bde7", 16, ), "z": binascii.unhexlify( b"8d8f4175e16e15a42eb9099b11528af88741cc206a088971d3064bb291ed" b"a608d1600bff829624db258fd15e95d96d3e74c6be3232afe5c855b9c596" b"81ce13b7aea9ff2b16707e4c02f0e82bf6dadf2149ac62630f6c62dea0e5" b"05e3279404da5ffd5a088e8474ae0c8726b8189cb3d2f04baffe700be849" b"df9f91567fc2ebb8" ), }, { "fail_agree": False, "fail_z": False, "g": int( "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5" "e3e5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc6" "23f648fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f" "588d535e4c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c0974" "2de989547288", 16, ), "p": int( "da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a" "06848bf7a42f18895c97402e81118e01a00d0855d51922f434c022350861d" "58ddf60d65bc6941fc6064b147071a4c30426d82fc90d888f94990267c64b" "eef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba0" "42c73a6ade35", 16, ), "q": 1386090807861091316803998193774751098153687863463, "x1": int( "32e642683d745a23dccf4f12f989d8dfd1fd9894c422930950cb4c71", 16 ), "x2": int( "7d8ae93df3bc09d399a4157ec562126acf51092c3269ab27f60a3a2b", 16 ), "y1": int( "8cd371363b32fcc2e936e345f2278b77001f2efdf78512c3ee75c12f88507" "e2d5c0e5cdded3bb78435506c8028a3f4d6f028c0f49a0d61f1285795197e" "56deac80279e723f2b3746e213ac8ec60f1cefc2308ff17a7e9e2efab537e" "17406d2829fd85e0c54dda2d9f0b4fcda3d2776110e096a817588e19588b7" "7be8b41bafdd41ad91b0edf629333bd6ac1e461208ead124c31b8a7935c72" "3e1c450c5798dc05f8265ad9e35095ff112af9e889f00315fa337a76a4506" "70866eca12cc6ad0778576962eb9cdc12721d3c15e4d87b67488a145d4002" "40670eb26695a42879cd3940a55087f6527667277e1212a202dbe455c45c6" "4b9be4a38153557bbb8fd755", 16, ), "y2": int( "22127e9728e906ea4b1512c8b1e80474b58446210c23ccfc800f83c2c15da" "8159940e494b235266f6a9d5f80529067794f1a9edd566755d23d0a3060fe" "074c5a10122df3e472973bba39ea3a988e8387f5f0491e590b6b5edc299b4" "598ab1e79b72681a0be8cd8735a5adb85fa31310f29ec407c9654f1bb83bc" "df7f771b68d176817f662e8d798b53ebb4e5dd407b7b1d8fdb62ea9e1b60d" "6c3d75d9bcf83f4b8d1ed39408bd8d973b4ea81e8e832eac361dcd5307133" "88a60971ea9f8b1e69c1e99df1cca12bdaf293dacfa1419c5692ceffa9198" "8aef3321ac8cbc2efae6c4337c8808310fb5a240395a98e6004fe613c39e8" "4f4177341746d9e388dcb2e8", 16, ), "z": binascii.unhexlify( b"0efeaa399a182e0a603baf0dd95aa0fae5289ebd47d5f0f60c86bc936839" b"c31c9f7f37bf04f76ab02f4094a8ab10ed907ec7291585cc085c3e8981df" b"2bd46a01c19ec9a2f66709df1d4fefbeb48c8263554e46890f59eb642bf9" b"5ff7f0de70138621c22c4cc32be6c3d5c82c0c9a76a9f5a65bffe0c096a3" b"50f96a9da945d7e5095b15b566ce3cb8b0377cd9375b6c046afa9ea0bc08" b"46773445f16566b2c84cae4f6d212e89ee539a1ce7ea325273fd228053ef" b"ce2a585eb9e8f308b48cf4e29593b6f7a02e8625e1e8bff1ea1405f8c8c3" b"4b8339a9a99c7c9de4eb9895df7719ccda9394f53080eff1226f6b9c7ae0" b"a38941e18b1a137aabbb62308eb35ba2" ), }, { "fail_agree": False, "fail_z": True, "g": int( "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5" "e3e5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc6" "23f648fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f" "588d535e4c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c0974" "2de989547288", 16, ), "p": int( "da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a" "06848bf7a42f18895c97402e81118e01a00d0855d51922f434c022350861d" "58ddf60d65bc6941fc6064b147071a4c30426d82fc90d888f94990267c64b" "eef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba0" "42c73a6ade35", 16, ), "q": 1386090807861091316803998193774751098153687863463, "x1": int( "66502429aba271e2f2ee2197a2b336e5f0467f192aa28b60dcbf1194", 16 ), "x2": int( "106b358be4f068348ac240ecbb454e5c39ca80b078cb0fafd856e9c5", 16 ), "y1": int( "dfb001294215423d7146a2453cdb8598ccef01e1d931a913c3e4ed4a3cf38" "a912066c28e4eaf77dd80ff07183a6160bd95932f513402f864dcf7a70cbe" "dc9b60bbfbc67f72a83d5f6463a2b5a4fc906d3e921f5e1069126113265b4" "40e15ccf2d7164bad7131f1613fec35df7f470d45888e0c91be091f3f9552" "d670b8b7f479853193cb3c39f35fc7bd547ccb1bc579a67302b4ba948e6db" "51043d351bb74a952e6a694e6e7456f714c47d7c8eeeb4fd83ad93c86b784" "45f9393fdfd65c7dbd7fd6eba9794ddf183901b1d213321fd0ab3f7588ab0" "f6b3692f365a87131eda0e062505861988f6ce63150207545ecf9678e0971" "330253dfb7cfd546c5346fec", 16, ), "y2": int( "715d0781975b7b03162f4401c1eda343fd9bf1140006034573b31828a618c" "356163554cd27da956f7179a69e860fb6efeaa2e2aa9f1261506a8344c492" "9953621381b13d6426e152c0f2f94bfcd2b758eca24923596d427ed8f957e" "8bc9b1c7d21a87ef02222a1477cf3bfaadc68106456ab9706026006eccd29" "0b21543de6bb97d5b8cf4ccee1c081a6d1dd27aaef060fa93888a47a4a416" "ad5c5bd490ea600e04379232fb1077fbf394f4579accdbe352714e25b8891" "6dca8d8f7e0c4ed9594f7693f656a235a2e88ebda48b0d557e32da9f12d2a" "4c3180f05b16b4fba9bec79278a3971b77f9223b5ab78b857e0376c500821" "1592c8c72d521373ee3b22b8", 16, ), "z": binascii.unhexlify( b"cf879ebd107bb877457809c3fc410218b7acba3c5967495a8f1c3370d57f" b"038a48dd69f9f69b9f4dd855e7c58a1e4ec32646a978266eb314db468ea1" b"dfcee8a85a1644a5732498c4fbcdf85098c6ed0ce12e431e99142fd23353" b"69b3f56620ada21aa69d883e82a0b5e35484dde32d17c2dc873f2cc5518e" b"b7fc19695dff9fc94c9d9432bb4b09d8180323cfc561ebc2d6eff8dd5f84" b"96f2b22377700a22bbfe61a6969c198129397454843e4fc3540026986039" b"665095490056287e4fc49e6cb3181cb2bf06444fd0040150271c9ce1f61c" b"13ecd5dd022194a2dbf3e1c7fbc6bd19497c7b888b4da613d28fa6f378a4" b"3369cb8795a1c823f7d6cf4d84bba578" ), }, { "fail_agree": True, "fail_z": False, "g": int( "35513ec441402b78353ab1bba550b21c76c89973885a627170262ef52497d" "5d137b8927a212aaab2f051198c90bb81dffd9eb10b36b7ca3b63565b4c10" "25aea3b5e9c4a348c9cfa17f3907a1e4469701c0dedb8a4b9e96c5965b1fb" "8c229b0c34baac774bf9dda4fc5ee8764358b3c84812878aab7464bc09e97" "aecab7d7e3fbb4870e2a3b89667a4158bf1ed1a90dfaf47019fbb52b1b963" "65bb4e1e9474993fe382fd23480dc875861be152997a621fdb7aef977ea5b" "4d3d74486b162dc28f95a64cf65587a919a57eef92934fc9410df7f09fa82" "f975328ed82ff29cc3e15a971f56f4ac2dcb289252575e02a6cdb7fcc6cdd" "d7b0dca9c422e63eb2b8f05", 16, ), "p": int( "f3722b9b911c6aede9eaeeaa406283de66a097f39a7225df6c3c916e57920" "d356e50478d307dbfd146bfb91b6f68ecbbcf54b3d19c33a4b17293fea3e3" "d6bff8ac4cca93a805386f062a8a27ae906ef5da94d279fd7b3d7289e0095" "6f76bae9c0d2b8d11742ca5809630632aae58f9c6dce00c7380581deffde2" "187b022f83c6ceaeaadb0844a17fcbb04039ca6843c91f0c9058b22434b26" "3c3dfda8de8429e087c5be97fc5c9db9526031ad3a218bd9916fb4a3c2796" "6d208b1e360014c01e95530c148fb3cd27e6a7250d3c3b81dcd220ca14548" "dbccf99ebb9e334db6bcd14e632c98dd3f9860af7ae450f1b7809b45f0ec1" "0e6f27672beebc9963befc73", 16, ), "q": int( "a9a17de95a29091bf8e07dab53ea1aba9403be3c61027c6c8f48bac5", 16 ), "x1": int( "1610eaa4e0ccc8857e2b53149e008492b1fbd9025a6e8d95aaee9c0f", 16 ), "x2": int( "c4c83d75b27864b052cadc556e500e25aabf0c9d1bc01f0e1fe3862", 16 ), "y1": int( "51ee21cd9f97015180f258fad5c94ff5a458806b1412087236bf77fe87aae" "1a36735816ed6e2160a731159814b6ae1f3f52c478dd9207094adfb62f766" "7d5c366327e66d23096395e938504db330953a708015f861fe9d948761109" "3b9fe7327518a7cc15994ab573313e154117c1a3ae88b8bdd1e316748249e" "4a9cbd1947f159836d13613d1f9449fc3442171d1970bc28958c1cafa2776" "a6f14ccdb29db02f64911bd83bfdcdfc843dd14a4cab9acb0bda8b293d2f5" "f7050768e57533cbc415a29e6f31cc365e107f91ae3722484e2c7329a85af" "69055a5a104da37e810878896d1b247b02b75234ecff82b1958f42d7b0316" "22e9394c98b5229112f7f620", 16, ), "y2": int( "467a857337a82472a1307a64dccc8e9994c5c63ec4312936885d17be41905" "1a5f037fbb052d7010ebe01634d9e8b8b522d9ab4749fdc274f465369b89e" "360df8f70b7865a3c71d2dbcd2df19e9293dab1153d3d63fcb7deb559b684" "dde6c6eed63214444807041c9a0ce3f52ca439ec16dd231995b5dc6f18e68" "01b6bd6454babccf9abbfacffb49c71e6494a4779cbfa550c5d7144114e6f" "c193f460dcd0be7e6e06e546da7653770dc5859df87029e722dbe81361030" "569148d1636988926bf0dcfe47c9d8a54698c08b3b5c70afe86b5c6f64346" "3f8f34889d27d6cfd2d478c2d7b3d008a985c7380f0b43f10024b59c35438" "80883c42d0e7e0a07326ba3a", 16, ), "z": binascii.unhexlify( b"10a30bacab82e652415376baffdbc008c7eb2e5a3aa68bc10ce486ca8498" b"3fd89b1b027bb40e75333406361005f5e756526a95fe01202df9217d81b1" b"713d5187c368fdd4c9c2433d9e6c18844769479b725c4140c92a304ee1bc" b"5726d8f5321b5b1c54a1a6b67c527e6817c0ed613a0d4e60db55de898788" b"b7e8d4aa9a81ab5ed7f6282962c433d246ed640555bdd76d29c287455126" b"4d74c76373f8a88871b41b041c98041b16f94f983ddf00f5bc7d2416d191" b"68c90178974a0602436cd186748bcc63a629edc3a0db59415cccd37a6513" b"0ea477c89da92d41371f5972891cf41f9c7f0e75ccbff9893225384db30d" b"aa5e310f08e3e0fad98bcdf8ecf35fe5" ), }, { "fail_agree": False, "fail_z": False, "g": int( "35513ec441402b78353ab1bba550b21c76c89973885a627170262ef5" "2497d5d137b8927a212aaab2f051198c90bb81dffd9eb10b36b7ca3b" "63565b4c1025aea3b5e9c4a348c9cfa17f3907a1e4469701c0dedb8a" "4b9e96c5965b1fb8c229b0c34baac774bf9dda4fc5ee8764358b3c84" "812878aab7464bc09e97aecab7d7e3fbb4870e2a3b89667a4158bf1e" "d1a90dfaf47019fbb52b1b96365bb4e1e9474993fe382fd23480dc87" "5861be152997a621fdb7aef977ea5b4d3d74486b162dc28f95a64cf6" "5587a919a57eef92934fc9410df7f09fa82f975328ed82ff29cc3e15" "a971f56f4ac2dcb289252575e02a6cdb7fcc6cddd7b0dca9c422e63e" "b2b8f05", 16, ), "p": int( "f3722b9b911c6aede9eaeeaa406283de66a097f39a7225df6c3c916e" "57920d356e50478d307dbfd146bfb91b6f68ecbbcf54b3d19c33a4b1" "7293fea3e3d6bff8ac4cca93a805386f062a8a27ae906ef5da94d279" "fd7b3d7289e00956f76bae9c0d2b8d11742ca5809630632aae58f9c6" "dce00c7380581deffde2187b022f83c6ceaeaadb0844a17fcbb04039" "ca6843c91f0c9058b22434b263c3dfda8de8429e087c5be97fc5c9db" "9526031ad3a218bd9916fb4a3c27966d208b1e360014c01e95530c14" "8fb3cd27e6a7250d3c3b81dcd220ca14548dbccf99ebb9e334db6bcd" "14e632c98dd3f9860af7ae450f1b7809b45f0ec10e6f27672beebc99" "63befc73", 16, ), "q": int( "a9a17de95a29091bf8e07dab53ea1aba9403be3c61027c6c8f48bac5", 16 ), "x1": int( "9ee22ac51664e40e0a24dbb94142dba40605e2b6eeaaa0268a0f6847", 16 ), "x2": int( "438093a468236658821bf64eb08456139963d4fb27121c3ed6c55876", 16 ), "y1": int( "c2630c9d38ed5c825d1c6a3eba7143f3fc8a049c8bcd1efc212d2af64eca9" "94308208691d330aa8f27fc4a1e55de4e512113996d21375a667f8c26d76d" "ee2f6809b15432a33fb735aca5c2263940f58712bded08f55443dee300b94" "89589e0462bd6bce19deaec4adc12fa61a694c8c5c999b28211d7835bac0f" "fd2b316850823e2dc1d1f58e05cbf75c673036d116b3f03b9687c89f9c2a0" "d43c4ffc9a605addbdcce0cb3790c6db846156bb857a7b3df40dc6ed04d19" "cc9eaebb6bbc034e77c3d882a1a62317cce25b6130f0803e3bc49b5e36768" "260073a617034872be0b50bed32740224beaf582d67fbcfef3b3ecc18f9c7" "1c782e9a68495ef31dc7986e", 16, ), "y2": int( "e192da8e1244e27221c1765344a5bb379dce741d427a734b4bdb6c4d16b24" "90bd37564d745008e63ae46ef332331d79887ac63298ce143e125f8b320c0" "f859b7f5f2c1e0053e4a7a16997e6143ff702300c9863ae7caef5c1dfca0e" "cf5197c557745b793f0790a4fe678aeb93fdb52490d4f273a5553944dda3a" "c8b9b792c9b67f8d7b9496398e432a423ae87ebeba688be3ed67eddd7575f" "a56431cd48579bf53c903bbe066dd78b23c0996ef3a880f0d91315104366a" "82f01abdecce96fd371f94e8420f8bc5b896c801df573554f749b03d0d28b" "1e1a990bc61c7e9659342ac7e268e9c0b7c40fdaab394f29cf0a54f780022" "f9a03b0bd28eb7db8b0b1b47", 16, ), "z": binascii.unhexlify( b"56f8f40fa4b8f3580f9014b30d60a42933a53a62182a690142f458dc275c" b"3b2f0e721bc5ee6e890b14516419110f5252ff1cceea8e274b2987aa78e3" b"bae90c1935b276b7a1f1c944f79d4774b7a85b3355bdf25cb02bddfbda4e" b"e7918bc93a5c9ca6d7e8fdedbda8e6c8a6ca794bad055a52b19c14895822" b"7344cbddd70271d4610316cfea1e559b0bc3a12d15023b30d9f2db602053" b"a0569c3bd2ce1faf59280ecd339f845dbcaaf2e883c5cc6263996f866b18" b"b75d049d4c82097af8a5ce353e14416b3eeb31ba9bc4f6f3dbd846c5299f" b"b5c0043a1b95b9149b39d14df9e6a69547abf8a4d518475576730ed52877" b"9366568e46b7dd4ed787cb72d0733c93" ), }, ] assert expected == load_kasvs_dh_vectors(vector_data) def test_load_kasvs_ecdh_vectors_empty_vector_data(): assert [] == load_kasvs_ecdh_vectors([]) def test_load_kasvs_ecdh_vectors(): vector_data = textwrap.dedent( """ # CAVS 11.0 # Parameter set(s) supported: EA EB EC ED EE # CAVSid: CAVSid (in hex: 434156536964) # IUTid: In hex: a1b2c3d4e5 [EA] [Curve selected: P-192] [SHA(s) supported (Used for hashing Z): SHA1] [EB] [Curve selected: P-224] [SHA(s) supported (Used for hashing Z): SHA224] [EC] [Curve selected: P-256] [SHA(s) supported (Used for hashing Z): SHA256] [ED] [Curve selected: P-384] [SHA(s) supported (Used for hashing Z): SHA384] [EE] [Curve selected: P-521] [SHA(s) supported (Used for hashing Z): SHA512] # Generated on Thu Mar 17 19:46:10 2011 [EA - SHA1] COUNT = 0 dsCAVS = f70c297a683d6b7ef82b5af7349606c4447c8b4fc6fa5e80 QsCAVSx = f7b5061fb557e516c50abf541d97dbfd76ca7172b22cf590 QsCAVSy = 135e15e21f9e85c76205fd148a92ac19f9e6243ddab322d1 dsIUT = a5b4bbad57f101ca48021cb7440cd681a9d40cd51b99d917 QsIUTx = 79a77fcb18a32cdb59ed5d87740f29e8565d649dbf01ce86 QsIUTy = f7187efaa0b1573f1fb00905d46810b880bf738b4c720bb7 Z = 26382468d721761e14a87dc3bee67340095c6455962d1ba3 CAVSHashZZ = af52ba700d3bbba7ce2916d6b729422c26c32364 Result = P (0 - Correct) COUNT = 2 dsCAVS = 5f909dcb0ccce58c82fada748c47297579e6a981b5518a96 QsCAVSx = 537f1ecfda0e366de393a9bc8188fcc280311bffefe21ecf QsCAVSy = a1fa1f98498d65f2754caff4e5303a4066a5ff89fde95381 dsIUT = 3357aa7f47f3e09421602cc12cdce4434c68e330d44de05e QsIUTx = 6a33d43d9c72173eabc7a771a5687748c4774c62762e96ec QsIUTy = 8033f238b3abc69470aad4be8dbe4f60a2fd50207626c56a Z = 3153034f6617326f19c35be8c99a0585431adf09d2f8e0fd CAVSHashZZ = f8414e30c2d382e28d2a57a2447fdc203baa416b Result = F (8 - Z changed ) COUNT = 8 dsCAVS = 8fcfaf0524cc868fad20e50410a2205319f1327308d98dc8 QsCAVSx = 9b0243d80a9e328738080fb4d46bc450243d0efb7ead0c92 QsCAVSy = ad5bebad7f03849693071537f60ef858cad214123beee7c7 dsIUT = bba95dac90289cb68ca2b006f9757219b70579c299ad7a7d QsIUTx = 7733dc0cb365cd6312724196b9b4eb491fd4d2e31b9afdb1 QsIUTy = 92ffa3722acc5b94d772258ba2d471b06c0f53f56fcd8662 Z = 0f3c6e4a29a08296ae730f56a1ebf819ea2edfa6f0434e40 CAVSHashZZ = c124545eed4b83a799e7e90371d806b5684a1bd2 Result = P (13 - Z value should have leading 0 nibble ) [EB - SHA224] COUNT = 0 dsCAVS = e53a88af7cf8ce6bf13c8b9ad191494e37a6acc1368c71f4306e39e5 QsCAVSx = 3a24217c4b957fea922eec9d9ac52d5cb4b3fcd95efde1e4fa0dd6e2 QsCAVSy = 775b94025a808eb6f4af14ea4b57dca576c35373c6dc198b15b981df dsIUT = 09f51e302c6a0fe6ff48f34c208c6af91e70f65f88102e6fcab9af4a QsIUTx = c5d5706ccd7424c74fd616e699865af96e56f39adea6aa059e5092b5 QsIUTy = f0729077bb602404d56d2f7e2ba5bb2f383df4a5425567881ff0165d Z = b1259ceedfb663d9515089cf727e7024fb3d86cbcec611b4ba0b4ab6 CAVSHashZZ = 8b21fd05a4b50e401908cd8f26757f5c57f22b69f170aa7381f8596d Result = P (0 - Correct) [EC - SHA256] COUNT = 0 dsCAVS = 305dfb4a8850cc59280891147baf457bfe5e2bae984571634a77dc8d3472fa9b QsCAVSx = 202cb5a224e6c2a84e624094486edf04116c8d68ec1f4a0e0ed9ee090e1a900b QsCAVSy = cacf3a5789bb33954be600425d62d9eae5371f90f88167258814213e4a4f4b1a dsIUT = 72cc52808f294b64b6f7233c3d2f5d96cc1d29287320e39e1c151deef0bc14eb QsIUTx = 49a768c9a4ca56e374f685dd76a461b1016c59dcded2c8d8cbd9f23ca453831f QsIUTy = b1e3bb9b5f12a3b5ae788535d4554bd8c46e0e6130075e4e437d3854cf8f1c34 Z = c0147c3c2691b450b5edc08b51aea224d9f4359ff67aab6da3146f396dbceaea CAVSHashZZ = ea9ffd54511979ab8c4b387784972cbd05fc5fd4ff78e048b0026557b56a5\ 1dd Result = F (2 - CAVS's Static public key Y fails PKV 5.6.2.5) [ED - SHA384] COUNT = 0 dsCAVS = 0e5c98ff2d2a3aab14ad0067b60dbe64e4f541ab5bed11c5a0c55ae1e60b51ff5\ faaf377837977d80cbfdc33c2ff542b QsCAVSx = d1bf2ac21637d66d6398aac01dcd56ac6f065fb45d1f6f16747bab9e9b01b463\ 0b59b20927aea147355bf41838acb482 QsCAVSy = 4c9e23f1c5a41647d094086bf4ed31708651f21d996c47780688ac10f77deee2\ e43b5241b6caecd2fd5444bc50472e0e dsIUT = f865418473e5bf7d2e1bbcd9bd5a9270c003a9dd35e778133ca59fcab4bb64fe24\ d6800e7047bdd033abc8bfa8db35b5 QsIUTx = 32b72ab9b558249dcbc6cbade234f58e4f7aa5d3f6420ea99a5f997e8c2a91fb7\ fd83779d0d2169428683771c745fd1a QsIUTy = c749e02a3719bb56bf1dfc4ba3820309c01ab6e84cb29db7cdd80f127233f5295\ 687f8178f3a8704c1063b84c2ee472f Z = a781430e6078a179df3f9ee27cd8fdc6188f161b6c4ccc4053ef6c6ca6fc222946883a\ 53c06db08f0a020023ced055aa CAVSHashZZ = ccb70d0adbabe4d8956519db0d536605cbb366aed58fc55718f56ae3648fa\ 5c9ee7bae56cc463587cb74e2f9c6ace1cb Result = P (0 - Correct) [EE - SHA512] COUNT = 0 dsCAVS = 0000002fef62381162942889a6094a6bb9ac1f4ddf66d9cda9f618232d31b90c5\ 0d7da78a47ed91d40cae946898571db972dc294b109815f38feee9eaac0d5f7c3250728 QsCAVSx = 0000004b05ffa025113390797f2736174aa1c784f4dd34e764ee40d40e4d2442\ 677ebea3498086c9473e5c92789cbdb02bb327bbd61d58690f6a83d9ca73bccbde37dec4 QsCAVSy = 0000004da67cffc98070b82af61feba78787efefb13bd810d80ff92304788e49\ a4e5b634b3565474a8ecb1615d7b1b77a7a27875adb73a8a5d8f3f84e5e8b744cda250b0 dsIUT = 00000311a5e520e238141527671a38cb6f776d96a9f82ef70dffa11dc0895f4060\ f1abbb9ad6fd259e4a7beaf5f7266ea1bb45bcbfebfda2705e5c551e710fb1d745f57e QsIUTx = 0000010ba3778cb2cc965834c0a9593adc6a222692656d657fb0d15293edf0ab3\ 3762384a96a16fddea7540b7ccbcca46ec4ac9bcf95fdb5aa18e158aab4d91981bd733e QsIUTy = 0000018522df93ddd636e5bc94daecdc600fa241686ec18634fd30b7cbdfdc9ff\ ba1166ac08df34a31896f6fad191414929261ebd7187afb72919f8a0c926be37f99c1e5 Z = 01a5e4b31be4b1346e53906b6767b1fe94ec1a8a5abc28fb6f01518c056959af3bc933\ 5dddab178b52318cc5512559931b8dc18de0ce810c2c7f15769d7ce70e719c CAVSHashZZ = d2d6538feb65d609f377b81a027dc800eed07b69c0e9eedb243369202ed47\ f47021022a6c9b45ed791d09d9540eb81ea065fc1959eca365001ee39928c343d75 Result = F (7 - IUT's Static private key d changed-prikey validity) """ ).splitlines() expected = [ { "errno": 0, "fail": False, "COUNT": 0, "CAVS": { "d": int( "f70c297a683d6b7ef82b5af7349606c4447c8b4fc6fa5e80", 16 ), "x": int( "f7b5061fb557e516c50abf541d97dbfd76ca7172b22cf590", 16 ), "y": int( "135e15e21f9e85c76205fd148a92ac19f9e6243ddab322d1", 16 ), }, "IUT": { "d": int( "a5b4bbad57f101ca48021cb7440cd681a9d40cd51b99d917", 16 ), "x": int( "79a77fcb18a32cdb59ed5d87740f29e8565d649dbf01ce86", 16 ), "y": int( "f7187efaa0b1573f1fb00905d46810b880bf738b4c720bb7", 16 ), }, "Z": int("26382468d721761e14a87dc3bee67340095c6455962d1ba3", 16), "curve": "secp192r1", }, { "errno": 8, "fail": True, "COUNT": 2, "CAVS": { "d": int( "5f909dcb0ccce58c82fada748c47297579e6a981b5518a96", 16 ), "x": int( "537f1ecfda0e366de393a9bc8188fcc280311bffefe21ecf", 16 ), "y": int( "a1fa1f98498d65f2754caff4e5303a4066a5ff89fde95381", 16 ), }, "IUT": { "d": int( "3357aa7f47f3e09421602cc12cdce4434c68e330d44de05e", 16 ), "x": int( "6a33d43d9c72173eabc7a771a5687748c4774c62762e96ec", 16 ), "y": int( "8033f238b3abc69470aad4be8dbe4f60a2fd50207626c56a", 16 ), }, "Z": int("3153034f6617326f19c35be8c99a0585431adf09d2f8e0fd", 16), "curve": "secp192r1", }, { "errno": 13, "fail": False, "COUNT": 8, "CAVS": { "d": int( "8fcfaf0524cc868fad20e50410a2205319f1327308d98dc8", 16 ), "x": int( "9b0243d80a9e328738080fb4d46bc450243d0efb7ead0c92", 16 ), "y": int( "ad5bebad7f03849693071537f60ef858cad214123beee7c7", 16 ), }, "IUT": { "d": int( "bba95dac90289cb68ca2b006f9757219b70579c299ad7a7d", 16 ), "x": int( "7733dc0cb365cd6312724196b9b4eb491fd4d2e31b9afdb1", 16 ), "y": int( "92ffa3722acc5b94d772258ba2d471b06c0f53f56fcd8662", 16 ), }, "Z": int("0f3c6e4a29a08296ae730f56a1ebf819ea2edfa6f0434e40", 16), "curve": "secp192r1", }, { "errno": 0, "fail": False, "COUNT": 0, "CAVS": { "d": int( "e53a88af7cf8ce6bf13c8b9ad191494e37a6acc1368c71f4" "306e39e5", 16, ), "x": int( "3a24217c4b957fea922eec9d9ac52d5cb4b3fcd95efde1e4" "fa0dd6e2", 16, ), "y": int( "775b94025a808eb6f4af14ea4b57dca576c35373c6dc198b" "15b981df", 16, ), }, "IUT": { "d": int( "09f51e302c6a0fe6ff48f34c208c6af91e70f65f88102e6f" "cab9af4a", 16, ), "x": int( "c5d5706ccd7424c74fd616e699865af96e56f39adea6aa05" "9e5092b5", 16, ), "y": int( "f0729077bb602404d56d2f7e2ba5bb2f383df4a542556788" "1ff0165d", 16, ), }, "Z": int( "b1259ceedfb663d9515089cf727e7024fb3d86cbcec611b4ba0b4ab6", 16, ), "curve": "secp224r1", }, { "errno": 2, "fail": True, "COUNT": 0, "CAVS": { "d": int( "305dfb4a8850cc59280891147baf457bfe5e2bae98457163" "4a77dc8d3472fa9b", 16, ), "x": int( "202cb5a224e6c2a84e624094486edf04116c8d68ec1f4a0e" "0ed9ee090e1a900b", 16, ), "y": int( "cacf3a5789bb33954be600425d62d9eae5371f90f8816725" "8814213e4a4f4b1a", 16, ), }, "IUT": { "d": int( "72cc52808f294b64b6f7233c3d2f5d96cc1d29287320e39e" "1c151deef0bc14eb", 16, ), "x": int( "49a768c9a4ca56e374f685dd76a461b1016c59dcded2c8d8" "cbd9f23ca453831f", 16, ), "y": int( "b1e3bb9b5f12a3b5ae788535d4554bd8c46e0e6130075e4e" "437d3854cf8f1c34", 16, ), }, "Z": int( "c0147c3c2691b450b5edc08b51aea224d9f4359ff67aab6d" "a3146f396dbceaea", 16, ), "curve": "secp256r1", }, { "errno": 0, "fail": False, "COUNT": 0, "CAVS": { "d": int( "0e5c98ff2d2a3aab14ad0067b60dbe64e4f541ab5bed11c5" "a0c55ae1e60b51ff5faaf377837977d80cbfdc33c2ff542b", 16, ), "x": int( "d1bf2ac21637d66d6398aac01dcd56ac6f065fb45d1f6f16" "747bab9e9b01b4630b59b20927aea147355bf41838acb482", 16, ), "y": int( "4c9e23f1c5a41647d094086bf4ed31708651f21d996c4778" "0688ac10f77deee2e43b5241b6caecd2fd5444bc50472e0e", 16, ), }, "IUT": { "d": int( "f865418473e5bf7d2e1bbcd9bd5a9270c003a9dd35e77813" "3ca59fcab4bb64fe24d6800e7047bdd033abc8bfa8db35b5", 16, ), "x": int( "32b72ab9b558249dcbc6cbade234f58e4f7aa5d3f6420ea9" "9a5f997e8c2a91fb7fd83779d0d2169428683771c745fd1a", 16, ), "y": int( "c749e02a3719bb56bf1dfc4ba3820309c01ab6e84cb29db7" "cdd80f127233f5295687f8178f3a8704c1063b84c2ee472f", 16, ), }, "Z": int( "a781430e6078a179df3f9ee27cd8fdc6188f161b6c4ccc40" "53ef6c6ca6fc222946883a53c06db08f0a020023ced055aa", 16, ), "curve": "secp384r1", }, { "errno": 7, "fail": True, "COUNT": 0, "CAVS": { "d": int( "0000002fef62381162942889a6094a6bb9ac1f4ddf66d9cd" "a9f618232d31b90c50d7da78a47ed91d40cae946898571db" "972dc294b109815f38feee9eaac0d5f7c3250728", 16, ), "x": int( "0000004b05ffa025113390797f2736174aa1c784f4dd34e7" "64ee40d40e4d2442677ebea3498086c9473e5c92789cbdb0" "2bb327bbd61d58690f6a83d9ca73bccbde37dec4", 16, ), "y": int( "0000004da67cffc98070b82af61feba78787efefb13bd810" "d80ff92304788e49a4e5b634b3565474a8ecb1615d7b1b77" "a7a27875adb73a8a5d8f3f84e5e8b744cda250b0", 16, ), }, "IUT": { "d": int( "00000311a5e520e238141527671a38cb6f776d96a9f82ef7" "0dffa11dc0895f4060f1abbb9ad6fd259e4a7beaf5f7266e" "a1bb45bcbfebfda2705e5c551e710fb1d745f57e", 16, ), "x": int( "0000010ba3778cb2cc965834c0a9593adc6a222692656d65" "7fb0d15293edf0ab33762384a96a16fddea7540b7ccbcca4" "6ec4ac9bcf95fdb5aa18e158aab4d91981bd733e", 16, ), "y": int( "0000018522df93ddd636e5bc94daecdc600fa241686ec186" "34fd30b7cbdfdc9ffba1166ac08df34a31896f6fad191414" "929261ebd7187afb72919f8a0c926be37f99c1e5", 16, ), }, "Z": int( "01a5e4b31be4b1346e53906b6767b1fe94ec1a8a5abc28fb" "6f01518c056959af3bc9335dddab178b52318cc551255993" "1b8dc18de0ce810c2c7f15769d7ce70e719c", 16, ), "curve": "secp521r1", }, ] assert expected == load_kasvs_ecdh_vectors(vector_data) def test_load_kasvs_ecdh_kdf_vectors(): vector_data = textwrap.dedent( """ # Parameter set(s) supported: EB EC ED EE # CAVSid: CAVSid (in hex: 434156536964) # IUTid: In hex: a1b2c3d4e5 [EB] [Curve selected: P-224] [SHA(s) supported (Used in the KDF function): SHA224 SHA256 SHA384 SHA512] [MAC algorithm supported: HMAC] [HMAC SHAs supported: SHA512] [HMACKeySize(in bits): 112] [HMAC Tag length(in bits): 64] # Generated on Mon Dec 22 11:45:18 2014 [EB - SHA224] COUNT = 50 dsCAVS = 540904b67b3716823dd621ed72ad3dbc615887b4f56f910b78a57199 QsCAVSx = 28e5f3a72d8f6b8499dd1bcdfceafcecec68a0d715789bcf4b55fe15 QsCAVSy = 8c8006a7da7c1a19f5328d7e865522b0c0dfb9a29b2c46dc96590d2a Nonce = 4eefb2a29a0e89c3898a7affdfa60dd7 dsIUT = 5e717ae889fc8d67be11c2ebe1a7d3550051448d68a040b2dee8e327 QsIUTx = ae7f3db340b647d61713f5374c019f1be2b28573cb6219bb7b747223 QsIUTy = 800e6bffcf97c15864ec6e5673fb83359b45f89b8a26a27f6f3dfbff NonceDKMIUT = bb7f1b40d14ebd70443393990b57 OI = a1b2c3d4e5bb7f1b40d14ebd70443393990b574341565369645b1582daab9cc6c30d6\ 1fdcf1cdfc7e9a304651e0fdb CAVSTag = 84de198c3a958c62 Z = 43f23b2c760d686fc99cc008b63aea92f866e224265af60d2d8ae540 MacData = 5374616e646172642054657374204d6573736167654eefb2a29a0e89c3898a7a\ ffdfa60dd7 DKM = ad65fa2d12541c3a21f3cd223efb Result = F (12 - Tag changed ) """ ).splitlines() expected = [ { "errno": 12, "fail": True, "COUNT": 50, "CAVS": { "d": int( "540904b67b3716823dd621ed72ad3dbc615887b4f56f910b" "78a57199", 16, ), "x": int( "28e5f3a72d8f6b8499dd1bcdfceafcecec68a0d715789bcf" "4b55fe15", 16, ), "y": int( "8c8006a7da7c1a19f5328d7e865522b0c0dfb9a29b2c46dc" "96590d2a", 16, ), }, "IUT": { "d": int( "5e717ae889fc8d67be11c2ebe1a7d3550051448d68a040b2" "dee8e327", 16, ), "x": int( "ae7f3db340b647d61713f5374c019f1be2b28573cb6219bb" "7b747223", 16, ), "y": int( "800e6bffcf97c15864ec6e5673fb83359b45f89b8a26a27f" "6f3dfbff", 16, ), }, "OI": int( "a1b2c3d4e5bb7f1b40d14ebd70443393990b574341565369" "645b1582daab9cc6c30d61fdcf1cdfc7e9a304651e0fdb", 16, ), "Z": int( "43f23b2c760d686fc99cc008b63aea92f866e224265af60d2d8ae540", 16, ), "DKM": int("ad65fa2d12541c3a21f3cd223efb", 16), "curve": "secp224r1", } ] assert expected == load_kasvs_ecdh_vectors(vector_data) def test_load_x963_vectors(): vector_data = textwrap.dedent( """ # CAVS 12.0 # 'ANS X9.63-2001' information for sample [SHA-1] [shared secret length = 192] [SharedInfo length = 0] [key data length = 128] COUNT = 0 Z = 1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd SharedInfo = Counter = 00000001 Hash input 1 = 1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd00000001 K1 = bf71dffd8f4d99223936beb46fee8ccc60439b7e key_data = bf71dffd8f4d99223936beb46fee8ccc COUNT = 1 Z = 5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab1 SharedInfo = Counter = 00000001 Hash input 1 = 5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab100000001 K1 = ec3e224446bfd7b3be1df404104af953c1b2d0f5 key_data = ec3e224446bfd7b3be1df404104af953 [SHA-512] [shared secret length = 521] [SharedInfo length = 128] [key data length = 1024] COUNT = 0 Z = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb369348b8150739\ 2f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d SharedInfo = e3b5b4c1b0d5cf1d2b3a2f9937895d31 Counter = 00000001 Hash input 1 = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9ad\ b369348b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d0\ 0000001e3b5b4c1b0d5cf1d2b3a2f9937895d31 K1 = 4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733633d6\ e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee20800226 Counter = 00000002 Hash input 2 = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9ad\ b369348b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d0\ 0000002e3b5b4c1b0d5cf1d2b3a2f9937895d31 K2 = 7089dbf351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1\ a59b4106cf2dbbc0ab2aa8e2efa7b17902d34276951ceccab87f9661c3e8816 key_data = 4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733633\ d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee208002267089dbf351f3f\ 5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1a59b4106cf2dbbc0ab2aa8e2ef\ a7b17902d34276951ceccab87f9661c3e8816 """ ).splitlines() assert load_x963_vectors(vector_data) == [ { "hash": "SHA-1", "count": 0, "shared_secret_length": 192, "Z": "1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd", "sharedinfo_length": 0, "key_data_length": 128, "key_data": "bf71dffd8f4d99223936beb46fee8ccc", }, { "hash": "SHA-1", "count": 1, "shared_secret_length": 192, "Z": "5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab1", "sharedinfo_length": 0, "key_data_length": 128, "key_data": "ec3e224446bfd7b3be1df404104af953", }, { "hash": "SHA-512", "count": 0, "shared_secret_length": 521, "Z": ( "00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb3693" "48b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e7" "53f55ef05a2d" ), "sharedinfo_length": 128, "sharedinfo": "e3b5b4c1b0d5cf1d2b3a2f9937895d31", "key_data_length": 1024, "key_data": ( "4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733" "633d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee" "208002267089dbf351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda" "33ca08bd56dd1a59b4106cf2dbbc0ab2aa8e2efa7b17902d34276951cecc" "ab87f9661c3e8816" ), }, ] def test_load_kbkdf_vectors(): vector_data = textwrap.dedent( """ # CAVS 14.4 # "SP800-108 - KDF" information for "test1" # KDF Mode Supported: Counter Mode # Location of counter tested: (Before Fixed Input Data)\ ( After Fixed Input Data)(In Middle of Fixed Input Data before Context) # PRFs tested: CMAC with key sizes: AES128 AES192 AES256 TDES2 TDES3\ HMAC with key sizes: SHA1 SHA224 SHA256 SHA384 SHA512 # Generated on Tue Apr 23 12:20:16 2013 [PRF=HMAC_SHA1] [CTRLOCATION=BEFORE_FIXED] [RLEN=8_BITS] COUNT=0 L = 128 KI = 00a39bd547fb88b2d98727cf64c195c61e1cad6c FixedInputDataByteLen = 60 FixedInputData = 98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f68\ 76f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242 Binary rep of i = 01 instring = 0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f68\ 76f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242 KO = 0611e1903609b47ad7a5fc2c82e47702 COUNT=1 L = 128 KI = a39bdf744ed7e33fdec060c8736e9725179885a8 FixedInputDataByteLen = 60 FixedInputData = af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182\ 35019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832 Binary rep of i = 01 instring = 01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182\ 35019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832 KO = 51dc4668947e3685099bc3b5f8527468 [PRF=HMAC_SHA224] [CTRLOCATION=AFTER_FIXED] [RLEN=8_BITS] COUNT=0 L = 128 KI = ab56556b107a3a79fe084df0f1bb3ad049a6cc1490f20da4b3df282c FixedInputDataByteLen = 60 FixedInputData = 7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91ee\ b5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a7 Binary rep of i = 01 instring = 7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91eeb5\ 730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a701 KO = b8894c6133a46701909b5c8a84322dec """ ).splitlines() assert load_nist_kbkdf_vectors(vector_data) == [ { "prf": "hmac_sha1", "ctrlocation": "before_fixed", "rlen": 8, "l": 128, "ki": b"00a39bd547fb88b2d98727cf64c195c61e1cad6c", "fixedinputdatabytelen": b"60", "fixedinputdata": ( b"98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f6876f" b"59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a682" b"42" ), "binary rep of i": b"01", "instring": ( b"0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f687" b"6f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a6" b"8242" ), "ko": b"0611e1903609b47ad7a5fc2c82e47702", }, { "prf": "hmac_sha1", "ctrlocation": "before_fixed", "rlen": 8, "l": 128, "ki": b"a39bdf744ed7e33fdec060c8736e9725179885a8", "fixedinputdatabytelen": b"60", "fixedinputdata": ( b"af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182350" b"19f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e8" b"32" ), "binary rep of i": b"01", "instring": ( b"01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e1823" b"5019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045" b"e832" ), "ko": b"51dc4668947e3685099bc3b5f8527468", }, { "prf": "hmac_sha224", "ctrlocation": "after_fixed", "rlen": 8, "l": 128, "ki": b"ab56556b107a3a79fe084df0f1bb3ad049a6cc1490f20da4b3df282c", "fixedinputdatabytelen": b"60", "fixedinputdata": ( b"7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91eeb57" b"30d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4" b"a7" ), "binary rep of i": b"01", "instring": ( b"7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91eeb57" b"30d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4" b"a701" ), "ko": b"b8894c6133a46701909b5c8a84322dec", }, ] def test_load_nist_ccm_vectors_dvpt(): vector_data = textwrap.dedent( """ # CAVS 11.0 # "CCM-DVPT" information # AES Keylen: 128 # Generated on Tue Mar 15 08:09:25 2011 [Alen = 0, Plen = 0, Nlen = 7, Tlen = 4] Key = 4ae701103c63deca5b5a3939d7d05992 Count = 0 Nonce = 5a8aa485c316e9 Adata = 00 CT = 02209f55 Result = Pass Payload = 00 Count = 1 Nonce = 3796cf51b87266 Adata = 00 CT = 9a04c241 Result = Fail [Alen = 0, Plen = 0, Nlen = 7, Tlen = 16] Key = 4bb3c4a4f893ad8c9bdc833c325d62b3 Count = 15 Nonce = 5a8aa485c316e9 Adata = 00 CT = 75d582db43ce9b13ab4b6f7f14341330 Result = Pass Payload = 00 Count = 16 Nonce = 3796cf51b87266 Adata = 00 CT = 3a65e03af37b81d05acc7ec1bc39deb0 Result = Fail """ ).splitlines() assert load_nist_ccm_vectors(vector_data) == [ { "key": b"4ae701103c63deca5b5a3939d7d05992", "alen": 0, "plen": 0, "nlen": 7, "tlen": 4, "nonce": b"5a8aa485c316e9", "adata": b"00", "ct": b"02209f55", "fail": False, "payload": b"00", }, { "key": b"4ae701103c63deca5b5a3939d7d05992", "alen": 0, "plen": 0, "nlen": 7, "tlen": 4, "nonce": b"3796cf51b87266", "adata": b"00", "ct": b"9a04c241", "fail": True, "payload": b"00", }, { "key": b"4bb3c4a4f893ad8c9bdc833c325d62b3", "alen": 0, "plen": 0, "nlen": 7, "tlen": 16, "nonce": b"5a8aa485c316e9", "adata": b"00", "ct": b"75d582db43ce9b13ab4b6f7f14341330", "fail": False, "payload": b"00", }, { "key": b"4bb3c4a4f893ad8c9bdc833c325d62b3", "alen": 0, "plen": 0, "nlen": 7, "tlen": 16, "nonce": b"3796cf51b87266", "adata": b"00", "ct": b"3a65e03af37b81d05acc7ec1bc39deb0", "fail": True, "payload": b"00", }, ] def test_load_nist_ccm_vectors_vadt(): vector_data = textwrap.dedent( """ # CAVS 11.0 # "CCM-VADT" information # AES Keylen: 128 # Generated on Tue Mar 15 08:09:24 2011 Plen = 24 Nlen = 13 Tlen = 16 [Alen = 0] Key = d24a3d3dde8c84830280cb87abad0bb3 Nonce = f1100035bb24a8d26004e0e24b Count = 0 Adata = 00 Payload = 7c86135ed9c2a515aaae0e9a208133897269220f30870006 CT = 1faeb0ee2ca2cd52f0aa3966578344f24e69b742c4ab37ab11233 Count = 1 Adata = 00 Payload = 48df73208cdc63d716752df7794807b1b2a80794a2433455 CT = 2bf7d09079bc0b904c711a0b0e4a70ca8ea892d9566f03f8b77a1 CT = 642145210f947bc4a0b1e678fd8c990c2c1d89d4110a95c954d61 [Alen = 1] Key = 08b0da255d2083808a1b4d367090bacc Nonce = 777828b13679a9e2ca89568233 Count = 10 Adata = dd Payload = 1b156d7e2bf7c9a25ad91cff7b0b02161cb78ff9162286b0 CT = e8b80af4960d5417c15726406e345c5c46831192b03432eed16b6 Count = 11 Adata = c5 Payload = 032fee9dbffccc751e6a1ee6d07bb218b3a7ec6bf5740ead CT = f0828917020651c085e42459c544ec52e99372005362baf308ebe """ ).splitlines() assert load_nist_ccm_vectors(vector_data) == [ { "plen": 24, "nlen": 13, "tlen": 16, "alen": 0, "key": b"d24a3d3dde8c84830280cb87abad0bb3", "nonce": b"f1100035bb24a8d26004e0e24b", "adata": b"00", "payload": b"7c86135ed9c2a515aaae0e9a208133897269220f30870006", "ct": b"1faeb0ee2ca2cd52f0aa3966578344f24e69b742c4ab37ab11233", }, { "plen": 24, "nlen": 13, "tlen": 16, "alen": 0, "key": b"d24a3d3dde8c84830280cb87abad0bb3", "nonce": b"f1100035bb24a8d26004e0e24b", "adata": b"00", "payload": b"48df73208cdc63d716752df7794807b1b2a80794a2433455", "ct": b"642145210f947bc4a0b1e678fd8c990c2c1d89d4110a95c954d61", }, { "plen": 24, "nlen": 13, "tlen": 16, "alen": 1, "key": b"08b0da255d2083808a1b4d367090bacc", "nonce": b"777828b13679a9e2ca89568233", "adata": b"dd", "payload": b"1b156d7e2bf7c9a25ad91cff7b0b02161cb78ff9162286b0", "ct": b"e8b80af4960d5417c15726406e345c5c46831192b03432eed16b6", }, { "plen": 24, "nlen": 13, "tlen": 16, "alen": 1, "key": b"08b0da255d2083808a1b4d367090bacc", "nonce": b"777828b13679a9e2ca89568233", "adata": b"c5", "payload": b"032fee9dbffccc751e6a1ee6d07bb218b3a7ec6bf5740ead", "ct": b"f0828917020651c085e42459c544ec52e99372005362baf308ebe", }, ] def test_vector_version(): assert cryptography.__version__ == cryptography_vectors.__version__ def test_raises_unsupported_algorithm_wrong_type(): # Check that it raises if the wrong type of exception is raised. class TestException(Exception): pass with pytest.raises(TestException): with raises_unsupported_algorithm(None): raise TestException def test_raises_unsupported_algorithm_wrong_reason(): # Check that it fails if the wrong reason code is raised. with pytest.raises(AssertionError): with raises_unsupported_algorithm(None): raise UnsupportedAlgorithm( "An error.", _Reasons.BACKEND_MISSING_INTERFACE ) def test_raises_unsupported_no_exc(): # Check that it fails if no exception is raised. with pytest.raises(pytest.fail.Exception): with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): pass def test_raises_unsupported_algorithm(): # Check that it doesn't assert if the right things are raised. with raises_unsupported_algorithm( _Reasons.BACKEND_MISSING_INTERFACE ) as exc_info: raise UnsupportedAlgorithm( "An error.", _Reasons.BACKEND_MISSING_INTERFACE ) assert exc_info.type is UnsupportedAlgorithm class TestDeprecated: def test_getattr(self): with pytest.warns(DeprecationWarning): assert deprecated_module.DEPRECATED == 3 assert deprecated_module.NOT_DEPRECATED == 12 def test_inspect_deprecated_module(self): # Check if inspection is supported by _ModuleWithDeprecations. assert isinstance( deprecated_module, cryptography.utils._ModuleWithDeprecations ) source_file = inspect.getsourcefile(deprecated_module) assert isinstance(source_file, str) assert source_file.endswith("deprecated_module.py") cryptography-43.0.0/tests/test_warnings.py010064400017510000177000000055451464676315000171450ustar 00000000000000# 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. import sys import types import typing import warnings import pytest from cryptography.utils import deprecated class TestDeprecated: @typing.no_type_check def test_deprecated(self, monkeypatch): mod = types.ModuleType("TestDeprecated/test_deprecated") monkeypatch.setitem(sys.modules, mod.__name__, mod) deprecated( name="X", value=1, module_name=mod.__name__, message="deprecated message text", warning_class=DeprecationWarning, ) mod.Y = deprecated( value=2, module_name=mod.__name__, message="more deprecated text", warning_class=PendingDeprecationWarning, ) mod = sys.modules[mod.__name__] mod.Z = 3 with warnings.catch_warnings(record=True) as log: warnings.simplefilter("always", PendingDeprecationWarning) warnings.simplefilter("always", DeprecationWarning) assert mod.X == 1 assert mod.Y == 2 assert mod.Z == 3 [msg1, msg2] = log assert msg1.category is DeprecationWarning assert msg1.message.args == ("deprecated message text",) assert msg2.category is PendingDeprecationWarning assert msg2.message.args == ("more deprecated text",) assert "Y" in dir(mod) @typing.no_type_check def test_deleting_deprecated_members(self, monkeypatch): mod = types.ModuleType("TestDeprecated/test_deprecated") monkeypatch.setitem(sys.modules, mod.__name__, mod) deprecated( name="X", value=1, module_name=mod.__name__, message="deprecated message text", warning_class=DeprecationWarning, ) mod.Y = deprecated( value=2, module_name=mod.__name__, message="more deprecated text", warning_class=PendingDeprecationWarning, ) mod = sys.modules[mod.__name__] mod.Z = 3 with warnings.catch_warnings(record=True) as log: warnings.simplefilter("always", PendingDeprecationWarning) warnings.simplefilter("always", DeprecationWarning) del mod.X del mod.Y del mod.Z [msg1, msg2] = log assert msg1.category is DeprecationWarning assert msg1.message.args == ("deprecated message text",) assert msg2.category is PendingDeprecationWarning assert msg2.message.args == ("more deprecated text",) assert "X" not in dir(mod) assert "Y" not in dir(mod) assert "Z" not in dir(mod) with pytest.raises(AttributeError): del mod.X cryptography-43.0.0/tests/utils.py010064400017510000177000000757331464676315000154240ustar 00000000000000# 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. import binascii import collections import json import os import re import typing from contextlib import contextmanager import pytest import cryptography_vectors from cryptography.exceptions import UnsupportedAlgorithm HashVector = collections.namedtuple("HashVector", ["message", "digest"]) KeyedHashVector = collections.namedtuple( "KeyedHashVector", ["message", "digest", "key"] ) def check_backend_support(backend, item): for mark in item.node.iter_markers("supported"): if not mark.kwargs["only_if"](backend): pytest.skip("{} ({})".format(mark.kwargs["skip_message"], backend)) @contextmanager def raises_unsupported_algorithm(reason): with pytest.raises(UnsupportedAlgorithm) as exc_info: yield exc_info assert exc_info.value._reason == reason T = typing.TypeVar("T") def load_vectors_from_file( filename, loader: typing.Callable[..., T], mode="r" ) -> T: with cryptography_vectors.open_vector_file(filename, mode) as vector_file: return loader(vector_file) def load_nist_vectors(vector_data): test_data = {} data = [] for line in vector_data: line = line.strip() # Blank lines, comments, and section headers are ignored if ( not line or line.startswith("#") or (line.startswith("[") and line.endswith("]")) ): continue if line.strip() == "FAIL": test_data["fail"] = True continue # Build our data using a simple Key = Value format name, value = (c.strip() for c in line.split("=")) # Some tests (PBKDF2) contain \0, which should be interpreted as a # null character rather than literal. value = value.replace("\\0", "\0") # COUNT is a special token that indicates a new block of data if name.upper() == "COUNT": test_data = {} data.append(test_data) continue # For all other tokens we simply want the name, value stored in # the dictionary else: test_data[name.lower()] = value.encode("ascii") return data def load_cryptrec_vectors(vector_data): cryptrec_list = [] for line in vector_data: line = line.strip() # Blank lines and comments are ignored if not line or line.startswith("#"): continue if line.startswith("K"): key = line.split(" : ")[1].replace(" ", "").encode("ascii") elif line.startswith("P"): pt = line.split(" : ")[1].replace(" ", "").encode("ascii") elif line.startswith("C"): ct = line.split(" : ")[1].replace(" ", "").encode("ascii") # after a C is found the K+P+C tuple is complete # there are many P+C pairs for each K cryptrec_list.append( {"key": key, "plaintext": pt, "ciphertext": ct} ) else: raise ValueError(f"Invalid line in file '{line}'") return cryptrec_list def load_hash_vectors(vector_data): vectors: typing.List[typing.Union[KeyedHashVector, HashVector]] = [] key = None msg = None md = None for line in vector_data: line = line.strip() if not line or line.startswith("#") or line.startswith("["): continue if line.startswith("Len"): length = int(line.split(" = ")[1]) elif line.startswith("Key"): # HMAC vectors contain a key attribute. Hash vectors do not. key = line.split(" = ")[1].encode("ascii") elif line.startswith("Msg"): # In the NIST vectors they have chosen to represent an empty # string as hex 00, which is of course not actually an empty # string. So we parse the provided length and catch this edge case. msg = line.split(" = ")[1].encode("ascii") if length > 0 else b"" elif line.startswith("MD") or line.startswith("Output"): md = line.split(" = ")[1] # after MD is found the Msg+MD (+ potential key) tuple is complete if key is not None: vectors.append(KeyedHashVector(msg, md, key)) key = None msg = None md = None else: vectors.append(HashVector(msg, md)) msg = None md = None else: raise ValueError("Unknown line in hash vector") return vectors def load_pkcs1_vectors(vector_data): """ Loads data out of RSA PKCS #1 vector files. """ private_key_vector: typing.Optional[typing.Dict[str, typing.Any]] = None public_key_vector: typing.Optional[typing.Dict[str, typing.Any]] = None attr = None key: typing.Any = None example_vector: typing.Optional[typing.Dict[str, typing.Any]] = None examples = [] vectors = [] for line in vector_data: if ( line.startswith("# PSS Example") or line.startswith("# OAEP Example") or line.startswith("# PKCS#1 v1.5") ): if example_vector: for key, value in example_vector.items(): hex_bytes = "".join(value).replace(" ", "").encode("ascii") example_vector[key] = hex_bytes examples.append(example_vector) attr = None example_vector = collections.defaultdict(list) if line.startswith("# Message"): attr = "message" continue elif line.startswith("# Salt"): attr = "salt" continue elif line.startswith("# Seed"): attr = "seed" continue elif line.startswith("# Signature"): attr = "signature" continue elif line.startswith("# Encryption"): attr = "encryption" continue elif example_vector and line.startswith( "# =============================================" ): for key, value in example_vector.items(): hex_bytes = "".join(value).replace(" ", "").encode("ascii") example_vector[key] = hex_bytes examples.append(example_vector) example_vector = None attr = None elif example_vector and line.startswith("#"): continue else: if attr is not None and example_vector is not None: example_vector[attr].append(line.strip()) continue if line.startswith("# Example") or line.startswith( "# =============================================" ): if key: assert private_key_vector assert public_key_vector for key, value in public_key_vector.items(): hex_str = "".join(value).replace(" ", "") public_key_vector[key] = int(hex_str, 16) for key, value in private_key_vector.items(): hex_str = "".join(value).replace(" ", "") private_key_vector[key] = int(hex_str, 16) private_key_vector["examples"] = examples examples = [] assert ( private_key_vector["public_exponent"] == public_key_vector["public_exponent"] ) assert ( private_key_vector["modulus"] == public_key_vector["modulus"] ) vectors.append((private_key_vector, public_key_vector)) public_key_vector = collections.defaultdict(list) private_key_vector = collections.defaultdict(list) key = None attr = None if private_key_vector is None or public_key_vector is None: continue if line.startswith("# Private key"): key = private_key_vector elif line.startswith("# Public key"): key = public_key_vector elif line.startswith("# Modulus:"): attr = "modulus" elif line.startswith("# Public exponent:"): attr = "public_exponent" elif line.startswith("# Exponent:"): if key is public_key_vector: attr = "public_exponent" else: assert key is private_key_vector attr = "private_exponent" elif line.startswith("# Prime 1:"): attr = "p" elif line.startswith("# Prime 2:"): attr = "q" elif line.startswith("# Prime exponent 1:"): attr = "dmp1" elif line.startswith("# Prime exponent 2:"): attr = "dmq1" elif line.startswith("# Coefficient:"): attr = "iqmp" elif line.startswith("#"): attr = None else: if key is not None and attr is not None: key[attr].append(line.strip()) return vectors def load_rsa_nist_vectors(vector_data): test_data: typing.Dict[str, typing.Any] = {} p = None salt_length = None data = [] for line in vector_data: line = line.strip() # Blank lines and section headers are ignored if not line or line.startswith("["): continue if line.startswith("# Salt len:"): salt_length = int(line.split(":")[1].strip()) continue elif line.startswith("#"): continue # Build our data using a simple Key = Value format name, value = (c.strip() for c in line.split("=")) if name == "n": n = int(value, 16) elif name == "e" and p is None: e = int(value, 16) elif name == "p": p = int(value, 16) elif name == "q": q = int(value, 16) elif name == "SHAAlg": if p is None: test_data = { "modulus": n, "public_exponent": e, "salt_length": salt_length, "algorithm": value, "fail": False, } else: test_data = {"modulus": n, "p": p, "q": q, "algorithm": value} if salt_length is not None: test_data["salt_length"] = salt_length data.append(test_data) elif name == "e" and p is not None: test_data["public_exponent"] = int(value, 16) elif name == "d": test_data["private_exponent"] = int(value, 16) elif name == "Result": test_data["fail"] = value.startswith("F") # For all other tokens we simply want the name, value stored in # the dictionary else: test_data[name.lower()] = value.encode("ascii") return data def load_fips_dsa_key_pair_vectors(vector_data): """ Loads data out of the FIPS DSA KeyPair vector files. """ vectors = [] for line in vector_data: line = line.strip() if not line or line.startswith("#") or line.startswith("[mod"): continue if line.startswith("P"): vectors.append({"p": int(line.split("=")[1], 16)}) elif line.startswith("Q"): vectors[-1]["q"] = int(line.split("=")[1], 16) elif line.startswith("G"): vectors[-1]["g"] = int(line.split("=")[1], 16) elif line.startswith("X") and "x" not in vectors[-1]: vectors[-1]["x"] = int(line.split("=")[1], 16) elif line.startswith("X") and "x" in vectors[-1]: vectors.append( { "p": vectors[-1]["p"], "q": vectors[-1]["q"], "g": vectors[-1]["g"], "x": int(line.split("=")[1], 16), } ) elif line.startswith("Y"): vectors[-1]["y"] = int(line.split("=")[1], 16) return vectors FIPS_SHA_REGEX = re.compile( r"\[mod = L=...., N=..., SHA-(?P1|224|256|384|512)\]" ) def load_fips_dsa_sig_vectors(vector_data): """ Loads data out of the FIPS DSA SigVer vector files. """ vectors = [] for line in vector_data: line = line.strip() if not line or line.startswith("#"): continue sha_match = FIPS_SHA_REGEX.match(line) if sha_match: digest_algorithm = "SHA-{}".format(sha_match.group("sha")) if line.startswith("[mod"): continue name, value = (c.strip() for c in line.split("=")) if name == "P": vectors.append( {"p": int(value, 16), "digest_algorithm": digest_algorithm} ) elif name == "Q": vectors[-1]["q"] = int(value, 16) elif name == "G": vectors[-1]["g"] = int(value, 16) elif name == "Msg" and "msg" not in vectors[-1]: hexmsg = value.strip().encode("ascii") vectors[-1]["msg"] = binascii.unhexlify(hexmsg) elif name == "Msg" and "msg" in vectors[-1]: hexmsg = value.strip().encode("ascii") vectors.append( { "p": vectors[-1]["p"], "q": vectors[-1]["q"], "g": vectors[-1]["g"], "digest_algorithm": vectors[-1]["digest_algorithm"], "msg": binascii.unhexlify(hexmsg), } ) elif name == "X": vectors[-1]["x"] = int(value, 16) elif name == "Y": vectors[-1]["y"] = int(value, 16) elif name == "R": vectors[-1]["r"] = int(value, 16) elif name == "S": vectors[-1]["s"] = int(value, 16) elif name == "Result": vectors[-1]["result"] = value.split("(")[0].strip() return vectors # https://tools.ietf.org/html/rfc4492#appendix-A _ECDSA_CURVE_NAMES = { "P-192": "secp192r1", "P-224": "secp224r1", "P-256": "secp256r1", "P-384": "secp384r1", "P-521": "secp521r1", "K-163": "sect163k1", "K-233": "sect233k1", "K-256": "secp256k1", "K-283": "sect283k1", "K-409": "sect409k1", "K-571": "sect571k1", "B-163": "sect163r2", "B-233": "sect233r1", "B-283": "sect283r1", "B-409": "sect409r1", "B-571": "sect571r1", } def load_fips_ecdsa_key_pair_vectors(vector_data): """ Loads data out of the FIPS ECDSA KeyPair vector files. """ vectors = [] key_data = None for line in vector_data: line = line.strip() if not line or line.startswith("#"): continue if line[1:-1] in _ECDSA_CURVE_NAMES: curve_name = _ECDSA_CURVE_NAMES[line[1:-1]] elif line.startswith("d = "): if key_data is not None: vectors.append(key_data) key_data = {"curve": curve_name, "d": int(line.split("=")[1], 16)} elif key_data is not None: if line.startswith("Qx = "): key_data["x"] = int(line.split("=")[1], 16) elif line.startswith("Qy = "): key_data["y"] = int(line.split("=")[1], 16) assert key_data is not None vectors.append(key_data) return vectors CURVE_REGEX = re.compile( r"\[(?P[PKB]-[0-9]{3}),SHA-(?P1|224|256|384|512)\]" ) def load_fips_ecdsa_signing_vectors(vector_data): """ Loads data out of the FIPS ECDSA SigGen vector files. """ vectors = [] data: typing.Optional[typing.Dict[str, object]] = None for line in vector_data: line = line.strip() curve_match = CURVE_REGEX.match(line) if curve_match: curve_name = _ECDSA_CURVE_NAMES[curve_match.group("curve")] digest_name = "SHA-{}".format(curve_match.group("sha")) elif line.startswith("Msg = "): if data is not None: vectors.append(data) hexmsg = line.split("=")[1].strip().encode("ascii") data = { "curve": curve_name, "digest_algorithm": digest_name, "message": binascii.unhexlify(hexmsg), } elif data is not None: if line.startswith("Qx = "): data["x"] = int(line.split("=")[1], 16) elif line.startswith("Qy = "): data["y"] = int(line.split("=")[1], 16) elif line.startswith("R = "): data["r"] = int(line.split("=")[1], 16) elif line.startswith("S = "): data["s"] = int(line.split("=")[1], 16) elif line.startswith("d = "): data["d"] = int(line.split("=")[1], 16) elif line.startswith("Result = "): data["fail"] = line.split("=")[1].strip()[0] == "F" assert data is not None vectors.append(data) return vectors KASVS_RESULT_REGEX = re.compile(r"([FP]) \(([0-9]+) -") def load_kasvs_dh_vectors(vector_data): """ Loads data out of the KASVS key exchange vector data """ vectors = [] data: typing.Dict[str, typing.Any] = {"fail_z": False, "fail_agree": False} for line in vector_data: line = line.strip() if not line or line.startswith("#"): continue if line.startswith("P = "): data["p"] = int(line.split("=")[1], 16) elif line.startswith("Q = "): data["q"] = int(line.split("=")[1], 16) elif line.startswith("G = "): data["g"] = int(line.split("=")[1], 16) elif line.startswith("Z = "): z_hex = line.split("=")[1].strip().encode("ascii") data["z"] = binascii.unhexlify(z_hex) elif line.startswith("XstatCAVS = "): data["x1"] = int(line.split("=")[1], 16) elif line.startswith("YstatCAVS = "): data["y1"] = int(line.split("=")[1], 16) elif line.startswith("XstatIUT = "): data["x2"] = int(line.split("=")[1], 16) elif line.startswith("YstatIUT = "): data["y2"] = int(line.split("=")[1], 16) elif line.startswith("Result = "): result_str = line.split("=")[1].strip() match = KASVS_RESULT_REGEX.match(result_str) assert match is not None if match.group(1) == "F": if int(match.group(2)) in (5, 10): data["fail_z"] = True else: data["fail_agree"] = True vectors.append(data) data = { "p": data["p"], "q": data["q"], "g": data["g"], "fail_z": False, "fail_agree": False, } return vectors def load_kasvs_ecdh_vectors(vector_data): """ Loads data out of the KASVS key exchange vector data """ curve_name_map = { "P-192": "secp192r1", "P-224": "secp224r1", "P-256": "secp256r1", "P-384": "secp384r1", "P-521": "secp521r1", } tags = [] sets = {} vectors = [] # find info in header for line in vector_data: line = line.strip() if line.startswith("#"): parm = line.split("Parameter set(s) supported:") if len(parm) == 2: names = parm[1].strip().split() for n in names: tags.append(f"[{n}]") break # Sets Metadata tag = None curve = None for line in vector_data: line = line.strip() if not line or line.startswith("#"): continue if line in tags: tag = line curve = None elif line.startswith("[Curve selected:"): curve = curve_name_map[line.split(":")[1].strip()[:-1]] if tag is not None and curve is not None: sets[tag.strip("[]")] = curve tag = None if len(tags) == len(sets): break # Data data: typing.Dict[str, typing.Any] = { "CAVS": {}, "IUT": {}, } tag = None for line in vector_data: line = line.strip() if not line or line.startswith("#"): continue if line.startswith("["): tag = line.split()[0][1:] elif line.startswith("COUNT = "): data["COUNT"] = int(line.split("=")[1]) elif line.startswith("dsCAVS = "): data["CAVS"]["d"] = int(line.split("=")[1], 16) elif line.startswith("QsCAVSx = "): data["CAVS"]["x"] = int(line.split("=")[1], 16) elif line.startswith("QsCAVSy = "): data["CAVS"]["y"] = int(line.split("=")[1], 16) elif line.startswith("dsIUT = "): data["IUT"]["d"] = int(line.split("=")[1], 16) elif line.startswith("QsIUTx = "): data["IUT"]["x"] = int(line.split("=")[1], 16) elif line.startswith("QsIUTy = "): data["IUT"]["y"] = int(line.split("=")[1], 16) elif line.startswith("OI = "): data["OI"] = int(line.split("=")[1], 16) elif line.startswith("Z = "): data["Z"] = int(line.split("=")[1], 16) elif line.startswith("DKM = "): data["DKM"] = int(line.split("=")[1], 16) elif line.startswith("Result = "): result_str = line.split("=")[1].strip() match = KASVS_RESULT_REGEX.match(result_str) assert match is not None if match.group(1) == "F": data["fail"] = True else: data["fail"] = False data["errno"] = int(match.group(2)) data["curve"] = sets[tag] vectors.append(data) data = { "CAVS": {}, "IUT": {}, } return vectors def load_rfc6979_vectors(vector_data): """ Loads data out of the ECDSA and DSA RFC6979 vector files. """ vectors = [] keys: typing.Dict[str, typing.List[str]] = dict() reading_key = False current_key_name = None data: typing.Dict[str, object] = dict() for line in vector_data: line = line.strip() if reading_key and current_key_name: keys[current_key_name].append(line) if line.startswith("-----END"): reading_key = False current_key_name = None if line.startswith("PrivateKey=") or line.startswith("PublicKey="): reading_key = True current_key_name = line.split("=")[1].strip() keys[current_key_name] = [] elif line.startswith("DigestSign = "): data["digest_sign"] = line.split("=")[1].strip() data["deterministic_nonce"] = False elif line.startswith("DigestVerify = "): data["digest_verify"] = line.split("=")[1].strip() data["verify_error"] = False elif line.startswith("Key = "): key_name = line.split("=")[1].strip() assert key_name in keys data["key"] = keys[key_name] data["key_name"] = key_name elif line.startswith("NonceType = "): nonce_type = line.split("=")[1].strip() data["deterministic_nonce"] = nonce_type == "deterministic" elif line.startswith("Input = "): data["input"] = line.split("=")[1].strip(' "') elif line.startswith("Output = "): data["output"] = line.split("=")[1].strip() elif line.startswith("Result = "): data["verify_error"] = line.split("=")[1].strip() == "VERIFY_ERROR" elif not line: if data: vectors.append(data) data = {} return vectors def load_x963_vectors(vector_data): """ Loads data out of the X9.63 vector data """ vectors = [] # Sets Metadata hashname = None vector = {} for line in vector_data: line = line.strip() if line.startswith("[SHA"): hashname = line[1:-1] shared_secret_len = 0 shared_info_len = 0 key_data_len = 0 elif line.startswith("[shared secret length"): shared_secret_len = int(line[1:-1].split("=")[1].strip()) elif line.startswith("[SharedInfo length"): shared_info_len = int(line[1:-1].split("=")[1].strip()) elif line.startswith("[key data length"): key_data_len = int(line[1:-1].split("=")[1].strip()) elif line.startswith("COUNT"): count = int(line.split("=")[1].strip()) vector["hash"] = hashname vector["count"] = count vector["shared_secret_length"] = shared_secret_len vector["sharedinfo_length"] = shared_info_len vector["key_data_length"] = key_data_len elif line.startswith("Z"): vector["Z"] = line.split("=")[1].strip() assert vector["Z"] is not None assert ((shared_secret_len + 7) // 8) * 2 == len(vector["Z"]) elif line.startswith("SharedInfo"): if shared_info_len != 0: vector["sharedinfo"] = line.split("=")[1].strip() assert vector["sharedinfo"] is not None silen = len(vector["sharedinfo"]) assert ((shared_info_len + 7) // 8) * 2 == silen elif line.startswith("key_data"): vector["key_data"] = line.split("=")[1].strip() assert vector["key_data"] is not None assert ((key_data_len + 7) // 8) * 2 == len(vector["key_data"]) vectors.append(vector) vector = {} return vectors def load_nist_kbkdf_vectors(vector_data): """ Load NIST SP 800-108 KDF Vectors """ vectors = [] test_data = None tag = {} for line in vector_data: line = line.strip() if not line or line.startswith("#"): continue if line.startswith("[") and line.endswith("]"): tag_data = line[1:-1] name, value = (c.strip() for c in tag_data.split("=")) if value.endswith("_BITS"): value = int(value.split("_")[0]) tag.update({name.lower(): value}) continue tag.update({name.lower(): value.lower()}) elif line.startswith("COUNT="): test_data = {} test_data.update(tag) vectors.append(test_data) elif line.startswith(("L", "DataBeforeCtrLen", "DataAfterCtrLen")): name, value = (c.strip() for c in line.split("=")) test_data[name.lower()] = int(value) else: name, value = (c.strip() for c in line.split("=")) test_data[name.lower()] = value.encode("ascii") return vectors def load_ed25519_vectors(vector_data): data = [] for line in vector_data: secret_key, public_key, message, signature, _ = line.split(":") # In the vectors the first element is secret key + public key secret_key = secret_key[0:64] # In the vectors the signature section is signature + message signature = signature[0:128] data.append( { "secret_key": secret_key, "public_key": public_key, "message": message, "signature": signature, } ) return data def load_nist_ccm_vectors(vector_data): test_data = {} section_data = None global_data = {} new_section = False data = [] for line in vector_data: line = line.strip() # Blank lines and comments should be ignored if not line or line.startswith("#"): continue # Some of the CCM vectors have global values for this. They are always # at the top before the first section header (see: VADT, VNT, VPT) if line.startswith(("Alen", "Plen", "Nlen", "Tlen")): name, value = (c.strip() for c in line.split("=")) global_data[name.lower()] = int(value) continue # section headers contain length data we might care about if line.startswith("["): new_section = True section_data = {} section = line[1:-1] items = [c.strip() for c in section.split(",")] for item in items: name, value = (c.strip() for c in item.split("=")) section_data[name.lower()] = int(value) continue name, value = (c.strip() for c in line.split("=")) if name.lower() in ("key", "nonce") and new_section: section_data[name.lower()] = value.encode("ascii") continue new_section = False # Payload is sometimes special because these vectors are absurd. Each # example may or may not have a payload. If it does not then the # previous example's payload should be used. We accomplish this by # writing it into the section_data. Because we update each example # with the section data it will be overwritten if a new payload value # is present. NIST should be ashamed of their vector creation. if name.lower() == "payload": section_data[name.lower()] = value.encode("ascii") # Result is a special token telling us if the test should pass/fail. # This is only present in the DVPT CCM tests if name.lower() == "result": if value.lower() == "pass": test_data["fail"] = False else: test_data["fail"] = True continue # COUNT is a special token that indicates a new block of data if name.lower() == "count": test_data = {} test_data.update(global_data) test_data.update(section_data) data.append(test_data) continue # For all other tokens we simply want the name, value stored in # the dictionary else: test_data[name.lower()] = value.encode("ascii") return data class WycheproofTest: def __init__(self, testfiledata, testgroup, testcase): self.testfiledata = testfiledata self.testgroup = testgroup self.testcase = testcase def __repr__(self): return "".format( self.testfiledata, self.testgroup, self.testcase, self.testcase["tcId"], ) @property def valid(self) -> bool: return self.testcase["result"] == "valid" @property def acceptable(self) -> bool: return self.testcase["result"] == "acceptable" @property def invalid(self) -> bool: return self.testcase["result"] == "invalid" def has_flag(self, flag: str) -> bool: return flag in self.testcase["flags"] def cache_value_to_group(self, cache_key: str, func): cache_val = self.testgroup.get(cache_key) if cache_val is not None: return cache_val self.testgroup[cache_key] = cache_val = func() return cache_val def load_wycheproof_tests(wycheproof, test_file, subdir): path = os.path.join(wycheproof, subdir, test_file) with open(path) as f: data = json.load(f) for group in data.pop("testGroups"): cases = group.pop("tests") for c in cases: yield WycheproofTest(data, group, c) cryptography-43.0.0/tests/wycheproof/__init__.py010064400017510000177000000000001464676315000201570ustar 00000000000000cryptography-43.0.0/tests/wycheproof/test_aes.py010064400017510000177000000131311464676315000202400ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidTag from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESGCM from ..hazmat.primitives.test_aead import _aead_supported from .utils import wycheproof_tests @wycheproof_tests("aes_cbc_pkcs5_test.json") def test_aes_cbc_pkcs5(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) padder = padding.PKCS7(128).padder() cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend) enc = cipher.encryptor() computed_ct = ( enc.update(padder.update(msg) + padder.finalize()) + enc.finalize() ) dec = cipher.decryptor() padded_msg = dec.update(ct) + dec.finalize() unpadder = padding.PKCS7(128).unpadder() if wycheproof.valid or wycheproof.acceptable: assert computed_ct == ct computed_msg = unpadder.update(padded_msg) + unpadder.finalize() assert computed_msg == msg else: assert computed_ct != ct with pytest.raises(ValueError): unpadder.update(padded_msg) + unpadder.finalize() @wycheproof_tests("aes_gcm_test.json") def test_aes_gcm(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) aad = binascii.unhexlify(wycheproof.testcase["aad"]) msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) if len(iv) < 8 or len(iv) > 128: pytest.skip( "Less than 64-bit IVs (and greater than 1024-bit) are no longer " "supported" ) if backend._fips_enabled and len(iv) != 12: # Red Hat disables non-96-bit IV support as part of its FIPS # patches. pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") if wycheproof.valid or wycheproof.acceptable: enc = Cipher(algorithms.AES(key), modes.GCM(iv), backend).encryptor() enc.authenticate_additional_data(aad) computed_ct = enc.update(msg) + enc.finalize() computed_tag = enc.tag assert computed_ct == ct assert computed_tag == tag dec = Cipher( algorithms.AES(key), modes.GCM(iv, tag, min_tag_length=len(tag)), backend, ).decryptor() dec.authenticate_additional_data(aad) computed_msg = dec.update(ct) + dec.finalize() assert computed_msg == msg else: dec = Cipher( algorithms.AES(key), modes.GCM(iv, tag, min_tag_length=len(tag)), backend, ).decryptor() dec.authenticate_additional_data(aad) dec.update(ct) with pytest.raises(InvalidTag): dec.finalize() @wycheproof_tests("aes_gcm_test.json") def test_aes_gcm_aead_api(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) aad = binascii.unhexlify(wycheproof.testcase["aad"]) msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) if len(iv) < 8 or len(iv) > 128: pytest.skip( "Less than 64-bit IVs (and greater than 1024-bit) are no longer " "supported" ) if backend._fips_enabled and len(iv) != 12: # Red Hat disables non-96-bit IV support as part of its FIPS # patches. pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") aesgcm = AESGCM(key) if wycheproof.valid or wycheproof.acceptable: computed_ct = aesgcm.encrypt(iv, msg, aad) assert computed_ct == ct + tag computed_msg = aesgcm.decrypt(iv, ct + tag, aad) assert computed_msg == msg else: with pytest.raises(InvalidTag): aesgcm.decrypt(iv, ct + tag, aad) @pytest.mark.skipif( not _aead_supported(AESCCM), reason="Requires OpenSSL with AES-CCM support", ) @wycheproof_tests("aes_ccm_test.json") def test_aes_ccm_aead_api(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) aad = binascii.unhexlify(wycheproof.testcase["aad"]) msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) if ( wycheproof.invalid and wycheproof.testcase["comment"] == "Invalid tag size" ): with pytest.raises(ValueError): AESCCM(key, tag_length=wycheproof.testgroup["tagSize"] // 8) return aesccm = AESCCM(key, tag_length=wycheproof.testgroup["tagSize"] // 8) if wycheproof.valid or wycheproof.acceptable: computed_ct = aesccm.encrypt(iv, msg, aad) assert computed_ct == ct + tag computed_msg = aesccm.decrypt(iv, ct + tag, aad) assert computed_msg == msg elif not 7 <= len(iv) <= 13: with pytest.raises(ValueError): aesccm.decrypt(iv, ct + tag, aad) else: with pytest.raises(InvalidTag): aesccm.decrypt(iv, ct + tag, aad) cryptography-43.0.0/tests/wycheproof/test_chacha20poly1305.py010064400017510000177000000031051464676315000222560ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidTag from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 from ..hazmat.primitives.test_aead import _aead_supported from .utils import wycheproof_tests @pytest.mark.skipif( not _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL with ChaCha20Poly1305 support", ) @wycheproof_tests("chacha20_poly1305_test.json") def test_chacha20poly1305(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) iv = binascii.unhexlify(wycheproof.testcase["iv"]) aad = binascii.unhexlify(wycheproof.testcase["aad"]) msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) if wycheproof.valid: chacha = ChaCha20Poly1305(key) computed_ct = chacha.encrypt(iv, msg, aad) assert computed_ct == ct + tag computed_msg = chacha.decrypt(iv, ct + tag, aad) assert computed_msg == msg elif len(iv) != 12: chacha = ChaCha20Poly1305(key) with pytest.raises(ValueError): chacha.encrypt(iv, msg, aad) with pytest.raises(ValueError): chacha.decrypt(iv, ct + tag, aad) else: chacha = ChaCha20Poly1305(key) with pytest.raises(InvalidTag): chacha.decrypt(iv, msg + tag, aad) cryptography-43.0.0/tests/wycheproof/test_cmac.py010064400017510000177000000021501464676315000203720ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.cmac import CMAC from .utils import wycheproof_tests @wycheproof_tests("aes_cmac_test.json") def test_aes_cmac(backend, wycheproof): key = binascii.unhexlify(wycheproof.testcase["key"]) msg = binascii.unhexlify(wycheproof.testcase["msg"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) # skip truncated tags, which we don't support in the API if wycheproof.valid and len(tag) == 16: ctx = CMAC(AES(key), backend) ctx.update(msg) ctx.verify(tag) elif len(key) not in [16, 24, 32]: with pytest.raises(ValueError): CMAC(AES(key), backend) else: ctx = CMAC(AES(key), backend) ctx.update(msg) with pytest.raises(InvalidSignature): ctx.verify(tag) cryptography-43.0.0/tests/wycheproof/test_dsa.py010064400017510000177000000031571464676315000202460ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa from .utils import wycheproof_tests _DIGESTS = { "SHA-1": hashes.SHA1(), "SHA-224": hashes.SHA224(), "SHA-256": hashes.SHA256(), } @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Requires OpenSSL with DSA support", ) @wycheproof_tests( "dsa_test.json", "dsa_2048_224_sha224_test.json", "dsa_2048_224_sha256_test.json", "dsa_2048_256_sha256_test.json", "dsa_3072_256_sha256_test.json", ) def test_dsa_signature(backend, wycheproof): key = serialization.load_der_public_key( binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend ) assert isinstance(key, dsa.DSAPublicKey) digest = _DIGESTS[wycheproof.testgroup["sha"]] if wycheproof.valid or ( wycheproof.acceptable and not wycheproof.has_flag("NoLeadingZero") ): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), digest, ) else: with pytest.raises(InvalidSignature): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), digest, ) cryptography-43.0.0/tests/wycheproof/test_ecdh.py010064400017510000177000000104631464676315000204000ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from ..hazmat.primitives.test_ec import _skip_exchange_algorithm_unsupported from .utils import wycheproof_tests _CURVES = { "secp224r1": ec.SECP224R1(), "secp256r1": ec.SECP256R1(), "secp384r1": ec.SECP384R1(), "secp521r1": ec.SECP521R1(), "secp224k1": None, "secp256k1": ec.SECP256K1(), "sect283r1": ec.SECT283R1(), "sect409r1": ec.SECT409R1(), "sect571r1": ec.SECT571R1(), "sect283k1": ec.SECT283K1(), "sect409k1": ec.SECT409K1(), "sect571k1": ec.SECT571K1(), "brainpoolP224r1": None, "brainpoolP256r1": ec.BrainpoolP256R1(), "brainpoolP320r1": None, "brainpoolP384r1": ec.BrainpoolP384R1(), "brainpoolP512r1": ec.BrainpoolP512R1(), "brainpoolP224t1": None, "brainpoolP256t1": None, "brainpoolP320t1": None, "brainpoolP384t1": None, "brainpoolP512t1": None, "FRP256v1": None, } @wycheproof_tests( "ecdh_test.json", "ecdh_brainpoolP224r1_test.json", "ecdh_brainpoolP256r1_test.json", "ecdh_brainpoolP320r1_test.json", "ecdh_brainpoolP384r1_test.json", "ecdh_brainpoolP512r1_test.json", "ecdh_secp224r1_test.json", "ecdh_secp256k1_test.json", "ecdh_secp256r1_test.json", "ecdh_secp384r1_test.json", "ecdh_secp521r1_test.json", "ecdh_sect283k1_test.json", "ecdh_sect283r1_test.json", "ecdh_sect409k1_test.json", "ecdh_sect409r1_test.json", "ecdh_sect571k1_test.json", "ecdh_sect571r1_test.json", ) def test_ecdh(backend, wycheproof): curve = _CURVES[wycheproof.testgroup["curve"]] if curve is None: pytest.skip( "Unsupported curve ({})".format(wycheproof.testgroup["curve"]) ) _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) private_key = wycheproof.cache_value_to_group( f"private_key_{wycheproof.testcase['private']}", lambda: ec.derive_private_key( int(wycheproof.testcase["private"], 16), curve ), ) try: # caching these values shows no performance improvement public_key = serialization.load_der_public_key( binascii.unhexlify(wycheproof.testcase["public"]), backend ) assert isinstance(public_key, ec.EllipticCurvePublicKey) except ValueError: assert wycheproof.invalid or wycheproof.acceptable return except UnsupportedAlgorithm: return if wycheproof.valid or wycheproof.acceptable: computed_shared = private_key.exchange(ec.ECDH(), public_key) expected_shared = binascii.unhexlify(wycheproof.testcase["shared"]) assert computed_shared == expected_shared else: with pytest.raises(ValueError): private_key.exchange(ec.ECDH(), public_key) @wycheproof_tests( "ecdh_secp224r1_ecpoint_test.json", "ecdh_secp256r1_ecpoint_test.json", "ecdh_secp384r1_ecpoint_test.json", "ecdh_secp521r1_ecpoint_test.json", ) def test_ecdh_ecpoint(backend, wycheproof): curve = _CURVES[wycheproof.testgroup["curve"]] assert isinstance(curve, ec.EllipticCurve) _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) private_key = wycheproof.cache_value_to_group( f"private_key_{wycheproof.testcase['private']}", lambda: ec.derive_private_key( int(wycheproof.testcase["private"], 16), curve ), ) if wycheproof.invalid: with pytest.raises(ValueError): ec.EllipticCurvePublicKey.from_encoded_point( curve, binascii.unhexlify(wycheproof.testcase["public"]) ) return assert wycheproof.valid or wycheproof.acceptable # caching these values shows no performance improvement public_key = ec.EllipticCurvePublicKey.from_encoded_point( curve, binascii.unhexlify(wycheproof.testcase["public"]) ) computed_shared = private_key.exchange(ec.ECDH(), public_key) expected_shared = binascii.unhexlify(wycheproof.testcase["shared"]) assert computed_shared == expected_shared cryptography-43.0.0/tests/wycheproof/test_ecdsa.py010064400017510000177000000067701464676315000205620ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from .utils import wycheproof_tests _DIGESTS = { "SHA-1": hashes.SHA1(), "SHA-224": hashes.SHA224(), "SHA-256": hashes.SHA256(), "SHA-384": hashes.SHA384(), "SHA-512": hashes.SHA512(), "SHA3-224": hashes.SHA3_224(), "SHA3-256": hashes.SHA3_256(), "SHA3-384": hashes.SHA3_384(), "SHA3-512": hashes.SHA3_512(), } @wycheproof_tests( "ecdsa_test.json", "ecdsa_brainpoolP224r1_sha224_test.json", "ecdsa_brainpoolP256r1_sha256_test.json", "ecdsa_brainpoolP320r1_sha384_test.json", "ecdsa_brainpoolP384r1_sha384_test.json", "ecdsa_brainpoolP512r1_sha512_test.json", "ecdsa_secp224r1_sha224_test.json", "ecdsa_secp224r1_sha256_test.json", "ecdsa_secp224r1_sha512_test.json", "ecdsa_secp224r1_sha3_224_test.json", "ecdsa_secp224r1_sha3_256_test.json", "ecdsa_secp224r1_sha3_512_test.json", "ecdsa_secp256k1_sha256_test.json", "ecdsa_secp256k1_sha512_test.json", "ecdsa_secp256k1_sha3_256_test.json", "ecdsa_secp256k1_sha3_512_test.json", "ecdsa_secp256r1_sha256_test.json", "ecdsa_secp256r1_sha512_test.json", "ecdsa_secp256r1_sha3_256_test.json", "ecdsa_secp256r1_sha3_512_test.json", "ecdsa_secp384r1_sha384_test.json", "ecdsa_secp384r1_sha512_test.json", "ecdsa_secp384r1_sha3_384_test.json", "ecdsa_secp384r1_sha3_512_test.json", "ecdsa_secp521r1_sha512_test.json", "ecdsa_secp521r1_sha3_512_test.json", "ecdsa_secp160k1_sha256_test.json", "ecdsa_secp160r1_sha256_test.json", "ecdsa_secp160r2_sha256_test.json", "ecdsa_secp192k1_sha256_test.json", "ecdsa_secp192r1_sha256_test.json", ) def test_ecdsa_signature(backend, wycheproof): try: key = wycheproof.cache_value_to_group( "cache_key", lambda: serialization.load_der_public_key( binascii.unhexlify(wycheproof.testgroup["keyDer"]) ), ) assert isinstance(key, ec.EllipticCurvePublicKey) except (UnsupportedAlgorithm, ValueError): # In some OpenSSL 1.1.1 versions (RHEL and Fedora), some keys fail to # load with ValueError, instead of Unsupported Algorithm. We can # remove handling for that exception when we drop support. pytest.skip( "unable to load key (curve {})".format( wycheproof.testgroup["key"]["curve"] ) ) digest = _DIGESTS[wycheproof.testgroup["sha"]] alg = ec.ECDSA(digest) if not backend.elliptic_curve_signature_algorithm_supported( alg, key.curve ): pytest.skip(f"Signature with {digest} and {key.curve} not supported") if wycheproof.valid or ( wycheproof.acceptable and not wycheproof.has_flag("MissingZero") ): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), alg, ) else: with pytest.raises(InvalidSignature): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), alg, ) cryptography-43.0.0/tests/wycheproof/test_eddsa.py010064400017510000177000000041511464676315000205520ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from .utils import wycheproof_tests @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) @wycheproof_tests("eddsa_test.json") def test_ed25519_signature(backend, wycheproof): # We want to fail if/when wycheproof adds more edwards curve tests # so we can add them as well. assert wycheproof.testgroup["key"]["curve"] == "edwards25519" key = Ed25519PublicKey.from_public_bytes( binascii.unhexlify(wycheproof.testgroup["key"]["pk"]) ) if wycheproof.valid or wycheproof.acceptable: key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), ) else: with pytest.raises(InvalidSignature): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), ) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) @wycheproof_tests("ed448_test.json") def test_ed448_signature(backend, wycheproof): key = Ed448PublicKey.from_public_bytes( binascii.unhexlify(wycheproof.testgroup["key"]["pk"]) ) if wycheproof.valid or wycheproof.acceptable: key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), ) else: with pytest.raises(InvalidSignature): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), ) cryptography-43.0.0/tests/wycheproof/test_hkdf.py010064400017510000177000000027661464676315000204200ustar 00000000000000# 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. import binascii import pytest from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF from .utils import wycheproof_tests _HASH_ALGORITHMS = { "HKDF-SHA-1": hashes.SHA1(), "HKDF-SHA-256": hashes.SHA256(), "HKDF-SHA-384": hashes.SHA384(), "HKDF-SHA-512": hashes.SHA512(), } @wycheproof_tests( "hkdf_sha1_test.json", "hkdf_sha256_test.json", "hkdf_sha384_test.json", "hkdf_sha512_test.json", ) def test_hkdf(backend, wycheproof): hash_algo = _HASH_ALGORITHMS[wycheproof.testfiledata["algorithm"]] if wycheproof.invalid: with pytest.raises(ValueError): HKDF( algorithm=hash_algo, length=wycheproof.testcase["size"], salt=binascii.unhexlify(wycheproof.testcase["salt"]), info=binascii.unhexlify(wycheproof.testcase["info"]), backend=backend, ) return h = HKDF( algorithm=hash_algo, length=wycheproof.testcase["size"], salt=binascii.unhexlify(wycheproof.testcase["salt"]), info=binascii.unhexlify(wycheproof.testcase["info"]), backend=backend, ) result = h.derive(binascii.unhexlify(wycheproof.testcase["ikm"])) assert result == binascii.unhexlify(wycheproof.testcase["okm"]) cryptography-43.0.0/tests/wycheproof/test_hmac.py010064400017510000177000000040331464676315000204010ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, hmac from .utils import wycheproof_tests _HMAC_ALGORITHMS = { "HMACSHA1": hashes.SHA1(), "HMACSHA224": hashes.SHA224(), "HMACSHA256": hashes.SHA256(), "HMACSHA384": hashes.SHA384(), "HMACSHA512": hashes.SHA512(), "HMACSHA3-224": hashes.SHA3_224(), "HMACSHA3-256": hashes.SHA3_256(), "HMACSHA3-384": hashes.SHA3_384(), "HMACSHA3-512": hashes.SHA3_512(), } @wycheproof_tests( "hmac_sha1_test.json", "hmac_sha224_test.json", "hmac_sha256_test.json", "hmac_sha384_test.json", "hmac_sha3_224_test.json", "hmac_sha3_256_test.json", "hmac_sha3_384_test.json", "hmac_sha3_512_test.json", "hmac_sha512_test.json", ) def test_hmac(backend, wycheproof): hash_algo = _HMAC_ALGORITHMS[wycheproof.testfiledata["algorithm"]] if wycheproof.testgroup["tagSize"] // 8 != hash_algo.digest_size: pytest.skip("Truncated HMAC not supported") if not backend.hmac_supported(hash_algo): pytest.skip(f"Hash {hash_algo.name} not supported") h = hmac.HMAC( key=binascii.unhexlify(wycheproof.testcase["key"]), algorithm=hash_algo, backend=backend, ) h.update(binascii.unhexlify(wycheproof.testcase["msg"])) if wycheproof.invalid: with pytest.raises(InvalidSignature): h.verify(binascii.unhexlify(wycheproof.testcase["tag"])) else: tag = h.finalize() assert tag == binascii.unhexlify(wycheproof.testcase["tag"]) h = hmac.HMAC( key=binascii.unhexlify(wycheproof.testcase["key"]), algorithm=hash_algo, backend=backend, ) h.update(binascii.unhexlify(wycheproof.testcase["msg"])) h.verify(binascii.unhexlify(wycheproof.testcase["tag"])) cryptography-43.0.0/tests/wycheproof/test_keywrap.py010064400017510000177000000036611464676315000211610ustar 00000000000000# 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. import binascii import pytest from cryptography.hazmat.primitives import keywrap from .utils import wycheproof_tests @wycheproof_tests("kwp_test.json") def test_keywrap_with_padding(backend, wycheproof): wrapping_key = binascii.unhexlify(wycheproof.testcase["key"]) key_to_wrap = binascii.unhexlify(wycheproof.testcase["msg"]) expected = binascii.unhexlify(wycheproof.testcase["ct"]) result = keywrap.aes_key_wrap_with_padding( wrapping_key, key_to_wrap, backend ) if wycheproof.valid or wycheproof.acceptable: assert result == expected if wycheproof.valid or (wycheproof.acceptable and not len(expected) < 16): result = keywrap.aes_key_unwrap_with_padding( wrapping_key, expected, backend ) assert result == key_to_wrap else: with pytest.raises(keywrap.InvalidUnwrap): keywrap.aes_key_unwrap_with_padding( wrapping_key, expected, backend ) @wycheproof_tests("kw_test.json") def test_keywrap(backend, wycheproof): wrapping_key = binascii.unhexlify(wycheproof.testcase["key"]) key_to_wrap = binascii.unhexlify(wycheproof.testcase["msg"]) expected = binascii.unhexlify(wycheproof.testcase["ct"]) if wycheproof.valid or ( wycheproof.acceptable and wycheproof.testcase["comment"] != "invalid size of wrapped key" ): result = keywrap.aes_key_wrap(wrapping_key, key_to_wrap, backend) assert result == expected if wycheproof.valid or wycheproof.acceptable: result = keywrap.aes_key_unwrap(wrapping_key, expected, backend) assert result == key_to_wrap else: with pytest.raises(keywrap.InvalidUnwrap): keywrap.aes_key_unwrap(wrapping_key, expected, backend) cryptography-43.0.0/tests/wycheproof/test_pbkdf2.py010064400017510000177000000024241464676315000206430ustar 00000000000000# 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. import binascii from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from .utils import wycheproof_tests _HASH_ALGORITHMS = { "PBKDF2-HMACSHA1": hashes.SHA1(), "PBKDF2-HMACSHA224": hashes.SHA224(), "PBKDF2-HMACSHA256": hashes.SHA256(), "PBKDF2-HMACSHA384": hashes.SHA384(), "PBKDF2-HMACSHA512": hashes.SHA512(), } @wycheproof_tests( "pbkdf2_hmacsha1_test.json", "pbkdf2_hmacsha224_test.json", "pbkdf2_hmacsha256_test.json", "pbkdf2_hmacsha384_test.json", "pbkdf2_hmacsha512_test.json", subdir="testvectors_v1", ) def test_pbkdf2(backend, wycheproof): assert wycheproof.valid algorithm = _HASH_ALGORITHMS[wycheproof.testfiledata["algorithm"]] p = PBKDF2HMAC( algorithm=algorithm, length=wycheproof.testcase["dkLen"], salt=binascii.unhexlify(wycheproof.testcase["salt"]), iterations=wycheproof.testcase["iterationCount"], ) assert p.derive( binascii.unhexlify(wycheproof.testcase["password"]) ) == binascii.unhexlify(wycheproof.testcase["dk"]) cryptography-43.0.0/tests/wycheproof/test_rsa.py010064400017510000177000000233161464676315000202630ustar 00000000000000# 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. import binascii import pytest from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa from .utils import wycheproof_tests _DIGESTS = { "SHA-1": hashes.SHA1(), "SHA-224": hashes.SHA224(), "SHA-256": hashes.SHA256(), "SHA-384": hashes.SHA384(), "SHA-512": hashes.SHA512(), # Not supported by OpenSSL<3 for RSA signing. # Enable these when we require CRYPTOGRAPHY_OPENSSL_300_OR_GREATER "SHA-512/224": None, "SHA-512/256": None, "SHA3-224": hashes.SHA3_224(), "SHA3-256": hashes.SHA3_256(), "SHA3-384": hashes.SHA3_384(), "SHA3-512": hashes.SHA3_512(), } def should_verify(backend, wycheproof): if wycheproof.valid: return True if wycheproof.acceptable: return not wycheproof.has_flag("MissingNull") return False @wycheproof_tests( "rsa_signature_test.json", "rsa_signature_2048_sha224_test.json", "rsa_signature_2048_sha256_test.json", "rsa_signature_2048_sha384_test.json", "rsa_signature_2048_sha512_test.json", "rsa_signature_2048_sha512_224_test.json", "rsa_signature_2048_sha512_256_test.json", "rsa_signature_2048_sha3_224_test.json", "rsa_signature_2048_sha3_256_test.json", "rsa_signature_2048_sha3_384_test.json", "rsa_signature_2048_sha3_512_test.json", "rsa_signature_3072_sha256_test.json", "rsa_signature_3072_sha384_test.json", "rsa_signature_3072_sha512_test.json", "rsa_signature_3072_sha512_256_test.json", "rsa_signature_3072_sha3_256_test.json", "rsa_signature_3072_sha3_384_test.json", "rsa_signature_3072_sha3_512_test.json", "rsa_signature_4096_sha384_test.json", "rsa_signature_4096_sha512_test.json", "rsa_signature_4096_sha512_256_test.json", ) def test_rsa_pkcs1v15_signature(backend, wycheproof): key = wycheproof.cache_value_to_group( "cached_key", lambda: serialization.load_der_public_key( binascii.unhexlify(wycheproof.testgroup["keyDer"]), ), ) assert isinstance(key, rsa.RSAPublicKey) digest = _DIGESTS[wycheproof.testgroup["sha"]] if digest is None or not backend.hash_supported(digest): pytest.skip( "Hash {} not supported".format(wycheproof.testgroup["sha"]) ) if should_verify(backend, wycheproof): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), padding.PKCS1v15(), digest, ) else: with pytest.raises(InvalidSignature): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), padding.PKCS1v15(), digest, ) @wycheproof_tests("rsa_sig_gen_misc_test.json") def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): key = wycheproof.cache_value_to_group( "cached_key", lambda: serialization.load_pem_private_key( wycheproof.testgroup["privateKeyPem"].encode("ascii"), password=None, unsafe_skip_rsa_key_validation=True, ), ) assert isinstance(key, rsa.RSAPrivateKey) digest = _DIGESTS[wycheproof.testgroup["sha"]] assert digest is not None if backend._fips_enabled: if key.key_size < backend._fips_rsa_min_key_size or isinstance( digest, hashes.SHA1 ): pytest.skip( f"Invalid params for FIPS. key: {key.key_size} bits, " f"digest: {digest.name}" ) sig = key.sign( binascii.unhexlify(wycheproof.testcase["msg"]), padding.PKCS1v15(), digest, ) assert sig == binascii.unhexlify(wycheproof.testcase["sig"]) @wycheproof_tests( "rsa_pss_2048_sha1_mgf1_20_test.json", "rsa_pss_2048_sha256_mgf1_0_test.json", "rsa_pss_2048_sha256_mgf1_32_test.json", "rsa_pss_2048_sha512_256_mgf1_28_test.json", "rsa_pss_2048_sha512_256_mgf1_32_test.json", "rsa_pss_3072_sha256_mgf1_32_test.json", "rsa_pss_4096_sha256_mgf1_32_test.json", "rsa_pss_4096_sha512_mgf1_32_test.json", "rsa_pss_misc_test.json", ) def test_rsa_pss_signature(backend, wycheproof): digest = _DIGESTS[wycheproof.testgroup["sha"]] if backend._fips_enabled and isinstance(digest, hashes.SHA1): pytest.skip("Invalid params for FIPS. SHA1 is disallowed") key = wycheproof.cache_value_to_group( "cached_key", lambda: serialization.load_der_public_key( binascii.unhexlify(wycheproof.testgroup["keyDer"]), ), ) assert isinstance(key, rsa.RSAPublicKey) mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] if digest is None or mgf_digest is None: pytest.skip( "PSS with digest={} and MGF digest={} not supported".format( wycheproof.testgroup["sha"], wycheproof.testgroup["mgfSha"], ) ) if wycheproof.valid or wycheproof.acceptable: key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), padding.PSS( mgf=padding.MGF1(mgf_digest), salt_length=wycheproof.testgroup["sLen"], ), digest, ) else: with pytest.raises(InvalidSignature): key.verify( binascii.unhexlify(wycheproof.testcase["sig"]), binascii.unhexlify(wycheproof.testcase["msg"]), padding.PSS( mgf=padding.MGF1(mgf_digest), salt_length=wycheproof.testgroup["sLen"], ), digest, ) @wycheproof_tests( "rsa_oaep_2048_sha1_mgf1sha1_test.json", "rsa_oaep_2048_sha224_mgf1sha1_test.json", "rsa_oaep_2048_sha224_mgf1sha224_test.json", "rsa_oaep_2048_sha256_mgf1sha1_test.json", "rsa_oaep_2048_sha256_mgf1sha256_test.json", "rsa_oaep_2048_sha384_mgf1sha1_test.json", "rsa_oaep_2048_sha384_mgf1sha384_test.json", "rsa_oaep_2048_sha512_mgf1sha1_test.json", "rsa_oaep_2048_sha512_mgf1sha512_test.json", "rsa_oaep_3072_sha256_mgf1sha1_test.json", "rsa_oaep_3072_sha256_mgf1sha256_test.json", "rsa_oaep_3072_sha512_mgf1sha1_test.json", "rsa_oaep_3072_sha512_mgf1sha512_test.json", "rsa_oaep_4096_sha256_mgf1sha1_test.json", "rsa_oaep_4096_sha256_mgf1sha256_test.json", "rsa_oaep_4096_sha512_mgf1sha1_test.json", "rsa_oaep_4096_sha512_mgf1sha512_test.json", "rsa_oaep_misc_test.json", ) def test_rsa_oaep_encryption(backend, wycheproof): digest = _DIGESTS[wycheproof.testgroup["sha"]] mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] assert digest is not None assert mgf_digest is not None padding_algo = padding.OAEP( mgf=padding.MGF1(algorithm=mgf_digest), algorithm=digest, label=binascii.unhexlify(wycheproof.testcase["label"]), ) if not backend.rsa_encryption_supported(padding_algo): pytest.skip( f"Does not support OAEP using {mgf_digest.name} MGF1 " f"or {digest.name} hash." ) key = wycheproof.cache_value_to_group( "cached_key", lambda: serialization.load_pem_private_key( wycheproof.testgroup["privateKeyPem"].encode("ascii"), password=None, unsafe_skip_rsa_key_validation=True, ), ) assert isinstance(key, rsa.RSAPrivateKey) if backend._fips_enabled and key.key_size < backend._fips_rsa_min_key_size: pytest.skip("Invalid params for FIPS. <2048 bit keys are disallowed") if wycheproof.valid or wycheproof.acceptable: pt = key.decrypt( binascii.unhexlify(wycheproof.testcase["ct"]), padding_algo ) assert pt == binascii.unhexlify(wycheproof.testcase["msg"]) else: with pytest.raises(ValueError): key.decrypt( binascii.unhexlify(wycheproof.testcase["ct"]), padding_algo ) @pytest.mark.supported( only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5 for encryption.", ) @wycheproof_tests( "rsa_pkcs1_2048_test.json", "rsa_pkcs1_3072_test.json", "rsa_pkcs1_4096_test.json", ) def test_rsa_pkcs1_encryption(backend, wycheproof): key = wycheproof.cache_value_to_group( "cached_key", lambda: serialization.load_pem_private_key( wycheproof.testgroup["privateKeyPem"].encode("ascii"), password=None, unsafe_skip_rsa_key_validation=True, ), ) assert isinstance(key, rsa.RSAPrivateKey) if wycheproof.valid: pt = key.decrypt( binascii.unhexlify(wycheproof.testcase["ct"]), padding.PKCS1v15() ) assert pt == binascii.unhexlify(wycheproof.testcase["msg"]) else: if backend._lib.Cryptography_HAS_IMPLICIT_RSA_REJECTION: try: assert key.decrypt( binascii.unhexlify(wycheproof.testcase["ct"]), padding.PKCS1v15(), ) != binascii.unhexlify(wycheproof.testcase["ct"]) except ValueError: # Some raise ValueError due to length mismatch. pass else: with pytest.raises(ValueError): key.decrypt( binascii.unhexlify(wycheproof.testcase["ct"]), padding.PKCS1v15(), ) cryptography-43.0.0/tests/wycheproof/test_utils.py010064400017510000177000000005771464676315000206420ustar 00000000000000# 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. from ..utils import WycheproofTest def test_wycheproof_test_repr(): wycheproof = WycheproofTest({}, {}, {"tcId": 3}) assert repr(wycheproof) == "" cryptography-43.0.0/tests/wycheproof/test_x25519.py010064400017510000177000000024661464676315000203560ustar 00000000000000# 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. import binascii import pytest from cryptography.hazmat.primitives.asymmetric.x25519 import ( X25519PrivateKey, X25519PublicKey, ) from .utils import wycheproof_tests @pytest.mark.supported( only_if=lambda backend: backend.x25519_supported(), skip_message="Requires OpenSSL with X25519 support", ) @wycheproof_tests("x25519_test.json") def test_x25519(backend, wycheproof): assert set(wycheproof.testgroup.items()) == { ("curve", "curve25519"), ("type", "XdhComp"), } private_key = X25519PrivateKey.from_private_bytes( binascii.unhexlify(wycheproof.testcase["private"]) ) public_key = X25519PublicKey.from_public_bytes( binascii.unhexlify(wycheproof.testcase["public"]) ) assert wycheproof.valid or wycheproof.acceptable expected = binascii.unhexlify(wycheproof.testcase["shared"]) if expected == b"\x00" * 32: assert wycheproof.acceptable # OpenSSL returns an error on all zeros shared key with pytest.raises(ValueError): private_key.exchange(public_key) else: assert private_key.exchange(public_key) == expected cryptography-43.0.0/tests/wycheproof/test_x448.py010064400017510000177000000030641464676315000202030ustar 00000000000000# 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. import binascii import pytest from cryptography.hazmat.primitives.asymmetric.x448 import ( X448PrivateKey, X448PublicKey, ) from .utils import wycheproof_tests @pytest.mark.supported( only_if=lambda backend: backend.x448_supported(), skip_message="Requires OpenSSL with X448 support", ) @wycheproof_tests("x448_test.json") def test_x448(backend, wycheproof): assert set(wycheproof.testgroup.items()) == { ("curve", "curve448"), ("type", "XdhComp"), } private_key = X448PrivateKey.from_private_bytes( binascii.unhexlify(wycheproof.testcase["private"]) ) public_key_bytes = binascii.unhexlify(wycheproof.testcase["public"]) if len(public_key_bytes) == 57: assert wycheproof.acceptable assert wycheproof.has_flag("NonCanonicalPublic") with pytest.raises(ValueError): X448PublicKey.from_public_bytes(public_key_bytes) return public_key = X448PublicKey.from_public_bytes(public_key_bytes) assert wycheproof.valid or wycheproof.acceptable expected = binascii.unhexlify(wycheproof.testcase["shared"]) if expected == b"\x00" * 56: assert wycheproof.acceptable # OpenSSL returns an error on all zeros shared key with pytest.raises(ValueError): private_key.exchange(public_key) else: assert private_key.exchange(public_key) == expected cryptography-43.0.0/tests/wycheproof/utils.py010064400017510000177000000013601464676315000175720ustar 00000000000000# 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. import pytest from ..utils import load_wycheproof_tests def wycheproof_tests(*paths, subdir="testvectors"): def wrapper(func): @pytest.mark.parametrize("path", paths) def run_wycheproof(backend, subtests, pytestconfig, path): wycheproof_root = pytestconfig.getoption( "--wycheproof-root", skip=True ) for test in load_wycheproof_tests(wycheproof_root, path, subdir): with subtests.test(): func(backend, test) return run_wycheproof return wrapper cryptography-43.0.0/tests/x509/__init__.py010064400017510000177000000000001464676315000164770ustar 00000000000000cryptography-43.0.0/tests/x509/test_name.py010064400017510000177000000162251464676315000167370ustar 00000000000000# 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. import pytest from cryptography.x509 import ( Name, NameAttribute, NameOID, RelativeDistinguishedName, ) class TestRFC4514: def test_invalid(self, subtests): for value in [ "C=US,CN=Joe , Smith,DC=example", ",C=US,CN=Joe , Smith,DC=example", "C=US,UNKNOWN=Joe , Smith,DC=example", "C=US,CN,DC=example", "C=US,FOOBAR=example", ]: with subtests.test(): with pytest.raises(ValueError): Name.from_rfc4514_string(value) def test_valid(self, subtests): for value, expected in [ ( r"CN=James \"Jim\" Smith\, III", Name( [ NameAttribute( NameOID.COMMON_NAME, 'James "Jim" Smith, III' ) ] ), ), ( r"UID=\# escape\+\,\;\00this\ ", Name([NameAttribute(NameOID.USER_ID, "# escape+,;\0this ")]), ), ( r"2.5.4.3=James \"Jim\" Smith\, III", Name( [ NameAttribute( NameOID.COMMON_NAME, 'James "Jim" Smith, III' ) ] ), ), ("ST=", Name([NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "")])), ( "OU=Sales+CN=J. Smith,DC=example,DC=net", Name( [ RelativeDistinguishedName( [NameAttribute(NameOID.DOMAIN_COMPONENT, "net")] ), RelativeDistinguishedName( [ NameAttribute( NameOID.DOMAIN_COMPONENT, "example" ) ] ), RelativeDistinguishedName( [ NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "Sales" ), NameAttribute( NameOID.COMMON_NAME, "J. Smith" ), ] ), ] ), ), ( "CN=cryptography.io,O=PyCA,L=,ST=,C=US", Name( [ NameAttribute(NameOID.COUNTRY_NAME, "US"), NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, ""), NameAttribute(NameOID.LOCALITY_NAME, ""), NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), ] ), ), ( r"C=US,CN=Joe \, Smith,DC=example", Name( [ NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), NameAttribute(NameOID.COMMON_NAME, "Joe , Smith"), NameAttribute(NameOID.COUNTRY_NAME, "US"), ] ), ), ( r"C=US,CN=Jane \"J\,S\" Smith,DC=example", Name( [ NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), NameAttribute(NameOID.COMMON_NAME, 'Jane "J,S" Smith'), NameAttribute(NameOID.COUNTRY_NAME, "US"), ] ), ), ( 'C=US,CN=\\"Jane J\\,S Smith\\",DC=example', Name( [ NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), NameAttribute(NameOID.COMMON_NAME, '"Jane J,S Smith"'), NameAttribute(NameOID.COUNTRY_NAME, "US"), ] ), ), ( 'C=US,CN=\\"Jane \\"J\\,S\\" Smith\\",DC=example', Name( [ NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), NameAttribute( NameOID.COMMON_NAME, '"Jane "J,S" Smith"' ), NameAttribute(NameOID.COUNTRY_NAME, "US"), ] ), ), ( r"C=US,CN=Jane=Smith,DC=example", Name( [ NameAttribute(NameOID.DOMAIN_COMPONENT, "example"), NameAttribute(NameOID.COMMON_NAME, "Jane=Smith"), NameAttribute(NameOID.COUNTRY_NAME, "US"), ] ), ), (r"CN=#616263", Name([NameAttribute(NameOID.COMMON_NAME, "abc")])), (r"CN=👍", Name([NameAttribute(NameOID.COMMON_NAME, "👍")])), ( "CN=\\\\123", Name([NameAttribute(NameOID.COMMON_NAME, "\\123")]), ), ("CN=\\\\\\;", Name([NameAttribute(NameOID.COMMON_NAME, "\\;")])), ( "CN=\\\\#123", Name([NameAttribute(NameOID.COMMON_NAME, "\\#123")]), ), ( "2.5.4.10=abc", Name([NameAttribute(NameOID.ORGANIZATION_NAME, "abc")]), ), ("", Name([])), ]: with subtests.test(): result = Name.from_rfc4514_string(value) assert result == expected def test_attr_name_override(self): assert Name.from_rfc4514_string( "CN=Santa Claus,E=santa@north.pole", {"E": NameOID.EMAIL_ADDRESS} ) == Name( [ NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"), NameAttribute(NameOID.COMMON_NAME, "Santa Claus"), ] ) assert Name.from_rfc4514_string( "CN=Santa Claus", {"CN": NameOID.EMAIL_ADDRESS} ) == Name( [ NameAttribute(NameOID.EMAIL_ADDRESS, "Santa Claus"), ] ) def test_generate_parse(self): name_value = Name( [ NameAttribute(NameOID.COMMON_NAME, "Common Name 1"), NameAttribute(NameOID.LOCALITY_NAME, "City for Name 1"), NameAttribute( NameOID.ORGANIZATION_NAME, "Name 1 Organization" ), ] ) assert ( Name.from_rfc4514_string(name_value.rfc4514_string()) == name_value ) name_string = "O=Organization,L=City,CN=Common Name" assert ( Name.from_rfc4514_string(name_string).rfc4514_string() == name_string ) cryptography-43.0.0/tests/x509/test_ocsp.py010064400017510000177000001627701464676315000167720ustar 00000000000000# 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. import base64 import datetime import os from typing import Optional import pytest from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.x509 import ocsp from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 from ..utils import load_vectors_from_file, raises_unsupported_algorithm from .test_x509 import DummyExtension, _load_cert def _load_data(filename, loader): return load_vectors_from_file( filename=filename, loader=lambda data: loader(data.read()), mode="rb" ) def _cert_and_issuer(): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) issuer = _load_cert( os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), x509.load_pem_x509_certificate, ) return cert, issuer def _generate_root(private_key=None, algorithm=hashes.SHA256()): from cryptography.hazmat.backends.openssl.backend import backend if private_key is None: private_key = EC_KEY_SECP256R1.private_key(backend) subject = x509.Name( [ x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(x509.NameOID.COMMON_NAME, "Cryptography CA"), ] ) builder = ( x509.CertificateBuilder() .serial_number(123456789) .issuer_name(subject) .subject_name(subject) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime.now()) .not_valid_after( datetime.datetime.now() + datetime.timedelta(days=3650) ) ) cert = builder.sign(private_key, algorithm, backend) return cert, private_key def _check_ocsp_response_times( ocsp_resp: ocsp.OCSPResponse, this_update: datetime.datetime, next_update: Optional[datetime.datetime], revocation_time: Optional[datetime.datetime], ) -> None: with pytest.warns(utils.DeprecatedIn43): assert ocsp_resp.this_update == this_update assert ocsp_resp.this_update_utc == this_update.replace( tzinfo=datetime.timezone.utc ) with pytest.warns(utils.DeprecatedIn43): assert ocsp_resp.next_update == next_update assert ocsp_resp.next_update_utc == ( next_update.replace(tzinfo=datetime.timezone.utc) if next_update is not None else None ) with pytest.warns(utils.DeprecatedIn43): assert ocsp_resp.revocation_time == revocation_time assert ocsp_resp.revocation_time_utc == ( revocation_time.replace(tzinfo=datetime.timezone.utc) if revocation_time is not None else None ) class TestOCSPRequest: def test_bad_request(self): with pytest.raises(ValueError): ocsp.load_der_ocsp_request(b"invalid") def test_load_request(self): req = _load_data( os.path.join("x509", "ocsp", "req-sha1.der"), ocsp.load_der_ocsp_request, ) assert isinstance(req, ocsp.OCSPRequest) assert req.issuer_name_hash == ( b"8\xcaF\x8c\x07D\x8d\xf4\x81\x96\xc7mmLpQ\x9e`\xa7\xbd" ) assert req.issuer_key_hash == ( b"yu\xbb\x84:\xcb,\xdez\t\xbe1\x1bC\xbc\x1c*MSX" ) assert isinstance(req.hash_algorithm, hashes.SHA1) assert req.serial_number == int( "98D9E5C0B4C373552DF77C5D0F1EB5128E4945F9", 16 ) assert len(req.extensions) == 0 def test_load_request_with_extensions(self): req = _load_data( os.path.join("x509", "ocsp", "req-ext-nonce.der"), ocsp.load_der_ocsp_request, ) assert len(req.extensions) == 1 ext = req.extensions[0] assert ext.critical is False assert ext.value == x509.OCSPNonce( b"{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd" ) def test_load_request_with_acceptable_responses(self): req = _load_data( os.path.join("x509", "ocsp", "req-acceptable-responses.der"), ocsp.load_der_ocsp_request, ) assert len(req.extensions) == 1 ext = req.extensions[0] assert ext.critical is False assert ext.value == x509.OCSPAcceptableResponses( [x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")] ) def test_load_request_with_unknown_extension(self): req = _load_data( os.path.join("x509", "ocsp", "req-ext-unknown-oid.der"), ocsp.load_der_ocsp_request, ) assert len(req.extensions) == 1 ext = req.extensions[0] assert ext.critical is False assert ext.value == x509.UnrecognizedExtension( x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.2213"), b"\x04\x10{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd", ) def test_load_request_with_duplicate_extension(self): req = _load_data( os.path.join("x509", "ocsp", "req-duplicate-ext.der"), ocsp.load_der_ocsp_request, ) with pytest.raises(x509.DuplicateExtension): req.extensions def test_load_request_two_requests(self): with pytest.raises(NotImplementedError): _load_data( os.path.join("x509", "ocsp", "req-multi-sha1.der"), ocsp.load_der_ocsp_request, ) def test_invalid_hash_algorithm(self): req = _load_data( os.path.join("x509", "ocsp", "req-invalid-hash-alg.der"), ocsp.load_der_ocsp_request, ) with raises_unsupported_algorithm(None): req.hash_algorithm def test_serialize_request(self): req_bytes = load_vectors_from_file( filename=os.path.join("x509", "ocsp", "req-sha1.der"), loader=lambda data: data.read(), mode="rb", ) req = ocsp.load_der_ocsp_request(req_bytes) assert req.public_bytes(serialization.Encoding.DER) == req_bytes def test_invalid_serialize_encoding(self): req = _load_data( os.path.join("x509", "ocsp", "req-sha1.der"), ocsp.load_der_ocsp_request, ) with pytest.raises(ValueError): req.public_bytes("invalid") with pytest.raises(ValueError): req.public_bytes(serialization.Encoding.PEM) class TestOCSPRequestBuilder: def test_add_cert_twice(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() builder = builder.add_certificate(cert, issuer, hashes.SHA1()) # Fails calling a second time with pytest.raises(ValueError): builder.add_certificate(cert, issuer, hashes.SHA1()) # Fails calling a second time with add_certificate_by_hash with pytest.raises(ValueError): builder.add_certificate_by_hash( b"0" * 20, b"0" * 20, 1, hashes.SHA1() ) def test_add_cert_by_hash_twice(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() builder = builder.add_certificate_by_hash( b"0" * 20, b"0" * 20, 1, hashes.SHA1() ) # Fails calling a second time with pytest.raises(ValueError): builder.add_certificate_by_hash( b"0" * 20, b"0" * 20, 1, hashes.SHA1() ) # Fails calling a second time with add_certificate with pytest.raises(ValueError): builder.add_certificate(cert, issuer, hashes.SHA1()) def test_add_cert_by_hash_bad_hash(self): builder = ocsp.OCSPRequestBuilder() with pytest.raises(ValueError): builder.add_certificate_by_hash( b"0" * 20, b"0" * 20, 1, "notahash", # type:ignore[arg-type] ) with pytest.raises(ValueError): builder.add_certificate_by_hash( b"0" * 19, b"0" * 20, 1, hashes.SHA1() ) with pytest.raises(ValueError): builder.add_certificate_by_hash( b"0" * 20, b"0" * 21, 1, hashes.SHA1() ) with pytest.raises(TypeError): builder.add_certificate_by_hash( b"0" * 20, b"0" * 20, "notanint", # type:ignore[arg-type] hashes.SHA1(), ) def test_create_ocsp_request_no_req(self): builder = ocsp.OCSPRequestBuilder() with pytest.raises(ValueError): builder.build() def test_create_ocsp_request_invalid_alg(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() with pytest.raises(ValueError): builder.add_certificate(cert, issuer, hashes.MD5()) def test_add_extension_twice(self): builder = ocsp.OCSPRequestBuilder() builder = builder.add_extension(x509.OCSPNonce(b"123"), False) with pytest.raises(ValueError): builder.add_extension(x509.OCSPNonce(b"123"), False) def test_add_invalid_extension(self): builder = ocsp.OCSPRequestBuilder() with pytest.raises(TypeError): builder.add_extension( "notanext", # type:ignore[arg-type] False, ) def test_unsupported_extension(self): cert, issuer = _cert_and_issuer() builder = ( ocsp.OCSPRequestBuilder() .add_extension(DummyExtension(), critical=False) .add_certificate(cert, issuer, hashes.SHA256()) ) with pytest.raises(NotImplementedError): builder.build() def test_create_ocsp_request_invalid_cert(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() with pytest.raises(TypeError): builder.add_certificate( b"notacert", # type:ignore[arg-type] issuer, hashes.SHA1(), ) with pytest.raises(TypeError): builder.add_certificate( cert, b"notacert", # type:ignore[arg-type] hashes.SHA1(), ) def test_create_ocsp_request(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() builder = builder.add_certificate(cert, issuer, hashes.SHA1()) req = builder.build() serialized = req.public_bytes(serialization.Encoding.DER) assert serialized == base64.b64decode( b"MEMwQTA/MD0wOzAJBgUrDgMCGgUABBRAC0Z68eay0wmDug1gfn5ZN0gkxAQUw5zz" b"/NNGCDS7zkZ/oHxb8+IIy1kCAj8g" ) @pytest.mark.parametrize( ("ext", "critical"), [ [x509.OCSPNonce(b"0000"), False], [x509.OCSPNonce(b"\x00\x01\x02"), True], ], ) def test_create_ocsp_request_with_extension(self, ext, critical): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() builder = builder.add_certificate( cert, issuer, hashes.SHA1() ).add_extension(ext, critical) req = builder.build() assert len(req.extensions) == 1 assert req.extensions[0].value == ext assert req.extensions[0].oid == ext.oid assert req.extensions[0].critical is critical def test_add_cert_by_hash(self): cert, _ = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() h = hashes.Hash(hashes.SHA1()) h.update(cert.issuer.public_bytes()) issuer_name_hash = h.finalize() # issuer_key_hash is a hash of the public key BitString DER, # not the subjectPublicKeyInfo issuer_key_hash = base64.b64decode(b"w5zz/NNGCDS7zkZ/oHxb8+IIy1k=") builder = builder.add_certificate_by_hash( issuer_name_hash, issuer_key_hash, cert.serial_number, hashes.SHA1(), ) req = builder.build() serialized = req.public_bytes(serialization.Encoding.DER) assert serialized == base64.b64decode( b"MEMwQTA/MD0wOzAJBgUrDgMCGgUABBRAC0Z68eay0wmDug1gfn5ZN0gkxAQUw5zz" b"/NNGCDS7zkZ/oHxb8+IIy1kCAj8g" ) class TestOCSPResponseBuilder: def test_add_response_twice(self): cert, issuer = _cert_and_issuer() time = datetime.datetime.now() builder = ocsp.OCSPResponseBuilder() builder = builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, time, None, None, ) with pytest.raises(ValueError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, time, None, None, ) def test_invalid_add_response(self): cert, issuer = _cert_and_issuer() time = datetime.datetime.now(datetime.timezone.utc).replace( tzinfo=None ) reason = x509.ReasonFlags.cessation_of_operation builder = ocsp.OCSPResponseBuilder() with pytest.raises(TypeError): builder.add_response( "bad", # type:ignore[arg-type] issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, time, None, None, ) with pytest.raises(TypeError): builder.add_response( cert, "bad", # type:ignore[arg-type] hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, time, None, None, ) with pytest.raises(ValueError): builder.add_response( cert, issuer, "notahash", # type:ignore[arg-type] ocsp.OCSPCertStatus.GOOD, time, time, None, None, ) with pytest.raises(TypeError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, "bad", # type:ignore[arg-type] time, None, None, ) with pytest.raises(TypeError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, "bad", # type:ignore[arg-type] None, None, ) with pytest.raises(TypeError): builder.add_response( cert, issuer, hashes.SHA256(), 0, # type:ignore[arg-type] time, time, None, None, ) with pytest.raises(ValueError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, time, time, None, ) with pytest.raises(ValueError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, time, None, reason, ) with pytest.raises(TypeError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, time, time, None, reason, ) with pytest.raises(TypeError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, time, time, time, 0, # type:ignore[arg-type] ) with pytest.raises(ValueError): builder.add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, time, time, time - datetime.timedelta(days=36500), None, ) def test_invalid_certificates(self): builder = ocsp.OCSPResponseBuilder() with pytest.raises(ValueError): builder.certificates([]) with pytest.raises(TypeError): builder.certificates(["notacert"]) # type: ignore[list-item] with pytest.raises(TypeError): builder.certificates("invalid") # type: ignore[arg-type] _, issuer = _cert_and_issuer() builder = builder.certificates([issuer]) with pytest.raises(ValueError): builder.certificates([issuer]) def test_invalid_responder_id(self): builder = ocsp.OCSPResponseBuilder() cert, _ = _cert_and_issuer() with pytest.raises(TypeError): builder.responder_id( ocsp.OCSPResponderEncoding.HASH, "invalid", # type: ignore[arg-type] ) with pytest.raises(TypeError): builder.responder_id("notanenum", cert) # type: ignore[arg-type] builder = builder.responder_id(ocsp.OCSPResponderEncoding.NAME, cert) with pytest.raises(ValueError): builder.responder_id(ocsp.OCSPResponderEncoding.NAME, cert) def test_invalid_extension(self): builder = ocsp.OCSPResponseBuilder() with pytest.raises(TypeError): builder.add_extension( "notanextension", # type: ignore[arg-type] True, ) def test_unsupported_extension(self): root_cert, private_key = _generate_root() cert, issuer = _cert_and_issuer() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = ( ocsp.OCSPResponseBuilder() .responder_id(ocsp.OCSPResponderEncoding.NAME, root_cert) .add_response( cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) .add_extension(DummyExtension(), critical=False) ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256()) def test_sign_no_response(self): builder = ocsp.OCSPResponseBuilder() root_cert, private_key = _generate_root() builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256()) def test_sign_no_responder_id(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() _, private_key = _generate_root() current_time = ( datetime.datetime.now().replace(tzinfo=None).replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256()) def test_sign_invalid_hash_algorithm(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now().replace(tzinfo=None).replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) with pytest.raises(TypeError): builder.sign(private_key, "notahash") # type: ignore[arg-type] def test_sign_good_cert(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.responder_name == root_cert.subject assert resp.responder_key_hash is None with pytest.warns(utils.DeprecatedIn43): assert (current_time - resp.produced_at).total_seconds() < 10 assert ( current_time.replace(tzinfo=datetime.timezone.utc) - resp.produced_at_utc ).total_seconds() < 10 assert ( resp.signature_algorithm_oid == x509.SignatureAlgorithmOID.ECDSA_WITH_SHA256 ) assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD assert resp.revocation_reason is None _check_ocsp_response_times( resp, this_update=this_update, next_update=next_update, revocation_time=None, ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) def test_sign_revoked_cert(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) revoked_date = this_update - datetime.timedelta(days=300) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, this_update, next_update, revoked_date, None, ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED assert resp.revocation_reason is None _check_ocsp_response_times( resp, this_update=this_update, next_update=next_update, revocation_time=revoked_date, ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) def test_sign_unknown_cert(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.UNKNOWN, this_update, next_update, None, None, ) resp = builder.sign(private_key, hashes.SHA384()) assert resp.certificate_status == ocsp.OCSPCertStatus.UNKNOWN _check_ocsp_response_times( resp, this_update=this_update, next_update=next_update, revocation_time=None, ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA384()) ) def test_sign_with_appended_certs(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = ( builder.responder_id(ocsp.OCSPResponderEncoding.NAME, root_cert) .add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) .certificates([root_cert]) ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.certificates == [root_cert] def test_sign_revoked_no_next_update(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) revoked_date = this_update - datetime.timedelta(days=300) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, this_update, None, revoked_date, None, ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED assert resp.revocation_reason is None _check_ocsp_response_times( resp, this_update=this_update, next_update=None, revocation_time=revoked_date, ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) def test_sign_revoked_with_reason(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) revoked_date = this_update - datetime.timedelta(days=300) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, this_update, next_update, revoked_date, x509.ReasonFlags.key_compromise, ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED assert resp.revocation_reason is x509.ReasonFlags.key_compromise _check_ocsp_response_times( resp, this_update=this_update, next_update=next_update, revocation_time=revoked_date, ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) def test_sign_responder_id_key_hash(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.HASH, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) resp = builder.sign(private_key, hashes.SHA256()) assert resp.responder_name is None assert resp.responder_key_hash == ( b"\x8ca\x94\xe0\x948\xed\x89\xd8\xd4N\x89p\t\xd6\xf9^_\xec}" ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) def test_invalid_sign_responder_cert_does_not_match_private_key(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, _ = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.HASH, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) from cryptography.hazmat.backends.openssl.backend import backend diff_key = ec.generate_private_key(ec.SECP256R1(), backend) with pytest.raises(ValueError): builder.sign(diff_key, hashes.SHA256()) def test_sign_with_extension(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = ( builder.responder_id(ocsp.OCSPResponderEncoding.HASH, root_cert) .add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) .add_extension(x509.OCSPNonce(b"012345"), False) ) resp = builder.sign(private_key, hashes.SHA256()) assert len(resp.extensions) == 1 assert resp.extensions[0].value == x509.OCSPNonce(b"012345") assert resp.extensions[0].critical is False private_key.public_key().verify( resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) ) @pytest.mark.parametrize( ("status", "der"), [ (ocsp.OCSPResponseStatus.MALFORMED_REQUEST, b"0\x03\n\x01\x01"), (ocsp.OCSPResponseStatus.INTERNAL_ERROR, b"0\x03\n\x01\x02"), (ocsp.OCSPResponseStatus.TRY_LATER, b"0\x03\n\x01\x03"), (ocsp.OCSPResponseStatus.SIG_REQUIRED, b"0\x03\n\x01\x05"), (ocsp.OCSPResponseStatus.UNAUTHORIZED, b"0\x03\n\x01\x06"), ], ) def test_build_non_successful_statuses(self, status, der): resp = ocsp.OCSPResponseBuilder.build_unsuccessful(status) assert resp.response_status is status assert resp.public_bytes(serialization.Encoding.DER) == der def test_invalid_build_not_a_status(self): with pytest.raises(TypeError): ocsp.OCSPResponseBuilder.build_unsuccessful( "notastatus" # type: ignore[arg-type] ) def test_invalid_build_successful_status(self): with pytest.raises(ValueError): ocsp.OCSPResponseBuilder.build_unsuccessful( ocsp.OCSPResponseStatus.SUCCESSFUL ) def test_sign_unknown_private_key(self, backend): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, _ = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) with pytest.raises(TypeError): builder.sign(object(), hashes.SHA256()) # type:ignore[arg-type] @pytest.mark.supported( only_if=lambda backend: backend.hash_supported( hashes.BLAKE2b(digest_size=64) ), skip_message="Does not support BLAKE2b", ) def test_sign_unrecognized_hash_algorithm(self, backend): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) with pytest.raises(UnsupportedAlgorithm): builder.sign(private_key, hashes.BLAKE2b(digest_size=64)) def test_sign_none_hash_not_eddsa(self): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() root_cert, private_key = _generate_root() current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, next_update, None, None, ) with pytest.raises(TypeError): builder.sign(private_key, None) class TestSignedCertificateTimestampsExtension: def test_init(self): with pytest.raises(TypeError): x509.SignedCertificateTimestamps( [object()] # type: ignore[list-item] ) def test_repr(self): assert repr(x509.SignedCertificateTimestamps([])) == ( "" ) def test_eq(self, backend): sct1 = ( _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), ocsp.load_der_ocsp_response, ) .single_extensions.get_extension_for_class( x509.SignedCertificateTimestamps ) .value ) sct2 = ( _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), ocsp.load_der_ocsp_response, ) .single_extensions.get_extension_for_class( x509.SignedCertificateTimestamps ) .value ) assert sct1 == sct2 def test_ne(self, backend): sct1 = ( _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), ocsp.load_der_ocsp_response, ) .single_extensions.get_extension_for_class( x509.SignedCertificateTimestamps ) .value ) sct2 = x509.SignedCertificateTimestamps([]) assert sct1 != sct2 assert sct1 != object() def test_hash(self, backend): sct1 = ( _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), ocsp.load_der_ocsp_response, ) .single_extensions.get_extension_for_class( x509.SignedCertificateTimestamps ) .value ) sct2 = ( _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), ocsp.load_der_ocsp_response, ) .single_extensions.get_extension_for_class( x509.SignedCertificateTimestamps ) .value ) sct3 = x509.SignedCertificateTimestamps([]) assert hash(sct1) == hash(sct2) assert hash(sct1) != hash(sct3) def test_entry_type(self, backend): [sct, _, _, _] = ( _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), ocsp.load_der_ocsp_response, ) .single_extensions.get_extension_for_class( x509.SignedCertificateTimestamps ) .value ) assert ( sct.entry_type == x509.certificate_transparency.LogEntryType.X509_CERTIFICATE ) class TestOCSPResponse: def test_bad_response(self): with pytest.raises(ValueError): ocsp.load_der_ocsp_response(b"invalid") def test_load_response(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-sha256.der"), ocsp.load_der_ocsp_response, ) issuer = _load_cert( os.path.join("x509", "letsencryptx3.pem"), x509.load_pem_x509_certificate, ) assert isinstance(resp, ocsp.OCSPResponse) assert resp.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL assert ( resp.signature_algorithm_oid == x509.SignatureAlgorithmOID.RSA_WITH_SHA256 ) assert isinstance(resp.signature_hash_algorithm, hashes.SHA256) assert resp.signature == base64.b64decode( b"I9KUlyLV/2LbNCVu1BQphxdNlU/jBzXsPYVscPjW5E93pCrSO84GkIWoOJtqsnt" b"78DLcQPnF3W24NXGzSGKlSWfXIsyoXCxnBm0mIbD5ZMnKyXEnqSR33Z9He/A+ML" b"A8gbrDUipGNPosesenkKUnOtFIzEGv29hV5E6AMP2ORPVsVlTAZegPJFbbVIWc0" b"rZGFCXKxijDxtUtgWzBhpBAI50JbPHi+IVuaOe4aDJLYgZ0BIBNa6bDI+rScyoy" b"5U0DToV7SZn6CoJ3U19X7BHdYn6TLX0xi43eXuzBGzdHnSzmsc7r/DvkAKJm3vb" b"dVECXqe/gFlXJUBcZ25jhs70MUA==" ) assert resp.tbs_response_bytes == base64.b64decode( b"MIHWoUwwSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzA" b"hBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzGA8yMDE4MDgzMDExMT" b"UwMFowdTBzMEswCQYFKw4DAhoFAAQUfuZq53Kas/z4oiBkbBahLWBxCF0EFKhKa" b"mMEfd265tE5t6ZFZe/zqOyhAhIDHHh6fckClQB7xfIiCztSevCAABgPMjAxODA4" b"MzAxMTAwMDBaoBEYDzIwMTgwOTA2MTEwMDAwWg==" ) public_key = issuer.public_key() assert isinstance(public_key, rsa.RSAPublicKey) public_key.verify( resp.signature, resp.tbs_response_bytes, PKCS1v15(), resp.signature_hash_algorithm, ) assert resp.certificates == [] assert resp.responder_key_hash is None assert resp.responder_name == issuer.subject with pytest.warns(utils.DeprecatedIn43): assert resp.produced_at == datetime.datetime(2018, 8, 30, 11, 15) assert resp.produced_at_utc == datetime.datetime( 2018, 8, 30, 11, 15, tzinfo=datetime.timezone.utc ) assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD assert resp.revocation_reason is None _check_ocsp_response_times( resp, this_update=datetime.datetime(2018, 8, 30, 11, 0), next_update=datetime.datetime(2018, 9, 6, 11, 0), revocation_time=None, ) assert resp.issuer_key_hash == ( b"\xa8Jjc\x04}\xdd\xba\xe6\xd19\xb7\xa6Ee\xef\xf3\xa8\xec\xa1" ) assert resp.issuer_name_hash == ( b"~\xe6j\xe7r\x9a\xb3\xfc\xf8\xa2 dl\x16\xa1-`q\x08]" ) assert isinstance(resp.hash_algorithm, hashes.SHA1) assert resp.serial_number == 271024907440004808294641238224534273948400 assert len(resp.extensions) == 0 def test_load_multi_valued_response(self): resp = _load_data( os.path.join("x509", "ocsp", "ocsp-army.deps.mil-resp.der"), ocsp.load_der_ocsp_response, ) with pytest.raises(ValueError): resp.serial_number assert isinstance(next(resp.responses), ocsp.OCSPSingleResponse) assert len(list(resp.responses)) == 20 def test_multi_valued_responses(self): req_valid = _load_data( os.path.join("x509", "ocsp", "ocsp-army.valid-req.der"), ocsp.load_der_ocsp_request, ) req_revoked = _load_data( os.path.join("x509", "ocsp", "ocsp-army.revoked-req.der"), ocsp.load_der_ocsp_request, ) req_irrelevant = _load_data( os.path.join("x509", "ocsp", "ocsp-army.inapplicable-req.der"), ocsp.load_der_ocsp_request, ) resp = _load_data( os.path.join("x509", "ocsp", "ocsp-army.deps.mil-resp.der"), ocsp.load_der_ocsp_response, ) for elem in resp.responses: serial = elem.serial_number assert req_irrelevant.serial_number != serial if req_valid.serial_number == serial: assert elem.issuer_key_hash == req_valid.issuer_key_hash assert elem.issuer_name_hash == req_valid.issuer_name_hash assert ( elem.hash_algorithm.name == req_valid.hash_algorithm.name ) assert elem.certificate_status == ocsp.OCSPCertStatus.GOOD with pytest.warns(utils.DeprecatedIn43): assert elem.this_update == datetime.datetime( 2020, 2, 22, 0, 0 ) assert elem.this_update_utc == datetime.datetime( 2020, 2, 22, 0, 0, tzinfo=datetime.timezone.utc ) with pytest.warns(utils.DeprecatedIn43): assert elem.next_update == datetime.datetime( 2020, 2, 29, 1, 0 ) assert elem.next_update_utc == datetime.datetime( 2020, 2, 29, 1, 0, tzinfo=datetime.timezone.utc ) elif req_revoked.serial_number == serial: assert elem.certificate_status == ocsp.OCSPCertStatus.REVOKED assert ( elem.revocation_reason == x509.ReasonFlags.cessation_of_operation ) with pytest.warns(utils.DeprecatedIn43): assert elem.revocation_time == datetime.datetime( 2018, 5, 30, 14, 1, 39 ) assert elem.revocation_time_utc == datetime.datetime( 2018, 5, 30, 14, 1, 39, tzinfo=datetime.timezone.utc ) def test_load_unauthorized(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-unauthorized.der"), ocsp.load_der_ocsp_response, ) assert resp.response_status == ocsp.OCSPResponseStatus.UNAUTHORIZED with pytest.raises(ValueError): resp.signature_algorithm_oid with pytest.raises(ValueError): resp.signature_hash_algorithm with pytest.raises(ValueError): resp.signature with pytest.raises(ValueError): resp.tbs_response_bytes with pytest.raises(ValueError): resp.certificates with pytest.raises(ValueError): resp.responder_key_hash with pytest.raises(ValueError): resp.responder_name with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.produced_at with pytest.raises(ValueError): resp.produced_at_utc with pytest.raises(ValueError): resp.certificate_status with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.revocation_time with pytest.raises(ValueError): resp.revocation_time_utc with pytest.raises(ValueError): resp.revocation_reason with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.this_update with pytest.raises(ValueError): resp.this_update_utc with pytest.raises(ValueError), pytest.warns(utils.DeprecatedIn43): resp.next_update with pytest.raises(ValueError): resp.next_update_utc with pytest.raises(ValueError): resp.issuer_key_hash with pytest.raises(ValueError): resp.issuer_name_hash with pytest.raises(ValueError): resp.hash_algorithm with pytest.raises(ValueError): resp.serial_number with pytest.raises(ValueError): resp.extensions def test_load_revoked(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-revoked.der"), ocsp.load_der_ocsp_response, ) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED with pytest.warns(utils.DeprecatedIn43): assert resp.revocation_time == datetime.datetime( 2016, 9, 2, 21, 28, 48 ) assert resp.revocation_time_utc == datetime.datetime( 2016, 9, 2, 21, 28, 48, tzinfo=datetime.timezone.utc ) assert resp.revocation_reason is None def test_load_delegate_unknown_cert(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-delegate-unknown-cert.der"), ocsp.load_der_ocsp_response, ) assert len(resp.certificates) == 1 assert isinstance(resp.certificates[0], x509.Certificate) assert resp.certificate_status == ocsp.OCSPCertStatus.UNKNOWN def test_load_invalid_signature_oid(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-invalid-signature-oid.der"), ocsp.load_der_ocsp_response, ) assert resp.signature_algorithm_oid == x509.ObjectIdentifier( "1.2.840.113549.1.1.2" ) with raises_unsupported_algorithm(None): resp.signature_hash_algorithm def test_unknown_hash_algorithm(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-unknown-hash-alg.der"), ocsp.load_der_ocsp_response, ) with raises_unsupported_algorithm(None): resp.hash_algorithm def test_load_responder_key_hash(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-responder-key-hash.der"), ocsp.load_der_ocsp_response, ) assert resp.responder_name is None assert resp.responder_key_hash == ( b"\x0f\x80a\x1c\x821a\xd5/(\xe7\x8dF8\xb4,\xe1\xc6\xd9\xe2" ) def test_load_revoked_reason(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-revoked-reason.der"), ocsp.load_der_ocsp_response, ) assert resp.revocation_reason is x509.ReasonFlags.superseded def test_load_revoked_no_next_update(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-revoked-no-next-update.der"), ocsp.load_der_ocsp_response, ) assert resp.serial_number == 16160 with pytest.warns(utils.DeprecatedIn43): assert resp.next_update is None assert resp.next_update_utc is None def test_response_extensions(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-revoked-reason.der"), ocsp.load_der_ocsp_response, ) assert len(resp.extensions) == 1 ext = resp.extensions[0] assert ext.critical is False assert ext.value == x509.OCSPNonce( b'5\x957\x9fa\x03\x83\x87\x89rW\x8f\xae\x99\xf7"' ) def test_response_unknown_extension(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-unknown-extension.der"), ocsp.load_der_ocsp_response, ) assert len(resp.extensions) == 1 ext = resp.extensions[0] assert ext.critical is False assert ext.value == x509.UnrecognizedExtension( x509.ObjectIdentifier("1.3.6.1.5.5.7.48.1.2.200"), b'\x04\x105\x957\x9fa\x03\x83\x87\x89rW\x8f\xae\x99\xf7"', ) def test_serialize_reponse(self): resp_bytes = load_vectors_from_file( filename=os.path.join("x509", "ocsp", "resp-revoked.der"), loader=lambda data: data.read(), mode="rb", ) resp = ocsp.load_der_ocsp_response(resp_bytes) assert resp.public_bytes(serialization.Encoding.DER) == resp_bytes def test_invalid_serialize_encoding(self): resp = _load_data( os.path.join("x509", "ocsp", "resp-revoked.der"), ocsp.load_der_ocsp_response, ) with pytest.raises(ValueError): resp.public_bytes("invalid") with pytest.raises(ValueError): resp.public_bytes(serialization.Encoding.PEM) def test_single_extensions_sct(self, backend): resp = _load_data( os.path.join("x509", "ocsp", "resp-sct-extension.der"), ocsp.load_der_ocsp_response, ) assert len(resp.single_extensions) == 1 ext = resp.single_extensions[0] assert ext.oid == x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") assert len(ext.value) == 4 log_ids = [base64.b64encode(sct.log_id) for sct in ext.value] assert log_ids == [ b"RJRlLrDuzq/EQAfYqP4owNrmgr7YyzG1P9MzlrW2gag=", b"b1N2rDHwMRnYmQCkURX/dxUcEdkCwQApBo2yCJo32RM=", b"u9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e0YU=", b"7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs=", ] def test_single_extensions(self, backend): resp = _load_data( os.path.join("x509", "ocsp", "resp-single-extension-reason.der"), ocsp.load_der_ocsp_response, ) assert len(resp.single_extensions) == 1 ext = resp.single_extensions[0] assert ext.oid == x509.CRLReason.oid assert ext.value == x509.CRLReason(x509.ReasonFlags.unspecified) def test_unknown_response_type(self): with pytest.raises(ValueError): _load_data( os.path.join( "x509", "ocsp", "resp-response-type-unknown-oid.der" ), ocsp.load_der_ocsp_response, ) def test_response_bytes_absent(self): with pytest.raises(ValueError): _load_data( os.path.join( "x509", "ocsp", "resp-successful-no-response-bytes.der" ), ocsp.load_der_ocsp_response, ) def test_unknown_response_status(self): with pytest.raises(ValueError): _load_data( os.path.join( "x509", "ocsp", "resp-unknown-response-status.der" ), ocsp.load_der_ocsp_response, ) class TestOCSPEdDSA: @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support / OCSP", ) def test_invalid_algorithm(self, backend): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() private_key = ed25519.Ed25519PrivateKey.generate() root_cert, _ = _generate_root(private_key, None) current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) revoked_date = this_update - datetime.timedelta(days=300) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, this_update, next_update, revoked_date, x509.ReasonFlags.key_compromise, ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256()) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support / OCSP", ) def test_sign_ed25519(self, backend): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() private_key = ed25519.Ed25519PrivateKey.generate() root_cert, _ = _generate_root(private_key, None) current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) revoked_date = this_update - datetime.timedelta(days=300) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, this_update, next_update, revoked_date, x509.ReasonFlags.key_compromise, ) resp = builder.sign(private_key, None) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED assert resp.revocation_reason is x509.ReasonFlags.key_compromise _check_ocsp_response_times( resp, this_update=this_update, next_update=next_update, revocation_time=revoked_date, ) assert resp.signature_hash_algorithm is None assert ( resp.signature_algorithm_oid == x509.SignatureAlgorithmOID.ED25519 ) private_key.public_key().verify( resp.signature, resp.tbs_response_bytes ) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support / OCSP", ) def test_sign_ed448(self, backend): builder = ocsp.OCSPResponseBuilder() cert, issuer = _cert_and_issuer() private_key = ed448.Ed448PrivateKey.generate() root_cert, _ = _generate_root(private_key, None) current_time = ( datetime.datetime.now(datetime.timezone.utc) .replace(tzinfo=None) .replace(microsecond=0) ) this_update = current_time - datetime.timedelta(days=1) next_update = this_update + datetime.timedelta(days=7) revoked_date = this_update - datetime.timedelta(days=300) builder = builder.responder_id( ocsp.OCSPResponderEncoding.NAME, root_cert ).add_response( cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, this_update, next_update, revoked_date, x509.ReasonFlags.key_compromise, ) resp = builder.sign(private_key, None) assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED assert resp.revocation_reason is x509.ReasonFlags.key_compromise _check_ocsp_response_times( resp, this_update=this_update, next_update=next_update, revocation_time=revoked_date, ) assert resp.signature_hash_algorithm is None assert resp.signature_algorithm_oid == x509.SignatureAlgorithmOID.ED448 private_key.public_key().verify( resp.signature, resp.tbs_response_bytes ) cryptography-43.0.0/tests/x509/test_x509.py010064400017510000177000007651711464676315000165370ustar 00000000000000# 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. import binascii import copy import datetime import ipaddress import os import typing import pytest from cryptography import utils, x509 from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.bindings._rust import test_support from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dh, dsa, ec, ed448, ed25519, padding, rsa, types, x448, x25519, ) from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, ) from cryptography.x509.name import _ASN1Type from cryptography.x509.oid import ( AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, PublicKeyAlgorithmOID, SignatureAlgorithmOID, SubjectInformationAccessOID, ) from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048, DSA_KEY_3072 from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 from ..hazmat.primitives.fixtures_rsa import ( RSA_KEY_2048_ALT, ) from ..hazmat.primitives.test_ec import _skip_curve_unsupported from ..hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 from ..utils import ( load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) # Make ruff happy since we're importing fixtures that pytest patches in as # func args __all__ = ["rsa_key_512", "rsa_key_2048"] class DummyExtension(x509.ExtensionType): oid = x509.ObjectIdentifier("1.2.3.4") class FakeGeneralName(x509.GeneralName): def __init__(self, value): self._value = value @property def value(self): return self._value T = typing.TypeVar("T") def _load_cert(filename, loader: typing.Callable[..., T]) -> T: return load_vectors_from_file( filename=filename, loader=lambda pemfile: loader(pemfile.read()), mode="rb", ) def _generate_ca_and_leaf( issuer_private_key: types.CertificateIssuerPrivateKeyTypes, subject_private_key: types.CertificateIssuerPrivateKeyTypes, ): if isinstance( issuer_private_key, (ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey), ): hash_alg = None else: hash_alg = hashes.SHA256() builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .public_key(issuer_private_key.public_key()) .serial_number(1) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2030, 1, 1)) ) ca = builder.sign(issuer_private_key, hash_alg) builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "leaf")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .public_key(subject_private_key.public_key()) .serial_number(100) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2025, 1, 1)) ) cert = builder.sign(issuer_private_key, hash_alg) return ca, cert def _break_cert_sig(cert: x509.Certificate) -> x509.Certificate: cert_bad_sig = bytearray(cert.public_bytes(serialization.Encoding.PEM)) # Break the sig by mutating 5 bytes. That's the base64 representation # though so there's somewhere closer to 2**-32 probability of # not breaking the sig. Spin that roulette wheel. cert_bad_sig[-40:-35] = 90, 90, 90, 90, 90 return x509.load_pem_x509_certificate(bytes(cert_bad_sig)) def _check_cert_times( cert: x509.Certificate, not_valid_before: typing.Optional[datetime.datetime], not_valid_after: typing.Optional[datetime.datetime], ) -> None: if not_valid_before: with pytest.warns(utils.DeprecatedIn42): assert cert.not_valid_before == not_valid_before assert cert.not_valid_before_utc == not_valid_before.replace( tzinfo=datetime.timezone.utc ) if not_valid_after: with pytest.warns(utils.DeprecatedIn42): assert cert.not_valid_after == not_valid_after assert cert.not_valid_after_utc == not_valid_after.replace( tzinfo=datetime.timezone.utc ) def _check_crl_times( crl: x509.CertificateRevocationList, last_update: datetime.datetime, next_update: datetime.datetime, ) -> None: with pytest.warns(utils.DeprecatedIn42): assert crl.last_update == last_update assert crl.next_update == next_update assert crl.last_update_utc == last_update.replace( tzinfo=datetime.timezone.utc ) assert crl.next_update_utc == next_update.replace( tzinfo=datetime.timezone.utc ) class TestCertificateRevocationList: def test_load_pem_crl(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) assert isinstance(crl, x509.CertificateRevocationList) fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) assert fingerprint == b"191b3428bf9d0dafa4edd42bc98603e182614c57" assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) assert ( crl.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA256 ) def test_load_der_crl(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, ) assert isinstance(crl, x509.CertificateRevocationList) fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) assert fingerprint == b"dd3db63c50f4c4a13e090f14053227cb1011a5ad" assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) def test_load_large_crl(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_almost_10k.pem"), x509.load_pem_x509_crl, ) assert len(crl) == 9999 def test_empty_crl_no_sequence(self, backend): # The SEQUENCE for revoked certificates is optional so let's # test that we handle it properly. crl = _load_cert( os.path.join("x509", "custom", "crl_empty_no_sequence.der"), x509.load_der_x509_crl, ) assert len(crl) == 0 with pytest.raises(IndexError): crl[0] assert crl.get_revoked_certificate_by_serial_number(12) is None assert list(iter(crl)) == [] def test_invalid_pem(self, backend): with pytest.raises(ValueError): x509.load_pem_x509_crl(b"notacrl", backend) pem_bytes = _load_cert( os.path.join("x509", "custom", "valid_signature_cert.pem"), lambda data: data, ) with pytest.raises(ValueError): x509.load_pem_x509_crl(pem_bytes, backend) def test_invalid_der(self, backend): with pytest.raises(ValueError): x509.load_der_x509_crl(b"notacrl", backend) def test_invalid_time(self, backend): with pytest.raises(ValueError, match="TBSCertList::this_update"): _load_cert( os.path.join("x509", "custom", "crl_invalid_time.der"), x509.load_der_x509_crl, ) def test_unknown_signature_algorithm(self, backend): crl = _load_cert( os.path.join( "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" ), x509.load_pem_x509_crl, ) with raises_unsupported_algorithm(None): crl.signature_hash_algorithm def test_invalid_version(self, backend): with pytest.raises(x509.InvalidVersion): _load_cert( os.path.join("x509", "custom", "crl_bad_version.pem"), x509.load_pem_x509_crl, ) def test_issuer(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, ) assert isinstance(crl.issuer, x509.Name) assert list(crl.issuer) == [ x509.NameAttribute(x509.OID_COUNTRY_NAME, "US"), x509.NameAttribute( x509.OID_ORGANIZATION_NAME, "Test Certificates 2011" ), x509.NameAttribute(x509.OID_COMMON_NAME, "Good CA"), ] assert crl.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ x509.NameAttribute(x509.OID_COMMON_NAME, "Good CA") ] def test_equality(self, backend): crl1 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, ) crl2 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, ) crl3 = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) assert crl1 == crl2 assert crl1 != crl3 assert crl1 != object() def test_comparison(self, backend): crl1 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, ) with pytest.raises(TypeError): crl1 < crl1 # type: ignore[operator] def test_update_dates(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) with pytest.warns(utils.DeprecatedIn42): assert isinstance(crl.next_update, datetime.datetime) assert isinstance(crl.last_update, datetime.datetime) assert crl.next_update.isoformat() == "2016-01-01T00:00:00" assert crl.last_update.isoformat() == "2015-01-01T00:00:00" assert isinstance(crl.next_update_utc, datetime.datetime) assert isinstance(crl.last_update_utc, datetime.datetime) assert crl.next_update_utc.isoformat() == "2016-01-01T00:00:00+00:00" assert crl.last_update_utc.isoformat() == "2015-01-01T00:00:00+00:00" def test_no_next_update(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_no_next_update.pem"), x509.load_pem_x509_crl, ) with pytest.warns(utils.DeprecatedIn42): assert crl.next_update is None assert crl.next_update_utc is None def test_unrecognized_extension(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_unrecognized_extension.der"), x509.load_der_x509_crl, ) unrecognized = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4.5"), b"abcdef", ) ext = crl.extensions.get_extension_for_oid(unrecognized.oid) assert ext.value == unrecognized def test_revoked_cert_retrieval(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) for r in crl: assert isinstance(r, x509.RevokedCertificate) # Check that len() works for CRLs. assert len(crl) == 12 it = iter(crl) assert len(typing.cast(typing.Sized, it)) == 12 next(it) assert len(typing.cast(typing.Sized, it)) == 11 def test_get_revoked_certificate_by_serial_number(self, backend): crl = _load_cert( os.path.join( "x509", "PKITS_data", "crls", "LongSerialNumberCACRL.crl" ), x509.load_der_x509_crl, ) serial_number = 725064303890588110203033396814564464046290047507 revoked = crl.get_revoked_certificate_by_serial_number(serial_number) assert isinstance(revoked, x509.RevokedCertificate) assert revoked.serial_number == serial_number assert crl.get_revoked_certificate_by_serial_number(500) is None def test_revoked_cert_retrieval_retain_only_revoked(self, backend): """ This test attempts to trigger the crash condition described in https://github.com/pyca/cryptography/issues/2557 PyPy does gc at its own pace, so it will only be reliable on CPython. """ revoked = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, )[11] with pytest.warns(utils.DeprecatedIn42): assert revoked.revocation_date == datetime.datetime( 2015, 1, 1, 0, 0 ) assert revoked.revocation_date_utc == datetime.datetime( 2015, 1, 1, 0, 0, tzinfo=datetime.timezone.utc ) assert revoked.serial_number == 11 def test_extensions(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_ian_aia_aki.pem"), x509.load_pem_x509_crl, ) crl_number = crl.extensions.get_extension_for_oid( ExtensionOID.CRL_NUMBER ) aki = crl.extensions.get_extension_for_class( x509.AuthorityKeyIdentifier ) aia = crl.extensions.get_extension_for_class( x509.AuthorityInformationAccess ) ian = crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ) assert crl_number.value == x509.CRLNumber(1) assert crl_number.critical is False assert aki.value == x509.AuthorityKeyIdentifier( key_identifier=(b"yu\xbb\x84:\xcb,\xdez\t\xbe1\x1bC\xbc\x1c*MSX"), authority_cert_issuer=None, authority_cert_serial_number=None, ) assert aia.value == x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.DNSName("cryptography.io"), ) ] ) assert ian.value == x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")] ) def test_delta_crl_indicator(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_delta_crl_indicator.pem"), x509.load_pem_x509_crl, ) dci = crl.extensions.get_extension_for_oid( ExtensionOID.DELTA_CRL_INDICATOR ) assert dci.value == x509.DeltaCRLIndicator(12345678901234567890) assert dci.critical is True def test_signature(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) assert crl.signature == binascii.unhexlify( b"536a5a0794f68267361e7bc2f19167a3e667a2ab141535616855d8deb2ba1af" b"9fd4546b1fe76b454eb436af7b28229fedff4634dfc9dd92254266219ae0ea8" b"75d9ff972e9a2da23d5945f073da18c50a4265bfed9ca16586347800ef49dd1" b"6856d7265f4f3c498a57f04dc04404e2bd2e2ada1f5697057aacef779a18371" b"c621edc9a5c2b8ec1716e8fa22feeb7fcec0ce9156c8d344aa6ae8d1a5d99d0" b"9386df36307df3b63c83908f4a61a0ff604c1e292ad63b349d1082ddd7ae1b7" b"c178bba995523ec6999310c54da5706549797bfb1230f5593ba7b4353dade4f" b"d2be13a57580a6eb20b5c4083f000abac3bf32cd8b75f23e4c8f4b3a79e1e2d" b"58a472b0" ) def test_tbs_certlist_bytes(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, ) ca_cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) public_key = ca_cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert crl.signature_hash_algorithm is not None public_key.verify( crl.signature, crl.tbs_certlist_bytes, padding.PKCS1v15(), crl.signature_hash_algorithm, ) def test_public_bytes_pem(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, ) # Encode it to PEM and load it back. crl = x509.load_pem_x509_crl( crl.public_bytes( encoding=serialization.Encoding.PEM, ), ) assert len(crl) == 0 _check_crl_times( crl, last_update=datetime.datetime(2015, 12, 20, 23, 44, 47), next_update=datetime.datetime(2015, 12, 28, 0, 44, 47), ) def test_public_bytes_der(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) # Encode it to DER and load it back. crl = x509.load_der_x509_crl( crl.public_bytes( encoding=serialization.Encoding.DER, ), ) assert len(crl) == 12 _check_crl_times( crl, last_update=datetime.datetime(2015, 1, 1, 0, 0, 0), next_update=datetime.datetime(2016, 1, 1, 0, 0, 0), ) @pytest.mark.parametrize( ("cert_path", "loader_func", "encoding"), [ ( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, serialization.Encoding.PEM, ), ( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, serialization.Encoding.DER, ), ], ) def test_public_bytes_match( self, cert_path, loader_func, encoding, backend ): crl_bytes = load_vectors_from_file( cert_path, lambda pemfile: pemfile.read(), mode="rb" ) crl = loader_func(crl_bytes, backend) serialized = crl.public_bytes(encoding) assert serialized == crl_bytes def test_public_bytes_invalid_encoding(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, ) with pytest.raises(TypeError): crl.public_bytes("NotAnEncoding") # type: ignore[arg-type] def test_verify_bad(self, backend): crl = _load_cert( os.path.join("x509", "custom", "invalid_signature_crl.pem"), x509.load_pem_x509_crl, ) crt = _load_cert( os.path.join("x509", "custom", "invalid_signature_cert.pem"), x509.load_pem_x509_certificate, ) public_key = crt.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert not crl.is_signature_valid(public_key) crl = _load_cert( os.path.join("x509", "custom", "crl_inner_outer_mismatch.der"), x509.load_der_x509_crl, ) assert not crl.is_signature_valid(public_key) def test_verify_good(self, backend): crl = _load_cert( os.path.join("x509", "custom", "valid_signature_crl.pem"), x509.load_pem_x509_crl, ) crt = _load_cert( os.path.join("x509", "custom", "valid_signature_cert.pem"), x509.load_pem_x509_certificate, ) public_key = crt.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert crl.is_signature_valid(public_key) def test_verify_argument_must_be_a_public_key(self, backend): crl = _load_cert( os.path.join("x509", "custom", "valid_signature_crl.pem"), x509.load_pem_x509_crl, ) with pytest.raises(TypeError): crl.is_signature_valid( "not a public key" # type: ignore[arg-type] ) with pytest.raises(TypeError): crl.is_signature_valid(object) # type: ignore[arg-type] class TestRevokedCertificate: def test_revoked_basics(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) for i, rev in enumerate(crl): assert isinstance(rev, x509.RevokedCertificate) assert isinstance(rev.serial_number, int) with pytest.warns(utils.DeprecatedIn42): assert isinstance(rev.revocation_date, datetime.datetime) assert isinstance(rev.revocation_date_utc, datetime.datetime) assert isinstance(rev.extensions, x509.Extensions) assert rev.serial_number == i with pytest.warns(utils.DeprecatedIn42): assert rev.revocation_date.isoformat() == "2015-01-01T00:00:00" assert ( rev.revocation_date_utc.isoformat() == "2015-01-01T00:00:00+00:00" ) def test_revoked_extensions(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) exp_issuer = [ x509.DirectoryName( x509.Name( [ x509.NameAttribute(x509.OID_COUNTRY_NAME, "US"), x509.NameAttribute( x509.OID_COMMON_NAME, "cryptography.io" ), ] ) ) ] # First revoked cert doesn't have extensions, test if it is handled # correctly. rev0 = crl[0] # It should return an empty Extensions object. assert isinstance(rev0.extensions, x509.Extensions) assert len(rev0.extensions) == 0 with pytest.raises(x509.ExtensionNotFound): rev0.extensions.get_extension_for_oid(x509.OID_CRL_REASON) with pytest.raises(x509.ExtensionNotFound): rev0.extensions.get_extension_for_oid(x509.OID_CERTIFICATE_ISSUER) with pytest.raises(x509.ExtensionNotFound): rev0.extensions.get_extension_for_oid(x509.OID_INVALIDITY_DATE) # Test manual retrieval of extension values. rev1 = crl[1] assert isinstance(rev1.extensions, x509.Extensions) reason = rev1.extensions.get_extension_for_class(x509.CRLReason).value assert reason == x509.CRLReason(x509.ReasonFlags.unspecified) issuer = rev1.extensions.get_extension_for_class( x509.CertificateIssuer ).value assert issuer == x509.CertificateIssuer(exp_issuer) date = rev1.extensions.get_extension_for_class( x509.InvalidityDate ).value assert date == x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)) # Check if all reason flags can be found in the CRL. flags = set(x509.ReasonFlags) for rev in crl: try: r = rev.extensions.get_extension_for_class(x509.CRLReason) except x509.ExtensionNotFound: # Not all revoked certs have a reason extension. pass else: flags.discard(r.value.reason) assert len(flags) == 0 def test_no_revoked_certs(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, ) assert len(crl) == 0 def test_duplicate_entry_ext(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), x509.load_pem_x509_crl, ) with pytest.raises(x509.DuplicateExtension): crl[0].extensions def test_unsupported_crit_entry_ext(self, backend): crl = _load_cert( os.path.join( "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" ), x509.load_pem_x509_crl, ) ext = crl[0].extensions.get_extension_for_oid( x509.ObjectIdentifier("1.2.3.4") ) assert isinstance(ext.value, x509.UnrecognizedExtension) assert ext.value.value == b"\n\x01\x00" def test_unsupported_reason(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_unsupported_reason.pem"), x509.load_pem_x509_crl, ) with pytest.raises(ValueError): crl[0].extensions def test_invalid_cert_issuer_ext(self, backend): crl = _load_cert( os.path.join( "x509", "custom", "crl_inval_cert_issuer_entry_ext.pem" ), x509.load_pem_x509_crl, ) with pytest.raises(ValueError): crl[0].extensions def test_indexing(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, ) with pytest.raises(IndexError): crl[-13] with pytest.raises(IndexError): crl[12] assert crl[-1].serial_number == crl[11].serial_number assert len(crl[2:4]) == 2 assert crl[2:4][0].serial_number == crl[2].serial_number assert crl[2:4][1].serial_number == crl[3].serial_number def test_get_revoked_certificate_doesnt_reorder( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) for i in [2, 500, 3, 49, 7, 1]: revoked_cert = ( x509.RevokedCertificateBuilder() .serial_number(i) .revocation_date(datetime.datetime(2012, 1, 1, 1, 1)) .build(backend) ) builder = builder.add_revoked_certificate(revoked_cert) crl = builder.sign(private_key, hashes.SHA256(), backend) assert crl[0].serial_number == 2 assert crl[2].serial_number == 3 # make sure get_revoked_certificate_by_serial_number doesn't affect # ordering after being invoked crl.get_revoked_certificate_by_serial_number(500) assert crl[0].serial_number == 2 assert crl[2].serial_number == 3 class TestRSAECertificate: def test_load_cert_pub_key(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ca", "rsae_ca.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) expected_pub_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ).public_key() assert isinstance(expected_pub_key, rsa.RSAPublicKey) pub_key = cert.public_key() assert isinstance(pub_key, rsa.RSAPublicKey) assert ( cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 ) assert pub_key == expected_pub_key pss = cert.signature_algorithm_parameters assert isinstance(pss, padding.PSS) assert isinstance(pss._mgf, padding.MGF1) assert isinstance(pss._mgf._algorithm, hashes.SHA256) assert pss._salt_length == 0x14 assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) pub_key.verify( cert.signature, cert.tbs_certificate_bytes, pss, cert.signature_hash_algorithm, ) class TestRSAPSSCertificate: def test_load_cert_pub_key(self, backend): cert = _load_cert( os.path.join("x509", "custom", "rsa_pss_cert.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) expected_pub_key = _load_cert( os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_pub.der"), serialization.load_der_public_key, ) assert isinstance(expected_pub_key, rsa.RSAPublicKey) pub_key = cert.public_key() assert isinstance(pub_key, rsa.RSAPublicKey) assert ( cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.RSASSA_PSS ) assert pub_key == expected_pub_key pss = cert.signature_algorithm_parameters assert isinstance(pss, padding.PSS) assert isinstance(pss._mgf, padding.MGF1) assert isinstance(pss._mgf._algorithm, hashes.SHA256) assert pss._salt_length == 222 assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) pub_key.verify( cert.signature, cert.tbs_certificate_bytes, pss, cert.signature_hash_algorithm, ) def test_load_pss_cert_no_null(self, backend): """ This test verifies that PSS certs where the hash algorithm identifiers have no trailing null still load properly. LibreSSL generates certs like this. """ cert = _load_cert( os.path.join("x509", "custom", "rsa_pss_sha256_no_null.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) pss = cert.signature_algorithm_parameters assert isinstance(pss, padding.PSS) assert isinstance(pss._mgf, padding.MGF1) assert isinstance(pss._mgf._algorithm, hashes.SHA256) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) def test_load_pss_sha1_mgf1_sha1(self, backend): cert = _load_cert( os.path.join("x509", "ee-pss-sha1-cert.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) pub_key = cert.public_key() assert isinstance(pub_key, rsa.RSAPublicKey) pss = cert.signature_algorithm_parameters assert isinstance(pss, padding.PSS) assert isinstance(pss._mgf, padding.MGF1) assert isinstance(pss._mgf._algorithm, hashes.SHA1) assert pss._salt_length == 20 assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) def test_invalid_mgf(self, backend): cert = _load_cert( os.path.join("x509", "custom", "rsa_pss_cert_invalid_mgf.der"), x509.load_der_x509_certificate, ) with pytest.raises(ValueError): cert.signature_algorithm_parameters def test_unsupported_mgf_hash(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "rsa_pss_cert_unsupported_mgf_hash.der" ), x509.load_der_x509_certificate, ) with pytest.raises(UnsupportedAlgorithm): cert.signature_algorithm_parameters def test_no_sig_params(self, backend): cert = _load_cert( os.path.join("x509", "custom", "rsa_pss_cert_no_sig_params.der"), x509.load_der_x509_certificate, ) with pytest.raises(ValueError): cert.signature_algorithm_parameters with pytest.raises(ValueError): cert.signature_hash_algorithm class TestRSACertificate: def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 11559813051657483483 fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8" assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) assert ( cert.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1 ) assert isinstance( cert.signature_algorithm_parameters, padding.PKCS1v15 ) assert isinstance(cert.public_key(), rsa.RSAPublicKey) assert ( cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 ) def test_check_pkcs1_signature_algorithm_parameters(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ca", "rsa_ca.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) assert isinstance( cert.signature_algorithm_parameters, padding.PKCS1v15 ) pk = cert.public_key() assert isinstance(pk, rsa.RSAPublicKey) assert cert.signature_hash_algorithm is not None pk.verify( cert.signature, cert.tbs_certificate_bytes, cert.signature_algorithm_parameters, cert.signature_hash_algorithm, ) def test_load_legacy_pem_header(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.old_header.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) def test_load_with_other_sections(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.with_garbage.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) cert = _load_cert( os.path.join("x509", "cryptography.io.with_headers.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert, x509.Certificate) def test_load_multiple_sections(self, backend): # We match OpenSSL's behavior of loading the first cert # if there are multiple. Arguably this would ideally be an # error, but "load the first" is a common expectation. cert = _load_cert( os.path.join("x509", "cryptography.io.chain.pem"), x509.load_pem_x509_certificate, ) cert2 = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) assert cert == cert2 def test_negative_serial_number(self, backend): # We load certificates with negative serial numbers but on load # and on access of the attribute we raise a warning with pytest.warns(utils.DeprecatedIn36): cert = _load_cert( os.path.join("x509", "custom", "negative_serial.pem"), x509.load_pem_x509_certificate, ) with pytest.warns(utils.DeprecatedIn36): assert cert.serial_number == -18008675309 def test_country_jurisdiction_country_too_long(self, backend): cert = _load_cert( os.path.join("x509", "custom", "bad_country.pem"), x509.load_pem_x509_certificate, ) with pytest.warns(UserWarning): assert ( cert.subject.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME)[ 0 ].value == "too long" ) with pytest.warns(UserWarning): assert ( cert.subject.get_attributes_for_oid( x509.NameOID.JURISDICTION_COUNTRY_NAME )[0].value == "also too long" ) def test_alternate_rsa_with_sha1_oid(self, backend): cert = _load_cert( os.path.join("x509", "custom", "alternate-rsa-sha1-oid.der"), x509.load_der_x509_certificate, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) assert ( cert.signature_algorithm_oid == SignatureAlgorithmOID._RSA_WITH_SHA1 ) assert isinstance(cert.public_key(), rsa.RSAPublicKey) assert ( cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 ) def test_load_bmpstring_explicittext(self, backend): cert = _load_cert( os.path.join("x509", "accvraiz1.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.CertificatePolicies) et = ext.value[0].policy_qualifiers[0].explicit_text assert et == ( "Autoridad de Certificación Raíz de la ACCV (Agencia " "de Tecnología y Certificación Electrónica, CIF Q4601" "156E). CPS en http://www.accv.es" ) def test_load_der_cert(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 2 fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) assert cert.signature == binascii.unhexlify( b"8e0f72fcbebe4755abcaf76c8ce0bae17cde4db16291638e1b1ce04a93cdb4c" b"44a3486070986c5a880c14fdf8497e7d289b2630ccb21d24a3d1aa1b2d87482" b"07f3a1e16ccdf8daa8a7ea1a33d49774f513edf09270bd8e665b6300a10f003" b"66a59076905eb63cf10a81a0ca78a6ef3127f6cb2f6fb7f947fce22a30d8004" b"8c243ba2c1a54c425fe12310e8a737638f4920354d4cce25cbd9dea25e6a2fe" b"0d8579a5c8d929b9275be221975479f3f75075bcacf09526523b5fd67f7683f" b"3cda420fabb1e9e6fc26bc0649cf61bb051d6932fac37066bb16f55903dfe78" b"53dc5e505e2a10fbba4f9e93a0d3b53b7fa34b05d7ba6eef869bfc34b8e514f" b"d5419f75" ) public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert len(cert.signature) == public_key.key_size // 8 @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported( hashes.SHA1() ), skip_message="Does not support SHA-1 signature.", ) def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"308202d8a003020102020900a06cb4b955f7f4db300d06092a864886f70d010" b"10505003058310b3009060355040613024155311330110603550408130a536f" b"6d652d53746174653121301f060355040a1318496e7465726e6574205769646" b"769747320507479204c74643111300f0603550403130848656c6c6f20434130" b"1e170d3134313132363231343132305a170d3134313232363231343132305a3" b"058310b3009060355040613024155311330110603550408130a536f6d652d53" b"746174653121301f060355040a1318496e7465726e657420576964676974732" b"0507479204c74643111300f0603550403130848656c6c6f2043413082012230" b"0d06092a864886f70d01010105000382010f003082010a0282010100b03af70" b"2059e27f1e2284b56bbb26c039153bf81f295b73a49132990645ede4d2da0a9" b"13c42e7d38d3589a00d3940d194f6e6d877c2ef812da22a275e83d8be786467" b"48b4e7f23d10e873fd72f57a13dec732fc56ab138b1bb308399bb412cd73921" b"4ef714e1976e09603405e2556299a05522510ac4574db5e9cb2cf5f99e8f48c" b"1696ab3ea2d6d2ddab7d4e1b317188b76a572977f6ece0a4ad396f0150e7d8b" b"1a9986c0cb90527ec26ca56e2914c270d2a198b632fa8a2fda55079d3d39864" b"b6fb96ddbe331cacb3cb8783a8494ccccd886a3525078847ca01ca5f803e892" b"14403e8a4b5499539c0b86f7a0daa45b204a8e079d8a5b03db7ba1ba3d7011a" b"70203010001a381bc3081b9301d0603551d0e04160414d8e89dc777e4472656" b"f1864695a9f66b7b0400ae3081890603551d23048181307f8014d8e89dc777e" b"4472656f1864695a9f66b7b0400aea15ca45a3058310b300906035504061302" b"4155311330110603550408130a536f6d652d53746174653121301f060355040" b"a1318496e7465726e6574205769646769747320507479204c74643111300f06" b"03550403130848656c6c6f204341820900a06cb4b955f7f4db300c0603551d1" b"3040530030101ff" ) public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert cert.signature_hash_algorithm is not None public_key.verify( cert.signature, cert.tbs_certificate_bytes, padding.PKCS1v15(), cert.signature_hash_algorithm, ) def test_tbs_precertificate_bytes_duplicate_extensions_raises( self, backend ): cert = _load_cert( os.path.join("x509", "custom", "two_basic_constraints.pem"), x509.load_pem_x509_certificate, ) with pytest.raises( x509.DuplicateExtension, match="Duplicate 2.5.29.19 extension found", ): cert.tbs_precertificate_bytes def test_tbs_precertificate_bytes_no_extensions_raises(self, backend): cert = _load_cert( os.path.join("x509", "v1_cert.pem"), x509.load_pem_x509_certificate, ) with pytest.raises( ValueError, match="Could not find pre-certificate SCT list extension", ): cert.tbs_precertificate_bytes def test_tbs_precertificate_bytes_missing_extension_raises(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) # This cert doesn't have an SCT list extension, so it will throw a # `ValueError` when we try to retrieve the property with pytest.raises( ValueError, match="Could not find pre-certificate SCT list extension", ): cert.tbs_precertificate_bytes def test_tbs_precertificate_bytes_strips_scts(self, backend): cert = _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, ) expected_tbs_precertificate_bytes = load_vectors_from_file( filename=os.path.join("x509", "cryptography-scts-tbs-precert.der"), loader=lambda data: data.read(), mode="rb", ) assert ( expected_tbs_precertificate_bytes == cert.tbs_precertificate_bytes ) assert cert.tbs_precertificate_bytes != cert.tbs_certificate_bytes def test_issuer(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, ) issuer = cert.issuer assert isinstance(issuer, x509.Name) assert list(issuer) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Test Certificates 2011" ), x509.NameAttribute(NameOID.COMMON_NAME, "Good CA"), ] assert issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ x509.NameAttribute(NameOID.COMMON_NAME, "Good CA") ] def test_all_issuer_name_types(self, backend): cert = _load_cert( os.path.join("x509", "custom", "all_supported_names.pem"), x509.load_pem_x509_certificate, ) issuer = cert.issuer assert isinstance(issuer, x509.Name) assert list(issuer) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.COUNTRY_NAME, "CA"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Illinois"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Chicago"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Zero, LLC"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "One, LLC"), x509.NameAttribute(NameOID.COMMON_NAME, "common name 0"), x509.NameAttribute(NameOID.COMMON_NAME, "common name 1"), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "OU 0"), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "OU 1"), x509.NameAttribute(NameOID.DN_QUALIFIER, "dnQualifier0"), x509.NameAttribute(NameOID.DN_QUALIFIER, "dnQualifier1"), x509.NameAttribute(NameOID.SERIAL_NUMBER, "123"), x509.NameAttribute(NameOID.SERIAL_NUMBER, "456"), x509.NameAttribute(NameOID.TITLE, "Title 0"), x509.NameAttribute(NameOID.TITLE, "Title 1"), x509.NameAttribute(NameOID.SURNAME, "Surname 0"), x509.NameAttribute(NameOID.SURNAME, "Surname 1"), x509.NameAttribute(NameOID.GIVEN_NAME, "Given Name 0"), x509.NameAttribute(NameOID.GIVEN_NAME, "Given Name 1"), x509.NameAttribute(NameOID.PSEUDONYM, "Incognito 0"), x509.NameAttribute(NameOID.PSEUDONYM, "Incognito 1"), x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "Last Gen"), x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "Next Gen"), x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc0"), x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc1"), x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test0@test.local"), x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test1@test.local"), ] def test_subject(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, ) subject = cert.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Test Certificates 2011" ), x509.NameAttribute( NameOID.COMMON_NAME, "Valid pre2000 UTC notBefore Date EE Certificate Test3", ), ] assert subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ x509.NameAttribute( NameOID.COMMON_NAME, "Valid pre2000 UTC notBefore Date EE Certificate Test3", ) ] def test_unicode_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "utf8_common_name.pem"), x509.load_pem_x509_certificate, ) assert cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ x509.NameAttribute(NameOID.COMMON_NAME, "We heart UTF8!\u2122") ] assert cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ x509.NameAttribute(NameOID.COMMON_NAME, "We heart UTF8!\u2122") ] def test_invalid_unicode_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "invalid_utf8_common_name.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(ValueError, match="subject"): cert.subject with pytest.raises(ValueError, match="issuer"): cert.issuer def test_non_ascii_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "utf8-dnsname.pem"), x509.load_pem_x509_certificate, ) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ).value names = san.get_values_for_type(x509.DNSName) assert names == [ "partner.biztositas.hu", "biztositas.hu", "*.biztositas.hu", "biztos\xedt\xe1s.hu", "*.biztos\xedt\xe1s.hu", "xn--biztosts-fza2j.hu", "*.xn--biztosts-fza2j.hu", ] def test_all_subject_name_types(self, backend): cert = _load_cert( os.path.join("x509", "custom", "all_supported_names.pem"), x509.load_pem_x509_certificate, ) subject = cert.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "AU"), x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "New York"), x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Ithaca"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Org Zero, LLC"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Org One, LLC"), x509.NameAttribute(NameOID.COMMON_NAME, "CN 0"), x509.NameAttribute(NameOID.COMMON_NAME, "CN 1"), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "Engineering 0" ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "Engineering 1" ), x509.NameAttribute(NameOID.DN_QUALIFIER, "qualified0"), x509.NameAttribute(NameOID.DN_QUALIFIER, "qualified1"), x509.NameAttribute(NameOID.SERIAL_NUMBER, "789"), x509.NameAttribute(NameOID.SERIAL_NUMBER, "012"), x509.NameAttribute(NameOID.TITLE, "Title IX"), x509.NameAttribute(NameOID.TITLE, "Title X"), x509.NameAttribute(NameOID.SURNAME, "Last 0"), x509.NameAttribute(NameOID.SURNAME, "Last 1"), x509.NameAttribute(NameOID.GIVEN_NAME, "First 0"), x509.NameAttribute(NameOID.GIVEN_NAME, "First 1"), x509.NameAttribute(NameOID.PSEUDONYM, "Guy Incognito 0"), x509.NameAttribute(NameOID.PSEUDONYM, "Guy Incognito 1"), x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "32X"), x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "Dreamcast"), x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc2"), x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "dc3"), x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test2@test.local"), x509.NameAttribute(NameOID.EMAIL_ADDRESS, "test3@test.local"), ] def test_load_good_ca_cert(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) _check_cert_times( cert, not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), ) assert cert.serial_number == 2 public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert cert.version is x509.Version.v3 fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" def test_utc_pre_2000_not_before_cert(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, ) _check_cert_times( cert, not_valid_before=datetime.datetime(1950, 1, 1, 12, 1), not_valid_after=None, ) def test_pre_2000_utc_not_after_cert(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "Invalidpre2000UTCEEnotAfterDateTest7EE.crt", ), x509.load_der_x509_certificate, ) _check_cert_times( cert, not_valid_before=None, not_valid_after=datetime.datetime(1999, 1, 1, 12, 1), ) def test_post_2000_utc_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) _check_cert_times( cert, not_valid_before=datetime.datetime(2014, 11, 26, 21, 41, 20), not_valid_after=datetime.datetime(2014, 12, 26, 21, 41, 20), ) def test_generalized_time_not_before_cert(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidGeneralizedTimenotBeforeDateTest4EE.crt", ), x509.load_der_x509_certificate, ) _check_cert_times( cert, not_valid_before=datetime.datetime(2002, 1, 1, 12, 1), not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), ) assert cert.version is x509.Version.v3 def test_generalized_time_not_after_cert(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, ) _check_cert_times( cert, not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), not_valid_after=datetime.datetime(2050, 1, 1, 12, 1), ) assert cert.version is x509.Version.v3 def test_invalid_version_cert(self, backend): with pytest.raises(x509.InvalidVersion) as exc: _load_cert( os.path.join("x509", "custom", "invalid_version.pem"), x509.load_pem_x509_certificate, ) assert exc.value.parsed_version == 7 def test_invalid_visiblestring_in_explicit_text(self, backend): cert = _load_cert( os.path.join( "x509", "belgian-eid-invalid-visiblestring.pem", ), x509.load_pem_x509_certificate, ) with pytest.warns(utils.DeprecatedIn41): cp = cert.extensions.get_extension_for_class( x509.CertificatePolicies ).value assert isinstance(cp, x509.CertificatePolicies) assert cp[0].policy_qualifiers[1].explicit_text == ( "Gebruik onderworpen aan aansprakelijkheidsbeperkingen, zie CPS " "- Usage soumis à des limitations de responsabilité, voir CPS - " "Verwendung unterliegt Haftungsbeschränkungen, gemäss CPS" ) def test_eq(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) assert cert == cert2 def test_ne(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) cert2 = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, ) assert cert != cert2 assert cert != object() def test_ordering_unsupported(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(TypeError, match="'>' not supported"): cert > cert2 # type: ignore[operator] def test_hash(self, backend): cert1 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, ) cert3 = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, ) assert hash(cert1) == hash(cert2) assert hash(cert1) != hash(cert3) def test_version_1_cert(self, backend): cert = _load_cert( os.path.join("x509", "v1_cert.pem"), x509.load_pem_x509_certificate, ) assert cert.version is x509.Version.v1 def test_invalid_pem(self, backend): with pytest.raises(ValueError, match="Unable to load"): x509.load_pem_x509_certificate(b"notacert", backend) crl = load_vectors_from_file( filename=os.path.join("x509", "custom", "crl_empty.pem"), loader=lambda pemfile: pemfile.read(), mode="rb", ) with pytest.raises(ValueError, match="Valid PEM but no"): x509.load_pem_x509_certificate(crl, backend) def test_invalid_der(self, backend): with pytest.raises(ValueError): x509.load_der_x509_certificate(b"notacert", backend) def test_unsupported_signature_hash_algorithm_cert(self, backend): cert = _load_cert( os.path.join("x509", "verisign_md2_root.pem"), x509.load_pem_x509_certificate, ) with raises_unsupported_algorithm(None): cert.signature_hash_algorithm def test_public_bytes_pem(self, backend): # Load an existing certificate. cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) # Encode it to PEM and load it back. cert = x509.load_pem_x509_certificate( cert.public_bytes( encoding=serialization.Encoding.PEM, ), backend, ) # We should recover what we had to start with. _check_cert_times( cert, not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), ) assert cert.serial_number == 2 public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert cert.version is x509.Version.v3 fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" def test_public_bytes_der(self, backend): # Load an existing certificate. cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) # Encode it to DER and load it back. cert = x509.load_der_x509_certificate( cert.public_bytes( encoding=serialization.Encoding.DER, ), ) # We should recover what we had to start with. _check_cert_times( cert, not_valid_before=datetime.datetime(2010, 1, 1, 8, 30), not_valid_after=datetime.datetime(2030, 12, 31, 8, 30), ) assert cert.serial_number == 2 public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert cert.version is x509.Version.v3 fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" def test_public_bytes_invalid_encoding(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) with pytest.raises(TypeError): cert.public_bytes("NotAnEncoding") # type: ignore[arg-type] @pytest.mark.parametrize( ("cert_path", "loader_func", "encoding"), [ ( os.path.join("x509", "v1_cert.pem"), x509.load_pem_x509_certificate, serialization.Encoding.PEM, ), ( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, serialization.Encoding.DER, ), ], ) def test_public_bytes_match( self, cert_path, loader_func, encoding, backend ): cert_bytes = load_vectors_from_file( cert_path, lambda pemfile: pemfile.read(), mode="rb" ) cert = loader_func(cert_bytes) serialized = cert.public_bytes(encoding) assert serialized == cert_bytes def test_certificate_repr(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) assert repr(cert) == ( ", ...)>" ) def test_parse_tls_feature_extension(self, backend): cert = _load_cert( os.path.join("x509", "tls-feature-ocsp-staple.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.TLSFeature) assert ext.critical is False assert ext.value == x509.TLSFeature( [x509.TLSFeatureType.status_request] ) def test_verify_directly_issued_by_rsa_pss( self, rsa_key_2048: rsa.RSAPrivateKey ): subject_private_key = RSA_KEY_2048_ALT.private_key( unsafe_skip_rsa_key_validation=True ) builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .public_key(rsa_key_2048.public_key()) .serial_number(1) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2030, 1, 1)) ) ca = builder.sign(rsa_key_2048, hashes.SHA256()) builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "leaf")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .public_key(subject_private_key.public_key()) .serial_number(100) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2025, 1, 1)) ) cert = builder.sign( rsa_key_2048, hashes.SHA256(), rsa_padding=padding.PSS( padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH, ), ) cert.verify_directly_issued_by(ca) def test_verify_directly_issued_by_rsa( self, rsa_key_2048: rsa.RSAPrivateKey ): issuer_private_key = rsa_key_2048 subject_private_key = RSA_KEY_2048_ALT.private_key( unsafe_skip_rsa_key_validation=True ) ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert.verify_directly_issued_by(ca) def test_verify_directly_issued_by_rsa_bad_sig( self, rsa_key_2048: rsa.RSAPrivateKey ): issuer_private_key = rsa_key_2048 subject_private_key = RSA_KEY_2048_ALT.private_key( unsafe_skip_rsa_key_validation=True ) ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert_bad_sig = _break_cert_sig(cert) with pytest.raises(InvalidSignature): cert_bad_sig.verify_directly_issued_by(ca) def test_verify_directly_issued_by_rsa_mismatched_inner_out_oid(self): cert = _load_cert( os.path.join( "x509", "custom", "mismatch_inner_outer_sig_algorithm.der" ), x509.load_der_x509_certificate, ) with pytest.raises(ValueError) as exc: cert.verify_directly_issued_by(cert) assert str(exc.value) == ( "Inner and outer signature algorithms do not match. This is an " "invalid certificate." ) def test_verify_directly_issued_by_subject_issuer_mismatch(self): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(ValueError) as exc: cert.verify_directly_issued_by(cert) assert str(exc.value) == ( "Issuer certificate subject does not match certificate issuer." ) def test_verify_directly_issued_by_algorithm_mismatch( self, rsa_key_2048: rsa.RSAPrivateKey ): issuer_private_key = rsa_key_2048 subject_private_key = RSA_KEY_2048_ALT.private_key( unsafe_skip_rsa_key_validation=True ) _, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) # We need a CA with the same issuer DN but diff signature algorithm secondary_issuer_key = ec.generate_private_key(ec.SECP256R1()) ca2, _ = _generate_ca_and_leaf( secondary_issuer_key, subject_private_key ) with pytest.raises(ValueError): cert.verify_directly_issued_by(ca2) @pytest.mark.supported( only_if=lambda backend: ( backend.ed25519_supported() and backend.x25519_supported() ), skip_message="Requires OpenSSL with Ed25519 and X25519 support", ) def test_verify_directly_issued_by_unsupported_key_type(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() x25519_public = x25519.X25519PrivateKey.generate().public_key() # Generate an ed25519 CA builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .public_key(private_key.public_key()) .serial_number(1) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2030, 1, 1)) ) cert = builder.sign(private_key, None) # Make a cert with the right issuer name but the wrong public key builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")]) ) .public_key(x25519_public) .serial_number(1) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2030, 1, 1)) ) leaf = builder.sign(private_key, None) with pytest.raises(TypeError): cert.verify_directly_issued_by(leaf) class TestRSACertificateRequest: @pytest.mark.parametrize( ("path", "loader_func"), [ [ os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ], [ os.path.join("x509", "requests", "rsa_sha1.der"), x509.load_der_x509_csr, ], ], ) def test_load_rsa_certificate_request(self, path, loader_func, backend): request = _load_cert(path, loader_func) assert isinstance(request.signature_hash_algorithm, hashes.SHA1) assert ( request.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1 ) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert ( request.public_key_algorithm_oid == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 ) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), ] extensions = request.extensions assert isinstance(extensions, x509.Extensions) assert list(extensions) == [] def test_load_legacy_pem_header(self, backend): cert = _load_cert( os.path.join("x509", "requests", "ec_sha256_old_header.pem"), x509.load_pem_x509_csr, ) assert isinstance(cert, x509.CertificateSigningRequest) def test_invalid_pem(self, backend): with pytest.raises(ValueError, match="Unable to load"): x509.load_pem_x509_csr(b"notacsr") crl = load_vectors_from_file( filename=os.path.join("x509", "custom", "crl_empty.pem"), loader=lambda pemfile: pemfile.read(), mode="rb", ) with pytest.raises(ValueError, match="Valid PEM but no"): x509.load_pem_x509_csr(crl) @pytest.mark.parametrize( "loader_func", [x509.load_pem_x509_csr, x509.load_der_x509_csr] ) def test_invalid_certificate_request(self, loader_func, backend): with pytest.raises(ValueError): loader_func(b"notacsr") def test_unsupported_signature_hash_algorithm_request(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_md4.pem"), x509.load_pem_x509_csr, ) with raises_unsupported_algorithm(None): request.signature_hash_algorithm def test_invalid_version(self, backend): with pytest.raises(x509.InvalidVersion): _load_cert( os.path.join("x509", "requests", "bad-version.pem"), x509.load_pem_x509_csr, ) def test_duplicate_extension(self, backend): request = _load_cert( os.path.join("x509", "requests", "two_basic_constraints.pem"), x509.load_pem_x509_csr, ) with pytest.raises(x509.DuplicateExtension) as exc: request.extensions assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS def test_unsupported_critical_extension(self, backend): request = _load_cert( os.path.join( "x509", "requests", "unsupported_extension_critical.pem" ), x509.load_pem_x509_csr, ) ext = request.extensions.get_extension_for_oid( x509.ObjectIdentifier("1.2.3.4") ) assert isinstance(ext.value, x509.UnrecognizedExtension) assert ext.value.value == b"value" def test_unsupported_extension(self, backend): request = _load_cert( os.path.join("x509", "requests", "unsupported_extension.pem"), x509.load_pem_x509_csr, ) extensions = request.extensions assert len(extensions) == 1 assert extensions[0].oid == x509.ObjectIdentifier("1.2.3.4") assert extensions[0].value == x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"value" ) def test_no_extension_with_other_attributes(self, backend): request = _load_cert( os.path.join("x509", "requests", "challenge-unstructured.pem"), x509.load_pem_x509_csr, ) assert len(request.extensions) == 0 def test_request_basic_constraints(self, backend): request = _load_cert( os.path.join("x509", "requests", "basic_constraints.pem"), x509.load_pem_x509_csr, ) extensions = request.extensions assert isinstance(extensions, x509.Extensions) assert list(extensions) == [ x509.Extension( ExtensionOID.BASIC_CONSTRAINTS, True, x509.BasicConstraints(ca=True, path_length=1), ), ] def test_subject_alt_name(self, backend): request = _load_cert( os.path.join("x509", "requests", "san_rsa_sha1.pem"), x509.load_pem_x509_csr, ) ext = request.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert list(ext.value) == [ x509.DNSName("cryptography.io"), x509.DNSName("sub.cryptography.io"), ] def test_freeipa_bad_critical(self, backend): csr = _load_cert( os.path.join("x509", "requests", "freeipa-bad-critical.pem"), x509.load_pem_x509_csr, ) with pytest.raises(ValueError): csr.extensions def test_public_bytes_pem(self, backend): # Load an existing CSR. request = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) # Encode it to PEM and load it back. request = x509.load_pem_x509_csr( request.public_bytes( encoding=serialization.Encoding.PEM, ), ) # We should recover what we had to start with. assert isinstance(request.signature_hash_algorithm, hashes.SHA1) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), ] def test_public_bytes_der(self, backend): # Load an existing CSR. request = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) # Encode it to DER and load it back. request = x509.load_der_x509_csr( request.public_bytes( encoding=serialization.Encoding.DER, ), ) # We should recover what we had to start with. assert isinstance(request.signature_hash_algorithm, hashes.SHA1) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), ] def test_signature(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) assert request.signature == binascii.unhexlify( b"8364c86ffbbfe0bfc9a21f831256658ca8989741b80576d36f08a934603a43b1" b"837246d00167a518abb1de7b51a1e5b7ebea14944800818b1a923c804f120a0d" b"624f6310ef79e8612755c2b01dcc7f59dfdbce0db3f2630f185f504b8c17af80" b"cbd364fa5fda68337153930948226cd4638287a0aed6524d3006885c19028a1e" b"e2f5a91d6e77dbaa0b49996ee0a0c60b55b61bd080a08bb34aa7f3e07e91f37f" b"6a11645be2d8654c1570dcda145ed7cc92017f7d53225d7f283f3459ec5bda41" b"cf6dd75d43676c543483385226b7e4fa29c8739f1b0eaf199613593991979862" b"e36181e8c4c270c354b7f52c128db1b70639823324c7ea24791b7bc3d7005f3b" ) @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported( hashes.SHA1() ), skip_message="Does not support SHA-1 signature.", ) def test_tbs_certrequest_bytes(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) assert request.tbs_certrequest_bytes == binascii.unhexlify( b"308201840201003057310b3009060355040613025553310e300c060355040813" b"055465786173310f300d0603550407130641757374696e310d300b060355040a" b"130450794341311830160603550403130f63727970746f6772617068792e696f" b"30820122300d06092a864886f70d01010105000382010f003082010a02820101" b"00a840a78460cb861066dfa3045a94ba6cf1b7ab9d24c761cffddcc2cb5e3f1d" b"c3e4be253e7039ef14fe9d6d2304f50d9f2e1584c51530ab75086f357138bff7" b"b854d067d1d5f384f1f2f2c39cc3b15415e2638554ef8402648ae3ef08336f22" b"b7ecc6d4331c2b21c3091a7f7a9518180754a646640b60419e4cc6f5c798110a" b"7f030a639fe87e33b4776dfcd993940ec776ab57a181ad8598857976dc303f9a" b"573ca619ab3fe596328e92806b828683edc17cc256b41948a2bfa8d047d2158d" b"3d8e069aa05fa85b3272abb1c4b4422b6366f3b70e642377b145cd6259e5d3e7" b"db048d51921e50766a37b1b130ee6b11f507d20a834001e8de16a92c14f2e964" b"a30203010001a000" ) assert request.signature_hash_algorithm is not None public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) public_key.verify( request.signature, request.tbs_certrequest_bytes, padding.PKCS1v15(), request.signature_hash_algorithm, ) def test_public_bytes_invalid_encoding(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) with pytest.raises(TypeError): request.public_bytes("NotAnEncoding") # type: ignore[arg-type] def test_signature_invalid(self, backend): request = _load_cert( os.path.join("x509", "requests", "invalid_signature.pem"), x509.load_pem_x509_csr, ) assert not request.is_signature_valid def test_signature_valid(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, ) assert request.is_signature_valid @pytest.mark.parametrize( ("request_path", "loader_func", "encoding"), [ ( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, serialization.Encoding.PEM, ), ( os.path.join("x509", "requests", "rsa_sha1.der"), x509.load_der_x509_csr, serialization.Encoding.DER, ), ], ) def test_public_bytes_match( self, request_path, loader_func, encoding, backend ): request_bytes = load_vectors_from_file( request_path, lambda pemfile: pemfile.read(), mode="rb" ) request = loader_func(request_bytes) serialized = request.public_bytes(encoding) assert serialized == request_bytes def test_eq(self, backend): request1 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) request2 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) assert request1 == request2 def test_ne(self, backend): request1 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) request2 = _load_cert( os.path.join("x509", "requests", "san_rsa_sha1.pem"), x509.load_pem_x509_csr, ) assert request1 != request2 assert request1 != object() def test_ordering_unsupported(self, backend): csr = _load_cert( os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, ) csr2 = _load_cert( os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, ) with pytest.raises(TypeError, match="'>' not supported"): csr > csr2 # type: ignore[operator] def test_hash(self, backend): request1 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) request2 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, ) request3 = _load_cert( os.path.join("x509", "requests", "san_rsa_sha1.pem"), x509.load_pem_x509_csr, ) assert hash(request1) == hash(request2) assert hash(request1) != hash(request3) @pytest.mark.parametrize( ("hashalg", "hashalg_oid"), [ (hashes.SHA224, x509.SignatureAlgorithmOID.RSA_WITH_SHA224), (hashes.SHA256, x509.SignatureAlgorithmOID.RSA_WITH_SHA256), (hashes.SHA384, x509.SignatureAlgorithmOID.RSA_WITH_SHA384), (hashes.SHA512, x509.SignatureAlgorithmOID.RSA_WITH_SHA512), (hashes.SHA3_224, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_224), (hashes.SHA3_256, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_256), (hashes.SHA3_384, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_384), (hashes.SHA3_512, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_512), ], ) def test_build_cert( self, rsa_key_2048: rsa.RSAPrivateKey, hashalg, hashalg_oid, backend ): if not backend.signature_hash_supported(hashalg()): pytest.skip(f"{hashalg} signature not supported") issuer_private_key = rsa_key_2048 subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name( [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io" ), ] ) ) .subject_name( x509.Name( [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io" ), ] ) ) .public_key(subject_private_key.public_key()) .add_extension( x509.BasicConstraints(ca=False, path_length=None), True, ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 public_key = cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) assert ( cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5 ) assert cert.signature_algorithm_oid == hashalg_oid assert type(cert.signature_hash_algorithm) is hashalg _check_cert_times( cert, not_valid_before=not_valid_before, not_valid_after=not_valid_after, ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) assert isinstance( subject_alternative_name.value, x509.SubjectAlternativeName ) assert list(subject_alternative_name.value) == [ x509.DNSName("cryptography.io"), ] def test_build_cert_private_type_encoding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): issuer_private_key = rsa_key_2048 subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) name = x509.Name( [ x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas", _ASN1Type.PrintableString, ), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io", _ASN1Type.IA5String, ), ] ) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name(name) .subject_name(name) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) for dn in (cert.subject, cert.issuer): assert ( dn.get_attributes_for_oid(NameOID.STATE_OR_PROVINCE_NAME)[ 0 ]._type == _ASN1Type.PrintableString ) assert ( dn.get_attributes_for_oid(NameOID.STATE_OR_PROVINCE_NAME)[ 0 ]._type == _ASN1Type.PrintableString ) assert ( dn.get_attributes_for_oid(NameOID.LOCALITY_NAME)[0]._type == _ASN1Type.UTF8String ) def test_build_cert_printable_string_country_name( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): issuer_private_key = rsa_key_2048 subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name( [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( NameOID.JURISDICTION_COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) ) .subject_name( x509.Name( [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute( NameOID.JURISDICTION_COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) parsed = test_support.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) # Check that each value was encoded as an ASN.1 PRINTABLESTRING. assert parsed.issuer_value_tags[0] == 0x13 assert parsed.subject_value_tags[0] == 0x13 assert parsed.issuer_value_tags[1] == 0x13 assert parsed.subject_value_tags[1] == 0x13 class TestCertificateBuilder: def test_checks_for_unsupported_extensions( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(1999, 1, 1)) .not_valid_after(datetime.datetime(2020, 1, 1)) .add_extension(DummyExtension(), False) ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) def test_encode_nonstandard_aia( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( x509.ObjectIdentifier("2.999.7"), x509.UniformResourceIdentifier("http://example.com"), ), ] ) builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(1999, 1, 1)) .not_valid_after(datetime.datetime(2020, 1, 1)) .add_extension(aia, False) ) builder.sign(private_key, hashes.SHA256(), backend) def test_encode_nonstandard_sia( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 sia = x509.SubjectInformationAccess( [ x509.AccessDescription( x509.ObjectIdentifier("2.999.7"), x509.UniformResourceIdentifier("http://example.com"), ), ] ) builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(2015, 1, 1)) .not_valid_after(datetime.datetime(2040, 1, 1)) .add_extension(sia, False) ) cert = builder.sign(private_key, hashes.SHA256(), backend) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_INFORMATION_ACCESS ) assert ext.value == sia def test_subject_dn_asn1_types( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 name = x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.LOCALITY_NAME, "value"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "value"), x509.NameAttribute(NameOID.STREET_ADDRESS, "value"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "value"), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "value"), x509.NameAttribute(NameOID.SERIAL_NUMBER, "value"), x509.NameAttribute(NameOID.SURNAME, "value"), x509.NameAttribute(NameOID.GIVEN_NAME, "value"), x509.NameAttribute(NameOID.TITLE, "value"), x509.NameAttribute(NameOID.GENERATION_QUALIFIER, "value"), x509.NameAttribute(NameOID.X500_UNIQUE_IDENTIFIER, "value"), x509.NameAttribute(NameOID.DN_QUALIFIER, "value"), x509.NameAttribute(NameOID.PSEUDONYM, "value"), x509.NameAttribute(NameOID.USER_ID, "value"), x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "value"), x509.NameAttribute(NameOID.EMAIL_ADDRESS, "value"), x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, "US"), x509.NameAttribute( NameOID.JURISDICTION_LOCALITY_NAME, "value" ), x509.NameAttribute( NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, "value" ), x509.NameAttribute(NameOID.BUSINESS_CATEGORY, "value"), x509.NameAttribute(NameOID.POSTAL_ADDRESS, "value"), x509.NameAttribute(NameOID.POSTAL_CODE, "value"), ] ) cert = ( x509.CertificateBuilder() .subject_name(name) .issuer_name(name) .public_key(private_key.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(1999, 1, 1)) .not_valid_after(datetime.datetime(2020, 1, 1)) .sign(private_key, hashes.SHA256(), backend) ) for dn in (cert.subject, cert.issuer): for oid, asn1_type in TestNameAttribute.EXPECTED_TYPES: assert dn.get_attributes_for_oid(oid)[0]._type == asn1_type @pytest.mark.parametrize( ("not_valid_before", "not_valid_after"), [ [datetime.datetime(1970, 2, 1), datetime.datetime(9999, 1, 1)], [datetime.datetime(1970, 2, 1), datetime.datetime(9999, 12, 31)], ], ) def test_extreme_times( self, rsa_key_2048: rsa.RSAPrivateKey, not_valid_before, not_valid_after, backend, ): private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(private_key.public_key()) .serial_number(777) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(private_key, hashes.SHA256(), backend) _check_cert_times( cert, not_valid_before=not_valid_before, not_valid_after=not_valid_after, ) parsed = test_support.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) # UTC TIME assert parsed.not_before_tag == 0x17 # GENERALIZED TIME assert parsed.not_after_tag == 0x18 def test_rdns_preserve_iteration_order( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): """ This test checks that RDN ordering is consistent when loading data from a certificate. Since the underlying RDN is an ASN.1 set these values get lexicographically ordered on encode and the parsed value won't necessarily be in the same order as the originally provided list. However, we want to make sure that the order is always consistent since it confuses people when it isn't. """ name = x509.Name( [ x509.RelativeDistinguishedName( [ x509.NameAttribute(NameOID.TITLE, "Test"), x509.NameAttribute(NameOID.COMMON_NAME, "Multivalue"), x509.NameAttribute(NameOID.SURNAME, "RDNs"), ] ), ] ) cert = ( x509.CertificateBuilder() .serial_number(1) .issuer_name(name) .subject_name(name) .public_key(rsa_key_2048.public_key()) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2038, 1, 1)) .sign(rsa_key_2048, hashes.SHA256(), backend) ) loaded_cert = x509.load_pem_x509_certificate( cert.public_bytes(encoding=serialization.Encoding.PEM) ) assert next(iter(loaded_cert.subject.rdns[0])) == x509.NameAttribute( NameOID.SURNAME, "RDNs" ) @pytest.mark.parametrize( ("alg", "mgf_alg"), [ (hashes.SHA512(), hashes.SHA256()), (hashes.SHA3_512(), hashes.SHA3_256()), ], ) def test_sign_pss( self, rsa_key_2048: rsa.RSAPrivateKey, alg, mgf_alg, backend ): if not backend.signature_hash_supported(alg): pytest.skip(f"{alg} signature not supported") builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(rsa_key_2048.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2038, 1, 1)) ) pss = padding.PSS( mgf=padding.MGF1(mgf_alg), salt_length=alg.digest_size ) cert = builder.sign(rsa_key_2048, alg, rsa_padding=pss) pk = cert.public_key() assert isinstance(pk, rsa.RSAPublicKey) assert isinstance(cert.signature_hash_algorithm, type(alg)) cert_params = cert.signature_algorithm_parameters assert isinstance(cert_params, padding.PSS) assert cert_params._salt_length == pss._salt_length assert isinstance(cert_params._mgf, padding.MGF1) assert isinstance(cert_params._mgf._algorithm, type(mgf_alg)) pk.verify( cert.signature, cert.tbs_certificate_bytes, cert_params, alg, ) @pytest.mark.parametrize( ("padding_len", "computed_len"), [ (padding.PSS.MAX_LENGTH, 222), (padding.PSS.DIGEST_LENGTH, 32), ], ) def test_sign_pss_length_options( self, rsa_key_2048: rsa.RSAPrivateKey, padding_len, computed_len, backend, ): builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(rsa_key_2048.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2038, 1, 1)) ) pss = padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len ) cert = builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) assert isinstance(cert.signature_algorithm_parameters, padding.PSS) assert cert.signature_algorithm_parameters._salt_length == computed_len def test_sign_pss_auto_unsupported( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(rsa_key_2048.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2038, 1, 1)) ) pss = padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO ) with pytest.raises(TypeError): builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) def test_sign_invalid_padding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(rsa_key_2048.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2038, 1, 1)) ) with pytest.raises(TypeError): builder.sign( rsa_key_2048, hashes.SHA256(), rsa_padding=b"notapadding", # type: ignore[arg-type] ) eckey = ec.generate_private_key(ec.SECP256R1()) with pytest.raises(TypeError): builder.sign( eckey, hashes.SHA256(), rsa_padding=padding.PKCS1v15() ) def test_sign_pss_hash_none( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(rsa_key_2048.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2038, 1, 1)) ) pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32) with pytest.raises(TypeError): builder.sign(rsa_key_2048, None, rsa_padding=pss) def test_no_subject_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) ) with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) def test_no_issuer_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) ) with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) def test_no_public_key(self, rsa_key_2048: rsa.RSAPrivateKey, backend): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) ) with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) def test_no_not_valid_before( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) ) with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) def test_no_not_valid_after( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) ) with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) def test_no_serial_number(self, rsa_key_2048: rsa.RSAPrivateKey, backend): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) ) with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) def test_issuer_name_must_be_a_name_type(self): builder = x509.CertificateBuilder() with pytest.raises(TypeError): builder.issuer_name("subject") # type:ignore[arg-type] with pytest.raises(TypeError): builder.issuer_name(object) # type:ignore[arg-type] def test_issuer_name_may_only_be_set_once(self): name = x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) builder = x509.CertificateBuilder().issuer_name(name) with pytest.raises(ValueError): builder.issuer_name(name) def test_subject_name_must_be_a_name_type(self): builder = x509.CertificateBuilder() with pytest.raises(TypeError): builder.subject_name("subject") # type:ignore[arg-type] with pytest.raises(TypeError): builder.subject_name(object) # type:ignore[arg-type] def test_subject_name_may_only_be_set_once(self): name = x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) builder = x509.CertificateBuilder().subject_name(name) with pytest.raises(ValueError): builder.subject_name(name) def test_not_valid_before_after_not_valid_after(self): builder = x509.CertificateBuilder() builder = builder.not_valid_after(datetime.datetime(2002, 1, 1, 12, 1)) with pytest.raises(ValueError): builder.not_valid_before(datetime.datetime(2003, 1, 1, 12, 1)) def test_not_valid_after_before_not_valid_before(self): builder = x509.CertificateBuilder() builder = builder.not_valid_before( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.not_valid_after(datetime.datetime(2001, 1, 1, 12, 1)) def test_public_key_must_be_public_key( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 builder = x509.CertificateBuilder() with pytest.raises(TypeError): builder.public_key(private_key) # type: ignore[arg-type] def test_public_key_may_only_be_set_once( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 public_key = private_key.public_key() builder = x509.CertificateBuilder().public_key(public_key) with pytest.raises(ValueError): builder.public_key(public_key) def test_serial_number_must_be_an_integer_type(self): with pytest.raises(TypeError): x509.CertificateBuilder().serial_number( 10.0 # type:ignore[arg-type] ) def test_serial_number_must_be_non_negative(self): with pytest.raises(ValueError): x509.CertificateBuilder().serial_number(-1) def test_serial_number_must_be_positive(self): with pytest.raises(ValueError): x509.CertificateBuilder().serial_number(0) def test_minimal_serial_number( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(1) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) ) cert = builder.sign(subject_private_key, hashes.SHA256(), backend) assert cert.serial_number == 1 def test_biggest_serial_number( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number((1 << 159) - 1) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "RU")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) ) cert = builder.sign(subject_private_key, hashes.SHA256(), backend) assert cert.serial_number == (1 << 159) - 1 def test_serial_number_must_be_less_than_160_bits_long(self): with pytest.raises(ValueError): x509.CertificateBuilder().serial_number(1 << 159) def test_serial_number_may_only_be_set_once(self): builder = x509.CertificateBuilder().serial_number(10) with pytest.raises(ValueError): builder.serial_number(20) def test_aware_not_valid_after( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): tz = datetime.timezone(datetime.timedelta(hours=-8)) time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_time = datetime.datetime(2012, 1, 17, 6, 43) private_key = rsa_key_2048 cert_builder = x509.CertificateBuilder().not_valid_after(time) cert_builder = ( cert_builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(utc_time - datetime.timedelta(days=365)) ) cert = cert_builder.sign(private_key, hashes.SHA256(), backend) _check_cert_times( cert, not_valid_before=None, not_valid_after=utc_time ) def test_earliest_time(self, rsa_key_2048: rsa.RSAPrivateKey, backend): time = datetime.datetime(1950, 1, 1) private_key = rsa_key_2048 cert_builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(time) .not_valid_after(time) ) cert = cert_builder.sign(private_key, hashes.SHA256(), backend) _check_cert_times(cert, not_valid_before=time, not_valid_after=time) parsed = test_support.test_parse_certificate( cert.public_bytes(serialization.Encoding.DER) ) # UTC TIME assert parsed.not_before_tag == 0x17 assert parsed.not_after_tag == 0x17 def test_invalid_not_valid_after(self): with pytest.raises(TypeError): x509.CertificateBuilder().not_valid_after( 104204304504 # type:ignore[arg-type] ) with pytest.raises(TypeError): x509.CertificateBuilder().not_valid_after( datetime.time() # type:ignore[arg-type] ) with pytest.raises(ValueError): x509.CertificateBuilder().not_valid_after( datetime.datetime(1940, 8, 10) ) def test_not_valid_after_may_only_be_set_once(self): builder = x509.CertificateBuilder().not_valid_after( datetime.datetime.now() ) with pytest.raises(ValueError): builder.not_valid_after(datetime.datetime.now()) def test_aware_not_valid_before( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): tz = datetime.timezone(datetime.timedelta(hours=-8)) time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_time = datetime.datetime(2012, 1, 17, 6, 43) private_key = rsa_key_2048 cert_builder = x509.CertificateBuilder().not_valid_before(time) cert_builder = ( cert_builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_after(utc_time + datetime.timedelta(days=366)) ) cert = cert_builder.sign(private_key, hashes.SHA256(), backend) _check_cert_times( cert, not_valid_before=utc_time, not_valid_after=None ) def test_invalid_not_valid_before(self): with pytest.raises(TypeError): x509.CertificateBuilder().not_valid_before( 104204304504 # type:ignore[arg-type] ) with pytest.raises(TypeError): x509.CertificateBuilder().not_valid_before( datetime.time() # type:ignore[arg-type] ) with pytest.raises(ValueError): x509.CertificateBuilder().not_valid_before( datetime.datetime(1940, 8, 10) ) def test_not_valid_before_may_only_be_set_once(self): builder = x509.CertificateBuilder().not_valid_before( datetime.datetime.now() ) with pytest.raises(ValueError): builder.not_valid_before(datetime.datetime.now()) def test_add_extension_checks_for_duplicates(self): builder = x509.CertificateBuilder().add_extension( x509.BasicConstraints(ca=False, path_length=None), True, ) with pytest.raises(ValueError): builder.add_extension( x509.BasicConstraints(ca=False, path_length=None), True, ) def test_add_invalid_extension_type(self): builder = x509.CertificateBuilder() with pytest.raises(TypeError): builder.add_extension( object(), # type:ignore[arg-type] False, ) @pytest.mark.parametrize("algorithm", [object(), None]) def test_sign_with_unsupported_hash( self, rsa_key_2048: rsa.RSAPrivateKey, algorithm, backend ): private_key = rsa_key_2048 builder = x509.CertificateBuilder() builder = ( builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) with pytest.raises(TypeError): builder.sign(private_key, algorithm, backend) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_sign_with_unsupported_hash_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_sign_with_unsupported_hash_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Requires OpenSSL with MD5 support", ) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.parametrize( "hash_algorithm", [ hashes.MD5(), hashes.SHA3_224(), hashes.SHA3_256(), hashes.SHA3_384(), hashes.SHA3_512(), ], ) def test_sign_dsa_with_unsupported_hash(self, hash_algorithm, backend): private_key = DSA_KEY_2048.private_key(backend) builder = x509.CertificateBuilder() builder = ( builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) with pytest.raises(UnsupportedAlgorithm): builder.sign(private_key, hash_algorithm, backend) @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Requires OpenSSL with MD5 support", ) def test_sign_ec_with_md5(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = EC_KEY_SECP256R1.private_key(backend) builder = x509.CertificateBuilder() builder = ( builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) with pytest.raises(UnsupportedAlgorithm): builder.sign( private_key, hashes.MD5(), # type: ignore[arg-type] backend, ) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.parametrize( ("hashalg", "hashalg_oid"), [ (hashes.SHA224, x509.SignatureAlgorithmOID.DSA_WITH_SHA224), (hashes.SHA256, x509.SignatureAlgorithmOID.DSA_WITH_SHA256), (hashes.SHA384, x509.SignatureAlgorithmOID.DSA_WITH_SHA384), (hashes.SHA512, x509.SignatureAlgorithmOID.DSA_WITH_SHA512), ], ) def test_build_cert_with_dsa_private_key( self, hashalg, hashalg_oid, backend ): issuer_private_key = DSA_KEY_2048.private_key(backend) subject_private_key = DSA_KEY_2048.private_key(backend) not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( x509.BasicConstraints(ca=False, path_length=None), True, ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 assert cert.signature_algorithm_oid == hashalg_oid public_key = cert.public_key() assert isinstance(public_key, dsa.DSAPublicKey) assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.DSA _check_cert_times( cert, not_valid_before=not_valid_before, not_valid_after=not_valid_after, ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) assert isinstance( subject_alternative_name.value, x509.SubjectAlternativeName ) assert list(subject_alternative_name.value) == [ x509.DNSName("cryptography.io"), ] @pytest.mark.parametrize( ("hashalg", "hashalg_oid"), [ (hashes.SHA224, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA224), (hashes.SHA256, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA256), (hashes.SHA384, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA384), (hashes.SHA512, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA512), (hashes.SHA3_224, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_224), (hashes.SHA3_256, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_256), (hashes.SHA3_384, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_384), (hashes.SHA3_512, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA3_512), ], ) def test_build_cert_with_ec_private_key( self, hashalg, hashalg_oid, backend ): _skip_curve_unsupported(backend, ec.SECP256R1()) if not backend.signature_hash_supported(hashalg()): pytest.skip(f"{hashalg} signature not supported") issuer_private_key = ec.generate_private_key(ec.SECP256R1(), backend) subject_private_key = ec.generate_private_key(ec.SECP256R1(), backend) not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( x509.BasicConstraints(ca=False, path_length=None), True, ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashalg(), backend) assert cert.version is x509.Version.v3 public_key = cert.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) assert ( cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.EC_PUBLIC_KEY ) assert cert.signature_algorithm_oid == hashalg_oid assert type(cert.signature_hash_algorithm) is hashalg _check_cert_times( cert, not_valid_before=not_valid_before, not_valid_after=not_valid_after, ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) assert isinstance( subject_alternative_name.value, x509.SubjectAlternativeName ) assert list(subject_alternative_name.value) == [ x509.DNSName("cryptography.io"), ] def test_build_cert_with_bmpstring_universalstring_name( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 issuer = x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io", _ASN1Type.BMPString, ), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) subject = x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io", _ASN1Type.UniversalString, ), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) builder = x509.CertificateBuilder() builder = ( builder.subject_name(subject) .issuer_name(issuer) .serial_number(1) .public_key(private_key.public_key()) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) cert = builder.sign(private_key, hashes.SHA256(), backend) assert cert.issuer == issuer assert cert.subject == subject @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_build_cert_with_ed25519(self, backend): issuer_private_key = ed25519.Ed25519PrivateKey.generate() subject_private_key = ed25519.Ed25519PrivateKey.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( x509.BasicConstraints(ca=False, path_length=None), True, ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, None, backend) issuer_private_key.public_key().verify( cert.signature, cert.tbs_certificate_bytes ) assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 assert cert.signature_hash_algorithm is None assert isinstance(cert.public_key(), ed25519.Ed25519PublicKey) assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED25519 assert cert.version is x509.Version.v3 _check_cert_times( cert, not_valid_before=not_valid_before, not_valid_after=not_valid_after, ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) assert isinstance( subject_alternative_name.value, x509.SubjectAlternativeName ) assert list(subject_alternative_name.value) == [ x509.DNSName("cryptography.io"), ] @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_build_cert_with_public_ed25519_rsa_sig( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): issuer_private_key = rsa_key_2048 subject_private_key = ed25519.Ed25519PrivateKey.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) assert cert.signature_hash_algorithm is not None issuer_private_key.public_key().verify( cert.signature, cert.tbs_certificate_bytes, padding.PKCS1v15(), cert.signature_hash_algorithm, ) assert cert.signature_algorithm_oid == ( SignatureAlgorithmOID.RSA_WITH_SHA256 ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), ed25519.Ed25519PublicKey) assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED25519 @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_build_cert_with_ed448(self, backend): issuer_private_key = ed448.Ed448PrivateKey.generate() subject_private_key = ed448.Ed448PrivateKey.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .add_extension( x509.BasicConstraints(ca=False, path_length=None), True, ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, None, backend) issuer_private_key.public_key().verify( cert.signature, cert.tbs_certificate_bytes ) assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED448 assert cert.signature_hash_algorithm is None assert isinstance(cert.public_key(), ed448.Ed448PublicKey) assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED448 assert cert.version is x509.Version.v3 _check_cert_times( cert, not_valid_before=not_valid_before, not_valid_after=not_valid_after, ) basic_constraints = cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None subject_alternative_name = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) assert isinstance( subject_alternative_name.value, x509.SubjectAlternativeName ) assert list(subject_alternative_name.value) == [ x509.DNSName("cryptography.io"), ] @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_build_cert_with_public_ed448_rsa_sig( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): issuer_private_key = rsa_key_2048 subject_private_key = ed448.Ed448PrivateKey.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) assert cert.signature_hash_algorithm is not None issuer_private_key.public_key().verify( cert.signature, cert.tbs_certificate_bytes, padding.PKCS1v15(), cert.signature_hash_algorithm, ) assert cert.signature_algorithm_oid == ( SignatureAlgorithmOID.RSA_WITH_SHA256 ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), ed448.Ed448PublicKey) assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED448 @pytest.mark.supported( only_if=lambda backend: ( backend.x25519_supported() and backend.x448_supported() ), skip_message="Requires OpenSSL with x25519 & x448 support", ) @pytest.mark.parametrize( ("priv_key_cls", "pub_key_cls", "pub_key_oid"), [ ( x25519.X25519PrivateKey, x25519.X25519PublicKey, PublicKeyAlgorithmOID.X25519, ), ( x448.X448PrivateKey, x448.X448PublicKey, PublicKeyAlgorithmOID.X448, ), ], ) def test_build_cert_with_public_x25519_x448_rsa_sig( self, rsa_key_2048: rsa.RSAPrivateKey, priv_key_cls, pub_key_cls, pub_key_oid, backend, ): issuer_private_key = rsa_key_2048 subject_private_key = priv_key_cls.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) assert cert.signature_hash_algorithm is not None issuer_private_key.public_key().verify( cert.signature, cert.tbs_certificate_bytes, padding.PKCS1v15(), cert.signature_hash_algorithm, ) assert cert.signature_algorithm_oid == ( SignatureAlgorithmOID.RSA_WITH_SHA256 ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), pub_key_cls) assert cert.public_key_algorithm_oid == pub_key_oid def test_build_cert_with_rsa_key_too_small( self, rsa_key_512: rsa.RSAPrivateKey, backend ): issuer_private_key = rsa_key_512 subject_private_key = rsa_key_512 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(subject_private_key.public_key()) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) with pytest.raises(ValueError): builder.sign(issuer_private_key, hashes.SHA512(), backend) @pytest.mark.parametrize( "add_ext", [ x509.SubjectAlternativeName( [ # These examples exist to verify compatibility with # certificates that have utf8 encoded data in the ia5string x509.DNSName._init_without_validation("a\xedt\xe1s.test"), x509.RFC822Name._init_without_validation( "test@a\xedt\xe1s.test" ), x509.UniformResourceIdentifier._init_without_validation( "http://a\xedt\xe1s.test" ), ] ), x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), ["http://other.com/cps"], ) ] ), x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), None, ) ] ), x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ "http://example.com/cps", "http://other.com/cps", x509.UserNotice( x509.NoticeReference("my org", [1, 2, 3, 4]), "thing", ), ], ) ] ), x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ "http://example.com/cps", x509.UserNotice( x509.NoticeReference( "UTF8\u2122'", [1, 2, 3, 4] ), "We heart UTF8!\u2122", ), ], ) ] ), x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [x509.UserNotice(None, "thing")], ) ] ), x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ x509.UserNotice( x509.NoticeReference("my org", [1, 2, 3, 4]), None, ) ], ) ] ), x509.IssuerAlternativeName( [ x509.DNSName("myissuer"), x509.RFC822Name("email@domain.com"), ] ), x509.ExtendedKeyUsage( [ ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.SERVER_AUTH, ExtendedKeyUsageOID.CODE_SIGNING, ] ), x509.InhibitAnyPolicy(3), x509.TLSFeature([x509.TLSFeatureType.status_request]), x509.TLSFeature([x509.TLSFeatureType.status_request_v2]), x509.TLSFeature( [ x509.TLSFeatureType.status_request, x509.TLSFeatureType.status_request_v2, ] ), x509.NameConstraints( permitted_subtrees=[ x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24")), x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/29")), x509.IPAddress(ipaddress.IPv4Network("127.0.0.1/32")), x509.IPAddress(ipaddress.IPv4Network("8.0.0.0/8")), x509.IPAddress(ipaddress.IPv4Network("0.0.0.0/0")), x509.IPAddress( ipaddress.IPv6Network("FF:0:0:0:0:0:0:0/96") ), x509.IPAddress( ipaddress.IPv6Network("FF:FF:0:0:0:0:0:0/128") ), ], excluded_subtrees=[x509.DNSName("name.local")], ), x509.NameConstraints( permitted_subtrees=[ x509.IPAddress(ipaddress.IPv4Network("0.0.0.0/0")), ], excluded_subtrees=None, ), x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[x509.DNSName("name.local")], ), x509.PolicyConstraints( require_explicit_policy=None, inhibit_policy_mapping=1 ), x509.PolicyConstraints( require_explicit_policy=3, inhibit_policy_mapping=1 ), x509.PolicyConstraints( require_explicit_policy=0, inhibit_policy_mapping=None ), x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( NameOID.COMMON_NAME, "indirect CRL for indirectCRL CA3", ), ] ), reasons=None, crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "indirectCRL CA3 cRLIssuer", ), ] ) ) ], ) ] ), x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), ] ) ) ], relative_name=None, reasons=None, crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.ORGANIZATION_NAME, "cryptography Testing", ), ] ) ) ], ) ] ), x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ), x509.UniformResourceIdentifier( "http://backup.myhost.com/myca.crl" ), ], relative_name=None, reasons=frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA", ), ] ) ) ], ) ] ), x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://domain.com/some.crl" ) ], relative_name=None, reasons=frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, x509.ReasonFlags.affiliation_changed, x509.ReasonFlags.superseded, x509.ReasonFlags.privilege_withdrawn, x509.ReasonFlags.cessation_of_operation, x509.ReasonFlags.aa_compromise, x509.ReasonFlags.certificate_hold, ] ), crl_issuer=None, ) ] ), x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=None, relative_name=None, reasons=None, crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA", ), ] ) ) ], ) ] ), x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://domain.com/some.crl" ) ], relative_name=None, reasons=frozenset([x509.ReasonFlags.aa_compromise]), crl_issuer=None, ) ] ), x509.FreshestCRL( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://domain.com/some.crl" ) ], relative_name=None, reasons=frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, x509.ReasonFlags.affiliation_changed, x509.ReasonFlags.superseded, x509.ReasonFlags.privilege_withdrawn, x509.ReasonFlags.cessation_of_operation, x509.ReasonFlags.aa_compromise, x509.ReasonFlags.certificate_hold, ] ), crl_issuer=None, ) ] ), x509.FreshestCRL( [ x509.DistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( NameOID.COMMON_NAME, "indirect CRL for indirectCRL CA3", ), ] ), reasons=None, crl_issuer=None, ) ] ), x509.FreshestCRL( [ x509.DistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( NameOID.COMMON_NAME, "indirect CRL for indirectCRL CA3", ), x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ] ), reasons=None, crl_issuer=None, ) ] ), x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier( "http://ocsp.domain.com" ), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier( "http://domain.com/ca.crt" ), ), ] ), x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), ] ), x509.AuthorityKeyIdentifier( b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" b"\xcbY", None, None, ), x509.AuthorityKeyIdentifier( b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" b"\xcbY", [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.ORGANIZATION_NAME, "PyCA" ), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA" ), ] ) ) ], 333, ), x509.AuthorityKeyIdentifier( None, [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.ORGANIZATION_NAME, "PyCA" ), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA" ), ] ) ) ], 333, ), x509.KeyUsage( digital_signature=True, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=False, encipher_only=False, decipher_only=False, ), x509.OCSPNoCheck(), x509.SubjectKeyIdentifier, ], ) def test_extensions( self, rsa_key_2048: rsa.RSAPrivateKey, add_ext, backend ): issuer_private_key = rsa_key_2048 subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) if add_ext is x509.SubjectKeyIdentifier: add_ext = x509.SubjectKeyIdentifier.from_public_key( subject_private_key.public_key() ) # Cert cert = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) .public_key(subject_private_key.public_key()) .serial_number(123) .add_extension(add_ext, critical=False) .sign(issuer_private_key, hashes.SHA256(), backend) ) ext = cert.extensions.get_extension_for_class(type(add_ext)) assert ext.critical is False assert ext.value == add_ext # CSR csr = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension(add_ext, False) .sign(subject_private_key, hashes.SHA256()) ) ext = csr.extensions.get_extension_for_class(type(add_ext)) assert ext.critical is False assert ext.value == add_ext def test_build_ca_request_with_path_length_none( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True ) .sign(private_key, hashes.SHA256(), backend) ) loaded_request = x509.load_pem_x509_csr( request.public_bytes(encoding=serialization.Encoding.PEM), backend ) subject = loaded_request.subject assert isinstance(subject, x509.Name) basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.path_length is None @pytest.mark.parametrize( "unrecognized", [ x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4.5"), b"abcdef", ) ], ) def test_unrecognized_extension( self, rsa_key_2048: rsa.RSAPrivateKey, backend, unrecognized ): private_key = rsa_key_2048 cert = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, "US")]) ) .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2030, 12, 31, 8, 30)) .public_key(private_key.public_key()) .serial_number(123) .add_extension(unrecognized, critical=False) .sign(private_key, hashes.SHA256(), backend) ) ext = cert.extensions.get_extension_for_oid(unrecognized.oid) assert ext.value == unrecognized class TestCertificateSigningRequestBuilder: def test_sign_invalid_hash_algorithm( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([]) ) with pytest.raises(TypeError): builder.sign( private_key, "NotAHash", # type: ignore[arg-type] backend, ) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_request_with_unsupported_hash_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_request_with_unsupported_hash_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_no_subject_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder() with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_build_ca_request_with_rsa( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) .sign(private_key, hashes.SHA256(), backend) ) assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 def test_build_ca_request_with_unicode( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ x509.NameAttribute( NameOID.ORGANIZATION_NAME, "PyCA\U0001f37a" ), ] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) .sign(private_key, hashes.SHA256(), backend) ) loaded_request = x509.load_pem_x509_csr( request.public_bytes(encoding=serialization.Encoding.PEM), backend ) subject = loaded_request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA\U0001f37a"), ] def test_subject_dn_asn1_types( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.LOCALITY_NAME, "value"), x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "value" ), x509.NameAttribute(NameOID.STREET_ADDRESS, "value"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "value"), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "value" ), x509.NameAttribute(NameOID.SERIAL_NUMBER, "value"), x509.NameAttribute(NameOID.SURNAME, "value"), x509.NameAttribute(NameOID.GIVEN_NAME, "value"), x509.NameAttribute(NameOID.TITLE, "value"), x509.NameAttribute( NameOID.GENERATION_QUALIFIER, "value" ), x509.NameAttribute( NameOID.X500_UNIQUE_IDENTIFIER, "value" ), x509.NameAttribute(NameOID.DN_QUALIFIER, "value"), x509.NameAttribute(NameOID.PSEUDONYM, "value"), x509.NameAttribute(NameOID.USER_ID, "value"), x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "value"), x509.NameAttribute(NameOID.EMAIL_ADDRESS, "value"), x509.NameAttribute( NameOID.JURISDICTION_COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.JURISDICTION_LOCALITY_NAME, "value" ), x509.NameAttribute( NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, "value", ), x509.NameAttribute(NameOID.BUSINESS_CATEGORY, "value"), x509.NameAttribute(NameOID.POSTAL_ADDRESS, "value"), x509.NameAttribute(NameOID.POSTAL_CODE, "value"), ] ) ) .sign(private_key, hashes.SHA256(), backend) ) for oid, asn1_type in TestNameAttribute.EXPECTED_TYPES: assert ( request.subject.get_attributes_for_oid(oid)[0]._type == asn1_type ) def test_build_ca_request_with_multivalue_rdns( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 subject = x509.Name( [ x509.RelativeDistinguishedName( [ x509.NameAttribute(NameOID.TITLE, "Test"), x509.NameAttribute(NameOID.COMMON_NAME, "Multivalue"), x509.NameAttribute(NameOID.SURNAME, "RDNs"), ] ), x509.RelativeDistinguishedName( [x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")] ), ] ) request = ( x509.CertificateSigningRequestBuilder() .subject_name(subject) .sign(private_key, hashes.SHA256(), backend) ) loaded_request = x509.load_pem_x509_csr( request.public_bytes(encoding=serialization.Encoding.PEM), backend ) assert isinstance(loaded_request.subject, x509.Name) assert loaded_request.subject == subject def test_build_nonca_request_with_rsa( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True, ) .sign(private_key, hashes.SHA256(), backend) ) assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is False assert basic_constraints.value.path_length is None def test_build_ca_request_with_ec(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) .sign(private_key, hashes.SHA256(), backend) ) assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_build_ca_request_with_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) .sign(private_key, None, backend) ) assert request.signature_hash_algorithm is None public_key = request.public_key() assert isinstance(public_key, ed25519.Ed25519PublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] basic_constraints = request.extensions.get_extension_for_class( x509.BasicConstraints ) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_build_ca_request_with_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) .sign(private_key, None, backend) ) assert request.signature_hash_algorithm is None public_key = request.public_key() assert isinstance(public_key, ed448.Ed448PublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] basic_constraints = request.extensions.get_extension_for_class( x509.BasicConstraints ) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) def test_build_ca_request_with_dsa(self, backend): private_key = DSA_KEY_2048.private_key(backend) request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) .sign(private_key, hashes.SHA256(), backend) ) assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, dsa.DSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), ] basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 def test_add_duplicate_extension(self): builder = x509.CertificateSigningRequestBuilder().add_extension( x509.BasicConstraints(True, 2), critical=True, ) with pytest.raises(ValueError): builder.add_extension( x509.BasicConstraints(True, 2), critical=True, ) def test_set_invalid_subject(self): builder = x509.CertificateSigningRequestBuilder() with pytest.raises(TypeError): builder.subject_name("NotAName") # type:ignore[arg-type] def test_add_invalid_extension_type(self): builder = x509.CertificateSigningRequestBuilder() with pytest.raises(TypeError): builder.add_extension( object(), # type:ignore[arg-type] False, ) def test_add_unsupported_extension( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder() builder = ( builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .add_extension(DummyExtension(), False) ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) def test_add_two_extensions( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder() request = ( builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]), critical=False, ) .add_extension( x509.BasicConstraints(ca=True, path_length=2), critical=True ) .sign(private_key, hashes.SHA256(), backend) ) assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, rsa.RSAPublicKey) basic_constraints = request.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) assert isinstance(basic_constraints.value, x509.BasicConstraints) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 ext = request.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) assert isinstance(ext.value, x509.SubjectAlternativeName) assert list(ext.value) == [x509.DNSName("cryptography.io")] def test_add_attributes(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) challenge_password = b"challenge me!" unstructured_name = b"no structure, for shame" locality = b"this shouldn't even be an X509 attribute" request = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) ) .add_attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, challenge_password ) .add_attribute( x509.oid.AttributeOID.UNSTRUCTURED_NAME, unstructured_name ) .add_attribute(x509.oid.NameOID.LOCALITY_NAME, locality) .add_extension( x509.ExtendedKeyUsage( [ ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.SERVER_AUTH, ExtendedKeyUsageOID.CODE_SIGNING, ] ), False, ) .sign(private_key, hashes.SHA256(), backend) ) assert ( request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ).value == challenge_password ) assert ( request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.UNSTRUCTURED_NAME ).value == unstructured_name ) assert ( request.attributes.get_attribute_for_oid( x509.oid.NameOID.LOCALITY_NAME ).value == locality ) assert len(request.attributes) == 4 def test_add_attributes_non_utf8(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) builder = ( x509.CertificateSigningRequestBuilder() .subject_name(x509.Name([])) .add_attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"\xbb\xad\x16\x9a\xac\xc9\x03i\xec\xcc\xdd6\xcbh\xfc\xf3", ) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_add_attribute_bad_types(self, backend): request = x509.CertificateSigningRequestBuilder() with pytest.raises(TypeError): request.add_attribute( b"not an oid", # type:ignore[arg-type] b"val", ) with pytest.raises(TypeError): request.add_attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, 383, # type:ignore[arg-type] ) def test_duplicate_attribute(self, backend): request = x509.CertificateSigningRequestBuilder().add_attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"val" ) with pytest.raises(ValueError): request.add_attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"val2" ) def test_add_attribute_tag(self, backend): private_key = ec.generate_private_key(ec.SECP256R1(), backend) builder = ( x509.CertificateSigningRequestBuilder() .subject_name(x509.Name([])) .add_attribute( x509.ObjectIdentifier("1.2.3.4"), b"\x00\x00", _tag=_ASN1Type.GeneralizedTime, ) ) request = builder.sign(private_key, hashes.SHA256(), backend) attr = request.attributes.get_attribute_for_oid( x509.ObjectIdentifier("1.2.3.4") ) assert attr.value == b"\x00\x00" assert attr._type == _ASN1Type.GeneralizedTime.value def test_add_attribute_tag_non_int(self, backend): builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([]) ) with pytest.raises(TypeError): builder.add_attribute( x509.ObjectIdentifier("1.2.3.4"), b"", _tag=object(), # type:ignore[arg-type] ) def test_set_subject_twice(self): builder = x509.CertificateSigningRequestBuilder() builder = builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) @pytest.mark.parametrize( "add_ext", [ x509.KeyUsage( digital_signature=True, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=False, encipher_only=False, decipher_only=False, ), x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=True, crl_sign=False, encipher_only=False, decipher_only=True, ), x509.SubjectAlternativeName( [ x509.DNSName("example.com"), x509.DNSName("*.example.com"), x509.RegisteredID(x509.ObjectIdentifier("1.2.3.4.5.6.7")), x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "PyCA" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "We heart UTF8!\u2122", ), ] ) ), x509.IPAddress(ipaddress.ip_address("127.0.0.1")), x509.IPAddress(ipaddress.ip_address("ff::")), x509.OtherName( type_id=x509.ObjectIdentifier("1.2.3.3.3.3"), value=b"0\x03\x02\x01\x05", ), x509.RFC822Name("test@example.com"), x509.RFC822Name("email"), x509.RFC822Name("email@xn--eml-vla4c.com"), x509.UniformResourceIdentifier( "https://xn--80ato2c.cryptography" ), x509.UniformResourceIdentifier( "gopher://cryptography:70/some/path" ), ] ), x509.ExtendedKeyUsage( [ ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.SERVER_AUTH, ExtendedKeyUsageOID.CODE_SIGNING, ] ), ], ) def test_extensions( self, rsa_key_2048: rsa.RSAPrivateKey, add_ext, backend ): private_key = rsa_key_2048 csr = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "SAN")]) ) .add_extension( add_ext, critical=False, ) .sign(private_key, hashes.SHA256(), backend) ) assert len(csr.extensions) == 1 ext = csr.extensions.get_extension_for_class(type(add_ext)) assert not ext.critical assert ext.value == add_ext def test_invalid_asn1_othername( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 builder = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "SAN")]) ) .add_extension( x509.SubjectAlternativeName( [ x509.OtherName( type_id=x509.ObjectIdentifier("1.2.3.3.3.3"), # Invalid length value=b"\x01\x05\x01\x05", ), ] ), critical=False, ) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_subject_alt_name_unsupported_general_name( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 builder = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "SAN")]) ) .add_extension( x509.SubjectAlternativeName([FakeGeneralName("")]), critical=False, ) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_rsa_key_too_small(self, rsa_key_512: rsa.RSAPrivateKey, backend): private_key = rsa_key_512 builder = x509.CertificateSigningRequestBuilder() builder = builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) @pytest.mark.parametrize( ("alg", "mgf_alg"), [ (hashes.SHA512(), hashes.SHA256()), (hashes.SHA3_512(), hashes.SHA3_256()), ], ) def test_sign_pss( self, rsa_key_2048: rsa.RSAPrivateKey, alg, mgf_alg, backend ): if not backend.signature_hash_supported(alg): pytest.skip(f"{alg} signature not supported") builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) pss = padding.PSS( mgf=padding.MGF1(mgf_alg), salt_length=alg.digest_size ) csr = builder.sign(rsa_key_2048, alg, rsa_padding=pss) pk = csr.public_key() assert isinstance(pk, rsa.RSAPublicKey) assert isinstance(csr.signature_hash_algorithm, type(alg)) cert_params = csr.signature_algorithm_parameters assert isinstance(cert_params, padding.PSS) assert cert_params._salt_length == pss._salt_length assert isinstance(cert_params._mgf, padding.MGF1) assert isinstance(cert_params._mgf._algorithm, type(mgf_alg)) pk.verify( csr.signature, csr.tbs_certrequest_bytes, cert_params, alg, ) @pytest.mark.parametrize( ("padding_len", "computed_len"), [ (padding.PSS.MAX_LENGTH, 222), (padding.PSS.DIGEST_LENGTH, 32), ], ) def test_sign_pss_length_options( self, rsa_key_2048: rsa.RSAPrivateKey, padding_len, computed_len, backend, ): builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) pss = padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len ) csr = builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) assert isinstance(csr.signature_algorithm_parameters, padding.PSS) assert csr.signature_algorithm_parameters._salt_length == computed_len def test_sign_pss_auto_unsupported( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) pss = padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO ) with pytest.raises(TypeError): builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) def test_sign_invalid_padding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(TypeError): builder.sign( rsa_key_2048, hashes.SHA256(), rsa_padding=b"notapadding", # type: ignore[arg-type] ) eckey = ec.generate_private_key(ec.SECP256R1()) with pytest.raises(TypeError): builder.sign( eckey, hashes.SHA256(), rsa_padding=padding.PKCS1v15() ) def test_sign_pss_hash_none( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32) with pytest.raises(TypeError): builder.sign(rsa_key_2048, None, rsa_padding=pss) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) class TestDSACertificate: @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported( hashes.SHA1() ), skip_message="Does not support SHA-1 signature.", ) def test_load_dsa_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) public_key = cert.public_key() assert isinstance(public_key, dsa.DSAPublicKey) assert cert.signature_algorithm_parameters is None num = public_key.public_numbers() assert num.y == int( "4c08bfe5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa94" "53b6656f13e543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2" "dea559f0b584c97a2b235b9b69b46bc6de1aed422a6f341832618bcaae2" "198aba388099dafb05ff0b5efecb3b0ae169a62e1c72022af50ae68af3b" "033c18e6eec1f7df4692c456ccafb79cc7e08da0a5786e9816ceda651d6" "1b4bb7b81c2783da97cea62df67af5e85991fdc13aff10fc60e06586386" "b96bb78d65750f542f86951e05a6d81baadbcd35a2e5cad4119923ae6a2" "002091a3d17017f93c52970113cdc119970b9074ca506eac91c3dd37632" "5df4af6b3911ef267d26623a5a1c5df4a6d13f1c", 16, ) assert num.parameter_numbers.g == int( "4b7ced71dc353965ecc10d441a9a06fc24943a32d66429dd5ef44d43e67" "d789d99770aec32c0415dc92970880872da45fef8dd1e115a3e4801387b" "a6d755861f062fd3b6e9ea8e2641152339b828315b1528ee6c7b79458d2" "1f3db973f6fc303f9397174c2799dd2351282aa2d8842c357a73495bbaa" "c4932786414c55e60d73169f5761036fba29e9eebfb049f8a3b1b7cee6f" "3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c64130a" "1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425c0" "b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76" "fa6247676a6d3ac945844a083509c6a1b436baca", 16, ) assert num.parameter_numbers.p == int( "bfade6048e373cd4e48b677e878c8e5b08c02102ae04eb2cb5c46a523a3" "af1c73d16b24f34a4964781ae7e50500e21777754a670bd19a7420d6330" "84e5556e33ca2c0e7d547ea5f46a07a01bf8669ae3bdec042d9b2ae5e6e" "cf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736857971444c25d0a" "33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e03e419fd4ca4" "e703313743d86caa885930f62ed5bf342d8165627681e9cc3244ba72aa2" "2148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56da93" "f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d" "833e36468f3907cfca788a3cb790f0341c8a31bf", 16, ) assert num.parameter_numbers.q == int( "822ff5d234e073b901cf5941f58e1f538e71d40d", 16 ) def test_load_dsa_cert_null_alg_params(self, backend): """ This test verifies that we successfully load certificates with encoded null parameters in the signature AlgorithmIdentifier. This is invalid, but all versions of Java less than 21 generate certificates with this encoding so we need to tolerate it at the moment. """ with pytest.warns(utils.DeprecatedIn41): cert = _load_cert( os.path.join("x509", "custom", "dsa_null_alg_params.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), dsa.DSAPublicKey) def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, ) assert cert.signature == binascii.unhexlify( b"302c021425c4a84a936ab311ee017d3cbd9a3c650bb3ae4a02145d30c64b4326" b"86bdf925716b4ed059184396bcce" ) r, s = decode_dss_signature(cert.signature) assert r == 215618264820276283222494627481362273536404860490 assert s == 532023851299196869156027211159466197586787351758 def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"3082051aa003020102020900a37352e0b2142f86300906072a8648ce3804033" b"067310b3009060355040613025553310e300c06035504081305546578617331" b"0f300d0603550407130641757374696e3121301f060355040a1318496e74657" b"26e6574205769646769747320507479204c7464311430120603550403130b50" b"79434120445341204341301e170d3134313132373035313431375a170d31343" b"13232373035313431375a3067310b3009060355040613025553310e300c0603" b"55040813055465786173310f300d0603550407130641757374696e3121301f0" b"60355040a1318496e7465726e6574205769646769747320507479204c746431" b"1430120603550403130b50794341204453412043413082033a3082022d06072" b"a8648ce380401308202200282010100bfade6048e373cd4e48b677e878c8e5b" b"08c02102ae04eb2cb5c46a523a3af1c73d16b24f34a4964781ae7e50500e217" b"77754a670bd19a7420d633084e5556e33ca2c0e7d547ea5f46a07a01bf8669a" b"e3bdec042d9b2ae5e6ecf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736" b"857971444c25d0a33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e0" b"3e419fd4ca4e703313743d86caa885930f62ed5bf342d8165627681e9cc3244" b"ba72aa22148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56d" b"a93f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d8" b"33e36468f3907cfca788a3cb790f0341c8a31bf021500822ff5d234e073b901" b"cf5941f58e1f538e71d40d028201004b7ced71dc353965ecc10d441a9a06fc2" b"4943a32d66429dd5ef44d43e67d789d99770aec32c0415dc92970880872da45" b"fef8dd1e115a3e4801387ba6d755861f062fd3b6e9ea8e2641152339b828315" b"b1528ee6c7b79458d21f3db973f6fc303f9397174c2799dd2351282aa2d8842" b"c357a73495bbaac4932786414c55e60d73169f5761036fba29e9eebfb049f8a" b"3b1b7cee6f3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c" b"64130a1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425" b"c0b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76fa" b"6247676a6d3ac945844a083509c6a1b436baca0382010500028201004c08bfe" b"5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa9453b6656f13e" b"543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2dea559f0b584c97" b"a2b235b9b69b46bc6de1aed422a6f341832618bcaae2198aba388099dafb05f" b"f0b5efecb3b0ae169a62e1c72022af50ae68af3b033c18e6eec1f7df4692c45" b"6ccafb79cc7e08da0a5786e9816ceda651d61b4bb7b81c2783da97cea62df67" b"af5e85991fdc13aff10fc60e06586386b96bb78d65750f542f86951e05a6d81" b"baadbcd35a2e5cad4119923ae6a2002091a3d17017f93c52970113cdc119970" b"b9074ca506eac91c3dd376325df4af6b3911ef267d26623a5a1c5df4a6d13f1" b"ca381cc3081c9301d0603551d0e04160414a4fb887a13fcdeb303bbae9a1dec" b"a72f125a541b3081990603551d2304819130818e8014a4fb887a13fcdeb303b" b"bae9a1deca72f125a541ba16ba4693067310b3009060355040613025553310e" b"300c060355040813055465786173310f300d0603550407130641757374696e3" b"121301f060355040a1318496e7465726e657420576964676974732050747920" b"4c7464311430120603550403130b5079434120445341204341820900a37352e" b"0b2142f86300c0603551d13040530030101ff" ) assert cert.signature_hash_algorithm is not None public_key = cert.public_key() assert isinstance(public_key, dsa.DSAPublicKey) public_key.verify( cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm, ) def test_verify_directly_issued_by_dsa(self, backend): issuer_private_key = DSA_KEY_3072.private_key() subject_private_key = DSA_KEY_2048.private_key() ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert.verify_directly_issued_by(ca) def test_verify_directly_issued_by_dsa_bad_sig(self, backend): issuer_private_key = DSA_KEY_3072.private_key() subject_private_key = DSA_KEY_2048.private_key() ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert_bad_sig = _break_cert_sig(cert) with pytest.raises(InvalidSignature): cert_bad_sig.verify_directly_issued_by(ca) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) @pytest.mark.supported( only_if=lambda backend: backend.signature_hash_supported(hashes.SHA1()), skip_message="Does not support SHA-1 signature.", ) class TestDSACertificateRequest: @pytest.mark.parametrize( ("path", "loader_func"), [ [ os.path.join("x509", "requests", "dsa_sha1.pem"), x509.load_pem_x509_csr, ], [ os.path.join("x509", "requests", "dsa_sha1.der"), x509.load_der_x509_csr, ], ], ) def test_load_dsa_request(self, path, loader_func, backend): request = _load_cert(path, loader_func) assert isinstance(request.signature_hash_algorithm, hashes.SHA1) public_key = request.public_key() assert isinstance(public_key, dsa.DSAPublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), ] def test_signature(self, backend): request = _load_cert( os.path.join("x509", "requests", "dsa_sha1.pem"), x509.load_pem_x509_csr, ) assert request.signature == binascii.unhexlify( b"302c021461d58dc028d0110818a7d817d74235727c4acfdf0214097b52e198e" b"ce95de17273f0a924df23ce9d8188" ) def test_tbs_certrequest_bytes(self, backend): request = _load_cert( os.path.join("x509", "requests", "dsa_sha1.pem"), x509.load_pem_x509_csr, ) assert request.tbs_certrequest_bytes == binascii.unhexlify( b"3082021802010030573118301606035504030c0f63727970746f677261706879" b"2e696f310d300b060355040a0c0450794341310b300906035504061302555331" b"0e300c06035504080c055465786173310f300d06035504070c0641757374696e" b"308201b63082012b06072a8648ce3804013082011e028181008d7fadbc09e284" b"aafa69154cea24177004909e519f8b35d685cde5b4ecdc9583e74d370a0f88ad" b"a98f026f27762fb3d5da7836f986dfcdb3589e5b925bea114defc03ef81dae30" b"c24bbc6df3d588e93427bba64203d4a5b1687b2b5e3b643d4c614976f89f95a3" b"8d3e4c89065fba97514c22c50adbbf289163a74b54859b35b7021500835de56b" b"d07cf7f82e2032fe78949aed117aa2ef0281801f717b5a07782fc2e4e68e311f" b"ea91a54edd36b86ac634d14f05a68a97eae9d2ef31fb1ef3de42c3d100df9ca6" b"4f5bdc2aec7bfdfb474cf831fea05853b5e059f2d24980a0ac463f1e818af352" b"3e3cb79a39d45fa92731897752842469cf8540b01491024eaafbce6018e8a1f4" b"658c343f4ba7c0b21e5376a21f4beb8491961e038184000281800713f07641f6" b"369bb5a9545274a2d4c01998367fb371bb9e13436363672ed68f82174c2de05c" b"8e839bc6de568dd50ba28d8d9d8719423aaec5557df10d773ab22d6d65cbb878" b"04a697bc8fd965b952f9f7e850edf13c8acdb5d753b6d10e59e0b5732e3c82ba" b"fa140342bc4a3bba16bd0681c8a6a2dbbb7efe6ce2b8463b170ba000" ) assert request.signature_hash_algorithm is not None public_key = request.public_key() assert isinstance(public_key, dsa.DSAPublicKey) public_key.verify( request.signature, request.tbs_certrequest_bytes, request.signature_hash_algorithm, ) class TestGOSTCertificate: def test_numeric_string_x509_name_entry(self): cert = _load_cert( os.path.join("x509", "e-trust.ru.der"), x509.load_der_x509_certificate, ) assert ( cert.subject.get_attributes_for_oid( x509.ObjectIdentifier("1.2.643.3.131.1.1") )[0].value == "007710474375" ) class TestECDSACertificate: def test_load_ecdsa_cert(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) public_key = cert.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) num = public_key.public_numbers() assert num.x == int( "dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34eadec69bbcd095f" "6f0ccd00bba615b51467e9e2d9fee8e630c17", 16, ) assert num.y == int( "ec0770f5cf842e40839ce83f416d3badd3a4145936789d0343ee10136c7" "2deae88a7a16bb543ce67dc23ff031ca3e23e", 16, ) assert isinstance(num.curve, ec.SECP384R1) assert isinstance(cert.signature_algorithm_parameters, ec.ECDSA) assert isinstance( cert.signature_algorithm_parameters.algorithm, hashes.SHA384 ) public_key.verify( cert.signature, cert.tbs_certificate_bytes, cert.signature_algorithm_parameters, ) def test_load_ecdsa_cert_null_alg_params(self, backend): """ This test verifies that we successfully load certificates with encoded null parameters in the signature AlgorithmIdentifier. This is invalid, but Java 11 (up to at least 11.0.19) generates certificates with this encoding so we need to tolerate it at the moment. """ with pytest.warns(utils.DeprecatedIn41): cert = _load_cert( os.path.join("x509", "custom", "ecdsa_null_alg.pem"), x509.load_pem_x509_certificate, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), ec.EllipticCurvePublicKey) def test_load_bitstring_dn(self, backend): cert = _load_cert( os.path.join("x509", "scottishpower-bitstring-dn.pem"), x509.load_pem_x509_certificate, ) assert cert.subject == x509.Name( [ x509.NameAttribute(x509.NameOID.COMMON_NAME, "ScottishPower"), x509.NameAttribute( x509.NameOID.ORGANIZATIONAL_UNIT_NAME, "02" ), x509.NameAttribute( NameOID.X500_UNIQUE_IDENTIFIER, b"\x00\x70\xb3\xd5\x1f\x30\x5f\x00\x01", _ASN1Type.BitString, ), ] ) assert repr(cert.subject) == ( "" ) def test_load_name_attribute_long_form_asn1_tag(self, backend): cert = _load_cert( os.path.join("x509", "custom", "long-form-name-attribute.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(ValueError, match="Long-form"): cert.subject with pytest.raises(ValueError, match="Long-form"): cert.issuer def test_ms_certificate_template(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ms-certificate-template.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.MSCertificateTemplate ) tpl = ext.value assert isinstance(tpl, x509.MSCertificateTemplate) assert tpl == x509.MSCertificateTemplate( template_id=x509.ObjectIdentifier("1.2.3.4.5.6.7.8.9.0"), major_version=1, minor_version=None, ) def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, ) assert cert.signature == binascii.unhexlify( b"3065023100adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085" b"a763f99e32de66930ff1ccb1098fdd6cabfa6b7fa0023039665bc2648db89e50" b"dca8d549a2edc7dcd1497f1701b8c8868f4e8c882ba89aa98ac5d100bdf854e2" b"9ae55b7cb32717" ) r, s = decode_dss_signature(cert.signature) assert r == int( "adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085a763f99e32" "de66930ff1ccb1098fdd6cabfa6b7fa0", 16, ) assert s == int( "39665bc2648db89e50dca8d549a2edc7dcd1497f1701b8c8868f4e8c882ba89a" "a98ac5d100bdf854e29ae55b7cb32717", 16, ) def test_tbs_certificate_bytes(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"308201c5a0030201020210055556bcf25ea43535c3a40fd5ab4572300a06082" b"a8648ce3d0403033061310b300906035504061302555331153013060355040a" b"130c446967694365727420496e6331193017060355040b13107777772e64696" b"769636572742e636f6d3120301e06035504031317446967694365727420476c" b"6f62616c20526f6f74204733301e170d3133303830313132303030305a170d3" b"338303131353132303030305a3061310b300906035504061302555331153013" b"060355040a130c446967694365727420496e6331193017060355040b1310777" b"7772e64696769636572742e636f6d3120301e06035504031317446967694365" b"727420476c6f62616c20526f6f742047333076301006072a8648ce3d0201060" b"52b8104002203620004dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34" b"eadec69bbcd095f6f0ccd00bba615b51467e9e2d9fee8e630c17ec0770f5cf8" b"42e40839ce83f416d3badd3a4145936789d0343ee10136c72deae88a7a16bb5" b"43ce67dc23ff031ca3e23ea3423040300f0603551d130101ff040530030101f" b"f300e0603551d0f0101ff040403020186301d0603551d0e04160414b3db48a4" b"f9a1c5d8ae3641cc1163696229bc4bc6" ) assert cert.signature_hash_algorithm is not None public_key = cert.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) public_key.verify( cert.signature, cert.tbs_certificate_bytes, ec.ECDSA(cert.signature_hash_algorithm), ) def test_load_ecdsa_no_named_curve(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) cert = _load_cert( os.path.join("x509", "custom", "ec_no_named_curve.pem"), x509.load_pem_x509_certificate, ) # This test can trigger three different value errors depending # on OpenSSL/BoringSSL and versions. Match on the text to ensure # we are getting the right error. with pytest.raises(ValueError, match="explicit parameters"): cert.public_key() def test_verify_directly_issued_by_ec(self): issuer_private_key = ec.generate_private_key(ec.SECP256R1()) subject_private_key = ec.generate_private_key(ec.SECP256R1()) ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert.verify_directly_issued_by(ca) def test_verify_directly_issued_by_ec_bad_sig(self): issuer_private_key = ec.generate_private_key(ec.SECP256R1()) subject_private_key = ec.generate_private_key(ec.SECP256R1()) ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert_bad_sig = _break_cert_sig(cert) with pytest.raises(InvalidSignature): cert_bad_sig.verify_directly_issued_by(ca) class TestECDSACertificateRequest: @pytest.mark.parametrize( ("path", "loader_func"), [ [ os.path.join("x509", "requests", "ec_sha256.pem"), x509.load_pem_x509_csr, ], [ os.path.join("x509", "requests", "ec_sha256.der"), x509.load_der_x509_csr, ], ], ) def test_load_ecdsa_certificate_request(self, path, loader_func, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) request = _load_cert(path, loader_func) assert isinstance(request.signature_hash_algorithm, hashes.SHA256) public_key = request.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) subject = request.subject assert isinstance(subject, x509.Name) assert list(subject) == [ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Austin"), ] def test_signature(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) request = _load_cert( os.path.join("x509", "requests", "ec_sha256.pem"), x509.load_pem_x509_csr, ) assert request.signature == binascii.unhexlify( b"306502302c1a9f7de8c1787332d2307a886b476a59f172b9b0e250262f3238b1" b"b45ee112bb6eb35b0fb56a123b9296eb212dffc302310094cf440c95c52827d5" b"56ae6d76500e3008255d47c29f7ee782ed7558e51bfd76aa45df6d999ed5c463" b"347fe2382d1751" ) def test_tbs_certrequest_bytes(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) request = _load_cert( os.path.join("x509", "requests", "ec_sha256.pem"), x509.load_pem_x509_csr, ) assert request.tbs_certrequest_bytes == binascii.unhexlify( b"3081d602010030573118301606035504030c0f63727970746f6772617068792" b"e696f310d300b060355040a0c0450794341310b300906035504061302555331" b"0e300c06035504080c055465786173310f300d06035504070c0641757374696" b"e3076301006072a8648ce3d020106052b8104002203620004de19b514c0b3c3" b"ae9b398ea3e26b5e816bdcf9102cad8f12fe02f9e4c9248724b39297ed7582e" b"04d8b32a551038d09086803a6d3fb91a1a1167ec02158b00efad39c9396462f" b"accff0ffaf7155812909d3726bd59fde001cff4bb9b2f5af8cbaa000" ) assert request.signature_hash_algorithm is not None public_key = request.public_key() assert isinstance(public_key, ec.EllipticCurvePublicKey) public_key.verify( request.signature, request.tbs_certrequest_bytes, ec.ECDSA(request.signature_hash_algorithm), ) class TestOtherCertificate: def test_unsupported_subject_public_key_info(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "unsupported_subject_public_key_info.pem" ), x509.load_pem_x509_certificate, ) with pytest.raises(ValueError): cert.public_key() def test_bad_time_in_validity(self, backend): with pytest.raises(ValueError, match="Validity::not_after"): _load_cert( os.path.join("x509", "badasn1time.pem"), x509.load_pem_x509_certificate, ) class TestNameAttribute: EXPECTED_TYPES: typing.ClassVar[ typing.List[typing.Tuple[x509.ObjectIdentifier, _ASN1Type]] ] = [ (NameOID.COMMON_NAME, _ASN1Type.UTF8String), (NameOID.COUNTRY_NAME, _ASN1Type.PrintableString), (NameOID.LOCALITY_NAME, _ASN1Type.UTF8String), (NameOID.STATE_OR_PROVINCE_NAME, _ASN1Type.UTF8String), (NameOID.STREET_ADDRESS, _ASN1Type.UTF8String), (NameOID.ORGANIZATION_NAME, _ASN1Type.UTF8String), (NameOID.ORGANIZATIONAL_UNIT_NAME, _ASN1Type.UTF8String), (NameOID.SERIAL_NUMBER, _ASN1Type.PrintableString), (NameOID.SURNAME, _ASN1Type.UTF8String), (NameOID.GIVEN_NAME, _ASN1Type.UTF8String), (NameOID.TITLE, _ASN1Type.UTF8String), (NameOID.GENERATION_QUALIFIER, _ASN1Type.UTF8String), (NameOID.X500_UNIQUE_IDENTIFIER, _ASN1Type.UTF8String), (NameOID.DN_QUALIFIER, _ASN1Type.PrintableString), (NameOID.PSEUDONYM, _ASN1Type.UTF8String), (NameOID.USER_ID, _ASN1Type.UTF8String), (NameOID.DOMAIN_COMPONENT, _ASN1Type.IA5String), (NameOID.EMAIL_ADDRESS, _ASN1Type.IA5String), (NameOID.JURISDICTION_COUNTRY_NAME, _ASN1Type.PrintableString), (NameOID.JURISDICTION_LOCALITY_NAME, _ASN1Type.UTF8String), (NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME, _ASN1Type.UTF8String), (NameOID.BUSINESS_CATEGORY, _ASN1Type.UTF8String), (NameOID.POSTAL_ADDRESS, _ASN1Type.UTF8String), (NameOID.POSTAL_CODE, _ASN1Type.UTF8String), ] def test_default_types(self): for oid, asn1_type in TestNameAttribute.EXPECTED_TYPES: na = x509.NameAttribute(oid, "US") assert na._type == asn1_type def test_alternate_type(self): na2 = x509.NameAttribute( NameOID.COMMON_NAME, "common", _ASN1Type.IA5String ) assert na2._type == _ASN1Type.IA5String def test_init_bad_oid(self): with pytest.raises(TypeError): x509.NameAttribute( None, # type:ignore[arg-type] "value", ) def test_init_bad_value(self): with pytest.raises(TypeError): x509.NameAttribute( x509.ObjectIdentifier("2.999.1"), b"bytes", ) def test_init_bitstring_not_bytes(self): with pytest.raises(TypeError): x509.NameAttribute( x509.ObjectIdentifier("2.5.4.45"), "str", _ASN1Type.BitString ) def test_init_bitstring_not_allowed_random_oid(self): # We only allow BitString type with X500_UNIQUE_IDENTIFIER with pytest.raises(TypeError): x509.NameAttribute( x509.NameOID.COMMON_NAME, b"ok", _ASN1Type.BitString ) def test_init_none_value(self): with pytest.raises(TypeError): x509.NameAttribute( NameOID.ORGANIZATION_NAME, None, # type:ignore[arg-type] ) def test_init_bad_length(self): with pytest.raises(ValueError): x509.NameAttribute(NameOID.COUNTRY_NAME, "United States") # unicode string of length 2, but > 2 bytes with pytest.raises(ValueError): x509.NameAttribute(NameOID.COUNTRY_NAME, "\U0001f37a\U0001f37a") with pytest.raises(ValueError): x509.NameAttribute(NameOID.JURISDICTION_COUNTRY_NAME, "Too Long") with pytest.raises(ValueError): x509.NameAttribute(NameOID.COMMON_NAME, "Too Long" * 10) with pytest.raises(ValueError): x509.NameAttribute(NameOID.COMMON_NAME, "") def test_invalid_type(self): with pytest.raises(TypeError): x509.NameAttribute( NameOID.COMMON_NAME, "common", "notanenum", # type:ignore[arg-type] ) def test_eq(self): assert x509.NameAttribute( x509.ObjectIdentifier("2.999.1"), "value" ) == x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value") def test_ne(self): assert x509.NameAttribute( x509.ObjectIdentifier("2.5.4.3"), "value" ) != x509.NameAttribute(x509.ObjectIdentifier("2.5.4.5"), "value") assert x509.NameAttribute( x509.ObjectIdentifier("2.999.1"), "value" ) != x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value2") assert ( x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value") != object() ) def test_repr(self): na = x509.NameAttribute(x509.ObjectIdentifier("2.5.4.3"), "value") assert repr(na) == ( ", value='value')>" ) def test_distinguished_name(self): # Escaping na = x509.NameAttribute(NameOID.COMMON_NAME, 'James "Jim" Smith, III') assert na.rfc4514_string() == r"CN=James \"Jim\" Smith\, III" na = x509.NameAttribute(NameOID.USER_ID, "# escape+,;\0this ") assert na.rfc4514_string() == r"UID=\# escape\+\,\;\00this\ " # Nonstandard attribute OID na = x509.NameAttribute(NameOID.BUSINESS_CATEGORY, "banking") assert na.rfc4514_string() == "2.5.4.15=banking" # non-utf8 attribute (bitstring with raw bytes) na = x509.NameAttribute( x509.ObjectIdentifier("2.5.4.45"), b"\x01\x02\x03\x04", _ASN1Type.BitString, ) assert na.rfc4514_string() == "2.5.4.45=#01020304" def test_distinguished_name_custom_attrs(self): name = x509.Name( [ x509.NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"), x509.NameAttribute(NameOID.COMMON_NAME, "Santa Claus"), ] ) assert name.rfc4514_string({}) == ( "CN=Santa Claus,1.2.840.113549.1.9.1=santa@north.pole" ) assert name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"}) == ( "CN=Santa Claus,E=santa@north.pole" ) assert name.rfc4514_string( {NameOID.COMMON_NAME: "CommonName", NameOID.EMAIL_ADDRESS: "E"} ) == ("CommonName=Santa Claus,E=santa@north.pole") def test_empty_name(self): assert x509.Name([]).rfc4514_string() == "" def test_empty_value(self): na = x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "") assert na.rfc4514_string() == r"ST=" class TestRelativeDistinguishedName: def test_init_empty(self): with pytest.raises(ValueError): x509.RelativeDistinguishedName([]) def test_init_not_nameattribute(self): with pytest.raises(TypeError): x509.RelativeDistinguishedName( ["not-a-NameAttribute"] # type:ignore[list-item] ) def test_init_duplicate_attribute(self): with pytest.raises(ValueError): x509.RelativeDistinguishedName( [ x509.NameAttribute( x509.ObjectIdentifier("2.999.1"), "val1" ), x509.NameAttribute( x509.ObjectIdentifier("2.999.1"), "val1" ), ] ) def test_hash(self): rdn1 = x509.RelativeDistinguishedName( [ x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), ] ) rdn2 = x509.RelativeDistinguishedName( [ x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), ] ) rdn3 = x509.RelativeDistinguishedName( [ x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value3"), ] ) assert hash(rdn1) == hash(rdn2) assert hash(rdn1) != hash(rdn3) def test_eq(self): rdn1 = x509.RelativeDistinguishedName( [ x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), ] ) rdn2 = x509.RelativeDistinguishedName( [ x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), ] ) assert rdn1 == rdn2 def test_ne(self): rdn1 = x509.RelativeDistinguishedName( [ x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2"), ] ) rdn2 = x509.RelativeDistinguishedName( [ x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value3"), ] ) assert rdn1 != rdn2 assert rdn1 != object() def test_iter_input(self): # Order must be preserved too attrs = [ x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1"), x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value2"), x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value3"), ] rdn = x509.RelativeDistinguishedName(iter(attrs)) assert list(rdn) == attrs assert list(rdn) == attrs def test_get_attributes_for_oid(self): oid = x509.ObjectIdentifier("2.999.1") attr = x509.NameAttribute(oid, "value1") rdn = x509.RelativeDistinguishedName([attr]) assert rdn.get_attributes_for_oid(oid) == [attr] assert rdn.get_attributes_for_oid(x509.ObjectIdentifier("1.2.3")) == [] class TestObjectIdentifier: def test_eq(self): oid1 = x509.ObjectIdentifier("2.999.1") oid2 = x509.ObjectIdentifier("2.999.1") assert oid1 == oid2 def test_ne(self): oid1 = x509.ObjectIdentifier("2.999.1") assert oid1 != x509.ObjectIdentifier("2.999.2") assert oid1 != object() def test_comparison(self): oid1 = x509.ObjectIdentifier("2.999.1") oid2 = x509.ObjectIdentifier("2.999.2") with pytest.raises(TypeError): oid1 < oid2 # type: ignore[operator] def test_repr(self): oid = x509.ObjectIdentifier("2.5.4.3") assert repr(oid) == "" oid = x509.ObjectIdentifier("2.999.1") assert repr(oid) == "" def test_name_property(self): oid = x509.ObjectIdentifier("2.5.4.3") assert oid._name == "commonName" oid = x509.ObjectIdentifier("2.999.1") assert oid._name == "Unknown OID" def test_too_short(self): with pytest.raises(ValueError): x509.ObjectIdentifier("1") def test_invalid_input(self): with pytest.raises(ValueError): x509.ObjectIdentifier("notavalidform") def test_invalid_node1(self): with pytest.raises(ValueError): x509.ObjectIdentifier("7.1.37") def test_invalid_node2(self): with pytest.raises(ValueError): x509.ObjectIdentifier("1.50.200") def test_valid(self): x509.ObjectIdentifier("0.35.200") x509.ObjectIdentifier("1.39.999") x509.ObjectIdentifier("2.5.29.3") x509.ObjectIdentifier("2.999.37.5.22.8") def test_oid_arc_too_large(self): with pytest.raises(ValueError): x509.ObjectIdentifier(f"2.25.{2**128 - 1}") class TestName: def test_eq(self): ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([ava1, ava2]) name2 = x509.Name( [ x509.RelativeDistinguishedName([ava1]), x509.RelativeDistinguishedName([ava2]), ] ) name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) name4 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) assert name1 == name2 assert name3 == name4 def test_ne(self): ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([ava1, ava2]) name2 = x509.Name([ava2, ava1]) name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) assert name1 != name2 assert name1 != name3 assert name1 != object() def test_hash(self): ava1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") ava2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([ava1, ava2]) name2 = x509.Name( [ x509.RelativeDistinguishedName([ava1]), x509.RelativeDistinguishedName([ava2]), ] ) name3 = x509.Name([ava2, ava1]) name4 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) name5 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) assert hash(name1) == hash(name2) assert hash(name1) != hash(name3) assert hash(name1) != hash(name4) assert hash(name4) == hash(name5) def test_iter_input(self): attrs = [ x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") ] name = x509.Name(iter(attrs)) assert list(name) == attrs assert list(name) == attrs def test_rdns(self): rdn1 = x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1") rdn2 = x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2") name1 = x509.Name([rdn1, rdn2]) assert name1.rdns == [ x509.RelativeDistinguishedName([rdn1]), x509.RelativeDistinguishedName([rdn2]), ] name2 = x509.Name([x509.RelativeDistinguishedName([rdn1, rdn2])]) assert name2.rdns == [x509.RelativeDistinguishedName([rdn1, rdn2])] @pytest.mark.parametrize( ("common_name", "org_name", "expected_repr"), [ ( "cryptography.io", "PyCA", "", ), ( "Certificación", "Certificación", "", ), ], ) def test_repr(self, common_name, org_name, expected_repr): name = x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, common_name), x509.NameAttribute(NameOID.ORGANIZATION_NAME, org_name), ] ) assert repr(name) == expected_repr def test_rfc4514_attribute_name(self): a = x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io") assert a.rfc4514_attribute_name == "CN" b = x509.NameAttribute(NameOID.PSEUDONYM, "cryptography.io") assert b.rfc4514_attribute_name == "2.5.4.65" def test_rfc4514_string(self): n = x509.Name( [ x509.RelativeDistinguishedName( [x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "net")] ), x509.RelativeDistinguishedName( [x509.NameAttribute(NameOID.DOMAIN_COMPONENT, "example")] ), x509.RelativeDistinguishedName( [ x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "Sales" ), x509.NameAttribute(NameOID.COMMON_NAME, "J. Smith"), ] ), ] ) assert n.rfc4514_string() == "OU=Sales+CN=J. Smith,DC=example,DC=net" def test_rfc4514_string_empty_values(self): n = x509.Name( [ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, ""), x509.NameAttribute(NameOID.LOCALITY_NAME, ""), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), ] ) assert n.rfc4514_string() == "CN=cryptography.io,O=PyCA,L=,ST=,C=US" def test_not_nameattribute(self): with pytest.raises(TypeError): x509.Name(["not-a-NameAttribute"]) # type: ignore[list-item] def test_bytes(self, backend): name = x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) assert name.public_bytes(backend) == binascii.unhexlify( b"30293118301606035504030c0f63727970746f6772617068792e696f310d300" b"b060355040a0c0450794341" ) def test_bitstring_encoding(self): name = x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io"), x509.NameAttribute( x509.ObjectIdentifier("2.5.4.45"), b"\x01\x02", _ASN1Type.BitString, ), ] ) assert name.public_bytes() == binascii.unhexlify( b"30273118301606035504030c0f63727970746f6772617068792e696f310b3" b"009060355042d03020102" ) def test_bmpstring_bytes(self, backend): # For this test we need an odd length string. BMPString is UCS-2 # encoded so it will always be even length and OpenSSL will error if # you pass an odd length string without encoding it properly first. name = x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io", _ASN1Type.BMPString, ), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) assert name.public_bytes(backend) == binascii.unhexlify( b"30383127302506035504031e1e00630072007900700074006f00670072006100" b"7000680079002e0069006f310d300b060355040a0c0450794341" ) def test_universalstring_bytes(self, backend): # UniversalString is UCS-4 name = x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io", _ASN1Type.UniversalString, ), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), ] ) assert name.public_bytes(backend) == binascii.unhexlify( b"30563145304306035504031c3c00000063000000720000007900000070000000" b"740000006f000000670000007200000061000000700000006800000079000000" b"2e000000690000006f310d300b060355040a0c0450794341" ) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) class TestEd25519Certificate: def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "ed25519", "root-ed25519.pem"), x509.load_pem_x509_certificate, ) # self-signed, so this will work public_key = cert.public_key() assert isinstance(public_key, ed25519.Ed25519PublicKey) assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED25519 public_key.verify(cert.signature, cert.tbs_certificate_bytes) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 9579446940964433301 assert cert.signature_hash_algorithm is None assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 assert cert.signature_algorithm_parameters is None def test_deepcopy(self, backend): cert = _load_cert( os.path.join("x509", "ed25519", "root-ed25519.pem"), x509.load_pem_x509_certificate, ) assert copy.deepcopy(cert) is cert def test_verify_directly_issued_by_ed25519(self, backend): issuer_private_key = ed25519.Ed25519PrivateKey.generate() subject_private_key = ed25519.Ed25519PrivateKey.generate() ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert.verify_directly_issued_by(ca) def test_verify_directly_issued_by_ed25519_bad_sig(self, backend): issuer_private_key = ed25519.Ed25519PrivateKey.generate() subject_private_key = ed25519.Ed25519PrivateKey.generate() ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert_bad_sig = _break_cert_sig(cert) with pytest.raises(InvalidSignature): cert_bad_sig.verify_directly_issued_by(ca) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) class TestEd448Certificate: def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "ed448", "root-ed448.pem"), x509.load_pem_x509_certificate, ) # self-signed, so this will work public_key = cert.public_key() assert isinstance(public_key, ed448.Ed448PublicKey) assert cert.public_key_algorithm_oid == PublicKeyAlgorithmOID.ED448 public_key.verify(cert.signature, cert.tbs_certificate_bytes) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 448 assert cert.signature_hash_algorithm is None assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED448 assert cert.signature_algorithm_parameters is None def test_verify_directly_issued_by_ed448(self, backend): issuer_private_key = ed448.Ed448PrivateKey.generate() subject_private_key = ed448.Ed448PrivateKey.generate() ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert.verify_directly_issued_by(ca) def test_verify_directly_issued_by_ed448_bad_sig(self, backend): issuer_private_key = ed448.Ed448PrivateKey.generate() subject_private_key = ed448.Ed448PrivateKey.generate() ca, cert = _generate_ca_and_leaf( issuer_private_key, subject_private_key ) cert_bad_sig = _break_cert_sig(cert) with pytest.raises(InvalidSignature): cert_bad_sig.verify_directly_issued_by(ca) @pytest.mark.supported( only_if=lambda backend: backend.dh_supported(), skip_message="DH not supported", ) class TestSignatureRejection: """Test if signing rejects DH keys properly.""" def load_key(self, backend): vector = load_vectors_from_file( os.path.join("asymmetric", "DH", "rfc3526.txt"), load_nist_vectors, )[1] p = int.from_bytes(binascii.unhexlify(vector["p"]), "big") params = dh.DHParameterNumbers(p, int(vector["g"])) param = params.parameters(backend) return param.generate_private_key() def test_crt_signing_check(self, rsa_key_2048: rsa.RSAPrivateKey, backend): issuer_private_key = self.load_key(backend) public_key = rsa_key_2048.public_key() not_valid_before = datetime.datetime(2020, 1, 1, 1, 1) not_valid_after = datetime.datetime(2050, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .serial_number(777) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .public_key(public_key) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) ) with pytest.raises(TypeError): builder.sign(issuer_private_key, hashes.SHA256(), backend) def test_csr_signing_check(self, backend): private_key = self.load_key(backend) builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(TypeError): builder.sign(private_key, hashes.SHA256(), backend) def test_crl_signing_check(self, backend): private_key = self.load_key(backend) last_time = ( datetime.datetime.now().replace(tzinfo=None).replace(microsecond=0) ) next_time = last_time builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "CA")]) ) .last_update(last_time) .next_update(next_time) ) with pytest.raises(TypeError): builder.sign(private_key, hashes.SHA256(), backend) def test_random_serial_number(monkeypatch): sample_data = os.urandom(20) def notrandom(size): assert size == len(sample_data) return sample_data monkeypatch.setattr(os, "urandom", notrandom) serial_number = x509.random_serial_number() assert serial_number == int.from_bytes(sample_data, "big") >> 1 assert serial_number.bit_length() < 160 class TestAttribute: def test_eq(self): attr1 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", ) attr2 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", ) assert attr1 == attr2 def test_ne(self): attr1 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", ) attr2 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", _ASN1Type.IA5String.value, ) attr3 = x509.Attribute( x509.oid.AttributeOID.UNSTRUCTURED_NAME, b"value", ) attr4 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"other value", ) assert attr1 != attr2 assert attr1 != attr3 assert attr1 != attr4 assert attr1 != object() def test_repr(self): attr1 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", ) assert repr(attr1) == ( ", value=b'value')>" ) def test_hash(self): attr1 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", _ASN1Type.UTF8String.value, ) attr2 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", _ASN1Type.UTF8String.value, ) attr3 = x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"value", _ASN1Type.IA5String.value, ) assert hash(attr1) == hash(attr2) assert hash(attr1) != hash(attr3) class TestAttributes: def test_no_attributes(self): attrs = x509.Attributes([]) assert len(attrs) == 0 def test_get_attribute_for_oid(self): attr_list = [ x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"nonsense", ), x509.Attribute( x509.oid.AttributeOID.UNSTRUCTURED_NAME, b"montessori", _ASN1Type.PrintableString.value, ), ] attrs = x509.Attributes(attr_list) attr = attrs.get_attribute_for_oid( x509.oid.AttributeOID.UNSTRUCTURED_NAME ) assert attr.oid == x509.oid.AttributeOID.UNSTRUCTURED_NAME assert attr.value == b"montessori" assert attr._type == _ASN1Type.PrintableString.value def test_indexing(self): attr_list = [ x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"nonsense", ), x509.Attribute( x509.oid.AttributeOID.UNSTRUCTURED_NAME, b"montessori", ), x509.Attribute( x509.ObjectIdentifier("2.999.2"), b"meaningless", ), x509.Attribute( x509.ObjectIdentifier("2.999.1"), b"meaningless", ), ] attrs = x509.Attributes(attr_list) assert len(attrs) == 4 assert list(attrs) == attr_list assert attrs[-1] == attrs[3] assert attrs[0:3:2] == [attrs[0], attrs[2]] def test_get_attribute_not_found(self): attrs = x509.Attributes([]) with pytest.raises(x509.AttributeNotFound) as exc: attrs.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) assert exc.value.oid == x509.oid.AttributeOID.CHALLENGE_PASSWORD def test_repr(self): attrs = x509.Attributes( [ x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"nonsense", ), ] ) assert repr(attrs) == ( ", value=b'nonsense')>])>" ) class TestRequestAttributes: def test_get_attribute_for_oid_challenge(self, backend): request = _load_cert( os.path.join("x509", "requests", "challenge.pem"), x509.load_pem_x509_csr, ) with pytest.warns(utils.DeprecatedIn36): assert ( request.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) == b"challenge me!" ) assert request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) == x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"challenge me!", ) def test_get_attribute_for_oid_multiple(self, backend): request = _load_cert( os.path.join("x509", "requests", "challenge-unstructured.pem"), x509.load_pem_x509_csr, ) with pytest.warns(utils.DeprecatedIn36): assert ( request.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) == b"beauty" ) with pytest.warns(utils.DeprecatedIn36): assert ( request.get_attribute_for_oid( x509.oid.AttributeOID.UNSTRUCTURED_NAME ) == b"an unstructured field" ) assert request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) == x509.Attribute( x509.oid.AttributeOID.CHALLENGE_PASSWORD, b"beauty", ) assert request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.UNSTRUCTURED_NAME ) == x509.Attribute( x509.oid.AttributeOID.UNSTRUCTURED_NAME, b"an unstructured field", ) def test_unsupported_asn1_type_in_attribute(self, backend): request = _load_cert( os.path.join("x509", "requests", "challenge-invalid.der"), x509.load_der_x509_csr, ) # Unsupported in the legacy path with pytest.raises(ValueError): with pytest.warns(utils.DeprecatedIn36): request.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) # supported in the new path where we just store the type and # return raw bytes attr = request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) assert attr._type == 2 def test_long_form_asn1_tag_in_attribute(self, backend): request = _load_cert( os.path.join("x509", "requests", "long-form-attribute.pem"), x509.load_pem_x509_csr, ) with pytest.raises(ValueError, match="Long-form"): request.attributes def test_challenge_multivalued(self, backend): """ We only support single-valued SETs in our X509 request attributes """ request = _load_cert( os.path.join("x509", "requests", "challenge-multi-valued.der"), x509.load_der_x509_csr, ) with pytest.raises(ValueError, match="Only single-valued"): with pytest.warns(utils.DeprecatedIn36): request.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) with pytest.raises(ValueError, match="Only single-valued"): request.attributes def test_no_challenge_password(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, ) with pytest.raises(x509.AttributeNotFound) as exc: with pytest.warns(utils.DeprecatedIn36): request.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) assert exc.value.oid == x509.oid.AttributeOID.CHALLENGE_PASSWORD with pytest.raises(x509.AttributeNotFound) as exc: request.attributes.get_attribute_for_oid( x509.oid.AttributeOID.CHALLENGE_PASSWORD ) assert exc.value.oid == x509.oid.AttributeOID.CHALLENGE_PASSWORD def test_no_attributes(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, ) assert len(request.attributes) == 0 def test_load_pem_x509_certificates(): with pytest.raises(ValueError): x509.load_pem_x509_certificates(b"") certs = load_vectors_from_file( filename=os.path.join("x509", "cryptography.io.chain.pem"), loader=lambda pemfile: x509.load_pem_x509_certificates(pemfile.read()), mode="rb", ) assert len(certs) == 2 assert certs[0].serial_number == 16160 assert certs[1].serial_number == 146039 certs = load_vectors_from_file( filename=os.path.join( "x509", "cryptography.io.chain_with_garbage.pem" ), loader=lambda pemfile: x509.load_pem_x509_certificates(pemfile.read()), mode="rb", ) assert len(certs) == 2 assert certs[0].serial_number == 16160 assert certs[1].serial_number == 146039 cryptography-43.0.0/tests/x509/test_x509_crlbuilder.py010064400017510000177000001050571464676315000207350ustar 00000000000000# 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. import datetime import pytest from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( ec, ed448, ed25519, padding, rsa, ) from cryptography.x509.oid import ( AuthorityInformationAccessOID, NameOID, SignatureAlgorithmOID, ) from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 from ..hazmat.primitives.test_ec import _skip_curve_unsupported from ..hazmat.primitives.test_rsa import rsa_key_512, rsa_key_2048 from .test_x509 import DummyExtension # Make ruff happy since we're importing fixtures that pytest patches in as # func args __all__ = ["rsa_key_512", "rsa_key_2048"] class TestCertificateRevocationListBuilder: def test_issuer_name_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.issuer_name("notanx509name") # type:ignore[arg-type] def test_set_issuer_name_twice(self): builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) with pytest.raises(ValueError): builder.issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) def test_aware_last_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): tz = datetime.timezone(datetime.timedelta(hours=-8)) last_time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_last = datetime.datetime(2012, 1, 17, 6, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_time) .next_update(next_time) ) crl = builder.sign(private_key, hashes.SHA256(), backend) with pytest.warns(utils.DeprecatedIn42): assert crl.last_update == utc_last assert crl.last_update_utc == utc_last.replace( tzinfo=datetime.timezone.utc ) def test_last_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.last_update("notadatetime") # type:ignore[arg-type] def test_last_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(ValueError): builder.last_update(datetime.datetime(1940, 8, 10)) def test_set_last_update_twice(self): builder = x509.CertificateRevocationListBuilder().last_update( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) def test_aware_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): tz = datetime.timezone(datetime.timedelta(hours=-8)) next_time = datetime.datetime(2022, 1, 16, 22, 43, tzinfo=tz) utc_next = datetime.datetime(2022, 1, 17, 6, 43) last_time = datetime.datetime(2012, 1, 17, 6, 43) private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_time) .next_update(next_time) ) crl = builder.sign(private_key, hashes.SHA256(), backend) with pytest.warns(utils.DeprecatedIn42): assert crl.next_update == utc_next assert crl.next_update_utc == utc_next.replace( tzinfo=datetime.timezone.utc ) def test_next_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.next_update("notadatetime") # type:ignore[arg-type] def test_next_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(ValueError): builder.next_update(datetime.datetime(1940, 8, 10)) def test_set_next_update_twice(self): builder = x509.CertificateRevocationListBuilder().next_update( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.next_update(datetime.datetime(2002, 1, 1, 12, 1)) def test_last_update_after_next_update(self): builder = x509.CertificateRevocationListBuilder() builder = builder.next_update(datetime.datetime(2002, 1, 1, 12, 1)) with pytest.raises(ValueError): builder.last_update(datetime.datetime(2003, 1, 1, 12, 1)) def test_next_update_after_last_update(self): builder = x509.CertificateRevocationListBuilder() builder = builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) with pytest.raises(ValueError): builder.next_update(datetime.datetime(2001, 1, 1, 12, 1)) def test_add_extension_checks_for_duplicates(self): builder = x509.CertificateRevocationListBuilder().add_extension( x509.CRLNumber(1), False ) with pytest.raises(ValueError): builder.add_extension(x509.CRLNumber(2), False) def test_add_invalid_extension(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.add_extension(object(), False) # type:ignore[arg-type] def test_add_invalid_revoked_certificate(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.add_revoked_certificate(object()) # type:ignore[arg-type] def test_no_issuer_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .last_update(datetime.datetime(2002, 1, 1, 12, 1)) .next_update(datetime.datetime(2030, 1, 1, 12, 1)) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_no_last_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .next_update(datetime.datetime(2030, 1, 1, 12, 1)) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_no_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .last_update(datetime.datetime(2030, 1, 1, 12, 1)) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) def test_sign_invalid_padding( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) with pytest.raises(TypeError): builder.sign( rsa_key_2048, hashes.SHA256(), rsa_padding=b"notapadding", # type: ignore[arg-type] ) eckey = ec.generate_private_key(ec.SECP256R1()) with pytest.raises(TypeError): builder.sign( eckey, hashes.SHA256(), rsa_padding=padding.PKCS1v15() ) def test_sign_empty_list(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 with pytest.warns(utils.DeprecatedIn42): assert crl.last_update == last_update assert crl.next_update == next_update assert crl.last_update_utc == last_update.replace( tzinfo=datetime.timezone.utc ) assert crl.next_update_utc == next_update.replace( tzinfo=datetime.timezone.utc ) def test_sign_pss(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) pss = padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH, ) crl = builder.sign(private_key, hashes.SHA256(), rsa_padding=pss) assert len(crl) == 0 assert isinstance(crl.signature_algorithm_parameters, padding.PSS) assert crl.signature_algorithm_parameters._salt_length == 32 private_key.public_key().verify( crl.signature, crl.tbs_certlist_bytes, crl.signature_algorithm_parameters, hashes.SHA256(), ) @pytest.mark.parametrize( "extension", [ x509.CRLNumber(13), x509.DeltaCRLIndicator(12345678901234567890), x509.AuthorityKeyIdentifier( b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" b"\xcbY", None, None, ), x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.DNSName("cryptography.io"), ) ] ), x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")] ), ], ) def test_sign_extensions( self, rsa_key_2048: rsa.RSAPrivateKey, backend, extension ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_extension(extension, False) ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 1 ext = crl.extensions.get_extension_for_class(type(extension)) assert ext.critical is False assert ext.value == extension def test_sign_multiple_extensions_critical( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")] ) crl_number = x509.CRLNumber(13) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_extension(crl_number, False) .add_extension(ian, True) ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 2 ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber) assert ext1.critical is False assert ext1.value == crl_number ext2 = crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ) assert ext2.critical is True assert ext2.value == ian def test_freshestcrl_extension( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) freshest = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://d.om/delta")], None, None, None, ) ] ) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_extension(freshest, False) ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 1 ext1 = crl.extensions.get_extension_for_class(x509.FreshestCRL) assert ext1.critical is False assert isinstance(ext1.value, x509.FreshestCRL) assert isinstance(ext1.value[0], x509.DistributionPoint) assert ext1.value[0].full_name is not None uri = ext1.value[0].full_name[0] assert isinstance(uri, x509.UniformResourceIdentifier) assert uri.value == "http://d.om/delta" def test_add_unsupported_extension( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_extension(DummyExtension(), False) ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) def test_add_unsupported_entry_extension( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_revoked_certificate( x509.RevokedCertificateBuilder() .serial_number(1234) .revocation_date( datetime.datetime.now(datetime.timezone.utc).replace( tzinfo=None ) ) .add_extension(DummyExtension(), critical=False) .build() ) ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) def test_sign_rsa_key_too_small( self, rsa_key_512: rsa.RSAPrivateKey, backend ): private_key = rsa_key_512 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) def test_sign_with_invalid_hash( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) with pytest.raises(TypeError): builder.sign( private_key, object(), # type: ignore[arg-type] backend, ) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_sign_with_invalid_hash_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) with pytest.raises(TypeError): builder.sign( private_key, object(), # type:ignore[arg-type] backend, ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_sign_with_invalid_hash_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) ) with pytest.raises(TypeError): builder.sign( private_key, object(), # type:ignore[arg-type] backend, ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Requires OpenSSL with DSA support", ) def test_sign_dsa_key(self, backend): private_key = DSA_KEY_2048.private_key(backend) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() .serial_number(2) .revocation_date(datetime.datetime(2012, 1, 1, 1, 1)) .add_extension(invalidity_date, False) .build(backend) ) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_revoked_certificate(revoked_cert0) .add_extension(ian, False) ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert ( crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ).value == ian ) assert crl[0].serial_number == revoked_cert0.serial_number with pytest.warns(utils.DeprecatedIn42): assert crl[0].revocation_date == revoked_cert0.revocation_date assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date def test_sign_ec_key(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() .serial_number(2) .revocation_date(datetime.datetime(2012, 1, 1, 1, 1)) .add_extension(invalidity_date, False) .build(backend) ) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_revoked_certificate(revoked_cert0) .add_extension(ian, False) ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert ( crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ).value == ian ) assert crl[0].serial_number == revoked_cert0.serial_number with pytest.warns(utils.DeprecatedIn42): assert crl[0].revocation_date == revoked_cert0.revocation_date assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_sign_ed25519_key(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() .serial_number(2) .revocation_date(datetime.datetime(2012, 1, 1, 1, 1)) .add_extension(invalidity_date, False) .build(backend) ) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_revoked_certificate(revoked_cert0) .add_extension(ian, False) ) crl = builder.sign(private_key, None, backend) assert crl.signature_hash_algorithm is None assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 assert ( crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ).value == ian ) assert crl[0].serial_number == revoked_cert0.serial_number with pytest.warns(utils.DeprecatedIn42): assert crl[0].revocation_date == revoked_cert0.revocation_date assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_sign_ed448_key(self, backend): private_key = ed448.Ed448PrivateKey.generate() invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")] ) revoked_cert0 = ( x509.RevokedCertificateBuilder() .serial_number(2) .revocation_date(datetime.datetime(2012, 1, 1, 1, 1)) .add_extension(invalidity_date, False) .build(backend) ) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_revoked_certificate(revoked_cert0) .add_extension(ian, False) ) crl = builder.sign(private_key, None, backend) assert crl.signature_hash_algorithm is None assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED448 assert ( crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ).value == ian ) assert crl[0].serial_number == revoked_cert0.serial_number with pytest.warns(utils.DeprecatedIn42): assert crl[0].revocation_date == revoked_cert0.revocation_date assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date def test_dsa_key_sign_md5(self, backend): private_key = DSA_KEY_2048.private_key(backend) last_time = datetime.datetime(2012, 1, 16, 22, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_time) .next_update(next_time) ) with pytest.raises(UnsupportedAlgorithm): builder.sign( private_key, hashes.MD5(), # type: ignore[arg-type] backend, ) def test_ec_key_sign_md5(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = EC_KEY_SECP256R1.private_key(backend) last_time = datetime.datetime(2012, 1, 16, 22, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_time) .next_update(next_time) ) with pytest.raises(UnsupportedAlgorithm): builder.sign( private_key, hashes.MD5(), # type: ignore[arg-type] backend, ) def test_sign_with_revoked_certificates( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): private_key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) revoked_cert0 = ( x509.RevokedCertificateBuilder() .serial_number(38) .revocation_date(datetime.datetime(2011, 1, 1, 1, 1)) .build(backend) ) revoked_cert1 = ( x509.RevokedCertificateBuilder() .serial_number(2) .revocation_date(datetime.datetime(2012, 1, 1, 1, 1)) .add_extension(invalidity_date, False) .add_extension( x509.CRLReason(x509.ReasonFlags.ca_compromise), False ) .build(backend) ) ci = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) revoked_cert2 = ( x509.RevokedCertificateBuilder() .serial_number(40) .revocation_date(datetime.datetime(2011, 1, 1, 1, 1)) .add_extension(ci, False) .build(backend) ) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_revoked_certificate(revoked_cert0) .add_revoked_certificate(revoked_cert1) .add_revoked_certificate(revoked_cert2) ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 3 with pytest.warns(utils.DeprecatedIn42): assert crl.last_update == last_update assert crl.next_update == next_update assert crl.last_update_utc == last_update.replace( tzinfo=datetime.timezone.utc ) assert crl.next_update_utc == next_update.replace( tzinfo=datetime.timezone.utc ) assert crl[0].serial_number == revoked_cert0.serial_number with pytest.warns(utils.DeprecatedIn42): assert crl[0].revocation_date == revoked_cert0.revocation_date assert crl[0].revocation_date_utc == revoked_cert0.revocation_date_utc assert len(crl[0].extensions) == 0 assert crl[1].serial_number == revoked_cert1.serial_number with pytest.warns(utils.DeprecatedIn42): assert crl[1].revocation_date == revoked_cert1.revocation_date assert crl[1].revocation_date_utc == revoked_cert1.revocation_date_utc assert len(crl[1].extensions) == 2 ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date assert ( crl[2] .extensions.get_extension_for_class(x509.CertificateIssuer) .value == ci ) cryptography-43.0.0/tests/x509/test_x509_ext.py010064400017510000177000006545121464676315000174130ustar 00000000000000# 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. import binascii import datetime import ipaddress import os import typing import pretend import pytest from cryptography import x509 from cryptography.hazmat._oid import _OID_NAMES from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.x509 import ( DNSName, NameConstraints, SubjectAlternativeName, ocsp, ) from cryptography.x509.extensions import ( ExtensionType, _key_identifier_from_public_key, ) from cryptography.x509.oid import ( AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, SubjectInformationAccessOID, ) from ..hazmat.primitives.test_ec import _skip_curve_unsupported from ..hazmat.primitives.test_rsa import rsa_key_2048 from ..utils import load_vectors_from_file from .test_x509 import _load_cert # Make ruff happy since we're importing fixtures that pytest patches in as # func args __all__ = ["rsa_key_2048"] def _make_certbuilder(private_key): name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "example.org")]) return ( x509.CertificateBuilder() .subject_name(name) .issuer_name(name) .public_key(private_key.public_key()) .serial_number(777) .not_valid_before(datetime.datetime(1999, 1, 1)) .not_valid_after(datetime.datetime(2020, 1, 1)) ) class TestExtension: def test_not_an_oid(self): bc = x509.BasicConstraints(ca=False, path_length=None) with pytest.raises(TypeError): x509.Extension("notanoid", True, bc) # type:ignore[arg-type] def test_critical_not_a_bool(self): bc = x509.BasicConstraints(ca=False, path_length=None) with pytest.raises(TypeError): x509.Extension( ExtensionOID.BASIC_CONSTRAINTS, "notabool", # type:ignore[arg-type] bc, ) def test_repr(self): bc = x509.BasicConstraints(ca=False, path_length=None) ext = x509.Extension(ExtensionOID.BASIC_CONSTRAINTS, True, bc) assert repr(ext) == ( ", critical=True, value=)>" ) def test_eq(self): ext1 = x509.Extension( x509.ObjectIdentifier("1.2.3.4"), False, x509.BasicConstraints(ca=False, path_length=None), ) ext2 = x509.Extension( x509.ObjectIdentifier("1.2.3.4"), False, x509.BasicConstraints(ca=False, path_length=None), ) assert ext1 == ext2 def test_ne(self): ext1 = x509.Extension( x509.ObjectIdentifier("1.2.3.4"), False, x509.BasicConstraints(ca=False, path_length=None), ) ext2 = x509.Extension( x509.ObjectIdentifier("1.2.3.5"), False, x509.BasicConstraints(ca=False, path_length=None), ) ext3 = x509.Extension( x509.ObjectIdentifier("1.2.3.4"), True, x509.BasicConstraints(ca=False, path_length=None), ) ext4 = x509.Extension( x509.ObjectIdentifier("1.2.3.4"), False, x509.BasicConstraints(ca=True, path_length=None), ) assert ext1 != ext2 assert ext1 != ext3 assert ext1 != ext4 assert ext1 != object() def test_hash(self): ext1 = x509.Extension( ExtensionOID.BASIC_CONSTRAINTS, False, x509.BasicConstraints(ca=False, path_length=None), ) ext2 = x509.Extension( ExtensionOID.BASIC_CONSTRAINTS, False, x509.BasicConstraints(ca=False, path_length=None), ) ext3 = x509.Extension( ExtensionOID.BASIC_CONSTRAINTS, False, x509.BasicConstraints(ca=True, path_length=None), ) assert hash(ext1) == hash(ext2) assert hash(ext1) != hash(ext3) class TestTLSFeature: def test_not_enum_type(self): with pytest.raises(TypeError): x509.TLSFeature([3]) # type:ignore[list-item] def test_empty_list(self): with pytest.raises(TypeError): x509.TLSFeature([]) def test_repr(self): ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) assert repr(ext1) == ( "])>" ) def test_eq(self): ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request]) assert ext1 == ext2 def test_ne(self): ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request_v2]) ext3 = x509.TLSFeature( [ x509.TLSFeatureType.status_request, x509.TLSFeatureType.status_request_v2, ] ) assert ext1 != ext2 assert ext1 != ext3 assert ext1 != object() def test_hash(self): ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request]) ext3 = x509.TLSFeature( [ x509.TLSFeatureType.status_request, x509.TLSFeatureType.status_request_v2, ] ) assert hash(ext1) == hash(ext2) assert hash(ext1) != hash(ext3) def test_iter(self): ext1_features = [x509.TLSFeatureType.status_request] ext1 = x509.TLSFeature(ext1_features) assert len(ext1) == 1 assert list(ext1) == ext1_features ext2_features = [ x509.TLSFeatureType.status_request, x509.TLSFeatureType.status_request_v2, ] ext2 = x509.TLSFeature(ext2_features) assert len(ext2) == 2 assert list(ext2) == ext2_features def test_indexing(self): ext = x509.TLSFeature( [ x509.TLSFeatureType.status_request, x509.TLSFeatureType.status_request_v2, ] ) assert ext[-1] == ext[1] assert ext[0] == x509.TLSFeatureType.status_request def test_public_bytes(self): ext1 = x509.TLSFeature([x509.TLSFeatureType.status_request]) assert ext1.public_bytes() == b"\x30\x03\x02\x01\x05" ext2 = x509.TLSFeature([x509.TLSFeatureType.status_request_v2]) assert ext2.public_bytes() == b"\x30\x03\x02\x01\x11" class TestUnrecognizedExtension: def test_invalid_oid(self): with pytest.raises(TypeError): x509.UnrecognizedExtension( "notanoid", # type:ignore[arg-type] b"somedata", ) def test_eq(self): ext1 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" ) ext2 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" ) assert ext1 == ext2 def test_ne(self): ext1 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" ) ext2 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x02" ) ext3 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" ) assert ext1 != ext2 assert ext1 != ext3 assert ext1 != object() def test_repr(self): ext1 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" ) assert repr(ext1) == ( ", value=b'\\x03\\x02\\x01')>" ) def test_hash(self): ext1 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" ) ext2 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" ) ext3 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" ) assert hash(ext1) == hash(ext2) assert hash(ext1) != hash(ext3) def test_public_bytes(self): ext1 = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" ) assert ext1.public_bytes() == b"\x03\x02\x01" # The following creates a BasicConstraints extension with an invalid # value. The serialization code should still handle it correctly by # special-casing UnrecognizedExtension. ext2 = x509.UnrecognizedExtension( x509.oid.ExtensionOID.BASIC_CONSTRAINTS, b"\x03\x02\x01" ) assert ext2.public_bytes() == b"\x03\x02\x01" class TestCertificateIssuer: def test_iter_names(self): ci = x509.CertificateIssuer( [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(ci) == 2 assert list(ci) == [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), ] def test_indexing(self): ci = x509.CertificateIssuer( [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), x509.DNSName("another.local"), x509.RFC822Name("email@another.local"), x509.UniformResourceIdentifier("http://another.local"), ] ) assert ci[-1] == ci[4] assert ci[2:6:2] == [ci[2], ci[4]] def test_eq(self): ci1 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) ci2 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) assert ci1 == ci2 def test_ne(self): ci1 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) ci2 = x509.CertificateIssuer([x509.DNSName("somethingelse.tld")]) assert ci1 != ci2 assert ci1 != object() def test_repr(self): ci = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) assert repr(ci) == ( "])>)>" ) def test_get_values_for_type(self): ci = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) names = ci.get_values_for_type(x509.DNSName) assert names == ["cryptography.io"] def test_hash(self): ci1 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) ci2 = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) ci3 = x509.CertificateIssuer( [x509.UniformResourceIdentifier("http://something")] ) assert hash(ci1) == hash(ci2) assert hash(ci1) != hash(ci3) def test_public_bytes(self): ext = x509.CertificateIssuer([x509.DNSName("cryptography.io")]) assert ext.public_bytes() == b"0\x11\x82\x0fcryptography.io" class TestCRLReason: def test_invalid_reason_flags(self): with pytest.raises(TypeError): x509.CRLReason("notareason") # type:ignore[arg-type] def test_eq(self): reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) reason2 = x509.CRLReason(x509.ReasonFlags.unspecified) assert reason1 == reason2 def test_ne(self): reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) reason2 = x509.CRLReason(x509.ReasonFlags.ca_compromise) assert reason1 != reason2 assert reason1 != object() def test_hash(self): reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) reason2 = x509.CRLReason(x509.ReasonFlags.unspecified) reason3 = x509.CRLReason(x509.ReasonFlags.ca_compromise) assert hash(reason1) == hash(reason2) assert hash(reason1) != hash(reason3) def test_repr(self): reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) assert repr(reason1) == ("") def test_public_bytes(self): ext = x509.CRLReason(x509.ReasonFlags.ca_compromise) assert ext.public_bytes() == b"\n\x01\x02" class TestDeltaCRLIndicator: def test_not_int(self): with pytest.raises(TypeError): x509.DeltaCRLIndicator("notanint") # type:ignore[arg-type] def test_eq(self): delta1 = x509.DeltaCRLIndicator(1) delta2 = x509.DeltaCRLIndicator(1) assert delta1 == delta2 def test_ne(self): delta1 = x509.DeltaCRLIndicator(1) delta2 = x509.DeltaCRLIndicator(2) assert delta1 != delta2 assert delta1 != object() def test_repr(self): delta1 = x509.DeltaCRLIndicator(2) assert repr(delta1) == ("") def test_hash(self): delta1 = x509.DeltaCRLIndicator(1) delta2 = x509.DeltaCRLIndicator(1) delta3 = x509.DeltaCRLIndicator(2) assert hash(delta1) == hash(delta2) assert hash(delta1) != hash(delta3) def test_public_bytes(self): ext = x509.DeltaCRLIndicator(2) assert ext.public_bytes() == b"\x02\x01\x02" class TestInvalidityDate: def test_invalid_invalidity_date(self): with pytest.raises(TypeError): x509.InvalidityDate("notadate") # type:ignore[arg-type] def test_eq(self): invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) assert invalid1 == invalid2 def test_ne(self): invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2)) assert invalid1 != invalid2 assert invalid1 != object() def test_repr(self): invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) assert repr(invalid1) == ( "" ) def test_hash(self): invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) invalid3 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2)) assert hash(invalid1) == hash(invalid2) assert hash(invalid1) != hash(invalid3) def test_public_bytes(self): ext = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) assert ext.public_bytes() == b"\x18\x0f20150101010100Z" def test_timezone_aware_api(self): naive_date = datetime.datetime(2015, 1, 1, 1, 1) ext_naive = x509.InvalidityDate(invalidity_date=naive_date) assert ext_naive.invalidity_date_utc == datetime.datetime( 2015, 1, 1, 1, 1, tzinfo=datetime.timezone.utc ) tz_aware_date = datetime.datetime( 2015, 1, 1, 1, 1, tzinfo=datetime.timezone(datetime.timedelta(hours=-8)), ) ext_aware = x509.InvalidityDate(invalidity_date=tz_aware_date) assert ext_aware.invalidity_date_utc == datetime.datetime( 2015, 1, 1, 9, 1, tzinfo=datetime.timezone.utc ) class TestNoticeReference: def test_notice_numbers_not_all_int(self): with pytest.raises(TypeError): x509.NoticeReference( "org", [1, 2, "three"], # type:ignore[list-item] ) def test_notice_numbers_none(self): with pytest.raises(TypeError): x509.NoticeReference("org", None) # type:ignore[arg-type] def test_iter_input(self): numbers = [1, 3, 4] nr = x509.NoticeReference("org", iter(numbers)) assert list(nr.notice_numbers) == numbers def test_repr(self): nr = x509.NoticeReference("org", [1, 3, 4]) assert repr(nr) == ( "" ) def test_eq(self): nr = x509.NoticeReference("org", [1, 2]) nr2 = x509.NoticeReference("org", [1, 2]) assert nr == nr2 def test_ne(self): nr = x509.NoticeReference("org", [1, 2]) nr2 = x509.NoticeReference("org", [1]) nr3 = x509.NoticeReference(None, [1, 2]) assert nr != nr2 assert nr != nr3 assert nr != object() def test_hash(self): nr = x509.NoticeReference("org", [1, 2]) nr2 = x509.NoticeReference("org", [1, 2]) nr3 = x509.NoticeReference(None, [1, 2]) assert hash(nr) == hash(nr2) assert hash(nr) != hash(nr3) class TestUserNotice: def test_notice_reference_invalid(self): with pytest.raises(TypeError): x509.UserNotice("invalid", None) # type:ignore[arg-type] def test_notice_reference_none(self): un = x509.UserNotice(None, "text") assert un.notice_reference is None assert un.explicit_text == "text" def test_repr(self): un = x509.UserNotice(x509.NoticeReference("org", [1]), "text") assert repr(un) == ( ", explicit_text='text')>" ) def test_eq(self): nr = x509.NoticeReference("org", [1, 2]) nr2 = x509.NoticeReference("org", [1, 2]) un = x509.UserNotice(nr, "text") un2 = x509.UserNotice(nr2, "text") assert un == un2 def test_ne(self): nr = x509.NoticeReference("org", [1, 2]) nr2 = x509.NoticeReference("org", [1]) un = x509.UserNotice(nr, "text") un2 = x509.UserNotice(nr2, "text") un3 = x509.UserNotice(nr, "text3") assert un != un2 assert un != un3 assert un != object() def test_hash(self): nr = x509.NoticeReference("org", [1, 2]) nr2 = x509.NoticeReference("org", [1, 2]) un = x509.UserNotice(nr, "text") un2 = x509.UserNotice(nr2, "text") un3 = x509.UserNotice(None, "text") assert hash(un) == hash(un2) assert hash(un) != hash(un3) class TestPolicyInformation: def test_invalid_policy_identifier(self): with pytest.raises(TypeError): x509.PolicyInformation("notanoid", None) # type:ignore[arg-type] def test_none_policy_qualifiers(self): pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") assert pi.policy_qualifiers is None def test_policy_qualifiers(self): pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") assert pi.policy_qualifiers == pq def test_invalid_policy_identifiers(self): with pytest.raises(TypeError): x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), [1, 2], # type:ignore[list-item] ) def test_iter_input(self): qual = ["foo", "bar"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), iter(qual)) assert pi.policy_qualifiers is not None assert list(pi.policy_qualifiers) == qual def test_repr(self): pq: typing.List[typing.Union[str, x509.UserNotice]] = [ "string", x509.UserNotice(None, "hi"), ] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) assert repr(pi) == ( ", policy_qualifiers=['string', ])>" ) def test_eq(self): pi = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string", x509.UserNotice(None, "hi")], ) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string", x509.UserNotice(None, "hi")], ) assert pi == pi2 def test_ne(self): pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string2"] ) pi3 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3.4"), ["string"] ) assert pi != pi2 assert pi != pi3 assert pi != object() def test_hash(self): pi = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string", x509.UserNotice(None, "hi")], ) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string", x509.UserNotice(None, "hi")], ) pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) assert hash(pi) == hash(pi2) assert hash(pi) != hash(pi3) class TestCertificatePolicies: def test_invalid_policies(self): pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) with pytest.raises(TypeError): x509.CertificatePolicies([1, pi]) # type:ignore[list-item] def test_iter_len(self): pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) cp = x509.CertificatePolicies([pi]) assert len(cp) == 1 for policyinfo in cp: assert policyinfo == pi def test_iter_input(self): policies = [ x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) ] cp = x509.CertificatePolicies(iter(policies)) assert list(cp) == policies def test_repr(self): pq = ["string"] pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) cp = x509.CertificatePolicies([pi]) assert repr(cp) == ( ", policy_qualifi" "ers=['string'])>])>" ) def test_eq(self): pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) cp = x509.CertificatePolicies([pi]) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string"] ) cp2 = x509.CertificatePolicies([pi2]) assert cp == cp2 def test_ne(self): pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) cp = x509.CertificatePolicies([pi]) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string2"] ) cp2 = x509.CertificatePolicies([pi2]) assert cp != cp2 assert cp != object() def test_indexing(self): pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["test"]) pi2 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.4"), ["test"]) pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.5"), ["test"]) pi4 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.6"), ["test"]) pi5 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.7"), ["test"]) cp = x509.CertificatePolicies([pi, pi2, pi3, pi4, pi5]) assert cp[-1] == cp[4] assert cp[2:6:2] == [cp[2], cp[4]] def test_long_oid(self, backend): """ Test that parsing a CertificatePolicies ext with a very long OID succeeds. """ cert = _load_cert( os.path.join("x509", "bigoid.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.CertificatePolicies) oid = x509.ObjectIdentifier( "1.3.6.1.4.1.311.21.8.8950086.10656446.2706058" ".12775672.480128.147.13466065.13029902" ) assert ext.value[0].policy_identifier == oid def test_hash(self): pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), ["string"]) cp = x509.CertificatePolicies([pi]) pi2 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), ["string"] ) cp2 = x509.CertificatePolicies([pi2]) pi3 = x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), [x509.UserNotice(None, "text")] ) cp3 = x509.CertificatePolicies([pi3]) assert hash(cp) == hash(cp2) assert hash(cp) != hash(cp3) class TestCertificatePoliciesExtension: def test_cps_uri_policy_qualifier(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cp_cps_uri.pem"), x509.load_pem_x509_certificate, ) cp = cert.extensions.get_extension_for_oid( ExtensionOID.CERTIFICATE_POLICIES ).value assert cp == x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), ["http://other.com/cps"], ) ] ) def test_user_notice_with_notice_reference(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "cp_user_notice_with_notice_reference.pem" ), x509.load_pem_x509_certificate, ) cp = cert.extensions.get_extension_for_oid( ExtensionOID.CERTIFICATE_POLICIES ).value assert cp == x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ "http://example.com/cps", "http://other.com/cps", x509.UserNotice( x509.NoticeReference("my org", [1, 2, 3, 4]), "thing", ), ], ) ] ) def test_user_notice_with_explicit_text(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "cp_user_notice_with_explicit_text.pem" ), x509.load_pem_x509_certificate, ) cp = cert.extensions.get_extension_for_oid( ExtensionOID.CERTIFICATE_POLICIES ).value assert cp == x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [x509.UserNotice(None, "thing")], ) ] ) def test_user_notice_no_explicit_text(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "cp_user_notice_no_explicit_text.pem" ), x509.load_pem_x509_certificate, ) cp = cert.extensions.get_extension_for_oid( ExtensionOID.CERTIFICATE_POLICIES ).value assert cp == x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ x509.UserNotice( x509.NoticeReference("my org", [1, 2, 3, 4]), None ) ], ) ] ) def test_non_ascii_qualifier( self, rsa_key_2048: rsa.RSAPrivateKey, backend ): issuer_private_key = rsa_key_2048 subject_private_key = rsa_key_2048 not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) builder = ( x509.CertificateBuilder() .subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) .not_valid_before(not_valid_before) .not_valid_after(not_valid_after) .public_key(subject_private_key.public_key()) .serial_number(123) .add_extension( x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("1.2.3"), "🤓" ) ] ), critical=False, ) ) with pytest.raises(ValueError, match="Qualifier"): builder.sign(issuer_private_key, hashes.SHA256(), backend) def test_public_bytes(self): ext = x509.CertificatePolicies( [ x509.PolicyInformation( x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), [ x509.UserNotice( x509.NoticeReference("my org", [1, 2, 3, 4]), None ) ], ) ] ) assert ( ext.public_bytes() == b"0705\x06\x0b`\x86H\x01\xe09\x01\x02\x03\x04\x010&0$\x06\x08+" b"\x06\x01\x05\x05\x07\x02\x020\x180\x16\x0c\x06my org0\x0c\x02" b"\x01\x01\x02\x01\x02\x02\x01\x03\x02\x01\x04" ) class TestKeyUsage: def test_key_agreement_false_encipher_decipher_true(self): with pytest.raises(ValueError): x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=True, decipher_only=False, ) with pytest.raises(ValueError): x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=True, decipher_only=True, ) with pytest.raises(ValueError): x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ) def test_properties_key_agreement_true(self): ku = x509.KeyUsage( digital_signature=True, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=False, encipher_only=False, decipher_only=False, ) assert ku.digital_signature is True assert ku.content_commitment is True assert ku.key_encipherment is False assert ku.data_encipherment is False assert ku.key_agreement is False assert ku.key_cert_sign is True assert ku.crl_sign is False def test_key_agreement_true_properties(self): ku = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ) assert ku.key_agreement is True assert ku.encipher_only is False assert ku.decipher_only is True def test_key_agreement_false_properties(self): ku = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ) assert ku.key_agreement is False with pytest.raises(ValueError): ku.encipher_only with pytest.raises(ValueError): ku.decipher_only def test_repr_key_agreement_false(self): ku = x509.KeyUsage( digital_signature=True, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=False, encipher_only=False, decipher_only=False, ) assert repr(ku) == ( "" ) def test_repr_key_agreement_true(self): ku = x509.KeyUsage( digital_signature=True, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=True, crl_sign=False, encipher_only=False, decipher_only=False, ) assert repr(ku) == ( "" ) def test_eq(self): ku = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ) ku2 = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ) assert ku == ku2 def test_ne(self): ku = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ) ku2 = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ) assert ku != ku2 assert ku != object() def test_hash(self): ku = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ) ku2 = x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ) ku3 = x509.KeyUsage( digital_signature=False, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ) assert hash(ku) == hash(ku2) assert hash(ku) != hash(ku3) @pytest.mark.parametrize( ("ext", "serialized"), [ ( x509.KeyUsage( digital_signature=False, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ), b"\x03\x02\x06@", ), ( x509.KeyUsage( digital_signature=False, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=True, ), b"\x03\x03\x07H\x80", ), ( x509.KeyUsage( digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=True, decipher_only=False, ), b"\x03\x02\x00\x89", ), ( x509.KeyUsage( digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=True, key_agreement=False, key_cert_sign=True, crl_sign=False, encipher_only=False, decipher_only=False, ), b"\x03\x02\x02\x94", ), ( x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ), b"\x03\x01\x00", ), ], ) def test_public_bytes(self, ext, serialized): assert ext.public_bytes() == serialized class TestSubjectKeyIdentifier: def test_properties(self): value = binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ski = x509.SubjectKeyIdentifier(value) assert ski.digest == value assert ski.key_identifier == value def test_repr(self): ski = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) ext = x509.Extension(ExtensionOID.SUBJECT_KEY_IDENTIFIER, False, ski) assert repr(ext) == ( ", critical=False, value=)>" ) def test_eq(self): ski = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) ski2 = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) assert ski == ski2 def test_ne(self): ski = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) ski2 = x509.SubjectKeyIdentifier( binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980") ) assert ski != ski2 assert ski != object() def test_hash(self): ski1 = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) ski2 = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) ski3 = x509.SubjectKeyIdentifier( binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980") ) assert hash(ski1) == hash(ski2) assert hash(ski1) != hash(ski3) def test_public_bytes(self): ext = x509.SubjectKeyIdentifier( binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") ) assert ( ext.public_bytes() == b'\x04\x14\t#\x84\x93"0I\x8b\xc9\x80\xaa\x80\x98Eoo\xf7\xff:' b"\xc9" ) class TestAuthorityKeyIdentifier: def test_authority_cert_issuer_not_generalname(self): with pytest.raises(TypeError): x509.AuthorityKeyIdentifier( b"identifier", ["notname"], # type:ignore[list-item] 3, ) def test_authority_cert_serial_number_not_integer(self): dirname = x509.DirectoryName( x509.Name( [ x509.NameAttribute( x509.ObjectIdentifier("2.999.1"), "value1" ), x509.NameAttribute( x509.ObjectIdentifier("2.999.2"), "value2" ), ] ) ) with pytest.raises(TypeError): x509.AuthorityKeyIdentifier( b"identifier", [dirname], "notanint", # type:ignore[arg-type] ) def test_authority_issuer_none_serial_not_none(self): with pytest.raises(ValueError): x509.AuthorityKeyIdentifier(b"identifier", None, 3) def test_authority_issuer_not_none_serial_none(self): dirname = x509.DirectoryName( x509.Name( [ x509.NameAttribute( x509.ObjectIdentifier("2.999.1"), "value1" ), x509.NameAttribute( x509.ObjectIdentifier("2.999.2"), "value2" ), ] ) ) with pytest.raises(ValueError): x509.AuthorityKeyIdentifier(b"identifier", [dirname], None) def test_authority_cert_serial_and_issuer_none(self): aki = x509.AuthorityKeyIdentifier(b"id", None, None) assert aki.key_identifier == b"id" assert aki.authority_cert_issuer is None assert aki.authority_cert_serial_number is None def test_authority_cert_serial_zero(self): dns = x509.DNSName("SomeIssuer") aki = x509.AuthorityKeyIdentifier(b"id", [dns], 0) assert aki.key_identifier == b"id" assert aki.authority_cert_issuer == [dns] assert aki.authority_cert_serial_number == 0 def test_iter_input(self): dirnames = [ x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) ] aki = x509.AuthorityKeyIdentifier(b"digest", iter(dirnames), 1234) assert aki.authority_cert_issuer is not None assert list(aki.authority_cert_issuer) == dirnames def test_repr(self): dirname = x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) assert repr(aki) == ( ")>], author" "ity_cert_serial_number=1234)>" ) def test_eq(self): dirname = x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) dirname2 = x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname2], 1234) assert aki == aki2 def test_ne(self): dirname = x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) dirname5 = x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "aCN")]) ) aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) aki2 = x509.AuthorityKeyIdentifier(b"diges", [dirname], 1234) aki3 = x509.AuthorityKeyIdentifier(b"digest", None, None) aki4 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 12345) aki5 = x509.AuthorityKeyIdentifier(b"digest", [dirname5], 12345) assert aki != aki2 assert aki != aki3 assert aki != aki4 assert aki != aki5 assert aki != object() def test_hash(self): dirname = x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) aki1 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) aki3 = x509.AuthorityKeyIdentifier(b"digest", None, None) assert hash(aki1) == hash(aki2) assert hash(aki1) != hash(aki3) def test_public_bytes(self): dirname = x509.DirectoryName( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "myCN")]) ) ext = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) assert ( ext.public_bytes() == b"0!\x80\x06digest\xa1\x13\xa4\x110\x0f1\r0\x0b\x06\x03U\x04" b"\x03\x0c\x04myCN\x82\x02\x04\xd2" ) class TestBasicConstraints: def test_ca_not_boolean(self): with pytest.raises(TypeError): x509.BasicConstraints( ca="notbool", # type:ignore[arg-type] path_length=None, ) def test_path_length_not_ca(self): with pytest.raises(ValueError): x509.BasicConstraints(ca=False, path_length=0) def test_path_length_not_int(self): with pytest.raises(TypeError): x509.BasicConstraints( ca=True, path_length=1.1, # type:ignore[arg-type] ) with pytest.raises(TypeError): x509.BasicConstraints( ca=True, path_length="notint", # type:ignore[arg-type] ) def test_path_length_negative(self): with pytest.raises(TypeError): x509.BasicConstraints(ca=True, path_length=-1) def test_repr(self): na = x509.BasicConstraints(ca=True, path_length=None) assert repr(na) == ("") def test_hash(self): na = x509.BasicConstraints(ca=True, path_length=None) na2 = x509.BasicConstraints(ca=True, path_length=None) na3 = x509.BasicConstraints(ca=True, path_length=0) assert hash(na) == hash(na2) assert hash(na) != hash(na3) def test_eq(self): na = x509.BasicConstraints(ca=True, path_length=None) na2 = x509.BasicConstraints(ca=True, path_length=None) assert na == na2 def test_ne(self): na = x509.BasicConstraints(ca=True, path_length=None) na2 = x509.BasicConstraints(ca=True, path_length=1) na3 = x509.BasicConstraints(ca=False, path_length=None) assert na != na2 assert na != na3 assert na != object() def test_public_bytes(self): ext = x509.BasicConstraints(ca=True, path_length=None) assert ext.public_bytes() == b"0\x03\x01\x01\xff" class TestExtendedKeyUsage: def test_not_all_oids(self): with pytest.raises(TypeError): x509.ExtendedKeyUsage(["notoid"]) # type:ignore[list-item] def test_iter_len(self): eku = x509.ExtendedKeyUsage( [ x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), ] ) assert len(eku) == 2 assert list(eku) == [ ExtendedKeyUsageOID.SERVER_AUTH, ExtendedKeyUsageOID.CLIENT_AUTH, ] def test_iter_input(self): usages = [ x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), ] aia = x509.ExtendedKeyUsage(iter(usages)) assert list(aia) == usages def test_repr(self): eku = x509.ExtendedKeyUsage( [ x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), ] ) assert repr(eku) == ( ", ])>" ) def test_eq(self): eku = x509.ExtendedKeyUsage( [x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7")] ) eku2 = x509.ExtendedKeyUsage( [x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7")] ) assert eku == eku2 def test_ne(self): eku = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6")]) eku2 = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6.1")]) assert eku != eku2 assert eku != object() def test_hash(self): eku = x509.ExtendedKeyUsage( [x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7")] ) eku2 = x509.ExtendedKeyUsage( [x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7")] ) eku3 = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6")]) assert hash(eku) == hash(eku2) assert hash(eku) != hash(eku3) def test_public_bytes(self): ext = x509.ExtendedKeyUsage( [x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7")] ) assert ext.public_bytes() == b"0\x08\x06\x02+\x06\x06\x02+\x07" class TestExtensions: def test_no_extensions(self, backend): cert = _load_cert( os.path.join("x509", "verisign_md2_root.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions assert len(ext) == 0 assert list(ext) == [] with pytest.raises(x509.ExtensionNotFound) as exc: ext.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS def test_one_extension(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None assert ext.value.ca is False def test_duplicate_extension(self, backend): cert = _load_cert( os.path.join("x509", "custom", "two_basic_constraints.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(x509.DuplicateExtension) as exc: cert.extensions assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS def test_unsupported_critical_extension(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "unsupported_extension_critical.pem" ), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( x509.ObjectIdentifier("1.2.3.4") ) assert isinstance(ext.value, x509.UnrecognizedExtension) assert ext.value.value == b"value" def test_unsupported_extension(self, backend): cert = _load_cert( os.path.join("x509", "custom", "unsupported_extension_2.pem"), x509.load_pem_x509_certificate, ) extensions = cert.extensions assert len(extensions) == 2 assert extensions[0].critical is False assert extensions[0].oid == x509.ObjectIdentifier( "1.3.6.1.4.1.41482.2" ) assert extensions[0].value == x509.UnrecognizedExtension( x509.ObjectIdentifier("1.3.6.1.4.1.41482.2"), b"1.3.6.1.4.1.41482.1.2", ) assert extensions[1].critical is False assert extensions[1].oid == x509.ObjectIdentifier( "1.3.6.1.4.1.45724.2.1.1" ) assert extensions[1].value == x509.UnrecognizedExtension( x509.ObjectIdentifier("1.3.6.1.4.1.45724.2.1.1"), b"\x03\x02\x040" ) def test_no_extensions_get_for_class(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) exts = cert.extensions with pytest.raises(x509.ExtensionNotFound) as exc: exts.get_extension_for_class(x509.IssuerAlternativeName) assert exc.value.oid == ExtensionOID.ISSUER_ALTERNATIVE_NAME def test_unrecognized_extension_for_class(self): exts = x509.Extensions([]) with pytest.raises(TypeError): exts.get_extension_for_class(x509.UnrecognizedExtension) def test_indexing(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) exts = cert.extensions assert exts[-1] == exts[7] assert exts[2:6:2] == [exts[2], exts[4]] def test_one_extension_get_for_class(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None def test_repr(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, ) assert repr(cert.extensions) == ( ", critical=False, value=)>])>" ) class TestBasicConstraintsExtension: def test_ca_true_pathlen_6(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" ), x509.load_der_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None assert ext.critical is True assert ext.value.ca is True assert ext.value.path_length == 6 def test_path_length_zero(self, backend): cert = _load_cert( os.path.join("x509", "custom", "bc_path_length_zero.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None assert ext.critical is True assert ext.value.ca is True assert ext.value.path_length == 0 def test_ca_true_no_pathlen(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None assert ext.critical is True assert ext.value.ca is True assert ext.value.path_length is None def test_ca_false(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None assert ext.critical is True assert ext.value.ca is False assert ext.value.path_length is None def test_no_basic_constraints(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidCertificatePathTest1EE.crt", ), x509.load_der_x509_certificate, ) with pytest.raises(x509.ExtensionNotFound): cert.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS ) def test_basic_constraint_not_critical(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None assert ext.critical is False assert ext.value.ca is False class TestSubjectKeyIdentifierExtension: def test_subject_key_identifier(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectKeyIdentifier ) ski = ext.value assert ext is not None assert ext.critical is False assert ski.digest == binascii.unhexlify( b"580184241bbc2b52944a3da510721451f5af3ac9" ) def test_no_subject_key_identifier(self, backend): cert = _load_cert( os.path.join("x509", "custom", "bc_path_length_zero.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(x509.ExtensionNotFound): cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER ) def test_from_rsa_public_key(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER ) ski = x509.SubjectKeyIdentifier.from_public_key(cert.public_key()) assert ext.value == ski @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), skip_message="Does not support DSA.", ) def test_from_dsa_public_key(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER ) ski = x509.SubjectKeyIdentifier.from_public_key(cert.public_key()) assert ext.value == ski def test_invalid_bit_string_padding_from_public_key(self, backend): data = load_vectors_from_file( filename=os.path.join( "asymmetric", "DER_Serialization", "dsa_public_key_invalid_bit_string.der", ), loader=lambda data: data.read(), mode="rb", ) pretend_key = pretend.stub(public_bytes=lambda x, y: data) with pytest.raises(ValueError): _key_identifier_from_public_key(pretend_key) # The previous value is invalid for 2 reasons: a) it's got non-zero # padding bits (i.e. the first byte of the value is not zero), b) the # padding bits aren't all set to zero (i.e. the last bits of the value) # Here we swap the last byte out with zeros so we can hit both error # checks. pretend_key = pretend.stub( public_bytes=lambda x, y: data[:-1] + b"\x00" ) with pytest.raises(ValueError, match="Invalid public key encoding"): _key_identifier_from_public_key(pretend_key) def test_from_ec_public_key(self, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER ) ski = x509.SubjectKeyIdentifier.from_public_key(cert.public_key()) assert ext.value == ski @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) def test_from_ed25519_public_key(self, backend): cert = _load_cert( os.path.join("x509", "ed25519", "root-ed25519.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER ) ski = x509.SubjectKeyIdentifier.from_public_key(cert.public_key()) assert ext.value == ski @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) def test_from_ed448_public_key(self, backend): cert = _load_cert( os.path.join("x509", "ed448", "root-ed448.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER ) ski = x509.SubjectKeyIdentifier.from_public_key(cert.public_key()) assert ext.value == ski class TestKeyUsageExtension: def test_no_key_usage(self, backend): cert = _load_cert( os.path.join("x509", "verisign_md2_root.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions with pytest.raises(x509.ExtensionNotFound) as exc: ext.get_extension_for_oid(ExtensionOID.KEY_USAGE) assert exc.value.oid == ExtensionOID.KEY_USAGE def test_all_purposes(self, backend): cert = _load_cert( os.path.join("x509", "custom", "all_key_usages.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.KeyUsage) assert ext is not None ku = ext.value assert ku.digital_signature is True assert ku.content_commitment is True assert ku.key_encipherment is True assert ku.data_encipherment is True assert ku.key_agreement is True assert ku.key_cert_sign is True assert ku.crl_sign is True assert ku.encipher_only is True assert ku.decipher_only is True def test_key_cert_sign_crl_sign(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" ), x509.load_der_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.KeyUsage) assert ext is not None assert ext.critical is True ku = ext.value assert ku.digital_signature is False assert ku.content_commitment is False assert ku.key_encipherment is False assert ku.data_encipherment is False assert ku.key_agreement is False assert ku.key_cert_sign is True assert ku.crl_sign is True class TestDNSName: def test_non_a_label(self): with pytest.raises(ValueError): x509.DNSName(".\xf5\xe4\xf6\xfc.example.com") def test_init(self): name = x509.DNSName("*.xn--4ca7aey.example.com") assert name.value == "*.xn--4ca7aey.example.com" with pytest.raises(TypeError): x509.DNSName(1.3) # type:ignore[arg-type] with pytest.raises(TypeError): x509.DNSName(b"bytes not allowed") # type:ignore[arg-type] def test_ne(self): n1 = x509.DNSName("test1") n2 = x509.DNSName("test2") n3 = x509.DNSName("test2") assert n1 != n2 assert not (n2 != n3) def test_hash(self): n1 = x509.DNSName("test1") n2 = x509.DNSName("test2") n3 = x509.DNSName("test2") assert hash(n1) != hash(n2) assert hash(n2) == hash(n3) class TestDirectoryName: def test_not_name(self): with pytest.raises(TypeError): x509.DirectoryName(b"notaname") # type:ignore[arg-type] with pytest.raises(TypeError): x509.DirectoryName(1.3) # type:ignore[arg-type] def test_repr(self): name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "value1")]) gn = x509.DirectoryName(name) assert repr(gn) == ")>" def test_eq(self): name = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) name2 = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) gn = x509.DirectoryName(name) gn2 = x509.DirectoryName(name2) assert gn == gn2 def test_ne(self): name = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) name2 = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2")] ) gn = x509.DirectoryName(name) gn2 = x509.DirectoryName(name2) assert gn != gn2 assert gn != object() def test_hash(self): name = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier("2.999.1"), "value1")] ) name2 = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier("2.999.2"), "value2")] ) gn = x509.DirectoryName(name) gn2 = x509.DirectoryName(name) gn3 = x509.DirectoryName(name2) assert hash(gn) == hash(gn2) assert hash(gn) != hash(gn3) class TestRFC822Name: def test_repr(self): gn = x509.RFC822Name("string") assert repr(gn) == "" def test_equality(self): gn = x509.RFC822Name("string") gn2 = x509.RFC822Name("string2") gn3 = x509.RFC822Name("string") assert gn != gn2 assert gn != object() assert gn == gn3 def test_not_text(self): with pytest.raises(TypeError): x509.RFC822Name(1.3) # type:ignore[arg-type] with pytest.raises(TypeError): x509.RFC822Name(b"bytes") # type:ignore[arg-type] def test_invalid_email(self): with pytest.raises(ValueError): x509.RFC822Name("Name ") with pytest.raises(ValueError): x509.RFC822Name("") def test_single_label(self): gn = x509.RFC822Name("administrator") assert gn.value == "administrator" def test_non_a_label(self): with pytest.raises(ValueError): x509.RFC822Name("email@em\xe5\xefl.com") def test_hash(self): g1 = x509.RFC822Name("email@host.com") g2 = x509.RFC822Name("email@host.com") g3 = x509.RFC822Name("admin@host.com") assert hash(g1) == hash(g2) assert hash(g1) != hash(g3) class TestUniformResourceIdentifier: def test_equality(self): gn = x509.UniformResourceIdentifier("string") gn2 = x509.UniformResourceIdentifier("string2") gn3 = x509.UniformResourceIdentifier("string") assert gn != gn2 assert gn != object() assert gn == gn3 def test_not_text(self): with pytest.raises(TypeError): x509.UniformResourceIdentifier(1.3) # type:ignore[arg-type] def test_no_parsed_hostname(self): gn = x509.UniformResourceIdentifier("singlelabel") assert gn.value == "singlelabel" def test_with_port(self): gn = x509.UniformResourceIdentifier("singlelabel:443/test") assert gn.value == "singlelabel:443/test" def test_non_a_label(self): with pytest.raises(ValueError): x509.UniformResourceIdentifier( "http://\u043f\u044b\u043a\u0430.cryptography" ) def test_empty_hostname(self): gn = x509.UniformResourceIdentifier("ldap:///some-nonsense") assert gn.value == "ldap:///some-nonsense" def test_hash(self): g1 = x509.UniformResourceIdentifier("http://host.com") g2 = x509.UniformResourceIdentifier("http://host.com") g3 = x509.UniformResourceIdentifier("http://other.com") assert hash(g1) == hash(g2) assert hash(g1) != hash(g3) def test_repr(self): gn = x509.UniformResourceIdentifier("string") assert repr(gn) == ("") class TestRegisteredID: def test_not_oid(self): with pytest.raises(TypeError): x509.RegisteredID(b"notanoid") # type:ignore[arg-type] with pytest.raises(TypeError): x509.RegisteredID(1.3) # type:ignore[arg-type] def test_repr(self): gn = x509.RegisteredID(NameOID.COMMON_NAME) assert repr(gn) == ( ")>" ) def test_eq(self): gn = x509.RegisteredID(NameOID.COMMON_NAME) gn2 = x509.RegisteredID(NameOID.COMMON_NAME) assert gn == gn2 def test_ne(self): gn = x509.RegisteredID(NameOID.COMMON_NAME) gn2 = x509.RegisteredID(ExtensionOID.BASIC_CONSTRAINTS) assert gn != gn2 assert gn != object() def test_hash(self): gn = x509.RegisteredID(NameOID.COMMON_NAME) gn2 = x509.RegisteredID(NameOID.COMMON_NAME) gn3 = x509.RegisteredID(ExtensionOID.BASIC_CONSTRAINTS) assert hash(gn) == hash(gn2) assert hash(gn) != hash(gn3) class TestIPAddress: def test_not_ipaddress(self): with pytest.raises(TypeError): x509.IPAddress(b"notanipaddress") # type:ignore[arg-type] with pytest.raises(TypeError): x509.IPAddress(1.3) # type:ignore[arg-type] def test_repr(self): gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) assert repr(gn) == "" gn2 = x509.IPAddress(ipaddress.IPv6Address("ff::")) assert repr(gn2) == "" gn3 = x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24")) assert repr(gn3) == "" gn4 = x509.IPAddress(ipaddress.IPv6Network("ff::/96")) assert repr(gn4) == "" def test_eq(self): gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) gn2 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) assert gn == gn2 def test_ne(self): gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) gn2 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.2")) assert gn != gn2 assert gn != object() def test_hash(self): gn = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) gn2 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) gn3 = x509.IPAddress(ipaddress.IPv4Address("127.0.0.2")) assert hash(gn) == hash(gn2) assert hash(gn) != hash(gn3) class TestOtherName: def test_invalid_args(self): with pytest.raises(TypeError): x509.OtherName( b"notanobjectidentifier", # type:ignore[arg-type] b"derdata", ) with pytest.raises(TypeError): x509.OtherName( x509.ObjectIdentifier("1.2.3.4"), "notderdata", # type:ignore[arg-type] ) def test_repr(self): gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") assert repr(gn) == ( ", value=b'derdata')>" ) gn = x509.OtherName(x509.ObjectIdentifier("2.5.4.65"), b"derdata") assert repr(gn) == ( ", value=b'derdata')>" ) def test_eq(self): gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") assert gn == gn2 def test_ne(self): gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") assert gn != object() gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata2") assert gn != gn2 gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.5"), b"derdata") assert gn != gn2 def test_hash(self): gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") gn3 = x509.OtherName(x509.ObjectIdentifier("1.2.3.5"), b"derdata") assert hash(gn) == hash(gn2) assert hash(gn) != hash(gn3) class TestGeneralNames: def test_get_values_for_type(self): gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) names = gns.get_values_for_type(x509.DNSName) assert names == ["cryptography.io"] def test_iter_names(self): gns = x509.GeneralNames( [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(gns) == 2 assert list(gns) == [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), ] def test_iter_input(self): names = [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), ] gns = x509.GeneralNames(iter(names)) assert list(gns) == names def test_indexing(self): gn = x509.GeneralNames( [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), x509.DNSName("another.local"), x509.RFC822Name("email@another.local"), x509.UniformResourceIdentifier("http://another.local"), ] ) assert gn[-1] == gn[4] assert gn[2:6:2] == [gn[2], gn[4]] def test_invalid_general_names(self): with pytest.raises(TypeError): x509.GeneralNames( [ x509.DNSName("cryptography.io"), "invalid", # type:ignore[list-item] ] ) def test_repr(self): gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) assert repr(gns) == ( "])>" ) def test_eq(self): gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) gns2 = x509.GeneralNames([x509.DNSName("cryptography.io")]) assert gns == gns2 def test_ne(self): gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) gns2 = x509.GeneralNames([x509.RFC822Name("admin@cryptography.io")]) assert gns != gns2 assert gns != object() def test_hash(self): gns = x509.GeneralNames([x509.DNSName("cryptography.io")]) gns2 = x509.GeneralNames([x509.DNSName("cryptography.io")]) gns3 = x509.GeneralNames([x509.RFC822Name("admin@cryptography.io")]) assert hash(gns) == hash(gns2) assert hash(gns) != hash(gns3) class TestIssuerAlternativeName: def test_get_values_for_type(self): san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) names = san.get_values_for_type(x509.DNSName) assert names == ["cryptography.io"] def test_iter_names(self): san = x509.IssuerAlternativeName( [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(san) == 2 assert list(san) == [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), ] def test_indexing(self): ian = x509.IssuerAlternativeName( [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), x509.DNSName("another.local"), x509.RFC822Name("email@another.local"), x509.UniformResourceIdentifier("http://another.local"), ] ) assert ian[-1] == ian[4] assert ian[2:6:2] == [ian[2], ian[4]] def test_invalid_general_names(self): with pytest.raises(TypeError): x509.IssuerAlternativeName( [ x509.DNSName("cryptography.io"), "invalid", # type:ignore[list-item] ] ) def test_repr(self): san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) assert repr(san) == ( "])>)>" ) def test_eq(self): san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) san2 = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) assert san == san2 def test_ne(self): san = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) san2 = x509.IssuerAlternativeName( [x509.RFC822Name("admin@cryptography.io")] ) assert san != san2 assert san != object() def test_hash(self): ian = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) ian2 = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) ian3 = x509.IssuerAlternativeName( [x509.RFC822Name("admin@cryptography.io")] ) assert hash(ian) == hash(ian2) assert hash(ian) != hash(ian3) def test_public_bytes(self): ext = x509.IssuerAlternativeName([x509.DNSName("cryptography.io")]) assert ext.public_bytes() == b"0\x11\x82\x0fcryptography.io" class TestRSAIssuerAlternativeNameExtension: def test_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ian_uri.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.IssuerAlternativeName ) assert list(ext.value) == [ x509.UniformResourceIdentifier("http://path.to.root/root.crt"), ] class TestCRLNumber: def test_eq(self): crl_number = x509.CRLNumber(15) assert crl_number == x509.CRLNumber(15) def test_ne(self): crl_number = x509.CRLNumber(15) assert crl_number != x509.CRLNumber(14) assert crl_number != object() def test_repr(self): crl_number = x509.CRLNumber(15) assert repr(crl_number) == "" def test_invalid_number(self): with pytest.raises(TypeError): x509.CRLNumber("notanumber") # type:ignore[arg-type] def test_hash(self): c1 = x509.CRLNumber(1) c2 = x509.CRLNumber(1) c3 = x509.CRLNumber(2) assert hash(c1) == hash(c2) assert hash(c1) != hash(c3) def test_public_bytes(self): ext = x509.CRLNumber(15) assert ext.public_bytes() == b"\x02\x01\x0f" class TestSubjectAlternativeName: def test_get_values_for_type(self): san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) names = san.get_values_for_type(x509.DNSName) assert names == ["cryptography.io"] def test_iter_names(self): san = x509.SubjectAlternativeName( [x509.DNSName("cryptography.io"), x509.DNSName("crypto.local")] ) assert len(san) == 2 assert list(san) == [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), ] def test_indexing(self): san = x509.SubjectAlternativeName( [ x509.DNSName("cryptography.io"), x509.DNSName("crypto.local"), x509.DNSName("another.local"), x509.RFC822Name("email@another.local"), x509.UniformResourceIdentifier("http://another.local"), ] ) assert san[-1] == san[4] assert san[2:6:2] == [san[2], san[4]] def test_invalid_general_names(self): with pytest.raises(TypeError): x509.SubjectAlternativeName( [ x509.DNSName("cryptography.io"), "invalid", # type:ignore[list-item] ] ) def test_repr(self): san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) assert repr(san) == ( "])>)>" ) def test_eq(self): san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) san2 = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) assert san == san2 def test_ne(self): san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) san2 = x509.SubjectAlternativeName( [x509.RFC822Name("admin@cryptography.io")] ) assert san != san2 assert san != object() def test_hash(self): san = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) san2 = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) san3 = x509.SubjectAlternativeName( [x509.RFC822Name("admin@cryptography.io")] ) assert hash(san) == hash(san2) assert hash(san) != hash(san3) def test_public_bytes(self): ext = x509.SubjectAlternativeName([x509.DNSName("cryptography.io")]) assert ext.public_bytes() == b"0\x11\x82\x0fcryptography.io" class TestRSASubjectAlternativeNameExtension: def test_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False san = ext.value dns = san.get_values_for_type(x509.DNSName) assert dns == ["www.cryptography.io", "cryptography.io"] def test_wildcard_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "wildcard_san.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) dns = ext.value.get_values_for_type(x509.DNSName) assert dns == [ "*.langui.sh", "langui.sh", "*.saseliminator.com", "saseliminator.com", ] def test_san_empty_hostname(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_empty_hostname.pem"), x509.load_pem_x509_certificate, ) san = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME ) assert isinstance(san.value, x509.SubjectAlternativeName) dns = san.value.get_values_for_type(x509.DNSName) assert dns == [""] def test_san_wildcard_idna_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_wildcard_idna.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) dns = ext.value.get_values_for_type(x509.DNSName) assert dns == ["*.xn--80ato2c.cryptography"] def test_unsupported_gn(self, backend): cert = _load_cert( os.path.join("x509", "san_x400address.der"), x509.load_der_x509_certificate, ) with pytest.raises(x509.UnsupportedGeneralNameType): cert.extensions def test_registered_id(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_registered_id.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False san = ext.value rid = san.get_values_for_type(x509.RegisteredID) assert rid == [x509.ObjectIdentifier("1.2.3.4")] def test_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_uri_with_port.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) assert uri == [ "gopher://xn--80ato2c.cryptography:70/path?q=s#hello", "http://someregulardomain.com", ] def test_ipaddress(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_ipaddr.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False san = ext.value ip = san.get_values_for_type(x509.IPAddress) assert [ ipaddress.ip_address("127.0.0.1"), ipaddress.ip_address("ff::"), ] == ip def test_dirname(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_dirname.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False san = ext.value dirname = san.get_values_for_type(x509.DirectoryName) assert [ x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, "test"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Org"), x509.NameAttribute( NameOID.STATE_OR_PROVINCE_NAME, "Texas" ), ] ) ] == dirname def test_rfc822name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_rfc822_idna.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False san = ext.value rfc822name = san.get_values_for_type(x509.RFC822Name) assert ["email@xn--eml-vla4c.com"] == rfc822name def test_idna2003_invalid(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_idna2003_dnsname.pem"), x509.load_pem_x509_certificate, ) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ).value assert len(san) == 1 [name] = san assert name.value == "xn--k4h.ws" def test_unicode_rfc822_name_dns_name_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_idna_names.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None rfc822_name = ext.value.get_values_for_type(x509.RFC822Name) dns_name = ext.value.get_values_for_type(x509.DNSName) uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) assert rfc822_name == ["email@xn--80ato2c.cryptography"] assert dns_name == ["xn--80ato2c.cryptography"] assert uri == ["https://www.xn--80ato2c.cryptography"] def test_rfc822name_dnsname_ipaddress_directoryname_uri(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_email_dns_ip_dirname_uri.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False san = ext.value rfc822_name = san.get_values_for_type(x509.RFC822Name) uri = san.get_values_for_type(x509.UniformResourceIdentifier) dns = san.get_values_for_type(x509.DNSName) ip = san.get_values_for_type(x509.IPAddress) dirname = san.get_values_for_type(x509.DirectoryName) assert ["user@cryptography.io"] == rfc822_name assert ["https://cryptography.io"] == uri assert ["cryptography.io"] == dns assert [ x509.Name( [ x509.NameAttribute(NameOID.COMMON_NAME, "dirCN"), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Cryptographic Authority" ), ] ) ] == dirname assert [ ipaddress.ip_address("127.0.0.1"), ipaddress.ip_address("ff::"), ] == ip def test_invalid_rfc822name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_rfc822_names.pem"), x509.load_pem_x509_certificate, ) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ).value values = san.get_values_for_type(x509.RFC822Name) assert values == [ "email", "email ", "email ", "email ", "myemail:", ] def test_other_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "san_other_name.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert ext is not None assert ext.critical is False expected = x509.OtherName( x509.ObjectIdentifier("1.2.3.4"), b"\x16\x0bHello World" ) assert len(ext.value) == 1 assert next(iter(ext.value)) == expected othernames = ext.value.get_values_for_type(x509.OtherName) assert othernames == [expected] def test_certbuilder(self, rsa_key_2048: rsa.RSAPrivateKey, backend): sans = [ "*.example.org", "*.xn--4ca7aey.example.com", "foobar.example.net", ] private_key = rsa_key_2048 builder = _make_certbuilder(private_key) builder = builder.add_extension( SubjectAlternativeName(list(map(DNSName, sans))), True ) cert = builder.sign(private_key, hashes.SHA256(), backend) result = [ x.value for x in cert.extensions.get_extension_for_class( SubjectAlternativeName ).value ] assert result == sans class TestExtendedKeyUsageExtension: def test_eku(self, backend): cert = _load_cert( os.path.join("x509", "custom", "extended_key_usage.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) assert ext is not None assert ext.critical is False assert [ x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.3"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.4"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.9"), x509.ObjectIdentifier("1.3.6.1.5.5.7.3.8"), x509.ObjectIdentifier("2.5.29.37.0"), x509.ObjectIdentifier("2.16.840.1.113730.4.1"), ] == list(ext.value) class TestAccessDescription: def test_invalid_access_method(self): with pytest.raises(TypeError): x509.AccessDescription( "notanoid", # type:ignore[arg-type] x509.DNSName("test"), ) def test_invalid_access_location(self): with pytest.raises(TypeError): x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, "invalid", # type:ignore[arg-type] ) def test_valid_nonstandard_method(self): ad = x509.AccessDescription( ObjectIdentifier("2.999.1"), x509.UniformResourceIdentifier("http://example.com"), ) assert ad is not None def test_repr(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) assert repr(ad) == ( ", access_location=)>" ) def test_eq(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad2 = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) assert ad == ad2 def test_ne(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad2 = x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad3 = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://notthesame"), ) assert ad != ad2 assert ad != ad3 assert ad != object() def test_hash(self): ad = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad2 = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ad3 = x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) assert hash(ad) == hash(ad2) assert hash(ad) != hash(ad3) class TestPolicyConstraints: def test_invalid_explicit_policy(self): with pytest.raises(TypeError): x509.PolicyConstraints("invalid", None) # type:ignore[arg-type] def test_invalid_inhibit_policy(self): with pytest.raises(TypeError): x509.PolicyConstraints(None, "invalid") # type:ignore[arg-type] def test_both_none(self): with pytest.raises(ValueError): x509.PolicyConstraints(None, None) def test_repr(self): pc = x509.PolicyConstraints(0, None) assert repr(pc) == ( "" ) def test_eq(self): pc = x509.PolicyConstraints(2, 1) pc2 = x509.PolicyConstraints(2, 1) assert pc == pc2 def test_ne(self): pc = x509.PolicyConstraints(2, 1) pc2 = x509.PolicyConstraints(2, 2) pc3 = x509.PolicyConstraints(3, 1) assert pc != pc2 assert pc != pc3 assert pc != object() def test_hash(self): pc = x509.PolicyConstraints(2, 1) pc2 = x509.PolicyConstraints(2, 1) pc3 = x509.PolicyConstraints(2, None) assert hash(pc) == hash(pc2) assert hash(pc) != hash(pc3) def test_public_bytes(self): ext = x509.PolicyConstraints(2, 1) assert ext.public_bytes() == b"0\x06\x80\x01\x02\x81\x01\x01" class TestPolicyConstraintsExtension: def test_inhibit_policy_mapping(self, backend): cert = _load_cert( os.path.join("x509", "department-of-state-root.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.POLICY_CONSTRAINTS, ) assert ext.critical is True assert ext.value == x509.PolicyConstraints( require_explicit_policy=None, inhibit_policy_mapping=0, ) def test_require_explicit_policy(self, backend): cert = _load_cert( os.path.join("x509", "custom", "policy_constraints_explicit.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.POLICY_CONSTRAINTS ) assert ext.critical is True assert ext.value == x509.PolicyConstraints( require_explicit_policy=1, inhibit_policy_mapping=None, ) def test_public_bytes(self): ext = x509.PolicyConstraints( require_explicit_policy=None, inhibit_policy_mapping=0, ) assert ext.public_bytes() == b"\x30\x03\x81\x01\x00" class TestAuthorityInformationAccess: def test_invalid_descriptions(self): with pytest.raises(TypeError): x509.AuthorityInformationAccess( ["notanAccessDescription"] # type:ignore[list-item] ) def test_iter_len(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) assert len(aia) == 2 assert list(aia) == [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] def test_iter_input(self): desc = [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ) ] aia = x509.AuthorityInformationAccess(iter(desc)) assert list(aia) == desc def test_repr(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) assert repr(aia) == ( ", acces" "s_location=)>, , access_lo" "cation=)>])>" ) def test_eq(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) aia2 = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) assert aia == aia2 def test_ne(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) aia2 = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), ] ) assert aia != aia2 assert aia != object() def test_indexing(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp2.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp3.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp4.domain.com"), ), ] ) assert aia[-1] == aia[4] assert aia[2:6:2] == [aia[2], aia[4]] def test_hash(self): aia = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) aia2 = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) aia3 = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.other.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) assert hash(aia) == hash(aia2) assert hash(aia) != hash(aia3) def test_public_bytes(self): ext = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.other.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier("http://domain.com/ca.crt"), ), ] ) assert ( ext.public_bytes() == b"0I0!\x06\x08+\x06\x01\x05\x05\x070\x01\x86\x15http://" b"ocsp.other.com0$\x06\x08+\x06\x01\x05\x05\x070\x02\x86\x18" b"http://domain.com/ca.crt" ) class TestSubjectInformationAccess: def test_invalid_descriptions(self): with pytest.raises(TypeError): x509.SubjectInformationAccess( ["notanAccessDescription"] # type:ignore[list-item] ) def test_iter_len(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) assert len(sia) == 2 assert list(sia) == [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] def test_iter_input(self): desc = [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ) ] sia = x509.SubjectInformationAccess(iter(desc)) assert list(sia) == desc def test_repr(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ) ] ) assert repr(sia) == ( ", access_location=)>])>" ) def test_eq(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) sia2 = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) assert sia == sia2 def test_ne(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) sia2 = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), ] ) assert sia != sia2 assert sia != object() def test_indexing(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca3.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca4.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca5.domain.com"), ), ] ) assert sia[-1] == sia[4] assert sia[2:6:2] == [sia[2], sia[4]] def test_hash(self): sia = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) sia2 = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca2.domain.com"), ), ] ) sia3 = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca3.domain.com"), ), ] ) assert hash(sia) == hash(sia2) assert hash(sia) != hash(sia3) def test_public_bytes(self): ext = x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca.domain.com"), ), x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("http://ca3.domain.com"), ), ] ) assert ( ext.public_bytes() == b"0E0 \x06\x08+\x06\x01\x05\x05\x070\x05\x86\x14http://" b"ca.domain.com0!\x06\x08+\x06\x01\x05\x05\x070\x05\x86\x15" b"http://ca3.domain.com" ) class TestSubjectInformationAccessExtension: def test_sia(self, backend): cert = _load_cert( os.path.join("x509", "custom", "sia.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_INFORMATION_ACCESS ) assert ext is not None assert ext.critical is False assert ext.value == x509.SubjectInformationAccess( [ x509.AccessDescription( SubjectInformationAccessOID.CA_REPOSITORY, x509.UniformResourceIdentifier("https://my.ca.issuer/"), ), x509.AccessDescription( x509.ObjectIdentifier("2.999.7"), x509.UniformResourceIdentifier( "gopher://info-mac-archive" ), ), ] ) class TestAuthorityInformationAccessExtension: def test_aia_ocsp_ca_issuers(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS ) assert ext is not None assert ext.critical is False assert ext.value == x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://gv.symcd.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier( "http://gv.symcb.com/gv.crt" ), ), ] ) def test_aia_multiple_ocsp_ca_issuers(self, backend): cert = _load_cert( os.path.join("x509", "custom", "aia_ocsp_ca_issuers.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS ) assert ext is not None assert ext.critical is False assert ext.value == x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp2.domain.com"), ), x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "myCN" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "some Org" ), ] ) ), ), ] ) def test_aia_ocsp_only(self, backend): cert = _load_cert( os.path.join("x509", "custom", "aia_ocsp.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS ) assert ext is not None assert ext.critical is False assert ext.value == x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier("http://ocsp.domain.com"), ), ] ) def test_aia_ca_issuers_only(self, backend): cert = _load_cert( os.path.join("x509", "custom", "aia_ca_issuers.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS ) assert ext is not None assert ext.critical is False assert ext.value == x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "myCN" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "some Org" ), ] ) ), ), ] ) def test_public_bytes(self): ext = x509.AuthorityInformationAccess( [ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "myCN" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "some Org" ), ] ) ), ), ] ) assert ( ext.public_bytes() == b'0200\x06\x08+\x06\x01\x05\x05\x070\x02\xa4$0"1\r0\x0b\x06' b"\x03U\x04\x03\x0c\x04myCN1\x110\x0f\x06\x03U\x04\n\x0c\x08" b"some Org" ) class TestAuthorityKeyIdentifierExtension: def test_aki_keyid(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.AuthorityKeyIdentifier ) assert ext is not None assert ext.critical is False assert ext.value.key_identifier == ( b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08\xcbY" ) assert ext.value.authority_cert_issuer is None assert ext.value.authority_cert_serial_number is None def test_aki_all_fields(self, backend): cert = _load_cert( os.path.join("x509", "custom", "authority_key_identifier.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.AuthorityKeyIdentifier ) assert ext is not None assert ext.critical is False assert ext.value.key_identifier == ( b"9E>\xca=b\x1d\xea\x86I\xf6Z\xab@\xb7\xa4p\x98\xf1\xec" ) assert ext.value.authority_cert_issuer == [ x509.DirectoryName( x509.Name( [ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io" ), ] ) ) ] assert ext.value.authority_cert_serial_number == 3 def test_aki_no_keyid(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "authority_key_identifier_no_keyid.pem" ), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_class( x509.AuthorityKeyIdentifier ) assert ext is not None assert ext.critical is False assert ext.value.key_identifier is None assert ext.value.authority_cert_issuer == [ x509.DirectoryName( x509.Name( [ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA"), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io" ), ] ) ) ] assert ext.value.authority_cert_serial_number == 3 def test_from_certificate(self, backend): issuer_cert = _load_cert( os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), x509.load_pem_x509_certificate, ) cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_KEY_IDENTIFIER ) public_key = issuer_cert.public_key() assert isinstance(public_key, rsa.RSAPublicKey) aki = x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key) assert ext.value == aki def test_from_issuer_subject_key_identifier(self, backend): issuer_cert = _load_cert( os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), x509.load_pem_x509_certificate, ) cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_KEY_IDENTIFIER ) ski_ext = issuer_cert.extensions.get_extension_for_class( x509.SubjectKeyIdentifier ) aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( ski_ext.value ) assert ext.value == aki class TestNameConstraints: def test_ipaddress_wrong_type(self): with pytest.raises(TypeError): x509.NameConstraints( permitted_subtrees=[ x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) ], excluded_subtrees=None, ) with pytest.raises(TypeError): x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[ x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")) ], ) def test_ipaddress_allowed_type(self): permitted = [x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/29"))] excluded = [x509.IPAddress(ipaddress.IPv4Network("10.10.0.0/24"))] nc = x509.NameConstraints( permitted_subtrees=permitted, excluded_subtrees=excluded ) assert nc.permitted_subtrees == permitted assert nc.excluded_subtrees == excluded def test_dnsname_wrong_value(self): with pytest.raises(ValueError): x509.NameConstraints( permitted_subtrees=[x509.DNSName("*.example.com")], excluded_subtrees=None, ) with pytest.raises(ValueError): x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[x509.DNSName("*.example.com")], ) def test_dnsname_allowed_value(self): permitted = [x509.DNSName("example.com")] excluded = [x509.DNSName("www.example.com")] nc = x509.NameConstraints( permitted_subtrees=permitted, excluded_subtrees=excluded ) assert nc.permitted_subtrees == permitted assert nc.excluded_subtrees == excluded def test_invalid_permitted_subtrees(self): with pytest.raises(TypeError): x509.NameConstraints("badpermitted", None) # type:ignore[arg-type] def test_invalid_excluded_subtrees(self): with pytest.raises(TypeError): x509.NameConstraints(None, "badexcluded") # type:ignore[arg-type] def test_no_subtrees(self): with pytest.raises(ValueError): x509.NameConstraints(None, None) def test_permitted_none(self): excluded = [x509.DNSName("name.local")] nc = x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=excluded ) assert nc.permitted_subtrees is None assert nc.excluded_subtrees is not None def test_excluded_none(self): permitted = [x509.DNSName("name.local")] nc = x509.NameConstraints( permitted_subtrees=permitted, excluded_subtrees=None ) assert nc.permitted_subtrees is not None assert nc.excluded_subtrees is None def test_iter_input(self): subtrees = [x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24"))] nc = x509.NameConstraints(iter(subtrees), iter(subtrees)) assert nc.permitted_subtrees is not None assert list(nc.permitted_subtrees) == subtrees assert nc.excluded_subtrees is not None assert list(nc.excluded_subtrees) == subtrees def test_empty_lists(self): with pytest.raises(ValueError): x509.NameConstraints(permitted_subtrees=None, excluded_subtrees=[]) with pytest.raises(ValueError): x509.NameConstraints(permitted_subtrees=[], excluded_subtrees=None) def test_repr(self): permitted = [x509.DNSName("name.local"), x509.DNSName("name2.local")] nc = x509.NameConstraints( permitted_subtrees=permitted, excluded_subtrees=None ) assert repr(nc) == ( ", ], excluded_subtrees=None)>" ) def test_eq(self): nc = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=[x509.DNSName("name2.local")], ) nc2 = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=[x509.DNSName("name2.local")], ) assert nc == nc2 def test_ne(self): nc = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=[x509.DNSName("name2.local")], ) nc2 = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=None, ) nc3 = x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[x509.DNSName("name2.local")], ) assert nc != nc2 assert nc != nc3 assert nc != object() def test_hash(self): nc = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=[x509.DNSName("name2.local")], ) nc2 = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=[x509.DNSName("name2.local")], ) nc3 = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=None, ) nc4 = x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[x509.DNSName("name.local")], ) assert hash(nc) == hash(nc2) assert hash(nc) != hash(nc3) assert hash(nc3) != hash(nc4) def test_public_bytes(self): ext = x509.NameConstraints( permitted_subtrees=[x509.DNSName("name.local")], excluded_subtrees=[x509.DNSName("name2.local")], ) assert ( ext.public_bytes() == b"0!\xa0\x0e0\x0c\x82\nname.local\xa1\x0f0\r\x82\x0bname2.local" ) class TestNameConstraintsExtension: def test_permitted_excluded(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted_excluded_2.pem"), x509.load_pem_x509_certificate, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[x509.DNSName("zombo.local")], excluded_subtrees=[ x509.DirectoryName( x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, "zombo")] ) ) ], ) def test_permitted(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted_2.pem"), x509.load_pem_x509_certificate, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[x509.DNSName("zombo.local")], excluded_subtrees=None, ) def test_permitted_with_leading_period(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted.pem"), x509.load_pem_x509_certificate, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[ x509.DNSName(".cryptography.io"), x509.UniformResourceIdentifier("ftp://cryptography.test"), ], excluded_subtrees=None, ) def test_excluded_with_leading_period(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_excluded.pem"), x509.load_pem_x509_certificate, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=None, excluded_subtrees=[ x509.DNSName(".cryptography.io"), x509.UniformResourceIdentifier("gopher://cryptography.test"), ], ) def test_permitted_excluded_with_ips(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_permitted_excluded.pem"), x509.load_pem_x509_certificate, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[ x509.IPAddress(ipaddress.IPv4Network("192.168.0.0/24")), x509.IPAddress(ipaddress.IPv6Network("FF:0:0:0:0:0:0:0/96")), ], excluded_subtrees=[ x509.DNSName(".domain.com"), x509.UniformResourceIdentifier("http://test.local"), ], ) def test_single_ip_netmask(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_single_ip_netmask.pem"), x509.load_pem_x509_certificate, ) nc = cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ).value assert nc == x509.NameConstraints( permitted_subtrees=[ x509.IPAddress(ipaddress.IPv6Network("FF:0:0:0:0:0:0:0/128")), x509.IPAddress(ipaddress.IPv4Network("192.168.0.1/32")), ], excluded_subtrees=None, ) def test_ip_invalid_length(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_ip_invalid_length.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(ValueError): cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ) def test_invalid_ipv6_netmask(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_invalid_ip_netmask.pem"), x509.load_pem_x509_certificate, ) with pytest.raises(ValueError): cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ) def test_invalid_ipv4_netmask(self, backend): cert = _load_cert( os.path.join("x509", "custom", "nc_invalid_ip4_netmask.der"), x509.load_der_x509_certificate, ) with pytest.raises(ValueError): cert.extensions.get_extension_for_oid( ExtensionOID.NAME_CONSTRAINTS ) def test_certbuilder(self, rsa_key_2048: rsa.RSAPrivateKey, backend): permitted = [ ".example.org", ".xn--4ca7aey.example.com", "foobar.example.net", ] private_key = rsa_key_2048 builder = _make_certbuilder(private_key) builder = builder.add_extension( NameConstraints( permitted_subtrees=list(map(DNSName, permitted)), excluded_subtrees=None, ), True, ) cert = builder.sign(private_key, hashes.SHA256(), backend) result = [ x.value for x in cert.extensions.get_extension_for_class( NameConstraints ).value.permitted_subtrees ] assert result == permitted def test_public_bytes(self): ext = x509.NameConstraints( permitted_subtrees=[x509.DNSName("zombo.local")], excluded_subtrees=[ x509.DirectoryName( x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, "zombo")] ) ) ], ) assert ( ext.public_bytes() == b"0)\xa0\x0f0\r\x82\x0bzombo.local\xa1\x160\x14\xa4\x120\x101" b"\x0e0\x0c\x06\x03U\x04\x03\x0c\x05zombo" ) class TestDistributionPoint: def test_distribution_point_full_name_not_general_names(self): with pytest.raises(TypeError): x509.DistributionPoint( ["notgn"], # type:ignore[list-item] None, None, None, ) def test_distribution_point_relative_name_not_name(self): with pytest.raises(TypeError): x509.DistributionPoint( None, "notname", # type:ignore[arg-type] None, None, ) def test_distribution_point_full_and_relative_not_none(self): with pytest.raises(ValueError): x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], x509.RelativeDistinguishedName( [x509.NameAttribute(NameOID.TITLE, "Test")] ), None, None, ) def test_no_full_name_relative_name_or_crl_issuer(self): with pytest.raises(ValueError): x509.DistributionPoint(None, None, None, None) def test_crl_issuer_not_general_names(self): with pytest.raises(TypeError): x509.DistributionPoint( None, None, None, ["notgn"], # type:ignore[list-item] ) def test_reason_not_reasonflags(self): with pytest.raises(TypeError): x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset(["notreasonflags"]), # type:ignore[list-item] None, ) def test_reason_not_frozenset(self): with pytest.raises(TypeError): x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, [x509.ReasonFlags.ca_compromise], # type:ignore[arg-type] None, ) def test_disallowed_reasons(self): with pytest.raises(ValueError): x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.unspecified]), None, ) with pytest.raises(ValueError): x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.remove_from_crl]), None, ) def test_reason_only(self): with pytest.raises(ValueError): x509.DistributionPoint( None, None, frozenset([x509.ReasonFlags.aa_compromise]), None ) def test_eq(self): dp = x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Important CA" ) ] ) ) ], ) dp2 = x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Important CA" ) ] ) ) ], ) assert dp == dp2 def test_ne(self): dp = x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Important CA" ) ] ) ) ], ) dp2 = x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, None, None, ) assert dp != dp2 assert dp != object() def test_iter_input(self): name = [x509.UniformResourceIdentifier("http://crypt.og/crl")] issuer = [ x509.DirectoryName( x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, "Important CA")] ) ) ] dp = x509.DistributionPoint( iter(name), None, frozenset([x509.ReasonFlags.ca_compromise]), iter(issuer), ) assert dp.full_name is not None assert list(dp.full_name) == name assert dp.crl_issuer is not None assert list(dp.crl_issuer) == issuer def test_repr(self): dp = x509.DistributionPoint( None, x509.RelativeDistinguishedName( [x509.NameAttribute(NameOID.COMMON_NAME, "myCN")] ), frozenset([x509.ReasonFlags.ca_compromise]), [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Important CA" ) ] ) ) ], ) assert repr(dp) == ( ", reasons=frozenset({}), crl_issuer=[)>])>" ) def test_hash(self): dp = x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Important CA" ) ] ) ) ], ) dp2 = x509.DistributionPoint( [x509.UniformResourceIdentifier("http://crypt.og/crl")], None, frozenset([x509.ReasonFlags.superseded]), [ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Important CA" ) ] ) ) ], ) dp3 = x509.DistributionPoint( None, x509.RelativeDistinguishedName( [x509.NameAttribute(NameOID.COMMON_NAME, "myCN")] ), None, None, ) assert hash(dp) == hash(dp2) assert hash(dp) != hash(dp3) class TestFreshestCRL: def test_invalid_distribution_points(self): with pytest.raises(TypeError): x509.FreshestCRL( ["notadistributionpoint"] # type:ignore[list-item] ) def test_iter_len(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), ] ) assert len(fcrl) == 1 assert list(fcrl) == [ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), ] def test_iter_input(self): points = [ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), ] fcrl = x509.FreshestCRL(iter(points)) assert list(fcrl) == points def test_repr(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), None, ), ] ) assert repr(fcrl) == ( "], relative" "_name=None, reasons=frozenset({}), crl_issuer=None)>])>" ) def test_eq(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl2 = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) assert fcrl == fcrl2 def test_ne(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl2 = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain2")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl3 = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl4 = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing2")], ), ] ) assert fcrl != fcrl2 assert fcrl != fcrl3 assert fcrl != fcrl4 assert fcrl != object() def test_hash(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl2 = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) fcrl3 = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) assert hash(fcrl) == hash(fcrl2) assert hash(fcrl) != hash(fcrl3) def test_indexing(self): fcrl = x509.FreshestCRL( [ x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing2")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing3")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing4")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing5")], ), ] ) assert fcrl[-1] == fcrl[4] assert fcrl[2:6:2] == [fcrl[2], fcrl[4]] def test_public_bytes(self): ext = x509.FreshestCRL( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), None, ), ] ) assert ( ext.public_bytes() == b"0\x180\x16\xa0\x10\xa0\x0e\x86\x0cftp://domain\x81\x02\x06@" ) class TestCRLDistributionPoints: def test_invalid_distribution_points(self): with pytest.raises(TypeError): x509.CRLDistributionPoints( ["notadistributionpoint"], # type:ignore[list-item] ) def test_iter_len(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), None, ), ] ) assert len(cdp) == 2 assert list(cdp) == [ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), None, ), ] def test_iter_input(self): points = [ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://domain")], None, None, None, ), ] cdp = x509.CRLDistributionPoints(iter(points)) assert list(cdp) == points def test_repr(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), None, ), ] ) assert repr(cdp) == ( "], relative" "_name=None, reasons=frozenset({}), crl_issuer=None)>])>" ) def test_eq(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp2 = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) assert cdp == cdp2 def test_ne(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp2 = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain2")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp3 = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp4 = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing2")], ), ] ) assert cdp != cdp2 assert cdp != cdp3 assert cdp != cdp4 assert cdp != object() def test_hash(self): cdp = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp2 = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) cdp3 = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset([x509.ReasonFlags.key_compromise]), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) assert hash(cdp) == hash(cdp2) assert hash(cdp) != hash(cdp3) def test_indexing(self): ci = x509.CRLDistributionPoints( [ x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing2")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing3")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing4")], ), x509.DistributionPoint( None, None, None, [x509.UniformResourceIdentifier("uri://thing5")], ), ] ) assert ci[-1] == ci[4] assert ci[2:6:2] == [ci[2], ci[4]] def test_public_bytes(self): ext = x509.CRLDistributionPoints( [ x509.DistributionPoint( [x509.UniformResourceIdentifier("ftp://domain")], None, frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), [x509.UniformResourceIdentifier("uri://thing")], ), ] ) assert ( ext.public_bytes() == b"0'0%\xa0\x10\xa0\x0e\x86\x0cftp://domain\x81\x02\x05`\xa2\r" b"\x86\x0buri://thing" ) class TestCRLDistributionPointsExtension: def test_fullname_and_crl_issuer(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidcRLIssuerTest28EE.crt" ), x509.load_der_x509_certificate, ) cdps = cert.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS ).value assert cdps == x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "indirectCRL CA3 cRLIssuer", ), x509.NameAttribute( NameOID.COMMON_NAME, "indirect CRL for indirectCRL CA3", ), ] ) ) ], relative_name=None, reasons=None, crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "indirectCRL CA3 cRLIssuer", ), ] ) ) ], ) ] ) def test_relativename_and_crl_issuer(self, backend): cert = _load_cert( os.path.join( "x509", "PKITS_data", "certs", "ValidcRLIssuerTest29EE.crt" ), x509.load_der_x509_certificate, ) cdps = cert.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS ).value assert cdps == x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( NameOID.COMMON_NAME, "indirect CRL for indirectCRL CA3", ), ] ), reasons=None, crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Test Certificates 2011", ), x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, "indirectCRL CA3 cRLIssuer", ), ] ) ) ], ) ] ) def test_fullname_crl_issuer_reasons(self, backend): cert = _load_cert( os.path.join( "x509", "custom", "cdp_fullname_reasons_crl_issuer.pem" ), x509.load_pem_x509_certificate, ) cdps = cert.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS ).value assert cdps == x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, reasons=frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, ] ), crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.ORGANIZATION_NAME, "PyCA" ), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA" ), ] ) ) ], ) ] ) def test_all_reasons(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_all_reasons.pem"), x509.load_pem_x509_certificate, ) cdps = cert.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS ).value assert cdps == x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://domain.com/some.crl" ) ], relative_name=None, reasons=frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, x509.ReasonFlags.affiliation_changed, x509.ReasonFlags.superseded, x509.ReasonFlags.privilege_withdrawn, x509.ReasonFlags.cessation_of_operation, x509.ReasonFlags.aa_compromise, x509.ReasonFlags.certificate_hold, ] ), crl_issuer=None, ) ] ) def test_single_reason(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_reason_aa_compromise.pem"), x509.load_pem_x509_certificate, ) cdps = cert.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS ).value assert cdps == x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://domain.com/some.crl" ) ], relative_name=None, reasons=frozenset([x509.ReasonFlags.aa_compromise]), crl_issuer=None, ) ] ) def test_crl_issuer_only(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_crl_issuer.pem"), x509.load_pem_x509_certificate, ) cdps = cert.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS ).value assert cdps == x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=None, relative_name=None, reasons=None, crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA" ), ] ) ) ], ) ] ) def test_crl_empty_hostname(self, backend): cert = _load_cert( os.path.join("x509", "custom", "cdp_empty_hostname.pem"), x509.load_pem_x509_certificate, ) cdps = cert.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS ).value assert cdps == x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" ) ], relative_name=None, reasons=None, crl_issuer=None, ) ] ) def test_public_bytes(self): ext = x509.CRLDistributionPoints( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" ) ], relative_name=None, reasons=None, crl_issuer=None, ) ] ) assert ( ext.public_bytes() == b"0-0+\xa0)\xa0'\x86%ldap:///CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" ) class TestFreshestCRLExtension: def test_vector(self, backend): cert = _load_cert( os.path.join("x509", "custom", "freshestcrl.pem"), x509.load_pem_x509_certificate, ) fcrl = cert.extensions.get_extension_for_class(x509.FreshestCRL).value assert fcrl == x509.FreshestCRL( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ), x509.UniformResourceIdentifier( "http://backup.myhost.com/myca.crl" ), ], relative_name=None, reasons=frozenset( [ x509.ReasonFlags.ca_compromise, x509.ReasonFlags.key_compromise, ] ), crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA" ), ] ) ) ], ) ] ) def test_public_bytes(self): ext = x509.FreshestCRL( [ x509.DistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ), x509.UniformResourceIdentifier( "http://backup.myhost.com/myca.crl" ), ], relative_name=None, reasons=frozenset( [ x509.ReasonFlags.ca_compromise, x509.ReasonFlags.key_compromise, x509.ReasonFlags.aa_compromise, ] ), crl_issuer=[ x509.DirectoryName( x509.Name( [ x509.NameAttribute( NameOID.COUNTRY_NAME, "US" ), x509.NameAttribute( NameOID.COMMON_NAME, "cryptography CA" ), ] ) ) ], ) ] ) assert ( ext.public_bytes() == b"0w0u\xa0A\xa0?\x86\x1ahttp://myhost.com/myca.crl\x86!http://" b"backup.myhost.com/myca.crl\x81\x03\x07`\x80\xa2+\xa4)0'1\x0b0\t" b"\x06\x03U\x04\x06\x13\x02US1\x180\x16\x06\x03U\x04\x03\x0c\x0fc" b"ryptography CA" ) class TestOCSPNoCheckExtension: def test_nocheck(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ocsp_nocheck.pem"), x509.load_pem_x509_certificate, ) ext = cert.extensions.get_extension_for_oid(ExtensionOID.OCSP_NO_CHECK) assert isinstance(ext.value, x509.OCSPNoCheck) def test_eq(self): onc1 = x509.OCSPNoCheck() onc2 = x509.OCSPNoCheck() assert onc1 == onc2 def test_hash(self): onc1 = x509.OCSPNoCheck() onc2 = x509.OCSPNoCheck() assert hash(onc1) == hash(onc2) def test_ne(self): onc1 = x509.OCSPNoCheck() onc2 = x509.OCSPNoCheck() assert onc1 == onc2 assert (onc1 != onc2) is False assert onc1 != object() def test_repr(self): onc = x509.OCSPNoCheck() assert repr(onc) == "" def test_public_bytes(self): ext = x509.OCSPNoCheck() assert ext.public_bytes() == b"\x05\x00" class TestInhibitAnyPolicy: def test_not_int(self): with pytest.raises(TypeError): x509.InhibitAnyPolicy("notint") # type:ignore[arg-type] def test_negative_int(self): with pytest.raises(ValueError): x509.InhibitAnyPolicy(-1) def test_repr(self): iap = x509.InhibitAnyPolicy(0) assert repr(iap) == "" def test_eq(self): iap = x509.InhibitAnyPolicy(1) iap2 = x509.InhibitAnyPolicy(1) assert iap == iap2 def test_ne(self): iap = x509.InhibitAnyPolicy(1) iap2 = x509.InhibitAnyPolicy(4) assert iap != iap2 assert iap != object() def test_hash(self): iap = x509.InhibitAnyPolicy(1) iap2 = x509.InhibitAnyPolicy(1) iap3 = x509.InhibitAnyPolicy(4) assert hash(iap) == hash(iap2) assert hash(iap) != hash(iap3) def test_public_bytes(self): ext = x509.InhibitAnyPolicy(1) assert ext.public_bytes() == b"\x02\x01\x01" class TestInhibitAnyPolicyExtension: def test_inhibit_any_policy(self, backend): cert = _load_cert( os.path.join("x509", "custom", "inhibit_any_policy_5.pem"), x509.load_pem_x509_certificate, ) iap = cert.extensions.get_extension_for_class( x509.InhibitAnyPolicy ).value assert iap.skip_certs == 5 class TestIssuingDistributionPointExtension: @pytest.mark.parametrize( ("filename", "expected"), [ ( "crl_idp_fullname_indirect_crl.pem", x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=True, only_contains_attribute_certs=False, ), ), ( "crl_idp_fullname_only.pem", x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), ), ( "crl_idp_fullname_only_aa.pem", x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=True, ), ), ( "crl_idp_fullname_only_user.pem", x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=True, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), ), ( "crl_idp_only_ca.pem", x509.IssuingDistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA", ) ] ), only_contains_user_certs=False, only_contains_ca_certs=True, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), ), ( "crl_idp_reasons_only.pem", x509.IssuingDistributionPoint( full_name=None, relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=frozenset( [x509.ReasonFlags.key_compromise] ), indirect_crl=False, only_contains_attribute_certs=False, ), ), ( "crl_idp_relative_user_all_reasons.pem", x509.IssuingDistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA", ) ] ), only_contains_user_certs=True, only_contains_ca_certs=False, only_some_reasons=frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, x509.ReasonFlags.affiliation_changed, x509.ReasonFlags.superseded, x509.ReasonFlags.cessation_of_operation, x509.ReasonFlags.certificate_hold, x509.ReasonFlags.privilege_withdrawn, x509.ReasonFlags.aa_compromise, ] ), indirect_crl=False, only_contains_attribute_certs=False, ), ), ( "crl_idp_relativename_only.pem", x509.IssuingDistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA", ) ] ), only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), ), ], ) def test_vectors(self, filename, expected, backend): crl = _load_cert( os.path.join("x509", "custom", filename), x509.load_pem_x509_crl, ) idp = crl.extensions.get_extension_for_class( x509.IssuingDistributionPoint ).value assert idp == expected @pytest.mark.parametrize( ( "error", "only_contains_user_certs", "only_contains_ca_certs", "indirect_crl", "only_contains_attribute_certs", "only_some_reasons", "full_name", "relative_name", ), [ ( TypeError, False, False, False, False, "notafrozenset", None, None, ), ( TypeError, False, False, False, False, frozenset(["bad"]), None, None, ), ( ValueError, False, False, False, False, frozenset([x509.ReasonFlags.unspecified]), None, None, ), ( ValueError, False, False, False, False, frozenset([x509.ReasonFlags.remove_from_crl]), None, None, ), (TypeError, "notabool", False, False, False, None, None, None), (TypeError, False, "notabool", False, False, None, None, None), (TypeError, False, False, "notabool", False, None, None, None), (TypeError, False, False, False, "notabool", None, None, None), (ValueError, True, True, False, False, None, None, None), (ValueError, False, False, True, True, None, None, None), (ValueError, False, False, False, False, None, None, None), ], ) def test_invalid_init( self, error, only_contains_user_certs, only_contains_ca_certs, indirect_crl, only_contains_attribute_certs, only_some_reasons, full_name, relative_name, ): with pytest.raises(error): x509.IssuingDistributionPoint( full_name, relative_name, only_contains_user_certs, only_contains_ca_certs, only_some_reasons, indirect_crl, only_contains_attribute_certs, ) def test_repr(self): idp = x509.IssuingDistributionPoint( None, None, False, False, frozenset([x509.ReasonFlags.key_compromise]), False, False, ) assert repr(idp) == ( "}), indirect_crl=False, only_contains_attribut" "e_certs=False)>" ) def test_eq(self): idp1 = x509.IssuingDistributionPoint( only_contains_user_certs=False, only_contains_ca_certs=False, indirect_crl=False, only_contains_attribute_certs=False, only_some_reasons=None, full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), ) idp2 = x509.IssuingDistributionPoint( only_contains_user_certs=False, only_contains_ca_certs=False, indirect_crl=False, only_contains_attribute_certs=False, only_some_reasons=None, full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), ) assert idp1 == idp2 def test_ne(self): idp1 = x509.IssuingDistributionPoint( only_contains_user_certs=False, only_contains_ca_certs=False, indirect_crl=False, only_contains_attribute_certs=False, only_some_reasons=None, full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), ) idp2 = x509.IssuingDistributionPoint( only_contains_user_certs=True, only_contains_ca_certs=False, indirect_crl=False, only_contains_attribute_certs=False, only_some_reasons=None, full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), ) assert idp1 != idp2 assert idp1 != object() def test_hash(self): idp1 = x509.IssuingDistributionPoint( None, None, True, False, None, False, False ) idp2 = x509.IssuingDistributionPoint( None, None, True, False, None, False, False ) idp3 = x509.IssuingDistributionPoint( None, x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), True, False, None, False, False, ) assert hash(idp1) == hash(idp2) assert hash(idp1) != hash(idp3) @pytest.mark.parametrize( "idp", [ x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=True, only_contains_attribute_certs=False, ), x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=True, ), x509.IssuingDistributionPoint( full_name=[ x509.UniformResourceIdentifier( "http://myhost.com/myca.crl" ) ], relative_name=None, only_contains_user_certs=True, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), x509.IssuingDistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), only_contains_user_certs=False, only_contains_ca_certs=True, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), x509.IssuingDistributionPoint( full_name=None, relative_name=None, only_contains_user_certs=False, only_contains_ca_certs=True, only_some_reasons=frozenset([x509.ReasonFlags.key_compromise]), indirect_crl=False, only_contains_attribute_certs=False, ), x509.IssuingDistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ), x509.NameAttribute( oid=x509.NameOID.COMMON_NAME, value="cryptography" ), ] ), only_contains_user_certs=True, only_contains_ca_certs=False, only_some_reasons=frozenset( [ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, x509.ReasonFlags.affiliation_changed, x509.ReasonFlags.privilege_withdrawn, x509.ReasonFlags.aa_compromise, ] ), indirect_crl=False, only_contains_attribute_certs=False, ), x509.IssuingDistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA" ) ] ), only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ), ], ) def test_generate(self, rsa_key_2048: rsa.RSAPrivateKey, idp, backend): key = rsa_key_2048 last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = ( x509.CertificateRevocationListBuilder() .issuer_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "cryptography.io CA" ) ] ) ) .last_update(last_update) .next_update(next_update) .add_extension(idp, True) ) crl = builder.sign(key, hashes.SHA256(), backend) ext = crl.extensions.get_extension_for_class( x509.IssuingDistributionPoint ) assert ext.critical is True assert ext.value == idp def test_public_bytes(self): ext = x509.IssuingDistributionPoint( full_name=None, relative_name=x509.RelativeDistinguishedName( [ x509.NameAttribute( oid=x509.NameOID.ORGANIZATION_NAME, value="PyCA", ) ] ), only_contains_user_certs=False, only_contains_ca_certs=False, only_some_reasons=None, indirect_crl=False, only_contains_attribute_certs=False, ) assert ( ext.public_bytes() == b"0\x11\xa0\x0f\xa1\r0\x0b\x06\x03U\x04\n\x0c\x04PyCA" ) class TestPrecertPoisonExtension: def test_load(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.precert.pem"), x509.load_pem_x509_certificate, ) poison = cert.extensions.get_extension_for_oid( ExtensionOID.PRECERT_POISON ).value assert isinstance(poison, x509.PrecertPoison) poison = cert.extensions.get_extension_for_class( x509.PrecertPoison ).value assert isinstance(poison, x509.PrecertPoison) def test_generate(self, rsa_key_2048: rsa.RSAPrivateKey, backend): private_key = rsa_key_2048 cert = ( _make_certbuilder(private_key) .add_extension(x509.PrecertPoison(), critical=True) .sign(private_key, hashes.SHA256(), backend) ) poison = cert.extensions.get_extension_for_oid( ExtensionOID.PRECERT_POISON ).value assert isinstance(poison, x509.PrecertPoison) def test_eq(self): pcp1 = x509.PrecertPoison() pcp2 = x509.PrecertPoison() assert pcp1 == pcp2 def test_hash(self): pcp1 = x509.PrecertPoison() pcp2 = x509.PrecertPoison() assert hash(pcp1) == hash(pcp2) def test_ne(self): pcp1 = x509.PrecertPoison() pcp2 = x509.PrecertPoison() assert pcp1 == pcp2 assert (pcp1 != pcp2) is False assert pcp1 != object() def test_repr(self): pcp = x509.PrecertPoison() assert repr(pcp) == "" def test_public_bytes(self): ext = x509.PrecertPoison() assert ext.public_bytes() == b"\x05\x00" class TestSignedCertificateTimestamps: def test_eq(self, backend): sct = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value[0] ) sct2 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value[0] ) assert sct == sct2 def test_ne(self, backend): sct = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value[0] ) sct2 = ( _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value[0] ) assert sct != sct2 assert sct != object() def test_hash(self, backend): sct = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value[0] ) sct2 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value[0] ) sct3 = ( _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value[0] ) assert hash(sct) == hash(sct2) assert hash(sct) != hash(sct3) def test_public_bytes(self, backend): ext = ( load_vectors_from_file( os.path.join("x509", "ocsp", "resp-sct-extension.der"), lambda data: ocsp.load_der_ocsp_response(data.read()), mode="rb", ) .single_extensions.get_extension_for_class( x509.SignedCertificateTimestamps ) .value ) assert ext.public_bytes() == ( b"\x04\x82\x01\xe6\x01\xe4\x00w\x00D\x94e.\xb0\xee\xce\xaf\xc4" b"@\x07\xd8\xa8\xfe(\xc0\xda\xe6\x82\xbe\xd8\xcb1\xb5?\xd33" b"\x96\xb5\xb6\x81\xa8\x00\x00\x01no\xc33h\x00\x00\x04\x03\x00" b"H0F\x02!\x00\xa0}J\xa7\xb1Y\xb4\x15P\xd7\x95Y\x12\xfb\xa1" b"\xdfh\x96u\xa3\x0f_\x01\xf2\xfd\xcbMI\x9bt\xe2\xfe\x02!\x00" b"\x89E\xd7\x86N<>\xe8\x07\xc4\xca\xdbO:\xb7\x9f]E\xbc\x1az" b"\xe5h\xab%\xdaukT\x8a\xf7\xc1\x00w\x00oSv\xac1\xf01\x19\xd8" b"\x99\x00\xa4Q\x15\xffw\x15\x1c\x11\xd9\x02\xc1\x00)\x06\x8d" b"\xb2\x08\x9a7\xd9\x13\x00\x00\x01no\xc33m\x00\x00\x04\x03" b"\x00H0F\x02!\x00\xd4\xe06\xd2\xed~{\x9fs-E2\xd8\xd2\xb41\xc6" b"v\x8b3\xf2\tS\x1d\xd8SUe\xe1\xcf\xfc;\x02!\x00\xd9cF[\x8e\xac" b'4\x02@\xd6\x8a\x10y\x98\x92\xbee\xf4\n\x11L\xbfpI(Y"O\x1al' b"\xe9g\x00w\x00\xbb\xd9\xdf\xbc\x1f\x8aq\xb5\x93\x94#\x97\xaa" b"\x92{G8W\x95\n\xabR\xe8\x1a\x90\x96d6\x8e\x1e\xd1\x85\x00" b"\x00\x01no\xc34g\x00\x00\x04\x03\x00H0F\x02!\x00\xf4:\xec" b"\x1b\xdeQ\r\xf8S\x9c\xf2\xeee<\xcf\xc5:\x0f\x0f\xeb\x8bv\x9f" b'8d.z\x9c"K\x9b\x11\x02!\x00\xe7`\xe9Ex\xf7)B<\xf7\xd62b\xfa' b"\xa2\xc7!\xc4\xbau\xcb\xad\x0ezEZ\x11\x13\xa1+\x89J\x00w\x00" b"\xeeK\xbd\xb7u\xce`\xba\xe1Bi\x1f\xab\xe1\x9ef\xa3\x0f~_\xb0" b"r\xd8\x83\x00\xc4{\x89z\xa8\xfd\xcb\x00\x00\x01no\xc32\xdd" b"\x00\x00\x04\x03\x00H0F\x02!\x00\x95Y\x81\x7f\xa4\xe5\x17o" b"\x06}\xac\xcdt-\xb0\xb8L\x18H\xecB\xcc-\xe5\x13>\x07\xba\xc0" b"}\xa3\xe6\x02!\x00\xbf\xc8\x88\x93m\x8d\xc3(GS\xaf=4}\x97" b"\xe6\xc2\x1djQ\x0e0\x8c\xcc\x9d\xc2\xc7\xc3\xb1\x0f\xec\x98" ) class TestPrecertificateSignedCertificateTimestampsExtension: def test_init(self): with pytest.raises(TypeError): x509.PrecertificateSignedCertificateTimestamps( [object()] # type:ignore[list-item] ) def test_repr(self): assert repr(x509.PrecertificateSignedCertificateTimestamps([])) == ( "" ) def test_eq(self, backend): psct1 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) psct2 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) assert psct1 == psct2 def test_ne(self, backend): psct1 = ( _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) psct2 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) assert psct1 != psct2 assert psct1 != object() def test_ordering(self, backend): psct1 = ( _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) psct2 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) with pytest.raises(TypeError): psct1[0] < psct2[0] def test_hash(self, backend): psct1 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) psct2 = ( _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) psct3 = ( _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) assert hash(psct1) == hash(psct2) assert hash(psct1) != hash(psct3) def test_simple(self, backend): cert = _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) scts = cert.extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ).value assert len(scts) == 1 [sct] = scts assert scts[0] == sct assert sct.version == x509.certificate_transparency.Version.v1 assert sct.log_id == ( b"\xa7\xceJNb\x07\xe0\xad\xde\xe5\xfd\xaaK\x1f\x86v\x87g\xb5\xd0" b"\x02\xa5]G1\x0e~g\n\x95\xea\xb2" ) assert sct.timestamp == datetime.datetime( 2016, 11, 17, 1, 56, 25, 396000 ) assert ( sct.entry_type == x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE ) assert isinstance(sct.signature_hash_algorithm, hashes.SHA256) assert ( sct.signature_algorithm == x509.certificate_transparency.SignatureAlgorithm.ECDSA ) assert sct.signature == ( b"\x30\x45\x02\x21\x00\xb8\x03\xad\x34\xf6\xfc\x0f\x2c\xff\x84\xa0" b"\x86\xe5\xd7\xcf\x5a\xf0\x0a\x07\x62\x6a\x7f\xb3\xa6\x44\x64\xf1" b"\x95\xa4\x48\x45\x11\x02\x20\x2f\x61\x8d\x53\x1b\x6f\x4a\xb8\x0a" b"\x67\xb2\x07\xe1\x8f\x6d\xad\xd1\x04\x4a\x5e\xb3\x89\xef\x7c\x60" b"\xc2\x68\x53\xf9\x3d\x1f\x6d" ) assert sct.extension_bytes == b"" def test_generate(self, rsa_key_2048: rsa.RSAPrivateKey, backend): cert = _load_cert( os.path.join("x509", "badssl-sct.pem"), x509.load_pem_x509_certificate, ) scts = cert.extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ).value assert len(scts) == 1 [sct] = scts private_key = rsa_key_2048 builder = _make_certbuilder(private_key).add_extension( x509.PrecertificateSignedCertificateTimestamps([sct]), critical=False, ) cert = builder.sign(private_key, hashes.SHA256(), backend) ext = cert.extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ).value assert list(ext) == [sct] def test_invalid_version(self, backend): cert = _load_cert( os.path.join("x509", "custom", "invalid-sct-version.der"), x509.load_der_x509_certificate, ) with pytest.raises(ValueError): cert.extensions def test_invalid_hash_algorithm(self, backend): cert = _load_cert( os.path.join("x509", "badssl-sct-none-hash.der"), x509.load_der_x509_certificate, ) with pytest.raises( ValueError, match="Invalid/unsupported hash algorithm for SCT: 0" ): cert.extensions def test_invalid_signature_algorithm(self, backend): cert = _load_cert( os.path.join("x509", "badssl-sct-anonymous-sig.der"), x509.load_der_x509_certificate, ) with pytest.raises( ValueError, match="Invalid/unsupported signature algorithm for SCT: 0", ): cert.extensions def test_invalid_length(self, backend): cert = _load_cert( os.path.join("x509", "custom", "invalid-sct-length.der"), x509.load_der_x509_certificate, ) with pytest.raises(ValueError): cert.extensions def test_public_bytes(self, backend): ext = ( _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, ) .extensions.get_extension_for_class( x509.PrecertificateSignedCertificateTimestamps ) .value ) assert ( ext.public_bytes() == b"\x04\x81\xf4\x00\xf2\x00w\x00)" def test_hash(self): nonce1 = x509.OCSPNonce(b"0" * 5) nonce2 = x509.OCSPNonce(b"0" * 5) nonce3 = x509.OCSPNonce(b"1" * 5) assert hash(nonce1) == hash(nonce2) assert hash(nonce1) != hash(nonce3) def test_public_bytes(self): ext = x509.OCSPNonce(b"0" * 5) assert ext.public_bytes() == b"\x04\x0500000" class TestOCSPAcceptableResponses: def test_invalid_types(self): with pytest.raises(TypeError): x509.OCSPAcceptableResponses(38) # type:ignore[arg-type] with pytest.raises(TypeError): x509.OCSPAcceptableResponses([38]) # type:ignore[list-item] def test_eq(self): acceptable_responses1 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.3")] ) acceptable_responses2 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.3")] ) assert acceptable_responses1 == acceptable_responses2 def test_ne(self): acceptable_responses1 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.3")] ) acceptable_responses2 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.4")] ) assert acceptable_responses1 != acceptable_responses2 assert acceptable_responses1 != object() def test_repr(self): acceptable_responses = x509.OCSPAcceptableResponses([]) assert ( repr(acceptable_responses) == "" ) def test_hash(self): acceptable_responses1 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.3")] ) acceptable_responses2 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.3")] ) acceptable_responses3 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.4")] ) assert hash(acceptable_responses1) == hash(acceptable_responses2) assert hash(acceptable_responses1) != hash(acceptable_responses3) def test_iter(self): acceptable_responses1 = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.2.3")] ) assert list(acceptable_responses1) == [ObjectIdentifier("1.2.3")] def test_public_bytes(self): ext = x509.OCSPAcceptableResponses([]) assert ext.public_bytes() == b"\x30\x00" ext = x509.OCSPAcceptableResponses( [ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")] ) assert ( ext.public_bytes() == b"\x30\x0b\x06\t+\x06\x01\x05\x05\x07\x30\x01\x01" ) class TestMSCertificateTemplate: def test_invalid_type(self): with pytest.raises(TypeError): x509.MSCertificateTemplate( "notanoid", # type:ignore[arg-type] None, None, ) oid = x509.ObjectIdentifier("1.2.3.4") with pytest.raises(TypeError): x509.MSCertificateTemplate( oid, "notanint", # type:ignore[arg-type] None, ) with pytest.raises(TypeError): x509.MSCertificateTemplate( oid, None, "notanint", # type:ignore[arg-type] ) def test_eq(self): template1 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, None ) template2 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, None ) assert template1 == template2 def test_ne(self): template1 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, None ) template2 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), 1, None ) template3 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, 1 ) template4 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3"), None, None ) assert template1 != template2 assert template1 != template3 assert template1 != template4 assert template1 != object() def test_repr(self): template = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, None ) assert repr(template) == ( ", major_version=None, minor_version=None)>" ) def test_hash(self): template1 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, None ) template2 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, None ) template3 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, 1 ) template4 = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3"), None, None ) assert hash(template1) == hash(template2) assert hash(template1) != hash(template3) assert hash(template1) != hash(template4) def test_public_bytes(self): ext = x509.MSCertificateTemplate( ObjectIdentifier("1.2.3.4"), None, None ) assert ext.public_bytes() == b"0\x05\x06\x03*\x03\x04" ext = x509.MSCertificateTemplate(ObjectIdentifier("1.2.3.4"), 1, 0) assert ( ext.public_bytes() == b"0\x0b\x06\x03*\x03\x04\x02\x01\x01\x02\x01\x00" ) def test_all_extension_oid_members_have_names_defined(): for oid in dir(ExtensionOID): if oid.startswith("__"): continue assert getattr(ExtensionOID, oid) in _OID_NAMES def test_unknown_extension(): class MyExtension(ExtensionType): oid = x509.ObjectIdentifier("1.2.3.4") with pytest.raises(NotImplementedError): MyExtension().public_bytes() with pytest.raises(NotImplementedError): rust_x509.encode_extension_value(MyExtension()) cryptography-43.0.0/tests/x509/test_x509_revokedcertbuilder.py010064400017510000177000000166171464676315000224750ustar 00000000000000# 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. import datetime import pytest from cryptography import utils, x509 class TestRevokedCertificateBuilder: def test_serial_number_must_be_integer(self): with pytest.raises(TypeError): x509.RevokedCertificateBuilder().serial_number( "notanx509name" # type: ignore[arg-type] ) def test_serial_number_must_be_non_negative(self): with pytest.raises(ValueError): x509.RevokedCertificateBuilder().serial_number(-1) def test_serial_number_must_be_positive(self): with pytest.raises(ValueError): x509.RevokedCertificateBuilder().serial_number(0) def test_minimal_serial_number(self, backend): revocation_date = datetime.datetime(2002, 1, 1, 12, 1) builder = ( x509.RevokedCertificateBuilder() .serial_number(1) .revocation_date(revocation_date) ) revoked_certificate = builder.build(backend) assert revoked_certificate.serial_number == 1 def test_biggest_serial_number(self, backend): revocation_date = datetime.datetime(2002, 1, 1, 12, 1) builder = ( x509.RevokedCertificateBuilder() .serial_number((1 << 159) - 1) .revocation_date(revocation_date) ) revoked_certificate = builder.build(backend) assert revoked_certificate.serial_number == (1 << 159) - 1 def test_serial_number_must_be_less_than_160_bits_long(self): with pytest.raises(ValueError): x509.RevokedCertificateBuilder().serial_number(1 << 159) def test_set_serial_number_twice(self): builder = x509.RevokedCertificateBuilder().serial_number(3) with pytest.raises(ValueError): builder.serial_number(4) def test_aware_revocation_date(self, backend): tz = datetime.timezone(datetime.timedelta(hours=-8)) time = datetime.datetime(2012, 1, 16, 22, 43, tzinfo=tz) utc_time = datetime.datetime(2012, 1, 17, 6, 43) serial_number = 333 builder = ( x509.RevokedCertificateBuilder() .serial_number(serial_number) .revocation_date(time) ) revoked_certificate = builder.build(backend) with pytest.warns(utils.DeprecatedIn42): assert revoked_certificate.revocation_date == utc_time assert revoked_certificate.revocation_date_utc == utc_time.replace( tzinfo=datetime.timezone.utc ) def test_revocation_date_invalid(self): with pytest.raises(TypeError): x509.RevokedCertificateBuilder().revocation_date( "notadatetime" # type: ignore[arg-type] ) def test_revocation_date_before_1950(self): with pytest.raises(ValueError): x509.RevokedCertificateBuilder().revocation_date( datetime.datetime(1940, 8, 10) ) def test_set_revocation_date_twice(self): builder = x509.RevokedCertificateBuilder().revocation_date( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.revocation_date(datetime.datetime(2002, 1, 1, 12, 1)) def test_add_extension_checks_for_duplicates(self): builder = x509.RevokedCertificateBuilder().add_extension( x509.CRLReason(x509.ReasonFlags.ca_compromise), False ) with pytest.raises(ValueError): builder.add_extension( x509.CRLReason(x509.ReasonFlags.ca_compromise), False ) def test_add_invalid_extension(self): with pytest.raises(TypeError): x509.RevokedCertificateBuilder().add_extension( "notanextension", # type: ignore[arg-type] False, ) def test_no_serial_number(self, backend): builder = x509.RevokedCertificateBuilder().revocation_date( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.build(backend) def test_no_revocation_date(self, backend): builder = x509.RevokedCertificateBuilder().serial_number(3) with pytest.raises(ValueError): builder.build(backend) def test_create_revoked(self, backend): serial_number = 333 revocation_date = datetime.datetime(2002, 1, 1, 12, 1) builder = ( x509.RevokedCertificateBuilder() .serial_number(serial_number) .revocation_date(revocation_date) ) revoked_certificate = builder.build(backend) assert revoked_certificate.serial_number == serial_number with pytest.warns(utils.DeprecatedIn42): assert revoked_certificate.revocation_date == revocation_date assert ( revoked_certificate.revocation_date_utc == revocation_date.replace(tzinfo=datetime.timezone.utc) ) assert len(revoked_certificate.extensions) == 0 @pytest.mark.parametrize( "extension", [ x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)), x509.CRLReason(x509.ReasonFlags.ca_compromise), x509.CertificateIssuer([x509.DNSName("cryptography.io")]), ], ) def test_add_extensions(self, backend, extension): serial_number = 333 revocation_date = datetime.datetime(2002, 1, 1, 12, 1) builder = ( x509.RevokedCertificateBuilder() .serial_number(serial_number) .revocation_date(revocation_date) .add_extension(extension, False) ) revoked_certificate = builder.build(backend) assert revoked_certificate.serial_number == serial_number with pytest.warns(utils.DeprecatedIn42): assert revoked_certificate.revocation_date == revocation_date assert ( revoked_certificate.revocation_date_utc == revocation_date.replace(tzinfo=datetime.timezone.utc) ) assert len(revoked_certificate.extensions) == 1 ext = revoked_certificate.extensions.get_extension_for_class( type(extension) ) assert ext.critical is False assert ext.value == extension def test_add_multiple_extensions(self, backend): serial_number = 333 revocation_date = datetime.datetime(2002, 1, 1, 12, 1) invalidity_date = x509.InvalidityDate( datetime.datetime(2015, 1, 1, 0, 0) ) certificate_issuer = x509.CertificateIssuer( [x509.DNSName("cryptography.io")] ) crl_reason = x509.CRLReason(x509.ReasonFlags.aa_compromise) builder = ( x509.RevokedCertificateBuilder() .serial_number(serial_number) .revocation_date(revocation_date) .add_extension(invalidity_date, True) .add_extension(crl_reason, True) .add_extension(certificate_issuer, True) ) revoked_certificate = builder.build(backend) assert len(revoked_certificate.extensions) == 3 for ext_data in [invalidity_date, certificate_issuer, crl_reason]: ext = revoked_certificate.extensions.get_extension_for_class( type(ext_data) ) assert ext.critical is True assert ext.value == ext_data cryptography-43.0.0/tests/x509/verification/__init__.py010064400017510000177000000000001464676315000211610ustar 00000000000000cryptography-43.0.0/tests/x509/verification/test_limbo.py010064400017510000177000000157451464676315000216110ustar 00000000000000# 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. import datetime import ipaddress import json import os import pytest from cryptography import x509 from cryptography.x509 import load_pem_x509_certificate from cryptography.x509.verification import ( ClientVerifier, PolicyBuilder, ServerVerifier, Store, VerificationError, ) LIMBO_UNSUPPORTED_FEATURES = { # NOTE: Path validation is required to reject wildcards on public suffixes, # however this isn't practical and most implementations make no attempt to # comply with this. "pedantic-public-suffix-wildcard", # TODO: We don't support Distinguished Name Constraints yet. "name-constraint-dn", # Our support for custom EKUs is limited, and we (like most impls.) don't # handle all EKU conditions under CABF. "pedantic-webpki-eku", # Most CABF validators do not enforce the CABF key requirements on # subscriber keys (i.e., in the leaf certificate). "pedantic-webpki-subscriber-key", # Tests that fail based on a strict reading of RFC 5280 # but are widely ignored by validators. "pedantic-rfc5280", # In rare circumstances, CABF relaxes RFC 5280's prescriptions in # incompatible ways. Our validator always tries (by default) to comply # closer to CABF, so we skip these. "rfc5280-incompatible-with-webpki", # We do not support policy constraints. "has-policy-constraints", } LIMBO_SKIP_TESTCASES = { # We unconditionally count intermediate certificates for pathlen and max # depth constraint purposes, even when self-issued. # This is a violation of RFC 5280, but is consistent with Go's crypto/x509 # and Rust's webpki crate do. "pathlen::self-issued-certs-pathlen", "pathlen::max-chain-depth-1-self-issued", # We allow certificates with serial numbers of zero. This is # invalid under RFC 5280 but is widely violated by certs in common # trust stores. "rfc5280::serial::zero", # We allow CAs that don't have AKIs, which is forbidden under # RFC 5280. This is consistent with what Go's crypto/x509 and Rust's # webpki crate do. "rfc5280::ski::root-missing-ski", "rfc5280::ski::intermediate-missing-ski", # We currently allow intermediate CAs that don't have AKIs, which # is technically forbidden under CABF. This is consistent with what # Go's crypto/x509 and Rust's webpki crate do. "rfc5280::aki::intermediate-missing-aki", # We allow root CAs where the AKI and SKI mismatch, which is technically # forbidden under CABF. This is consistent with what # Go's crypto/x509 and Rust's webpki crate do. "webpki::aki::root-with-aki-ski-mismatch", # We allow RSA keys that aren't divisible by 8, which is technically # forbidden under CABF. No other implementation checks this either. "webpki::forbidden-rsa-not-divisable-by-8-in-root", # We disallow CAs in the leaf position, which is explicitly forbidden # by CABF (but implicitly permitted under RFC 5280). This is consistent # with what webpki and rustls do, but inconsistent with Go and OpenSSL. "rfc5280::ca-as-leaf", "pathlen::validation-ignores-pathlen-in-leaf", } def _get_limbo_peer(expected_peer): kind = expected_peer["kind"] assert kind in ("DNS", "IP", "RFC822") value = expected_peer["value"] if kind == "DNS": return x509.DNSName(value) elif kind == "IP": return x509.IPAddress(ipaddress.ip_address(value)) else: return x509.RFC822Name(value) def _limbo_testcase(id_, testcase): if id_ in LIMBO_SKIP_TESTCASES: pytest.skip(f"explicitly skipped testcase: {id_}") features = testcase["features"] unsupported = LIMBO_UNSUPPORTED_FEATURES.intersection(features) if unsupported: pytest.skip(f"explicitly skipped features: {unsupported}") assert testcase["signature_algorithms"] == [] trusted_certs = [ load_pem_x509_certificate(cert.encode()) for cert in testcase["trusted_certs"] ] untrusted_intermediates = [ load_pem_x509_certificate(cert.encode()) for cert in testcase["untrusted_intermediates"] ] peer_certificate = load_pem_x509_certificate( testcase["peer_certificate"].encode() ) validation_time = testcase["validation_time"] validation_time = ( datetime.datetime.fromisoformat(validation_time) if validation_time is not None else None ) max_chain_depth = testcase["max_chain_depth"] should_pass = testcase["expected_result"] == "SUCCESS" builder = PolicyBuilder().store(Store(trusted_certs)) if validation_time is not None: builder = builder.time(validation_time) if max_chain_depth is not None: builder = builder.max_chain_depth(max_chain_depth) verifier: ServerVerifier | ClientVerifier if testcase["validation_kind"] == "SERVER": assert testcase["extended_key_usage"] == [] or testcase[ "extended_key_usage" ] == ["serverAuth"] peer_name = _get_limbo_peer(testcase["expected_peer_name"]) # Some tests exercise invalid leaf SANs, which get caught before # validation even begins. try: verifier = builder.build_server_verifier(peer_name) except ValueError: assert not should_pass return else: assert testcase["extended_key_usage"] == ["clientAuth"] verifier = builder.build_client_verifier() if should_pass: if isinstance(verifier, ServerVerifier): built_chain = verifier.verify( peer_certificate, untrusted_intermediates ) else: verified_client = verifier.verify( peer_certificate, untrusted_intermediates ) expected_subjects = [ _get_limbo_peer(p) for p in testcase["expected_peer_names"] ] assert expected_subjects == verified_client.subjects built_chain = verified_client.chain # Assert that the verifier returns chains in [EE, ..., TA] order. assert built_chain[0] == peer_certificate for intermediate in built_chain[1:-1]: assert intermediate in untrusted_intermediates assert built_chain[-1] in trusted_certs else: with pytest.raises(VerificationError): verifier.verify(peer_certificate, untrusted_intermediates) def test_limbo(subtests, pytestconfig): limbo_root = pytestconfig.getoption("--x509-limbo-root", skip=True) limbo_path = os.path.join(limbo_root, "limbo.json") with open(limbo_path, mode="rb") as limbo_file: limbo = json.load(limbo_file) testcases = limbo["testcases"] for testcase in testcases: with subtests.test(): # NOTE: Pass in the id separately to make pytest # error renderings slightly nicer. _limbo_testcase(testcase["id"], testcase) cryptography-43.0.0/tests/x509/verification/test_verification.py010064400017510000177000000152601464676315000231610ustar 00000000000000# 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. import datetime import os from functools import lru_cache from ipaddress import IPv4Address import pytest from cryptography import x509 from cryptography.x509.general_name import DNSName, IPAddress from cryptography.x509.verification import ( PolicyBuilder, Store, VerificationError, ) from tests.x509.test_x509 import _load_cert @lru_cache(maxsize=1) def dummy_store() -> Store: cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) return Store([cert]) class TestPolicyBuilder: def test_time_already_set(self): with pytest.raises(ValueError): PolicyBuilder().time(datetime.datetime.now()).time( datetime.datetime.now() ) def test_store_already_set(self): with pytest.raises(ValueError): PolicyBuilder().store(dummy_store()).store(dummy_store()) def test_max_chain_depth_already_set(self): with pytest.raises(ValueError): PolicyBuilder().max_chain_depth(8).max_chain_depth(9) def test_ipaddress_subject(self): policy = ( PolicyBuilder() .store(dummy_store()) .build_server_verifier(IPAddress(IPv4Address("0.0.0.0"))) ) assert policy.subject == IPAddress(IPv4Address("0.0.0.0")) def test_dnsname_subject(self): policy = ( PolicyBuilder() .store(dummy_store()) .build_server_verifier(DNSName("cryptography.io")) ) assert policy.subject == DNSName("cryptography.io") def test_subject_bad_types(self): # Subject must be a supported GeneralName type with pytest.raises(TypeError): PolicyBuilder().store(dummy_store()).build_server_verifier( "cryptography.io" # type: ignore[arg-type] ) with pytest.raises(TypeError): PolicyBuilder().store(dummy_store()).build_server_verifier( "0.0.0.0" # type: ignore[arg-type] ) with pytest.raises(TypeError): PolicyBuilder().store(dummy_store()).build_server_verifier( IPv4Address("0.0.0.0") # type: ignore[arg-type] ) with pytest.raises(TypeError): PolicyBuilder().store(dummy_store()).build_server_verifier(None) # type: ignore[arg-type] def test_builder_pattern(self): now = datetime.datetime.now().replace(microsecond=0) store = dummy_store() max_chain_depth = 16 builder = PolicyBuilder() builder = builder.time(now) builder = builder.store(store) builder = builder.max_chain_depth(max_chain_depth) verifier = builder.build_server_verifier(DNSName("cryptography.io")) assert verifier.subject == DNSName("cryptography.io") assert verifier.validation_time == now assert verifier.store == store assert verifier.max_chain_depth == max_chain_depth def test_build_server_verifier_missing_store(self): with pytest.raises( ValueError, match="A server verifier must have a trust store" ): PolicyBuilder().build_server_verifier(DNSName("cryptography.io")) class TestStore: def test_store_rejects_empty_list(self): with pytest.raises(ValueError): Store([]) def test_store_rejects_non_certificates(self): with pytest.raises(TypeError): Store(["not a cert"]) # type: ignore[list-item] class TestClientVerifier: def test_build_client_verifier_missing_store(self): with pytest.raises( ValueError, match="A client verifier must have a trust store" ): PolicyBuilder().build_client_verifier() def test_verify(self): # expires 2018-11-16 01:15:03 UTC leaf = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) store = Store([leaf]) validation_time = datetime.datetime.fromisoformat( "2018-11-16T00:00:00+00:00" ) builder = PolicyBuilder().store(store) builder = builder.time(validation_time).max_chain_depth(16) verifier = builder.build_client_verifier() assert verifier.validation_time == validation_time.replace(tzinfo=None) assert verifier.max_chain_depth == 16 assert verifier.store is store verified_client = verifier.verify(leaf, []) assert verified_client.chain == [leaf] assert x509.DNSName("www.cryptography.io") in verified_client.subjects assert x509.DNSName("cryptography.io") in verified_client.subjects assert len(verified_client.subjects) == 2 def test_verify_fails_renders_oid(self): leaf = _load_cert( os.path.join("x509", "custom", "ekucrit-testuser-cert.pem"), x509.load_pem_x509_certificate, ) store = Store([leaf]) validation_time = datetime.datetime.fromisoformat( "2024-06-26T00:00:00+00:00" ) builder = PolicyBuilder().store(store) builder = builder.time(validation_time) verifier = builder.build_client_verifier() pattern = ( r"invalid extension: 2\.5\.29\.37: " r"Certificate extension has incorrect criticality" ) with pytest.raises( VerificationError, match=pattern, ): verifier.verify(leaf, []) class TestServerVerifier: @pytest.mark.parametrize( ("validation_time", "valid"), [ # 03:15:02 UTC+2, or 1 second before expiry in UTC ("2018-11-16T03:15:02+02:00", True), # 00:15:04 UTC-1, or 1 second after expiry in UTC ("2018-11-16T00:15:04-01:00", False), ], ) def test_verify_tz_aware(self, validation_time, valid): # expires 2018-11-16 01:15:03 UTC leaf = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, ) store = Store([leaf]) builder = PolicyBuilder().store(store) builder = builder.time( datetime.datetime.fromisoformat(validation_time) ) verifier = builder.build_server_verifier(DNSName("cryptography.io")) if valid: assert verifier.verify(leaf, []) == [leaf] else: with pytest.raises( x509.verification.VerificationError, match="cert is not valid at validation time", ): verifier.verify(leaf, []) cryptography-43.0.0/PKG-INFO000064400000012500000000000000010733ustar Metadata-Version: 2.3 Name: cryptography Version: 43.0.0 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: BSD Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography Requires-Dist: cffi >=1.12 ; platform_python_implementation != 'PyPy' Requires-Dist: bcrypt >=3.1.5 ; extra == 'ssh' Requires-Dist: nox ; extra == 'nox' Requires-Dist: cryptography-vectors ==43.0.0 ; extra == 'test' Requires-Dist: pytest >=6.2.0 ; extra == 'test' Requires-Dist: pytest-benchmark ; extra == 'test' Requires-Dist: pytest-cov ; extra == 'test' Requires-Dist: pytest-xdist ; extra == 'test' Requires-Dist: pretend ; extra == 'test' Requires-Dist: certifi ; extra == 'test' Requires-Dist: pytest-randomly ; extra == 'test-randomorder' Requires-Dist: sphinx >=5.3.0 ; extra == 'docs' Requires-Dist: sphinx-rtd-theme >=1.1.1 ; extra == 'docs' Requires-Dist: pyenchant >=1.6.11 ; extra == 'docstest' Requires-Dist: readme-renderer ; extra == 'docstest' Requires-Dist: sphinxcontrib-spelling >=4.0.1 ; extra == 'docstest' Requires-Dist: build ; extra == 'sdist' Requires-Dist: ruff ; extra == 'pep8test' Requires-Dist: mypy ; extra == 'pep8test' Requires-Dist: check-sdist ; extra == 'pep8test' Requires-Dist: click ; extra == 'pep8test' Provides-Extra: ssh Provides-Extra: nox Provides-Extra: test Provides-Extra: test-randomorder Provides-Extra: docs Provides-Extra: docstest Provides-Extra: sdist Provides-Extra: pep8test License-File: LICENSE License-File: LICENSE.APACHE License-File: LICENSE.BSD Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. Author: The cryptography developers Author-email: The Python Cryptographic Authority and individual contributors License: Apache-2.0 OR BSD-3-Clause Requires-Python: >=3.7 Description-Content-Type: text/x-rst; charset=UTF-8 Project-URL: homepage, https://github.com/pyca/cryptography Project-URL: documentation, https://cryptography.io/ Project-URL: source, https://github.com/pyca/cryptography/ Project-URL: issues, https://github.com/pyca/cryptography/issues Project-URL: changelog, https://cryptography.io/en/latest/changelog/ pyca/cryptography ================= .. image:: https://img.shields.io/pypi/v/cryptography.svg :target: https://pypi.org/project/cryptography/ :alt: Latest Version .. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest :target: https://cryptography.io :alt: Latest Docs .. image:: https://github.com/pyca/cryptography/workflows/CI/badge.svg?branch=main :target: https://github.com/pyca/cryptography/actions?query=workflow%3ACI+branch%3Amain ``cryptography`` is a package which provides cryptographic recipes and primitives to Python developers. Our goal is for it to be your "cryptographic standard library". It supports Python 3.7+ and PyPy3 7.3.11+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and key derivation functions. For example, to encrypt something with ``cryptography``'s high level symmetric encryption recipe: .. code-block:: pycon >>> from cryptography.fernet import Fernet >>> # Put this somewhere safe! >>> key = Fernet.generate_key() >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token b'...' >>> f.decrypt(token) b'A really secret message. Not for prying eyes.' You can find more information in the `documentation`_. You can install ``cryptography`` with: .. code-block:: console $ pip install cryptography For full details see `the installation documentation`_. Discussion ~~~~~~~~~~ If you run into bugs, you can file them in our `issue tracker`_. We maintain a `cryptography-dev`_ mailing list for development discussion. You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get involved. Security ~~~~~~~~ Need to report a security issue? Please consult our `security reporting`_ documentation. .. _`documentation`: https://cryptography.io/ .. _`the installation documentation`: https://cryptography.io/en/latest/installation/ .. _`issue tracker`: https://github.com/pyca/cryptography/issues .. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev .. _`security reporting`: https://cryptography.io/en/latest/security/