From 8afa0f797214b7b7ae8b8209a40927bb9f621e53 Mon Sep 17 00:00:00 2001 From: "deepin-community-bot[bot]" <156989552+deepin-community-bot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:02:34 +0000 Subject: [PATCH] feat: update python-cryptography to 41.0.7-5 --- CHANGELOG.rst | 302 ++- LICENSE | 3 - LICENSE.PSF | 41 - MANIFEST.in | 8 +- PKG-INFO | 62 +- README.rst | 8 +- debian/changelog | 47 + debian/control | 32 +- debian/gbp.conf | 2 + ...-US-Pacific-with-America-Los_Angeles.patch | 68 - ...te_into-to-mutate-immutable-objects-.patch | 47 - debian/patches/Upgrade-to-pyo3-0.16.patch | 912 --------- debian/patches/Upgrade-to-pyo3-0.17.patch | 213 -- ...-0.19.patch => Upgrade-to-pyo3-0.20.patch} | 51 +- .../Use-local-python3-doc-inventory.patch | 9 +- debian/patches/allow-pem-version-1.0.patch | 21 - debian/patches/drop-cffi-dep.patch | 35 +- ...ase-asn1-version-from-0.12.1-to-0.12.patch | 21 - ...chrono-dependency-from-0.4.22-to-0.4.patch | 21 - debian/patches/series | 9 +- debian/rules | 4 +- debian/tests/control | 4 +- debian/upstream/signing-key.asc | 94 - debian/watch | 2 +- docs/_ext/cryptography-docs.py | 1 - docs/api-stability.rst | 3 + docs/conf.py | 17 +- .../custom-vectors/arc4/generate_arc4.py | 11 +- .../custom-vectors/cast5/generate_cast5.py | 14 +- .../custom-vectors/hkdf/generate_hkdf.py | 2 +- .../custom-vectors/idea/generate_idea.py | 16 +- .../custom-vectors/idea/verify_idea.py | 4 +- .../rsa-oaep-sha2/generate_rsa_oaep_sha2.py | 5 +- .../secp256k1/generate_secp256k1.py | 15 +- .../secp256k1/verify_secp256k1.py | 1 - .../custom-vectors/seed/generate_seed.py | 16 +- .../custom-vectors/seed/verify_seed.py | 4 +- docs/development/getting-started.rst | 77 +- docs/development/submitting-patches.rst | 8 +- docs/development/test-vectors.rst | 79 +- docs/doing-a-release.rst | 16 +- docs/faq.rst | 61 +- docs/fernet.rst | 8 +- docs/glossary.rst | 11 +- docs/hazmat/primitives/aead.rst | 59 +- docs/hazmat/primitives/asymmetric/dh.rst | 21 - docs/hazmat/primitives/asymmetric/dsa.rst | 16 +- docs/hazmat/primitives/asymmetric/ec.rst | 38 +- docs/hazmat/primitives/asymmetric/ed25519.rst | 28 + docs/hazmat/primitives/asymmetric/ed448.rst | 28 + docs/hazmat/primitives/asymmetric/index.rst | 80 + docs/hazmat/primitives/asymmetric/rsa.rst | 40 +- .../primitives/asymmetric/serialization.rst | 486 ++++- docs/hazmat/primitives/asymmetric/x25519.rst | 27 + docs/hazmat/primitives/asymmetric/x448.rst | 27 + .../primitives/cryptographic-hashes.rst | 4 +- .../primitives/key-derivation-functions.rst | 12 +- docs/hazmat/primitives/mac/hmac.rst | 2 +- docs/hazmat/primitives/mac/poly1305.rst | 2 +- .../primitives/symmetric-encryption.rst | 6 +- docs/hazmat/primitives/twofactor.rst | 11 +- docs/installation.rst | 79 +- docs/limitations.rst | 16 +- docs/openssl.rst | 86 +- docs/random-numbers.rst | 7 +- docs/security.rst | 13 +- docs/spelling_wordlist.txt | 7 + docs/x509/certificate-transparency.rst | 10 +- docs/x509/ocsp.rst | 36 +- docs/x509/reference.rst | 384 +++- noxfile.py | 271 +++ pyproject.toml | 109 +- setup.cfg | 88 - setup.py | 22 +- src/_cffi_src/build_openssl.py | 90 +- src/_cffi_src/openssl/asn1.py | 24 +- src/_cffi_src/openssl/bignum.py | 19 +- src/_cffi_src/openssl/bio.py | 12 +- src/_cffi_src/openssl/callbacks.py | 5 +- src/_cffi_src/openssl/cmac.py | 1 + src/_cffi_src/openssl/conf.py | 18 - src/_cffi_src/openssl/crypto.py | 43 +- src/_cffi_src/openssl/cryptography.py | 88 +- src/_cffi_src/openssl/dh.py | 144 +- src/_cffi_src/openssl/dsa.py | 5 +- src/_cffi_src/openssl/ec.py | 46 +- src/_cffi_src/openssl/ecdsa.py | 1 + src/_cffi_src/openssl/engine.py | 14 +- src/_cffi_src/openssl/err.py | 11 +- src/_cffi_src/openssl/evp.py | 102 +- src/_cffi_src/openssl/evp_aead.py | 88 + src/_cffi_src/openssl/fips.py | 3 +- src/_cffi_src/openssl/hmac.py | 25 - src/_cffi_src/openssl/nid.py | 3 +- src/_cffi_src/openssl/objects.py | 10 +- src/_cffi_src/openssl/opensslv.py | 1 + src/_cffi_src/openssl/osrandom_engine.py | 23 - src/_cffi_src/openssl/pem.py | 8 +- src/_cffi_src/openssl/pkcs12.py | 1 + src/_cffi_src/openssl/pkcs7.py | 14 +- src/_cffi_src/openssl/provider.py | 1 + src/_cffi_src/openssl/rand.py | 2 +- src/_cffi_src/openssl/rsa.py | 11 +- src/_cffi_src/openssl/src/osrandom_engine.c | 660 ------ src/_cffi_src/openssl/src/osrandom_engine.h | 118 -- src/_cffi_src/openssl/ssl.py | 273 +-- src/_cffi_src/openssl/x509.py | 46 +- src/_cffi_src/openssl/x509_vfy.py | 63 +- src/_cffi_src/openssl/x509name.py | 25 +- src/_cffi_src/openssl/x509v3.py | 7 +- src/_cffi_src/utils.py | 51 +- src/cryptography.egg-info/PKG-INFO | 62 +- src/cryptography.egg-info/SOURCES.txt | 82 +- src/cryptography.egg-info/requires.txt | 22 +- src/cryptography/__about__.py | 6 +- src/cryptography/__init__.py | 20 +- src/cryptography/exceptions.py | 30 +- src/cryptography/fernet.py | 3 +- src/cryptography/hazmat/__init__.py | 3 + src/cryptography/hazmat/_oid.py | 14 + src/cryptography/hazmat/backends/__init__.py | 3 + .../hazmat/backends/openssl/__init__.py | 2 +- .../hazmat/backends/openssl/aead.py | 366 +++- .../hazmat/backends/openssl/backend.py | 1337 +++--------- .../hazmat/backends/openssl/ciphers.py | 15 +- .../hazmat/backends/openssl/cmac.py | 10 +- .../hazmat/backends/openssl/decode_asn1.py | 1 + .../hazmat/backends/openssl/dh.py | 318 --- .../hazmat/backends/openssl/dsa.py | 239 --- .../hazmat/backends/openssl/ec.py | 41 +- .../hazmat/backends/openssl/ed25519.py | 155 -- .../hazmat/backends/openssl/ed448.py | 156 -- .../hazmat/backends/openssl/hashes.py | 87 - .../hazmat/backends/openssl/hmac.py | 85 - .../hazmat/backends/openssl/poly1305.py | 68 - .../hazmat/backends/openssl/rsa.py | 71 +- .../hazmat/backends/openssl/utils.py | 19 +- .../hazmat/backends/openssl/x25519.py | 132 -- .../hazmat/backends/openssl/x448.py | 117 -- .../hazmat/backends/openssl/x509.py | 45 - .../hazmat/bindings/_rust/__init__.pyi | 9 +- .../hazmat/bindings/{ => _rust}/_openssl.pyi | 0 .../hazmat/bindings/_rust/exceptions.pyi | 17 + .../hazmat/bindings/_rust/ocsp.pyi | 7 +- .../bindings/_rust/openssl/__init__.pyi | 47 + .../hazmat/bindings/_rust/openssl/dh.pyi | 22 + .../hazmat/bindings/_rust/openssl/dsa.pyi | 20 + .../hazmat/bindings/_rust/openssl/ed25519.pyi | 14 + .../hazmat/bindings/_rust/openssl/ed448.pyi | 14 + .../hazmat/bindings/_rust/openssl/hashes.pyi | 17 + .../hazmat/bindings/_rust/openssl/hmac.pyi | 21 + .../hazmat/bindings/_rust/openssl/kdf.pyi | 22 + .../bindings/_rust/openssl/poly1305.pyi | 13 + .../hazmat/bindings/_rust/openssl/x25519.pyi | 14 + .../hazmat/bindings/_rust/openssl/x448.pyi | 14 + .../hazmat/bindings/_rust/pkcs7.pyi | 15 + .../hazmat/bindings/_rust/x509.pyi | 14 +- .../hazmat/bindings/openssl/_conditional.py | 111 +- .../hazmat/bindings/openssl/binding.py | 179 +- .../hazmat/primitives/_asymmetric.py | 6 +- .../hazmat/primitives/_cipheralgorithm.py | 17 +- .../hazmat/primitives/_serialization.py | 12 +- .../hazmat/primitives/asymmetric/dh.py | 35 +- .../hazmat/primitives/asymmetric/dsa.py | 31 +- .../hazmat/primitives/asymmetric/ec.py | 87 +- .../hazmat/primitives/asymmetric/ed25519.py | 40 +- .../hazmat/primitives/asymmetric/ed448.py | 36 +- .../hazmat/primitives/asymmetric/padding.py | 7 +- .../hazmat/primitives/asymmetric/rsa.py | 38 +- .../hazmat/primitives/asymmetric/types.py | 58 +- .../hazmat/primitives/asymmetric/utils.py | 2 +- .../hazmat/primitives/asymmetric/x25519.py | 40 +- .../hazmat/primitives/asymmetric/x448.py | 38 +- .../hazmat/primitives/ciphers/__init__.py | 2 +- .../hazmat/primitives/ciphers/aead.py | 51 +- .../hazmat/primitives/ciphers/algorithms.py | 21 +- .../hazmat/primitives/ciphers/base.py | 26 +- .../hazmat/primitives/ciphers/modes.py | 20 +- src/cryptography/hazmat/primitives/cmac.py | 13 +- .../hazmat/primitives/constant_time.py | 1 + src/cryptography/hazmat/primitives/hashes.py | 96 +- src/cryptography/hazmat/primitives/hmac.py | 69 +- .../hazmat/primitives/kdf/__init__.py | 1 + .../hazmat/primitives/kdf/concatkdf.py | 14 +- .../hazmat/primitives/kdf/hkdf.py | 8 +- .../hazmat/primitives/kdf/kbkdf.py | 2 + .../hazmat/primitives/kdf/pbkdf2.py | 11 +- .../hazmat/primitives/kdf/scrypt.py | 14 +- .../hazmat/primitives/kdf/x963kdf.py | 10 +- src/cryptography/hazmat/primitives/keywrap.py | 1 + src/cryptography/hazmat/primitives/padding.py | 1 + .../hazmat/primitives/poly1305.py | 57 +- .../primitives/serialization/__init__.py | 18 +- .../hazmat/primitives/serialization/base.py | 29 +- .../hazmat/primitives/serialization/pkcs12.py | 23 +- .../hazmat/primitives/serialization/pkcs7.py | 97 +- .../hazmat/primitives/serialization/ssh.py | 898 +++++++- .../hazmat/primitives/twofactor/__init__.py | 2 + .../hazmat/primitives/twofactor/hotp.py | 10 +- .../hazmat/primitives/twofactor/totp.py | 6 +- src/cryptography/utils.py | 52 +- src/cryptography/x509/__init__.py | 22 +- src/cryptography/x509/base.py | 226 ++- .../x509/certificate_transparency.py | 25 +- src/cryptography/x509/extensions.py | 241 ++- src/cryptography/x509/general_name.py | 31 +- src/cryptography/x509/name.py | 12 +- src/cryptography/x509/ocsp.py | 169 +- src/cryptography/x509/oid.py | 7 +- src/rust/Cargo.lock | 409 ++-- src/rust/Cargo.toml | 22 +- src/rust/build.rs | 20 + src/rust/cryptography-cffi/Cargo.toml | 15 + src/rust/cryptography-cffi/build.rs | 128 ++ src/rust/cryptography-cffi/src/lib.rs | 31 + src/rust/cryptography-openssl/Cargo.toml | 14 + src/rust/cryptography-openssl/build.rs | 24 + src/rust/cryptography-openssl/src/fips.rs | 32 + src/rust/cryptography-openssl/src/hmac.rs | 93 + src/rust/cryptography-openssl/src/lib.rs | 37 + src/rust/cryptography-x509/Cargo.toml | 11 + src/rust/cryptography-x509/src/certificate.rs | 48 + src/rust/cryptography-x509/src/common.rs | 333 +++ src/rust/cryptography-x509/src/crl.rs | 73 + src/rust/cryptography-x509/src/csr.rs | 70 + src/rust/cryptography-x509/src/extensions.rs | 255 +++ src/rust/cryptography-x509/src/lib.rs | 18 + src/rust/cryptography-x509/src/name.rs | 88 + src/rust/cryptography-x509/src/ocsp_req.rs | 50 + src/rust/cryptography-x509/src/ocsp_resp.rs | 91 + src/rust/cryptography-x509/src/oid.rs | 99 + src/rust/cryptography-x509/src/pkcs7.rs | 60 + src/rust/src/asn1.rs | 262 +-- src/rust/src/backend/dh.rs | 444 ++++ src/rust/src/backend/dsa.rs | 333 +++ src/rust/src/backend/ed25519.rs | 178 ++ src/rust/src/backend/ed448.rs | 175 ++ src/rust/src/backend/hashes.rs | 143 ++ src/rust/src/backend/hmac.rs | 95 + src/rust/src/backend/kdf.rs | 60 + src/rust/src/backend/mod.rs | 42 + src/rust/src/backend/poly1305.rs | 127 ++ src/rust/src/backend/utils.rs | 302 +++ src/rust/src/backend/x25519.rs | 166 ++ src/rust/src/backend/x448.rs | 165 ++ src/rust/src/buf.rs | 49 + src/rust/src/error.rs | 144 ++ src/rust/src/exceptions.rs | 40 + src/rust/src/intern.rs | 44 - src/rust/src/lib.rs | 88 +- src/rust/src/oid.rs | 24 +- src/rust/src/pkcs7.rs | 389 ++++ src/rust/src/pool.rs | 29 +- src/rust/src/x509/certificate.rs | 752 ++++--- src/rust/src/x509/common.rs | 565 ++---- src/rust/src/x509/crl.rs | 540 ++--- src/rust/src/x509/csr.rs | 367 ++-- src/rust/src/x509/extensions.rs | 612 +++--- src/rust/src/x509/mod.rs | 9 +- src/rust/src/x509/ocsp.rs | 165 +- src/rust/src/x509/ocsp_req.rs | 201 +- src/rust/src/x509/ocsp_resp.rs | 883 ++++---- src/rust/src/x509/oid.rs | 101 - src/rust/src/x509/sct.rs | 102 +- src/rust/src/x509/sign.rs | 786 +++++-- tests/bench/test_aead.py | 88 +- tests/bench/test_ec_load.py | 13 + tests/bench/test_hashes.py | 14 + tests/bench/test_hmac.py | 14 + tests/bench/test_x509.py | 11 +- tests/conftest.py | 32 +- tests/hazmat/backends/test_openssl.py | 288 +-- tests/hazmat/backends/test_openssl_memleak.py | 300 +-- tests/hazmat/bindings/test_openssl.py | 59 +- tests/hazmat/primitives/fixtures_dsa.py | 1 - tests/hazmat/primitives/fixtures_ec.py | 1 - tests/hazmat/primitives/fixtures_rsa.py | 1 - tests/hazmat/primitives/test_3des.py | 2 +- tests/hazmat/primitives/test_aead.py | 32 +- tests/hazmat/primitives/test_aes.py | 10 +- tests/hazmat/primitives/test_aes_gcm.py | 2 +- tests/hazmat/primitives/test_arc4.py | 2 +- tests/hazmat/primitives/test_block.py | 4 +- tests/hazmat/primitives/test_blowfish.py | 2 +- tests/hazmat/primitives/test_camellia.py | 2 +- tests/hazmat/primitives/test_cast5.py | 10 +- tests/hazmat/primitives/test_chacha20.py | 2 +- tests/hazmat/primitives/test_ciphers.py | 8 + tests/hazmat/primitives/test_cmac.py | 11 +- tests/hazmat/primitives/test_concatkdf.py | 6 +- tests/hazmat/primitives/test_dh.py | 31 +- tests/hazmat/primitives/test_dsa.py | 31 +- tests/hazmat/primitives/test_ec.py | 89 +- tests/hazmat/primitives/test_ed25519.py | 65 +- tests/hazmat/primitives/test_ed448.py | 35 +- tests/hazmat/primitives/test_hash_vectors.py | 2 +- tests/hazmat/primitives/test_hashes.py | 2 +- tests/hazmat/primitives/test_hkdf.py | 5 +- tests/hazmat/primitives/test_hkdf_vectors.py | 2 +- tests/hazmat/primitives/test_hmac.py | 7 +- tests/hazmat/primitives/test_hmac_vectors.py | 2 +- tests/hazmat/primitives/test_idea.py | 10 +- tests/hazmat/primitives/test_kbkdf.py | 2 +- tests/hazmat/primitives/test_kbkdf_vectors.py | 2 +- tests/hazmat/primitives/test_keywrap.py | 2 +- .../primitives/test_pbkdf2hmac_vectors.py | 27 +- tests/hazmat/primitives/test_pkcs12.py | 42 +- tests/hazmat/primitives/test_pkcs7.py | 78 +- tests/hazmat/primitives/test_rsa.py | 527 +++-- tests/hazmat/primitives/test_scrypt.py | 8 +- tests/hazmat/primitives/test_seed.py | 10 +- tests/hazmat/primitives/test_serialization.py | 1040 +--------- tests/hazmat/primitives/test_sm4.py | 12 +- tests/hazmat/primitives/test_ssh.py | 1808 +++++++++++++++++ tests/hazmat/primitives/test_x25519.py | 88 +- tests/hazmat/primitives/test_x448.py | 34 +- .../hazmat/primitives/twofactor/test_hotp.py | 5 +- .../hazmat/primitives/twofactor/test_totp.py | 5 +- tests/hazmat/primitives/utils.py | 46 +- tests/hazmat/test_oid.py | 10 + tests/hypothesis/__init__.py | 3 - tests/hypothesis/test_fernet.py | 16 - tests/hypothesis/test_padding.py | 34 - tests/hypothesis/test_x509.py | 22 - tests/test_cryptography_utils.py | 8 +- tests/test_fernet.py | 14 +- tests/test_interfaces.py | 80 - tests/test_rust_utils.py | 16 +- tests/test_utils.py | 4 +- tests/utils.py | 44 +- tests/wycheproof/test_aes.py | 2 +- tests/wycheproof/test_chacha20poly1305.py | 2 +- tests/wycheproof/test_dsa.py | 5 +- tests/wycheproof/test_ecdh.py | 33 +- tests/wycheproof/test_ecdsa.py | 15 +- tests/wycheproof/test_eddsa.py | 2 +- tests/wycheproof/test_hkdf.py | 1 - tests/wycheproof/test_hmac.py | 3 +- tests/wycheproof/test_rsa.py | 97 +- tests/wycheproof/utils.py | 4 +- tests/x509/test_name.py | 1 - tests/x509/test_ocsp.py | 93 +- tests/x509/test_x509.py | 1403 +++++++++---- tests/x509/test_x509_crlbuilder.py | 111 +- tests/x509/test_x509_ext.py | 320 +-- tests/x509/test_x509_revokedcertbuilder.py | 7 +- 346 files changed, 17480 insertions(+), 13473 deletions(-) delete mode 100644 LICENSE.PSF create mode 100644 debian/gbp.conf delete mode 100644 debian/patches/0010-Replace-US-Pacific-with-America-Los_Angeles.patch delete mode 100644 debian/patches/Don-t-allow-update_into-to-mutate-immutable-objects-.patch delete mode 100644 debian/patches/Upgrade-to-pyo3-0.16.patch delete mode 100644 debian/patches/Upgrade-to-pyo3-0.17.patch rename debian/patches/{Upgrade-to-pyo3-0.19.patch => Upgrade-to-pyo3-0.20.patch} (59%) delete mode 100644 debian/patches/allow-pem-version-1.0.patch delete mode 100644 debian/patches/ease-asn1-version-from-0.12.1-to-0.12.patch delete mode 100644 debian/patches/ease-chrono-dependency-from-0.4.22-to-0.4.patch delete mode 100644 debian/upstream/signing-key.asc create mode 100644 noxfile.py delete mode 100644 src/_cffi_src/openssl/conf.py create mode 100644 src/_cffi_src/openssl/evp_aead.py delete mode 100644 src/_cffi_src/openssl/hmac.py delete mode 100644 src/_cffi_src/openssl/osrandom_engine.py delete mode 100644 src/_cffi_src/openssl/src/osrandom_engine.c delete mode 100644 src/_cffi_src/openssl/src/osrandom_engine.h delete mode 100644 src/cryptography/hazmat/backends/openssl/dh.py delete mode 100644 src/cryptography/hazmat/backends/openssl/dsa.py delete mode 100644 src/cryptography/hazmat/backends/openssl/ed25519.py delete mode 100644 src/cryptography/hazmat/backends/openssl/ed448.py delete mode 100644 src/cryptography/hazmat/backends/openssl/hashes.py delete mode 100644 src/cryptography/hazmat/backends/openssl/hmac.py delete mode 100644 src/cryptography/hazmat/backends/openssl/poly1305.py delete mode 100644 src/cryptography/hazmat/backends/openssl/x25519.py delete mode 100644 src/cryptography/hazmat/backends/openssl/x448.py delete mode 100644 src/cryptography/hazmat/backends/openssl/x509.py rename src/cryptography/hazmat/bindings/{ => _rust}/_openssl.pyi (100%) create mode 100644 src/cryptography/hazmat/bindings/_rust/exceptions.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi create mode 100644 src/cryptography/hazmat/bindings/_rust/pkcs7.pyi create mode 100644 src/rust/build.rs create mode 100644 src/rust/cryptography-cffi/Cargo.toml create mode 100644 src/rust/cryptography-cffi/build.rs create mode 100644 src/rust/cryptography-cffi/src/lib.rs create mode 100644 src/rust/cryptography-openssl/Cargo.toml create mode 100644 src/rust/cryptography-openssl/build.rs create mode 100644 src/rust/cryptography-openssl/src/fips.rs create mode 100644 src/rust/cryptography-openssl/src/hmac.rs create mode 100644 src/rust/cryptography-openssl/src/lib.rs create mode 100644 src/rust/cryptography-x509/Cargo.toml create mode 100644 src/rust/cryptography-x509/src/certificate.rs create mode 100644 src/rust/cryptography-x509/src/common.rs create mode 100644 src/rust/cryptography-x509/src/crl.rs create mode 100644 src/rust/cryptography-x509/src/csr.rs create mode 100644 src/rust/cryptography-x509/src/extensions.rs create mode 100644 src/rust/cryptography-x509/src/lib.rs create mode 100644 src/rust/cryptography-x509/src/name.rs create mode 100644 src/rust/cryptography-x509/src/ocsp_req.rs create mode 100644 src/rust/cryptography-x509/src/ocsp_resp.rs create mode 100644 src/rust/cryptography-x509/src/oid.rs create mode 100644 src/rust/cryptography-x509/src/pkcs7.rs create mode 100644 src/rust/src/backend/dh.rs create mode 100644 src/rust/src/backend/dsa.rs create mode 100644 src/rust/src/backend/ed25519.rs create mode 100644 src/rust/src/backend/ed448.rs create mode 100644 src/rust/src/backend/hashes.rs create mode 100644 src/rust/src/backend/hmac.rs create mode 100644 src/rust/src/backend/kdf.rs create mode 100644 src/rust/src/backend/mod.rs create mode 100644 src/rust/src/backend/poly1305.rs create mode 100644 src/rust/src/backend/utils.rs create mode 100644 src/rust/src/backend/x25519.rs create mode 100644 src/rust/src/backend/x448.rs create mode 100644 src/rust/src/buf.rs create mode 100644 src/rust/src/error.rs create mode 100644 src/rust/src/exceptions.rs delete mode 100644 src/rust/src/intern.rs create mode 100644 src/rust/src/pkcs7.rs delete mode 100644 src/rust/src/x509/oid.rs create mode 100644 tests/bench/test_ec_load.py create mode 100644 tests/bench/test_hashes.py create mode 100644 tests/bench/test_hmac.py create mode 100644 tests/hazmat/primitives/test_ssh.py delete mode 100644 tests/hypothesis/__init__.py delete mode 100644 tests/hypothesis/test_fernet.py delete mode 100644 tests/hypothesis/test_padding.py delete mode 100644 tests/hypothesis/test_x509.py delete mode 100644 tests/test_interfaces.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 27c485f..4aa2556 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,254 @@ Changelog ========= +.. _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 @@ -20,8 +268,8 @@ Changelog .. _v38-0-2: -38.0.2 - 2022-10-11 -~~~~~~~~~~~~~~~~~~~ +38.0.2 - 2022-10-11 (YANKED) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. attention:: @@ -29,6 +277,7 @@ Changelog * Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL 3.0.6. + .. _v38-0-1: 38.0.1 - 2022-09-07 @@ -768,7 +1017,7 @@ Changelog 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 - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point`. + ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point``. .. _v2-4-2: @@ -1467,7 +1716,7 @@ Changelog * Added a ``__hash__`` method to :class:`~cryptography.x509.Name`. * Add support for encoding and decoding elliptic curve points to a byte string form using - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point` + ``cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point`` and :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point`. * Added :meth:`~cryptography.x509.Extensions.get_extension_for_class`. @@ -1614,16 +1863,16 @@ Changelog ``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.RSAPublicKeyWithSerialization`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, and - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. + :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.RSAPrivateKeyWithSerialization`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, and - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`. + :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`. @@ -1696,42 +1945,32 @@ Changelog and :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` now support PKCS1 RSA public keys (in addition to the previous support for SubjectPublicKeyInfo format for RSA, EC, and DSA). -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - and deprecated ``EllipticCurvePrivateKeyWithNumbers``. +* Added ``EllipticCurvePrivateKeyWithSerialization`` and deprecated + ``EllipticCurvePrivateKeyWithNumbers``. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.private_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` - and deprecated ``RSAPrivateKeyWithNumbers``. +* Added ``RSAPrivateKeyWithSerialization`` and deprecated ``RSAPrivateKeyWithNumbers``. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.private_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` - and deprecated ``DSAPrivateKeyWithNumbers``. +* Added ``DSAPrivateKeyWithSerialization`` and deprecated ``DSAPrivateKeyWithNumbers``. * Added :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.private_bytes` to :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` - and deprecated ``RSAPublicKeyWithNumbers``. +* Added ``RSAPublicKeyWithSerialization`` and deprecated ``RSAPublicKeyWithNumbers``. * Added ``public_bytes`` to - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization` - and deprecated ``EllipticCurvePublicKeyWithNumbers``. + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. +* Added ``EllipticCurvePublicKeyWithSerialization`` and deprecated + ``EllipticCurvePublicKeyWithNumbers``. * Added ``public_bytes`` to - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. -* Added - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization` - and deprecated ``DSAPublicKeyWithNumbers``. + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. +* Added ``DSAPublicKeyWithSerialization`` and deprecated ``DSAPublicKeyWithNumbers``. * Added ``public_bytes`` to - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`. + :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 @@ -2049,5 +2288,6 @@ Changelog * Initial release. +.. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic .. _`main`: https://github.com/pyca/cryptography/ .. _`cffi`: https://cffi.readthedocs.io/ diff --git a/LICENSE b/LICENSE index 0707425..b11f379 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,3 @@ This 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. - -The code used in the OS random engine is derived from CPython, and is licensed -under the terms of the PSF License Agreement. diff --git a/LICENSE.PSF b/LICENSE.PSF deleted file mode 100644 index 4d3a4f5..0000000 --- a/LICENSE.PSF +++ /dev/null @@ -1,41 +0,0 @@ -1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and - the Individual or Organization ("Licensee") accessing and otherwise using Python - 2.7.12 software in source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby - grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, - analyze, test, perform and/or display publicly, prepare derivative works, - distribute, and otherwise use Python 2.7.12 alone or in any derivative - version, provided, however, that PSF's License Agreement and PSF's notice of - copyright, i.e., "Copyright © 2001-2016 Python Software Foundation; All Rights - Reserved" are retained in Python 2.7.12 alone or in any derivative version - prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on or - incorporates Python 2.7.12 or any part thereof, and wants to make the - derivative work available to others as provided herein, then Licensee hereby - agrees to include in any such work a brief summary of the changes made to Python - 2.7.12. - -4. PSF is making Python 2.7.12 available to Licensee on an "AS IS" basis. - PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF - EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR - WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE - USE OF PYTHON 2.7.12 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.7.12 - FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF - MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.7.12, OR ANY DERIVATIVE - THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material breach of - its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any relationship - of agency, partnership, or joint venture between PSF and Licensee. This License - Agreement does not grant permission to use PSF trademarks or trade name in a - trademark sense to endorse or promote products or services of Licensee, or any - third party. - -8. By copying, installing or otherwise using Python 2.7.12, Licensee agrees - to be bound by the terms and conditions of this License Agreement. diff --git a/MANIFEST.in b/MANIFEST.in index 8471d75..dcffd60 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,8 +3,8 @@ include CONTRIBUTING.rst include LICENSE include LICENSE.APACHE include LICENSE.BSD -include LICENSE.PSF include README.rst +include noxfile.py include pyproject.toml recursive-include src py.typed *.pyi @@ -16,9 +16,9 @@ prune docs/_build recursive-include tests *.py exclude vectors recursive-exclude vectors * +exclude src/rust/target +recursive-exclude src/rust/target * recursive-exclude .github * -exclude release.py .readthedocs.yml dev-requirements.txt tox.ini mypy.ini - -recursive-exclude .circleci * +exclude release.py .readthedocs.yml ci-constraints-requirements.txt mypy.ini diff --git a/PKG-INFO b/PKG-INFO index 2f4c544..99cb9cc 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,16 +1,14 @@ Metadata-Version: 2.1 Name: cryptography -Version: 38.0.4 +Version: 41.0.7 Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. -Home-page: https://github.com/pyca/cryptography -Author: The Python Cryptographic Authority and individual contributors -Author-email: cryptography-dev@python.org -License: BSD-3-Clause OR Apache-2.0 -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/ -Platform: UNKNOWN +Author-email: The Python Cryptographic Authority and individual contributors +License: Apache-2.0 OR BSD-3-Clause +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/ Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License @@ -24,26 +22,46 @@ 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.6 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography -Requires-Python: >=3.6 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: LICENSE.APACHE +License-File: LICENSE.BSD +Requires-Dist: cffi>=1.12 +Provides-Extra: ssh +Requires-Dist: bcrypt>=3.1.5; extra == "ssh" +Provides-Extra: nox +Requires-Dist: nox; extra == "nox" Provides-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" +Provides-Extra: test-randomorder +Requires-Dist: pytest-randomly; extra == "test-randomorder" Provides-Extra: docs +Requires-Dist: sphinx>=5.3.0; extra == "docs" +Requires-Dist: sphinx-rtd-theme>=1.1.1; extra == "docs" Provides-Extra: docstest +Requires-Dist: pyenchant>=1.6.11; extra == "docstest" +Requires-Dist: twine>=1.12.0; extra == "docstest" +Requires-Dist: sphinxcontrib-spelling>=4.0.1; extra == "docstest" Provides-Extra: sdist +Requires-Dist: build; extra == "sdist" Provides-Extra: pep8test -Provides-Extra: ssh -License-File: LICENSE -License-File: LICENSE.APACHE -License-File: LICENSE.BSD -License-File: LICENSE.PSF +Requires-Dist: black; extra == "pep8test" +Requires-Dist: ruff; extra == "pep8test" +Requires-Dist: mypy; extra == "pep8test" +Requires-Dist: check-sdist; extra == "pep8test" pyca/cryptography ================= @@ -61,8 +79,8 @@ pyca/cryptography ``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.6+ and PyPy3 7.2+. +primitives to Python developers. Our goal is for it to be your "cryptographic +standard library". It supports Python 3.7+ and PyPy3 7.3.10+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and @@ -77,9 +95,9 @@ key derivation functions. For example, to encrypt something with >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token - '...' + b'...' >>> f.decrypt(token) - 'A really secret message. Not for prying eyes.' + b'A really secret message. Not for prying eyes.' You can find more information in the `documentation`_. @@ -113,5 +131,3 @@ documentation. .. _`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/ - - diff --git a/README.rst b/README.rst index 9b260f5..d71765b 100644 --- a/README.rst +++ b/README.rst @@ -14,8 +14,8 @@ pyca/cryptography ``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.6+ and PyPy3 7.2+. +primitives to Python developers. Our goal is for it to be your "cryptographic +standard library". It supports Python 3.7+ and PyPy3 7.3.10+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and @@ -30,9 +30,9 @@ key derivation functions. For example, to encrypt something with >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token - '...' + b'...' >>> f.decrypt(token) - 'A really secret message. Not for prying eyes.' + b'A really secret message. Not for prying eyes.' You can find more information in the `documentation`_. diff --git a/debian/changelog b/debian/changelog index d284662..38be6ee 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,50 @@ +python-cryptography (41.0.7-5) unstable; urgency=medium + + * AMAU, Closes: #1064979 + + [ Andreas Tille ] + * Enable building twice in a row + + -- Jérémy Lal Thu, 07 Mar 2024 13:42:35 +0100 + +python-cryptography (41.0.7-4) unstable; urgency=medium + + * orphan + + -- Sandro Tosi Wed, 28 Feb 2024 12:23:58 -0500 + +python-cryptography (41.0.7-3) unstable; urgency=medium + + * Upgrade to pyo3 0.20. Closes: #1063365 + + -- Jérémy Lal Thu, 08 Feb 2024 15:34:30 +0100 + +python-cryptography (41.0.7-2) unstable; urgency=medium + + * patch: drop pem 1.0 workaround, depends pem 1.1. Closes: 1060294. + * autopkgtest: fix version of cryptography-vectors + + -- Jérémy Lal Tue, 09 Jan 2024 01:14:48 +0100 + +python-cryptography (41.0.7-1) unstable; urgency=medium + + * Team upload + * New upstream version 41.0.7 + * patch: remove n/a, disable a test to keep building with pem 1.0 + * Update deps + * Bump Standards-Version + + [ Sandro Tosi ] + * New upstream release; Closes: #1031049 + - fixes CVE-2023-23931 + * debian/watch + - remove pgpsigurlmangle, .asc file no longer on PyPI + + [ Nicolas Dandrimont ] + * New upstream version 41.0.3 + + -- Jérémy Lal Sun, 07 Jan 2024 13:24:39 +0100 + python-cryptography (38.0.4-4) unstable; urgency=medium * Team Upload. diff --git a/debian/control b/debian/control index 4f71768..fcb8697 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: python-cryptography -Maintainer: Tristan Seligmann -Uploaders: Debian Python Team , - Sandro Tosi , +Maintainer: Debian Python Team +Uploaders: + Jérémy Lal , Section: python Priority: optional Build-Depends: cargo, @@ -9,21 +9,26 @@ Build-Depends: cargo, dh-sequence-python3, dh-sequence-sphinxdoc , dpkg-dev (>= 1.17.14), - librust-asn1-0.12-dev, - librust-asn1-derive-0.12-dev, - librust-chrono-0.4-dev, + librust-asn1-0.15-dev, + librust-asn1-derive-0.15-dev, + librust-cfg-if-dev, + librust-foreign-types-shared-0.1-dev, + librust-once-cell-dev, + librust-openssl-dev, + librust-openssl-sys-dev, librust-ouroboros-0.15-dev, librust-paste-dev, - librust-pem-1.0-dev, - librust-pyo3-0.19-dev, - librust-pyo3-0.19+default-dev, - librust-pyo3-macros-0.19-dev, + librust-pem-1.1-dev, + librust-pyo3-0.20-dev, + librust-pyo3-0.20+default-dev, + librust-pyo3-macros-0.20-dev, + librust-self-cell-dev, libssl-dev, pybuild-plugin-pyproject, python3-all-dev, python3-cffi, - python3-cryptography-vectors (<< 38.0.5~) , - python3-cryptography-vectors (>= 38.0.4~) , + python3-cryptography-vectors (<< 41.0.8~) , + python3-cryptography-vectors (>= 41.0.7~) , python3-doc , python3-hypothesis , python3-iso8601 , @@ -33,12 +38,11 @@ Build-Depends: cargo, python3-pytest-subtests , python3-setuptools, python3-setuptools-rust, - python3-six, python3-sphinx , python3-sphinx-rtd-theme , python3-tz , tzdata , -Standards-Version: 4.6.1 +Standards-Version: 4.6.2 Homepage: https://cryptography.io/ Vcs-Git: https://salsa.debian.org/python-team/packages/python-cryptography.git Vcs-Browser: https://salsa.debian.org/python-team/packages/python-cryptography diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..c85a432 --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,2 @@ +[DEFAULT] +debian-branch = debian/41.x diff --git a/debian/patches/0010-Replace-US-Pacific-with-America-Los_Angeles.patch b/debian/patches/0010-Replace-US-Pacific-with-America-Los_Angeles.patch deleted file mode 100644 index f30d0a1..0000000 --- a/debian/patches/0010-Replace-US-Pacific-with-America-Los_Angeles.patch +++ /dev/null @@ -1,68 +0,0 @@ -From: Nicolas Dandrimont -Date: Tue, 8 Aug 2023 17:06:18 +0200 -Subject: Replace US/Pacific with America/Los_Angeles - -Needed for recent tzdata versions ---- - tests/x509/test_x509.py | 4 ++-- - tests/x509/test_x509_crlbuilder.py | 4 ++-- - tests/x509/test_x509_revokedcertbuilder.py | 2 +- - 3 files changed, 5 insertions(+), 5 deletions(-) - -diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py -index aecf6f8..4d496c6 100644 ---- a/tests/x509/test_x509.py -+++ b/tests/x509/test_x509.py -@@ -2445,7 +2445,7 @@ class TestCertificateBuilder: - - def test_aware_not_valid_after(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) -- tz = pytz.timezone("US/Pacific") -+ tz = pytz.timezone("America/Los_Angeles") - time = tz.localize(time) - utc_time = datetime.datetime(2012, 1, 17, 6, 43) - private_key = RSA_KEY_2048.private_key(backend) -@@ -2517,7 +2517,7 @@ class TestCertificateBuilder: - - def test_aware_not_valid_before(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) -- tz = pytz.timezone("US/Pacific") -+ tz = pytz.timezone("America/Los_Angeles") - time = tz.localize(time) - utc_time = datetime.datetime(2012, 1, 17, 6, 43) - private_key = RSA_KEY_2048.private_key(backend) -diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py -index e3e3428..d5083c2 100644 ---- a/tests/x509/test_x509_crlbuilder.py -+++ b/tests/x509/test_x509_crlbuilder.py -@@ -42,7 +42,7 @@ class TestCertificateRevocationListBuilder: - - def test_aware_last_update(self, backend): - last_time = datetime.datetime(2012, 1, 16, 22, 43) -- tz = pytz.timezone("US/Pacific") -+ tz = pytz.timezone("America/Los_Angeles") - last_time = tz.localize(last_time) - utc_last = datetime.datetime(2012, 1, 17, 6, 43) - next_time = datetime.datetime(2022, 1, 17, 6, 43) -@@ -84,7 +84,7 @@ class TestCertificateRevocationListBuilder: - - def test_aware_next_update(self, backend): - next_time = datetime.datetime(2022, 1, 16, 22, 43) -- tz = pytz.timezone("US/Pacific") -+ tz = pytz.timezone("America/Los_Angeles") - next_time = tz.localize(next_time) - utc_next = datetime.datetime(2022, 1, 17, 6, 43) - last_time = datetime.datetime(2012, 1, 17, 6, 43) -diff --git a/tests/x509/test_x509_revokedcertbuilder.py b/tests/x509/test_x509_revokedcertbuilder.py -index b2facfa..f047c5c 100644 ---- a/tests/x509/test_x509_revokedcertbuilder.py -+++ b/tests/x509/test_x509_revokedcertbuilder.py -@@ -60,7 +60,7 @@ class TestRevokedCertificateBuilder: - - def test_aware_revocation_date(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) -- tz = pytz.timezone("US/Pacific") -+ tz = pytz.timezone("America/Los_Angeles") - time = tz.localize(time) - utc_time = datetime.datetime(2012, 1, 17, 6, 43) - serial_number = 333 diff --git a/debian/patches/Don-t-allow-update_into-to-mutate-immutable-objects-.patch b/debian/patches/Don-t-allow-update_into-to-mutate-immutable-objects-.patch deleted file mode 100644 index 8c0cead..0000000 --- a/debian/patches/Don-t-allow-update_into-to-mutate-immutable-objects-.patch +++ /dev/null @@ -1,47 +0,0 @@ -From: Alex Gaynor -Date: Tue, 7 Feb 2023 11:34:18 -0500 -Subject: Don't allow update_into to mutate immutable objects (#8230) -Origin: https://github.com/pyca/cryptography/commit/9fbf84efc861668755ab645530ec7be9cf3c6696 -Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2023-23931 -Bug-Debian: https://bugs.debian.org/1031049 - ---- - src/cryptography/hazmat/backends/openssl/ciphers.py | 2 +- - tests/hazmat/primitives/test_ciphers.py | 8 ++++++++ - 2 files changed, 9 insertions(+), 1 deletion(-) - -diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py -index 286583f93255..075d68fb9057 100644 ---- a/src/cryptography/hazmat/backends/openssl/ciphers.py -+++ b/src/cryptography/hazmat/backends/openssl/ciphers.py -@@ -156,7 +156,7 @@ class _CipherContext: - data_processed = 0 - total_out = 0 - outlen = self._backend._ffi.new("int *") -- baseoutbuf = self._backend._ffi.from_buffer(buf) -+ baseoutbuf = self._backend._ffi.from_buffer(buf, require_writable=True) - baseinbuf = self._backend._ffi.from_buffer(data) - - while data_processed != total_data_len: -diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py -index 02127dd9cab5..bf3b047dec25 100644 ---- a/tests/hazmat/primitives/test_ciphers.py -+++ b/tests/hazmat/primitives/test_ciphers.py -@@ -318,6 +318,14 @@ class TestCipherUpdateInto: - with pytest.raises(ValueError): - encryptor.update_into(b"testing", buf) - -+ def test_update_into_immutable(self, backend): -+ key = b"\x00" * 16 -+ c = ciphers.Cipher(AES(key), modes.ECB(), backend) -+ encryptor = c.encryptor() -+ buf = b"\x00" * 32 -+ with pytest.raises((TypeError, BufferError)): -+ encryptor.update_into(b"testing", buf) -+ - @pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) --- -2.39.2 - diff --git a/debian/patches/Upgrade-to-pyo3-0.16.patch b/debian/patches/Upgrade-to-pyo3-0.16.patch deleted file mode 100644 index 3862b04..0000000 --- a/debian/patches/Upgrade-to-pyo3-0.16.patch +++ /dev/null @@ -1,912 +0,0 @@ -From: Alex Gaynor -Date: Wed, 7 Sep 2022 13:28:39 +0200 -Subject: Upgrade to pyo3 0.16 - -Ported from upstream PR: https://github.com/pyca/cryptography/pull/6935 ---- - MANIFEST.in | 2 + - setup.cfg | 1 - - setup.py | 2 +- - src/cryptography/__init__.py | 13 ------- - src/rust/Cargo.lock | 80 +++++++++++++++------------------------- - src/rust/Cargo.toml | 2 +- - src/rust/src/asn1.rs | 2 +- - src/rust/src/oid.rs | 5 +-- - src/rust/src/x509/certificate.rs | 21 +++++------ - src/rust/src/x509/common.rs | 26 ++++++------- - src/rust/src/x509/crl.rs | 76 ++++++++++++++++---------------------- - src/rust/src/x509/csr.rs | 19 ++++------ - src/rust/src/x509/extensions.rs | 2 +- - src/rust/src/x509/ocsp_req.rs | 4 +- - src/rust/src/x509/ocsp_resp.rs | 37 +++++++++---------- - src/rust/src/x509/sct.rs | 43 ++++++++++----------- - src/rust/src/x509/sign.rs | 12 +++--- - 17 files changed, 143 insertions(+), 204 deletions(-) - -diff --git a/MANIFEST.in b/MANIFEST.in -index 8471d75..f51a4d8 100644 ---- a/MANIFEST.in -+++ b/MANIFEST.in -@@ -16,6 +16,8 @@ prune docs/_build - recursive-include tests *.py - exclude vectors - recursive-exclude vectors * -+exclude src/rust/target -+recursive-exclude src/rust/target * - - recursive-exclude .github * - -diff --git a/setup.cfg b/setup.cfg -index 23bb773..2a669fa 100644 ---- a/setup.cfg -+++ b/setup.cfg -@@ -27,7 +27,6 @@ classifiers = - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only -- Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 -diff --git a/setup.py b/setup.py -index 320994e..5283c0f 100644 ---- a/setup.py -+++ b/setup.py -@@ -52,7 +52,7 @@ try: - features=( - [] - if platform.python_implementation() == "PyPy" -- else ["pyo3/abi3-py36"] -+ else ["pyo3/abi3-py37"] - ), - rust_version=">=1.48.0", - ) -diff --git a/src/cryptography/__init__.py b/src/cryptography/__init__.py -index 599bf51..32be322 100644 ---- a/src/cryptography/__init__.py -+++ b/src/cryptography/__init__.py -@@ -2,15 +2,11 @@ - # 2.0, and the BSD License. See the LICENSE file in the root of this repository - # for complete details. - --import sys --import warnings -- - from cryptography.__about__ import ( - __author__, - __copyright__, - __version__, - ) --from cryptography.utils import CryptographyDeprecationWarning - - - __all__ = [ -@@ -18,12 +14,3 @@ __all__ = [ - "__author__", - "__copyright__", - ] -- --if sys.version_info[:2] == (3, 6): -- warnings.warn( -- "Python 3.6 is no longer supported by the Python core team. " -- "Therefore, support for it is deprecated in cryptography and will be" -- " removed in a future release.", -- CryptographyDeprecationWarning, -- stacklevel=2, -- ) -diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock -index 4a0ecfd..976f865 100644 ---- a/src/rust/Cargo.lock -+++ b/src/rust/Cargo.lock -@@ -120,24 +120,10 @@ dependencies = [ - - [[package]] - name = "indoc" --version = "0.3.6" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" --dependencies = [ -- "indoc-impl", -- "proc-macro-hack", --] -- --[[package]] --name = "indoc-impl" --version = "0.3.6" -+version = "1.0.4" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" -+checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e" - dependencies = [ -- "proc-macro-hack", -- "proc-macro2", -- "quote", -- "syn", - "unindent", - ] - -@@ -257,25 +243,6 @@ dependencies = [ - "winapi", - ] - --[[package]] --name = "paste" --version = "0.1.18" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" --dependencies = [ -- "paste-impl", -- "proc-macro-hack", --] -- --[[package]] --name = "paste-impl" --version = "0.1.18" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" --dependencies = [ -- "proc-macro-hack", --] -- - [[package]] - name = "pem" - version = "1.1.0" -@@ -309,12 +276,6 @@ dependencies = [ - "version_check", - ] - --[[package]] --name = "proc-macro-hack" --version = "0.5.19" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" -- - [[package]] - name = "proc-macro2" - version = "1.0.43" -@@ -326,35 +287,47 @@ dependencies = [ - - [[package]] - name = "pyo3" --version = "0.15.2" -+version = "0.16.5" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "d41d50a7271e08c7c8a54cd24af5d62f73ee3a6f6a314215281ebdec421d5752" -+checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" - dependencies = [ - "cfg-if", - "indoc", - "libc", - "parking_lot", -- "paste", - "pyo3-build-config", -+ "pyo3-ffi", - "pyo3-macros", - "unindent", - ] - - [[package]] - name = "pyo3-build-config" --version = "0.15.2" -+version = "0.16.5" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "779239fc40b8e18bc8416d3a37d280ca9b9fb04bda54b98037bb6748595c2410" -+checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" - dependencies = [ - "once_cell", -+ "target-lexicon", -+] -+ -+[[package]] -+name = "pyo3-ffi" -+version = "0.16.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" -+dependencies = [ -+ "libc", -+ "pyo3-build-config", - ] - - [[package]] - name = "pyo3-macros" --version = "0.15.2" -+version = "0.16.5" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "00b247e8c664be87998d8628e86f282c25066165f1f8dda66100c48202fdb93a" -+checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" - dependencies = [ -+ "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn", -@@ -362,12 +335,11 @@ dependencies = [ - - [[package]] - name = "pyo3-macros-backend" --version = "0.15.2" -+version = "0.16.5" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "5a8c2812c412e00e641d99eeb79dd478317d981d938aa60325dfa7157b607095" -+checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" - dependencies = [ - "proc-macro2", -- "pyo3-build-config", - "quote", - "syn", - ] -@@ -413,6 +385,12 @@ dependencies = [ - "unicode-ident", - ] - -+[[package]] -+name = "target-lexicon" -+version = "0.12.4" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" -+ - [[package]] - name = "unicode-ident" - version = "1.0.3" -diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml -index 49e70a3..73347ab 100644 ---- a/src/rust/Cargo.toml -+++ b/src/rust/Cargo.toml -@@ -7,7 +7,7 @@ publish = false - - [dependencies] - once_cell = "1" --pyo3 = { version = "0.15.2" } -+pyo3 = { version = "0.16" } - asn1 = { version = "0.12.2", default-features = false, features = ["derive"] } - pem = "1.1" - chrono = { version = "0.4.22", default-features = false, features = ["alloc", "clock"] } -diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs -index 1ca443d..ec873e7 100644 ---- a/src/rust/src/asn1.rs -+++ b/src/rust/src/asn1.rs -@@ -273,7 +273,7 @@ mod tests { - PyAsn1Error::Asn1Write(asn1::WriteError::AllocationError) - )); - let py_e: pyo3::PyErr = e.into(); -- assert!(py_e.is_instance::(py)); -+ assert!(py_e.is_instance_of::(py)); - - let e: PyAsn1Error = pyo3::PyDowncastError::new(py.None().as_ref(py), "abc").into(); - assert!(matches!(e, PyAsn1Error::Py(_))); -diff --git a/src/rust/src/oid.rs b/src/rust/src/oid.rs -index bc65daf..8a77ee6 100644 ---- a/src/rust/src/oid.rs -+++ b/src/rust/src/oid.rs -@@ -35,10 +35,7 @@ impl ObjectIdentifier { - .getattr(crate::intern!(py, "_OID_NAMES"))?; - oid_names.call_method1("get", (slf, "Unknown OID")) - } --} - --#[pyo3::prelude::pyproto] --impl pyo3::PyObjectProtocol for ObjectIdentifier { - fn __repr__(&self) -> pyo3::PyResult { - let gil = pyo3::Python::acquire_gil(); - let py = gil.python(); -@@ -58,7 +55,7 @@ impl pyo3::PyObjectProtocol for ObjectIdentifier { - - fn __richcmp__( - &self, -- other: pyo3::PyRef, -+ other: pyo3::PyRef<'_, ObjectIdentifier>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { -diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs -index 59841d7..6a19dd7 100644 ---- a/src/rust/src/x509/certificate.rs -+++ b/src/rust/src/x509/certificate.rs -@@ -82,8 +82,8 @@ pub(crate) struct Certificate { - pub(crate) cached_extensions: Option, - } - --#[pyo3::prelude::pyproto] --impl pyo3::PyObjectProtocol for Certificate { -+#[pyo3::prelude::pymethods] -+impl Certificate { - fn __hash__(&self) -> u64 { - let mut hasher = DefaultHasher::new(); - self.raw.borrow_value().hash(&mut hasher); -@@ -92,7 +92,7 @@ impl pyo3::PyObjectProtocol for Certificate { - - fn __richcmp__( - &self, -- other: pyo3::PyRef, -+ other: pyo3::PyRef<'_, Certificate>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { -@@ -112,10 +112,7 @@ impl pyo3::PyObjectProtocol for Certificate { - let subject_repr = subject.repr()?.extract::<&str>()?; - Ok(format!("", subject_repr)) - } --} - --#[pyo3::prelude::pymethods] --impl Certificate { - fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { - slf - } -@@ -158,9 +155,9 @@ impl Certificate { - .getattr(crate::intern!(py, "Encoding"))?; - - let result = asn1::write_single(self.raw.borrow_value())?; -- if encoding == encoding_class.getattr(crate::intern!(py, "DER"))? { -+ if encoding.is(encoding_class.getattr(crate::intern!(py, "DER"))?) { - Ok(pyo3::types::PyBytes::new(py, &result)) -- } else if encoding == encoding_class.getattr(crate::intern!(py, "PEM"))? { -+ } else if encoding.is(encoding_class.getattr(crate::intern!(py, "PEM"))?) { - let pem = pem::encode_config( - &pem::Pem { - tag: "CERTIFICATE".to_string(), -@@ -291,7 +288,7 @@ impl Certificate { - let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?); - match hash_alg { - Ok(data) => Ok(data), -- Err(_) => Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ Err(_) => Err(PyAsn1Error::from(pyo3::PyErr::from_value( - py.import("cryptography.exceptions")?.call_method1( - "UnsupportedAlgorithm", - (format!( -@@ -368,11 +365,11 @@ fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, PyAsn - match version { - 0 => Ok(x509_module - .getattr(crate::intern!(py, "Version"))? -- .get_item("v1")?), -+ .get_item(crate::intern!(py, "v1"))?), - 2 => Ok(x509_module - .getattr(crate::intern!(py, "Version"))? -- .get_item("v3")?), -- _ => Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ .get_item(crate::intern!(py, "v3"))?), -+ _ => Err(PyAsn1Error::from(pyo3::PyErr::from_value( - x509_module - .getattr(crate::intern!(py, "InvalidVersion"))? - .call1((format!("{} is not a valid X509 version", version), version))?, -diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs -index 5cc8338..76d8d01 100644 ---- a/src/rust/src/x509/common.rs -+++ b/src/rust/src/x509/common.rs -@@ -112,10 +112,10 @@ pub(crate) fn encode_name_entry<'p>( - let tag = attr_type - .getattr(crate::intern!(py, "value"))? - .extract::()?; -- let value: &[u8] = if attr_type != asn1_type.getattr(crate::intern!(py, "BitString"))? { -- let encoding = if attr_type == asn1_type.getattr(crate::intern!(py, "BMPString"))? { -+ let value: &[u8] = if !attr_type.is(asn1_type.getattr(crate::intern!(py, "BitString"))?) { -+ let encoding = if attr_type.is(asn1_type.getattr(crate::intern!(py, "BMPString"))?) { - "utf_16_be" -- } else if attr_type == asn1_type.getattr(crate::intern!(py, "UniversalString"))? { -+ } else if attr_type.is(asn1_type.getattr(crate::intern!(py, "UniversalString"))?) { - "utf_32_be" - } else { - "utf8" -@@ -233,18 +233,18 @@ pub(crate) fn encode_general_name<'a>( - let gn_module = py.import("cryptography.x509.general_name")?; - let gn_type = gn.get_type().as_ref(); - let gn_value = gn.getattr(crate::intern!(py, "value"))?; -- if gn_type == gn_module.getattr(crate::intern!(py, "DNSName"))? { -+ if gn_type.is(gn_module.getattr(crate::intern!(py, "DNSName"))?) { - Ok(GeneralName::DNSName(UnvalidatedIA5String( - gn_value.extract::<&str>()?, - ))) -- } else if gn_type == gn_module.getattr(crate::intern!(py, "RFC822Name"))? { -+ } else if gn_type.is(gn_module.getattr(crate::intern!(py, "RFC822Name"))?) { - Ok(GeneralName::RFC822Name(UnvalidatedIA5String( - gn_value.extract::<&str>()?, - ))) -- } else if gn_type == gn_module.getattr(crate::intern!(py, "DirectoryName"))? { -+ } else if gn_type.is(gn_module.getattr(crate::intern!(py, "DirectoryName"))?) { - let name = encode_name(py, gn_value)?; - Ok(GeneralName::DirectoryName(name)) -- } else if gn_type == gn_module.getattr(crate::intern!(py, "OtherName"))? { -+ } else if gn_type.is(gn_module.getattr(crate::intern!(py, "OtherName"))?) { - Ok(GeneralName::OtherName(OtherName { - type_id: py_oid_to_oid(gn.getattr(crate::intern!(py, "type_id"))?)?, - value: asn1::parse_single(gn_value.extract::<&[u8]>()?).map_err(|e| { -@@ -254,15 +254,15 @@ pub(crate) fn encode_general_name<'a>( - )) - })?, - })) -- } else if gn_type == gn_module.getattr(crate::intern!(py, "UniformResourceIdentifier"))? { -+ } else if gn_type.is(gn_module.getattr(crate::intern!(py, "UniformResourceIdentifier"))?) { - Ok(GeneralName::UniformResourceIdentifier( - UnvalidatedIA5String(gn_value.extract::<&str>()?), - )) -- } else if gn_type == gn_module.getattr(crate::intern!(py, "IPAddress"))? { -+ } else if gn_type.is(gn_module.getattr(crate::intern!(py, "IPAddress"))?) { - Ok(GeneralName::IPAddress( - gn.call_method0("_packed")?.extract::<&[u8]>()?, - )) -- } else if gn_type == gn_module.getattr(crate::intern!(py, "RegisteredID"))? { -+ } else if gn_type.is(gn_module.getattr(crate::intern!(py, "RegisteredID"))?) { - let oid = py_oid_to_oid(gn_value)?; - Ok(GeneralName::RegisteredID(oid)) - } else { -@@ -462,7 +462,7 @@ pub(crate) fn parse_general_name( - .to_object(py) - } - _ => { -- return Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ return Err(PyAsn1Error::from(pyo3::PyErr::from_value( - x509_module.call_method1( - "UnsupportedGeneralNameType", - ("x400Address/EDIPartyName are not supported types",), -@@ -560,7 +560,7 @@ pub(crate) fn parse_and_cache_extensions< - let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?; - - if seen_oids.contains(&raw_ext.extn_id) { -- return Err(pyo3::PyErr::from_instance(x509_module.call_method1( -+ return Err(pyo3::PyErr::from_value(x509_module.call_method1( - "DuplicateExtension", - ( - format!("Duplicate {} extension found", raw_ext.extn_id), -@@ -606,7 +606,7 @@ pub(crate) fn encode_extensions< - let oid = py_oid_to_oid(py_ext.getattr(crate::intern!(py, "oid"))?)?; - - let ext_val = py_ext.getattr(crate::intern!(py, "value"))?; -- if unrecognized_extension_type.is_instance(ext_val)? { -+ if ext_val.is_instance(unrecognized_extension_type)? { - exts.push(Extension { - extn_id: oid, - critical: py_ext.getattr(crate::intern!(py, "critical"))?.extract()?, -diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs -index b34de10..29cfd60 100644 ---- a/src/rust/src/x509/crl.rs -+++ b/src/rust/src/x509/crl.rs -@@ -25,7 +25,7 @@ fn load_der_x509_crl( - let version = raw.borrow_value().tbs_cert_list.version.unwrap_or(1); - if version != 1 { - let x509_module = py.import("cryptography.x509")?; -- return Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ return Err(PyAsn1Error::from(pyo3::PyErr::from_value( - x509_module - .getattr(crate::intern!(py, "InvalidVersion"))? - .call1((format!("{} is not a valid CRL version", version), version))?, -@@ -96,11 +96,11 @@ impl CertificateRevocationList { - } - } - --#[pyo3::prelude::pyproto] --impl pyo3::PyObjectProtocol for CertificateRevocationList { -+#[pyo3::prelude::pymethods] -+impl CertificateRevocationList { - fn __richcmp__( - &self, -- other: pyo3::PyRef, -+ other: pyo3::PyRef<'_, CertificateRevocationList>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { -@@ -111,14 +111,26 @@ impl pyo3::PyObjectProtocol for CertificateRevocationList { - )), - } - } --} - --#[pyo3::prelude::pyproto] --impl pyo3::PyMappingProtocol for CertificateRevocationList { - fn __len__(&self) -> usize { - self.len() - } - -+ fn __iter__(&self) -> CRLIterator { -+ CRLIterator { -+ contents: OwnedCRLIteratorData::try_new(Arc::clone(&self.raw), |v| { -+ Ok::<_, ()>( -+ v.borrow_value() -+ .tbs_cert_list -+ .revoked_certificates -+ .as_ref() -+ .map(|v| v.unwrap_read().clone()), -+ ) -+ }) -+ .unwrap(), -+ } -+ } -+ - fn __getitem__(&self, idx: &pyo3::PyAny) -> pyo3::PyResult { - let gil = pyo3::Python::acquire_gil(); - let py = gil.python(); -@@ -132,7 +144,7 @@ impl pyo3::PyMappingProtocol for CertificateRevocationList { - }); - }); - -- if idx.is_instance::()? { -+ if idx.is_instance_of::()? { - let indices = idx - .downcast::()? - .indices(self.len().try_into().unwrap())?; -@@ -153,10 +165,7 @@ impl pyo3::PyMappingProtocol for CertificateRevocationList { - Ok(pyo3::PyCell::new(py, self.revoked_cert(py, idx as usize)?)?.to_object(py)) - } - } --} - --#[pyo3::prelude::pymethods] --impl CertificateRevocationList { - fn fingerprint<'p>( - &self, - py: pyo3::Python<'p>, -@@ -188,7 +197,7 @@ impl CertificateRevocationList { - .get_item(oid) - { - Ok(v) => Ok(v), -- Err(_) => Err(pyo3::PyErr::from_instance(exceptions_module.call_method1( -+ Err(_) => Err(pyo3::PyErr::from_value(exceptions_module.call_method1( - "UnsupportedAlgorithm", - (format!( - "Signature algorithm OID:{} not recognized", -@@ -222,9 +231,9 @@ impl CertificateRevocationList { - .getattr(crate::intern!(py, "Encoding"))?; - - let result = asn1::write_single(self.raw.borrow_value())?; -- if encoding == encoding_class.getattr(crate::intern!(py, "DER"))? { -+ if encoding.is(encoding_class.getattr(crate::intern!(py, "DER"))?) { - Ok(pyo3::types::PyBytes::new(py, &result)) -- } else if encoding == encoding_class.getattr(crate::intern!(py, "PEM"))? { -+ } else if encoding.is(encoding_class.getattr(crate::intern!(py, "PEM"))?) { - let pem = pem::encode_config( - &pem::Pem { - tag: "X509 CRL".to_string(), -@@ -424,24 +433,6 @@ impl CertificateRevocationList { - } - } - --#[pyo3::prelude::pyproto] --impl pyo3::PyIterProtocol<'_> for CertificateRevocationList { -- fn __iter__(slf: pyo3::PyRef<'p, Self>) -> CRLIterator { -- CRLIterator { -- contents: OwnedCRLIteratorData::try_new(Arc::clone(&slf.raw), |v| { -- Ok::<_, ()>( -- v.borrow_value() -- .tbs_cert_list -- .revoked_certificates -- .as_ref() -- .map(|v| v.unwrap_read().clone()), -- ) -- }) -- .unwrap(), -- } -- } --} -- - #[ouroboros::self_referencing] - struct OwnedCRLIteratorData { - data: Arc, -@@ -484,14 +475,18 @@ fn try_map_arc_data_mut_crl_iterator( - }) - } - --#[pyo3::prelude::pyproto] --impl pyo3::PyIterProtocol<'_> for CRLIterator { -- fn __iter__(slf: pyo3::PyRef<'p, Self>) -> pyo3::PyRef<'p, Self> { -+#[pyo3::prelude::pymethods] -+impl CRLIterator { -+ fn __len__(&self) -> usize { -+ self.contents.borrow_value().clone().map_or(0, |v| v.len()) -+ } -+ -+ fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf - } - -- fn __next__(mut slf: pyo3::PyRefMut<'p, Self>) -> Option { -- let revoked = try_map_arc_data_mut_crl_iterator(&mut slf.contents, |_data, v| match v { -+ 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(()), -@@ -506,13 +501,6 @@ impl pyo3::PyIterProtocol<'_> for CRLIterator { - } - } - --#[pyo3::prelude::pyproto] --impl pyo3::PySequenceProtocol<'_> for CRLIterator { -- fn __len__(&self) -> usize { -- self.contents.borrow_value().clone().map_or(0, |v| v.len()) -- } --} -- - #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] - struct RawCertificateRevocationList<'a> { - tbs_cert_list: TBSCertList<'a>, -diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs -index 7579bcc..af4df7e 100644 ---- a/src/rust/src/x509/csr.rs -+++ b/src/rust/src/x509/csr.rs -@@ -79,8 +79,8 @@ struct CertificateSigningRequest { - cached_extensions: Option, - } - --#[pyo3::prelude::pyproto] --impl pyo3::basic::PyObjectProtocol for CertificateSigningRequest { -+#[pyo3::prelude::pymethods] -+impl CertificateSigningRequest { - fn __hash__(&self) -> u64 { - let mut hasher = DefaultHasher::new(); - self.raw.borrow_data().hash(&mut hasher); -@@ -89,7 +89,7 @@ impl pyo3::basic::PyObjectProtocol for CertificateSigningRequest { - - fn __richcmp__( - &self, -- other: pyo3::PyRef, -+ other: pyo3::PyRef<'_, CertificateSigningRequest>, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { -@@ -100,10 +100,7 @@ impl pyo3::basic::PyObjectProtocol for CertificateSigningRequest { - )), - } - } --} - --#[pyo3::prelude::pymethods] --impl CertificateSigningRequest { - fn public_key<'p>(&self, py: pyo3::Python<'p>) -> PyAsn1Result<&'p pyo3::PyAny> { - // This makes an unnecessary copy. It'd be nice to get rid of it. - let serialized = pyo3::types::PyBytes::new( -@@ -149,7 +146,7 @@ impl CertificateSigningRequest { - let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?); - match hash_alg { - Ok(data) => Ok(data), -- Err(_) => Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ Err(_) => Err(PyAsn1Error::from(pyo3::PyErr::from_value( - py.import("cryptography.exceptions")?.call_method1( - "UnsupportedAlgorithm", - (format!( -@@ -176,9 +173,9 @@ impl CertificateSigningRequest { - .getattr(crate::intern!(py, "Encoding"))?; - - let result = asn1::write_single(self.raw.borrow_value())?; -- if encoding == encoding_class.getattr(crate::intern!(py, "DER"))? { -+ if encoding.is(encoding_class.getattr(crate::intern!(py, "DER"))?) { - Ok(pyo3::types::PyBytes::new(py, &result)) -- } else if encoding == encoding_class.getattr(crate::intern!(py, "PEM"))? { -+ } else if encoding.is(encoding_class.getattr(crate::intern!(py, "PEM"))?) { - let pem = pem::encode_config( - &pem::Pem { - tag: "CERTIFICATE REQUEST".to_string(), -@@ -239,7 +236,7 @@ impl CertificateSigningRequest { - } - } - } -- Err(pyo3::PyErr::from_instance( -+ Err(pyo3::PyErr::from_value( - py.import("cryptography.x509")?.call_method1( - "AttributeNotFound", - (format!("No {} attribute was found", oid), oid), -@@ -338,7 +335,7 @@ fn load_der_x509_csr(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result()? { -+ let qualifier = if py_qualifier.is_instance_of::()? { - let cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { - Some(s) => s, - None => { -diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs -index 92fe96f..483acc8 100644 ---- a/src/rust/src/x509/ocsp_req.rs -+++ b/src/rust/src/x509/ocsp_req.rs -@@ -82,7 +82,7 @@ impl OCSPRequest { - Some(alg_name) => Ok(hashes.getattr(alg_name)?.call0()?), - None => { - let exceptions = py.import("cryptography.exceptions")?; -- Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ Err(PyAsn1Error::from(pyo3::PyErr::from_value( - exceptions - .getattr(crate::intern!(py, "UnsupportedAlgorithm"))? - .call1((format!( -@@ -134,7 +134,7 @@ impl OCSPRequest { - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "Encoding"))? - .getattr(crate::intern!(py, "DER"))?; -- if encoding != der { -+ if !encoding.is(der) { - return Err(pyo3::exceptions::PyValueError::new_err( - "The only allowed encoding value is Encoding.DER", - ) -diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs -index 22d2940..00205a2 100644 ---- a/src/rust/src/x509/ocsp_resp.rs -+++ b/src/rust/src/x509/ocsp_resp.rs -@@ -176,7 +176,7 @@ impl OCSPResponse { - "Signature algorithm OID: {} not recognized", - self.requires_successful_response()?.signature_algorithm.oid - ); -- Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ Err(PyAsn1Error::from(pyo3::PyErr::from_value( - py.import("cryptography.exceptions")? - .call_method1("UnsupportedAlgorithm", (exc_messsage,))?, - ))) -@@ -365,7 +365,7 @@ impl OCSPResponse { - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "Encoding"))? - .getattr(crate::intern!(py, "DER"))?; -- if encoding != der { -+ if !encoding.is(der) { - return Err(pyo3::exceptions::PyValueError::new_err( - "The only allowed encoding value is Encoding.DER", - ) -@@ -517,7 +517,7 @@ impl SingleResponse<'_> { - Some(alg_name) => Ok(hashes.getattr(alg_name)?.call0()?), - None => { - let exceptions = py.import("cryptography.exceptions")?; -- Err(PyAsn1Error::from(pyo3::PyErr::from_instance( -+ Err(PyAsn1Error::from(pyo3::PyErr::from_value( - exceptions - .getattr(crate::intern!(py, "UnsupportedAlgorithm"))? - .call1((format!( -@@ -599,16 +599,14 @@ fn create_ocsp_basic_response<'p>( - .extract()?; - - let py_cert_status = py_single_resp.getattr(crate::intern!(py, "_cert_status"))?; -- let cert_status = if py_cert_status -- == ocsp_mod -- .getattr(crate::intern!(py, "OCSPCertStatus"))? -- .getattr(crate::intern!(py, "GOOD"))? -+ let cert_status = if py_cert_status.is(ocsp_mod -+ .getattr(crate::intern!(py, "OCSPCertStatus"))? -+ .getattr(crate::intern!(py, "GOOD"))?) - { - CertStatus::Good(()) -- } else if py_cert_status -- == ocsp_mod -- .getattr(crate::intern!(py, "OCSPCertStatus"))? -- .getattr(crate::intern!(py, "UNKNOWN"))? -+ } else if py_cert_status.is(ocsp_mod -+ .getattr(crate::intern!(py, "OCSPCertStatus"))? -+ .getattr(crate::intern!(py, "UNKNOWN"))?) - { - CertStatus::Unknown(()) - } else { -@@ -657,10 +655,9 @@ fn create_ocsp_basic_response<'p>( - }]; - - let borrowed_cert = responder_cert.borrow(); -- let responder_id = if responder_encoding -- == ocsp_mod -- .getattr(crate::intern!(py, "OCSPResponderEncoding"))? -- .getattr(crate::intern!(py, "HASH"))? -+ let responder_id = if responder_encoding.is(ocsp_mod -+ .getattr(crate::intern!(py, "OCSPResponderEncoding"))? -+ .getattr(crate::intern!(py, "HASH"))?) - { - let sha1 = py - .import("cryptography.hazmat.primitives.hashes")? -@@ -785,15 +782,15 @@ struct OCSPResponseIterator { - contents: OwnedOCSPResponseIteratorData, - } - --#[pyo3::prelude::pyproto] --impl pyo3::PyIterProtocol<'_> for OCSPResponseIterator { -- fn __iter__(slf: pyo3::PyRef<'p, Self>) -> pyo3::PyRef<'p, Self> { -+#[pyo3::prelude::pymethods] -+impl OCSPResponseIterator { -+ fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf - } - -- fn __next__(mut slf: pyo3::PyRefMut<'p, Self>) -> Option { -+ fn __next__(&mut self) -> Option { - let single_resp = -- try_map_arc_data_mut_ocsp_response_iterator(&mut slf.contents, |_data, v| { -+ try_map_arc_data_mut_ocsp_response_iterator(&mut self.contents, |_data, v| { - match v.next() { - Some(single_resp) => Ok(single_resp), - None => Err(()), -diff --git a/src/rust/src/x509/sct.rs b/src/rust/src/x509/sct.rs -index aaa374b..ed2b628 100644 ---- a/src/rust/src/x509/sct.rs -+++ b/src/rust/src/x509/sct.rs -@@ -143,6 +143,26 @@ pub(crate) struct Sct { - - #[pyo3::prelude::pymethods] - impl Sct { -+ fn __richcmp__( -+ &self, -+ other: pyo3::PyRef<'_, Sct>, -+ op: pyo3::basic::CompareOp, -+ ) -> pyo3::PyResult { -+ match op { -+ pyo3::basic::CompareOp::Eq => Ok(self.sct_data == other.sct_data), -+ pyo3::basic::CompareOp::Ne => Ok(self.sct_data != other.sct_data), -+ _ => Err(pyo3::exceptions::PyTypeError::new_err( -+ "SCTs cannot be ordered", -+ )), -+ } -+ } -+ -+ 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<&'p pyo3::PyAny> { - py.import("cryptography.x509.certificate_transparency")? -@@ -209,29 +229,6 @@ impl Sct { - } - } - --#[pyo3::prelude::pyproto] --impl pyo3::PyObjectProtocol for Sct { -- fn __richcmp__( -- &self, -- other: pyo3::PyRef, -- op: pyo3::basic::CompareOp, -- ) -> pyo3::PyResult { -- match op { -- pyo3::basic::CompareOp::Eq => Ok(self.sct_data == other.sct_data), -- pyo3::basic::CompareOp::Ne => Ok(self.sct_data != other.sct_data), -- _ => Err(pyo3::exceptions::PyTypeError::new_err( -- "SCTs cannot be ordered", -- )), -- } -- } -- -- fn __hash__(&self) -> u64 { -- let mut hasher = DefaultHasher::new(); -- self.sct_data.hash(&mut hasher); -- hasher.finish() -- } --} -- - pub(crate) fn parse_scts( - py: pyo3::Python<'_>, - data: &[u8], -diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs -index 4d91575..034e470 100644 ---- a/src/rust/src/x509/sign.rs -+++ b/src/rust/src/x509/sign.rs -@@ -58,15 +58,15 @@ fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::P - .getattr(crate::intern!(py, "Ed448PrivateKey"))? - .extract()?; - -- if rsa_private_key.is_instance(private_key)? { -+ if private_key.is_instance(rsa_private_key)? { - Ok(KeyType::Rsa) -- } else if dsa_key_type.is_instance(private_key)? { -+ } else if private_key.is_instance(dsa_key_type)? { - Ok(KeyType::Dsa) -- } else if ec_key_type.is_instance(private_key)? { -+ } else if private_key.is_instance(ec_key_type)? { - Ok(KeyType::Ec) -- } else if ed25519_key_type.is_instance(private_key)? { -+ } else if private_key.is_instance(ed25519_key_type)? { - Ok(KeyType::Ed25519) -- } else if ed448_key_type.is_instance(private_key)? { -+ } else if private_key.is_instance(ed448_key_type)? { - Ok(KeyType::Ed448) - } else { - Err(pyo3::exceptions::PyTypeError::new_err( -@@ -87,7 +87,7 @@ fn identify_hash_type( - .import("cryptography.hazmat.primitives.hashes")? - .getattr(crate::intern!(py, "HashAlgorithm"))? - .extract()?; -- if !hash_algorithm_type.is_instance(hash_algorithm)? { -+ if !hash_algorithm.is_instance(hash_algorithm_type)? { - return Err(pyo3::exceptions::PyTypeError::new_err( - "Algorithm must be a registered hash algorithm.", - )); diff --git a/debian/patches/Upgrade-to-pyo3-0.17.patch b/debian/patches/Upgrade-to-pyo3-0.17.patch deleted file mode 100644 index 650576d..0000000 --- a/debian/patches/Upgrade-to-pyo3-0.17.patch +++ /dev/null @@ -1,213 +0,0 @@ -From: Alex Gaynor -Date: Wed, 24 Aug 2022 07:53:13 -0400 -Subject: Upgrade to pyo3 0.17 - -Ported from upstream PR: https://github.com/pyca/cryptography/pull/6935 ---- - src/rust/Cargo.lock | 30 ++++++++++++++++++++---------- - src/rust/Cargo.toml | 2 +- - src/rust/src/oid.rs | 5 +---- - src/rust/src/pool.rs | 10 +++++----- - src/rust/src/x509/certificate.rs | 5 +---- - src/rust/src/x509/crl.rs | 9 +++++---- - src/rust/src/x509/ocsp_req.rs | 2 +- - src/rust/src/x509/ocsp_resp.rs | 2 +- - 8 files changed, 35 insertions(+), 30 deletions(-) - -diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock -index 976f865..09354fd 100644 ---- a/src/rust/Cargo.lock -+++ b/src/rust/Cargo.lock -@@ -170,6 +170,15 @@ dependencies = [ - "cfg-if", - ] - -+[[package]] -+name = "memoffset" -+version = "0.6.5" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -+dependencies = [ -+ "autocfg", -+] -+ - [[package]] - name = "num-integer" - version = "0.1.45" -@@ -287,13 +296,14 @@ dependencies = [ - - [[package]] - name = "pyo3" --version = "0.16.5" -+version = "0.17.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" -+checksum = "12f72538a0230791398a0986a6518ebd88abc3fded89007b506ed072acc831e1" - dependencies = [ - "cfg-if", - "indoc", - "libc", -+ "memoffset", - "parking_lot", - "pyo3-build-config", - "pyo3-ffi", -@@ -303,9 +313,9 @@ dependencies = [ - - [[package]] - name = "pyo3-build-config" --version = "0.16.5" -+version = "0.17.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" -+checksum = "fc4cf18c20f4f09995f3554e6bcf9b09bd5e4d6b67c562fdfaafa644526ba479" - dependencies = [ - "once_cell", - "target-lexicon", -@@ -313,9 +323,9 @@ dependencies = [ - - [[package]] - name = "pyo3-ffi" --version = "0.16.5" -+version = "0.17.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" -+checksum = "a41877f28d8ebd600b6aa21a17b40c3b0fc4dfe73a27b6e81ab3d895e401b0e9" - dependencies = [ - "libc", - "pyo3-build-config", -@@ -323,9 +333,9 @@ dependencies = [ - - [[package]] - name = "pyo3-macros" --version = "0.16.5" -+version = "0.17.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" -+checksum = "2e81c8d4bcc2f216dc1b665412df35e46d12ee8d3d046b381aad05f1fcf30547" - dependencies = [ - "proc-macro2", - "pyo3-macros-backend", -@@ -335,9 +345,9 @@ dependencies = [ - - [[package]] - name = "pyo3-macros-backend" --version = "0.16.5" -+version = "0.17.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" -+checksum = "85752a767ee19399a78272cc2ab625cd7d373b2e112b4b13db28de71fa892784" - dependencies = [ - "proc-macro2", - "quote", -diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml -index 73347ab..8d11c97 100644 ---- a/src/rust/Cargo.toml -+++ b/src/rust/Cargo.toml -@@ -7,7 +7,7 @@ publish = false - - [dependencies] - once_cell = "1" --pyo3 = { version = "0.16" } -+pyo3 = { version = "0.17" } - asn1 = { version = "0.12.2", default-features = false, features = ["derive"] } - pem = "1.1" - chrono = { version = "0.4.22", default-features = false, features = ["alloc", "clock"] } -diff --git a/src/rust/src/oid.rs b/src/rust/src/oid.rs -index 8a77ee6..0cb3e85 100644 ---- a/src/rust/src/oid.rs -+++ b/src/rust/src/oid.rs -@@ -36,10 +36,7 @@ impl ObjectIdentifier { - oid_names.call_method1("get", (slf, "Unknown OID")) - } - -- fn __repr__(&self) -> pyo3::PyResult { -- let gil = pyo3::Python::acquire_gil(); -- let py = gil.python(); -- -+ fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let self_clone = pyo3::PyCell::new( - py, - ObjectIdentifier { -diff --git a/src/rust/src/pool.rs b/src/rust/src/pool.rs -index 9dacd7f..6e1c59b 100644 ---- a/src/rust/src/pool.rs -+++ b/src/rust/src/pool.rs -@@ -63,11 +63,11 @@ impl FixedPool { - impl Drop for FixedPool { - fn drop(&mut self) { - if let Some(value) = self.value.replace(None) { -- let gil = pyo3::Python::acquire_gil(); -- let py = gil.python(); -- self.destroy_fn -- .call1(py, (value,)) -- .expect("FixedPool destroy function failed in destructor"); -+ pyo3::Python::with_gil(|py| { -+ self.destroy_fn -+ .call1(py, (value,)) -+ .expect("FixedPool destroy function failed in destructor"); -+ }); - } - } - } -diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs -index 6a19dd7..948e23f 100644 ---- a/src/rust/src/x509/certificate.rs -+++ b/src/rust/src/x509/certificate.rs -@@ -104,10 +104,7 @@ impl Certificate { - } - } - -- fn __repr__(&self) -> pyo3::PyResult { -- let gil = pyo3::Python::acquire_gil(); -- let py = gil.python(); -- -+ fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let subject = self.subject(py)?; - let subject_repr = subject.repr()?.extract::<&str>()?; - Ok(format!("", subject_repr)) -diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs -index 29cfd60..832c8be 100644 ---- a/src/rust/src/x509/crl.rs -+++ b/src/rust/src/x509/crl.rs -@@ -131,10 +131,11 @@ impl CertificateRevocationList { - } - } - -- fn __getitem__(&self, idx: &pyo3::PyAny) -> pyo3::PyResult { -- let gil = pyo3::Python::acquire_gil(); -- let py = gil.python(); -- -+ fn __getitem__( -+ &self, -+ py: pyo3::Python<'_>, -+ idx: &pyo3::PyAny, -+ ) -> pyo3::PyResult { - self.raw.with(|val| { - val.revoked_certs.get_or_init(py, || { - match &val.value.tbs_cert_list.revoked_certificates { -diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs -index 483acc8..e086b4d 100644 ---- a/src/rust/src/x509/ocsp_req.rs -+++ b/src/rust/src/x509/ocsp_req.rs -@@ -79,7 +79,7 @@ impl OCSPRequest { - - let hashes = py.import("cryptography.hazmat.primitives.hashes")?; - match ocsp::OIDS_TO_HASH.get(&cert_id.hash_algorithm.oid) { -- Some(alg_name) => Ok(hashes.getattr(alg_name)?.call0()?), -+ Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?), - None => { - let exceptions = py.import("cryptography.exceptions")?; - Err(PyAsn1Error::from(pyo3::PyErr::from_value( -diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs -index 00205a2..715df01 100644 ---- a/src/rust/src/x509/ocsp_resp.rs -+++ b/src/rust/src/x509/ocsp_resp.rs -@@ -514,7 +514,7 @@ impl SingleResponse<'_> { - fn py_hash_algorithm<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let hashes = py.import("cryptography.hazmat.primitives.hashes")?; - match ocsp::OIDS_TO_HASH.get(&self.cert_id.hash_algorithm.oid) { -- Some(alg_name) => Ok(hashes.getattr(alg_name)?.call0()?), -+ Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?), - None => { - let exceptions = py.import("cryptography.exceptions")?; - Err(PyAsn1Error::from(pyo3::PyErr::from_value( diff --git a/debian/patches/Upgrade-to-pyo3-0.19.patch b/debian/patches/Upgrade-to-pyo3-0.20.patch similarity index 59% rename from debian/patches/Upgrade-to-pyo3-0.19.patch rename to debian/patches/Upgrade-to-pyo3-0.20.patch index a65a64c..db9ba61 100644 --- a/debian/patches/Upgrade-to-pyo3-0.19.patch +++ b/debian/patches/Upgrade-to-pyo3-0.20.patch @@ -39,25 +39,21 @@ Date: Wed May 31 16:27:10 2023 -0400 src/rust/src/x509/extensions.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) -diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml -index 8327b25..f77390a 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml -@@ -7,7 +7,7 @@ publish = false +@@ -9,7 +9,7 @@ [dependencies] once_cell = "1" --pyo3 = { version = "0.17" } -+pyo3 = { version = "0.19" } - asn1 = { version = "0.12", default-features = false, features = ["derive"] } - pem = ">= 1.0, < 1.2" - chrono = { version = "0.4", default-features = false, features = ["alloc", "clock"] } -diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs -index 832c8be..6d49fb5 100644 +-pyo3 = { version = "0.18", features = ["abi3-py37"] } ++pyo3 = { version = "0.20", features = ["abi3-py37"] } + asn1 = { version = "0.15.2", default-features = false } + cryptography-cffi = { path = "cryptography-cffi" } + cryptography-x509 = { path = "cryptography-x509" } --- a/src/rust/src/x509/crl.rs +++ b/src/rust/src/x509/crl.rs -@@ -145,7 +145,7 @@ impl CertificateRevocationList { - }); +@@ -145,7 +145,7 @@ + revoked_certs }); - if idx.is_instance_of::()? { @@ -65,16 +61,25 @@ index 832c8be..6d49fb5 100644 let indices = idx .downcast::()? .indices(self.len().try_into().unwrap())?; -diff --git a/src/rust/src/x509/extensions.rs b/src/rust/src/x509/extensions.rs -index e5db68a..474ad82 100644 --- a/src/rust/src/x509/extensions.rs +++ b/src/rust/src/x509/extensions.rs -@@ -228,7 +228,7 @@ pub(crate) fn encode_extension( - 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 qualifier = if py_qualifier.is_instance_of::() { - let cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { - Some(s) => s, - None => { +@@ -211,7 +211,7 @@ + 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 qualifier = if py_qualifier.is_instance_of::() { + let cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { + Some(s) => s, + None => { +--- a/src/rust/cryptography-cffi/Cargo.toml ++++ b/src/rust/cryptography-cffi/Cargo.toml +@@ -8,7 +8,7 @@ + rust-version = "1.56.0" + + [dependencies] +-pyo3 = { version = "0.18", features = ["abi3-py37"] } ++pyo3 = { version = "0.20", features = ["abi3-py37"] } + openssl-sys = "0.9.88" + + [build-dependencies] diff --git a/debian/patches/Use-local-python3-doc-inventory.patch b/debian/patches/Use-local-python3-doc-inventory.patch index 24301ac..e100b93 100644 --- a/debian/patches/Use-local-python3-doc-inventory.patch +++ b/debian/patches/Use-local-python3-doc-inventory.patch @@ -6,19 +6,18 @@ Subject: Use local python3-doc inventory. docs/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) -diff --git a/docs/conf.py b/docs/conf.py -index 0c38d4d..d47de91 100644 --- a/docs/conf.py +++ b/docs/conf.py -@@ -186,7 +186,10 @@ texinfo_documents = [ +@@ -183,7 +183,11 @@ + ), ] - # Example configuration for intersphinx: refer to the Python standard library. --intersphinx_mapping = {"https://docs.python.org/3": None} +-intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} +intersphinx_mapping = { + 'py': ('https://docs.python.org/3/', + ('/usr/share/doc/python3-doc/html/objects.inv', None)), +} ++ epub_theme = "epub" diff --git a/debian/patches/allow-pem-version-1.0.patch b/debian/patches/allow-pem-version-1.0.patch deleted file mode 100644 index 27fef27..0000000 --- a/debian/patches/allow-pem-version-1.0.patch +++ /dev/null @@ -1,21 +0,0 @@ -From: Claudius Heine -Date: Wed, 7 Sep 2022 13:38:22 +0200 -Subject: allow pem version 1.0 - ---- - src/rust/Cargo.toml | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml -index d03d915..662f008 100644 ---- a/src/rust/Cargo.toml -+++ b/src/rust/Cargo.toml -@@ -9,7 +9,7 @@ publish = false - once_cell = "1" - pyo3 = { version = "0.17" } - asn1 = { version = "0.12", default-features = false, features = ["derive"] } --pem = "1.1" -+pem = ">= 1.0, < 1.2" - chrono = { version = "0.4.22", default-features = false, features = ["alloc", "clock"] } - ouroboros = "0.15" - diff --git a/debian/patches/drop-cffi-dep.patch b/debian/patches/drop-cffi-dep.patch index be04379..98cb856 100644 --- a/debian/patches/drop-cffi-dep.patch +++ b/debian/patches/drop-cffi-dep.patch @@ -15,15 +15,28 @@ Bug-Debian: https://bugs.debian.org/1026537 setup.cfg | 1 - 1 file changed, 1 deletion(-) -diff --git a/setup.cfg b/setup.cfg -index 2a669fa..94e8d20 100644 ---- a/setup.cfg -+++ b/setup.cfg -@@ -43,7 +43,6 @@ package_dir = - =src - packages = find: - install_requires = -- cffi >=1.12 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -4,7 +4,6 @@ + "setuptools>=61.0.0", + "wheel", + # Must be kept in sync with `project.dependencies` +- "cffi>=1.12; platform_python_implementation != 'PyPy'", + "setuptools-rust>=0.11.4", + ] + build-backend = "setuptools.build_meta" +@@ -44,7 +43,6 @@ + requires-python = ">=3.7" + dependencies = [ + # Must be kept in sync with `build-system.requires` +- "cffi >=1.12", + ] - [options.packages.find] - where = src + [project.urls] +@@ -151,4 +149,4 @@ + "ci-constraints-requirements.txt", + ".gitattributes", + ".gitignore", +-] +\ No newline at end of file ++] diff --git a/debian/patches/ease-asn1-version-from-0.12.1-to-0.12.patch b/debian/patches/ease-asn1-version-from-0.12.1-to-0.12.patch deleted file mode 100644 index 4f21e4a..0000000 --- a/debian/patches/ease-asn1-version-from-0.12.1-to-0.12.patch +++ /dev/null @@ -1,21 +0,0 @@ -From: Claudius Heine -Date: Wed, 7 Sep 2022 13:32:58 +0200 -Subject: ease asn1 version from 0.12.1 to 0.12 - ---- - src/rust/Cargo.toml | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml -index 8d11c97..d03d915 100644 ---- a/src/rust/Cargo.toml -+++ b/src/rust/Cargo.toml -@@ -8,7 +8,7 @@ publish = false - [dependencies] - once_cell = "1" - pyo3 = { version = "0.17" } --asn1 = { version = "0.12.2", default-features = false, features = ["derive"] } -+asn1 = { version = "0.12", default-features = false, features = ["derive"] } - pem = "1.1" - chrono = { version = "0.4.22", default-features = false, features = ["alloc", "clock"] } - ouroboros = "0.15" diff --git a/debian/patches/ease-chrono-dependency-from-0.4.22-to-0.4.patch b/debian/patches/ease-chrono-dependency-from-0.4.22-to-0.4.patch deleted file mode 100644 index 4fe2c3c..0000000 --- a/debian/patches/ease-chrono-dependency-from-0.4.22-to-0.4.patch +++ /dev/null @@ -1,21 +0,0 @@ -From: Claudius Heine -Date: Wed, 7 Sep 2022 13:39:02 +0200 -Subject: ease chrono dependency from 0.4.22 to 0.4 - ---- - src/rust/Cargo.toml | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml -index 662f008..8327b25 100644 ---- a/src/rust/Cargo.toml -+++ b/src/rust/Cargo.toml -@@ -10,7 +10,7 @@ once_cell = "1" - pyo3 = { version = "0.17" } - asn1 = { version = "0.12", default-features = false, features = ["derive"] } - pem = ">= 1.0, < 1.2" --chrono = { version = "0.4.22", default-features = false, features = ["alloc", "clock"] } -+chrono = { version = "0.4", default-features = false, features = ["alloc", "clock"] } - ouroboros = "0.15" - - [features] diff --git a/debian/patches/series b/debian/patches/series index 60ad3c7..34ad2e9 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,10 +1,3 @@ Use-local-python3-doc-inventory.patch -Upgrade-to-pyo3-0.16.patch -Upgrade-to-pyo3-0.17.patch -ease-asn1-version-from-0.12.1-to-0.12.patch -allow-pem-version-1.0.patch -ease-chrono-dependency-from-0.4.22-to-0.4.patch drop-cffi-dep.patch -Don-t-allow-update_into-to-mutate-immutable-objects-.patch -Upgrade-to-pyo3-0.19.patch -0010-Replace-US-Pacific-with-America-Los_Angeles.patch +Upgrade-to-pyo3-0.20.patch diff --git a/debian/rules b/debian/rules index 3c38ddf..48331e0 100755 --- a/debian/rules +++ b/debian/rules @@ -19,13 +19,13 @@ export PYBUILD_NAME=cryptography export PYBUILD_TEST_PYTEST=1 export PYBUILD_TEST_ARGS={dir}/tests/ export PYBUILD_BEFORE_TEST=cp -R {dir}/src/${PYBUILD_NAME}.egg-info {build_dir} -export PYBUILD_AFTER_TEST=rm -r {build_dir}/.hypothesis %: dh $@ --buildsystem=pybuild override_dh_auto_clean: + if [ -f src/rust/Cargo.lock.orig ] ; then mv src/rust/Cargo.lock.orig src/rust/Cargo.lock ; fi dh_auto_clean rm -rf src/rust/target rm -rf debian/cargo_registry @@ -43,7 +43,7 @@ override_dh_auto_configure: $(CARGO) prepare-debian debian/cargo_registry --link-from-system mkdir -p src/rust/target/release ln -sf ../$(DEB_HOST_RUST_TYPE)/release/libcryptography_rust.so src/rust/target/release/libcryptography_rust.so - rm -f src/rust/Cargo.lock + mv src/rust/Cargo.lock src/rust/Cargo.lock.orig dh_auto_configure diff --git a/debian/tests/control b/debian/tests/control index 6170fb8..802f050 100644 --- a/debian/tests/control +++ b/debian/tests/control @@ -3,8 +3,8 @@ Depends: python3-all, python3-cffi, python3-cryptography, - python3-cryptography-vectors (<< 38.0.5~), - python3-cryptography-vectors (>= 38.0.4~), + python3-cryptography-vectors (<< 41.0.8~), + python3-cryptography-vectors (>= 41.0.7~), python3-hypothesis, python3-iso8601, python3-pretend, diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc deleted file mode 100644 index 22fb779..0000000 --- a/debian/upstream/signing-key.asc +++ /dev/null @@ -1,94 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBFFGXnMBEACrFZe22Ps0uTdXASlz2iA6cRU8GZv7fYeaMOtOBMECP+iK7l/b -3OOr4NgYdQbaJBitKde88xoJdxePXD7pysmtHvxR6bDGeaA/YRGa9Cc0u7S3TpOG -jRIKjaREk4EW0VmMhtkkZbGaMTiCpPlhQci8R6IO6x2eJveRsH32MiKzm6XqsRML -a2grFCO2SKXbMywcA21qXvCDF7KnfhNFzeHE+qMNjn+9zi1rMK0YNo0DMSCDkYXH -ytyo44CeQNnn9itgDqEP0xM03C+x50YbUFJzt+uTZeBIshhnfdHPaYuULRreUHcM -PNltj2+3kRzJlELXhHxjNLk0u+wdsSUg2vjuKCiaCDu1gkfaBkT4wyDoQ8XtWzNE -ya2vzH/D5s0motyFLSqScf56CAg5xLCILbFaYCfc+cuB4JwRRGliiXDtWkBZW6Qw -lAMmuz/b1TWkMkCZDcBNfk2P22KIp/+B1J254yQ8Lap+RXFnDu9UOZAa02pZt+ix -m1ZG7A1f5Gi6hhxicVeZwwHErcILBJs3v2wdY4Tz7Gy2MrR0PQ02NVCz6L0mre99 -y9SIltLHPLyax4GHIUvBGs4muu5xeyf64iEAmFBt5BJTN5WumTqlbCw2TSJptjxG -6KGNdRu9yj75GcQUoTGN9fzaNA0oNZsxw+5JS4k3bEn5cKlEMaLacFrkLwARAQAB -tC9BbGV4YW5kZXIgTmF0aGFuIEdheW5vciA8YWxleC5nYXlub3JAZ21haWwuY29t -PokCOAQTAQIAIgUCUUZecwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ -El9cZ9/pQIQI9g//YDfL6emKd/8X0hbQuMa5qGGa2raFdyO5zhorl1HN+BEy5SDb -hr1Weg9+yn3CuBKjY7iRPfZHsk5a9ctl+ATkcdvYfohSrjZb/NUBjrOuaje3nrmy -78dVDIkXnxXS5G55sOgYTYa84rSVYokc/xaqc608+e0tnYDg+ZqeWd1gaWFhYONM -e5jZGNwQNO/qEZi7qqX/f0WPXgEV0s44Jft2l8jT1utAcG5vIXwq5iG4XHIUWuLD -achLBhcJ1PT7/M+n7IC4HSaKiRgIWZ6Fjri+HifeDsNXLWrfopG25kNO6k8x+/Xd -MUfk8C9TPzqQybYxvuF82aOY13riFaJ+nbKRpaCFss2/kP6hydciTHWzQ5f8oIy1 -JnL7rRuaRzucVVZ/88LQgZawJ3xGYJPQtXBcNVs2QoBCH3rKc9Sks2CItHNWFSBY -cFP+wjcKwm/MOcddw+Xo2tvASkfhLV3pxpPiBXinmaKy4EH+yHy2vYwCAxYdXX7X -Da4UXyznc9QOfRHNRa+TlxYmvUa6tQa9rQeAxbuoWi0KMYL8/1f12d3wN4HeAsfD -r/nBXxuYsk+Y/h/ReJQ7ohNs3g4TGDZr6Tk7aWadYiOFp3SGAf0caHBHMdMY2Q2M -QtLUux0OUayinEULlDyDnXehCDaiwJsq4vKd5GhK/sQUP6y9JSpufHiLeMa0TUFs -ZXhhbmRlciBOYXRoYW4gR2F5bm9yIChEamFuZ28gU29mdHdhcmUgRm91bmRhdGlv -bikgPGFsZXhAZGphbmdvcHJvamVjdC5jb20+iQI4BBMBAgAiBQJRZOktAhsDBgsJ -CAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRASX1xn3+lAhKPXD/0QZLusg9ywNUyf -ea2fzhFp/AU1jFtACefPHHM9Cfp/MWuSXUn8z6OVEaoPTkY49hYO1h/x0tus3wSe -Svsx61qQfvawlhuf4+jmUkZLXcm+HE8YevQZRHVF8iOUR7GQzI8b1vAuLDZUuRM5 -HRSAkSCakqT6h8RO9IMQEc5CHpp6IgdgAzxKFJClP6EvETLuPJ1z2WVZTZ0ZiJ5K -yNqG/IuV+7dt1QwknAsuJkFVQGH516//sIGThp+6GU1LXq/bXZHIyuv2Qx1/GhPM -mYovvF0C7TSwWKH0DzKIWiwJR0+tDEfOOOGSLNPDxcpgcnep8JHoj9zCFEyITYsL -IOJHZ10v6uC7SAEV+rbhOvwjSGmOsNr+hybAwsmzuBrwdEpM1TFuheSXKZkxIkkF -GBcG0JGXVSSYkW9vL4vuutqW3Y7/LFYGBDakZj1iFNk1XvYSPLNuKLSP1JVjuiDj -3C2tHXR+0rZVr7Hfm5/IFFMzOloj5sf+/GadcETjylwsQ2xUK1iE0lLHA+PJD11b -IM6vRdTEHQxc+nKBLDy0gD/qJmjYgjZ8T9JgrsmJgHJ+5DJwPMQRFp+TLwVX1uOJ -ujRy8wy+VjuVionPvCSDDQEKex9aBUpdVpgUiISZ4q9JX1aOe5hRaXVCRKL4Yy7b -NyD4384AnmcqrHrvb6efGmUfa5q7lbkCDQRRRl5zARAAwNCSnz0Dh2HPeyqToErR -/dSL9y11RvDfjXQAxAYpMzxegIbW+Hpi7ZLrQvoR6wvYZ7QOHsUYYcw/jhEg1iOE -/vtgpTgTIIqNvzCLOcJS6l4AOw2auiuf+Q/YDC8WfUbn6LlZJ8bReDrDEou91lYl -yl4iauNhCQNj6hzNsw/z/Ka/C/p20XBusNVWbfh8/zsJfW3fnKJgkUNckmz/cXSF -8RJol+aZnamSzYluiZUP3+nidP8T8KilU7KvOe2w6l376i9XNqafejJAd+9SMZRy -j9mYlaZmEektiT4hZJoDJOUgVfcPbSU18xuLjoRZ1ANH5SdY8VvYuFz73BGi6keA -sHuRv3ir8asmycGvTaXyAQ0JduGRfkW+aMvYjCEzdoK++S+ufy0WWlNbgnj8Y7Ya -0J/zozAPjiMi9/9DRoi31tx9Mc2JKjiJJFUmkv4TC8FHlsF3yCh7V9Vr5oeSksdx -2F7j9lce5WDuAwjRP/NYSlFRn9WqPo16J/+oMcWif3bWj9Uz7t3q5bZJexm7Q8qI -fguujg3nOA0Rqmw+a+Z7gsep1+aa30578k3btyoDnjeuGxI0/JKTJhFvZWXymSqd -gWtYkfREVC2KiAHgEBlgNVFaqhIZchC0ocJZLtnew6C7Se3u+jeKsrNYGL4pJ6GE -COlhXCmKmPZ3wHWA311Z5O8AEQEAAYkCHwQYAQIACQUCUUZecwIbDAAKCRASX1xn -3+lAhPVOD/0drh9cKgE6jpUpV0/9iHprR2PaYBkrxfH6m+N/FPWJrD+H5yk7csnA -lCFe5Jf8Vi4azT9/5HdmYxsQcmyQgl3Gt3JxyL9wqBQ+X0E9tmweW3lnaaYSf0qx -z7xSFK8Nvn2g+iJnXS5K7CEzdEiqUrjeF+louVwlnNAVP9ci1bGKAPL4Bp6cq05p -Sijr6Td5nwJsNmg0iRrEC2hhMKRmk7Pkwhy7Mkn6pkNq5dslJ32PKvn0c3zMdsUN -2j+mnvWRJH5fq5cpygzksei0xutcJOZzQs0NKdSQqJTRsoqGLbWiOBgBR0XjDUWg -Qd+Y92hJltBiz0HW926S4yEblUQtvVmnmI+aOqv1eCEgQBcjyEnVT2HpoffLLagf -6UErppjxIx5KepYOhvh2edjQZTgnikt9ONkxYdJG3FDswb9izzLWk+OSUWaHnIXZ -toaVntaHssdR5jJs/tQQTxKEuhbxzaI7SYAsBGEciuTx9dA22D9/zMWWsXlSr82P -pNeS2vPM08rHd7BFDc2w670LCyH5ahq2AZXjfjBiIYrN8sfZCohwk9i6R7ATwMTT -9e3XRgms8r4MSM+6t6+ozXaL6AGHlo9l0BMzQgWoyL01mGatrxpWbHzZ6rAQ+iKb -hVQn13T+kdJsTHDGjPzYtjfKYrDx10PeU1XYQfv+pdqTlael02HVFg== -=gnkS ------END PGP PUBLIC KEY BLOCK----- ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -mQENBFIirTsBCADAeRXlWJkJ9SaKmR6+ox2aXOF3TCyfgtoJpf6WvBxbtoEpD//m -5789KO9lPHQrJGR34E36kOkmkHfrLLtmnELCx/UNvLm3VuW31rL3RvTTrxe1Fyft -5JQyYF/WSm6Bnj9yu8fVJUhSGe12SvODD7053bY1bSleMX5I6tGeIXJtih8b5u9u -1WNv0rxZqGLKaYTzgmtNcyKGK9jLKtkRwPivpjgrjeGQ+OHf/mwFY+HEOQUw+Cj4 -5FsCo6jvj+n4r9mYu+Ut6zDOx0cWf66QhZvawDyB2TSSulJsudUETDmoJJ5X9PqH -F/bHBdzc5I6HYy+CezSvDmjC+3DnIB//nXMPABEBAAG0JVBhdWwgS2VocmVyIDxw -YXVsLmwua2VocmVyQGdtYWlsLmNvbT6JATgEEwECACIFAlIirTsCGwMGCwkIBwMC -BhUIAgkKCwQWAgMBAh4BAheAAAoJECNa5fEp+e2YnM0H/i8bU3gQ/lMGli97Puui -Sm5es3AwE+dC/ubaAB8Hcdm8UH50uOI6JcmLYYcjglnFEQSDzrKg649Dcvjx7hDN -XoCx5V6dC8LcTVES5gGrRr/+ZXtsCVZn2TcLUMQ9bq1yb3jAYxOmWQ1rUvu0Kq+Z -1j9IERKpt3MZcXBlOxHP6zIhaaerLLRn5+SjCHCAZQYsKh9f6fMoRvbmaLyKrLBn -/n9/esn1b0joWEphwOER8UF5fckqDopovGojDXyNEKGGkXTkWtLk69AcaXcBI97X -SqYUmzvnHcAPilpKmfdnvcGPrS/wSY/F4T71aeQ+1QoE83CfavNMQ09g4rETSr3e -Vlq5AQ0EUiKtOwEIANRWXywm/B46dy2paG/dd1ApwdX3siIfnCKXEsLB1iTA5/HW -BZ5+hHRYmI24RyBj9lVhS9UJzpKZE+KLOZRFwMGGp3TxntInflamuI3iC1N7XqCz -gLMFJdHPO60LctbvOHTOx1Scb+AycmymF1HuUFbj1jlYUkwRPOiPvHHWkYQlfeUP -MPFo/M7Ae5FxKA4PYfJRQl62wsBRNE5k7IwOmstyUUnDZXIxpB+wNvpxQpAvWT8B -IyAvtlrkrE53frfyd0KUOR0iSHNcWcUL0L6XvsaOYb4i20bP7YE5XoVzzANbXTa1 -wVtz2yNoI7/8BLb2NMIacykUxryYtu6E9cmnwkkAEQEAAYkBHwQYAQIACQUCUiKt -OwIbDAAKCRAjWuXxKfntmM/yB/91f/17kL4iAS05WgM1xvgmyYJ3FOgP3hyqD5Ur -YkkmoFoF+r6bfBlW8AeOawYTvXinKdv9sM6q0EmiO+iqAuRRfaXZWCDqZdEpy+lv -Ev3jhVyuf8O+d8VEILsKia0cmzn6F1UMdp3E9TDgXr1/hMCuABvbfWzEkRQrGHc2 -cWLXXxko3mykZMLkl0MPGjmzEh87RE55hLk5HroXaMtdyz1knfybVnXgOUxMuqc+ -+wj499FP1jHvTEEknRopxsMe59+CdsoFBR9xs0Ets7K011P4CMKZZAXVwxF551QL -xnqe0Tn1t76rxPJKpyvUM+WtakEVPffuQqSkU8dIJgwwXrZo -=3pQn ------END PGP PUBLIC KEY BLOCK----- diff --git a/debian/watch b/debian/watch index 3e39854..fd28164 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ version=3 -opts=uversionmangle=s/(rc|a|b|c)/~$1/,pgpsigurlmangle=s/$/.asc/ \ +opts=uversionmangle=s/(rc|a|b|c)/~$1/ \ https://pypi.debian.net/cryptography/cryptography-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) diff --git a/docs/_ext/cryptography-docs.py b/docs/_ext/cryptography-docs.py index 1131f6a..43a9c6c 100644 --- a/docs/_ext/cryptography-docs.py +++ b/docs/_ext/cryptography-docs.py @@ -5,7 +5,6 @@ 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 diff --git a/docs/api-stability.rst b/docs/api-stability.rst index 3822702..eafbd1d 100644 --- a/docs/api-stability.rst +++ b/docs/api-stability.rst @@ -24,6 +24,9 @@ What doesn't this policy cover? 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. diff --git a/docs/conf.py b/docs/conf.py index 0c38d4d..4cbbde3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. @@ -49,6 +47,7 @@ "sphinx.ext.intersphinx", "sphinx.ext.linkcode", "cryptography-docs", + "sphinx_rtd_theme", ] if spelling is not None: @@ -73,7 +72,7 @@ # General information about the project. project = "Cryptography" -copyright = "2013-2022, Individual Contributors" +copyright = "2013-2023, 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 @@ -125,7 +124,6 @@ if sphinx_rtd_theme: html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] else: html_theme = "default" @@ -185,8 +183,7 @@ ), ] -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"https://docs.python.org/3": None} +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} epub_theme = "epub" @@ -197,14 +194,14 @@ linkcheck_timeout = 5 linkcheck_ignore = [ - # Small DH key results in a TLS failure on modern OpenSSL + # Insecure renegotiation settings r"https://info.isl.ntt.co.jp/crypt/eng/camellia/", # Inconsistent small DH params they seem incapable of fixing r"https://www.secg.org/sec1-v2.pdf", # Incomplete cert chain - r"https://e-trust.gosuslugi.ru", - # Expired cert (1 week at time of writing) - r"https://www.cosic.esat.kuleuven.be", + r"https://www.oscca.gov.cn", + # Cloudflare returns 403s for all non-browser requests + r"https://speakerdeck.com", ] autosectionlabel_prefix_document = True diff --git a/docs/development/custom-vectors/arc4/generate_arc4.py b/docs/development/custom-vectors/arc4/generate_arc4.py index 14a99d0..2ca919b 100644 --- a/docs/development/custom-vectors/arc4/generate_arc4.py +++ b/docs/development/custom-vectors/arc4/generate_arc4.py @@ -7,7 +7,6 @@ from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import algorithms - _RFC6229_KEY_MATERIALS = [ ( True, @@ -77,13 +76,11 @@ def _build_vectors(): while current_offset < offset: encryptor.update(plaintext) current_offset += len(plaintext) - output.append("\nCOUNT = {}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 - output.append("KEY = {}".format(key)) - output.append("OFFSET = {}".format(offset)) - output.append( - "PLAINTEXT = {}".format(binascii.hexlify(plaintext)) - ) + output.append(f"KEY = {key}") + output.append(f"OFFSET = {offset}") + output.append(f"PLAINTEXT = {binascii.hexlify(plaintext)}") output.append( "CIPHERTEXT = {}".format( binascii.hexlify(encryptor.update(plaintext)) diff --git a/docs/development/custom-vectors/cast5/generate_cast5.py b/docs/development/custom-vectors/cast5/generate_cast5.py index b57d71d..27fb463 100644 --- a/docs/development/custom-vectors/cast5/generate_cast5.py +++ b/docs/development/custom-vectors/cast5/generate_cast5.py @@ -25,7 +25,7 @@ def build_vectors(mode, filename): iv = None plaintext = None - with open(filename, "r") as vector_file: + with open(filename) as vector_file: for line in vector_file: line = line.strip() if line.startswith("KEY"): @@ -35,20 +35,18 @@ def build_vectors(mode, filename): encrypt(mode, key, iv, plaintext) ) ) - output.append("\nCOUNT = {}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 name, key = line.split(" = ") - output.append("KEY = {}".format(key)) + output.append(f"KEY = {key}") elif line.startswith("IV"): name, iv = line.split(" = ") iv = iv[0:16] - output.append("IV = {}".format(iv)) + output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): name, plaintext = line.split(" = ") - output.append("PLAINTEXT = {}".format(plaintext)) - output.append( - "CIPHERTEXT = {}".format(encrypt(mode, key, iv, plaintext)) - ) + output.append(f"PLAINTEXT = {plaintext}") + output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) diff --git a/docs/development/custom-vectors/hkdf/generate_hkdf.py b/docs/development/custom-vectors/hkdf/generate_hkdf.py index 410d26f..97ad2fa 100644 --- a/docs/development/custom-vectors/hkdf/generate_hkdf.py +++ b/docs/development/custom-vectors/hkdf/generate_hkdf.py @@ -24,7 +24,7 @@ def _build_vectors(): "IKM = " + binascii.hexlify(IKM).decode("ascii"), "salt = ", "info = ", - "L = {}".format(L), + f"L = {L}", "OKM = " + binascii.hexlify(OKM).decode("ascii"), ] return "\n".join(output) diff --git a/docs/development/custom-vectors/idea/generate_idea.py b/docs/development/custom-vectors/idea/generate_idea.py index 4d0defa..c0e93ee 100644 --- a/docs/development/custom-vectors/idea/generate_idea.py +++ b/docs/development/custom-vectors/idea/generate_idea.py @@ -15,7 +15,7 @@ def encrypt(mode, key, iv, plaintext): def build_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() count = 0 @@ -28,23 +28,21 @@ def build_vectors(mode, filename): if line.startswith("KEY"): if count != 0: output.append( - "CIPHERTEXT = {0}".format( - encrypt(mode, key, iv, plaintext) - ) + f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) - output.append("\nCOUNT = {0}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 name, key = line.split(" = ") - output.append("KEY = {0}".format(key)) + output.append(f"KEY = {key}") elif line.startswith("IV"): name, iv = line.split(" = ") iv = iv[0:16] - output.append("IV = {0}".format(iv)) + output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): name, plaintext = line.split(" = ") - output.append("PLAINTEXT = {0}".format(plaintext)) + output.append(f"PLAINTEXT = {plaintext}") - output.append("CIPHERTEXT = {0}".format(encrypt(mode, key, iv, plaintext))) + output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) diff --git a/docs/development/custom-vectors/idea/verify_idea.py b/docs/development/custom-vectors/idea/verify_idea.py index d356de0..52e5f73 100644 --- a/docs/development/custom-vectors/idea/verify_idea.py +++ b/docs/development/custom-vectors/idea/verify_idea.py @@ -9,7 +9,7 @@ def encrypt(mode, key, iv, plaintext): encryptor = botan.Cipher( - "IDEA/{0}/NoPadding".format(mode), "encrypt", binascii.unhexlify(key) + f"IDEA/{mode}/NoPadding", "encrypt", binascii.unhexlify(key) ) cipher_text = encryptor.cipher( @@ -19,7 +19,7 @@ def encrypt(mode, key, iv, plaintext): def verify_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() vectors = load_nist_vectors(vector_file) diff --git a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py index 009ba7f..f9e7912 100644 --- a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py +++ b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py @@ -8,7 +8,6 @@ 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 @@ -83,7 +82,7 @@ def build_vectors(mgf1alg, hashalg, filename): ), ) output.append( - "# OAEP Example {0} alg={1} mgf1={2}".format( + "# OAEP Example {} alg={} mgf1={}".format( count, hashalg.name, mgf1alg.name ) ) @@ -119,5 +118,5 @@ def write_file(data, filename): write_file( build_vectors(hashtuple[0], hashtuple[1], oaep_path), - "oaep-{0}-{1}.txt".format(hashtuple[0].name, hashtuple[1].name), + f"oaep-{hashtuple[0].name}-{hashtuple[1].name}.txt", ) diff --git a/docs/development/custom-vectors/secp256k1/generate_secp256k1.py b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py index 2f1f69e..545b25a 100644 --- a/docs/development/custom-vectors/secp256k1/generate_secp256k1.py +++ b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py @@ -7,7 +7,6 @@ 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 = { @@ -41,7 +40,7 @@ def build_vectors(fips_vectors): continue yield "" - yield "[K-256,{0}]".format(digest_algorithm) + yield f"[K-256,{digest_algorithm}]" yield "" for message in messages: @@ -57,12 +56,12 @@ def build_vectors(fips_vectors): r, s = sigdecode_der(signature, None) - yield "Msg = {0}".format(hexlify(message)) - yield "d = {0:x}".format(secret_key.privkey.secret_multiplier) - yield "Qx = {0:x}".format(public_key.pubkey.point.x()) - yield "Qy = {0:x}".format(public_key.pubkey.point.y()) - yield "R = {0:x}".format(r) - yield "S = {0:x}".format(s) + 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 "" diff --git a/docs/development/custom-vectors/secp256k1/verify_secp256k1.py b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py index 3ba21c8..7949a74 100644 --- a/docs/development/custom-vectors/secp256k1/verify_secp256k1.py +++ b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py @@ -5,7 +5,6 @@ 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 = { diff --git a/docs/development/custom-vectors/seed/generate_seed.py b/docs/development/custom-vectors/seed/generate_seed.py index 3e0125b..c2ebf4b 100644 --- a/docs/development/custom-vectors/seed/generate_seed.py +++ b/docs/development/custom-vectors/seed/generate_seed.py @@ -15,7 +15,7 @@ def encrypt(mode, key, iv, plaintext): def build_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() count = 0 @@ -28,22 +28,20 @@ def build_vectors(mode, filename): if line.startswith("KEY"): if count != 0: output.append( - "CIPHERTEXT = {0}".format( - encrypt(mode, key, iv, plaintext) - ) + f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}" ) - output.append("\nCOUNT = {0}".format(count)) + output.append(f"\nCOUNT = {count}") count += 1 name, key = line.split(" = ") - output.append("KEY = {0}".format(key)) + output.append(f"KEY = {key}") elif line.startswith("IV"): name, iv = line.split(" = ") - output.append("IV = {0}".format(iv)) + output.append(f"IV = {iv}") elif line.startswith("PLAINTEXT"): name, plaintext = line.split(" = ") - output.append("PLAINTEXT = {0}".format(plaintext)) + output.append(f"PLAINTEXT = {plaintext}") - output.append("CIPHERTEXT = {0}".format(encrypt(mode, key, iv, plaintext))) + output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}") return "\n".join(output) diff --git a/docs/development/custom-vectors/seed/verify_seed.py b/docs/development/custom-vectors/seed/verify_seed.py index 252088d..c28ed1e 100644 --- a/docs/development/custom-vectors/seed/verify_seed.py +++ b/docs/development/custom-vectors/seed/verify_seed.py @@ -7,7 +7,7 @@ def encrypt(mode, key, iv, plaintext): encryptor = botan.Cipher( - "SEED/{0}/NoPadding".format(mode), "encrypt", binascii.unhexlify(key) + f"SEED/{mode}/NoPadding", "encrypt", binascii.unhexlify(key) ) cipher_text = encryptor.cipher( @@ -17,7 +17,7 @@ def encrypt(mode, key, iv, plaintext): def verify_vectors(mode, filename): - with open(filename, "r") as f: + with open(filename) as f: vector_file = f.read().splitlines() vectors = load_nist_vectors(vector_file) diff --git a/docs/development/getting-started.rst b/docs/development/getting-started.rst index b52a4fd..a428346 100644 --- a/docs/development/getting-started.rst +++ b/docs/development/getting-started.rst @@ -3,84 +3,39 @@ Getting started Development dependencies ------------------------ + Working on ``cryptography`` requires the installation of a small number of development dependencies in addition to the dependencies for -:doc:`/installation`. These are listed in ``dev-requirements.txt`` and they can -be installed in a `virtualenv`_ using `pip`_. Before you install them, follow -the **build** instructions in :doc:`/installation` (be sure to stop before -actually installing ``cryptography``). Once you've done that, install the -development dependencies, and then install ``cryptography`` in ``editable`` -mode. For example: +:doc:`/installation`. 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 --requirement dev-requirements.txt - $ pip install --editable . - -Make sure that ``pip install --requirement ...`` has installed the Python -package ``vectors/`` and packages on ``tests/`` . If it didn't, you may -install them manually by using ``pip`` on each directory. - -You will also need to install ``enchant`` using your system's package manager -to check spelling in the documentation. - -You are now ready to run the tests and build the documentation. + $ pip install nox + $ # Specify your Python version here. + $ nox -e tests -p py310 OpenSSL on macOS ~~~~~~~~~~~~~~~~ -You must have installed `OpenSSL`_ via `Homebrew`_ or `MacPorts`_ and must set -``CFLAGS`` and ``LDFLAGS`` environment variables before installing the -``dev-requirements.txt`` otherwise pip will fail with include errors. - -For example, with `Homebrew`_: - -.. code-block:: console - - $ env LDFLAGS="-L$(brew --prefix openssl@1.1)/lib" \ - CFLAGS="-I$(brew --prefix openssl@1.1)/include" \ - pip install --requirement ./dev-requirements.txt - -Alternatively for a static build you can specify -``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1`` and ensure ``LDFLAGS`` points to the -absolute path for the `OpenSSL`_ libraries before calling pip. - -.. tip:: - You will also need to set these values when `Building documentation`_. +You must have installed `OpenSSL`_ (via `Homebrew`_ , `MacPorts`_, or a custom +build) and must configure the build `as documented here`_ before calling +``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`_. `pytest`_ will discover the tests -automatically, so all you have to do is: +designed to be run using `pytest`_. ``nox`` automatically invokes ``pytest``: .. code-block:: console - $ pytest + $ nox -e tests -p py310 ... 62746 passed in 220.43 seconds -This runs the tests with the default Python interpreter. - -You can also verify that the tests pass on other supported Python interpreters. -For this we use `tox`_, which will automatically create a `virtualenv`_ for -each supported Python version and run the tests. For example: - -.. code-block:: console - - $ tox - ... - ERROR: pypy: InterpreterNotFound: pypy - py38: commands succeeded - docs: commands succeeded - pep8: commands succeeded - -You may not have all the required Python versions installed, in which case you -will see one or more ``InterpreterNotFound`` errors. - Building documentation ---------------------- @@ -88,14 +43,13 @@ Building documentation ``cryptography`` documentation is stored in the ``docs/`` directory. It is written in `reStructured Text`_ and rendered using `Sphinx`_. -Use `tox`_ to build the documentation. For example: +Use `nox`_ to build the documentation. For example: .. code-block:: console - $ tox -e docs + $ nox -e docs ... - docs: commands succeeded - congratulations :) + nox > Session docs was successful. The HTML documentation index can now be found at ``docs/_build/html/index.html``. @@ -104,8 +58,9 @@ The HTML documentation index can now be found at .. _`MacPorts`: https://www.macports.org .. _`OpenSSL`: https://www.openssl.org .. _`pytest`: https://pypi.org/project/pytest/ -.. _`tox`: https://pypi.org/project/tox/ +.. _`nox`: https://pypi.org/project/nox/ .. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _`pip`: https://pypi.org/project/pip/ .. _`sphinx`: https://pypi.org/project/Sphinx/ .. _`reStructured Text`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html +.. _`as documented here`: https://docs.rs/openssl/latest/openssl/#automatic \ No newline at end of file diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst index 80a8bb4..6148419 100644 --- a/docs/development/submitting-patches.rst +++ b/docs/development/submitting-patches.rst @@ -20,9 +20,9 @@ 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 ``black`` and running -``flake8`` against it. If you've installed the development requirements this -will automatically use our configuration. You can also run the ``tox`` job with -``tox -e flake``. +``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.`_ @@ -95,7 +95,7 @@ Documentation ------------- All features should be documented with prose in the ``docs`` section. To ensure -it builds you can run ``tox -e docs``. +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 diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 121b215..cfab7ed 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -22,7 +22,7 @@ for various cryptographic algorithms. These are not included in the repository continuous integration environments. We have ensured all test vectors are used as of commit -``2196000605e45d91097147c9c71f26b72af58003``. +``b063b4aedae951c69df014cd25fa6d69ae9e8cb9``. Asymmetric ciphers ~~~~~~~~~~~~~~~~~~ @@ -220,6 +220,10 @@ X.509 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 @@ -283,6 +287,10 @@ X.509 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 ~~~~~~~~~~~~~~~~~~~~ @@ -474,8 +482,25 @@ Custom X.509 Vectors 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. Custom X.509 Request Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -598,6 +623,9 @@ Custom X.509 Certificate Revocation List Vectors 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 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -648,8 +676,10 @@ Custom X.509 OCSP Test Vectors 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 +* ``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 ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -789,6 +819,10 @@ Custom PKCS7 Test Vectors 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. @@ -821,6 +855,43 @@ using command-line tools from OpenSSH_7.6p1 package. 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 ~~~~~~ @@ -954,13 +1025,13 @@ header format (substituting the correct information): .. _`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 +.. _`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 +.. _`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 .. _`Ed25519 website`: https://ed25519.cr.yp.to/software.html diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index 12d6bb0..c7e82ff 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -40,8 +40,7 @@ Bumping the version number The next step in doing a release is bumping the version number in the software. -* Update the version number in ``src/cryptography/__about__.py``. -* Update the version number in ``vectors/cryptography_vectors/__about__.py``. +* 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. @@ -54,7 +53,7 @@ The commit that merged the version number bump is now the official release commit for this release. You will need to have ``gpg`` installed and a ``gpg`` key in order to do a release. Once this has happened: -* Run ``python release.py {version}``. +* Run ``python release.py release {version}``. The release should now be available on PyPI and a tag should be available in the repository. @@ -82,18 +81,19 @@ the expected OpenSSL version. Post-release tasks ------------------ -* Update the version number to the next major (e.g. ``0.5.dev1``) in - ``src/cryptography/__about__.py`` and - ``vectors/cryptography_vectors/__about__.py``. +* 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. -* Send an email to the `mailing list`_ and `python-announce`_ announcing the - release. .. _`CVE from MITRE`: https://cveform.mitre.org/ .. _`oss-security`: https://www.openwall.com/lists/oss-security/ diff --git a/docs/faq.rst b/docs/faq.rst index f9f35c1..ac7f415 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -1,6 +1,22 @@ Frequently 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 @@ -102,23 +118,14 @@ 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. -error: ``-Werror=sign-conversion``: No option ``-Wsign-conversion`` during installation ---------------------------------------------------------------------------------------- - -The compiler you are using is too old and not supported by ``cryptography``. -Please upgrade to a more recent version. If you are running OpenBSD 6.1 or -earlier the default compiler is extremely old. Use ``pkg_add`` to install a -newer ``gcc`` and then install ``cryptography`` using -``CC=/path/to/newer/gcc pip install cryptography``. - -Installing cryptography with OpenSSL 0.9.8, 1.0.0, 1.0.1, 1.0.2 fails ---------------------------------------------------------------------- +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, and 1.0.2 -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.0 or later). This may -require you to upgrade to a newer operating system. +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`` ---------------------------------------------------------------------------- @@ -154,7 +161,7 @@ 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. +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 @@ -185,6 +192,7 @@ For example, this is a PEM file for a RSA Public Key: :: 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 @@ -194,10 +202,27 @@ 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 -.. _`WSGIApplicationGroup`: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIApplicationGroup.html .. _`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 diff --git a/docs/fernet.rst b/docs/fernet.rst index 167cf51..b55ecea 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -129,7 +129,7 @@ has support for implementing key rotation via :class:`MultiFernet`. :param bytes or str token: The Fernet token. This is the result of calling :meth:`encrypt`. - :returns int: The UNIX timestamp of the token. + :returns int: The Unix timestamp of the token. :raises cryptography.fernet.InvalidToken: If the ``token``'s signature is invalid this exception is raised. @@ -237,7 +237,7 @@ password through a key derivation function such as ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=390000, + ... iterations=480000, ... ) >>> key = base64.urlsafe_b64encode(kdf.derive(password)) >>> f = Fernet(key) @@ -252,7 +252,7 @@ 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 July 2022`_. +recommends as of December 2022`_. Implementation -------------- @@ -280,5 +280,5 @@ unsuitable for very large files at this time. .. _`Fernet`: https://github.com/fernet/spec/ -.. _`Django recommends as of July 2022`: https://github.com/django/django/blob/main/django/contrib/auth/hashers.py +.. _`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 diff --git a/docs/glossary.rst b/docs/glossary.rst index b85a610..86718cc 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -93,13 +93,22 @@ Glossary bytes-like A bytes-like object contains binary data and supports the `buffer protocol`_. This includes ``bytes``, ``bytearray``, and - ``memoryview`` objects. + ``memoryview`` objects. It is :term:`unsafe` to pass a mutable object + (e.g., a ``bytearray`` or other implementor 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/ diff --git a/docs/hazmat/primitives/aead.rst b/docs/hazmat/primitives/aead.rst index b886010..db9ef96 100644 --- a/docs/hazmat/primitives/aead.rst +++ b/docs/hazmat/primitives/aead.rst @@ -56,10 +56,12 @@ also support providing integrity for associated data which is not encrypted. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to encrypt. - :param bytes associated_data: Additional data that should be + :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. @@ -73,9 +75,11 @@ also support providing integrity for associated data which is not encrypted. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to decrypt (with tag appended). - :param bytes associated_data: Additional data to authenticate. Can be + :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 @@ -130,9 +134,11 @@ also support providing integrity for associated data which is not encrypted. 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 bytes data: The data to encrypt. - :param bytes associated_data: Additional data that should be + :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. @@ -147,9 +153,11 @@ also support providing integrity for associated data which is not encrypted. 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 bytes data: The data to decrypt (with tag appended). - :param bytes associated_data: Additional data to authenticate. Can be + :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 @@ -158,7 +166,7 @@ also support providing integrity for associated data which is not encrypted. .. class:: AESOCB3(key) - .. versionadded:: 36.0 + .. 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. @@ -204,9 +212,11 @@ also support providing integrity for associated data which is not encrypted. :param nonce: A 12-15 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to encrypt. - :param bytes associated_data: Additional data that should be + :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. @@ -219,9 +229,11 @@ also support providing integrity for associated data which is not encrypted. :param nonce: A 12 byte value. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to decrypt (with tag appended). - :param bytes associated_data: Additional data to authenticate. Can be + :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 @@ -230,7 +242,7 @@ also support providing integrity for associated data which is not encrypted. .. class:: AESSIV(key) - .. versionadded:: 37.0 + .. versionadded:: 37.0.0 The SIV (synthetic initialization vector) construction is defined in :rfc:`5297`. Depending on how it is used, SIV allows either @@ -288,8 +300,9 @@ also support providing integrity for associated data which is not encrypted. authenticating the ``associated_data``. The output of this can be passed directly to the ``decrypt`` method. - :param bytes data: The data to encrypt. - :param list associated_data: An optional ``list`` of ``bytes``. This + :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``. @@ -304,7 +317,7 @@ also support providing integrity for associated data which is not encrypted. ``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``. This + :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. @@ -377,9 +390,11 @@ also support providing integrity for associated data which is not encrypted. ``len(data) < 2 ** (8 * (15 - len(nonce)))`` **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to encrypt. - :param bytes associated_data: Additional data that should be + :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. @@ -394,9 +409,11 @@ also support providing integrity for associated data which is not encrypted. is the same value used when you originally called encrypt. **NEVER REUSE A NONCE** with a key. :type nonce: :term:`bytes-like` - :param bytes data: The data to decrypt (with tag appended). - :param bytes associated_data: Additional data to authenticate. Can be + :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 diff --git a/docs/hazmat/primitives/asymmetric/dh.rst b/docs/hazmat/primitives/asymmetric/dh.rst index e880b81..361aa6d 100644 --- a/docs/hazmat/primitives/asymmetric/dh.rst +++ b/docs/hazmat/primitives/asymmetric/dh.rst @@ -174,13 +174,6 @@ Group parameters :return bytes: Serialized parameters. -.. class:: DHParametersWithSerialization - - .. versionadded:: 1.7 - - Alias for :class:`DHParameters`. - - Key interfaces ~~~~~~~~~~~~~~ @@ -247,13 +240,6 @@ Key interfaces :return bytes: Serialized key. -.. class:: DHPrivateKeyWithSerialization - - .. versionadded:: 1.7 - - Alias for :class:`DHPrivateKey`. - - .. class:: DHPublicKey .. versionadded:: 1.7 @@ -293,13 +279,6 @@ Key interfaces :return bytes: Serialized key. -.. class:: DHPublicKeyWithSerialization - - .. versionadded:: 1.7 - - Alias for :class:`DHPublicKey`. - - Numbers ~~~~~~~ diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 69f128b..5df8014 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -315,7 +315,6 @@ Key interfaces :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 @@ -337,13 +336,6 @@ Key interfaces :return bytes: Serialized key. -.. class:: DSAPrivateKeyWithSerialization - - .. versionadded:: 0.8 - - Alias for :class:`DSAPrivateKey`. - - .. class:: DSAPublicKey .. versionadded:: 0.3 @@ -408,17 +400,11 @@ Key interfaces :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. -.. class:: DSAPublicKeyWithSerialization - - .. versionadded:: 0.8 - - Alias for :class:`DSAPublicKey`. - - .. _`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 diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 95244c2..5842e9c 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -187,22 +187,6 @@ Elliptic Curve Signature Algorithms :raises ValueError: Raised if the point is invalid for the curve. :returns: A new instance of :class:`EllipticCurvePublicKey`. - .. method:: encode_point() - - .. warning:: - - This method is deprecated as of version 2.5. Callers should migrate - to using - :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes`. - - .. versionadded:: 1.1 - - Encodes an elliptic curve point to a byte string as described in - `SEC 1 v2.0`_ section 2.3.3. This method only supports uncompressed - points. - - :return bytes: The encoded point. - .. classmethod:: from_encoded_point(curve, data) .. versionadded:: 1.1 @@ -550,11 +534,7 @@ Key Interfaces .. versionadded:: 0.5 - An elliptic curve private key for use with an algorithm such as `ECDSA`_ or - `EdDSA`_. An elliptic curve private key that is not an - :term:`opaque key` also implements - :class:`EllipticCurvePrivateKeyWithSerialization` to provide serialization - methods. + An elliptic curve private key for use with an algorithm such as `ECDSA`_. .. method:: exchange(algorithm, peer_public_key) @@ -648,13 +628,6 @@ Key Interfaces :return bytes: Serialized key. -.. class:: EllipticCurvePrivateKeyWithSerialization - - .. versionadded:: 0.8 - - Alias for :class:`EllipticCurvePrivateKey`. - - .. class:: EllipticCurvePublicKey .. versionadded:: 0.5 @@ -715,6 +688,7 @@ Key Interfaces :param signature_algorithm: An instance of :class:`EllipticCurveSignatureAlgorithm`. + :returns: None :raises cryptography.exceptions.InvalidSignature: If the signature does not validate. @@ -749,13 +723,6 @@ Key Interfaces :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. -.. class:: EllipticCurvePublicKeyWithSerialization - - .. versionadded:: 0.6 - - Alias for :class:`EllipticCurvePublicKey`. - - Serialization ~~~~~~~~~~~~~ @@ -952,7 +919,6 @@ Elliptic Curve Object Identifiers .. _`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 -.. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA .. _`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 diff --git a/docs/hazmat/primitives/asymmetric/ed25519.rst b/docs/hazmat/primitives/asymmetric/ed25519.rst index 3229f09..1ca06fc 100644 --- a/docs/hazmat/primitives/asymmetric/ed25519.rst +++ b/docs/hazmat/primitives/asymmetric/ed25519.rst @@ -103,6 +103,20 @@ Key interfaces :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 @@ -163,12 +177,26 @@ Key interfaces :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 bytes signature: The signature to verify. :param bytes data: The data to verify. + :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the signature cannot be verified. diff --git a/docs/hazmat/primitives/asymmetric/ed448.rst b/docs/hazmat/primitives/asymmetric/ed448.rst index fb79dcb..efe245d 100644 --- a/docs/hazmat/primitives/asymmetric/ed448.rst +++ b/docs/hazmat/primitives/asymmetric/ed448.rst @@ -81,6 +81,20 @@ Key interfaces :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 @@ -117,12 +131,26 @@ Key interfaces :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 bytes signature: The signature to verify. :param bytes data: The data to verify. + :returns: None :raises cryptography.exceptions.InvalidSignature: Raised when the signature cannot be verified. diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst index c27e178..136dd32 100644 --- a/docs/hazmat/primitives/asymmetric/index.rst +++ b/docs/hazmat/primitives/asymmetric/index.rst @@ -36,3 +36,83 @@ private key is able to decrypt it. .. _`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`. diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index d21cb80..23401f5 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -304,7 +304,7 @@ Padding .. attribute:: DIGEST_LENGTH - .. versionadded:: 37.0 + .. 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 @@ -312,7 +312,7 @@ Padding .. attribute:: AUTO - .. versionadded:: 37.0 + .. versionadded:: 37.0.0 Pass this attribute to ``salt_length`` to automatically determine the salt length when verifying. Raises ``ValueError`` if used when signing. @@ -473,7 +473,21 @@ is unavailable. A `Chinese remainder theorem`_ coefficient used to speed up RSA operations. Calculated as: q\ :sup:`-1` mod p - .. method:: private_key() + .. 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`. @@ -541,6 +555,11 @@ Key interfaces .. 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. @@ -623,13 +642,6 @@ Key interfaces :return bytes: Serialized key. -.. class:: RSAPrivateKeyWithSerialization - - .. versionadded:: 0.8 - - Alias for :class:`RSAPrivateKey`. - - .. class:: RSAPublicKey .. versionadded:: 0.2 @@ -710,6 +722,7 @@ Key interfaces :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. @@ -763,13 +776,6 @@ Key interfaces :raises cryptography.exceptions.UnsupportedAlgorithm: If signature data recovery is not supported with the provided ``padding`` type. -.. class:: RSAPublicKeyWithSerialization - - .. versionadded:: 0.8 - - Alias for :class:`RSAPublicKey`. - - .. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem) .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography .. _`specific mathematical properties`: https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index db3271b..c60accc 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -125,7 +125,7 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END extract the public key with :meth:`Certificate.public_key `. -.. function:: load_pem_private_key(data, password) +.. function:: load_pem_private_key(data, password, *, unsafe_skip_rsa_key_validation=False) .. versionadded:: 0.6 @@ -141,7 +141,20 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END :param password: The password to use to decrypt the data. Should be ``None`` if the private key is not encrypted. - :type data: :term:`bytes-like` + :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`, @@ -234,7 +247,7 @@ 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) +.. function:: load_der_private_key(data, password, *, unsafe_skip_rsa_key_validation=False) .. versionadded:: 0.8 @@ -248,6 +261,19 @@ the rest. 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`, @@ -362,10 +388,28 @@ 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. @@ -373,13 +417,8 @@ DSA keys look almost identical but begin with ``ssh-dss`` rather than :param data: The OpenSSH encoded key data. :type data: :term:`bytes-like` - :returns: One of - :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`, - depending on the contents of ``data``. + :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. @@ -405,10 +444,27 @@ An example ECDSA key in OpenSSH format:: 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. @@ -418,13 +474,8 @@ An example ECDSA key in OpenSSH format:: :param bytes password: Password bytes to use to decrypt password-protected key. Or ``None`` if not needed. - :returns: One of - :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`, - depending on the contents of ``data``. + :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 @@ -433,6 +484,303 @@ An example ECDSA key in OpenSSH format:: :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 ~~~~~~ @@ -447,6 +795,23 @@ file suffix. ``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 @@ -470,7 +835,7 @@ file suffix. .. function:: load_pkcs12(data, password) - .. versionadded:: 36.0 + .. versionadded:: 36.0.0 Deserialize a PKCS12 blob, and return a :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates` @@ -517,17 +882,7 @@ file suffix. :type name: bytes :param key: The private key to include in the structure. - :type key: An - :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` - object. + :type key: :data:`PKCS12PrivateKeyTypes` :param cert: The certificate associated with the private key. :type cert: :class:`~cryptography.x509.Certificate` or ``None`` @@ -575,12 +930,12 @@ file suffix. >>> 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, None, None, encryption + ... b"friendlyname", key, cert, None, encryption ... ) .. class:: PKCS12Certificate - .. versionadded:: 36.0 + .. versionadded:: 36.0.0 Represents additional data provided for a certificate in a PKCS12 file. @@ -596,14 +951,15 @@ file suffix. .. class:: PKCS12KeyAndCertificates - .. versionadded:: 36.0 + .. 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`. + :attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates.cert` + (see :data:`PKCS12PrivateKeyTypes`). .. attribute:: cert @@ -618,6 +974,7 @@ file suffix. instances. .. class:: PBES + :canonical: cryptography.hazmat.primitives._serialization.PBES .. versionadded:: 38.0.0 @@ -649,6 +1006,25 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, ``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 @@ -687,7 +1063,7 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, .. function:: serialize_certificates(certs, encoding) - .. versionadded:: 37.0 + .. versionadded:: 37.0.0 Serialize a list of certificates to a PKCS7 structure. @@ -758,16 +1134,13 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``, :param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` - associated with the certificate provided. + 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 an instance of - :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`. + will be used to generate the signature. This must be one of the + types in :data:`PKCS7HashTypes`. .. method:: add_certificate(certificate) @@ -840,17 +1213,18 @@ 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.RSAPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey` and - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. .. attribute:: TraditionalOpenSSL @@ -952,12 +1326,12 @@ Serialization Formats An enumeration for public key formats. Used with the ``public_bytes`` method available on - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey` , and - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. .. attribute:: SubjectPublicKeyInfo @@ -1016,7 +1390,7 @@ Serialization Formats An enumeration for parameters formats. Used with the ``parameter_bytes`` method available on - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParametersWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. .. attribute:: PKCS3 @@ -1026,14 +1400,15 @@ 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.RSAPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` , - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization`, - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`, + :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 @@ -1086,6 +1461,7 @@ 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 @@ -1099,6 +1475,7 @@ Serialization Encryption Types 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 @@ -1108,6 +1485,7 @@ Serialization Encryption Types :param bytes password: The password to use for encryption. .. class:: NoEncryption + :canonical: cryptography.hazmat.primitives._serialization.NoEncryption Do not encrypt. diff --git a/docs/hazmat/primitives/asymmetric/x25519.rst b/docs/hazmat/primitives/asymmetric/x25519.rst index 014f3d0..859e0a5 100644 --- a/docs/hazmat/primitives/asymmetric/x25519.rst +++ b/docs/hazmat/primitives/asymmetric/x25519.rst @@ -129,6 +129,20 @@ Key interfaces :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 @@ -176,6 +190,19 @@ Key interfaces :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 diff --git a/docs/hazmat/primitives/asymmetric/x448.rst b/docs/hazmat/primitives/asymmetric/x448.rst index f166355..439c3b4 100644 --- a/docs/hazmat/primitives/asymmetric/x448.rst +++ b/docs/hazmat/primitives/asymmetric/x448.rst @@ -123,6 +123,20 @@ Key interfaces :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 @@ -171,6 +185,19 @@ Key interfaces :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 diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst index 572822e..b6c889d 100644 --- a/docs/hazmat/primitives/cryptographic-hashes.rst +++ b/docs/hazmat/primitives/cryptographic-hashes.rst @@ -117,7 +117,7 @@ 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 1.1.0. + these are not supported at this time due to limitations in OpenSSL. .. class:: BLAKE2b(digest_size) @@ -292,5 +292,5 @@ Interfaces .. _`Lifetimes of cryptographic hash functions`: https://valerieaurora.org/hash.html .. _`BLAKE2`: https://blake2.net .. _`length-extension attacks`: https://en.wikipedia.org/wiki/Length_extension_attack -.. _`GM/T 0004-2012`: http://www.oscca.gov.cn/sca/xxgk/2010-12/17/1002389/files/302a3ada057c4a73830536d03e683110.pdf +.. _`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 diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index ddd3356..7c5c643 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -62,7 +62,7 @@ PBKDF2 ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=390000, + ... iterations=480000, ... ) >>> key = kdf.derive(b"my great password") >>> # verify @@ -70,7 +70,7 @@ PBKDF2 ... algorithm=hashes.SHA256(), ... length=32, ... salt=salt, - ... iterations=390000, + ... iterations=480000, ... ) >>> kdf.verify(b"my great password", key) @@ -460,7 +460,8 @@ HKDF 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. + 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. @@ -723,7 +724,7 @@ KBKDF .. class:: KBKDFCMAC(algorithm, mode, length, rlen, llen, location,\ label, context, fixed) - .. versionadded:: 35.0 + .. 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 @@ -879,7 +880,7 @@ KBKDF .. attribute:: MiddleFixed - .. versionadded:: 38.0 + .. versionadded:: 38.0.0 The counter iteration variable will be concatenated in the middle of the fixed input data. @@ -1037,3 +1038,4 @@ Interface .. _`here`: https://stackoverflow.com/a/30308723/1170681 .. _`recommends`: https://tools.ietf.org/html/rfc7914#section-2 .. _`The scrypt paper`: https://www.tarsnap.com/scrypt/scrypt.pdf +.. _`understanding HKDF`: https://soatok.blog/2021/11/17/understanding-hkdf/ diff --git a/docs/hazmat/primitives/mac/hmac.rst b/docs/hazmat/primitives/mac/hmac.rst index c94b790..bce8538 100644 --- a/docs/hazmat/primitives/mac/hmac.rst +++ b/docs/hazmat/primitives/mac/hmac.rst @@ -54,7 +54,7 @@ of a message. ... cryptography.exceptions.InvalidSignature: Signature did not match digest. - :param key: Secret key as ``bytes``. + :param key: The secret key. :type key: :term:`bytes-like` :param algorithm: An :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` diff --git a/docs/hazmat/primitives/mac/poly1305.rst b/docs/hazmat/primitives/mac/poly1305.rst index 7504a07..e3240f5 100644 --- a/docs/hazmat/primitives/mac/poly1305.rst +++ b/docs/hazmat/primitives/mac/poly1305.rst @@ -48,7 +48,7 @@ messages allows an attacker to forge tags. Poly1305 is described in ... cryptography.exceptions.InvalidSignature: Value did not match computed tag. - :param key: Secret key as ``bytes``. + :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 diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index ec17e73..2bf7a88 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -152,7 +152,9 @@ Algorithms 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. + :term:`bits` in length. The 128-bit value is a concatenation of 4-byte + little-endian counter and the 12-byte nonce (as described in + :rfc:`7539`). :type nonce: :term:`bytes-like` .. note:: @@ -618,8 +620,6 @@ Interfaces 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 NotImplementedError: This is raised if the version of ``cffi`` - used is too old (this can happen on older PyPy releases). :raises ValueError: This is raised if the supplied buffer is too small. .. doctest:: diff --git a/docs/hazmat/primitives/twofactor.rst b/docs/hazmat/primitives/twofactor.rst index 0d7d88f..4cd437b 100644 --- a/docs/hazmat/primitives/twofactor.rst +++ b/docs/hazmat/primitives/twofactor.rst @@ -18,6 +18,15 @@ codes (HMAC). .. 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 @@ -47,7 +56,7 @@ codes (HMAC). :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. + 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`_, diff --git a/docs/installation.rst b/docs/installation.rst index 361ed5a..f35f270 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -13,34 +13,33 @@ single most common cause of installation problems. Supported platforms ------------------- -Currently we test ``cryptography`` on Python 3.6+ and PyPy3 on these +Currently we test ``cryptography`` on Python 3.7+ and PyPy3 7.3.10+ on these operating systems. * x86-64 RHEL 8.x +* x86-64 CentOS 9 Stream * x86-64 Fedora (latest) * x86-64 macOS 12 Monterey -* ARM64 macOS 12 Monterey -* x86-64 Ubuntu 18.04, 20.04, 22.04, rolling -* ARM64 Ubuntu 20.04 -* x86-64 Debian Stretch (9.x), Buster (10.x), Bullseye (11.x), Bookworm (12.x) +* ARM64 macOS 13 Ventura +* x86-64 Ubuntu 20.04, 22.04, rolling +* ARM64 Ubuntu 22.04 +* x86-64 Debian Buster (10.x), Bullseye (11.x), Bookworm (12.x) and Sid (unstable) * x86-64 Alpine (latest) * 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: +OpenSSL releases in addition to distribution provided releases from the +above supported platforms: -* ``OpenSSL 1.1.0-latest`` * ``OpenSSL 1.1.1-latest`` * ``OpenSSL 3.0-latest`` +* ``OpenSSL 3.1-latest`` -In addition we test against several versions of LibreSSL and the latest commit -in BoringSSL. - -.. warning:: - - Cryptography 37.0.0 has deprecated support for OpenSSL 1.1.0. +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 @@ -57,15 +56,14 @@ just run 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.6 and above). Wherever you place your copy of OpenSSL -you'll need to set the ``LIB`` and ``INCLUDE`` environment variables to include -the proper locations. For example: +(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 LIB=C:\OpenSSL-win64\lib;%LIB% - C:\> set INCLUDE=C:\OpenSSL-win64\include;%INCLUDE% + C:\> set OPENSSL_DIR=C:\OpenSSL-win64 C:\> pip install cryptography You will also need to have :ref:`Rust installed and @@ -81,11 +79,10 @@ Building cryptography on Linux .. note:: - If you are on RHEL/CentOS/Fedora/Debian/Ubuntu or another distribution - derived from the preceding list, then you should **upgrade pip** and - attempt to install ``cryptography`` again before following the instructions - to compile it below. These platforms will receive a binary wheel and - require no compiler if you have an updated ``pip``! + 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`` @@ -108,13 +105,13 @@ Alpine .. warning:: - The Rust available by default in Alpine < 3.14 is older than the minimum + The Rust available by default in Alpine < 3.15 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 + $ 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``. @@ -123,30 +120,30 @@ Debian/Ubuntu .. warning:: - The Rust available in some Debian versions is older than the minimum - supported version. Debian Bullseye is sufficiently new, but otherwise + The Rust available in most Debian versions is older than the minimum + supported version. Debian Bookworm is sufficiently new, but otherwise please 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 + python3-dev cargo pkg-config Fedora/RHEL/CentOS ~~~~~~~~~~~~~~~~~~ .. warning:: - For RHEL and CentOS you must be on version 8.3 or newer for the command - below to install a sufficiently new Rust. If your Rust is less than 1.48.0 + For RHEL and CentOS you must be on version 8.6 or newer for the command + below to install a sufficiently new Rust. If your Rust is less than 1.56.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 + openssl-devel cargo pkg-config Building @@ -230,7 +227,7 @@ dependencies. ./config no-shared no-ssl2 no-ssl3 -fPIC --prefix=${CWD}/openssl make && make install cd .. - CFLAGS="-I${CWD}/openssl/include" LDFLAGS="-L${CWD}/openssl/lib" pip wheel --no-binary :all: cryptography + OPENSSL_DIR="${CWD}/openssl" pip wheel --no-cache-dir --no-binary cryptography cryptography Building cryptography on macOS ------------------------------ @@ -262,7 +259,9 @@ 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. +`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 @@ -274,15 +273,15 @@ To build cryptography and dynamically link it: .. code-block:: console - $ brew install openssl@1.1 rust - $ env LDFLAGS="-L$(brew --prefix openssl@1.1)/lib" CFLAGS="-I$(brew --prefix openssl@1.1)/include" pip install cryptography + $ 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 LDFLAGS="-L/opt/local/lib" CFLAGS="-I/opt/local/include" pip install cryptography + $ env OPENSSL_DIR="-L/opt/local" pip install cryptography You can also build cryptography statically: @@ -290,15 +289,15 @@ You can also build cryptography statically: .. code-block:: console - $ brew install openssl@1.1 rust - $ env CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 LDFLAGS="$(brew --prefix openssl@1.1)/lib/libssl.a $(brew --prefix openssl@1.1)/lib/libcrypto.a" CFLAGS="-I$(brew --prefix openssl@1.1)/include" pip install cryptography + $ 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 CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 LDFLAGS="/opt/local/lib/libssl.a /opt/local/lib/libcrypto.a" CFLAGS="-I/opt/local/include" pip install cryptography + $ 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`_. @@ -315,7 +314,7 @@ Rust a Rust toolchain. Building ``cryptography`` requires having a working Rust toolchain. The current -minimum supported Rust version is 1.48.0. **This is newer than the Rust some +minimum supported Rust version is 1.56.0. **This is newer than the Rust some package managers ship**, so users may need to install with the instructions below. diff --git a/docs/limitations.rst b/docs/limitations.rst index 227ed6c..3f43c74 100644 --- a/docs/limitations.rst +++ b/docs/limitations.rst @@ -24,21 +24,23 @@ 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. +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. -For this reason, at present, we recommend not implementing online protocols +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. -If a constant time RSA PKCS1 v1.5 decryption API is truly required, you should -contribute one to ``cryptography``. - .. _`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-1276151799 \ No newline at end of file diff --git a/docs/openssl.rst b/docs/openssl.rst index b628d0a..d4e69f4 100644 --- a/docs/openssl.rst +++ b/docs/openssl.rst @@ -10,8 +10,8 @@ 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 more advanced -control. +of the project, but there are some public methods available for debugging +purposes. .. data:: cryptography.hazmat.backends.openssl.backend @@ -29,83 +29,17 @@ control. typically shown in hexadecimal (e.g. ``0x1010003f``). This is not necessarily the same version as it was compiled against. - .. method:: activate_osrandom_engine() +.. _legacy-provider: - Activates the OS random engine. This will effectively disable OpenSSL's - default CSPRNG. +Legacy provider in OpenSSL 3.x +------------------------------ - .. method:: osrandom_engine_implementation() +.. versionadded:: 39.0.0 - .. versionadded:: 1.7 - - Returns the implementation of OS random engine. - - .. method:: activate_builtin_random() - - This will activate the default OpenSSL CSPRNG. - -OS random engine ----------------- - -.. note:: - - As of OpenSSL 1.1.1d its CSPRNG is fork-safe by default. - ``cryptography`` does not compile or load the custom engine on - >= 1.1.1d. - -By default OpenSSL uses a user-space CSPRNG that is seeded from system random ( -``/dev/urandom`` or ``CryptGenRandom``). This CSPRNG is not reseeded -automatically when a process calls ``fork()``. This can result in situations -where two different processes can return similar or identical keys and -compromise the security of the system. - -The approach this project has chosen to mitigate this vulnerability is to -include an engine that replaces the OpenSSL default CSPRNG with one that -sources its entropy from ``/dev/urandom`` on UNIX-like operating systems and -uses ``CryptGenRandom`` on Windows. This method of pulling from the system pool -allows us to avoid potential issues with `initializing the RNG`_ as well as -protecting us from the ``fork()`` weakness. - -This engine is **active** by default when importing the OpenSSL backend. When -active this engine will be used to generate all the random data OpenSSL -requests. - -When importing only the binding it is added to the engine list but -**not activated**. - - -OS random sources ------------------ - -On macOS and FreeBSD ``/dev/urandom`` is an alias for ``/dev/random``. The -implementation on macOS uses the `Yarrow`_ algorithm. FreeBSD uses the -`Fortuna`_ algorithm. - -On Windows the implementation of ``CryptGenRandom`` depends on which version of -the operation system you are using. See the `Microsoft documentation`_ for more -details. - -Linux uses its own PRNG design. ``/dev/urandom`` is a non-blocking source -seeded from the same pool as ``/dev/random``. - -+------------------------------------------+------------------------------+ -| Windows | ``CryptGenRandom()`` | -+------------------------------------------+------------------------------+ -| Linux >= 3.17 with working | ``getrandom()`` | -| ``SYS_getrandom`` syscall | | -+------------------------------------------+------------------------------+ -| OpenBSD >= 5.6 | ``getentropy()`` | -+------------------------------------------+------------------------------+ -| BSD family (including macOS 10.12+) with | ``getentropy()`` | -| ``SYS_getentropy`` in ``sys/syscall.h`` | | -+------------------------------------------+------------------------------+ -| fallback | ``/dev/urandom`` with | -| | cached file descriptor | -+------------------------------------------+------------------------------+ +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/ -.. _`initializing the RNG`: https://en.wikipedia.org/wiki/OpenSSL#Predictable_private_keys_.28Debian-specific.29 -.. _`Fortuna`: https://en.wikipedia.org/wiki/Fortuna_(PRNG) -.. _`Yarrow`: https://en.wikipedia.org/wiki/Yarrow_algorithm -.. _`Microsoft documentation`: https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/nf-wincrypt-cryptgenrandom diff --git a/docs/random-numbers.rst b/docs/random-numbers.rst index 6161563..5d6fd3d 100644 --- a/docs/random-numbers.rst +++ b/docs/random-numbers.rst @@ -18,16 +18,13 @@ you can obtain them with: >>> import os >>> iv = os.urandom(16) -This will use ``/dev/urandom`` on UNIX platforms, and ``CryptGenRandom`` on -Windows. -If you need your random number as an integer (for example, for -:meth:`~cryptography.x509.CertificateBuilder.serial_number`), you can use +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(20), byteorder="big") + >>> 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 diff --git a/docs/security.rst b/docs/security.rst index 6cd9dbe..e1fba3a 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -52,14 +52,12 @@ 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 to ``alex.gaynor@gmail.com`` and/or ``paul.l.kehrer@gmail.com``. You -should verify that your MTA uses TLS to ensure the confidentiality of your -message. +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 via email, you should receive an acknowledgment -within 48 hours, and depending on the action to be taken, you may receive -further follow-up emails. +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 ------------------ @@ -89,4 +87,5 @@ The steps for issuing a security release are described in our :doc:`/doing-a-release` documentation. +.. _`security advisory page`: https://github.com/pyca/cryptography/security/advisories/new .. _`main`: https://github.com/pyca/cryptography diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 14f31e1..74642f3 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -38,6 +38,7 @@ decrypted decrypting deprecations DER +dereference deserialize deserialized Deserialization @@ -60,6 +61,8 @@ Google hazmat Homebrew hostname +hostnames +implementor incrementing indistinguishability initialisms @@ -75,6 +78,7 @@ Koblitz Lange logins metadata +MGF Monterey Mozilla multi @@ -91,11 +95,13 @@ personalization RHEL parsers Parsers +PEM pickleable plaintext Poly pre precompute +precomputed preprocessor preprocessors presentational @@ -125,6 +131,7 @@ unencrypted unicode unpadded unpadding +Ventura verifier Verifier Verisign diff --git a/docs/x509/certificate-transparency.rst b/docs/x509/certificate-transparency.rst index dffee0c..3393338 100644 --- a/docs/x509/certificate-transparency.rst +++ b/docs/x509/certificate-transparency.rst @@ -52,7 +52,7 @@ issued. .. attribute:: signature_hash_algorithm - .. versionadded:: 38.0 + .. versionadded:: 38.0.0 :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` @@ -61,7 +61,7 @@ issued. .. attribute:: signature_algorithm - .. versionadded:: 38.0 + .. versionadded:: 38.0.0 :type: :class:`~cryptography.x509.certificate_transparency.SignatureAlgorithm` @@ -70,7 +70,7 @@ issued. .. attribute:: signature - .. versionadded:: 38.0 + .. versionadded:: 38.0.0 :type: bytes @@ -78,7 +78,7 @@ issued. .. attribute:: extension_bytes - .. versionadded:: 38.0 + .. versionadded:: 38.0.0 :type: bytes @@ -111,7 +111,7 @@ issued. .. class:: SignatureAlgorithm - .. versionadded:: 38.0 + .. versionadded:: 38.0.0 An enumeration for SignedCertificateTimestamp signature algorithms. diff --git a/docs/x509/ocsp.rst b/docs/x509/ocsp.rst index aee6fdd..76bfc02 100644 --- a/docs/x509/ocsp.rst +++ b/docs/x509/ocsp.rst @@ -134,7 +134,8 @@ Creating Requests .. method:: add_certificate(cert, issuer, algorithm) Adds a request using a certificate, issuer certificate, and hash - algorithm. This can only be called once. + 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. @@ -151,6 +152,35 @@ Creating Requests :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. @@ -299,7 +329,7 @@ Creating Responses :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 certificate. + that will be used to sign the response. :param algorithm: The :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that @@ -774,4 +804,4 @@ Interfaces :type: int - The serial number of the certificate that was checked. \ No newline at end of file + The serial number of the certificate that was checked. diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 28069e8..e14c8ff 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -146,10 +146,35 @@ X.509 Reference -----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 @@ -168,7 +193,25 @@ Loading Certificates >>> 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 @@ -184,6 +227,7 @@ Loading Certificate Revocation Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. function:: load_pem_x509_crl(data) + :canonical: cryptography.x509.base.load_pem_x509_crl .. versionadded:: 1.1 @@ -205,6 +249,7 @@ Loading Certificate Revocation Lists True .. function:: load_der_x509_crl(data) + :canonical: cryptography.x509.base.load_der_x509_crl .. versionadded:: 1.1 @@ -220,6 +265,7 @@ Loading Certificate Signing Requests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. function:: load_pem_x509_csr(data) + :canonical: cryptography.x509.base.load_pem_x509_csr .. versionadded:: 0.9 @@ -242,6 +288,7 @@ Loading Certificate Signing Requests True .. function:: load_der_x509_csr(data) + :canonical: cryptography.x509.base.load_der_x509_csr .. versionadded:: 0.9 @@ -257,6 +304,7 @@ X.509 Certificate Object ~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: Certificate + :canonical: cryptography.x509.base.Certificate .. versionadded:: 0.7 @@ -307,13 +355,7 @@ X.509 Certificate Object The public key associated with the certificate. :returns: One of - :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` or - :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey` + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: @@ -395,6 +437,34 @@ X.509 Certificate Object >>> 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` @@ -462,10 +532,39 @@ X.509 Certificate Object 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 + .. versionadded:: 38.0.0 :type: bytes @@ -501,6 +600,7 @@ X.509 CRL (Certificate Revocation List) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateRevocationList + :canonical: cryptography.x509.base.CertificateRevocationList .. versionadded:: 1.0 @@ -663,6 +763,7 @@ X.509 Certificate Builder ~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateBuilder + :canonical: cryptography.x509.base.CertificateBuilder .. versionadded:: 1.0 @@ -724,13 +825,7 @@ X.509 Certificate Builder Sets the subject's public key. :param public_key: The subject's public key. This can be one of - :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` or - :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. method:: serial_number(serial_number) @@ -777,17 +872,13 @@ X.509 Certificate Builder :param critical: Set to ``True`` if the extension must be understood and handled by whoever reads the certificate. - .. method:: sign(private_key, algorithm) + .. method:: sign(private_key, algorithm, *, rsa_padding=None) Sign the certificate using the CA's private key. - :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 certificate. + :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 @@ -800,6 +891,22 @@ X.509 Certificate Builder :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` @@ -807,6 +914,7 @@ X.509 CSR (Certificate Signing Request) Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateSigningRequest + :canonical: cryptography.x509.base.CertificateSigningRequest .. versionadded:: 0.9 @@ -815,11 +923,7 @@ X.509 CSR (Certificate Signing Request) Object The public key associated with the request. :returns: One of - :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` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: @@ -880,7 +984,7 @@ X.509 CSR (Certificate Signing Request) Object .. attribute:: attributes - .. versionadded:: 36.0 + .. versionadded:: 36.0.0 :type: :class:`Attributes` @@ -927,6 +1031,7 @@ X.509 Certificate Revocation List Builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateRevocationListBuilder + :canonical: cryptography.x509.base.CertificateRevocationListBuilder .. versionadded:: 1.2 @@ -1010,13 +1115,9 @@ X.509 Certificate Revocation List Builder Sign this CRL using the CA's private key. - :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 certificate. + :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 @@ -1035,6 +1136,7 @@ X.509 Revoked Certificate Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: RevokedCertificate + :canonical: cryptography.x509.base.RevokedCertificate .. versionadded:: 1.0 @@ -1077,6 +1179,7 @@ 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 @@ -1129,6 +1232,7 @@ X.509 CSR (Certificate Signing Request) Builder Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: CertificateSigningRequestBuilder + :canonical: cryptography.x509.base.CertificateSigningRequestBuilder .. versionadded:: 1.0 @@ -1186,15 +1290,11 @@ X.509 CSR (Certificate Signing Request) Builder Object .. method:: sign(private_key, algorithm) - :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` + :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. + 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` @@ -1212,6 +1312,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. class:: Name + :canonical: cryptography.x509.name.Name .. versionadded:: 0.8 @@ -1251,7 +1352,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. classmethod:: from_rfc4514_string(data, attr_name_overrides=None) - .. versionadded: 37.0 + .. versionadded: 37.0.0 :param str data: An :rfc:`4514` string. :param attr_name_overrides: Specify custom OID to name mappings, which @@ -1290,7 +1391,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 - .. versionchanged:: 36.0 + .. versionchanged:: 36.0.0 Added ``attr_name_overrides`` parameter. @@ -1326,6 +1427,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. class:: Version + :canonical: cryptography.x509.base.Version .. versionadded:: 0.7 @@ -1340,6 +1442,7 @@ X.509 CSR (Certificate Signing Request) Builder Object For version 3 X.509 certificates. .. class:: NameAttribute + :canonical: cryptography.x509.name.NameAttribute .. versionadded:: 0.8 @@ -1354,13 +1457,15 @@ X.509 CSR (Certificate Signing Request) Builder Object .. attribute:: value - :type: str + :type: ``str`` or ``bytes`` - The value of the attribute. + 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 + .. versionadded:: 35.0.0 :type: str @@ -1370,7 +1475,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 - .. versionchanged:: 36.0 + .. versionchanged:: 36.0.0 Added ``attr_name_overrides`` parameter. @@ -1385,6 +1490,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. class:: RelativeDistinguishedName(attributes) + :canonical: cryptography.x509.name.RelativeDistinguishedName .. versionadded:: 1.6 @@ -1402,7 +1508,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. method:: rfc4514_string(attr_name_overrides=None) .. versionadded:: 2.5 - .. versionchanged:: 36.0 + .. versionchanged:: 36.0.0 Added ``attr_name_overrides`` parameter. @@ -1417,6 +1523,7 @@ X.509 CSR (Certificate Signing Request) Builder Object .. class:: ObjectIdentifier + :canonical: ObjectIdentifier .. versionadded:: 0.8 @@ -1436,6 +1543,7 @@ General Name Classes ~~~~~~~~~~~~~~~~~~~~ .. class:: GeneralName + :canonical: cryptography.x509.general_name.GeneralName .. versionadded:: 0.9 @@ -1443,6 +1551,7 @@ General Name Classes against. .. class:: RFC822Name(value) + :canonical: cryptography.x509.general_name.RFC822Name .. versionadded:: 0.9 @@ -1464,6 +1573,7 @@ General Name Classes :type: str .. class:: DNSName(value) + :canonical: cryptography.x509.general_name.DNSName .. versionadded:: 0.9 @@ -1487,6 +1597,7 @@ General Name Classes :type: str .. class:: DirectoryName(value) + :canonical: cryptography.x509.general_name.DirectoryName .. versionadded:: 0.9 @@ -1497,6 +1608,7 @@ General Name Classes :type: :class:`Name` .. class:: UniformResourceIdentifier(value) + :canonical: cryptography.x509.general_name.UniformResourceIdentifier .. versionadded:: 0.9 @@ -1519,6 +1631,7 @@ General Name Classes :type: str .. class:: IPAddress(value) + :canonical: cryptography.x509.general_name.IPAddress .. versionadded:: 0.9 @@ -1531,6 +1644,7 @@ General Name Classes or :class:`~ipaddress.IPv6Network`. .. class:: RegisteredID(value) + :canonical: cryptography.x509.general_name.RegisteredID .. versionadded:: 0.9 @@ -1541,6 +1655,7 @@ General Name Classes :type: :class:`ObjectIdentifier` .. class:: OtherName(type_id, value) + :canonical: cryptography.x509.general_name.OtherName .. versionadded:: 1.0 @@ -1558,6 +1673,7 @@ X.509 Extensions ~~~~~~~~~~~~~~~~ .. class:: Extensions + :canonical: cryptography.x509.extensions.Extensions .. versionadded:: 0.9 @@ -1597,6 +1713,7 @@ X.509 Extensions , critical=True, value=)> .. class:: Extension + :canonical: cryptography.x509.extensions.Extension .. versionadded:: 0.9 @@ -1620,6 +1737,7 @@ X.509 Extensions Returns an instance of the extension type corresponding to the OID. .. class:: ExtensionType + :canonical: cryptography.x509.extensions.ExtensionType .. versionadded:: 1.0 @@ -1634,13 +1752,14 @@ X.509 Extensions .. method:: public_bytes() - .. versionadded:: 36.0 + .. 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 @@ -1740,6 +1859,7 @@ X.509 Extensions .. class:: BasicConstraints(ca, path_length) + :canonical: cryptography.x509.extensions.BasicConstraints .. versionadded:: 0.9 @@ -1775,6 +1895,7 @@ X.509 Extensions is not allowed to create subordinates with ``ca`` set to true. .. class:: ExtendedKeyUsage(usages) + :canonical: cryptography.x509.extensions.ExtendedKeyusage .. versionadded:: 0.9 @@ -1797,6 +1918,7 @@ X.509 Extensions .. class:: OCSPNoCheck() + :canonical: cryptography.x509.extensions.OCSPNoCheck .. versionadded:: 1.0 @@ -1819,6 +1941,7 @@ X.509 Extensions .. class:: TLSFeature(features) + :canonical: cryptography.x509.extensions.TLSFeature .. versionadded:: 2.1 @@ -1837,6 +1960,7 @@ X.509 Extensions Returns :attr:`~cryptography.x509.oid.ExtensionOID.TLS_FEATURE`. .. class:: TLSFeatureType + :canonical: cryptography.x509.extensions.TLSFeatureType .. versionadded:: 2.1 @@ -1857,6 +1981,7 @@ X.509 Extensions .. class:: NameConstraints(permitted_subtrees, excluded_subtrees) + :canonical: cryptography.x509.extensions.NameConstraints .. versionadded:: 1.0 @@ -1891,6 +2016,7 @@ X.509 Extensions ``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 @@ -1952,11 +2078,7 @@ X.509 Extensions section 4.2.1.2. :param public_key: One of - :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` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPublicKeyTypes`. .. doctest:: @@ -1996,6 +2118,7 @@ X.509 Extensions .. class:: SubjectKeyIdentifier(digest) + :canonical: cryptography.x509.extensions.SubjectKeyIdentifier .. versionadded:: 0.9 @@ -2036,11 +2159,7 @@ X.509 Extensions recommendation in :rfc:`5280` section 4.2.1.2. :param public_key: One of - :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` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :data:`~cryptography.hazmat.primitives.asymmetric.types.CertificatePublicKeyTypes`. .. doctest:: @@ -2050,6 +2169,7 @@ X.509 Extensions .. class:: SubjectAlternativeName(general_names) + :canonical: cryptography.x509.extensions.SubjectAlternativeName .. versionadded:: 0.9 @@ -2090,6 +2210,7 @@ X.509 Extensions .. class:: IssuerAlternativeName(general_names) + :canonical: cryptography.x509.extensions.IssuerAlternativeName .. versionadded:: 1.0 @@ -2118,6 +2239,7 @@ X.509 Extensions .. class:: PrecertificateSignedCertificateTimestamps(scts) + :canonical: cryptography.x509.extensions.PrecertificateSignedCertificateTimestamps .. versionadded:: 2.0 @@ -2144,6 +2266,7 @@ X.509 Extensions .. class:: PrecertPoison() + :canonical: cryptography.x509.extensions.PrecertPoison .. versionadded:: 2.4 @@ -2161,6 +2284,7 @@ X.509 Extensions .. class:: SignedCertificateTimestamps(scts) + :canonical: cryptography.x509.extensions.SignedCertificateTimestamps .. versionadded:: 3.0 @@ -2188,6 +2312,7 @@ X.509 Extensions .. class:: DeltaCRLIndicator(crl_number) + :canonical: cryptography.x509.extensions.DeltaCRLIndicator .. versionadded:: 2.1 @@ -2212,6 +2337,7 @@ X.509 Extensions .. class:: AuthorityInformationAccess(descriptions) + :canonical: cryptography.x509.extensions.AuthorityInformationAccess .. versionadded:: 0.9 @@ -2235,6 +2361,7 @@ X.509 Extensions .. class:: SubjectInformationAccess(descriptions) + :canonical: cryptography.x509.extensions.SubjectInformationAccess .. versionadded:: 3.0 @@ -2258,6 +2385,7 @@ X.509 Extensions .. class:: AccessDescription(access_method, access_location) + :canonical: cryptography.x509.extensions.AccessDescription .. versionadded:: 0.9 @@ -2291,6 +2419,7 @@ X.509 Extensions Where to access the information defined by the access method. .. class:: FreshestCRL(distribution_points) + :canonical: cryptography.x509.extensions.FreshestCRL .. versionadded:: 2.1 @@ -2309,6 +2438,7 @@ X.509 Extensions :attr:`~cryptography.x509.oid.ExtensionOID.FRESHEST_CRL`. .. class:: CRLDistributionPoints(distribution_points) + :canonical: cryptography.x509.extensions.CRLDistributionPoints .. versionadded:: 0.9 @@ -2329,6 +2459,7 @@ X.509 Extensions :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 @@ -2364,6 +2495,7 @@ X.509 Extensions revocation checks. .. class:: ReasonFlags + :canonical: cryptography.x509.extensions.ReasonFlags .. versionadded:: 0.9 @@ -2416,6 +2548,7 @@ X.509 Extensions in a :class:`DistributionPoint`. .. class:: InhibitAnyPolicy(skip_certs) + :canonical: cryptography.x509.extensions.InhibitAnyPolicy .. versionadded:: 1.0 @@ -2445,6 +2578,7 @@ X.509 Extensions :type: int .. class:: PolicyConstraints + :canonical: cryptography.x509.extensions.PolicyConstraints .. versionadded:: 1.3 @@ -2483,6 +2617,7 @@ X.509 Extensions certificate, but not in additional certificates in the chain. .. class:: CRLNumber(crl_number) + :canonical: cryptography.x509.extensions.CRLNumber .. versionadded:: 1.2 @@ -2505,6 +2640,7 @@ X.509 Extensions .. 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 @@ -2574,6 +2710,7 @@ X.509 Extensions non-None. .. class:: UnrecognizedExtension + :canonical: cryptography.x509.extensions.UnrecognizedExtension .. versionadded:: 1.2 @@ -2594,7 +2731,36 @@ X.509 Extensions 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 @@ -2611,7 +2777,7 @@ X.509 Extensions def contains_domain_validated(policies): return any( - policy.oid.dotted_string == "2.23.140.1.2.1" + policy.policy_identifier.dotted_string == "2.23.140.1.2.1" for policy in policies ) @@ -2630,6 +2796,7 @@ 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 @@ -2649,6 +2816,7 @@ These classes may be present within a :class:`CertificatePolicies` instance. display to the relying party when the certificate is used. .. class:: UserNotice(notice_reference, explicit_text) + :canonical: cryptography.x509.extensions.UserNotice .. versionadded:: 0.9 @@ -2671,6 +2839,7 @@ These classes may be present within a :class:`CertificatePolicies` instance. :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 @@ -2699,6 +2868,7 @@ 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 @@ -2727,6 +2897,7 @@ These extensions are only valid within a :class:`RevokedCertificate` object. The type of the returned values depends on the :class:`GeneralName`. .. class:: CRLReason(reason) + :canonical: cryptography.x509.extensions.CRLReason .. versionadded:: 1.2 @@ -2748,6 +2919,7 @@ These extensions are only valid within a :class:`RevokedCertificate` object. :type: An element from :class:`~cryptography.x509.ReasonFlags` .. class:: InvalidityDate(invalidity_date) + :canonical: cryptography.x509.extensions.InvalidityDate .. versionadded:: 1.2 @@ -2776,6 +2948,7 @@ OCSP Extensions ~~~~~~~~~~~~~~~ .. class:: OCSPNonce(nonce) + :canonical: cryptography.x509.extensions.OCSPNonce .. versionadded:: 2.4 @@ -2797,12 +2970,36 @@ OCSP Extensions :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 + .. 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 @@ -2810,7 +3007,7 @@ X.509 Request Attributes .. method:: get_attribute_for_oid(oid) - .. versionadded:: 36.0 + .. versionadded:: 36.0.0 :param oid: An :class:`ObjectIdentifier` instance. @@ -2821,8 +3018,9 @@ X.509 Request Attributes .. class:: Attribute + :canonical: cryptography.x509.base.Attribute - .. versionadded:: 36.0 + .. versionadded:: 36.0.0 An attribute associated with an X.509 request. @@ -2847,6 +3045,7 @@ 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. @@ -2889,7 +3088,7 @@ instances. The following common OIDs are available as constants. 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 - :func:`~cryptography.x509.Certificate.serial_number`). + :attr:`~cryptography.x509.Certificate.serial_number`). .. attribute:: SURNAME @@ -2903,6 +3102,12 @@ instances. The following common OIDs are available as constants. 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"``. @@ -2974,6 +3179,7 @@ instances. The following common OIDs are available as constants. .. class:: SignatureAlgorithmOID + :canonical: cryptography.hazmat._oid.SignatureAlgorithmOID .. versionadded:: 1.0 @@ -3098,14 +3304,14 @@ instances. The following common OIDs are available as constants. .. attribute:: DSA_WITH_SHA384 - .. versionadded:: 36.0 + .. 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 + .. 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. @@ -3126,6 +3332,7 @@ instances. The following common OIDs are available as constants. .. class:: ExtendedKeyUsageOID + :canonical: cryptography.hazmat._oid.ExtendedKeyUsageOID .. versionadded:: 1.0 @@ -3175,7 +3382,7 @@ instances. The following common OIDs are available as constants. .. attribute:: SMARTCARD_LOGON - .. versionadded:: 35.0 + .. 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 @@ -3183,7 +3390,7 @@ instances. The following common OIDs are available as constants. .. attribute:: KERBEROS_PKINIT_KDC - .. versionadded:: 35.0 + .. 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 @@ -3192,7 +3399,7 @@ instances. The following common OIDs are available as constants. .. attribute:: IPSEC_IKE - .. versionadded:: 37.0 + .. 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, @@ -3201,7 +3408,7 @@ instances. The following common OIDs are available as constants. .. attribute:: CERTIFICATE_TRANSPARENCY - .. versionadded:: 38.0 + .. 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 @@ -3210,6 +3417,7 @@ instances. The following common OIDs are available as constants. .. class:: AuthorityInformationAccessOID + :canonical: cryptography.hazmat._oid.AuthorityInformationAccessOID .. versionadded:: 1.0 @@ -3227,6 +3435,7 @@ instances. The following common OIDs are available as constants. .. class:: SubjectInformationAccessOID + :canonical: cryptography.hazmat._oid.SubjectInformationAccessOID .. versionadded:: 3.0 @@ -3238,6 +3447,7 @@ instances. The following common OIDs are available as constants. .. class:: CertificatePoliciesOID + :canonical: cryptography.hazmat._oid.CertificatePoliciesOID .. versionadded:: 1.0 @@ -3255,6 +3465,7 @@ instances. The following common OIDs are available as constants. .. class:: ExtensionOID + :canonical: cryptography.hazmat._oid.ExtensionOID .. versionadded:: 1.0 @@ -3387,8 +3598,23 @@ instances. The following common OIDs are available as constants. 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 @@ -3406,6 +3632,7 @@ instances. The following common OIDs are available as constants. .. class:: OCSPExtensionOID + :canonical: cryptography.hazmat._oid.OCSPExtensionOID .. versionadded:: 2.4 @@ -3413,8 +3640,15 @@ instances. The following common OIDs are available as constants. 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 @@ -3431,6 +3665,7 @@ Helper Functions .. currentmodule:: cryptography.x509 .. function:: random_serial_number() + :canonical: cryptography.x509.base.random_serial_number .. versionadded:: 1.6 @@ -3442,6 +3677,7 @@ Exceptions .. currentmodule:: cryptography.x509 .. class:: InvalidVersion + :canonical: cryptography.x509.base.InvalidVersion This is raised when an X.509 certificate has an invalid version number. @@ -3452,6 +3688,7 @@ Exceptions 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. @@ -3463,6 +3700,7 @@ Exceptions 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. @@ -3474,6 +3712,7 @@ Exceptions Returns the OID. .. class:: AttributeNotFound + :canonical: cryptography.x509.base.AttributeNotFound This is raised when calling :meth:`Attributes.get_attribute_for_oid` with @@ -3486,6 +3725,7 @@ Exceptions 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. diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..86a6a68 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,271 @@ +# This file is dual licensed under the terms 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 typing +import uuid + +import nox + +nox.options.reuse_existing_virtualenvs = True + + +def install(session: nox.Session, *args: str) -> None: + session.install( + "-v", + "-c", + "ci-constraints-requirements.txt", + *args, + silent=False, + ) + + +@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, f".[{extras}]") + install(session, "-e", "./vectors") + + session.run("pip", "list") + + if session.name != "tests-nocoverage": + cov_args = [ + "--cov=cryptography", + "--cov=tests", + ] + else: + cov_args = [] + + session.run( + "pytest", + "-n", + "auto", + "--dist=worksteal", + *cov_args, + "--durations=10", + *session.posargs, + "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", + ) + + # This is in the docs job because `twine check` verifies that the README + # is valid reStructuredText. + session.run("python", "-m", "build", "--sdist") + session.run("twine", "check", "dist/*") + + +@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: + install(session, ".[pep8test,test,ssh,nox]") + + session.run("ruff", ".") + session.run("black", "--check", ".") + session.run("check-sdist") + session.run( + "mypy", + "src/cryptography/", + "vectors/cryptography_vectors/", + "tests/", + "release.py", + "noxfile.py", + ) + + +@nox.session +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"), + } + ) + + install(session, ".") + + with session.chdir("src/rust/"): + session.run("cargo", "fmt", "--all", "--", "--check", external=True) + session.run("cargo", "clippy", "--", "-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) + + +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: typing.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) diff --git a/pyproject.toml b/pyproject.toml index 01b8ace..ab696ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,92 @@ [build-system] requires = [ - # The minimum setuptools version is specific to the PEP 517 backend, - # and may be stricter than the version required in `setup.cfg` - "setuptools>=40.6.0,!=60.9.0", + # First version of setuptools to support pyproject.toml configuration + "setuptools>=61.0.0", "wheel", - # Must be kept in sync with the `install_requirements` in `setup.cfg` + # Must be kept in sync with `project.dependencies` "cffi>=1.12; platform_python_implementation != 'PyPy'", "setuptools-rust>=0.11.4", ] build-backend = "setuptools.build_meta" +[project] +name = "cryptography" +version = "41.0.7" +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 :: 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", +] + +[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/" + +[tool.setuptools] +zip-safe = false +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] +include = ["cryptography*"] + +[project.optional-dependencies] +ssh = ["bcrypt >=3.1.5"] + +# All the following are used for our own testing. +nox = ["nox"] +test = [ + "pytest >=6.2.0", + "pytest-benchmark", + "pytest-cov", + "pytest-xdist", + "pretend", +] +test-randomorder = ["pytest-randomly"] +docs = ["sphinx >=5.3.0", "sphinx-rtd-theme >=1.1.1"] +docstest = ["pyenchant >=1.6.11", "twine >=1.12.0", "sphinxcontrib-spelling >=4.0.1"] +sdist = ["build"] +pep8test = ["black", "ruff", "mypy", "check-sdist"] + [tool.black] line-length = 79 -target-version = ["py36"] +target-version = ["py37"] [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", @@ -27,6 +98,8 @@ 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 = [ @@ -45,9 +118,9 @@ source = [ [tool.coverage.paths] source = [ "src/cryptography", - "*.tox/*/lib*/python*/site-packages/cryptography", - "*.tox\\*\\Lib\\site-packages\\cryptography", - "*.tox/pypy/site-packages/cryptography", + "*.nox/*/lib*/python*/site-packages/cryptography", + "*.nox\\*\\Lib\\site-packages\\cryptography", + "*.nox/pypy/site-packages/cryptography", ] tests =[ "tests/", @@ -57,7 +130,25 @@ tests =[ [tool.coverage.report] exclude_lines = [ "@abc.abstractmethod", - "@abc.abstractproperty", "@typing.overload", "if typing.TYPE_CHECKING", ] + +[tool.ruff] +# UP006: Minimum Python 3.9 +# UP007, UP038: Minimum Python 3.10 +ignore = ['N818', 'UP006', 'UP007', 'UP038'] +select = ['E', 'F', 'I', 'N', 'W', 'UP'] +line-length = 79 + +[tool.ruff.isort] +known-first-party = ["cryptography", "cryptography_vectors", "tests"] + +[tool.check-sdist] +git-only = [ + "vectors/*", + "release.py", + "ci-constraints-requirements.txt", + ".gitattributes", + ".gitignore", +] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 23bb773..8bfd5a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,91 +1,3 @@ -[metadata] -name = cryptography -version = attr: cryptography.__version__ -description = cryptography is a package which provides cryptographic recipes and primitives to Python developers. -long_description = file: README.rst -long_description_content_type = text/x-rst -license = BSD-3-Clause OR Apache-2.0 -url = https://github.com/pyca/cryptography -author = The Python Cryptographic Authority and individual contributors -author_email = cryptography-dev@python.org -project_urls = - Documentation=https://cryptography.io/ - Source=https://github.com/pyca/cryptography/ - Issues=https://github.com/pyca/cryptography/issues - Changelog=https://cryptography.io/en/latest/changelog/ -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.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: Implementation :: CPython - Programming Language :: Python :: Implementation :: PyPy - Topic :: Security :: Cryptography - -[options] -python_requires = >=3.6 -include_package_data = True -zip_safe = False -package_dir = - =src -packages = find: -install_requires = - cffi >=1.12 - -[options.packages.find] -where = src -exclude = - _cffi_src - _cffi_src.* - -[options.extras_require] -test = - pytest>=6.2.0 - pytest-benchmark - pytest-cov - pytest-subtests - pytest-xdist - pretend - iso8601 - pytz - hypothesis>=1.11.4,!=3.79.2 -docs = - sphinx >= 1.6.5,!=1.8.0,!=3.1.0,!=3.1.1 - sphinx_rtd_theme -docstest = - pyenchant >= 1.6.11 - twine >= 1.12.0 - sphinxcontrib-spelling >= 4.0.1 -sdist = - setuptools_rust >= 0.11.4 -pep8test = - black - flake8 - flake8-import-order - pep8-naming -ssh = - bcrypt >= 3.1.5 - -[flake8] -ignore = E203,E211,W503,W504,N818 -exclude = .tox,*.egg,.git,_build,.hypothesis -select = E,W,F,N,I -application-import-names = cryptography,cryptography_vectors,tests - [egg_info] tag_build = tag_date = 0 diff --git a/setup.py b/setup.py index 320994e..4fe0c02 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ import shutil import subprocess import sys +import warnings from setuptools import setup @@ -30,6 +31,11 @@ raise +# distutils emits this warning if you pass `setup()` an unknown option. This +# is what happens if you somehow run this file without `cffi` installed: +# `cffi_modules` is an unknown option. +warnings.filterwarnings("error", message="Unknown distribution option") + base_dir = os.path.dirname(__file__) src_dir = os.path.join(base_dir, "src") @@ -37,24 +43,18 @@ # means that we need to add the src/ directory to the sys.path. sys.path.insert(0, src_dir) +if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (7, 3, 10): + raise RuntimeError("cryptography is not compatible with PyPy3 < 7.3.10") + try: - # See setup.cfg for most of the config metadata. + # See pyproject.toml for most of the config metadata. setup( - cffi_modules=[ - "src/_cffi_src/build_openssl.py:ffi", - ], rust_extensions=[ RustExtension( "cryptography.hazmat.bindings._rust", "src/rust/Cargo.toml", py_limited_api=True, - # Enable abi3 mode if we're not using PyPy. - features=( - [] - if platform.python_implementation() == "PyPy" - else ["pyo3/abi3-py36"] - ), - rust_version=">=1.48.0", + rust_version=">=1.56.0", ) ], ) diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index 3ead86a..6c4fd90 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -2,75 +2,21 @@ # 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 -from distutils import dist -from distutils.ccompiler import get_default_compiler -from distutils.command.config import config -from _cffi_src.utils import build_ffi_for_binding, compiler_type - - -def _get_openssl_libraries(platform): - if os.environ.get("CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS", None): - return [] - # OpenSSL goes by a different library name on different operating systems. - if platform == "win32" and compiler_type() == "msvc": - return [ - "libssl", - "libcrypto", - "advapi32", - "crypt32", - "gdi32", - "user32", - "ws2_32", - ] - else: - # darwin, linux, mingw all use this path - # In some circumstances, the order in which these libs are - # specified on the linker command-line is significant; - # libssl must come before libcrypto - # (https://marc.info/?l=openssl-users&m=135361825921871) - # -lpthread required due to usage of pthread an potential - # existance of a static part containing e.g. pthread_atfork - # (https://github.com/pyca/cryptography/issues/5084) - if sys.platform == "zos": - return ["ssl", "crypto"] - else: - return ["ssl", "crypto", "pthread"] - - -def _extra_compile_args(platform): - """ - We set -Wconversion args here so that we only do Wconversion checks on the - code we're compiling and not on cffi itself (as passing -Wconversion in - CFLAGS would do). We set no error on sign conversion because some - function signatures in LibreSSL differ from OpenSSL have changed on long - vs. unsigned long in the past. Since that isn't a precision issue we don't - care. - """ - # make sure the compiler used supports the flags to be added - is_gcc = False - if get_default_compiler() == "unix": - d = dist.Distribution() - cmd = config(d) - cmd._check_compiler() - is_gcc = ( - "gcc" in cmd.compiler.compiler[0] - or "clang" in cmd.compiler.compiler[0] - ) - if is_gcc or not ( - platform in ["win32", "hp-ux11", "sunos5"] - or platform.startswith("aix") - ): - return ["-Wconversion", "-Wno-error=sign-conversion"] - else: - return [] +# 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="cryptography.hazmat.bindings._openssl", + module_name="_openssl", module_prefix="_cffi_src.openssl.", modules=[ # This goes first so we can define some cryptography-wide symbols. @@ -81,7 +27,6 @@ def _extra_compile_args(platform): "bignum", "bio", "cmac", - "conf", "crypto", "dh", "dsa", @@ -90,12 +35,11 @@ def _extra_compile_args(platform): "engine", "err", "evp", + "evp_aead", "fips", - "hmac", "nid", "objects", "opensslv", - "osrandom_engine", "pem", "pkcs12", "rand", @@ -108,6 +52,18 @@ def _extra_compile_args(platform): "pkcs7", "callbacks", ], - libraries=_get_openssl_libraries(sys.platform), - extra_compile_args=_extra_compile_args(sys.platform), ) + +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) diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py index 17ded38..d2be452 100644 --- a/src/_cffi_src/openssl/asn1.py +++ b/src/_cffi_src/openssl/asn1.py @@ -2,6 +2,7 @@ # 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 @@ -39,20 +40,8 @@ """ FUNCTIONS = """ -void ASN1_OBJECT_free(ASN1_OBJECT *); - /* ASN1 STRING */ -unsigned char *ASN1_STRING_data(ASN1_STRING *); const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *); -int ASN1_STRING_set(ASN1_STRING *, const void *, int); - -/* ASN1 OCTET STRING */ -ASN1_OCTET_STRING *ASN1_OCTET_STRING_new(void); -void ASN1_OCTET_STRING_free(ASN1_OCTET_STRING *); -int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *, const unsigned char *, int); - -/* ASN1 IA5STRING */ -ASN1_IA5STRING *ASN1_IA5STRING_new(void); /* ASN1 INTEGER */ void ASN1_INTEGER_free(ASN1_INTEGER *); @@ -71,17 +60,14 @@ void ASN1_ENUMERATED_free(ASN1_ENUMERATED *); int ASN1_ENUMERATED_set(ASN1_ENUMERATED *, long); -/* These became const ASN1_* in 1.1.0 */ -int ASN1_STRING_type(ASN1_STRING *); -int ASN1_STRING_to_UTF8(unsigned char **, ASN1_STRING *); -int i2a_ASN1_INTEGER(BIO *, ASN1_INTEGER *); +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 *); -/* This became const ASN1_TIME in 1.1.0f */ -ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(ASN1_TIME *, +ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(const ASN1_TIME *, ASN1_GENERALIZEDTIME **); int ASN1_STRING_length(ASN1_STRING *); -int ASN1_STRING_set_default_mask_asc(char *); BIGNUM *ASN1_INTEGER_to_BN(ASN1_INTEGER *, BIGNUM *); ASN1_INTEGER *BN_to_ASN1_INTEGER(BIGNUM *, ASN1_INTEGER *); diff --git a/src/_cffi_src/openssl/bignum.py b/src/_cffi_src/openssl/bignum.py index 052c7ed..0444033 100644 --- a/src/_cffi_src/openssl/bignum.py +++ b/src/_cffi_src/openssl/bignum.py @@ -2,6 +2,7 @@ # 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 @@ -38,12 +39,8 @@ int BN_MONT_CTX_set(BN_MONT_CTX *, const BIGNUM *, BN_CTX *); void BN_MONT_CTX_free(BN_MONT_CTX *); -BIGNUM *BN_dup(const BIGNUM *); - int BN_set_word(BIGNUM *, BN_ULONG); -const BIGNUM *BN_value_one(void); - char *BN_bn2hex(const BIGNUM *); int BN_hex2bn(BIGNUM **, const char *); @@ -52,20 +49,8 @@ int BN_num_bits(const BIGNUM *); -int BN_cmp(const BIGNUM *, const BIGNUM *); int BN_is_negative(const BIGNUM *); int BN_is_odd(const BIGNUM *); -int BN_add(BIGNUM *, const BIGNUM *, const BIGNUM *); -int BN_sub(BIGNUM *, const BIGNUM *, const BIGNUM *); -int BN_nnmod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); -int BN_mod_add(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); -int BN_mod_sub(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); -int BN_mod_mul(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); -int BN_mod_exp(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, - BN_CTX *); int BN_mod_exp_mont(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *, BN_MONT_CTX *); int BN_mod_exp_mont_consttime(BIGNUM *, const BIGNUM *, const BIGNUM *, @@ -74,8 +59,6 @@ int BN_num_bytes(const BIGNUM *); -int BN_mod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); - /* The following 3 prime methods are exposed for Tribler. */ int BN_generate_prime_ex(BIGNUM *, int, int, const BIGNUM *, const BIGNUM *, BN_GENCB *); diff --git a/src/_cffi_src/openssl/bio.py b/src/_cffi_src/openssl/bio.py index 3e83f2f..1742e34 100644 --- a/src/_cffi_src/openssl/bio.py +++ b/src/_cffi_src/openssl/bio.py @@ -2,6 +2,7 @@ # 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 @@ -15,14 +16,9 @@ FUNCTIONS = """ int BIO_free(BIO *); -void BIO_free_all(BIO *); BIO *BIO_new_file(const char *, const char *); -size_t BIO_ctrl_pending(BIO *); int BIO_read(BIO *, void *, int); -int BIO_gets(BIO *, char *, int); int BIO_write(BIO *, const void *, int); -/* Added in 1.1.0 */ -int BIO_up_ref(BIO *); BIO *BIO_new(BIO_METHOD *); const BIO_METHOD *BIO_s_mem(void); @@ -34,8 +30,6 @@ int BIO_should_io_special(BIO *); int BIO_should_retry(BIO *); int BIO_reset(BIO *); -void BIO_set_retry_read(BIO *); -void BIO_clear_retry_flags(BIO *); BIO_ADDR *BIO_ADDR_new(void); void BIO_ADDR_free(BIO_ADDR *); @@ -43,7 +37,11 @@ CUSTOMIZATIONS = """ #if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL + +#if !defined(_WIN32) #include +#endif + #include typedef struct sockaddr BIO_ADDR; diff --git a/src/_cffi_src/openssl/callbacks.py b/src/_cffi_src/openssl/callbacks.py index 79d4f24..ddb7642 100644 --- a/src/_cffi_src/openssl/callbacks.py +++ b/src/_cffi_src/openssl/callbacks.py @@ -2,6 +2,7 @@ # 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 @@ -31,7 +32,7 @@ } CRYPTOGRAPHY_PASSWORD_DATA; int Cryptography_pem_password_cb(char *buf, int size, - int rwflag, void *userdata) { + int rwflag, void *userdata) { /* The password cb is only invoked if OpenSSL decides the private key is encrypted. So this path only occurs if it needs a password */ CRYPTOGRAPHY_PASSWORD_DATA *st = (CRYPTOGRAPHY_PASSWORD_DATA *)userdata; @@ -41,7 +42,7 @@ st->error = -1; return 0; } else if (st->length < size) { - memcpy(buf, st->password, st->length); + memcpy(buf, st->password, (size_t)st->length); return st->length; } else { st->error = -2; diff --git a/src/_cffi_src/openssl/cmac.py b/src/_cffi_src/openssl/cmac.py index a254263..7095066 100644 --- a/src/_cffi_src/openssl/cmac.py +++ b/src/_cffi_src/openssl/cmac.py @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations INCLUDES = """ #if !defined(OPENSSL_NO_CMAC) diff --git a/src/_cffi_src/openssl/conf.py b/src/_cffi_src/openssl/conf.py deleted file mode 100644 index dd1e80a..0000000 --- a/src/_cffi_src/openssl/conf.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - - -INCLUDES = """ -#include -""" - -TYPES = """ -""" - -FUNCTIONS = """ -void OPENSSL_config(const char *); -""" - -CUSTOMIZATIONS = """ -""" diff --git a/src/_cffi_src/openssl/crypto.py b/src/_cffi_src/openssl/crypto.py index fb95646..b81b5de 100644 --- a/src/_cffi_src/openssl/crypto.py +++ b/src/_cffi_src/openssl/crypto.py @@ -2,6 +2,7 @@ # 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 @@ -9,13 +10,7 @@ TYPES = """ static const long Cryptography_HAS_MEM_FUNCTIONS; -static const long Cryptography_HAS_OPENSSL_CLEANUP; -static const int SSLEAY_VERSION; -static const int SSLEAY_CFLAGS; -static const int SSLEAY_PLATFORM; -static const int SSLEAY_DIR; -static const int SSLEAY_BUILT_ON; static const int OPENSSL_VERSION; static const int OPENSSL_CFLAGS; static const int OPENSSL_BUILT_ON; @@ -26,14 +21,9 @@ FUNCTIONS = """ void OPENSSL_cleanup(void); -/* SSLeay was removed in 1.1.0 */ -unsigned long SSLeay(void); -const char *SSLeay_version(int); -/* these functions were added to replace the SSLeay functions in 1.1.0 */ unsigned long OpenSSL_version_num(void); const char *OpenSSL_version(int); -/* this is a macro in 1.1.0 */ void *OPENSSL_malloc(size_t); void OPENSSL_free(void *); @@ -51,37 +41,6 @@ """ CUSTOMIZATIONS = """ -/* In 1.1.0 SSLeay has finally been retired. We bidirectionally define the - values so you can use either one. This is so we can use the new function - names no matter what OpenSSL we're running on, but users on older pyOpenSSL - releases won't see issues if they're running OpenSSL 1.1.0 */ -#if !defined(SSLEAY_VERSION) -# define SSLeay OpenSSL_version_num -# define SSLeay_version OpenSSL_version -# define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER -# define SSLEAY_VERSION OPENSSL_VERSION -# define SSLEAY_CFLAGS OPENSSL_CFLAGS -# define SSLEAY_BUILT_ON OPENSSL_BUILT_ON -# define SSLEAY_PLATFORM OPENSSL_PLATFORM -# define SSLEAY_DIR OPENSSL_DIR -#endif -#if !defined(OPENSSL_VERSION) -# define OpenSSL_version_num SSLeay -# define OpenSSL_version SSLeay_version -# define OPENSSL_VERSION SSLEAY_VERSION -# define OPENSSL_CFLAGS SSLEAY_CFLAGS -# define OPENSSL_BUILT_ON SSLEAY_BUILT_ON -# define OPENSSL_PLATFORM SSLEAY_PLATFORM -# define OPENSSL_DIR SSLEAY_DIR -#endif - -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_360 -static const long Cryptography_HAS_OPENSSL_CLEANUP = 0; -void (*OPENSSL_cleanup)(void) = NULL; -#else -static const long Cryptography_HAS_OPENSSL_CLEANUP = 1; -#endif - #if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_MEM_FUNCTIONS = 0; int (*Cryptography_CRYPTO_set_mem_functions)( diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index 44a69cd..f5fcb04 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -2,12 +2,30 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations INCLUDES = """ -/* define our OpenSSL API compatibility level to 1.0.1. Any symbols older than - that will raise an error during compilation. We can raise this number again - after we drop 1.0.2 support in the distant future. */ -#define OPENSSL_API_COMPAT 0x10001000L +/* 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 @@ -24,76 +42,36 @@ #define CRYPTOGRAPHY_IS_BORINGSSL 0 #endif -/* - LibreSSL removed e_os2.h from the public headers so we'll only include it - if we're using vanilla OpenSSL. -*/ -#if !CRYPTOGRAPHY_IS_LIBRESSL -#include -#endif -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#endif - #if CRYPTOGRAPHY_IS_LIBRESSL -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_322 \ - (LIBRESSL_VERSION_NUMBER < 0x3020200f) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_332 \ - (LIBRESSL_VERSION_NUMBER < 0x3030200f) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 \ - (LIBRESSL_VERSION_NUMBER < 0x3040000f) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_350 \ - (LIBRESSL_VERSION_NUMBER < 0x3050000f) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_360 \ - (LIBRESSL_VERSION_NUMBER < 0x3060000f) +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 \ + (LIBRESSL_VERSION_NUMBER < 0x3070000f) #else -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_322 (0) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_332 (0) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 (0) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_350 (0) -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_360 (0) +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 (0) #endif -#if OPENSSL_VERSION_NUMBER < 0x10100000 - #error "pyca/cryptography MUST be linked with Openssl 1.1.0 or later" +#if OPENSSL_VERSION_NUMBER < 0x10101040 + #error "pyca/cryptography MUST be linked with Openssl 1.1.1d or later" #endif -#define CRYPTOGRAPHY_OPENSSL_111D_OR_GREATER \ - (OPENSSL_VERSION_NUMBER >= 0x10101040 && !CRYPTOGRAPHY_IS_LIBRESSL) #define CRYPTOGRAPHY_OPENSSL_300_OR_GREATER \ (OPENSSL_VERSION_NUMBER >= 0x30000000 && !CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 \ - (OPENSSL_VERSION_NUMBER < 0x10101000 || CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B \ - (OPENSSL_VERSION_NUMBER < 0x10101020 || CRYPTOGRAPHY_IS_LIBRESSL) -#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111D \ - (OPENSSL_VERSION_NUMBER < 0x10101040 || CRYPTOGRAPHY_IS_LIBRESSL) #define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E \ (OPENSSL_VERSION_NUMBER < 0x10101050 || CRYPTOGRAPHY_IS_LIBRESSL) -#if (CRYPTOGRAPHY_OPENSSL_LESS_THAN_111D && !CRYPTOGRAPHY_IS_LIBRESSL && \ - !defined(OPENSSL_NO_ENGINE)) || defined(USE_OSRANDOM_RNG_FOR_TESTING) -#define CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE 1 -#else -#define CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE 0 -#endif +/* Ed25519 support is in all supported OpenSSLs as well as LibreSSL 3.7.0. */ +#define CRYPTOGRAPHY_HAS_WORKING_ED25519 \ + (!CRYPTOGRAPHY_IS_LIBRESSL || \ + (CRYPTOGRAPHY_IS_LIBRESSL && !CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370)) """ TYPES = """ -static const int CRYPTOGRAPHY_OPENSSL_111D_OR_GREATER; static const int CRYPTOGRAPHY_OPENSSL_300_OR_GREATER; -static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111; -static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B; static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E; -static const int CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE; +static const int CRYPTOGRAPHY_HAS_WORKING_ED25519; -static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340; -static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_350; +static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370; static const int CRYPTOGRAPHY_IS_LIBRESSL; static const int CRYPTOGRAPHY_IS_BORINGSSL; diff --git a/src/_cffi_src/openssl/dh.py b/src/_cffi_src/openssl/dh.py index c378ad1..b4a42e7 100644 --- a/src/_cffi_src/openssl/dh.py +++ b/src/_cffi_src/openssl/dh.py @@ -2,6 +2,7 @@ # 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 @@ -14,151 +15,8 @@ """ FUNCTIONS = """ -DH *DH_new(void); void DH_free(DH *); -int DH_size(const DH *); -int DH_generate_key(DH *); -DH *DHparams_dup(DH *); - -/* added in 1.1.0 when the DH struct was opaqued */ -void DH_get0_pqg(const DH *, const BIGNUM **, const BIGNUM **, - const BIGNUM **); -int DH_set0_pqg(DH *, BIGNUM *, BIGNUM *, BIGNUM *); -void DH_get0_key(const DH *, const BIGNUM **, const BIGNUM **); -int DH_set0_key(DH *, BIGNUM *, BIGNUM *); - -int Cryptography_DH_check(const DH *, int *); -int DH_generate_parameters_ex(DH *, int, int, BN_GENCB *); -DH *d2i_DHparams_bio(BIO *, DH **); -int i2d_DHparams_bio(BIO *, DH *); -DH *Cryptography_d2i_DHxparams_bio(BIO *, DH **); -int Cryptography_i2d_DHxparams_bio(BIO *, DH *); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_350 -#ifndef DH_CHECK_Q_NOT_PRIME -#define DH_CHECK_Q_NOT_PRIME 0x10 -#endif - -#ifndef DH_CHECK_INVALID_Q_VALUE -#define DH_CHECK_INVALID_Q_VALUE 0x20 -#endif - -#ifndef DH_CHECK_INVALID_J_VALUE -#define DH_CHECK_INVALID_J_VALUE 0x40 -#endif - -/* DH_check implementation taken from OpenSSL 1.1.0pre6 */ - -/*- - * Check that p is a safe prime and - * if g is 2, 3 or 5, check that it is a suitable generator - * where - * for 2, p mod 24 == 11 - * for 3, p mod 12 == 5 - * for 5, p mod 10 == 3 or 7 - * should hold. - */ - -int Cryptography_DH_check(const DH *dh, int *ret) -{ - int ok = 0, r; - BN_CTX *ctx = NULL; - BN_ULONG l; - BIGNUM *t1 = NULL, *t2 = NULL; - - *ret = 0; - ctx = BN_CTX_new(); - if (ctx == NULL) - goto err; - BN_CTX_start(ctx); - t1 = BN_CTX_get(ctx); - if (t1 == NULL) - goto err; - t2 = BN_CTX_get(ctx); - if (t2 == NULL) - goto err; - - if (dh->q) { - if (BN_cmp(dh->g, BN_value_one()) <= 0) - *ret |= DH_NOT_SUITABLE_GENERATOR; - else if (BN_cmp(dh->g, dh->p) >= 0) - *ret |= DH_NOT_SUITABLE_GENERATOR; - else { - /* Check g^q == 1 mod p */ - if (!BN_mod_exp(t1, dh->g, dh->q, dh->p, ctx)) - goto err; - if (!BN_is_one(t1)) - *ret |= DH_NOT_SUITABLE_GENERATOR; - } - r = BN_is_prime_ex(dh->q, BN_prime_checks, ctx, NULL); - if (r < 0) - goto err; - if (!r) - *ret |= DH_CHECK_Q_NOT_PRIME; - /* Check p == 1 mod q i.e. q divides p - 1 */ - if (!BN_div(t1, t2, dh->p, dh->q, ctx)) - goto err; - if (!BN_is_one(t2)) - *ret |= DH_CHECK_INVALID_Q_VALUE; - if (dh->j && BN_cmp(dh->j, t1)) - *ret |= DH_CHECK_INVALID_J_VALUE; - - } else if (BN_is_word(dh->g, DH_GENERATOR_2)) { - l = BN_mod_word(dh->p, 24); - if (l == (BN_ULONG)-1) - goto err; - if (l != 11) - *ret |= DH_NOT_SUITABLE_GENERATOR; - } else if (BN_is_word(dh->g, DH_GENERATOR_5)) { - l = BN_mod_word(dh->p, 10); - if (l == (BN_ULONG)-1) - goto err; - if ((l != 3) && (l != 7)) - *ret |= DH_NOT_SUITABLE_GENERATOR; - } else - *ret |= DH_UNABLE_TO_CHECK_GENERATOR; - - r = BN_is_prime_ex(dh->p, BN_prime_checks, ctx, NULL); - if (r < 0) - goto err; - if (!r) - *ret |= DH_CHECK_P_NOT_PRIME; - else if (!dh->q) { - if (!BN_rshift1(t1, dh->p)) - goto err; - r = BN_is_prime_ex(t1, BN_prime_checks, ctx, NULL); - if (r < 0) - goto err; - if (!r) - *ret |= DH_CHECK_P_NOT_SAFE_PRIME; - } - ok = 1; - err: - if (ctx != NULL) { - BN_CTX_end(ctx); - BN_CTX_free(ctx); - } - return (ok); -} -#else -int Cryptography_DH_check(const DH *dh, int *ret) { - return DH_check(dh, ret); -} -#endif - -/* These functions were added in OpenSSL 1.1.0f commit d0c50e80a8 */ -/* Define our own to simplify support across all versions. */ -#if defined(EVP_PKEY_DHX) && EVP_PKEY_DHX != -1 -DH *Cryptography_d2i_DHxparams_bio(BIO *bp, DH **x) { - return ASN1_d2i_bio_of(DH, DH_new, d2i_DHxparams, bp, x); -} -int Cryptography_i2d_DHxparams_bio(BIO *bp, DH *x) { - return ASN1_i2d_bio_of_const(DH, i2d_DHxparams, bp, x); -} -#else -DH *(*Cryptography_d2i_DHxparams_bio)(BIO *bp, DH **x) = NULL; -int (*Cryptography_i2d_DHxparams_bio)(BIO *bp, DH *x) = NULL; -#endif """ diff --git a/src/_cffi_src/openssl/dsa.py b/src/_cffi_src/openssl/dsa.py index 7f3f452..d910763 100644 --- a/src/_cffi_src/openssl/dsa.py +++ b/src/_cffi_src/openssl/dsa.py @@ -2,6 +2,7 @@ # 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 @@ -22,11 +23,7 @@ int DSA_verify(int, const unsigned char *, int, const unsigned char *, int, DSA *); -/* added in 1.1.0 to access the opaque struct */ -void DSA_get0_pqg(const DSA *, const BIGNUM **, const BIGNUM **, - const BIGNUM **); int DSA_set0_pqg(DSA *, BIGNUM *, BIGNUM *, BIGNUM *); -void DSA_get0_key(const DSA *, const BIGNUM **, const BIGNUM **); int DSA_set0_key(DSA *, BIGNUM *, BIGNUM *); int DSA_generate_parameters_ex(DSA *, int, unsigned char *, int, int *, unsigned long *, BN_GENCB *); diff --git a/src/_cffi_src/openssl/ec.py b/src/_cffi_src/openssl/ec.py index d9c3074..0e3604d 100644 --- a/src/_cffi_src/openssl/ec.py +++ b/src/_cffi_src/openssl/ec.py @@ -2,6 +2,7 @@ # 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 @@ -9,14 +10,11 @@ """ TYPES = """ -static const int Cryptography_HAS_EC2M; - static const int OPENSSL_EC_NAMED_CURVE; typedef ... EC_KEY; typedef ... EC_GROUP; typedef ... EC_POINT; -typedef ... EC_METHOD; typedef struct { int nid; const char *comment; @@ -33,40 +31,30 @@ EC_GROUP *EC_GROUP_new_by_curve_name(int); -const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *); -const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *); int EC_GROUP_get_curve_name(const EC_GROUP *); size_t EC_get_builtin_curves(EC_builtin_curve *, size_t); -EC_KEY *EC_KEY_new(void); void EC_KEY_free(EC_KEY *); EC_KEY *EC_KEY_new_by_curve_name(int); const EC_GROUP *EC_KEY_get0_group(const EC_KEY *); -int EC_GROUP_get_order(const EC_GROUP *, BIGNUM *, BN_CTX *); -int EC_KEY_set_group(EC_KEY *, const EC_GROUP *); const BIGNUM *EC_KEY_get0_private_key(const EC_KEY *); int EC_KEY_set_private_key(EC_KEY *, const BIGNUM *); const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *); int EC_KEY_set_public_key(EC_KEY *, const EC_POINT *); void EC_KEY_set_asn1_flag(EC_KEY *, int); int EC_KEY_generate_key(EC_KEY *); -int EC_KEY_set_public_key_affine_coordinates(EC_KEY *, BIGNUM *, BIGNUM *); EC_POINT *EC_POINT_new(const EC_GROUP *); void EC_POINT_free(EC_POINT *); -void EC_POINT_clear_free(EC_POINT *); -EC_POINT *EC_POINT_dup(const EC_POINT *, const EC_GROUP *); - -int EC_POINT_set_affine_coordinates_GFp(const EC_GROUP *, EC_POINT *, - const BIGNUM *, const BIGNUM *, BN_CTX *); +int EC_POINT_cmp(const EC_GROUP *, const EC_POINT *, const EC_POINT *, + BN_CTX *); -int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *, - const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *); - -int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP *, - const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *); +int EC_POINT_set_affine_coordinates(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, BN_CTX *); +int EC_POINT_get_affine_coordinates(const EC_GROUP *, const EC_POINT *, + BIGNUM *, BIGNUM *, BN_CTX *); size_t EC_POINT_point2oct(const EC_GROUP *, const EC_POINT *, point_conversion_form_t, @@ -75,35 +63,15 @@ int EC_POINT_oct2point(const EC_GROUP *, EC_POINT *, const unsigned char *, size_t, BN_CTX *); -int EC_POINT_add(const EC_GROUP *, EC_POINT *, const EC_POINT *, - const EC_POINT *, BN_CTX *); - -int EC_POINT_dbl(const EC_GROUP *, EC_POINT *, const EC_POINT *, BN_CTX *); -int EC_POINT_invert(const EC_GROUP *, EC_POINT *, BN_CTX *); int EC_POINT_is_at_infinity(const EC_GROUP *, const EC_POINT *); -int EC_POINT_is_on_curve(const EC_GROUP *, const EC_POINT *, BN_CTX *); - -int EC_POINT_cmp( - const EC_GROUP *, const EC_POINT *, const EC_POINT *, BN_CTX *); int EC_POINT_mul(const EC_GROUP *, EC_POINT *, const BIGNUM *, const EC_POINT *, const BIGNUM *, BN_CTX *); -int EC_METHOD_get_field_type(const EC_METHOD *); - const char *EC_curve_nid2nist(int); int EC_GROUP_get_asn1_flag(const EC_GROUP *); """ CUSTOMIZATIONS = """ -#if defined(OPENSSL_NO_EC2M) -static const long Cryptography_HAS_EC2M = 0; - -int (*EC_POINT_get_affine_coordinates_GF2m)(const EC_GROUP *, - const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *) = NULL; - -#else -static const long Cryptography_HAS_EC2M = 1; -#endif """ diff --git a/src/_cffi_src/openssl/ecdsa.py b/src/_cffi_src/openssl/ecdsa.py index 53294af..716b5d0 100644 --- a/src/_cffi_src/openssl/ecdsa.py +++ b/src/_cffi_src/openssl/ecdsa.py @@ -2,6 +2,7 @@ # 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 diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py index 9931639..9629a2c 100644 --- a/src/_cffi_src/openssl/engine.py +++ b/src/_cffi_src/openssl/engine.py @@ -2,6 +2,7 @@ # 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 @@ -41,18 +42,20 @@ typedef void UI_METHOD; #endif -/* Despite being OPENSSL_NO_ENGINE, BoringSSL defines these symbols. */ -#if !CRYPTOGRAPHY_IS_BORINGSSL +/* 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_by_id)(const char *) = NULL; -int (*ENGINE_init)(ENGINE *) = NULL; -int (*ENGINE_finish)(ENGINE *) = NULL; 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; @@ -65,6 +68,7 @@ void *) = NULL; EVP_PKEY *(*ENGINE_load_public_key)(ENGINE *, const char *, UI_METHOD *, void *) = NULL; +#endif #else static const long Cryptography_HAS_ENGINE = 1; diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index dc27abb..2bb2545 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -2,6 +2,7 @@ # 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 @@ -15,28 +16,22 @@ static const int EVP_R_BAD_DECRYPT; static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM; static const int PKCS12_R_PKCS12_CIPHERFINAL_ERROR; -static const int PEM_R_UNSUPPORTED_ENCRYPTION; static const int EVP_R_XTS_DUPLICATED_KEYS; static const int ERR_LIB_EVP; -static const int ERR_LIB_PEM; static const int ERR_LIB_PROV; -static const int ERR_LIB_ASN1; static const int ERR_LIB_PKCS12; 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 X509_R_CERT_ALREADY_IN_HASH_TABLE; - static const int SSL_R_UNEXPECTED_EOF_WHILE_READING; static const int Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING; """ FUNCTIONS = """ -void ERR_error_string_n(unsigned long, char *, size_t); const char *ERR_lib_error_string(unsigned long); const char *ERR_func_error_string(unsigned long); const char *ERR_reason_error_string(unsigned long); @@ -45,9 +40,7 @@ void ERR_clear_error(void); void ERR_put_error(int, int, int, const char *, int); -int ERR_GET_LIB(unsigned long); int ERR_GET_REASON(unsigned long); - """ CUSTOMIZATIONS = """ @@ -57,7 +50,7 @@ #define ERR_LIB_PROV 0 #endif -#if !CRYPTOGRAPHY_OPENSSL_111D_OR_GREATER || CRYPTOGRAPHY_IS_BORINGSSL +#ifndef EVP_R_XTS_DUPLICATED_KEYS static const int EVP_R_XTS_DUPLICATED_KEYS = 0; #endif diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py index f4d9fb9..ce54fd9 100644 --- a/src/_cffi_src/openssl/evp.py +++ b/src/_cffi_src/openssl/evp.py @@ -2,6 +2,7 @@ # 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 @@ -33,13 +34,12 @@ static const int Cryptography_HAS_SCRYPT; static const int Cryptography_HAS_EVP_PKEY_DHX; -static const int Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint; -static const int Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY; static const long Cryptography_HAS_RAW_KEY; static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF; static const long Cryptography_HAS_300_FIPS; static const long Cryptography_HAS_300_EVP_CIPHER; static const long Cryptography_HAS_EVP_PKEY_DH; +static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX; """ FUNCTIONS = """ @@ -58,10 +58,6 @@ void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *); int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *, int); -int EVP_MD_CTX_copy_ex(EVP_MD_CTX *, const EVP_MD_CTX *); -int EVP_DigestInit_ex(EVP_MD_CTX *, const EVP_MD *, ENGINE *); -int EVP_DigestUpdate(EVP_MD_CTX *, const void *, size_t); -int EVP_DigestFinal_ex(EVP_MD_CTX *, unsigned char *, unsigned int *); int EVP_DigestFinalXOF(EVP_MD_CTX *, unsigned char *, size_t); const EVP_MD *EVP_get_digestbyname(const char *); @@ -70,8 +66,6 @@ int EVP_PKEY_type(int); int EVP_PKEY_size(EVP_PKEY *); RSA *EVP_PKEY_get1_RSA(EVP_PKEY *); -DSA *EVP_PKEY_get1_DSA(EVP_PKEY *); -DH *EVP_PKEY_get1_DH(EVP_PKEY *); int EVP_PKEY_encrypt(EVP_PKEY_CTX *, unsigned char *, size_t *, const unsigned char *, size_t); @@ -87,17 +81,8 @@ int EVP_VerifyFinal(EVP_MD_CTX *, const unsigned char *, unsigned int, EVP_PKEY *); -int EVP_DigestSignInit(EVP_MD_CTX *, EVP_PKEY_CTX **, const EVP_MD *, - ENGINE *, EVP_PKEY *); -int EVP_DigestSignUpdate(EVP_MD_CTX *, const void *, size_t); -int EVP_DigestSignFinal(EVP_MD_CTX *, unsigned char *, size_t *); -int EVP_DigestVerifyInit(EVP_MD_CTX *, EVP_PKEY_CTX **, const EVP_MD *, - ENGINE *, EVP_PKEY *); - - EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *, ENGINE *); -EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int, ENGINE *); void EVP_PKEY_CTX_free(EVP_PKEY_CTX *); int EVP_PKEY_sign_init(EVP_PKEY_CTX *); int EVP_PKEY_sign(EVP_PKEY_CTX *, unsigned char *, size_t *, @@ -117,32 +102,18 @@ int EVP_PKEY_cmp(const EVP_PKEY *, const EVP_PKEY *); -int EVP_PKEY_keygen_init(EVP_PKEY_CTX *); -int EVP_PKEY_keygen(EVP_PKEY_CTX *, EVP_PKEY **); int EVP_PKEY_derive_init(EVP_PKEY_CTX *); int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *, EVP_PKEY *); +int EVP_PKEY_derive_set_peer_ex(EVP_PKEY_CTX *, EVP_PKEY *, int); int EVP_PKEY_derive(EVP_PKEY_CTX *, unsigned char *, size_t *); -int EVP_PKEY_set_type(EVP_PKEY *, int); int EVP_PKEY_id(const EVP_PKEY *); EVP_MD_CTX *EVP_MD_CTX_new(void); void EVP_MD_CTX_free(EVP_MD_CTX *); -/* Added in 1.1.1 */ -int EVP_DigestSign(EVP_MD_CTX *, unsigned char *, size_t *, - const unsigned char *, size_t); -int EVP_DigestVerify(EVP_MD_CTX *, const unsigned char *, size_t, - const unsigned char *, size_t); -/* Added in 1.1.0 */ -size_t EVP_PKEY_get1_tls_encodedpoint(EVP_PKEY *, unsigned char **); -int EVP_PKEY_set1_tls_encodedpoint(EVP_PKEY *, const unsigned char *, - size_t); - -/* EVP_PKEY * became const in 1.1.0 */ -int EVP_PKEY_bits(EVP_PKEY *); +int EVP_PKEY_bits(const EVP_PKEY *); -void OpenSSL_add_all_algorithms(void); int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); EC_KEY *EVP_PKEY_get1_EC_KEY(EVP_PKEY *); @@ -150,15 +121,8 @@ int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *, int, int, void *); -int PKCS5_PBKDF2_HMAC(const char *, int, const unsigned char *, int, int, - const EVP_MD *, int, unsigned char *); - int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX *, const EVP_MD *); -int EVP_PBE_scrypt(const char *, size_t, const unsigned char *, size_t, - uint64_t, uint64_t, uint64_t, uint64_t, unsigned char *, - size_t); - EVP_PKEY *EVP_PKEY_new_raw_private_key(int, ENGINE *, const unsigned char *, size_t); EVP_PKEY *EVP_PKEY_new_raw_public_key(int, ENGINE *, const unsigned char *, @@ -178,46 +142,17 @@ const long EVP_PKEY_DHX = -1; #endif -EVP_MD_CTX *Cryptography_EVP_MD_CTX_new(void) { - return EVP_MD_CTX_new(); -} -void Cryptography_EVP_MD_CTX_free(EVP_MD_CTX *md) { - EVP_MD_CTX_free(md); -} - #if CRYPTOGRAPHY_IS_LIBRESSL || defined(OPENSSL_NO_SCRYPT) static const long Cryptography_HAS_SCRYPT = 0; -int (*EVP_PBE_scrypt)(const char *, size_t, const unsigned char *, size_t, - uint64_t, uint64_t, uint64_t, uint64_t, unsigned char *, - size_t) = NULL; #else static const long Cryptography_HAS_SCRYPT = 1; #endif -#if !CRYPTOGRAPHY_IS_LIBRESSL -static const long Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint = 1; -#else -static const long Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint = 0; -size_t (*EVP_PKEY_get1_tls_encodedpoint)(EVP_PKEY *, unsigned char **) = NULL; -int (*EVP_PKEY_set1_tls_encodedpoint)(EVP_PKEY *, const unsigned char *, - size_t) = NULL; -#endif - -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 || \ - (CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 && !CRYPTOGRAPHY_IS_LIBRESSL) -static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 0; -int (*EVP_DigestSign)(EVP_MD_CTX *, unsigned char *, size_t *, - const unsigned char *tbs, size_t) = NULL; -int (*EVP_DigestVerify)(EVP_MD_CTX *, const unsigned char *, size_t, - const unsigned char *, size_t) = NULL; -#else -static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 1; -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 -static const long Cryptography_HAS_RAW_KEY = 0; +#if CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 0; int (*EVP_DigestFinalXOF)(EVP_MD_CTX *, unsigned char *, size_t) = NULL; +#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 +static const long Cryptography_HAS_RAW_KEY = 0; EVP_PKEY *(*EVP_PKEY_new_raw_private_key)(int, ENGINE *, const unsigned char *, size_t) = NULL; EVP_PKEY *(*EVP_PKEY_new_raw_public_key)(int, ENGINE *, const unsigned char *, @@ -228,18 +163,17 @@ size_t *) = NULL; #else static const long Cryptography_HAS_RAW_KEY = 1; +#endif +#else +static const long Cryptography_HAS_RAW_KEY = 1; static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 1; #endif -/* OpenSSL 1.1.0+ does this define for us, but if not present we'll do it */ -#if !defined(EVP_CTRL_AEAD_SET_IVLEN) -# define EVP_CTRL_AEAD_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN -#endif -#if !defined(EVP_CTRL_AEAD_GET_TAG) -# define EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG -#endif -#if !defined(EVP_CTRL_AEAD_SET_TAG) -# define EVP_CTRL_AEAD_SET_TAG EVP_CTRL_GCM_SET_TAG +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX = 1; +#else +static const long Cryptography_HAS_EVP_PKEY_SET_PEER_EX = 0; +int (*EVP_PKEY_derive_set_peer_ex)(EVP_PKEY_CTX *, EVP_PKEY *, int) = NULL; #endif /* This is tied to X25519 support so we reuse the Cryptography_HAS_X25519 @@ -294,10 +228,4 @@ #else static const long Cryptography_HAS_EVP_PKEY_DH = 1; #endif - -// This can be removed when we drop OpenSSL 1.1.0 support -// OPENSSL_LESS_THAN_111 -#if !defined(EVP_PKEY_RSA_PSS) -#define EVP_PKEY_RSA_PSS 912 -#endif """ diff --git a/src/_cffi_src/openssl/evp_aead.py b/src/_cffi_src/openssl/evp_aead.py new file mode 100644 index 0000000..a748bcd --- /dev/null +++ b/src/_cffi_src/openssl/evp_aead.py @@ -0,0 +1,88 @@ +# This file is dual licensed under the terms 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 = """ +#if CRYPTOGRAPHY_IS_BORINGSSL +#include +#endif +""" + +TYPES = """ +typedef ... EVP_AEAD; +typedef ... EVP_AEAD_CTX; +static const size_t EVP_AEAD_DEFAULT_TAG_LENGTH; + +static const long Cryptography_HAS_EVP_AEAD; +""" + +FUNCTIONS = """ +const EVP_AEAD *EVP_aead_chacha20_poly1305(void); +void EVP_AEAD_CTX_free(EVP_AEAD_CTX *); +int EVP_AEAD_CTX_seal(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t); +int EVP_AEAD_CTX_open(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t); +size_t EVP_AEAD_max_overhead(const EVP_AEAD *); +/* The function EVP_AEAD_CTX_NEW() has different signatures in BoringSSL and + LibreSSL, so we cannot declare it here. We define a wrapper for it instead. +*/ +EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *, + const uint8_t *, size_t, + size_t); +""" + +CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_IS_BORINGSSL || CRYPTOGRAPHY_IS_LIBRESSL +static const long Cryptography_HAS_EVP_AEAD = 1; +#else +static const long Cryptography_HAS_EVP_AEAD = 0; +#endif + +#if CRYPTOGRAPHY_IS_BORINGSSL +EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *aead, + const uint8_t *key, + size_t key_len, size_t tag_len) { + return EVP_AEAD_CTX_new(aead, key, key_len, tag_len); +} +#elif CRYPTOGRAPHY_IS_LIBRESSL +EVP_AEAD_CTX *Cryptography_EVP_AEAD_CTX_new(const EVP_AEAD *aead, + const uint8_t *key, + size_t key_len, size_t tag_len) { + EVP_AEAD_CTX *ctx = EVP_AEAD_CTX_new(); + if (ctx == NULL) { + return NULL; + } + + /* This mimics BoringSSL's behavior: any error here is pushed onto + the stack. + */ + int result = EVP_AEAD_CTX_init(ctx, aead, key, key_len, tag_len, NULL); + if (result != 1) { + return NULL; + } + + return ctx; +} +#else +typedef void EVP_AEAD; +typedef void EVP_AEAD_CTX; +static const size_t EVP_AEAD_DEFAULT_TAG_LENGTH = 0; +const EVP_AEAD *(*EVP_aead_chacha20_poly1305)(void) = NULL; +void (*EVP_AEAD_CTX_free)(EVP_AEAD_CTX *) = NULL; +int (*EVP_AEAD_CTX_seal)(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t) = NULL; +int (*EVP_AEAD_CTX_open)(const EVP_AEAD_CTX *, uint8_t *, size_t *, size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + const uint8_t *, size_t) = NULL; +size_t (*EVP_AEAD_max_overhead)(const EVP_AEAD *) = NULL; +EVP_AEAD_CTX *(*Cryptography_EVP_AEAD_CTX_new)(const EVP_AEAD *, + const uint8_t *, size_t, + size_t) = NULL; +#endif +""" diff --git a/src/_cffi_src/openssl/fips.py b/src/_cffi_src/openssl/fips.py index dd81d06..9e3ce95 100644 --- a/src/_cffi_src/openssl/fips.py +++ b/src/_cffi_src/openssl/fips.py @@ -2,6 +2,7 @@ # 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 @@ -17,7 +18,7 @@ """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_350 || CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER static const long Cryptography_HAS_FIPS = 0; int (*FIPS_mode_set)(int) = NULL; int (*FIPS_mode)(void) = NULL; diff --git a/src/_cffi_src/openssl/hmac.py b/src/_cffi_src/openssl/hmac.py deleted file mode 100644 index 8b19153..0000000 --- a/src/_cffi_src/openssl/hmac.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - - -INCLUDES = """ -#include -""" - -TYPES = """ -typedef ... HMAC_CTX; -""" - -FUNCTIONS = """ -int HMAC_Init_ex(HMAC_CTX *, const void *, int, const EVP_MD *, ENGINE *); -int HMAC_Update(HMAC_CTX *, const unsigned char *, size_t); -int HMAC_Final(HMAC_CTX *, unsigned char *, unsigned int *); -int HMAC_CTX_copy(HMAC_CTX *, HMAC_CTX *); - -HMAC_CTX *HMAC_CTX_new(void); -void HMAC_CTX_free(HMAC_CTX *ctx); -""" - -CUSTOMIZATIONS = """ -""" diff --git a/src/_cffi_src/openssl/nid.py b/src/_cffi_src/openssl/nid.py index 3099912..7f6cb62 100644 --- a/src/_cffi_src/openssl/nid.py +++ b/src/_cffi_src/openssl/nid.py @@ -2,6 +2,7 @@ # 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 @@ -15,8 +16,6 @@ static const int NID_undef; static const int NID_aes_256_cbc; static const int NID_pbe_WithSHA1And3_Key_TripleDES_CBC; -static const int NID_X25519; -static const int NID_X448; static const int NID_ED25519; static const int NID_ED448; static const int NID_poly1305; diff --git a/src/_cffi_src/openssl/objects.py b/src/_cffi_src/openssl/objects.py index 9911599..5f9bdb3 100644 --- a/src/_cffi_src/openssl/objects.py +++ b/src/_cffi_src/openssl/objects.py @@ -2,20 +2,13 @@ # 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 struct { - int type; - int alias; - const char *name; - const char *data; -} OBJ_NAME; - -static const long OBJ_NAME_TYPE_MD_METH; """ FUNCTIONS = """ @@ -24,7 +17,6 @@ int OBJ_obj2nid(const ASN1_OBJECT *); int OBJ_sn2nid(const char *); int OBJ_txt2nid(const char *); -ASN1_OBJECT *OBJ_txt2obj(const char *, int); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/opensslv.py b/src/_cffi_src/openssl/opensslv.py index 630ebd7..7957bd7 100644 --- a/src/_cffi_src/openssl/opensslv.py +++ b/src/_cffi_src/openssl/opensslv.py @@ -2,6 +2,7 @@ # 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 diff --git a/src/_cffi_src/openssl/osrandom_engine.py b/src/_cffi_src/openssl/osrandom_engine.py deleted file mode 100644 index dbc304b..0000000 --- a/src/_cffi_src/openssl/osrandom_engine.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file is dual licensed under the terms 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 - -HERE = os.path.dirname(os.path.abspath(__file__)) - -with open(os.path.join(HERE, "src/osrandom_engine.h")) as f: - INCLUDES = f.read() - -TYPES = """ -static const char *const Cryptography_osrandom_engine_name; -static const char *const Cryptography_osrandom_engine_id; -""" - -FUNCTIONS = """ -int Cryptography_add_osrandom_engine(void); -""" - -with open(os.path.join(HERE, "src/osrandom_engine.c")) as f: - CUSTOMIZATIONS = f.read() diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py index 2ebcdf6..950bd37 100644 --- a/src/_cffi_src/openssl/pem.py +++ b/src/_cffi_src/openssl/pem.py @@ -2,6 +2,7 @@ # 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 @@ -27,7 +28,6 @@ int i2d_PKCS8PrivateKey_bio(BIO *, EVP_PKEY *, const EVP_CIPHER *, char *, int, pem_password_cb *, void *); -int i2d_PKCS7_bio(BIO *, PKCS7 *); PKCS7 *d2i_PKCS7_bio(BIO *, PKCS7 **); EVP_PKEY *d2i_PKCS8PrivateKey_bio(BIO *, EVP_PKEY **, pem_password_cb *, @@ -42,7 +42,6 @@ int PEM_write_bio_X509_CRL(BIO *, X509_CRL *); PKCS7 *PEM_read_bio_PKCS7(BIO *, PKCS7 **, pem_password_cb *, void *); -int PEM_write_bio_PKCS7(BIO *, PKCS7 *); DH *PEM_read_bio_DHparams(BIO *, DH **, pem_password_cb *, void *); @@ -63,12 +62,7 @@ int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *, unsigned char *, int, pem_password_cb *, void *); -int PEM_write_bio_DHparams(BIO *, DH *); -int PEM_write_bio_DHxparams(BIO *, DH *); """ CUSTOMIZATIONS = """ -#if !defined(EVP_PKEY_DHX) || EVP_PKEY_DHX == -1 -int (*PEM_write_bio_DHxparams)(BIO *, DH *) = NULL; -#endif """ diff --git a/src/_cffi_src/openssl/pkcs12.py b/src/_cffi_src/openssl/pkcs12.py index 135afc9..234f97b 100644 --- a/src/_cffi_src/openssl/pkcs12.py +++ b/src/_cffi_src/openssl/pkcs12.py @@ -2,6 +2,7 @@ # 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 diff --git a/src/_cffi_src/openssl/pkcs7.py b/src/_cffi_src/openssl/pkcs7.py index c802fac..ef75157 100644 --- a/src/_cffi_src/openssl/pkcs7.py +++ b/src/_cffi_src/openssl/pkcs7.py @@ -2,6 +2,7 @@ # 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 @@ -42,24 +43,11 @@ ...; } PKCS7; -static const int PKCS7_BINARY; -static const int PKCS7_DETACHED; -static const int PKCS7_NOATTR; -static const int PKCS7_NOCERTS; -static const int PKCS7_NOCHAIN; -static const int PKCS7_NOINTERN; -static const int PKCS7_NOSIGS; -static const int PKCS7_NOSMIMECAP; -static const int PKCS7_NOVERIFY; -static const int PKCS7_STREAM; static const int PKCS7_TEXT; -static const int PKCS7_PARTIAL; """ FUNCTIONS = """ void PKCS7_free(PKCS7 *); -PKCS7 *PKCS7_sign(X509 *, EVP_PKEY *, Cryptography_STACK_OF_X509 *, - BIO *, int); int SMIME_write_PKCS7(BIO *, PKCS7 *, BIO *, int); int PEM_write_bio_PKCS7_stream(BIO *, PKCS7 *, BIO *, int); PKCS7_SIGNER_INFO *PKCS7_sign_add_signer(PKCS7 *, X509 *, EVP_PKEY *, diff --git a/src/_cffi_src/openssl/provider.py b/src/_cffi_src/openssl/provider.py index d741ad7..769fded 100644 --- a/src/_cffi_src/openssl/provider.py +++ b/src/_cffi_src/openssl/provider.py @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations INCLUDES = """ #if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER diff --git a/src/_cffi_src/openssl/rand.py b/src/_cffi_src/openssl/rand.py index 9e95fe7..ee00fe6 100644 --- a/src/_cffi_src/openssl/rand.py +++ b/src/_cffi_src/openssl/rand.py @@ -2,6 +2,7 @@ # 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 @@ -12,7 +13,6 @@ """ FUNCTIONS = """ -int RAND_set_rand_method(const RAND_METHOD *); void RAND_add(const void *, int, double); int RAND_status(void); int RAND_bytes(unsigned char *, int); diff --git a/src/_cffi_src/openssl/rsa.py b/src/_cffi_src/openssl/rsa.py index c8e3e5a..eea6e39 100644 --- a/src/_cffi_src/openssl/rsa.py +++ b/src/_cffi_src/openssl/rsa.py @@ -2,6 +2,7 @@ # 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 @@ -11,11 +12,12 @@ typedef ... RSA; typedef ... BN_GENCB; static const int RSA_PKCS1_PADDING; -static const int RSA_NO_PADDING; static const int RSA_PKCS1_OAEP_PADDING; static const int RSA_PKCS1_PSS_PADDING; static const int RSA_F4; static const int RSA_PSS_SALTLEN_AUTO; + +static const int Cryptography_HAS_IMPLICIT_RSA_REJECTION; """ FUNCTIONS = """ @@ -27,7 +29,6 @@ int RSA_blinding_on(RSA *, BN_CTX *); int RSA_print(BIO *, const RSA *, int); -/* added in 1.1.0 when the RSA struct was opaqued */ int RSA_set0_key(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); int RSA_set0_factors(RSA *, BIGNUM *, BIGNUM *); int RSA_set0_crt_params(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); @@ -50,4 +51,10 @@ #if !defined(RSA_PSS_SALTLEN_AUTO) #define RSA_PSS_SALTLEN_AUTO -2 #endif + +#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 """ diff --git a/src/_cffi_src/openssl/src/osrandom_engine.c b/src/_cffi_src/openssl/src/osrandom_engine.c deleted file mode 100644 index a84857b..0000000 --- a/src/_cffi_src/openssl/src/osrandom_engine.c +++ /dev/null @@ -1,660 +0,0 @@ -/* osurandom engine - * - * Windows CryptGenRandom() - * macOS >= 10.12 getentropy() - * OpenBSD 5.6+ getentropy() - * other BSD getentropy() if SYS_getentropy is defined - * Linux 3.17+ getrandom() with fallback to /dev/urandom - * other /dev/urandom with cached fd - * - * The /dev/urandom, getrandom and getentropy code is derived from Python's - * Python/random.c, written by Antoine Pitrou and Victor Stinner. - * - * Copyright 2001-2016 Python Software Foundation; All Rights Reserved. - */ - -#ifdef __linux__ -#include -#endif - -#if CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE -/* OpenSSL has ENGINE support and is older than 1.1.1d (the first version that - * properly implements fork safety in its RNG) so build the engine. */ -static const char *Cryptography_osrandom_engine_id = "osrandom"; - -/**************************************************************************** - * Windows - */ -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM -static const char *Cryptography_osrandom_engine_name = "osrandom_engine CryptGenRandom()"; -static HCRYPTPROV hCryptProv = 0; - -static int osrandom_init(ENGINE *e) { - if (hCryptProv != 0) { - return 1; - } - if (CryptAcquireContext(&hCryptProv, NULL, NULL, - PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - return 1; - } else { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_INIT, - CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT, - __FILE__, __LINE__ - ); - return 0; - } -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - if (hCryptProv == 0) { - return 0; - } - - if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM, - __FILE__, __LINE__ - ); - return 0; - } - return 1; -} - -static int osrandom_finish(ENGINE *e) { - if (CryptReleaseContext(hCryptProv, 0)) { - hCryptProv = 0; - return 1; - } else { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_FINISH, - CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT, - __FILE__, __LINE__ - ); - return 0; - } -} - -static int osrandom_rand_status(void) { - return hCryptProv != 0; -} - -static const char *osurandom_get_implementation(void) { - return "CryptGenRandom"; -} - -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM */ - -/**************************************************************************** - * /dev/urandom helpers for all non-BSD Unix platforms - */ -#ifdef CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM - -static struct { - int fd; - dev_t st_dev; - ino_t st_ino; -} urandom_cache = { -1 }; - -static int open_cloexec(const char *path) { - int open_flags = O_RDONLY; -#ifdef O_CLOEXEC - open_flags |= O_CLOEXEC; -#endif - - int fd = open(path, open_flags); - if (fd == -1) { - return -1; - } - -#ifndef O_CLOEXEC - int flags = fcntl(fd, F_GETFD); - if (flags == -1) { - return -1; - } - if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { - return -1; - } -#endif - return fd; -} - -#ifdef __linux__ -/* On Linux, we open("/dev/random") and use poll() to wait until it's readable - * before we read from /dev/urandom, this ensures that we don't read from - * /dev/urandom before the kernel CSPRNG is initialized. This isn't necessary on - * other platforms because they don't have the same _bug_ as Linux does with - * /dev/urandom and early boot. */ -static int wait_on_devrandom(void) { - struct pollfd pfd = {}; - int ret = 0; - int random_fd = open_cloexec("/dev/random"); - if (random_fd < 0) { - return -1; - } - pfd.fd = random_fd; - pfd.events = POLLIN; - pfd.revents = 0; - do { - ret = poll(&pfd, 1, -1); - } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); - close(random_fd); - return ret; -} -#endif - -/* return -1 on error */ -static int dev_urandom_fd(void) { - int fd = -1; - struct stat st; - - /* Check that fd still points to the correct device */ - if (urandom_cache.fd >= 0) { - if (fstat(urandom_cache.fd, &st) - || st.st_dev != urandom_cache.st_dev - || st.st_ino != urandom_cache.st_ino) { - /* Somebody replaced our FD. Invalidate our cache but don't - * close the fd. */ - urandom_cache.fd = -1; - } - } - if (urandom_cache.fd < 0) { -#ifdef __linux__ - if (wait_on_devrandom() < 0) { - goto error; - } -#endif - - fd = open_cloexec("/dev/urandom"); - if (fd < 0) { - goto error; - } - if (fstat(fd, &st)) { - goto error; - } - /* Another thread initialized the fd */ - if (urandom_cache.fd >= 0) { - close(fd); - return urandom_cache.fd; - } - urandom_cache.st_dev = st.st_dev; - urandom_cache.st_ino = st.st_ino; - urandom_cache.fd = fd; - } - return urandom_cache.fd; - - error: - if (fd != -1) { - close(fd); - } - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD, - CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED, - __FILE__, __LINE__ - ); - return -1; -} - -static int dev_urandom_read(unsigned char *buffer, int size) { - int fd; - int n; - - fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - - while (size > 0) { - do { - n = (int)read(fd, buffer, (size_t)size); - } while (n < 0 && errno == EINTR); - - if (n <= 0) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ, - CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED, - __FILE__, __LINE__ - ); - return 0; - } - buffer += n; - size -= n; - } - return 1; -} - -static void dev_urandom_close(void) { - if (urandom_cache.fd >= 0) { - int fd; - struct stat st; - - if (fstat(urandom_cache.fd, &st) - && st.st_dev == urandom_cache.st_dev - && st.st_ino == urandom_cache.st_ino) { - fd = urandom_cache.fd; - urandom_cache.fd = -1; - close(fd); - } - } -} -#endif /* CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM */ - -/**************************************************************************** - * BSD getentropy - */ -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY -static const char *Cryptography_osrandom_engine_name = "osrandom_engine getentropy()"; - -static int getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_NOT_INIT; - -static int osrandom_init(ENGINE *e) { -#if !defined(__APPLE__) - getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS; -#else - if (__builtin_available(macOS 10.12, *)) { - getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS; - } else { - getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK; - int fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - } -#endif - return 1; -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - int len; - int res; - - switch(getentropy_works) { -#if defined(__APPLE__) - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK: - return dev_urandom_read(buffer, size); -#endif - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS: - while (size > 0) { - /* OpenBSD and macOS restrict maximum buffer size to 256. */ - len = size > 256 ? 256 : size; -/* on mac, availability is already checked using `__builtin_available` above */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - res = getentropy(buffer, (size_t)len); -#pragma clang diagnostic pop - if (res < 0) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED, - __FILE__, __LINE__ - ); - return 0; - } - buffer += len; - size -= len; - } - return 1; - } - __builtin_unreachable(); -} - -static int osrandom_finish(ENGINE *e) { - return 1; -} - -static int osrandom_rand_status(void) { - return 1; -} - -static const char *osurandom_get_implementation(void) { - switch(getentropy_works) { - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK: - return "/dev/urandom"; - case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS: - return "getentropy"; - } - __builtin_unreachable(); -} -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY */ - -/**************************************************************************** - * Linux getrandom engine with fallback to dev_urandom - */ - -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM -static const char *Cryptography_osrandom_engine_name = "osrandom_engine getrandom()"; - -static int getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT; - -static int osrandom_init(ENGINE *e) { - /* We try to detect working getrandom until we succeed. */ - if (getrandom_works != CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS) { - long n; - char dest[1]; - /* if the kernel CSPRNG is not initialized this will block */ - n = syscall(SYS_getrandom, dest, sizeof(dest), 0); - if (n == sizeof(dest)) { - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS; - } else { - int e = errno; - switch(e) { - case ENOSYS: - /* Fallback: Kernel does not support the syscall. */ - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK; - break; - case EPERM: - /* Fallback: seccomp prevents syscall */ - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK; - break; - default: - /* EINTR cannot occur for buflen < 256. */ - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_INIT, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED, - "errno", e - ); - getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED; - break; - } - } - } - - /* fallback to dev urandom */ - if (getrandom_works == CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK) { - int fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - } - return 1; -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - long n; - - switch(getrandom_works) { - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED, - __FILE__, __LINE__ - ); - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT, - __FILE__, __LINE__ - ); - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: - return dev_urandom_read(buffer, size); - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: - while (size > 0) { - do { - n = syscall(SYS_getrandom, buffer, size, 0); - } while (n < 0 && errno == EINTR); - - if (n <= 0) { - ERR_Cryptography_OSRandom_error( - CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, - CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED, - __FILE__, __LINE__ - ); - return 0; - } - buffer += n; - size -= (int)n; - } - return 1; - } - __builtin_unreachable(); -} - -static int osrandom_finish(ENGINE *e) { - dev_urandom_close(); - return 1; -} - -static int osrandom_rand_status(void) { - switch(getrandom_works) { - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: - return 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: - return urandom_cache.fd >= 0; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: - return 1; - } - __builtin_unreachable(); -} - -static const char *osurandom_get_implementation(void) { - switch(getrandom_works) { - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: - return ""; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: - return ""; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: - return "/dev/urandom"; - case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: - return "getrandom"; - } - __builtin_unreachable(); -} -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM */ - -/**************************************************************************** - * dev_urandom engine for all remaining platforms - */ - -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM -static const char *Cryptography_osrandom_engine_name = "osrandom_engine /dev/urandom"; - -static int osrandom_init(ENGINE *e) { - int fd = dev_urandom_fd(); - if (fd < 0) { - return 0; - } - return 1; -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - return dev_urandom_read(buffer, size); -} - -static int osrandom_finish(ENGINE *e) { - dev_urandom_close(); - return 1; -} - -static int osrandom_rand_status(void) { - return urandom_cache.fd >= 0; -} - -static const char *osurandom_get_implementation(void) { - return "/dev/urandom"; -} -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM */ - -/**************************************************************************** - * ENGINE boiler plate - */ - -/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a - -1 in the event that there is an error when calling RAND_pseudo_bytes. */ -static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) { - int res = osrandom_rand_bytes(buffer, size); - if (res == 0) { - return -1; - } else { - return res; - } -} - -static RAND_METHOD osrandom_rand = { - NULL, - osrandom_rand_bytes, - NULL, - NULL, - osrandom_pseudo_rand_bytes, - osrandom_rand_status, -}; - -static const ENGINE_CMD_DEFN osrandom_cmd_defns[] = { - {CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION, - "get_implementation", - "Get CPRNG implementation.", - ENGINE_CMD_FLAG_NO_INPUT}, - {0, NULL, NULL, 0} -}; - -static int osrandom_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void)) { - const char *name; - size_t len; - - switch (cmd) { - case CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION: - /* i: buffer size, p: char* buffer */ - name = osurandom_get_implementation(); - len = strlen(name); - if ((p == NULL) && (i == 0)) { - /* return required buffer len */ - return (int)len; - } - if ((p == NULL) || i < 0 || ((size_t)i <= len)) { - /* no buffer or buffer too small */ - ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_INVALID_ARGUMENT); - return 0; - } - strcpy((char *)p, name); - return (int)len; - default: - ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_CTRL_COMMAND_NOT_IMPLEMENTED); - return 0; - } -} - -/* error reporting */ -#define ERR_FUNC(func) ERR_PACK(0, func, 0) -#define ERR_REASON(reason) ERR_PACK(0, 0, reason) - -static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_lib_name[] = { - {0, "osrandom_engine"}, - {0, NULL} -}; - -static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_funcs[] = { - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_INIT), - "osrandom_init"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES), - "osrandom_rand_bytes"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_FINISH), - "osrandom_finish"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD), - "dev_urandom_fd"}, - {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ), - "dev_urandom_read"}, - {0, NULL} -}; - -static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_reasons[] = { - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT), - "CryptAcquireContext() failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM), - "CryptGenRandom() failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT), - "CryptReleaseContext() failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED), - "getentropy() failed"}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED), - "open('/dev/urandom') failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED), - "Reading from /dev/urandom fd failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED), - "getrandom() initialization failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED), - "getrandom() initialization failed with unexpected errno."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED), - "getrandom() syscall failed."}, - {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT), - "getrandom() engine was not properly initialized."}, - {0, NULL} -}; - -static int Cryptography_OSRandom_lib_error_code = 0; - -static void ERR_load_Cryptography_OSRandom_strings(void) -{ - if (Cryptography_OSRandom_lib_error_code == 0) { - Cryptography_OSRandom_lib_error_code = ERR_get_next_error_library(); - ERR_load_strings(Cryptography_OSRandom_lib_error_code, - CRYPTOGRAPHY_OSRANDOM_lib_name); - ERR_load_strings(Cryptography_OSRandom_lib_error_code, - CRYPTOGRAPHY_OSRANDOM_str_funcs); - ERR_load_strings(Cryptography_OSRandom_lib_error_code, - CRYPTOGRAPHY_OSRANDOM_str_reasons); - } -} - -static void ERR_Cryptography_OSRandom_error(int function, int reason, - char *file, int line) -{ - ERR_PUT_error(Cryptography_OSRandom_lib_error_code, function, reason, - file, line); -} - -/* Returns 1 if successfully added, 2 if engine has previously been added, - and 0 for error. */ -int Cryptography_add_osrandom_engine(void) { - ENGINE *e; - - ERR_load_Cryptography_OSRandom_strings(); - - e = ENGINE_by_id(Cryptography_osrandom_engine_id); - if (e != NULL) { - ENGINE_free(e); - return 2; - } else { - ERR_clear_error(); - } - - e = ENGINE_new(); - if (e == NULL) { - return 0; - } - if (!ENGINE_set_id(e, Cryptography_osrandom_engine_id) || - !ENGINE_set_name(e, Cryptography_osrandom_engine_name) || - !ENGINE_set_RAND(e, &osrandom_rand) || - !ENGINE_set_init_function(e, osrandom_init) || - !ENGINE_set_finish_function(e, osrandom_finish) || - !ENGINE_set_cmd_defns(e, osrandom_cmd_defns) || - !ENGINE_set_ctrl_function(e, osrandom_ctrl)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_add(e)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_free(e)) { - return 0; - } - - return 1; -} - -#else -/* If OpenSSL has no ENGINE support then we don't want - * to compile the osrandom engine, but we do need some - * placeholders */ -static const char *Cryptography_osrandom_engine_id = "no-engine-support"; -static const char *Cryptography_osrandom_engine_name = "osrandom_engine disabled"; - -int Cryptography_add_osrandom_engine(void) { - return 0; -} - -#endif diff --git a/src/_cffi_src/openssl/src/osrandom_engine.h b/src/_cffi_src/openssl/src/osrandom_engine.h deleted file mode 100644 index 93d918b..0000000 --- a/src/_cffi_src/openssl/src/osrandom_engine.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef OPENSSL_NO_ENGINE -/* OpenSSL has ENGINE support so include all of this. */ -#ifdef _WIN32 - #include -#else - #include - #include - /* for defined(BSD) */ - #ifndef __MVS__ - #include - #endif - - #ifdef BSD - /* for SYS_getentropy */ - #include - #endif - - #ifdef __APPLE__ - #include - /* To support weak linking we need to declare this as a weak import even if - * it's not present in sys/random (e.g. macOS < 10.12). */ - extern int getentropy(void *buffer, size_t size) __attribute((weak_import)); - #endif - - #ifdef __linux__ - /* for SYS_getrandom */ - #include - #ifndef GRND_NONBLOCK - #define GRND_NONBLOCK 0x0001 - #endif /* GRND_NONBLOCK */ - - #ifndef SYS_getrandom - /* We only bother to define the constants for platforms where we ship - * wheels, since that's the predominant way you get a situation where - * you don't have SYS_getrandom at compile time but do have the syscall - * at runtime */ - #if defined(__x86_64__) - #define SYS_getrandom 318 - #elif defined(__i386__) - #define SYS_getrandom 355 - #elif defined(__aarch64__) - #define SYS_getrandom 278 - #endif - #endif - #endif /* __linux__ */ -#endif /* _WIN32 */ - -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM 1 -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY 2 -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM 3 -#define CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM 4 - -#ifndef CRYPTOGRAPHY_OSRANDOM_ENGINE - #if defined(_WIN32) - /* Windows */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM - #elif defined(BSD) && defined(SYS_getentropy) - /* OpenBSD 5.6+ & macOS with SYS_getentropy defined, although < 10.12 will fallback - * to urandom */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY - #elif defined(__linux__) && defined(SYS_getrandom) - /* Linux 3.17+ */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM - #else - /* Keep this as last entry, fall back to /dev/urandom */ - #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM - #endif -#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE */ - -/* Fallbacks need /dev/urandom helper functions. */ -#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM || \ - CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM || \ - (CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY && \ - defined(__APPLE__)) - #define CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM 1 -#endif - -enum { - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED = -2, - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT, - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK, - CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS -}; - -enum { - CRYPTOGRAPHY_OSRANDOM_GETENTROPY_NOT_INIT, - CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK, - CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS -}; - -/* engine ctrl */ -#define CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION ENGINE_CMD_BASE - -/* error reporting */ -static void ERR_load_Cryptography_OSRandom_strings(void); -static void ERR_Cryptography_OSRandom_error(int function, int reason, - char *file, int line); - -#define CRYPTOGRAPHY_OSRANDOM_F_INIT 100 -#define CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES 101 -#define CRYPTOGRAPHY_OSRANDOM_F_FINISH 102 -#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD 300 -#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ 301 - -#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT 100 -#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM 101 -#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT 102 - -#define CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED 200 - -#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED 300 -#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED 301 - -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED 400 -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED 402 -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED 403 -#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT 404 -#endif diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py index 61f83ef..dfab7f6 100644 --- a/src/_cffi_src/openssl/ssl.py +++ b/src/_cffi_src/openssl/ssl.py @@ -2,50 +2,29 @@ # 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 - -typedef STACK_OF(SSL_CIPHER) Cryptography_STACK_OF_SSL_CIPHER; """ TYPES = """ static const long Cryptography_HAS_SSL_ST; static const long Cryptography_HAS_TLS_ST; -static const long Cryptography_HAS_SSL3_METHOD; -static const long Cryptography_HAS_TLSv1_1; -static const long Cryptography_HAS_TLSv1_2; -static const long Cryptography_HAS_TLSv1_3; static const long Cryptography_HAS_TLSv1_3_FUNCTIONS; -static const long Cryptography_HAS_SECURE_RENEGOTIATION; -static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS; -static const long Cryptography_HAS_DTLS; 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_GET_PROTO_VERSION; -static const long Cryptography_HAS_TLSEXT_HOSTNAME; static const long Cryptography_HAS_SSL_COOKIE; -/* Internally invented symbol to tell us if SSL_MODE_RELEASE_BUFFERS is - * supported - */ -static const long Cryptography_HAS_RELEASE_BUFFERS; - -/* Internally invented symbol to tell us if SSL_OP_NO_COMPRESSION is - * supported - */ -static const long Cryptography_HAS_OP_NO_COMPRESSION; static const long Cryptography_HAS_OP_NO_RENEGOTIATION; -static const long Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING; -static const long Cryptography_HAS_SSL_SET_SSL_CTX; -static const long Cryptography_HAS_SSL_OP_NO_TICKET; 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; @@ -57,7 +36,6 @@ 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_WANT_CONNECT; static const long SSL_ERROR_SYSCALL; static const long SSL_ERROR_SSL; static const long SSL_SENT_SHUTDOWN; @@ -68,8 +46,6 @@ 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_DTLSv1; -static const long SSL_OP_NO_DTLSv1_2; static const long SSL_OP_NO_RENEGOTIATION; static const long SSL_OP_NO_COMPRESSION; static const long SSL_OP_SINGLE_DH_USE; @@ -95,8 +71,6 @@ 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_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; -static const long SSL_OP_LEGACY_SERVER_CONNECT; static const long SSL_OP_IGNORE_UNEXPECTED_EOF; static const long SSL_VERIFY_PEER; static const long SSL_VERIFY_FAIL_IF_NO_PEER_CERT; @@ -133,9 +107,7 @@ 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 SSL3_RANDOM_SIZE; static const long TLS_ST_BEFORE; static const long TLS_ST_OK; @@ -156,7 +128,6 @@ static const long TLSEXT_STATUSTYPE_ocsp; typedef ... SSL_CIPHER; -typedef ... Cryptography_STACK_OF_SSL_CIPHER; typedef struct { const char *name; @@ -188,7 +159,8 @@ void SSL_set_verify(SSL *, int, int (*)(int, X509_STORE_CTX *)); int SSL_get_verify_mode(const SSL *); -/* Added in 1.0.2 */ +long SSL_get_extms_support(SSL *); + X509_VERIFY_PARAM *SSL_get0_param(SSL *); X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *); @@ -217,16 +189,12 @@ 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 *); -void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *, void *); 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_cert_verify_callback(SSL_CTX *, - int (*)(X509_STORE_CTX *, void *), - void *); void SSL_CTX_set_cookie_generate_cb(SSL_CTX *, int (*)( @@ -241,9 +209,6 @@ unsigned int )); -long SSL_CTX_get_read_ahead(SSL_CTX *); -long SSL_CTX_set_read_ahead(SSL_CTX *, long); - int SSL_CTX_use_psk_identity_hint(SSL_CTX *, const char *); void SSL_CTX_set_psk_server_callback(SSL_CTX *, unsigned int (*)( @@ -287,14 +252,25 @@ int SSL_CTX_set_session_id_context(SSL_CTX *, const unsigned char *, unsigned int); -void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *); 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_get_info_callback(SSL_CTX *))(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 *)); @@ -308,104 +284,45 @@ /* Information about actually used cipher */ const char *SSL_CIPHER_get_name(const SSL_CIPHER *); int SSL_CIPHER_get_bits(const SSL_CIPHER *, int *); -/* the modern signature of this is uint32_t, but older openssl declared it - as unsigned long. To make our compiler flags happy we'll declare it as a - 64-bit wide value, which should always be safe */ -uint64_t SSL_CIPHER_get_id(const SSL_CIPHER *); -int SSL_CIPHER_is_aead(const SSL_CIPHER *); -int SSL_CIPHER_get_cipher_nid(const SSL_CIPHER *); -int SSL_CIPHER_get_digest_nid(const SSL_CIPHER *); -int SSL_CIPHER_get_kx_nid(const SSL_CIPHER *); -int SSL_CIPHER_get_auth_nid(const SSL_CIPHER *); 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); -/* Function signature changed to const char * in 1.1.0 */ const char *SSL_CIPHER_get_version(const SSL_CIPHER *); -/* These became macros in 1.1.0 */ -int SSL_library_init(void); -void SSL_load_error_strings(void); SSL_SESSION *SSL_get_session(const SSL *); -const unsigned char *SSL_SESSION_get_id(const SSL_SESSION *, unsigned int *); -long SSL_SESSION_get_time(const SSL_SESSION *); -long SSL_SESSION_get_timeout(const SSL_SESSION *); -int SSL_SESSION_has_ticket(const SSL_SESSION *); -long SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *); - -unsigned long SSL_set_mode(SSL *, unsigned long); -unsigned long SSL_clear_mode(SSL *, unsigned long); -unsigned long SSL_get_mode(SSL *); -unsigned long SSL_set_options(SSL *, unsigned long); -unsigned long SSL_get_options(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_get_secure_renegotiation_support(SSL *); long SSL_CTX_set_min_proto_version(SSL_CTX *, int); long SSL_CTX_set_max_proto_version(SSL_CTX *, int); -long SSL_set_min_proto_version(SSL *, int); -long SSL_set_max_proto_version(SSL *, int); - -long SSL_CTX_get_min_proto_version(SSL_CTX *); -long SSL_CTX_get_max_proto_version(SSL_CTX *); -long SSL_get_min_proto_version(SSL *); -long SSL_get_max_proto_version(SSL *); - -/* Defined as unsigned long because SSL_OP_ALL is greater than signed 32-bit - and Windows defines long as 32-bit. */ -unsigned long SSL_CTX_set_options(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_clear_options(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_get_options(SSL_CTX *); -unsigned long SSL_CTX_set_mode(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_clear_mode(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_get_mode(SSL_CTX *); -unsigned long SSL_CTX_set_session_cache_mode(SSL_CTX *, unsigned long); -unsigned long SSL_CTX_get_session_cache_mode(SSL_CTX *); -unsigned long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *); -unsigned long SSL_CTX_set_tmp_ecdh(SSL_CTX *, EC_KEY *); -unsigned long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *); - -/*- These aren't macros these functions are all const X on openssl > 1.0.x -*/ - -/* methods */ - -const SSL_METHOD *TLSv1_1_method(void); -const SSL_METHOD *TLSv1_1_server_method(void); -const SSL_METHOD *TLSv1_1_client_method(void); - -const SSL_METHOD *TLSv1_2_method(void); -const SSL_METHOD *TLSv1_2_server_method(void); -const SSL_METHOD *TLSv1_2_client_method(void); - -const SSL_METHOD *SSLv3_method(void); -const SSL_METHOD *SSLv3_server_method(void); -const SSL_METHOD *SSLv3_client_method(void); - -const SSL_METHOD *TLSv1_method(void); -const SSL_METHOD *TLSv1_server_method(void); -const SSL_METHOD *TLSv1_client_method(void); - -const SSL_METHOD *DTLSv1_method(void); -const SSL_METHOD *DTLSv1_server_method(void); -const SSL_METHOD *DTLSv1_client_method(void); - -/* Added in 1.0.2 */ + +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 *SSLv23_method(void); -const SSL_METHOD *SSLv23_server_method(void); -const SSL_METHOD *SSLv23_client_method(void); - const SSL_METHOD *TLS_method(void); const SSL_METHOD *TLS_server_method(void); const SSL_METHOD *TLS_client_method(void); @@ -418,15 +335,10 @@ const char *SSL_get_version(const SSL *); int SSL_version(const SSL *); -void *SSL_CTX_get_ex_data(const SSL_CTX *, int); -void *SSL_get_ex_data(const SSL *, int); - void SSL_set_tlsext_host_name(SSL *, char *); void SSL_CTX_set_tlsext_servername_callback( SSL_CTX *, int (*)(SSL *, int *, void *)); -void SSL_CTX_set_tlsext_servername_arg( - SSL_CTX *, void *); long SSL_set_tlsext_status_ocsp_resp(SSL *, unsigned char *, int); long SSL_get_tlsext_status_ocsp_resp(SSL *, const unsigned char **); @@ -438,18 +350,6 @@ int SSL_set_tlsext_use_srtp(SSL *, const char *); SRTP_PROTECTION_PROFILE *SSL_get_selected_srtp_profile(SSL *); -long SSL_session_reused(SSL *); - -int SSL_select_next_proto(unsigned char **, unsigned char *, - const unsigned char *, unsigned int, - const unsigned char *, unsigned int); - -int sk_SSL_CIPHER_num(Cryptography_STACK_OF_SSL_CIPHER *); -const SSL_CIPHER *sk_SSL_CIPHER_value(Cryptography_STACK_OF_SSL_CIPHER *, int); - -/* ALPN APIs were introduced in OpenSSL 1.0.2. To continue to support earlier - * versions some special handling of these is necessary. - */ 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 *, @@ -462,17 +362,9 @@ void *); void SSL_get0_alpn_selected(const SSL *, const unsigned char **, unsigned *); -long SSL_get_server_tmp_key(SSL *, EVP_PKEY **); - -/* SSL_CTX_set_cert_cb is introduced in OpenSSL 1.0.2. To continue to support - * earlier versions some special handling of these is necessary. - */ void SSL_CTX_set_cert_cb(SSL_CTX *, int (*)(SSL *, void *), void *); void SSL_set_cert_cb(SSL *, int (*)(SSL *, void *), void *); -int SSL_SESSION_set1_id_context(SSL_SESSION *, const unsigned char *, - unsigned int); -/* Added in 1.1.0 for the great opaquing of structs */ 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); @@ -480,24 +372,9 @@ int SSL_export_keying_material(SSL *, unsigned char *, size_t, const char *, size_t, const unsigned char *, size_t, int); -long SSL_CTX_sess_number(SSL_CTX *); -long SSL_CTX_sess_connect(SSL_CTX *); -long SSL_CTX_sess_connect_good(SSL_CTX *); -long SSL_CTX_sess_connect_renegotiate(SSL_CTX *); -long SSL_CTX_sess_accept(SSL_CTX *); -long SSL_CTX_sess_accept_good(SSL_CTX *); -long SSL_CTX_sess_accept_renegotiate(SSL_CTX *); -long SSL_CTX_sess_hits(SSL_CTX *); -long SSL_CTX_sess_cb_hits(SSL_CTX *); -long SSL_CTX_sess_misses(SSL_CTX *); -long SSL_CTX_sess_timeouts(SSL_CTX *); -long SSL_CTX_sess_cache_full(SSL_CTX *); - /* DTLS support */ long Cryptography_DTLSv1_get_timeout(SSL *, time_t *, long *); long DTLSv1_handle_timeout(SSL *); -long DTLS_set_link_mtu(SSL *, long); -long DTLS_get_link_min_mtu(SSL *); long SSL_set_mtu(SSL *, long); int DTLSv1_listen(SSL *, BIO_ADDR *); size_t DTLS_get_data_mtu(SSL *); @@ -550,54 +427,19 @@ """ CUSTOMIZATIONS = """ -// This symbol is being preserved because removing it will break users with -// pyOpenSSL < 19.1 and pip < 20.x. We need to leave this in place until those -// users have upgraded. PersistentlyDeprecated2020 -static const long Cryptography_HAS_TLSEXT_HOSTNAME = 1; - #ifdef OPENSSL_NO_ENGINE int (*SSL_CTX_set_client_cert_engine)(SSL_CTX *, ENGINE *) = NULL; #endif -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_350 || CRYPTOGRAPHY_IS_BORINGSSL +#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 -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_350 || \ - (CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 && !CRYPTOGRAPHY_IS_LIBRESSL) -static const long Cryptography_HAS_KEYLOG = 0; -void (*SSL_CTX_set_keylog_callback)(SSL_CTX *, - void (*) (const SSL *, const char *) - ) = NULL; -void (*(*SSL_CTX_get_keylog_callback)(SSL_CTX *))( - const SSL *, - const char * - ) = NULL; -#else static const long Cryptography_HAS_KEYLOG = 1; -#endif - -static const long Cryptography_HAS_SECURE_RENEGOTIATION = 1; - -#ifdef OPENSSL_NO_SSL3_METHOD -static const long Cryptography_HAS_SSL3_METHOD = 0; -SSL_METHOD* (*SSLv3_method)(void) = NULL; -SSL_METHOD* (*SSLv3_client_method)(void) = NULL; -SSL_METHOD* (*SSLv3_server_method)(void) = NULL; -#else -static const long Cryptography_HAS_SSL3_METHOD = 1; -#endif -static const long Cryptography_HAS_RELEASE_BUFFERS = 1; -static const long Cryptography_HAS_OP_NO_COMPRESSION = 1; -static const long Cryptography_HAS_TLSv1_1 = 1; -static const long Cryptography_HAS_TLSv1_2 = 1; -static const long Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING = 1; -static const long Cryptography_HAS_SSL_OP_NO_TICKET = 1; -static const long Cryptography_HAS_SSL_SET_SSL_CTX = 1; static const long Cryptography_HAS_NEXTPROTONEG = 0; static const long Cryptography_HAS_ALPN = 1; @@ -619,12 +461,14 @@ 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 -static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS = 1; - /* 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 @@ -645,22 +489,12 @@ #endif #if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_332 -static const long SSL_OP_NO_DTLSv1 = 0; -static const long SSL_OP_NO_DTLSv1_2 = 0; -#endif -long (*DTLS_set_link_mtu)(SSL *, long) = NULL; -long (*DTLS_get_link_min_mtu)(SSL *) = NULL; -#endif - -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 || 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 -static const long Cryptography_HAS_DTLS = 1; /* 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) { @@ -747,18 +581,7 @@ SRTP_PROTECTION_PROFILE * (*SSL_get_selected_srtp_profile)(SSL *) = NULL; #endif -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 || \ - (CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 && !CRYPTOGRAPHY_IS_LIBRESSL) -static const long Cryptography_HAS_TLSv1_3 = 0; -static const long TLS1_3_VERSION = 0; -static const long SSL_OP_NO_TLSv1_3 = 0; -#else -static const long Cryptography_HAS_TLSv1_3 = 1; -#endif - -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 || \ - (CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 && !CRYPTOGRAPHY_IS_LIBRESSL) || \ - CRYPTOGRAPHY_IS_BORINGSSL +#if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 0; static const long SSL_VERIFY_POST_HANDSHAKE = 0; @@ -774,17 +597,6 @@ static const long Cryptography_HAS_TLSv1_3_FUNCTIONS = 1; #endif -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 && !CRYPTOGRAPHY_IS_LIBRESSL -static const long Cryptography_HAS_GET_PROTO_VERSION = 0; - -long (*SSL_CTX_get_min_proto_version)(SSL_CTX *) = NULL; -long (*SSL_CTX_get_max_proto_version)(SSL_CTX *) = NULL; -long (*SSL_get_min_proto_version)(SSL *) = NULL; -long (*SSL_get_max_proto_version)(SSL *) = NULL; -#else -static const long Cryptography_HAS_GET_PROTO_VERSION = 1; -#endif - #if CRYPTOGRAPHY_IS_BORINGSSL static const long Cryptography_HAS_SSL_COOKIE = 0; @@ -805,8 +617,7 @@ #else static const long Cryptography_HAS_SSL_COOKIE = 1; #endif -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 || \ - CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL +#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 (*)( @@ -823,7 +634,7 @@ size_t *, SSL_SESSION ** )) = NULL; -#if CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 || CRYPTOGRAPHY_IS_BORINGSSL +#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 *, diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index 4ba1492..9f79104 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -2,6 +2,7 @@ # 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 @@ -47,7 +48,6 @@ X509 *X509_new(void); void X509_free(X509 *); X509 *X509_dup(X509 *); -int X509_cmp(const X509 *, const X509 *); int X509_up_ref(X509 *); int X509_print_ex(BIO *, X509 *, unsigned long, unsigned long); @@ -81,15 +81,12 @@ X509_REQ *X509_REQ_new(void); void X509_REQ_free(X509_REQ *); int X509_REQ_set_pubkey(X509_REQ *, EVP_PKEY *); -int X509_REQ_set_subject_name(X509_REQ *, X509_NAME *); 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 X509_REQ_add1_attr_by_OBJ(X509_REQ *, const ASN1_OBJECT *, - int, const unsigned char *, int); int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int); ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *); @@ -99,31 +96,25 @@ int X509_REVOKED_set_serialNumber(X509_REVOKED *, ASN1_INTEGER *); -int X509_REVOKED_add_ext(X509_REVOKED *, X509_EXTENSION*, int); 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 *X509_CRL_dup(X509_CRL *); X509_CRL *d2i_X509_CRL_bio(BIO *, X509_CRL **); int X509_CRL_add0_revoked(X509_CRL *, X509_REVOKED *); -int X509_CRL_add_ext(X509_CRL *, X509_EXTENSION *, int); -int X509_CRL_cmp(const X509_CRL *, const X509_CRL *); 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 X509_CRL_verify(X509_CRL *, EVP_PKEY *); int i2d_X509_CRL_bio(BIO *, X509_CRL *); void X509_CRL_free(X509_CRL *); int NETSCAPE_SPKI_verify(NETSCAPE_SPKI *, EVP_PKEY *); int NETSCAPE_SPKI_sign(NETSCAPE_SPKI *, EVP_PKEY *, const EVP_MD *); char *NETSCAPE_SPKI_b64_encode(NETSCAPE_SPKI *); -NETSCAPE_SPKI *NETSCAPE_SPKI_b64_decode(const char *, int); EVP_PKEY *NETSCAPE_SPKI_get_pubkey(NETSCAPE_SPKI *); int NETSCAPE_SPKI_set_pubkey(NETSCAPE_SPKI *, EVP_PKEY *); NETSCAPE_SPKI *NETSCAPE_SPKI_new(void); @@ -156,24 +147,15 @@ int i2d_RSAPublicKey_bio(BIO *, RSA *); int i2d_DSAPrivateKey_bio(BIO *, DSA *); -/* These became const X509 in 1.1.0 */ -int X509_get_ext_count(X509 *); -X509_EXTENSION *X509_get_ext(X509 *, int); -X509_NAME *X509_get_subject_name(X509 *); -X509_NAME *X509_get_issuer_name(X509 *); +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 *); -/* This became const ASN1_OBJECT * in 1.1.0 */ -X509_EXTENSION *X509_EXTENSION_create_by_OBJ(X509_EXTENSION **, - ASN1_OBJECT *, int, - ASN1_OCTET_STRING *); +int X509_EXTENSION_get_critical(const X509_EXTENSION *); - -/* This became const X509_EXTENSION * in 1.1.0 */ -int X509_EXTENSION_get_critical(X509_EXTENSION *); - -/* This became const X509_REVOKED * in 1.1.0 */ -int X509_REVOKED_get_ext_count(X509_REVOKED *); -X509_EXTENSION *X509_REVOKED_get_ext(X509_REVOKED *, int); +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 *); @@ -197,31 +179,25 @@ 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 *); -int sk_X509_EXTENSION_insert(X509_EXTENSIONS *, X509_EXTENSION *, int); -X509_EXTENSION *sk_X509_EXTENSION_delete(X509_EXTENSIONS *, int); 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); -long X509_CRL_get_version(X509_CRL *); -const ASN1_TIME *X509_CRL_get0_lastUpdate(const X509_CRL *); -const ASN1_TIME *X509_CRL_get0_nextUpdate(const X509_CRL *); 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 *); -EC_KEY *d2i_EC_PUBKEY_bio(BIO *, EC_KEY **); -int i2d_EC_PUBKEY_bio(BIO *, EC_KEY *); -EC_KEY *d2i_ECPrivateKey_bio(BIO *, EC_KEY **); int i2d_ECPrivateKey_bio(BIO *, EC_KEY *); -/* these functions were added in 1.1.0 */ 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 = """ diff --git a/src/_cffi_src/openssl/x509_vfy.py b/src/_cffi_src/openssl/x509_vfy.py index 7997515..f1ea8ee 100644 --- a/src/_cffi_src/openssl/x509_vfy.py +++ b/src/_cffi_src/openssl/x509_vfy.py @@ -2,6 +2,7 @@ # 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 @@ -18,7 +19,6 @@ """ TYPES = """ -static const long Cryptography_HAS_110_VERIFICATION_PARAMS; static const long Cryptography_HAS_X509_STORE_CTX_GET_ISSUER; typedef ... Cryptography_STACK_OF_ASN1_OBJECT; @@ -31,11 +31,6 @@ typedef int (*X509_STORE_CTX_get_issuer_fn)(X509 **, X509_STORE_CTX *, X509 *); -/* 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 error codes */ 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; @@ -94,8 +89,12 @@ 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_USE_CHECK_TIME; 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; @@ -103,19 +102,10 @@ 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_ANY; static const long X509_V_FLAG_INHIBIT_MAP; static const long X509_V_FLAG_NOTIFY_POLICY; -static const long X509_V_FLAG_EXTENDED_CRL_SUPPORT; -static const long X509_V_FLAG_USE_DELTAS; static const long X509_V_FLAG_CHECK_SS_SIGNATURE; -static const long X509_V_FLAG_TRUSTED_FIRST; static const long X509_V_FLAG_PARTIAL_CHAIN; -static const long X509_V_FLAG_NO_ALT_CHAINS; -static const long X509_V_FLAG_NO_CHECK_TIME; - -static const long X509_LU_X509; -static const long X509_LU_CRL; static const long X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; static const long X509_CHECK_FLAG_NO_WILDCARDS; @@ -153,6 +143,7 @@ /* 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 */ @@ -161,76 +152,36 @@ void X509_STORE_CTX_free(X509_STORE_CTX *); int X509_STORE_CTX_init(X509_STORE_CTX *, X509_STORE *, X509 *, Cryptography_STACK_OF_X509 *); -void X509_STORE_CTX_set_cert(X509_STORE_CTX *, X509 *); -X509_VERIFY_PARAM *X509_STORE_CTX_get0_param(X509_STORE_CTX *); -void X509_STORE_CTX_set0_param(X509_STORE_CTX *, X509_VERIFY_PARAM *); -int X509_STORE_CTX_set_default(X509_STORE_CTX *, const char *); -void X509_STORE_CTX_set_verify_cb(X509_STORE_CTX *, - int (*)(int, X509_STORE_CTX *)); 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 *); -int X509_STORE_CTX_set_ex_data(X509_STORE_CTX *, int, void *); void *X509_STORE_CTX_get_ex_data(X509_STORE_CTX *, int); -int X509_STORE_CTX_get1_issuer(X509 **, X509_STORE_CTX *, X509 *); /* X509_VERIFY_PARAM */ X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void); int X509_VERIFY_PARAM_set_flags(X509_VERIFY_PARAM *, unsigned long); -int X509_VERIFY_PARAM_clear_flags(X509_VERIFY_PARAM *, unsigned long); -unsigned long X509_VERIFY_PARAM_get_flags(X509_VERIFY_PARAM *); -int X509_VERIFY_PARAM_set_purpose(X509_VERIFY_PARAM *, int); -int X509_VERIFY_PARAM_set_trust(X509_VERIFY_PARAM *, int); void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *, time_t); -int X509_VERIFY_PARAM_add0_policy(X509_VERIFY_PARAM *, ASN1_OBJECT *); -int X509_VERIFY_PARAM_set1_policies(X509_VERIFY_PARAM *, - Cryptography_STACK_OF_ASN1_OBJECT *); -void X509_VERIFY_PARAM_set_depth(X509_VERIFY_PARAM *, int); -int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *); void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *); -/* X509_STORE_CTX */ -void X509_STORE_CTX_set0_crls(X509_STORE_CTX *, - Cryptography_STACK_OF_X509_CRL *); - -/* 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_email(X509_VERIFY_PARAM *, const char *, - size_t); int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *, const unsigned char *, size_t); -int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *, const char *); int sk_X509_OBJECT_num(Cryptography_STACK_OF_X509_OBJECT *); -X509_OBJECT *sk_X509_OBJECT_value(Cryptography_STACK_OF_X509_OBJECT *, int); -X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *); Cryptography_STACK_OF_X509_OBJECT *X509_STORE_get0_objects(X509_STORE *); -X509 *X509_OBJECT_get0_X509(X509_OBJECT *); -/* added in 1.1.0 */ X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *); -X509_STORE_CTX_get_issuer_fn X509_STORE_get_get_issuer(X509_STORE *); void X509_STORE_set_get_issuer(X509_STORE *, X509_STORE_CTX_get_issuer_fn); """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_LIBRESSL && CRYPTOGRAPHY_LIBRESSL_LESS_THAN_322 -static const long Cryptography_HAS_110_VERIFICATION_PARAMS = 0; -#ifndef X509_CHECK_FLAG_NEVER_CHECK_SUBJECT -static const long X509_CHECK_FLAG_NEVER_CHECK_SUBJECT = 0; -#endif -#else -static const long Cryptography_HAS_110_VERIFICATION_PARAMS = 1; -#endif - #if CRYPTOGRAPHY_IS_LIBRESSL static const long Cryptography_HAS_X509_STORE_CTX_GET_ISSUER = 0; typedef void *X509_STORE_CTX_get_issuer_fn; -X509_STORE_CTX_get_issuer_fn (*X509_STORE_get_get_issuer)(X509_STORE *) = NULL; void (*X509_STORE_set_get_issuer)(X509_STORE *, X509_STORE_CTX_get_issuer_fn) = NULL; #else diff --git a/src/_cffi_src/openssl/x509name.py b/src/_cffi_src/openssl/x509name.py index 37b0d9e..5e0349e 100644 --- a/src/_cffi_src/openssl/x509name.py +++ b/src/_cffi_src/openssl/x509name.py @@ -2,6 +2,7 @@ # 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 @@ -32,33 +33,21 @@ 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 *); -/* These became const X509_NAME * in 1.1.0 */ -int X509_NAME_entry_count(X509_NAME *); -X509_NAME_ENTRY *X509_NAME_get_entry(X509_NAME *, int); -char *X509_NAME_oneline(X509_NAME *, char *, int); +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); -/* These became const X509_NAME_ENTRY * in 1.1.0 */ -ASN1_OBJECT *X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *); -ASN1_STRING *X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *); -int X509_NAME_add_entry(X509_NAME *, X509_NAME_ENTRY *, int, int); +ASN1_OBJECT *X509_NAME_ENTRY_get_object(const X509_NAME_ENTRY *); +ASN1_STRING *X509_NAME_ENTRY_get_data(const X509_NAME_ENTRY *); -/* this became const unsigned char * in 1.1.0 */ -int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, unsigned char *, +int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, const unsigned char *, int, int, int); -/* These became const ASN1_OBJECT * in 1.1.0 */ -X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_OBJ(X509_NAME_ENTRY **, - ASN1_OBJECT *, int, - const unsigned char *, 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 *); -Cryptography_STACK_OF_X509_NAME_ENTRY *sk_X509_NAME_ENTRY_new_null(void); -int sk_X509_NAME_ENTRY_push(Cryptography_STACK_OF_X509_NAME_ENTRY *, - X509_NAME_ENTRY *); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 46569bd..dae98da 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -2,6 +2,7 @@ # 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 @@ -48,10 +49,10 @@ int GENERAL_NAME_print(BIO *, GENERAL_NAME *); void GENERAL_NAMES_free(GENERAL_NAMES *); void *X509V3_EXT_d2i(X509_EXTENSION *); -/* The last two char * args became const char * in 1.1.0 */ -X509_EXTENSION *X509V3_EXT_nconf(CONF *, X509V3_CTX *, char *, char *); +X509_EXTENSION *X509V3_EXT_nconf(CONF *, X509V3_CTX *, const char *, + const char *); -void *X509V3_set_ctx_nodb(X509V3_CTX *); +void X509V3_set_ctx_nodb(X509V3_CTX *); int sk_GENERAL_NAME_num(struct stack_st_GENERAL_NAME *); GENERAL_NAME *sk_GENERAL_NAME_value(struct stack_st_GENERAL_NAME *, int); diff --git a/src/_cffi_src/utils.py b/src/_cffi_src/utils.py index 5afc084..b5fba37 100644 --- a/src/_cffi_src/utils.py +++ b/src/_cffi_src/utils.py @@ -2,15 +2,15 @@ # 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 distutils.ccompiler import new_compiler -from distutils.dist import Distribution +import typing 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 = {} @@ -19,11 +19,9 @@ def build_ffi_for_binding( - module_name, - module_prefix, - modules, - libraries, - extra_compile_args, + module_name: str, + module_prefix: str, + modules: typing.List[str], ): """ Modules listed in ``modules`` should have the following attributes: @@ -53,17 +51,13 @@ def build_ffi_for_binding( module_name, cdef_source="\n".join(types + functions), verify_source=verify_source, - libraries=libraries, - extra_compile_args=extra_compile_args, ) def build_ffi( - module_name, - cdef_source, - verify_source, - libraries, - extra_compile_args, + module_name: str, + cdef_source: str, + verify_source: str, ): ffi = FFI() # Always add the CRYPTOGRAPHY_PACKAGE_VERSION to the shared object @@ -71,24 +65,21 @@ def build_ffi( 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, - libraries=libraries, - extra_compile_args=extra_compile_args, ) return ffi - - -def compiler_type(): - """ - Gets the compiler type from distutils. On Windows with MSVC it will be - "msvc". On macOS and linux it is "unix". - """ - dist = Distribution() - dist.parse_config_files() - cmd = dist.get_command_obj("build") - cmd.ensure_finalized() - compiler = new_compiler(compiler=cmd.compiler) - return compiler.compiler_type diff --git a/src/cryptography.egg-info/PKG-INFO b/src/cryptography.egg-info/PKG-INFO index 2f4c544..99cb9cc 100644 --- a/src/cryptography.egg-info/PKG-INFO +++ b/src/cryptography.egg-info/PKG-INFO @@ -1,16 +1,14 @@ Metadata-Version: 2.1 Name: cryptography -Version: 38.0.4 +Version: 41.0.7 Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. -Home-page: https://github.com/pyca/cryptography -Author: The Python Cryptographic Authority and individual contributors -Author-email: cryptography-dev@python.org -License: BSD-3-Clause OR Apache-2.0 -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/ -Platform: UNKNOWN +Author-email: The Python Cryptographic Authority and individual contributors +License: Apache-2.0 OR BSD-3-Clause +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/ Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License @@ -24,26 +22,46 @@ 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.6 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography -Requires-Python: >=3.6 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: LICENSE.APACHE +License-File: LICENSE.BSD +Requires-Dist: cffi>=1.12 +Provides-Extra: ssh +Requires-Dist: bcrypt>=3.1.5; extra == "ssh" +Provides-Extra: nox +Requires-Dist: nox; extra == "nox" Provides-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" +Provides-Extra: test-randomorder +Requires-Dist: pytest-randomly; extra == "test-randomorder" Provides-Extra: docs +Requires-Dist: sphinx>=5.3.0; extra == "docs" +Requires-Dist: sphinx-rtd-theme>=1.1.1; extra == "docs" Provides-Extra: docstest +Requires-Dist: pyenchant>=1.6.11; extra == "docstest" +Requires-Dist: twine>=1.12.0; extra == "docstest" +Requires-Dist: sphinxcontrib-spelling>=4.0.1; extra == "docstest" Provides-Extra: sdist +Requires-Dist: build; extra == "sdist" Provides-Extra: pep8test -Provides-Extra: ssh -License-File: LICENSE -License-File: LICENSE.APACHE -License-File: LICENSE.BSD -License-File: LICENSE.PSF +Requires-Dist: black; extra == "pep8test" +Requires-Dist: ruff; extra == "pep8test" +Requires-Dist: mypy; extra == "pep8test" +Requires-Dist: check-sdist; extra == "pep8test" pyca/cryptography ================= @@ -61,8 +79,8 @@ pyca/cryptography ``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.6+ and PyPy3 7.2+. +primitives to Python developers. Our goal is for it to be your "cryptographic +standard library". It supports Python 3.7+ and PyPy3 7.3.10+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and @@ -77,9 +95,9 @@ key derivation functions. For example, to encrypt something with >>> f = Fernet(key) >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") >>> token - '...' + b'...' >>> f.decrypt(token) - 'A really secret message. Not for prying eyes.' + b'A really secret message. Not for prying eyes.' You can find more information in the `documentation`_. @@ -113,5 +131,3 @@ documentation. .. _`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/ - - diff --git a/src/cryptography.egg-info/SOURCES.txt b/src/cryptography.egg-info/SOURCES.txt index 8711f91..3997855 100644 --- a/src/cryptography.egg-info/SOURCES.txt +++ b/src/cryptography.egg-info/SOURCES.txt @@ -3,11 +3,10 @@ CONTRIBUTING.rst LICENSE LICENSE.APACHE LICENSE.BSD -LICENSE.PSF MANIFEST.in README.rst +noxfile.py pyproject.toml -setup.cfg setup.py docs/Makefile docs/api-stability.rst @@ -95,7 +94,6 @@ src/_cffi_src/openssl/bignum.py src/_cffi_src/openssl/bio.py src/_cffi_src/openssl/callbacks.py src/_cffi_src/openssl/cmac.py -src/_cffi_src/openssl/conf.py src/_cffi_src/openssl/crypto.py src/_cffi_src/openssl/cryptography.py src/_cffi_src/openssl/dh.py @@ -105,12 +103,11 @@ src/_cffi_src/openssl/ecdsa.py src/_cffi_src/openssl/engine.py src/_cffi_src/openssl/err.py src/_cffi_src/openssl/evp.py +src/_cffi_src/openssl/evp_aead.py src/_cffi_src/openssl/fips.py -src/_cffi_src/openssl/hmac.py src/_cffi_src/openssl/nid.py src/_cffi_src/openssl/objects.py src/_cffi_src/openssl/opensslv.py -src/_cffi_src/openssl/osrandom_engine.py src/_cffi_src/openssl/pem.py src/_cffi_src/openssl/pkcs12.py src/_cffi_src/openssl/pkcs7.py @@ -122,8 +119,6 @@ src/_cffi_src/openssl/x509.py src/_cffi_src/openssl/x509_vfy.py src/_cffi_src/openssl/x509name.py src/_cffi_src/openssl/x509v3.py -src/_cffi_src/openssl/src/osrandom_engine.c -src/_cffi_src/openssl/src/osrandom_engine.h src/cryptography/__about__.py src/cryptography/__init__.py src/cryptography/exceptions.py @@ -145,25 +140,28 @@ src/cryptography/hazmat/backends/openssl/backend.py src/cryptography/hazmat/backends/openssl/ciphers.py src/cryptography/hazmat/backends/openssl/cmac.py src/cryptography/hazmat/backends/openssl/decode_asn1.py -src/cryptography/hazmat/backends/openssl/dh.py -src/cryptography/hazmat/backends/openssl/dsa.py src/cryptography/hazmat/backends/openssl/ec.py -src/cryptography/hazmat/backends/openssl/ed25519.py -src/cryptography/hazmat/backends/openssl/ed448.py -src/cryptography/hazmat/backends/openssl/hashes.py -src/cryptography/hazmat/backends/openssl/hmac.py -src/cryptography/hazmat/backends/openssl/poly1305.py src/cryptography/hazmat/backends/openssl/rsa.py src/cryptography/hazmat/backends/openssl/utils.py -src/cryptography/hazmat/backends/openssl/x25519.py -src/cryptography/hazmat/backends/openssl/x448.py -src/cryptography/hazmat/backends/openssl/x509.py src/cryptography/hazmat/bindings/__init__.py -src/cryptography/hazmat/bindings/_openssl.pyi src/cryptography/hazmat/bindings/_rust/__init__.pyi +src/cryptography/hazmat/bindings/_rust/_openssl.pyi src/cryptography/hazmat/bindings/_rust/asn1.pyi +src/cryptography/hazmat/bindings/_rust/exceptions.pyi src/cryptography/hazmat/bindings/_rust/ocsp.pyi +src/cryptography/hazmat/bindings/_rust/pkcs7.pyi src/cryptography/hazmat/bindings/_rust/x509.pyi +src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi +src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi +src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi +src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi +src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi +src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi +src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi +src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi +src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi +src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi +src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi src/cryptography/hazmat/bindings/openssl/__init__.py src/cryptography/hazmat/bindings/openssl/_conditional.py src/cryptography/hazmat/bindings/openssl/binding.py @@ -220,11 +218,47 @@ src/cryptography/x509/ocsp.py src/cryptography/x509/oid.py src/rust/Cargo.lock src/rust/Cargo.toml +src/rust/build.rs +src/rust/cryptography-cffi/Cargo.toml +src/rust/cryptography-cffi/build.rs +src/rust/cryptography-cffi/src/lib.rs +src/rust/cryptography-openssl/Cargo.toml +src/rust/cryptography-openssl/build.rs +src/rust/cryptography-openssl/src/fips.rs +src/rust/cryptography-openssl/src/hmac.rs +src/rust/cryptography-openssl/src/lib.rs +src/rust/cryptography-x509/Cargo.toml +src/rust/cryptography-x509/src/certificate.rs +src/rust/cryptography-x509/src/common.rs +src/rust/cryptography-x509/src/crl.rs +src/rust/cryptography-x509/src/csr.rs +src/rust/cryptography-x509/src/extensions.rs +src/rust/cryptography-x509/src/lib.rs +src/rust/cryptography-x509/src/name.rs +src/rust/cryptography-x509/src/ocsp_req.rs +src/rust/cryptography-x509/src/ocsp_resp.rs +src/rust/cryptography-x509/src/oid.rs +src/rust/cryptography-x509/src/pkcs7.rs src/rust/src/asn1.rs -src/rust/src/intern.rs +src/rust/src/buf.rs +src/rust/src/error.rs +src/rust/src/exceptions.rs src/rust/src/lib.rs src/rust/src/oid.rs +src/rust/src/pkcs7.rs src/rust/src/pool.rs +src/rust/src/backend/dh.rs +src/rust/src/backend/dsa.rs +src/rust/src/backend/ed25519.rs +src/rust/src/backend/ed448.rs +src/rust/src/backend/hashes.rs +src/rust/src/backend/hmac.rs +src/rust/src/backend/kdf.rs +src/rust/src/backend/mod.rs +src/rust/src/backend/poly1305.rs +src/rust/src/backend/utils.rs +src/rust/src/backend/x25519.rs +src/rust/src/backend/x448.rs src/rust/src/x509/certificate.rs src/rust/src/x509/common.rs src/rust/src/x509/crl.rs @@ -234,7 +268,6 @@ src/rust/src/x509/mod.rs src/rust/src/x509/ocsp.rs src/rust/src/x509/ocsp_req.rs src/rust/src/x509/ocsp_resp.rs -src/rust/src/x509/oid.rs src/rust/src/x509/sct.rs src/rust/src/x509/sign.rs tests/__init__.py @@ -243,7 +276,6 @@ tests/deprecated_module.py tests/doubles.py tests/test_cryptography_utils.py tests/test_fernet.py -tests/test_interfaces.py tests/test_meta.py tests/test_rust_utils.py tests/test_utils.py @@ -251,6 +283,9 @@ tests/test_warnings.py tests/utils.py tests/bench/__init__.py tests/bench/test_aead.py +tests/bench/test_ec_load.py +tests/bench/test_hashes.py +tests/bench/test_hmac.py tests/bench/test_x509.py tests/hazmat/__init__.py tests/hazmat/test_oid.py @@ -304,6 +339,7 @@ tests/hazmat/primitives/test_scrypt.py tests/hazmat/primitives/test_seed.py tests/hazmat/primitives/test_serialization.py tests/hazmat/primitives/test_sm4.py +tests/hazmat/primitives/test_ssh.py tests/hazmat/primitives/test_x25519.py tests/hazmat/primitives/test_x448.py tests/hazmat/primitives/test_x963_vectors.py @@ -312,10 +348,6 @@ tests/hazmat/primitives/utils.py tests/hazmat/primitives/twofactor/__init__.py tests/hazmat/primitives/twofactor/test_hotp.py tests/hazmat/primitives/twofactor/test_totp.py -tests/hypothesis/__init__.py -tests/hypothesis/test_fernet.py -tests/hypothesis/test_padding.py -tests/hypothesis/test_x509.py tests/wycheproof/__init__.py tests/wycheproof/test_aes.py tests/wycheproof/test_chacha20poly1305.py diff --git a/src/cryptography.egg-info/requires.txt b/src/cryptography.egg-info/requires.txt index 2893a9e..dbccc5a 100644 --- a/src/cryptography.egg-info/requires.txt +++ b/src/cryptography.egg-info/requires.txt @@ -1,22 +1,25 @@ cffi>=1.12 [docs] -sphinx!=1.8.0,!=3.1.0,!=3.1.1,>=1.6.5 -sphinx_rtd_theme +sphinx>=5.3.0 +sphinx-rtd-theme>=1.1.1 [docstest] pyenchant>=1.6.11 twine>=1.12.0 sphinxcontrib-spelling>=4.0.1 +[nox] +nox + [pep8test] black -flake8 -flake8-import-order -pep8-naming +ruff +mypy +check-sdist [sdist] -setuptools_rust>=0.11.4 +build [ssh] bcrypt>=3.1.5 @@ -25,9 +28,8 @@ bcrypt>=3.1.5 pytest>=6.2.0 pytest-benchmark pytest-cov -pytest-subtests pytest-xdist pretend -iso8601 -pytz -hypothesis!=3.79.2,>=1.11.4 + +[test-randomorder] +pytest-randomly diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py index ca79574..014e0ad 100644 --- a/src/cryptography/__about__.py +++ b/src/cryptography/__about__.py @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations __all__ = [ "__version__", @@ -9,7 +10,8 @@ "__copyright__", ] -__version__ = "38.0.4" +__version__ = "41.0.7" + __author__ = "The Python Cryptographic Authority and individual contributors" -__copyright__ = "Copyright 2013-2022 {}".format(__author__) +__copyright__ = f"Copyright 2013-2023 {__author__}" diff --git a/src/cryptography/__init__.py b/src/cryptography/__init__.py index 599bf51..86b9a25 100644 --- a/src/cryptography/__init__.py +++ b/src/cryptography/__init__.py @@ -2,28 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import sys -import warnings - -from cryptography.__about__ import ( - __author__, - __copyright__, - __version__, -) -from cryptography.utils import CryptographyDeprecationWarning +from __future__ import annotations +from cryptography.__about__ import __author__, __copyright__, __version__ __all__ = [ "__version__", "__author__", "__copyright__", ] - -if sys.version_info[:2] == (3, 6): - warnings.warn( - "Python 3.6 is no longer supported by the Python core team. " - "Therefore, support for it is deprecated in cryptography and will be" - " removed in a future release.", - CryptographyDeprecationWarning, - stacklevel=2, - ) diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index a315703..47fdd18 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -2,37 +2,23 @@ # 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.bindings._rust import exceptions as rust_exceptions if typing.TYPE_CHECKING: - from cryptography.hazmat.bindings.openssl.binding import ( - _OpenSSLErrorWithText, - ) - - -class _Reasons(utils.Enum): - BACKEND_MISSING_INTERFACE = 0 - UNSUPPORTED_HASH = 1 - UNSUPPORTED_CIPHER = 2 - UNSUPPORTED_PADDING = 3 - UNSUPPORTED_MGF = 4 - UNSUPPORTED_PUBLIC_KEY_ALGORITHM = 5 - UNSUPPORTED_ELLIPTIC_CURVE = 6 - UNSUPPORTED_SERIALIZATION = 7 - UNSUPPORTED_X509 = 8 - UNSUPPORTED_EXCHANGE_ALGORITHM = 9 - UNSUPPORTED_DIFFIE_HELLMAN = 10 - UNSUPPORTED_MAC = 11 + from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +_Reasons = rust_exceptions._Reasons class UnsupportedAlgorithm(Exception): def __init__( self, message: str, reason: typing.Optional[_Reasons] = None ) -> None: - super(UnsupportedAlgorithm, self).__init__(message) + super().__init__(message) self._reason = reason @@ -58,9 +44,9 @@ class InvalidSignature(Exception): class InternalError(Exception): def __init__( - self, msg: str, err_code: typing.List["_OpenSSLErrorWithText"] + self, msg: str, err_code: typing.List[rust_openssl.OpenSSLError] ) -> None: - super(InternalError, self).__init__(msg) + super().__init__(msg) self.err_code = err_code diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py index c18c3bc..ad8fb40 100644 --- a/src/cryptography/fernet.py +++ b/src/cryptography/fernet.py @@ -2,6 +2,7 @@ # 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 @@ -28,7 +29,7 @@ def __init__( self, key: typing.Union[bytes, str], backend: typing.Any = None, - ): + ) -> None: try: key = base64.urlsafe_b64decode(key) except binascii.Error as exc: diff --git a/src/cryptography/hazmat/__init__.py b/src/cryptography/hazmat/__init__.py index 007694b..b9f1187 100644 --- a/src/cryptography/hazmat/__init__.py +++ b/src/cryptography/hazmat/__init__.py @@ -1,6 +1,9 @@ # This file is dual licensed under the terms 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 diff --git a/src/cryptography/hazmat/_oid.py b/src/cryptography/hazmat/_oid.py index 604cf07..01d4b34 100644 --- a/src/cryptography/hazmat/_oid.py +++ b/src/cryptography/hazmat/_oid.py @@ -2,6 +2,8 @@ # 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 ( @@ -38,10 +40,12 @@ class ExtensionOID: ) 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: @@ -62,6 +66,7 @@ class NameOID: 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") @@ -128,11 +133,19 @@ class SignatureAlgorithmOID: 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(), @@ -256,6 +269,7 @@ class AttributeOID: "signedCertificateTimestampList" ), ExtensionOID.PRECERT_POISON: "ctPoison", + ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate", CRLEntryExtensionOID.CRL_REASON: "cRLReason", CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", diff --git a/src/cryptography/hazmat/backends/__init__.py b/src/cryptography/hazmat/backends/__init__.py index 3926f85..b4400aa 100644 --- a/src/cryptography/hazmat/backends/__init__.py +++ b/src/cryptography/hazmat/backends/__init__.py @@ -1,6 +1,9 @@ # This file is dual licensed under the terms 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 diff --git a/src/cryptography/hazmat/backends/openssl/__init__.py b/src/cryptography/hazmat/backends/openssl/__init__.py index 31fd17c..51b0447 100644 --- a/src/cryptography/hazmat/backends/openssl/__init__.py +++ b/src/cryptography/hazmat/backends/openssl/__init__.py @@ -2,8 +2,8 @@ # 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"] diff --git a/src/cryptography/hazmat/backends/openssl/aead.py b/src/cryptography/hazmat/backends/openssl/aead.py index f7914af..b36f535 100644 --- a/src/cryptography/hazmat/backends/openssl/aead.py +++ b/src/cryptography/hazmat/backends/openssl/aead.py @@ -2,11 +2,12 @@ # 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.exceptions import InvalidTag - if typing.TYPE_CHECKING: from cryptography.hazmat.backends.openssl.backend import Backend from cryptography.hazmat.primitives.ciphers.aead import ( @@ -17,15 +18,215 @@ ChaCha20Poly1305, ) - _AEAD_TYPES = typing.Union[ + _AEADTypes = typing.Union[ AESCCM, AESGCM, AESOCB3, AESSIV, ChaCha20Poly1305 ] + +def _is_evp_aead_supported_cipher( + backend: Backend, cipher: _AEADTypes +) -> bool: + """ + Checks whether the given cipher is supported through + EVP_AEAD rather than the normal OpenSSL EVP_CIPHER API. + """ + from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 + + return backend._lib.Cryptography_HAS_EVP_AEAD and isinstance( + cipher, ChaCha20Poly1305 + ) + + +def _aead_cipher_supported(backend: Backend, cipher: _AEADTypes) -> bool: + if _is_evp_aead_supported_cipher(backend, cipher): + return True + else: + cipher_name = _evp_cipher_cipher_name(cipher) + if backend._fips_enabled and cipher_name not in backend._fips_aead: + return False + # SIV isn't loaded through get_cipherbyname but instead a new fetch API + # only available in 3.0+. But if we know we're on 3.0+ then we know + # it's supported. + if cipher_name.endswith(b"-siv"): + return backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER == 1 + else: + return ( + backend._lib.EVP_get_cipherbyname(cipher_name) + != backend._ffi.NULL + ) + + +def _aead_create_ctx( + backend: Backend, + cipher: _AEADTypes, + key: bytes, +): + if _is_evp_aead_supported_cipher(backend, cipher): + return _evp_aead_create_ctx(backend, cipher, key) + else: + return _evp_cipher_create_ctx(backend, cipher, key) + + +def _encrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any = None, +) -> bytes: + if _is_evp_aead_supported_cipher(backend, cipher): + return _evp_aead_encrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + else: + return _evp_cipher_encrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + + +def _decrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any = None, +) -> bytes: + if _is_evp_aead_supported_cipher(backend, cipher): + return _evp_aead_decrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + else: + return _evp_cipher_decrypt( + backend, cipher, nonce, data, associated_data, tag_length, ctx + ) + + +def _evp_aead_create_ctx( + backend: Backend, + cipher: _AEADTypes, + key: bytes, + tag_len: typing.Optional[int] = None, +): + aead_cipher = _evp_aead_get_cipher(backend, cipher) + assert aead_cipher is not None + key_ptr = backend._ffi.from_buffer(key) + tag_len = ( + backend._lib.EVP_AEAD_DEFAULT_TAG_LENGTH + if tag_len is None + else tag_len + ) + ctx = backend._lib.Cryptography_EVP_AEAD_CTX_new( + aead_cipher, key_ptr, len(key), tag_len + ) + backend.openssl_assert(ctx != backend._ffi.NULL) + ctx = backend._ffi.gc(ctx, backend._lib.EVP_AEAD_CTX_free) + return ctx + + +def _evp_aead_get_cipher(backend: Backend, cipher: _AEADTypes): + from cryptography.hazmat.primitives.ciphers.aead import ( + ChaCha20Poly1305, + ) + + # Currently only ChaCha20-Poly1305 is supported using this API + assert isinstance(cipher, ChaCha20Poly1305) + return backend._lib.EVP_aead_chacha20_poly1305() + + +def _evp_aead_encrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any, +) -> bytes: + assert ctx is not None + + aead_cipher = _evp_aead_get_cipher(backend, cipher) + assert aead_cipher is not None + + out_len = backend._ffi.new("size_t *") + # max_out_len should be in_len plus the result of + # EVP_AEAD_max_overhead. + max_out_len = len(data) + backend._lib.EVP_AEAD_max_overhead(aead_cipher) + out_buf = backend._ffi.new("uint8_t[]", max_out_len) + data_ptr = backend._ffi.from_buffer(data) + nonce_ptr = backend._ffi.from_buffer(nonce) + aad = b"".join(associated_data) + aad_ptr = backend._ffi.from_buffer(aad) + + res = backend._lib.EVP_AEAD_CTX_seal( + ctx, + out_buf, + out_len, + max_out_len, + nonce_ptr, + len(nonce), + data_ptr, + len(data), + aad_ptr, + len(aad), + ) + backend.openssl_assert(res == 1) + encrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] + return encrypted_data + + +def _evp_aead_decrypt( + backend: Backend, + cipher: _AEADTypes, + nonce: bytes, + data: bytes, + associated_data: typing.List[bytes], + tag_length: int, + ctx: typing.Any, +) -> bytes: + if len(data) < tag_length: + raise InvalidTag + + assert ctx is not None + + out_len = backend._ffi.new("size_t *") + # max_out_len should at least in_len + max_out_len = len(data) + out_buf = backend._ffi.new("uint8_t[]", max_out_len) + data_ptr = backend._ffi.from_buffer(data) + nonce_ptr = backend._ffi.from_buffer(nonce) + aad = b"".join(associated_data) + aad_ptr = backend._ffi.from_buffer(aad) + + res = backend._lib.EVP_AEAD_CTX_open( + ctx, + out_buf, + out_len, + max_out_len, + nonce_ptr, + len(nonce), + data_ptr, + len(data), + aad_ptr, + len(aad), + ) + + if res == 0: + backend._consume_errors() + raise InvalidTag + + decrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] + return decrypted_data + + _ENCRYPT = 1 _DECRYPT = 0 -def _aead_cipher_name(cipher: "_AEAD_TYPES") -> bytes: +def _evp_cipher_cipher_name(cipher: _AEADTypes) -> bytes: from cryptography.hazmat.primitives.ciphers.aead import ( AESCCM, AESGCM, @@ -47,7 +248,7 @@ def _aead_cipher_name(cipher: "_AEAD_TYPES") -> bytes: return f"aes-{len(cipher._key) * 8}-gcm".encode("ascii") -def _evp_cipher(cipher_name: bytes, backend: "Backend"): +def _evp_cipher(cipher_name: bytes, backend: Backend): if cipher_name.endswith(b"-siv"): evp_cipher = backend._lib.EVP_CIPHER_fetch( backend._ffi.NULL, @@ -63,8 +264,31 @@ def _evp_cipher(cipher_name: bytes, backend: "Backend"): return evp_cipher -def _aead_setup( - backend: "Backend", +def _evp_cipher_create_ctx( + backend: Backend, + cipher: _AEADTypes, + key: bytes, +): + ctx = backend._lib.EVP_CIPHER_CTX_new() + backend.openssl_assert(ctx != backend._ffi.NULL) + ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) + cipher_name = _evp_cipher_cipher_name(cipher) + evp_cipher = _evp_cipher(cipher_name, backend) + key_ptr = backend._ffi.from_buffer(key) + res = backend._lib.EVP_CipherInit_ex( + ctx, + evp_cipher, + backend._ffi.NULL, + key_ptr, + backend._ffi.NULL, + 0, + ) + backend.openssl_assert(res != 0) + return ctx + + +def _evp_cipher_aead_setup( + backend: Backend, cipher_name: bytes, key: bytes, nonce: bytes, @@ -84,8 +308,7 @@ def _aead_setup( int(operation == _ENCRYPT), ) backend.openssl_assert(res != 0) - res = backend._lib.EVP_CIPHER_CTX_set_key_length(ctx, len(key)) - backend.openssl_assert(res != 0) + # CCM requires the IVLEN to be set before calling SET_TAG on decrypt res = backend._lib.EVP_CIPHER_CTX_ctrl( ctx, backend._lib.EVP_CTRL_AEAD_SET_IVLEN, @@ -95,13 +318,13 @@ def _aead_setup( backend.openssl_assert(res != 0) if operation == _DECRYPT: assert tag is not None - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag - ) - backend.openssl_assert(res != 0) + _evp_cipher_set_tag(backend, ctx, tag) elif cipher_name.endswith(b"-ccm"): res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, tag_len, backend._ffi.NULL + ctx, + backend._lib.EVP_CTRL_AEAD_SET_TAG, + tag_len, + backend._ffi.NULL, ) backend.openssl_assert(res != 0) @@ -119,7 +342,30 @@ def _aead_setup( return ctx -def _set_length(backend: "Backend", ctx, data_len: int) -> None: +def _evp_cipher_set_tag(backend, ctx, tag: bytes) -> None: + tag_ptr = backend._ffi.from_buffer(tag) + res = backend._lib.EVP_CIPHER_CTX_ctrl( + ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag_ptr + ) + backend.openssl_assert(res != 0) + + +def _evp_cipher_set_nonce_operation( + backend, ctx, nonce: bytes, operation: int +) -> None: + nonce_ptr = backend._ffi.from_buffer(nonce) + res = backend._lib.EVP_CipherInit_ex( + ctx, + backend._ffi.NULL, + backend._ffi.NULL, + backend._ffi.NULL, + nonce_ptr, + int(operation == _ENCRYPT), + ) + backend.openssl_assert(res != 0) + + +def _evp_cipher_set_length(backend: Backend, ctx, data_len: int) -> None: intptr = backend._ffi.new("int *") res = backend._lib.EVP_CipherUpdate( ctx, backend._ffi.NULL, intptr, backend._ffi.NULL, data_len @@ -127,18 +373,22 @@ def _set_length(backend: "Backend", ctx, data_len: int) -> None: backend.openssl_assert(res != 0) -def _process_aad(backend: "Backend", ctx, associated_data: bytes) -> None: +def _evp_cipher_process_aad( + backend: Backend, ctx, associated_data: bytes +) -> None: outlen = backend._ffi.new("int *") + a_data_ptr = backend._ffi.from_buffer(associated_data) res = backend._lib.EVP_CipherUpdate( - ctx, backend._ffi.NULL, outlen, associated_data, len(associated_data) + ctx, backend._ffi.NULL, outlen, a_data_ptr, len(associated_data) ) backend.openssl_assert(res != 0) -def _process_data(backend: "Backend", ctx, data: bytes) -> bytes: +def _evp_cipher_process_data(backend: Backend, ctx, data: bytes) -> bytes: outlen = backend._ffi.new("int *") buf = backend._ffi.new("unsigned char[]", len(data)) - res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data, len(data)) + data_ptr = backend._ffi.from_buffer(data) + res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data_ptr, len(data)) if res == 0: # AES SIV can error here if the data is invalid on decrypt backend._consume_errors() @@ -146,28 +396,40 @@ def _process_data(backend: "Backend", ctx, data: bytes) -> bytes: return backend._ffi.buffer(buf, outlen[0])[:] -def _encrypt( - backend: "Backend", - cipher: "_AEAD_TYPES", +def _evp_cipher_encrypt( + backend: Backend, + cipher: _AEADTypes, nonce: bytes, data: bytes, associated_data: typing.List[bytes], tag_length: int, + ctx: typing.Any = None, ) -> bytes: from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV - cipher_name = _aead_cipher_name(cipher) - ctx = _aead_setup( - backend, cipher_name, cipher._key, nonce, None, tag_length, _ENCRYPT - ) - # CCM requires us to pass the length of the data before processing anything + if ctx is None: + cipher_name = _evp_cipher_cipher_name(cipher) + ctx = _evp_cipher_aead_setup( + backend, + cipher_name, + cipher._key, + nonce, + None, + tag_length, + _ENCRYPT, + ) + else: + _evp_cipher_set_nonce_operation(backend, ctx, nonce, _ENCRYPT) + + # CCM requires us to pass the length of the data before processing + # anything. # However calling this with any other AEAD results in an error if isinstance(cipher, AESCCM): - _set_length(backend, ctx, len(data)) + _evp_cipher_set_length(backend, ctx, len(data)) for ad in associated_data: - _process_aad(backend, ctx, ad) - processed_data = _process_data(backend, ctx, data) + _evp_cipher_process_aad(backend, ctx, ad) + processed_data = _evp_cipher_process_data(backend, ctx, data) outlen = backend._ffi.new("int *") # All AEADs we support besides OCB are streaming so they return nothing # in finalization. OCB can return up to (16 byte block - 1) bytes so @@ -184,8 +446,8 @@ def _encrypt( tag = backend._ffi.buffer(tag_buf)[:] if isinstance(cipher, AESSIV): - # 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 + # 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 backend.openssl_assert(len(tag) == 16) return tag + processed_data @@ -193,13 +455,14 @@ def _encrypt( return processed_data + tag -def _decrypt( - backend: "Backend", - cipher: "_AEAD_TYPES", +def _evp_cipher_decrypt( + backend: Backend, + cipher: _AEADTypes, nonce: bytes, data: bytes, associated_data: typing.List[bytes], tag_length: int, + ctx: typing.Any = None, ) -> bytes: from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV @@ -207,38 +470,51 @@ def _decrypt( raise InvalidTag if isinstance(cipher, AESSIV): - # 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 + # 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 = data[:tag_length] data = data[tag_length:] else: tag = data[-tag_length:] data = data[:-tag_length] - cipher_name = _aead_cipher_name(cipher) - ctx = _aead_setup( - backend, cipher_name, cipher._key, nonce, tag, tag_length, _DECRYPT - ) - # CCM requires us to pass the length of the data before processing anything + if ctx is None: + cipher_name = _evp_cipher_cipher_name(cipher) + ctx = _evp_cipher_aead_setup( + backend, + cipher_name, + cipher._key, + nonce, + tag, + tag_length, + _DECRYPT, + ) + else: + _evp_cipher_set_nonce_operation(backend, ctx, nonce, _DECRYPT) + _evp_cipher_set_tag(backend, ctx, tag) + + # CCM requires us to pass the length of the data before processing + # anything. # However calling this with any other AEAD results in an error if isinstance(cipher, AESCCM): - _set_length(backend, ctx, len(data)) + _evp_cipher_set_length(backend, ctx, len(data)) for ad in associated_data: - _process_aad(backend, ctx, ad) + _evp_cipher_process_aad(backend, ctx, ad) # CCM has a different error path if the tag doesn't match. Errors are # raised in Update and Final is irrelevant. if isinstance(cipher, AESCCM): outlen = backend._ffi.new("int *") buf = backend._ffi.new("unsigned char[]", len(data)) - res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data, len(data)) + d_ptr = backend._ffi.from_buffer(data) + res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, d_ptr, len(data)) if res != 1: backend._consume_errors() raise InvalidTag processed_data = backend._ffi.buffer(buf, outlen[0])[:] else: - processed_data = _process_data(backend, ctx, data) + processed_data = _evp_cipher_process_data(backend, ctx, data) outlen = backend._ffi.new("int *") # OCB can return up to 15 bytes (16 byte block - 1) in finalization buf = backend._ffi.new("unsigned char[]", 16) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index f8776b7..f1c7900 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -2,12 +2,12 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations import collections import contextlib import itertools import typing -import warnings from contextlib import contextmanager from cryptography import utils, x509 @@ -15,51 +15,15 @@ from cryptography.hazmat.backends.openssl import aead from cryptography.hazmat.backends.openssl.ciphers import _CipherContext from cryptography.hazmat.backends.openssl.cmac import _CMACContext -from cryptography.hazmat.backends.openssl.dh import ( - _DHParameters, - _DHPrivateKey, - _DHPublicKey, - _dh_params_dup, -) -from cryptography.hazmat.backends.openssl.dsa import ( - _DSAParameters, - _DSAPrivateKey, - _DSAPublicKey, -) from cryptography.hazmat.backends.openssl.ec import ( _EllipticCurvePrivateKey, _EllipticCurvePublicKey, ) -from cryptography.hazmat.backends.openssl.ed25519 import ( - _Ed25519PrivateKey, - _Ed25519PublicKey, -) -from cryptography.hazmat.backends.openssl.ed448 import ( - _ED448_KEY_SIZE, - _Ed448PrivateKey, - _Ed448PublicKey, -) -from cryptography.hazmat.backends.openssl.hashes import _HashContext -from cryptography.hazmat.backends.openssl.hmac import _HMACContext -from cryptography.hazmat.backends.openssl.poly1305 import ( - _POLY1305_KEY_SIZE, - _Poly1305Context, -) from cryptography.hazmat.backends.openssl.rsa import ( _RSAPrivateKey, _RSAPublicKey, ) -from cryptography.hazmat.backends.openssl.x25519 import ( - _X25519PrivateKey, - _X25519PublicKey, -) -from cryptography.hazmat.backends.openssl.x448 import ( - _X448PrivateKey, - _X448PublicKey, -) -from cryptography.hazmat.bindings._rust import ( - x509 as rust_x509, -) +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl import binding from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding @@ -67,22 +31,21 @@ dh, dsa, ec, - ed25519, ed448, + ed25519, rsa, - x25519, x448, + x25519, ) from cryptography.hazmat.primitives.asymmetric.padding import ( MGF1, OAEP, - PKCS1v15, PSS, + PKCS1v15, ) from cryptography.hazmat.primitives.asymmetric.types import ( - CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES, - PRIVATE_KEY_TYPES, - PUBLIC_KEY_TYPES, + PrivateKeyTypes, + PublicKeyTypes, ) from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, @@ -93,9 +56,9 @@ AES128, AES256, ARC4, + SM4, Camellia, ChaCha20, - SM4, TripleDES, _BlowfishInternal, _CAST5Internal, @@ -109,21 +72,19 @@ CTR, ECB, GCM, - Mode, OFB, XTS, + Mode, ) -from cryptography.hazmat.primitives.kdf import scrypt -from cryptography.hazmat.primitives.serialization import pkcs7, ssh +from cryptography.hazmat.primitives.serialization import ssh from cryptography.hazmat.primitives.serialization.pkcs12 import ( PBES, PKCS12Certificate, PKCS12KeyAndCertificates, - _ALLOWED_PKCS12_TYPES, - _PKCS12_CAS_TYPES, + PKCS12PrivateKeyTypes, + _PKCS12CATypes, ) - _MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) @@ -182,111 +143,41 @@ class Backend: _fips_dh_min_key_size = 2048 _fips_dh_min_modulus = 1 << _fips_dh_min_key_size - def __init__(self): + def __init__(self) -> None: self._binding = binding.Binding() self._ffi = self._binding.ffi self._lib = self._binding.lib - self._rsa_skip_check_key = False - self._fips_enabled = self._is_fips_enabled() + self._fips_enabled = rust_openssl.is_fips_enabled() - self._cipher_registry = {} + self._cipher_registry: typing.Dict[ + typing.Tuple[typing.Type[CipherAlgorithm], typing.Type[Mode]], + typing.Callable, + ] = {} self._register_default_ciphers() - if self._fips_enabled and self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - warnings.warn( - "OpenSSL FIPS mode is enabled. Can't enable DRBG fork safety.", - UserWarning, - ) - else: - self.activate_osrandom_engine() self._dh_types = [self._lib.EVP_PKEY_DH] if self._lib.Cryptography_HAS_EVP_PKEY_DHX: self._dh_types.append(self._lib.EVP_PKEY_DHX) def __repr__(self) -> str: - return "".format( - self.openssl_version_text(), self._fips_enabled + return "".format( + self.openssl_version_text(), + self._fips_enabled, + self._binding._legacy_provider_loaded, ) def openssl_assert( self, ok: bool, - errors: typing.Optional[typing.List[binding._OpenSSLError]] = None, + errors: typing.Optional[typing.List[rust_openssl.OpenSSLError]] = None, ) -> None: return binding._openssl_assert(self._lib, ok, errors=errors) - def _is_fips_enabled(self) -> bool: - if self._lib.Cryptography_HAS_300_FIPS: - mode = self._lib.EVP_default_properties_is_fips_enabled( - self._ffi.NULL - ) - else: - mode = getattr(self._lib, "FIPS_mode", lambda: 0)() - - if mode == 0: - # OpenSSL without FIPS pushes an error on the error stack - self._lib.ERR_clear_error() - return bool(mode) - def _enable_fips(self) -> None: # This function enables FIPS mode for OpenSSL 3.0.0 on installs that # have the FIPS provider installed properly. self._binding._enable_fips() - assert self._is_fips_enabled() - self._fips_enabled = self._is_fips_enabled() - - def activate_builtin_random(self) -> None: - if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - # Obtain a new structural reference. - e = self._lib.ENGINE_get_default_RAND() - if e != self._ffi.NULL: - self._lib.ENGINE_unregister_RAND(e) - # Reset the RNG to use the built-in. - res = self._lib.RAND_set_rand_method(self._ffi.NULL) - self.openssl_assert(res == 1) - # decrement the structural reference from get_default_RAND - res = self._lib.ENGINE_finish(e) - self.openssl_assert(res == 1) - - @contextlib.contextmanager - def _get_osurandom_engine(self): - # Fetches an engine by id and returns it. This creates a structural - # reference. - e = self._lib.ENGINE_by_id(self._lib.Cryptography_osrandom_engine_id) - self.openssl_assert(e != self._ffi.NULL) - # Initialize the engine for use. This adds a functional reference. - res = self._lib.ENGINE_init(e) - self.openssl_assert(res == 1) - - try: - yield e - finally: - # Decrement the structural ref incremented by ENGINE_by_id. - res = self._lib.ENGINE_free(e) - self.openssl_assert(res == 1) - # Decrement the functional ref incremented by ENGINE_init. - res = self._lib.ENGINE_finish(e) - self.openssl_assert(res == 1) - - def activate_osrandom_engine(self) -> None: - if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - # Unregister and free the current engine. - self.activate_builtin_random() - with self._get_osurandom_engine() as e: - # Set the engine as the default RAND provider. - res = self._lib.ENGINE_set_default_RAND(e) - self.openssl_assert(res == 1) - # Reset the RNG to use the engine - res = self._lib.RAND_set_rand_method(self._ffi.NULL) - self.openssl_assert(res == 1) - - def osrandom_engine_implementation(self) -> str: - buf = self._ffi.new("char[]", 64) - with self._get_osurandom_engine() as e: - res = self._lib.ENGINE_ctrl_cmd( - e, b"get_implementation", len(buf), buf, self._ffi.NULL, 0 - ) - self.openssl_assert(res > 0) - return self._ffi.string(buf).decode("ascii") + assert rust_openssl.is_fips_enabled() + self._fips_enabled = rust_openssl.is_fips_enabled() def openssl_version_text(self) -> str: """ @@ -302,11 +193,6 @@ def openssl_version_text(self) -> str: def openssl_version_number(self) -> int: return self._lib.OpenSSL_version_num() - def create_hmac_ctx( - self, key: bytes, algorithm: hashes.HashAlgorithm - ) -> _HMACContext: - return _HMACContext(self, key, algorithm) - def _evp_md_from_algorithm(self, algorithm: hashes.HashAlgorithm): if algorithm.name == "blake2b" or algorithm.name == "blake2s": alg = "{}{}".format( @@ -352,11 +238,6 @@ def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: return self.hash_supported(algorithm) - def create_hash_ctx( - self, algorithm: hashes.HashAlgorithm - ) -> hashes.HashContext: - return _HashContext(self, algorithm) - def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: if self._fips_enabled: # FIPS mode requires AES. TripleDES is disallowed/deprecated in @@ -371,7 +252,7 @@ def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: evp_cipher = adapter(self, cipher, mode) return self._ffi.NULL != evp_cipher - def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): + def register_cipher_adapter(self, cipher_cls, mode_cls, adapter) -> None: if (cipher_cls, mode_cls) in self._cipher_registry: raise ValueError( "Duplicate registration for: {} {}.".format( @@ -403,26 +284,6 @@ def _register_default_ciphers(self) -> None: self.register_cipher_adapter( TripleDES, ECB, GetCipherByName("des-ede3") ) - for mode_cls in [CBC, CFB, OFB, ECB]: - self.register_cipher_adapter( - _BlowfishInternal, mode_cls, GetCipherByName("bf-{mode.name}") - ) - for mode_cls in [CBC, CFB, OFB, ECB]: - self.register_cipher_adapter( - _SEEDInternal, mode_cls, GetCipherByName("seed-{mode.name}") - ) - for cipher_cls, mode_cls in itertools.product( - [_CAST5Internal, _IDEAInternal], - [CBC, OFB, CFB, ECB], - ): - self.register_cipher_adapter( - cipher_cls, - mode_cls, - GetCipherByName("{cipher.name}-{mode.name}"), - ) - self.register_cipher_adapter(ARC4, type(None), GetCipherByName("rc4")) - # We don't actually support RC2, this is just used by some tests. - self.register_cipher_adapter(_RC2, type(None), GetCipherByName("rc2")) self.register_cipher_adapter( ChaCha20, type(None), GetCipherByName("chacha20") ) @@ -431,6 +292,42 @@ def _register_default_ciphers(self) -> None: self.register_cipher_adapter( SM4, mode_cls, GetCipherByName("sm4-{mode.name}") ) + # Don't register legacy ciphers if they're unavailable. Hypothetically + # this wouldn't be necessary because we test availability by seeing if + # we get an EVP_CIPHER * in the _CipherContext __init__, but OpenSSL 3 + # will return a valid pointer even though the cipher is unavailable. + if ( + self._binding._legacy_provider_loaded + or not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER + ): + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + _BlowfishInternal, + mode_cls, + GetCipherByName("bf-{mode.name}"), + ) + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + _SEEDInternal, + mode_cls, + GetCipherByName("seed-{mode.name}"), + ) + for cipher_cls, mode_cls in itertools.product( + [_CAST5Internal, _IDEAInternal], + [CBC, OFB, CFB, ECB], + ): + self.register_cipher_adapter( + cipher_cls, + mode_cls, + GetCipherByName("{cipher.name}-{mode.name}"), + ) + self.register_cipher_adapter( + ARC4, type(None), GetCipherByName("rc4") + ) + # We don't actually support RC2, this is just used by some tests. + self.register_cipher_adapter( + _RC2, type(None), GetCipherByName("rc2") + ) def create_symmetric_encryption_ctx( self, cipher: CipherAlgorithm, mode: Mode @@ -445,37 +342,8 @@ def create_symmetric_decryption_ctx( def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: return self.hmac_supported(algorithm) - def derive_pbkdf2_hmac( - self, - algorithm: hashes.HashAlgorithm, - length: int, - salt: bytes, - iterations: int, - key_material: bytes, - ) -> bytes: - buf = self._ffi.new("unsigned char[]", length) - evp_md = self._evp_md_non_null_from_algorithm(algorithm) - key_material_ptr = self._ffi.from_buffer(key_material) - res = self._lib.PKCS5_PBKDF2_HMAC( - key_material_ptr, - len(key_material), - salt, - len(salt), - iterations, - evp_md, - length, - buf, - ) - self.openssl_assert(res == 1) - return self._ffi.buffer(buf)[:] - - def _consume_errors(self) -> typing.List[binding._OpenSSLError]: - return binding._consume_errors(self._lib) - - def _consume_errors_with_text( - self, - ) -> typing.List[binding._OpenSSLErrorWithText]: - return binding._consume_errors_with_text(self._lib) + def _consume_errors(self) -> typing.List[rust_openssl.OpenSSLError]: + return rust_openssl.capture_error_stack() def _bn_to_int(self, bn) -> int: assert bn != self._ffi.NULL @@ -489,20 +357,15 @@ def _bn_to_int(self, bn) -> int: val = int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") return val - def _int_to_bn(self, num: int, bn=None): + def _int_to_bn(self, num: int): """ Converts a python integer to a BIGNUM. The returned BIGNUM will not be garbage collected (to support adding them to structs that take ownership of the object). Be sure to register it for GC if it will be discarded after use. """ - assert bn is None or bn != self._ffi.NULL - - if bn is None: - bn = self._ffi.NULL - binary = num.to_bytes(int(num.bit_length() / 8.0 + 1), "big") - bn_ptr = self._lib.BN_bin2bn(binary, len(binary), bn) + bn_ptr = self._lib.BN_bin2bn(binary, len(binary), self._ffi.NULL) self.openssl_assert(bn_ptr != self._ffi.NULL) return bn_ptr @@ -524,8 +387,9 @@ def generate_rsa_private_key( self.openssl_assert(res == 1) evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) + # We can skip RSA key validation here since we just generated the key return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, self._rsa_skip_check_key + self, rsa_cdata, evp_pkey, unsafe_skip_rsa_key_validation=True ) def generate_rsa_parameters_supported( @@ -538,7 +402,9 @@ def generate_rsa_parameters_supported( ) def load_rsa_private_numbers( - self, numbers: rsa.RSAPrivateNumbers + self, + numbers: rsa.RSAPrivateNumbers, + unsafe_skip_rsa_key_validation: bool, ) -> rsa.RSAPrivateKey: rsa._check_private_key_components( numbers.p, @@ -570,7 +436,10 @@ def load_rsa_private_numbers( evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, self._rsa_skip_check_key + self, + rsa_cdata, + evp_pkey, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, ) def load_rsa_public_numbers( @@ -600,7 +469,7 @@ def _rsa_cdata_to_evp_pkey(self, rsa_cdata): self.openssl_assert(res == 1) return evp_pkey - def _bytes_to_bio(self, data: bytes): + def _bytes_to_bio(self, data: bytes) -> _MemoryBIO: """ Return a _MemoryBIO namedtuple of (BIO, char*). @@ -635,7 +504,9 @@ def _read_mem_bio(self, bio) -> bytes: bio_data = self._ffi.buffer(buf[0], buf_len)[:] return bio_data - def _evp_pkey_to_private_key(self, evp_pkey) -> PRIVATE_KEY_TYPES: + def _evp_pkey_to_private_key( + self, evp_pkey, unsafe_skip_rsa_key_validation: bool + ) -> PrivateKeyTypes: """ Return the appropriate type of PrivateKey given an evp_pkey cdata pointer. @@ -648,7 +519,10 @@ def _evp_pkey_to_private_key(self, evp_pkey) -> PRIVATE_KEY_TYPES: self.openssl_assert(rsa_cdata != self._ffi.NULL) rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, self._rsa_skip_check_key + self, + rsa_cdata, + evp_pkey, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, ) elif ( key_type == self._lib.EVP_PKEY_RSA_PSS @@ -667,39 +541,46 @@ def _evp_pkey_to_private_key(self, evp_pkey) -> PRIVATE_KEY_TYPES: res = self._lib.i2d_RSAPrivateKey_bio(bio, rsa_cdata) self.openssl_assert(res == 1) return self.load_der_private_key( - self._read_mem_bio(bio), password=None + self._read_mem_bio(bio), + password=None, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, ) elif key_type == self._lib.EVP_PKEY_DSA: - dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - return _DSAPrivateKey(self, dsa_cdata, evp_pkey) + return rust_openssl.dsa.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == self._lib.EVP_PKEY_EC: ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) self.openssl_assert(ec_cdata != self._ffi.NULL) ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) elif key_type in self._dh_types: - dh_cdata = self._lib.EVP_PKEY_get1_DH(evp_pkey) - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHPrivateKey(self, dh_cdata, evp_pkey) + return rust_openssl.dh.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1 - return _Ed25519PrivateKey(self, evp_pkey) + # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed25519.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 - return _X448PrivateKey(self, evp_pkey) - elif key_type == getattr(self._lib, "EVP_PKEY_X25519", None): - # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.0 - return _X25519PrivateKey(self, evp_pkey) + # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.x448.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) + elif key_type == self._lib.EVP_PKEY_X25519: + return rust_openssl.x25519.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_ED448 is not present in OpenSSL < 1.1.1 - return _Ed448PrivateKey(self, evp_pkey) + # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed448.private_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) else: raise UnsupportedAlgorithm("Unsupported key type.") - def _evp_pkey_to_public_key(self, evp_pkey) -> PUBLIC_KEY_TYPES: + def _evp_pkey_to_public_key(self, evp_pkey) -> PublicKeyTypes: """ Return the appropriate type of PublicKey given an evp_pkey cdata pointer. @@ -726,38 +607,46 @@ def _evp_pkey_to_public_key(self, evp_pkey) -> PUBLIC_KEY_TYPES: self.openssl_assert(res == 1) return self.load_der_public_key(self._read_mem_bio(bio)) elif key_type == self._lib.EVP_PKEY_DSA: - dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - return _DSAPublicKey(self, dsa_cdata, evp_pkey) + return rust_openssl.dsa.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == self._lib.EVP_PKEY_EC: ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) if ec_cdata == self._ffi.NULL: - errors = self._consume_errors_with_text() + errors = self._consume_errors() raise ValueError("Unable to load EC key", errors) ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) elif key_type in self._dh_types: - dh_cdata = self._lib.EVP_PKEY_get1_DH(evp_pkey) - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHPublicKey(self, dh_cdata, evp_pkey) + return rust_openssl.dh.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1 - return _Ed25519PublicKey(self, evp_pkey) + # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed25519.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 - return _X448PublicKey(self, evp_pkey) - elif key_type == getattr(self._lib, "EVP_PKEY_X25519", None): - # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.0 - return _X25519PublicKey(self, evp_pkey) + # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.x448.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) + elif key_type == self._lib.EVP_PKEY_X25519: + return rust_openssl.x25519.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.1 - return _Ed448PublicKey(self, evp_pkey) + # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL + return rust_openssl.ed448.public_key_from_ptr( + int(self._ffi.cast("uintptr_t", evp_pkey)) + ) else: raise UnsupportedAlgorithm("Unsupported key type.") def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False + return isinstance( algorithm, ( @@ -788,42 +677,24 @@ def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: 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 generate_dsa_parameters(self, key_size: int) -> dsa.DSAParameters: if key_size not in (1024, 2048, 3072, 4096): raise ValueError( "Key size must be 1024, 2048, 3072, or 4096 bits." ) - ctx = self._lib.DSA_new() - self.openssl_assert(ctx != self._ffi.NULL) - ctx = self._ffi.gc(ctx, self._lib.DSA_free) - - res = self._lib.DSA_generate_parameters_ex( - ctx, - key_size, - self._ffi.NULL, - 0, - self._ffi.NULL, - self._ffi.NULL, - self._ffi.NULL, - ) - - self.openssl_assert(res == 1) - - return _DSAParameters(self, ctx) + return rust_openssl.dsa.generate_parameters(key_size) def generate_dsa_private_key( self, parameters: dsa.DSAParameters ) -> dsa.DSAPrivateKey: - ctx = self._lib.DSAparams_dup( - parameters._dsa_cdata # type: ignore[attr-defined] - ) - self.openssl_assert(ctx != self._ffi.NULL) - ctx = self._ffi.gc(ctx, self._lib.DSA_free) - self._lib.DSA_generate_key(ctx) - evp_pkey = self._dsa_cdata_to_evp_pkey(ctx) - - return _DSAPrivateKey(self, ctx, evp_pkey) + return parameters.generate_private_key() def generate_dsa_private_key_and_parameters( self, key_size: int @@ -831,76 +702,28 @@ def generate_dsa_private_key_and_parameters( parameters = self.generate_dsa_parameters(key_size) return self.generate_dsa_private_key(parameters) - def _dsa_cdata_set_values(self, dsa_cdata, p, q, g, pub_key, priv_key): - res = self._lib.DSA_set0_pqg(dsa_cdata, p, q, g) - self.openssl_assert(res == 1) - res = self._lib.DSA_set0_key(dsa_cdata, pub_key, priv_key) - self.openssl_assert(res == 1) - def load_dsa_private_numbers( self, numbers: dsa.DSAPrivateNumbers ) -> dsa.DSAPrivateKey: dsa._check_dsa_private_numbers(numbers) - parameter_numbers = numbers.public_numbers.parameter_numbers - - dsa_cdata = self._lib.DSA_new() - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - - p = self._int_to_bn(parameter_numbers.p) - q = self._int_to_bn(parameter_numbers.q) - g = self._int_to_bn(parameter_numbers.g) - pub_key = self._int_to_bn(numbers.public_numbers.y) - priv_key = self._int_to_bn(numbers.x) - self._dsa_cdata_set_values(dsa_cdata, p, q, g, pub_key, priv_key) - - evp_pkey = self._dsa_cdata_to_evp_pkey(dsa_cdata) - - return _DSAPrivateKey(self, dsa_cdata, evp_pkey) + return rust_openssl.dsa.from_private_numbers(numbers) def load_dsa_public_numbers( self, numbers: dsa.DSAPublicNumbers ) -> dsa.DSAPublicKey: dsa._check_dsa_parameters(numbers.parameter_numbers) - dsa_cdata = self._lib.DSA_new() - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - - p = self._int_to_bn(numbers.parameter_numbers.p) - q = self._int_to_bn(numbers.parameter_numbers.q) - g = self._int_to_bn(numbers.parameter_numbers.g) - pub_key = self._int_to_bn(numbers.y) - priv_key = self._ffi.NULL - self._dsa_cdata_set_values(dsa_cdata, p, q, g, pub_key, priv_key) - - evp_pkey = self._dsa_cdata_to_evp_pkey(dsa_cdata) - - return _DSAPublicKey(self, dsa_cdata, evp_pkey) + return rust_openssl.dsa.from_public_numbers(numbers) def load_dsa_parameter_numbers( self, numbers: dsa.DSAParameterNumbers ) -> dsa.DSAParameters: dsa._check_dsa_parameters(numbers) - dsa_cdata = self._lib.DSA_new() - self.openssl_assert(dsa_cdata != self._ffi.NULL) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - - p = self._int_to_bn(numbers.p) - q = self._int_to_bn(numbers.q) - g = self._int_to_bn(numbers.g) - res = self._lib.DSA_set0_pqg(dsa_cdata, p, q, g) - self.openssl_assert(res == 1) - - return _DSAParameters(self, dsa_cdata) - - def _dsa_cdata_to_evp_pkey(self, dsa_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_DSA(evp_pkey, dsa_cdata) - self.openssl_assert(res == 1) - return evp_pkey + return rust_openssl.dsa.from_parameter_numbers(numbers) def dsa_supported(self) -> bool: - return not self._fips_enabled + return ( + not self._lib.CRYPTOGRAPHY_IS_BORINGSSL and not self._fips_enabled + ) def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if not self.dsa_supported(): @@ -916,16 +739,19 @@ def create_cmac_ctx(self, algorithm: BlockCipherAlgorithm) -> _CMACContext: return _CMACContext(self, algorithm) def load_pem_private_key( - self, data: bytes, password: typing.Optional[bytes] - ) -> PRIVATE_KEY_TYPES: + self, + data: bytes, + password: typing.Optional[bytes], + unsafe_skip_rsa_key_validation: bool, + ) -> PrivateKeyTypes: return self._load_key( self._lib.PEM_read_bio_PrivateKey, - self._evp_pkey_to_private_key, data, password, + unsafe_skip_rsa_key_validation, ) - def load_pem_public_key(self, data: bytes) -> PUBLIC_KEY_TYPES: + def load_pem_public_key(self, data: bytes) -> PublicKeyTypes: mem_bio = self._bytes_to_bio(data) # In OpenSSL 3.0.x the PEM_read_bio_PUBKEY function will invoke # the default password callback if you pass an encrypted private @@ -968,20 +794,14 @@ def load_pem_public_key(self, data: bytes) -> PUBLIC_KEY_TYPES: self._handle_key_loading_error() def load_pem_parameters(self, data: bytes) -> dh.DHParameters: - mem_bio = self._bytes_to_bio(data) - # only DH is supported currently - dh_cdata = self._lib.PEM_read_bio_DHparams( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL - ) - if dh_cdata != self._ffi.NULL: - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHParameters(self, dh_cdata) - else: - self._handle_key_loading_error() + return rust_openssl.dh.from_pem_parameters(data) def load_der_private_key( - self, data: bytes, password: typing.Optional[bytes] - ) -> PRIVATE_KEY_TYPES: + self, + data: bytes, + password: typing.Optional[bytes], + unsafe_skip_rsa_key_validation: bool, + ) -> PrivateKeyTypes: # OpenSSL has a function called d2i_AutoPrivateKey that in theory # handles this automatically, however it doesn't handle encrypted # private keys. Instead we try to load the key two different ways. @@ -989,25 +809,22 @@ def load_der_private_key( bio_data = self._bytes_to_bio(data) key = self._evp_pkey_from_der_traditional_key(bio_data, password) if key: - return self._evp_pkey_to_private_key(key) + return self._evp_pkey_to_private_key( + key, unsafe_skip_rsa_key_validation + ) else: # Finally we try to load it with the method that handles encrypted # PKCS8 properly. return self._load_key( self._lib.d2i_PKCS8PrivateKey_bio, - self._evp_pkey_to_private_key, data, password, + unsafe_skip_rsa_key_validation, ) def _evp_pkey_from_der_traditional_key(self, bio_data, password): key = self._lib.d2i_PrivateKey_bio(bio_data.bio, self._ffi.NULL) if key != self._ffi.NULL: - # In OpenSSL 3.0.0-alpha15 there exist scenarios where the key will - # successfully load but errors are still put on the stack. Tracked - # as https://github.com/openssl/openssl/issues/14996 - self._consume_errors() - key = self._ffi.gc(key, self._lib.EVP_PKEY_free) if password is not None: raise TypeError( @@ -1019,7 +836,7 @@ def _evp_pkey_from_der_traditional_key(self, bio_data, password): self._consume_errors() return None - def load_der_public_key(self, data: bytes) -> PUBLIC_KEY_TYPES: + def load_der_public_key(self, data: bytes) -> PublicKeyTypes: mem_bio = self._bytes_to_bio(data) evp_pkey = self._lib.d2i_PUBKEY_bio(mem_bio.bio, self._ffi.NULL) if evp_pkey != self._ffi.NULL: @@ -1043,24 +860,7 @@ def load_der_public_key(self, data: bytes) -> PUBLIC_KEY_TYPES: self._handle_key_loading_error() def load_der_parameters(self, data: bytes) -> dh.DHParameters: - mem_bio = self._bytes_to_bio(data) - dh_cdata = self._lib.d2i_DHparams_bio(mem_bio.bio, self._ffi.NULL) - if dh_cdata != self._ffi.NULL: - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHParameters(self, dh_cdata) - elif self._lib.Cryptography_HAS_EVP_PKEY_DHX: - # We check to see if the is dhx. - self._consume_errors() - res = self._lib.BIO_reset(mem_bio.bio) - self.openssl_assert(res == 1) - dh_cdata = self._lib.Cryptography_d2i_DHxparams_bio( - mem_bio.bio, self._ffi.NULL - ) - if dh_cdata != self._ffi.NULL: - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - return _DHParameters(self, dh_cdata) - - self._handle_key_loading_error() + return rust_openssl.dh.from_der_parameters(data) def _cert2ossl(self, cert: x509.Certificate) -> typing.Any: data = cert.public_bytes(serialization.Encoding.DER) @@ -1070,90 +870,30 @@ def _cert2ossl(self, cert: x509.Certificate) -> typing.Any: x509 = self._ffi.gc(x509, self._lib.X509_free) return x509 - def _ossl2cert(self, x509: typing.Any) -> x509.Certificate: - bio = self._create_mem_bio_gc() - res = self._lib.i2d_X509_bio(bio, x509) - self.openssl_assert(res == 1) - return rust_x509.load_der_x509_certificate(self._read_mem_bio(bio)) - - def _csr2ossl(self, csr: x509.CertificateSigningRequest) -> typing.Any: - data = csr.public_bytes(serialization.Encoding.DER) - mem_bio = self._bytes_to_bio(data) - x509_req = self._lib.d2i_X509_REQ_bio(mem_bio.bio, self._ffi.NULL) - self.openssl_assert(x509_req != self._ffi.NULL) - x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) - return x509_req - - def _ossl2csr( - self, x509_req: typing.Any - ) -> x509.CertificateSigningRequest: + def _ossl2cert(self, x509_ptr: typing.Any) -> x509.Certificate: bio = self._create_mem_bio_gc() - res = self._lib.i2d_X509_REQ_bio(bio, x509_req) + res = self._lib.i2d_X509_bio(bio, x509_ptr) self.openssl_assert(res == 1) - return rust_x509.load_der_x509_csr(self._read_mem_bio(bio)) + return x509.load_der_x509_certificate(self._read_mem_bio(bio)) - def _crl2ossl(self, crl: x509.CertificateRevocationList) -> typing.Any: - data = crl.public_bytes(serialization.Encoding.DER) + def _key2ossl(self, key: PKCS12PrivateKeyTypes) -> typing.Any: + data = key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + ) mem_bio = self._bytes_to_bio(data) - x509_crl = self._lib.d2i_X509_CRL_bio(mem_bio.bio, self._ffi.NULL) - self.openssl_assert(x509_crl != self._ffi.NULL) - x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) - return x509_crl - - def _ossl2crl( - self, x509_crl: typing.Any - ) -> x509.CertificateRevocationList: - bio = self._create_mem_bio_gc() - res = self._lib.i2d_X509_CRL_bio(bio, x509_crl) - self.openssl_assert(res == 1) - return rust_x509.load_der_x509_crl(self._read_mem_bio(bio)) - def _crl_is_signature_valid( - self, - crl: x509.CertificateRevocationList, - public_key: CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES, - ) -> bool: - if not isinstance( - public_key, - ( - _DSAPublicKey, - _RSAPublicKey, - _EllipticCurvePublicKey, - ), - ): - raise TypeError( - "Expecting one of DSAPublicKey, RSAPublicKey," - " or EllipticCurvePublicKey." - ) - x509_crl = self._crl2ossl(crl) - res = self._lib.X509_CRL_verify(x509_crl, public_key._evp_pkey) - - if res != 1: - self._consume_errors() - return False - - return True - - def _csr_is_signature_valid( - self, csr: x509.CertificateSigningRequest - ) -> bool: - x509_req = self._csr2ossl(csr) - pkey = self._lib.X509_REQ_get_pubkey(x509_req) - self.openssl_assert(pkey != self._ffi.NULL) - pkey = self._ffi.gc(pkey, self._lib.EVP_PKEY_free) - res = self._lib.X509_REQ_verify(x509_req, pkey) - - if res != 1: - self._consume_errors() - return False - - return True - - def _check_keys_correspond(self, key1, key2): - if self._lib.EVP_PKEY_cmp(key1._evp_pkey, key2._evp_pkey) != 1: - raise ValueError("Keys do not correspond") + evp_pkey = self._lib.d2i_PrivateKey_bio( + mem_bio.bio, + self._ffi.NULL, + ) + self.openssl_assert(evp_pkey != self._ffi.NULL) + return self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - def _load_key(self, openssl_read_func, convert_func, data, password): + def _load_key( + self, openssl_read_func, data, password, unsafe_skip_rsa_key_validation + ) -> PrivateKeyTypes: mem_bio = self._bytes_to_bio(data) userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") @@ -1188,11 +928,6 @@ def _load_key(self, openssl_read_func, convert_func, data, password): else: self._handle_key_loading_error() - # In OpenSSL 3.0.0-alpha15 there exist scenarios where the key will - # successfully load but errors are still put on the stack. Tracked - # as https://github.com/openssl/openssl/issues/14996 - self._consume_errors() - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) if password is not None and userdata.called == 0: @@ -1204,7 +939,9 @@ def _load_key(self, openssl_read_func, convert_func, data, password): password is not None and userdata.called == 1 ) or password is None - return convert_func(evp_pkey) + return self._evp_pkey_to_private_key( + evp_pkey, unsafe_skip_rsa_key_validation + ) def _handle_key_loading_error(self) -> typing.NoReturn: errors = self._consume_errors() @@ -1244,13 +981,12 @@ def _handle_key_loading_error(self) -> typing.NoReturn: raise ValueError("Unsupported public key algorithm.") else: - errors_with_text = binding._errors_with_text(errors) raise ValueError( "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).", - errors_with_text, + errors, ) def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: @@ -1298,7 +1034,7 @@ def generate_elliptic_curve_private_key( return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) else: raise UnsupportedAlgorithm( - "Backend object does not support {}.".format(curve.name), + f"Backend object does not support {curve.name}.", _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) @@ -1317,9 +1053,38 @@ def load_elliptic_curve_private_numbers( self._consume_errors() raise ValueError("Invalid EC key.") - self._ec_key_set_public_key_affine_coordinates( - ec_cdata, public.x, public.y - ) + with self._tmp_bn_ctx() as bn_ctx: + self._ec_key_set_public_key_affine_coordinates( + ec_cdata, public.x, public.y, bn_ctx + ) + # derive the expected public point and compare it to the one we + # just set based on the values we were given. If they don't match + # this isn't a valid key pair. + group = self._lib.EC_KEY_get0_group(ec_cdata) + self.openssl_assert(group != self._ffi.NULL) + set_point = backend._lib.EC_KEY_get0_public_key(ec_cdata) + self.openssl_assert(set_point != self._ffi.NULL) + computed_point = self._lib.EC_POINT_new(group) + self.openssl_assert(computed_point != self._ffi.NULL) + computed_point = self._ffi.gc( + computed_point, self._lib.EC_POINT_free + ) + res = self._lib.EC_POINT_mul( + group, + computed_point, + private_value, + self._ffi.NULL, + self._ffi.NULL, + bn_ctx, + ) + self.openssl_assert(res == 1) + if ( + self._lib.EC_POINT_cmp( + group, set_point, computed_point, bn_ctx + ) + != 0 + ): + raise ValueError("Invalid EC key.") evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) @@ -1329,9 +1094,10 @@ def load_elliptic_curve_public_numbers( self, numbers: ec.EllipticCurvePublicNumbers ) -> ec.EllipticCurvePublicKey: ec_cdata = self._ec_key_new_by_curve(numbers.curve) - self._ec_key_set_public_key_affine_coordinates( - ec_cdata, numbers.x, numbers.y - ) + with self._tmp_bn_ctx() as bn_ctx: + self._ec_key_set_public_key_affine_coordinates( + ec_cdata, numbers.x, numbers.y, bn_ctx + ) evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) @@ -1363,7 +1129,8 @@ def derive_elliptic_curve_private_key( ) -> ec.EllipticCurvePrivateKey: ec_cdata = self._ec_key_new_by_curve(curve) - get_func, group = self._ec_key_determine_group_get_func(ec_cdata) + group = self._lib.EC_KEY_get0_group(ec_cdata) + self.openssl_assert(group != self._ffi.NULL) point = self._lib.EC_POINT_new(group) self.openssl_assert(point != self._ffi.NULL) @@ -1381,7 +1148,9 @@ def derive_elliptic_curve_private_key( bn_x = self._lib.BN_CTX_get(bn_ctx) bn_y = self._lib.BN_CTX_get(bn_ctx) - res = get_func(group, point, bn_x, bn_y, bn_ctx) + res = self._lib.EC_POINT_get_affine_coordinates( + group, point, bn_x, bn_y, bn_ctx + ) if res != 1: self._consume_errors() raise ValueError("Unable to derive key from private_value") @@ -1436,7 +1205,7 @@ def _elliptic_curve_to_nid(self, curve: ec.EllipticCurve) -> int: curve_nid = self._lib.OBJ_sn2nid(curve_name.encode()) if curve_nid == self._lib.NID_undef: raise UnsupportedAlgorithm( - "{} is not a supported elliptic curve".format(curve.name), + f"{curve.name} is not a supported elliptic curve", _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) return curve_nid @@ -1452,35 +1221,13 @@ def _tmp_bn_ctx(self): finally: self._lib.BN_CTX_end(bn_ctx) - def _ec_key_determine_group_get_func(self, ctx): - """ - Given an EC_KEY determine the group and what function is required to - get point coordinates. - """ - self.openssl_assert(ctx != self._ffi.NULL) - - nid_two_field = self._lib.OBJ_sn2nid(b"characteristic-two-field") - self.openssl_assert(nid_two_field != self._lib.NID_undef) - - group = self._lib.EC_KEY_get0_group(ctx) - self.openssl_assert(group != self._ffi.NULL) - - method = self._lib.EC_GROUP_method_of(group) - self.openssl_assert(method != self._ffi.NULL) - - nid = self._lib.EC_METHOD_get_field_type(method) - self.openssl_assert(nid != self._lib.NID_undef) - - if nid == nid_two_field and self._lib.Cryptography_HAS_EC2M: - get_func = self._lib.EC_POINT_get_affine_coordinates_GF2m - else: - get_func = self._lib.EC_POINT_get_affine_coordinates_GFp - - assert get_func - - return get_func, group - - def _ec_key_set_public_key_affine_coordinates(self, ctx, x: int, y: int): + def _ec_key_set_public_key_affine_coordinates( + self, + ec_cdata, + x: int, + y: int, + bn_ctx, + ) -> None: """ Sets the public key point in the EC_KEY context to the affine x and y values. @@ -1493,10 +1240,19 @@ def _ec_key_set_public_key_affine_coordinates(self, ctx, x: int, y: int): x = self._ffi.gc(self._int_to_bn(x), self._lib.BN_free) y = self._ffi.gc(self._int_to_bn(y), self._lib.BN_free) - res = self._lib.EC_KEY_set_public_key_affine_coordinates(ctx, x, y) + group = self._lib.EC_KEY_get0_group(ec_cdata) + self.openssl_assert(group != self._ffi.NULL) + point = self._lib.EC_POINT_new(group) + self.openssl_assert(point != self._ffi.NULL) + point = self._ffi.gc(point, self._lib.EC_POINT_free) + res = self._lib.EC_POINT_set_affine_coordinates( + group, point, x, y, bn_ctx + ) if res != 1: self._consume_errors() raise ValueError("Invalid EC key.") + res = self._lib.EC_KEY_set_public_key(ec_cdata, point) + self.openssl_assert(res == 1) def _private_key_bytes( self, @@ -1572,14 +1328,9 @@ def _private_key_bytes( if encoding is serialization.Encoding.PEM: if key_type == self._lib.EVP_PKEY_RSA: write_bio = self._lib.PEM_write_bio_RSAPrivateKey - elif key_type == self._lib.EVP_PKEY_DSA: - write_bio = self._lib.PEM_write_bio_DSAPrivateKey - elif key_type == self._lib.EVP_PKEY_EC: - write_bio = self._lib.PEM_write_bio_ECPrivateKey else: - raise ValueError( - "Unsupported key type for TraditionalOpenSSL" - ) + assert key_type == self._lib.EVP_PKEY_EC + write_bio = self._lib.PEM_write_bio_ECPrivateKey return self._private_key_bytes_via_bio( write_bio, cdata, password ) @@ -1592,14 +1343,9 @@ def _private_key_bytes( ) if key_type == self._lib.EVP_PKEY_RSA: write_bio = self._lib.i2d_RSAPrivateKey_bio - elif key_type == self._lib.EVP_PKEY_EC: - write_bio = self._lib.i2d_ECPrivateKey_bio - elif key_type == self._lib.EVP_PKEY_DSA: - write_bio = self._lib.i2d_DSAPrivateKey_bio else: - raise ValueError( - "Unsupported key type for TraditionalOpenSSL" - ) + assert key_type == self._lib.EVP_PKEY_EC + write_bio = self._lib.i2d_ECPrivateKey_bio return self._bio_func_output(write_bio, cdata) raise ValueError("Unsupported encoding for TraditionalOpenSSL") @@ -1620,7 +1366,9 @@ def _private_key_bytes( # like Raw. raise ValueError("format is invalid with this key") - def _private_key_bytes_via_bio(self, write_bio, evp_pkey, password): + def _private_key_bytes_via_bio( + self, write_bio, evp_pkey, password + ) -> bytes: if not password: evp_cipher = self._ffi.NULL else: @@ -1637,7 +1385,7 @@ def _private_key_bytes_via_bio(self, write_bio, evp_pkey, password): self._ffi.NULL, ) - def _bio_func_output(self, write_bio, *args): + def _bio_func_output(self, write_bio, *args) -> bytes: bio = self._create_mem_bio_gc() res = write_bio(bio, *args) self.openssl_assert(res == 1) @@ -1704,46 +1452,12 @@ def dh_supported(self) -> bool: def generate_dh_parameters( self, generator: int, key_size: int ) -> dh.DHParameters: - if key_size < dh._MIN_MODULUS_SIZE: - raise ValueError( - "DH key_size must be at least {} bits".format( - dh._MIN_MODULUS_SIZE - ) - ) - - if generator not in (2, 5): - raise ValueError("DH generator must be 2 or 5") - - dh_param_cdata = self._lib.DH_new() - self.openssl_assert(dh_param_cdata != self._ffi.NULL) - dh_param_cdata = self._ffi.gc(dh_param_cdata, self._lib.DH_free) - - res = self._lib.DH_generate_parameters_ex( - dh_param_cdata, key_size, generator, self._ffi.NULL - ) - self.openssl_assert(res == 1) - - return _DHParameters(self, dh_param_cdata) - - def _dh_cdata_to_evp_pkey(self, dh_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_DH(evp_pkey, dh_cdata) - self.openssl_assert(res == 1) - return evp_pkey + return rust_openssl.dh.generate_parameters(generator, key_size) def generate_dh_private_key( self, parameters: dh.DHParameters ) -> dh.DHPrivateKey: - dh_key_cdata = _dh_params_dup( - parameters._dh_cdata, self # type: ignore[attr-defined] - ) - - res = self._lib.DH_generate_key(dh_key_cdata) - self.openssl_assert(res == 1) - - evp_pkey = self._dh_cdata_to_evp_pkey(dh_key_cdata) - - return _DHPrivateKey(self, dh_key_cdata, evp_pkey) + return parameters.generate_private_key() def generate_dh_private_key_and_parameters( self, generator: int, key_size: int @@ -1755,376 +1469,103 @@ def generate_dh_private_key_and_parameters( def load_dh_private_numbers( self, numbers: dh.DHPrivateNumbers ) -> dh.DHPrivateKey: - parameter_numbers = numbers.public_numbers.parameter_numbers - - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - - p = self._int_to_bn(parameter_numbers.p) - g = self._int_to_bn(parameter_numbers.g) - - if parameter_numbers.q is not None: - q = self._int_to_bn(parameter_numbers.q) - else: - q = self._ffi.NULL - - pub_key = self._int_to_bn(numbers.public_numbers.y) - priv_key = self._int_to_bn(numbers.x) - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - res = self._lib.DH_set0_key(dh_cdata, pub_key, priv_key) - self.openssl_assert(res == 1) - - codes = self._ffi.new("int[]", 1) - res = self._lib.Cryptography_DH_check(dh_cdata, codes) - self.openssl_assert(res == 1) - - # DH_check will return DH_NOT_SUITABLE_GENERATOR if p % 24 does not - # equal 11 when the generator is 2 (a quadratic nonresidue). - # We want to ignore that error because p % 24 == 23 is also fine. - # Specifically, g is then a quadratic residue. Within the context of - # Diffie-Hellman this means it can only generate half the possible - # values. That sounds bad, but quadratic nonresidues leak a bit of - # the key to the attacker in exchange for having the full key space - # available. See: https://crypto.stackexchange.com/questions/12961 - if codes[0] != 0 and not ( - parameter_numbers.g == 2 - and codes[0] ^ self._lib.DH_NOT_SUITABLE_GENERATOR == 0 - ): - raise ValueError("DH private numbers did not pass safety checks.") - - evp_pkey = self._dh_cdata_to_evp_pkey(dh_cdata) - - return _DHPrivateKey(self, dh_cdata, evp_pkey) + return rust_openssl.dh.from_private_numbers(numbers) def load_dh_public_numbers( self, numbers: dh.DHPublicNumbers ) -> dh.DHPublicKey: - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - - parameter_numbers = numbers.parameter_numbers - - p = self._int_to_bn(parameter_numbers.p) - g = self._int_to_bn(parameter_numbers.g) - - if parameter_numbers.q is not None: - q = self._int_to_bn(parameter_numbers.q) - else: - q = self._ffi.NULL - - pub_key = self._int_to_bn(numbers.y) - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - res = self._lib.DH_set0_key(dh_cdata, pub_key, self._ffi.NULL) - self.openssl_assert(res == 1) - - evp_pkey = self._dh_cdata_to_evp_pkey(dh_cdata) - - return _DHPublicKey(self, dh_cdata, evp_pkey) + return rust_openssl.dh.from_public_numbers(numbers) def load_dh_parameter_numbers( self, numbers: dh.DHParameterNumbers ) -> dh.DHParameters: - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - - p = self._int_to_bn(numbers.p) - g = self._int_to_bn(numbers.g) - - if numbers.q is not None: - q = self._int_to_bn(numbers.q) - else: - q = self._ffi.NULL - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - return _DHParameters(self, dh_cdata) + return rust_openssl.dh.from_parameter_numbers(numbers) def dh_parameters_supported( self, p: int, g: int, q: typing.Optional[int] = None ) -> bool: - dh_cdata = self._lib.DH_new() - self.openssl_assert(dh_cdata != self._ffi.NULL) - dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) - - p = self._int_to_bn(p) - g = self._int_to_bn(g) - - if q is not None: - q = self._int_to_bn(q) + try: + rust_openssl.dh.from_parameter_numbers( + dh.DHParameterNumbers(p=p, g=g, q=q) + ) + except ValueError: + return False else: - q = self._ffi.NULL - - res = self._lib.DH_set0_pqg(dh_cdata, p, q, g) - self.openssl_assert(res == 1) - - codes = self._ffi.new("int[]", 1) - res = self._lib.Cryptography_DH_check(dh_cdata, codes) - self.openssl_assert(res == 1) - - return codes[0] == 0 + return True def dh_x942_serialization_supported(self) -> bool: return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 def x25519_load_public_bytes(self, data: bytes) -> x25519.X25519PublicKey: - # When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 we can - # switch this to EVP_PKEY_new_raw_public_key - if len(data) != 32: - raise ValueError("An X25519 public key is 32 bytes long") - - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set_type(evp_pkey, self._lib.NID_X25519) - self.openssl_assert(res == 1) - res = self._lib.EVP_PKEY_set1_tls_encodedpoint( - evp_pkey, data, len(data) - ) - self.openssl_assert(res == 1) - return _X25519PublicKey(self, evp_pkey) + return rust_openssl.x25519.from_public_bytes(data) def x25519_load_private_bytes( self, data: bytes ) -> x25519.X25519PrivateKey: - # When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 we can - # switch this to EVP_PKEY_new_raw_private_key and drop the - # zeroed_bytearray garbage. - # OpenSSL only has facilities for loading PKCS8 formatted private - # keys using the algorithm identifiers specified in - # https://tools.ietf.org/html/draft-ietf-curdle-pkix-09. - # This is the standard PKCS8 prefix for a 32 byte X25519 key. - # The form is: - # 0:d=0 hl=2 l= 46 cons: SEQUENCE - # 2:d=1 hl=2 l= 1 prim: INTEGER :00 - # 5:d=1 hl=2 l= 5 cons: SEQUENCE - # 7:d=2 hl=2 l= 3 prim: OBJECT :1.3.101.110 - # 12:d=1 hl=2 l= 34 prim: OCTET STRING (the key) - # Of course there's a bit more complexity. In reality OCTET STRING - # contains an OCTET STRING of length 32! So the last two bytes here - # are \x04\x20, which is an OCTET STRING of length 32. - if len(data) != 32: - raise ValueError("An X25519 private key is 32 bytes long") - - pkcs8_prefix = b'0.\x02\x01\x000\x05\x06\x03+en\x04"\x04 ' - with self._zeroed_bytearray(48) as ba: - ba[0:16] = pkcs8_prefix - ba[16:] = data - bio = self._bytes_to_bio(ba) - evp_pkey = self._lib.d2i_PrivateKey_bio(bio.bio, self._ffi.NULL) - - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - self.openssl_assert( - self._lib.EVP_PKEY_id(evp_pkey) == self._lib.EVP_PKEY_X25519 - ) - return _X25519PrivateKey(self, evp_pkey) - - def _evp_pkey_keygen_gc(self, nid): - evp_pkey_ctx = self._lib.EVP_PKEY_CTX_new_id(nid, self._ffi.NULL) - self.openssl_assert(evp_pkey_ctx != self._ffi.NULL) - evp_pkey_ctx = self._ffi.gc(evp_pkey_ctx, self._lib.EVP_PKEY_CTX_free) - res = self._lib.EVP_PKEY_keygen_init(evp_pkey_ctx) - self.openssl_assert(res == 1) - evp_ppkey = self._ffi.new("EVP_PKEY **") - res = self._lib.EVP_PKEY_keygen(evp_pkey_ctx, evp_ppkey) - self.openssl_assert(res == 1) - self.openssl_assert(evp_ppkey[0] != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_ppkey[0], self._lib.EVP_PKEY_free) - return evp_pkey + return rust_openssl.x25519.from_private_bytes(data) def x25519_generate_key(self) -> x25519.X25519PrivateKey: - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_X25519) - return _X25519PrivateKey(self, evp_pkey) + return rust_openssl.x25519.generate_key() def x25519_supported(self) -> bool: if self._fips_enabled: return False - return not self._lib.CRYPTOGRAPHY_IS_LIBRESSL + return not self._lib.CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 def x448_load_public_bytes(self, data: bytes) -> x448.X448PublicKey: - if len(data) != 56: - raise ValueError("An X448 public key is 56 bytes long") - - evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( - self._lib.NID_X448, self._ffi.NULL, data, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return _X448PublicKey(self, evp_pkey) + return rust_openssl.x448.from_public_bytes(data) def x448_load_private_bytes(self, data: bytes) -> x448.X448PrivateKey: - if len(data) != 56: - raise ValueError("An X448 private key is 56 bytes long") - - data_ptr = self._ffi.from_buffer(data) - evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( - self._lib.NID_X448, self._ffi.NULL, data_ptr, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return _X448PrivateKey(self, evp_pkey) + return rust_openssl.x448.from_private_bytes(data) def x448_generate_key(self) -> x448.X448PrivateKey: - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_X448) - return _X448PrivateKey(self, evp_pkey) + return rust_openssl.x448.generate_key() def x448_supported(self) -> bool: if self._fips_enabled: return False return ( - not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 + not self._lib.CRYPTOGRAPHY_IS_LIBRESSL and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL ) def ed25519_supported(self) -> bool: if self._fips_enabled: return False - return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B + return self._lib.CRYPTOGRAPHY_HAS_WORKING_ED25519 def ed25519_load_public_bytes( self, data: bytes ) -> ed25519.Ed25519PublicKey: - utils._check_bytes("data", data) - - if len(data) != ed25519._ED25519_KEY_SIZE: - raise ValueError("An Ed25519 public key is 32 bytes long") - - evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( - self._lib.NID_ED25519, self._ffi.NULL, data, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - return _Ed25519PublicKey(self, evp_pkey) + return rust_openssl.ed25519.from_public_bytes(data) def ed25519_load_private_bytes( self, data: bytes ) -> ed25519.Ed25519PrivateKey: - if len(data) != ed25519._ED25519_KEY_SIZE: - raise ValueError("An Ed25519 private key is 32 bytes long") - - utils._check_byteslike("data", data) - data_ptr = self._ffi.from_buffer(data) - evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( - self._lib.NID_ED25519, self._ffi.NULL, data_ptr, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - return _Ed25519PrivateKey(self, evp_pkey) + return rust_openssl.ed25519.from_private_bytes(data) def ed25519_generate_key(self) -> ed25519.Ed25519PrivateKey: - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED25519) - return _Ed25519PrivateKey(self, evp_pkey) + return rust_openssl.ed25519.generate_key() def ed448_supported(self) -> bool: if self._fips_enabled: return False return ( - not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B + not self._lib.CRYPTOGRAPHY_IS_LIBRESSL and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL ) def ed448_load_public_bytes(self, data: bytes) -> ed448.Ed448PublicKey: - utils._check_bytes("data", data) - if len(data) != _ED448_KEY_SIZE: - raise ValueError("An Ed448 public key is 57 bytes long") - - evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( - self._lib.NID_ED448, self._ffi.NULL, data, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - return _Ed448PublicKey(self, evp_pkey) + return rust_openssl.ed448.from_public_bytes(data) def ed448_load_private_bytes(self, data: bytes) -> ed448.Ed448PrivateKey: - utils._check_byteslike("data", data) - if len(data) != _ED448_KEY_SIZE: - raise ValueError("An Ed448 private key is 57 bytes long") - - data_ptr = self._ffi.from_buffer(data) - evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( - self._lib.NID_ED448, self._ffi.NULL, data_ptr, len(data) - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - return _Ed448PrivateKey(self, evp_pkey) + return rust_openssl.ed448.from_private_bytes(data) def ed448_generate_key(self) -> ed448.Ed448PrivateKey: - evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED448) - return _Ed448PrivateKey(self, evp_pkey) - - def derive_scrypt( - self, - key_material: bytes, - salt: bytes, - length: int, - n: int, - r: int, - p: int, - ) -> bytes: - buf = self._ffi.new("unsigned char[]", length) - key_material_ptr = self._ffi.from_buffer(key_material) - res = self._lib.EVP_PBE_scrypt( - key_material_ptr, - len(key_material), - salt, - len(salt), - n, - r, - p, - scrypt._MEM_LIMIT, - buf, - length, - ) - if res != 1: - errors = self._consume_errors_with_text() - # memory required formula explained here: - # https://blog.filippo.io/the-scrypt-parameters/ - min_memory = 128 * n * r // (1024**2) - raise MemoryError( - "Not enough memory to derive key. These parameters require" - " {} MB of memory.".format(min_memory), - errors, - ) - return self._ffi.buffer(buf)[:] + return rust_openssl.ed448.generate_key() def aead_cipher_supported(self, cipher) -> bool: - cipher_name = aead._aead_cipher_name(cipher) - if self._fips_enabled and cipher_name not in self._fips_aead: - return False - # SIV isn't loaded through get_cipherbyname but instead a new fetch API - # only available in 3.0+. But if we know we're on 3.0+ then we know - # it's supported. - if cipher_name.endswith(b"-siv"): - return self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER == 1 - else: - return ( - self._lib.EVP_get_cipherbyname(cipher_name) != self._ffi.NULL - ) - - @contextlib.contextmanager - def _zeroed_bytearray(self, length: int) -> typing.Iterator[bytearray]: - """ - This method creates a bytearray, which we copy data into (hopefully - also from a mutable buffer that can be dynamically erased!), and then - zero when we're done. - """ - ba = bytearray(length) - try: - yield ba - finally: - self._zero_data(ba, length) + return aead._aead_cipher_supported(self, cipher) def _zero_data(self, data, length: int) -> None: # We clear things this way because at the moment we're not @@ -2159,7 +1600,7 @@ def _zeroed_null_terminated_buf(self, data): def load_key_and_certificates_from_pkcs12( self, data: bytes, password: typing.Optional[bytes] ) -> typing.Tuple[ - typing.Optional[PRIVATE_KEY_TYPES], + typing.Optional[PrivateKeyTypes], typing.Optional[x509.Certificate], typing.List[x509.Certificate], ]: @@ -2190,11 +1631,8 @@ def load_pkcs12( res = self._lib.PKCS12_parse( p12, password_buf, evp_pkey_ptr, x509_ptr, sk_x509_ptr ) - # OpenSSL 3.0.6 leaves errors on the stack even in success, so - # we consume all errors unconditionally. - # https://github.com/openssl/openssl/issues/19389 - self._consume_errors() if res == 0: + self._consume_errors() raise ValueError("Invalid password or PKCS12 data") cert = None @@ -2203,7 +1641,11 @@ def load_pkcs12( if evp_pkey_ptr[0] != self._ffi.NULL: evp_pkey = self._ffi.gc(evp_pkey_ptr[0], self._lib.EVP_PKEY_free) - key = self._evp_pkey_to_private_key(evp_pkey) + # We don't support turning off RSA key validation when loading + # PKCS12 keys + key = self._evp_pkey_to_private_key( + evp_pkey, unsafe_skip_rsa_key_validation=False + ) if x509_ptr[0] != self._ffi.NULL: x509 = self._ffi.gc(x509_ptr[0], self._lib.X509_free) @@ -2247,9 +1689,9 @@ def load_pkcs12( def serialize_key_and_certificates_to_pkcs12( self, name: typing.Optional[bytes], - key: typing.Optional[_ALLOWED_PKCS12_TYPES], + key: typing.Optional[PKCS12PrivateKeyTypes], cert: typing.Optional[x509.Certificate], - cas: typing.Optional[typing.List[_PKCS12_CAS_TYPES]], + cas: typing.Optional[typing.List[_PKCS12CATypes]], encryption_algorithm: serialization.KeySerializationEncryption, ) -> bytes: password = None @@ -2345,13 +1787,15 @@ def serialize_key_and_certificates_to_pkcs12( if isinstance(ca, PKCS12Certificate): ca_alias = ca.friendly_name ossl_ca = self._cert2ossl(ca.certificate) - with self._zeroed_null_terminated_buf( - ca_alias - ) as ca_name_buf: + if ca_alias is None: res = self._lib.X509_alias_set1( - ossl_ca, ca_name_buf, -1 + ossl_ca, self._ffi.NULL, -1 ) - self.openssl_assert(res == 1) + else: + res = self._lib.X509_alias_set1( + ossl_ca, ca_alias, len(ca_alias) + ) + self.openssl_assert(res == 1) else: ossl_ca = self._cert2ossl(ca) ossl_cas.append(ossl_ca) @@ -2361,15 +1805,14 @@ def serialize_key_and_certificates_to_pkcs12( with self._zeroed_null_terminated_buf(password) as password_buf: with self._zeroed_null_terminated_buf(name) as name_buf: ossl_cert = self._cert2ossl(cert) if cert else self._ffi.NULL - if key is not None: - evp_pkey = key._evp_pkey # type: ignore[union-attr] - else: - evp_pkey = self._ffi.NULL + ossl_pkey = ( + self._key2ossl(key) if key is not None else self._ffi.NULL + ) p12 = self._lib.PKCS12_create( password_buf, name_buf, - evp_pkey, + ossl_pkey, ossl_cert, sk_x509, nid_key, @@ -2406,13 +1849,6 @@ def poly1305_supported(self) -> bool: return False return self._lib.Cryptography_HAS_POLY1305 == 1 - def create_poly1305_ctx(self, key: bytes) -> _Poly1305Context: - utils._check_byteslike("key", key) - if len(key) != _POLY1305_KEY_SIZE: - raise ValueError("A poly1305 key is 32 bytes long") - - return _Poly1305Context(self, key) - def pkcs7_supported(self) -> bool: return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL @@ -2444,7 +1880,7 @@ def load_der_pkcs7_certificates( p7 = self._ffi.gc(p7, self._lib.PKCS7_free) return self._load_pkcs7_certificates(p7) - def _load_pkcs7_certificates(self, p7): + def _load_pkcs7_certificates(self, p7) -> typing.List[x509.Certificate]: nid = self._lib.OBJ_obj2nid(p7.type) self.openssl_assert(nid != self._lib.NID_undef) if nid != self._lib.NID_pkcs7_signed: @@ -2454,167 +1890,20 @@ def _load_pkcs7_certificates(self, p7): _Reasons.UNSUPPORTED_SERIALIZATION, ) + certs: list[x509.Certificate] = [] + if p7.d.sign == self._ffi.NULL: + return certs + sk_x509 = p7.d.sign.cert num = self._lib.sk_X509_num(sk_x509) - certs = [] for i in range(num): x509 = self._lib.sk_X509_value(sk_x509, i) self.openssl_assert(x509 != self._ffi.NULL) - res = self._lib.X509_up_ref(x509) - # When OpenSSL is less than 1.1.0 up_ref returns the current - # refcount. On 1.1.0+ it returns 1 for success. - self.openssl_assert(res >= 1) - x509 = self._ffi.gc(x509, self._lib.X509_free) cert = self._ossl2cert(x509) certs.append(cert) return certs - def pkcs7_serialize_certificates( - self, - certs: typing.List[x509.Certificate], - encoding: serialization.Encoding, - ): - certs = list(certs) - if not certs or not all( - isinstance(cert, x509.Certificate) for cert in certs - ): - raise TypeError("certs must be a list of certs with length >= 1") - - if encoding not in ( - serialization.Encoding.PEM, - serialization.Encoding.DER, - ): - raise TypeError("encoding must DER or PEM from the Encoding enum") - - certs_sk = self._lib.sk_X509_new_null() - certs_sk = self._ffi.gc(certs_sk, self._lib.sk_X509_free) - # This list is to keep the x509 values alive until end of function - ossl_certs = [] - for cert in certs: - ossl_cert = self._cert2ossl(cert) - ossl_certs.append(ossl_cert) - res = self._lib.sk_X509_push(certs_sk, ossl_cert) - self.openssl_assert(res >= 1) - # We use PKCS7_sign here because it creates the PKCS7 and PKCS7_SIGNED - # structures for us rather than requiring manual assignment. - p7 = self._lib.PKCS7_sign( - self._ffi.NULL, - self._ffi.NULL, - certs_sk, - self._ffi.NULL, - self._lib.PKCS7_PARTIAL, - ) - bio_out = self._create_mem_bio_gc() - if encoding is serialization.Encoding.PEM: - res = self._lib.PEM_write_bio_PKCS7_stream( - bio_out, p7, self._ffi.NULL, 0 - ) - else: - assert encoding is serialization.Encoding.DER - res = self._lib.i2d_PKCS7_bio(bio_out, p7) - - self.openssl_assert(res == 1) - return self._read_mem_bio(bio_out) - - def pkcs7_sign( - self, - builder: pkcs7.PKCS7SignatureBuilder, - encoding: serialization.Encoding, - options: typing.List[pkcs7.PKCS7Options], - ) -> bytes: - assert builder._data is not None - bio = self._bytes_to_bio(builder._data) - init_flags = self._lib.PKCS7_PARTIAL - final_flags = 0 - - if len(builder._additional_certs) == 0: - certs = self._ffi.NULL - else: - certs = self._lib.sk_X509_new_null() - certs = self._ffi.gc(certs, self._lib.sk_X509_free) - # This list is to keep the x509 values alive until end of function - ossl_certs = [] - for cert in builder._additional_certs: - ossl_cert = self._cert2ossl(cert) - ossl_certs.append(ossl_cert) - res = self._lib.sk_X509_push(certs, ossl_cert) - self.openssl_assert(res >= 1) - - if pkcs7.PKCS7Options.DetachedSignature in options: - # Don't embed the data in the PKCS7 structure - init_flags |= self._lib.PKCS7_DETACHED - final_flags |= self._lib.PKCS7_DETACHED - - # This just inits a structure for us. However, there - # are flags we need to set, joy. - p7 = self._lib.PKCS7_sign( - self._ffi.NULL, - self._ffi.NULL, - certs, - self._ffi.NULL, - init_flags, - ) - self.openssl_assert(p7 != self._ffi.NULL) - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - signer_flags = 0 - # These flags are configurable on a per-signature basis - # but we've deliberately chosen to make the API only allow - # setting it across all signatures for now. - if pkcs7.PKCS7Options.NoCapabilities in options: - signer_flags |= self._lib.PKCS7_NOSMIMECAP - elif pkcs7.PKCS7Options.NoAttributes in options: - signer_flags |= self._lib.PKCS7_NOATTR - - if pkcs7.PKCS7Options.NoCerts in options: - signer_flags |= self._lib.PKCS7_NOCERTS - - for certificate, private_key, hash_algorithm in builder._signers: - ossl_cert = self._cert2ossl(certificate) - md = self._evp_md_non_null_from_algorithm(hash_algorithm) - p7signerinfo = self._lib.PKCS7_sign_add_signer( - p7, - ossl_cert, - private_key._evp_pkey, # type: ignore[union-attr] - md, - signer_flags, - ) - self.openssl_assert(p7signerinfo != self._ffi.NULL) - - for option in options: - # DetachedSignature, NoCapabilities, and NoAttributes are already - # handled so we just need to check these last two options. - if option is pkcs7.PKCS7Options.Text: - final_flags |= self._lib.PKCS7_TEXT - elif option is pkcs7.PKCS7Options.Binary: - final_flags |= self._lib.PKCS7_BINARY - - bio_out = self._create_mem_bio_gc() - if encoding is serialization.Encoding.SMIME: - # This finalizes the structure - res = self._lib.SMIME_write_PKCS7( - bio_out, p7, bio.bio, final_flags - ) - elif encoding is serialization.Encoding.PEM: - res = self._lib.PKCS7_final(p7, bio.bio, final_flags) - self.openssl_assert(res == 1) - res = self._lib.PEM_write_bio_PKCS7_stream( - bio_out, p7, bio.bio, final_flags - ) - else: - assert encoding is serialization.Encoding.DER - # We need to call finalize here becauase i2d_PKCS7_bio does not - # finalize. - res = self._lib.PKCS7_final(p7, bio.bio, final_flags) - self.openssl_assert(res == 1) - # OpenSSL 3.0 leaves a random bio error on the stack: - # https://github.com/openssl/openssl/issues/16681 - if self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - self._consume_errors() - res = self._lib.i2d_PKCS7_bio(bio_out, p7) - self.openssl_assert(res == 1) - return self._read_mem_bio(bio_out) - class GetCipherByName: def __init__(self, fmt: str): @@ -2642,7 +1931,7 @@ def __call__(self, backend: Backend, cipher: CipherAlgorithm, mode: Mode): def _get_xts_cipher(backend: Backend, cipher: AES, mode): - cipher_name = "aes-{}-xts".format(cipher.key_size // 2) + cipher_name = f"aes-{cipher.key_size // 2}-xts" return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py index 1058de9..bc42adb 100644 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -2,13 +2,14 @@ # 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.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.primitives.ciphers import algorithms, modes - if typing.TYPE_CHECKING: from cryptography.hazmat.backends.openssl.backend import Backend @@ -18,9 +19,7 @@ class _CipherContext: _DECRYPT = 0 _MAX_CHUNK_SIZE = 2**30 - 1 - def __init__( - self, backend: "Backend", cipher, mode, operation: int - ) -> None: + def __init__(self, backend: Backend, cipher, mode, operation: int) -> None: self._backend = backend self._cipher = cipher self._mode = mode @@ -51,9 +50,9 @@ def __init__( evp_cipher = adapter(self._backend, cipher, mode) if evp_cipher == self._backend._ffi.NULL: - msg = "cipher {0.name} ".format(cipher) + msg = f"cipher {cipher.name} " if mode is not None: - msg += "in {0.name} mode ".format(mode) + msg += f"in {mode.name} mode " msg += ( "is not supported by this backend (Your version of OpenSSL " "may be too old. Current version: {}.)" @@ -120,7 +119,7 @@ def __init__( lib = self._backend._lib if res == 0 and ( ( - lib.CRYPTOGRAPHY_OPENSSL_111D_OR_GREATER + not lib.CRYPTOGRAPHY_IS_LIBRESSL and errors[0]._lib_reason_match( lib.ERR_LIB_EVP, lib.EVP_R_XTS_DUPLICATED_KEYS ) @@ -157,7 +156,7 @@ def update_into(self, data: bytes, buf: bytes) -> int: data_processed = 0 total_out = 0 outlen = self._backend._ffi.new("int *") - baseoutbuf = self._backend._ffi.from_buffer(buf) + baseoutbuf = self._backend._ffi.from_buffer(buf, require_writable=True) baseinbuf = self._backend._ffi.from_buffer(data) while data_processed != total_data_len: diff --git a/src/cryptography/hazmat/backends/openssl/cmac.py b/src/cryptography/hazmat/backends/openssl/cmac.py index 35f50c5..bdd7fec 100644 --- a/src/cryptography/hazmat/backends/openssl/cmac.py +++ b/src/cryptography/hazmat/backends/openssl/cmac.py @@ -2,6 +2,8 @@ # 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.exceptions import ( @@ -13,15 +15,15 @@ from cryptography.hazmat.primitives.ciphers.modes import CBC if typing.TYPE_CHECKING: - from cryptography.hazmat.primitives import ciphers from cryptography.hazmat.backends.openssl.backend import Backend + from cryptography.hazmat.primitives import ciphers class _CMACContext: def __init__( self, - backend: "Backend", - algorithm: "ciphers.BlockCipherAlgorithm", + backend: Backend, + algorithm: ciphers.BlockCipherAlgorithm, ctx=None, ) -> None: if not backend.cmac_algorithm_supported(algorithm): @@ -72,7 +74,7 @@ def finalize(self) -> bytes: return self._backend._ffi.buffer(buf)[:] - def copy(self) -> "_CMACContext": + def copy(self) -> _CMACContext: copied_ctx = self._backend._lib.CMAC_CTX_new() copied_ctx = self._backend._ffi.gc( copied_ctx, self._backend._lib.CMAC_CTX_free diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index df91d6d..bf123b6 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -2,6 +2,7 @@ # 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 x509 diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py deleted file mode 100644 index 70364a3..0000000 --- a/src/cryptography/hazmat/backends/openssl/dh.py +++ /dev/null @@ -1,318 +0,0 @@ -# This file is dual licensed under the terms 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.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import dh - - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _dh_params_dup(dh_cdata, backend: "Backend"): - lib = backend._lib - ffi = backend._ffi - - param_cdata = lib.DHparams_dup(dh_cdata) - backend.openssl_assert(param_cdata != ffi.NULL) - param_cdata = ffi.gc(param_cdata, lib.DH_free) - if lib.CRYPTOGRAPHY_IS_LIBRESSL: - # In libressl DHparams_dup don't copy q - q = ffi.new("BIGNUM **") - lib.DH_get0_pqg(dh_cdata, ffi.NULL, q, ffi.NULL) - q_dup = lib.BN_dup(q[0]) - res = lib.DH_set0_pqg(param_cdata, ffi.NULL, q_dup, ffi.NULL) - backend.openssl_assert(res == 1) - - return param_cdata - - -def _dh_cdata_to_parameters(dh_cdata, backend: "Backend") -> "_DHParameters": - param_cdata = _dh_params_dup(dh_cdata, backend) - return _DHParameters(backend, param_cdata) - - -class _DHParameters(dh.DHParameters): - def __init__(self, backend: "Backend", dh_cdata): - self._backend = backend - self._dh_cdata = dh_cdata - - def parameter_numbers(self) -> dh.DHParameterNumbers: - p = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - q_val: typing.Optional[int] - if q[0] == self._backend._ffi.NULL: - q_val = None - else: - q_val = self._backend._bn_to_int(q[0]) - return dh.DHParameterNumbers( - p=self._backend._bn_to_int(p[0]), - g=self._backend._bn_to_int(g[0]), - q=q_val, - ) - - def generate_private_key(self) -> dh.DHPrivateKey: - return self._backend.generate_dh_private_key(self) - - def parameter_bytes( - self, - encoding: serialization.Encoding, - format: serialization.ParameterFormat, - ) -> bytes: - if encoding is serialization.Encoding.OpenSSH: - raise TypeError("OpenSSH encoding is not supported") - - if format is not serialization.ParameterFormat.PKCS3: - raise ValueError("Only PKCS3 serialization is supported") - - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg( - self._dh_cdata, self._backend._ffi.NULL, q, self._backend._ffi.NULL - ) - if ( - q[0] != self._backend._ffi.NULL - and not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX - ): - raise UnsupportedAlgorithm( - "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - if encoding is serialization.Encoding.PEM: - if q[0] != self._backend._ffi.NULL: - write_bio = self._backend._lib.PEM_write_bio_DHxparams - else: - write_bio = self._backend._lib.PEM_write_bio_DHparams - elif encoding is serialization.Encoding.DER: - if q[0] != self._backend._ffi.NULL: - write_bio = self._backend._lib.Cryptography_i2d_DHxparams_bio - else: - write_bio = self._backend._lib.i2d_DHparams_bio - else: - raise TypeError("encoding must be an item from the Encoding enum") - - bio = self._backend._create_mem_bio_gc() - res = write_bio(bio, self._dh_cdata) - self._backend.openssl_assert(res == 1) - return self._backend._read_mem_bio(bio) - - -def _get_dh_num_bits(backend, dh_cdata) -> int: - p = backend._ffi.new("BIGNUM **") - backend._lib.DH_get0_pqg(dh_cdata, p, backend._ffi.NULL, backend._ffi.NULL) - backend.openssl_assert(p[0] != backend._ffi.NULL) - return backend._lib.BN_num_bits(p[0]) - - -class _DHPrivateKey(dh.DHPrivateKey): - def __init__(self, backend: "Backend", dh_cdata, evp_pkey): - self._backend = backend - self._dh_cdata = dh_cdata - self._evp_pkey = evp_pkey - self._key_size_bytes = self._backend._lib.DH_size(dh_cdata) - - @property - def key_size(self) -> int: - return _get_dh_num_bits(self._backend, self._dh_cdata) - - def private_numbers(self) -> dh.DHPrivateNumbers: - p = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - if q[0] == self._backend._ffi.NULL: - q_val = None - else: - q_val = self._backend._bn_to_int(q[0]) - pub_key = self._backend._ffi.new("BIGNUM **") - priv_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key(self._dh_cdata, pub_key, priv_key) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) - return dh.DHPrivateNumbers( - public_numbers=dh.DHPublicNumbers( - parameter_numbers=dh.DHParameterNumbers( - p=self._backend._bn_to_int(p[0]), - g=self._backend._bn_to_int(g[0]), - q=q_val, - ), - y=self._backend._bn_to_int(pub_key[0]), - ), - x=self._backend._bn_to_int(priv_key[0]), - ) - - def exchange(self, peer_public_key: dh.DHPublicKey) -> bytes: - if not isinstance(peer_public_key, _DHPublicKey): - raise TypeError("peer_public_key must be a DHPublicKey") - - ctx = self._backend._lib.EVP_PKEY_CTX_new( - self._evp_pkey, self._backend._ffi.NULL - ) - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc(ctx, self._backend._lib.EVP_PKEY_CTX_free) - res = self._backend._lib.EVP_PKEY_derive_init(ctx) - self._backend.openssl_assert(res == 1) - res = self._backend._lib.EVP_PKEY_derive_set_peer( - ctx, peer_public_key._evp_pkey - ) - # Invalid kex errors here in OpenSSL 3.0 because checks were moved - # to EVP_PKEY_derive_set_peer - self._exchange_assert(res == 1) - keylen = self._backend._ffi.new("size_t *") - res = self._backend._lib.EVP_PKEY_derive( - ctx, self._backend._ffi.NULL, keylen - ) - # Invalid kex errors here in OpenSSL < 3 - self._exchange_assert(res == 1) - self._backend.openssl_assert(keylen[0] > 0) - buf = self._backend._ffi.new("unsigned char[]", keylen[0]) - res = self._backend._lib.EVP_PKEY_derive(ctx, buf, keylen) - self._backend.openssl_assert(res == 1) - - key = self._backend._ffi.buffer(buf, keylen[0])[:] - pad = self._key_size_bytes - len(key) - - if pad > 0: - key = (b"\x00" * pad) + key - - return key - - def _exchange_assert(self, ok: bool) -> None: - if not ok: - errors_with_text = self._backend._consume_errors_with_text() - raise ValueError( - "Error computing shared key.", - errors_with_text, - ) - - def public_key(self) -> dh.DHPublicKey: - dh_cdata = _dh_params_dup(self._dh_cdata, self._backend) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key( - self._dh_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) - self._backend.openssl_assert(pub_key_dup != self._backend._ffi.NULL) - - res = self._backend._lib.DH_set0_key( - dh_cdata, pub_key_dup, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res == 1) - evp_pkey = self._backend._dh_cdata_to_evp_pkey(dh_cdata) - return _DHPublicKey(self._backend, dh_cdata, evp_pkey) - - def parameters(self) -> dh.DHParameters: - return _dh_cdata_to_parameters(self._dh_cdata, self._backend) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - if format is not serialization.PrivateFormat.PKCS8: - raise ValueError( - "DH private keys support only PKCS8 serialization" - ) - if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg( - self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL, - ) - if q[0] != self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._dh_cdata, - ) - - -class _DHPublicKey(dh.DHPublicKey): - def __init__(self, backend: "Backend", dh_cdata, evp_pkey): - self._backend = backend - self._dh_cdata = dh_cdata - self._evp_pkey = evp_pkey - self._key_size_bits = _get_dh_num_bits(self._backend, self._dh_cdata) - - @property - def key_size(self) -> int: - return self._key_size_bits - - def public_numbers(self) -> dh.DHPublicNumbers: - p = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - if q[0] == self._backend._ffi.NULL: - q_val = None - else: - q_val = self._backend._bn_to_int(q[0]) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key( - self._dh_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - return dh.DHPublicNumbers( - parameter_numbers=dh.DHParameterNumbers( - p=self._backend._bn_to_int(p[0]), - g=self._backend._bn_to_int(g[0]), - q=q_val, - ), - y=self._backend._bn_to_int(pub_key[0]), - ) - - def parameters(self) -> dh.DHParameters: - return _dh_cdata_to_parameters(self._dh_cdata, self._backend) - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - if format is not serialization.PublicFormat.SubjectPublicKeyInfo: - raise ValueError( - "DH public keys support only " - "SubjectPublicKeyInfo serialization" - ) - - if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: - q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg( - self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL, - ) - if q[0] != self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) diff --git a/src/cryptography/hazmat/backends/openssl/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py deleted file mode 100644 index 8634b72..0000000 --- a/src/cryptography/hazmat/backends/openssl/dsa.py +++ /dev/null @@ -1,239 +0,0 @@ -# This file is dual licensed under the terms 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.exceptions import InvalidSignature -from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, -) -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ( - dsa, - utils as asym_utils, -) - - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _dsa_sig_sign( - backend: "Backend", private_key: "_DSAPrivateKey", data: bytes -) -> bytes: - sig_buf_len = backend._lib.DSA_size(private_key._dsa_cdata) - sig_buf = backend._ffi.new("unsigned char[]", sig_buf_len) - buflen = backend._ffi.new("unsigned int *") - - # The first parameter passed to DSA_sign is unused by OpenSSL but - # must be an integer. - res = backend._lib.DSA_sign( - 0, data, len(data), sig_buf, buflen, private_key._dsa_cdata - ) - backend.openssl_assert(res == 1) - backend.openssl_assert(buflen[0]) - - return backend._ffi.buffer(sig_buf)[: buflen[0]] - - -def _dsa_sig_verify( - backend: "Backend", - public_key: "_DSAPublicKey", - signature: bytes, - data: bytes, -) -> None: - # The first parameter passed to DSA_verify is unused by OpenSSL but - # must be an integer. - res = backend._lib.DSA_verify( - 0, data, len(data), signature, len(signature), public_key._dsa_cdata - ) - - if res != 1: - backend._consume_errors() - raise InvalidSignature - - -class _DSAParameters(dsa.DSAParameters): - def __init__(self, backend: "Backend", dsa_cdata): - self._backend = backend - self._dsa_cdata = dsa_cdata - - def parameter_numbers(self) -> dsa.DSAParameterNumbers: - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - return dsa.DSAParameterNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]), - ) - - def generate_private_key(self) -> dsa.DSAPrivateKey: - return self._backend.generate_dsa_private_key(self) - - -class _DSAPrivateKey(dsa.DSAPrivateKey): - _key_size: int - - def __init__(self, backend: "Backend", dsa_cdata, evp_pkey): - self._backend = backend - self._dsa_cdata = dsa_cdata - self._evp_pkey = evp_pkey - - p = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg( - dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL - ) - self._backend.openssl_assert(p[0] != backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(p[0]) - - @property - def key_size(self) -> int: - return self._key_size - - def private_numbers(self) -> dsa.DSAPrivateNumbers: - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - pub_key = self._backend._ffi.new("BIGNUM **") - priv_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - self._backend._lib.DSA_get0_key(self._dsa_cdata, pub_key, priv_key) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) - return dsa.DSAPrivateNumbers( - public_numbers=dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]), - ), - y=self._backend._bn_to_int(pub_key[0]), - ), - x=self._backend._bn_to_int(priv_key[0]), - ) - - def public_key(self) -> dsa.DSAPublicKey: - dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) - self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL) - dsa_cdata = self._backend._ffi.gc( - dsa_cdata, self._backend._lib.DSA_free - ) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_key( - self._dsa_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) - res = self._backend._lib.DSA_set0_key( - dsa_cdata, pub_key_dup, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res == 1) - evp_pkey = self._backend._dsa_cdata_to_evp_pkey(dsa_cdata) - return _DSAPublicKey(self._backend, dsa_cdata, evp_pkey) - - def parameters(self) -> dsa.DSAParameters: - dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) - self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL) - dsa_cdata = self._backend._ffi.gc( - dsa_cdata, self._backend._lib.DSA_free - ) - return _DSAParameters(self._backend, dsa_cdata) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._dsa_cdata, - ) - - def sign( - self, - data: bytes, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], - ) -> bytes: - data, _ = _calculate_digest_and_algorithm(data, algorithm) - return _dsa_sig_sign(self._backend, self, data) - - -class _DSAPublicKey(dsa.DSAPublicKey): - _key_size: int - - def __init__(self, backend: "Backend", dsa_cdata, evp_pkey): - self._backend = backend - self._dsa_cdata = dsa_cdata - self._evp_pkey = evp_pkey - p = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg( - dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL - ) - self._backend.openssl_assert(p[0] != backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(p[0]) - - @property - def key_size(self) -> int: - return self._key_size - - def public_numbers(self) -> dsa.DSAPublicNumbers: - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - g = self._backend._ffi.new("BIGNUM **") - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) - self._backend._lib.DSA_get0_key( - self._dsa_cdata, pub_key, self._backend._ffi.NULL - ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - return dsa.DSAPublicNumbers( - parameter_numbers=dsa.DSAParameterNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]), - ), - y=self._backend._bn_to_int(pub_key[0]), - ) - - def parameters(self) -> dsa.DSAParameters: - dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) - dsa_cdata = self._backend._ffi.gc( - dsa_cdata, self._backend._lib.DSA_free - ) - return _DSAParameters(self._backend, dsa_cdata) - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def verify( - self, - signature: bytes, - data: bytes, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], - ) -> None: - data, _ = _calculate_digest_and_algorithm(data, algorithm) - return _dsa_sig_verify(self._backend, self, signature, data) diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py index 9bc6dd3..9821bd1 100644 --- a/src/cryptography/hazmat/backends/openssl/ec.py +++ b/src/cryptography/hazmat/backends/openssl/ec.py @@ -2,6 +2,8 @@ # 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.exceptions import ( @@ -30,7 +32,7 @@ def _check_signature_algorithm( ) -def _ec_key_curve_sn(backend: "Backend", ec_key) -> str: +def _ec_key_curve_sn(backend: Backend, ec_key) -> str: group = backend._lib.EC_KEY_get0_group(ec_key) backend.openssl_assert(group != backend._ffi.NULL) @@ -60,7 +62,7 @@ def _ec_key_curve_sn(backend: "Backend", ec_key) -> str: return sn -def _mark_asn1_named_ec_curve(backend: "Backend", ec_cdata): +def _mark_asn1_named_ec_curve(backend: Backend, ec_cdata): """ Set the named curve flag on the EC_KEY. This causes OpenSSL to serialize EC keys along with their curve OID which makes @@ -72,7 +74,7 @@ def _mark_asn1_named_ec_curve(backend: "Backend", ec_cdata): ) -def _check_key_infinity(backend: "Backend", ec_cdata) -> None: +def _check_key_infinity(backend: Backend, ec_cdata) -> None: point = backend._lib.EC_KEY_get0_public_key(ec_cdata) backend.openssl_assert(point != backend._ffi.NULL) group = backend._lib.EC_KEY_get0_group(ec_cdata) @@ -83,18 +85,18 @@ def _check_key_infinity(backend: "Backend", ec_cdata) -> None: ) -def _sn_to_elliptic_curve(backend: "Backend", sn: str) -> ec.EllipticCurve: +def _sn_to_elliptic_curve(backend: Backend, sn: str) -> ec.EllipticCurve: try: return ec._CURVE_TYPES[sn]() except KeyError: raise UnsupportedAlgorithm( - "{} is not a supported elliptic curve".format(sn), + f"{sn} is not a supported elliptic curve", _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) def _ecdsa_sig_sign( - backend: "Backend", private_key: "_EllipticCurvePrivateKey", data: bytes + backend: Backend, private_key: _EllipticCurvePrivateKey, data: bytes ) -> bytes: max_size = backend._lib.ECDSA_size(private_key._ec_key) backend.openssl_assert(max_size > 0) @@ -109,8 +111,8 @@ def _ecdsa_sig_sign( def _ecdsa_sig_verify( - backend: "Backend", - public_key: "_EllipticCurvePublicKey", + backend: Backend, + public_key: _EllipticCurvePublicKey, signature: bytes, data: bytes, ) -> None: @@ -123,7 +125,7 @@ def _ecdsa_sig_verify( class _EllipticCurvePrivateKey(ec.EllipticCurvePrivateKey): - def __init__(self, backend: "Backend", ec_key_cdata, evp_pkey): + def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): self._backend = backend self._ec_key = ec_key_cdata self._evp_pkey = evp_pkey @@ -215,7 +217,7 @@ def sign( class _EllipticCurvePublicKey(ec.EllipticCurvePublicKey): - def __init__(self, backend: "Backend", ec_key_cdata, evp_pkey): + def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): self._backend = backend self._ec_key = ec_key_cdata self._evp_pkey = evp_pkey @@ -233,10 +235,19 @@ def curve(self) -> ec.EllipticCurve: def key_size(self) -> int: return self.curve.key_size - def public_numbers(self) -> ec.EllipticCurvePublicNumbers: - get_func, group = self._backend._ec_key_determine_group_get_func( - self._ec_key + def __eq__(self, other: object) -> bool: + if not isinstance(other, _EllipticCurvePublicKey): + return NotImplemented + + return ( + self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) + == 1 ) + + def public_numbers(self) -> ec.EllipticCurvePublicNumbers: + group = self._backend._lib.EC_KEY_get0_group(self._ec_key) + self._backend.openssl_assert(group != self._backend._ffi.NULL) + point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) self._backend.openssl_assert(point != self._backend._ffi.NULL) @@ -244,7 +255,9 @@ def public_numbers(self) -> ec.EllipticCurvePublicNumbers: bn_x = self._backend._lib.BN_CTX_get(bn_ctx) bn_y = self._backend._lib.BN_CTX_get(bn_ctx) - res = get_func(group, point, bn_x, bn_y, bn_ctx) + res = self._backend._lib.EC_POINT_get_affine_coordinates( + group, point, bn_x, bn_y, bn_ctx + ) self._backend.openssl_assert(res == 1) x = self._backend._bn_to_int(bn_x) diff --git a/src/cryptography/hazmat/backends/openssl/ed25519.py b/src/cryptography/hazmat/backends/openssl/ed25519.py deleted file mode 100644 index 5cfdffb..0000000 --- a/src/cryptography/hazmat/backends/openssl/ed25519.py +++ /dev/null @@ -1,155 +0,0 @@ -# This file is dual licensed under the terms 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 exceptions -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.ed25519 import ( - Ed25519PrivateKey, - Ed25519PublicKey, - _ED25519_KEY_SIZE, - _ED25519_SIG_SIZE, -) - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -class _Ed25519PublicKey(Ed25519PublicKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self) -> bytes: - buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:] - - def verify(self, signature: bytes, data: bytes) -> None: - evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestVerifyInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - res = self._backend._lib.EVP_DigestVerify( - evp_md_ctx, signature, len(signature), data, len(data) - ) - if res != 1: - self._backend._consume_errors() - raise exceptions.InvalidSignature - - -class _Ed25519PrivateKey(Ed25519PrivateKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self) -> Ed25519PublicKey: - buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) - public_bytes = self._backend._ffi.buffer(buf)[:] - return self._backend.ed25519_load_public_bytes(public_bytes) - - def sign(self, data: bytes) -> bytes: - evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestSignInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - buf = self._backend._ffi.new("unsigned char[]", _ED25519_SIG_SIZE) - buflen = self._backend._ffi.new("size_t *", len(buf)) - res = self._backend._lib.EVP_DigestSign( - evp_md_ctx, buf, buflen, data, len(data) - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_SIG_SIZE) - return self._backend._ffi.buffer(buf, buflen[0])[:] - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self) -> bytes: - buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_private_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/ed448.py b/src/cryptography/hazmat/backends/openssl/ed448.py deleted file mode 100644 index dad93c6..0000000 --- a/src/cryptography/hazmat/backends/openssl/ed448.py +++ /dev/null @@ -1,156 +0,0 @@ -# This file is dual licensed under the terms 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 exceptions -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.ed448 import ( - Ed448PrivateKey, - Ed448PublicKey, -) - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - -_ED448_KEY_SIZE = 57 -_ED448_SIG_SIZE = 114 - - -class _Ed448PublicKey(Ed448PublicKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self) -> bytes: - buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:] - - def verify(self, signature: bytes, data: bytes) -> None: - evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestVerifyInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - res = self._backend._lib.EVP_DigestVerify( - evp_md_ctx, signature, len(signature), data, len(data) - ) - if res != 1: - self._backend._consume_errors() - raise exceptions.InvalidSignature - - -class _Ed448PrivateKey(Ed448PrivateKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self) -> Ed448PublicKey: - buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) - public_bytes = self._backend._ffi.buffer(buf)[:] - return self._backend.ed448_load_public_bytes(public_bytes) - - def sign(self, data: bytes) -> bytes: - evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() - self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) - evp_md_ctx = self._backend._ffi.gc( - evp_md_ctx, self._backend._lib.EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestSignInit( - evp_md_ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - buf = self._backend._ffi.new("unsigned char[]", _ED448_SIG_SIZE) - buflen = self._backend._ffi.new("size_t *", len(buf)) - res = self._backend._lib.EVP_DigestSign( - evp_md_ctx, buf, buflen, data, len(data) - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_SIG_SIZE) - return self._backend._ffi.buffer(buf, buflen[0])[:] - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self) -> bytes: - buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_private_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/hashes.py b/src/cryptography/hazmat/backends/openssl/hashes.py deleted file mode 100644 index 278b381..0000000 --- a/src/cryptography/hazmat/backends/openssl/hashes.py +++ /dev/null @@ -1,87 +0,0 @@ -# This file is dual licensed under the terms 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.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.primitives import hashes - - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -class _HashContext(hashes.HashContext): - def __init__( - self, backend: "Backend", algorithm: hashes.HashAlgorithm, ctx=None - ) -> None: - self._algorithm = algorithm - - self._backend = backend - - if ctx is None: - ctx = self._backend._lib.EVP_MD_CTX_new() - ctx = self._backend._ffi.gc( - ctx, self._backend._lib.EVP_MD_CTX_free - ) - evp_md = self._backend._evp_md_from_algorithm(algorithm) - if evp_md == self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "{} is not a supported hash on this backend.".format( - algorithm.name - ), - _Reasons.UNSUPPORTED_HASH, - ) - res = self._backend._lib.EVP_DigestInit_ex( - ctx, evp_md, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res != 0) - - self._ctx = ctx - - @property - def algorithm(self) -> hashes.HashAlgorithm: - return self._algorithm - - def copy(self) -> "_HashContext": - copied_ctx = self._backend._lib.EVP_MD_CTX_new() - copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx) - self._backend.openssl_assert(res != 0) - return _HashContext(self._backend, self.algorithm, ctx=copied_ctx) - - def update(self, data: bytes) -> None: - data_ptr = self._backend._ffi.from_buffer(data) - res = self._backend._lib.EVP_DigestUpdate( - self._ctx, data_ptr, len(data) - ) - self._backend.openssl_assert(res != 0) - - def finalize(self) -> bytes: - if isinstance(self.algorithm, hashes.ExtendableOutputFunction): - # extendable output functions use a different finalize - return self._finalize_xof() - else: - buf = self._backend._ffi.new( - "unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE - ) - outlen = self._backend._ffi.new("unsigned int *") - res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen) - self._backend.openssl_assert(res != 0) - self._backend.openssl_assert( - outlen[0] == self.algorithm.digest_size - ) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def _finalize_xof(self) -> bytes: - buf = self._backend._ffi.new( - "unsigned char[]", self.algorithm.digest_size - ) - res = self._backend._lib.EVP_DigestFinalXOF( - self._ctx, buf, self.algorithm.digest_size - ) - self._backend.openssl_assert(res != 0) - return self._backend._ffi.buffer(buf)[: self.algorithm.digest_size] diff --git a/src/cryptography/hazmat/backends/openssl/hmac.py b/src/cryptography/hazmat/backends/openssl/hmac.py deleted file mode 100644 index 5fd5407..0000000 --- a/src/cryptography/hazmat/backends/openssl/hmac.py +++ /dev/null @@ -1,85 +0,0 @@ -# This file is dual licensed under the terms 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.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.primitives import constant_time, hashes - - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -class _HMACContext(hashes.HashContext): - def __init__( - self, - backend: "Backend", - key: bytes, - algorithm: hashes.HashAlgorithm, - ctx=None, - ): - self._algorithm = algorithm - self._backend = backend - - if ctx is None: - ctx = self._backend._lib.HMAC_CTX_new() - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc(ctx, self._backend._lib.HMAC_CTX_free) - evp_md = self._backend._evp_md_from_algorithm(algorithm) - if evp_md == self._backend._ffi.NULL: - raise UnsupportedAlgorithm( - "{} is not a supported hash on this backend".format( - algorithm.name - ), - _Reasons.UNSUPPORTED_HASH, - ) - key_ptr = self._backend._ffi.from_buffer(key) - res = self._backend._lib.HMAC_Init_ex( - ctx, key_ptr, len(key), evp_md, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res != 0) - - self._ctx = ctx - self._key = key - - @property - def algorithm(self) -> hashes.HashAlgorithm: - return self._algorithm - - def copy(self) -> "_HMACContext": - copied_ctx = self._backend._lib.HMAC_CTX_new() - self._backend.openssl_assert(copied_ctx != self._backend._ffi.NULL) - copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.HMAC_CTX_free - ) - res = self._backend._lib.HMAC_CTX_copy(copied_ctx, self._ctx) - self._backend.openssl_assert(res != 0) - return _HMACContext( - self._backend, self._key, self.algorithm, ctx=copied_ctx - ) - - def update(self, data: bytes) -> None: - data_ptr = self._backend._ffi.from_buffer(data) - res = self._backend._lib.HMAC_Update(self._ctx, data_ptr, len(data)) - self._backend.openssl_assert(res != 0) - - def finalize(self) -> bytes: - buf = self._backend._ffi.new( - "unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE - ) - outlen = self._backend._ffi.new("unsigned int *") - res = self._backend._lib.HMAC_Final(self._ctx, buf, outlen) - self._backend.openssl_assert(res != 0) - self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def verify(self, signature: bytes) -> None: - digest = self.finalize() - if not constant_time.bytes_eq(digest, signature): - raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/backends/openssl/poly1305.py b/src/cryptography/hazmat/backends/openssl/poly1305.py deleted file mode 100644 index dd6d376..0000000 --- a/src/cryptography/hazmat/backends/openssl/poly1305.py +++ /dev/null @@ -1,68 +0,0 @@ -# This file is dual licensed under the terms 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.exceptions import InvalidSignature -from cryptography.hazmat.primitives import constant_time - - -_POLY1305_TAG_SIZE = 16 -_POLY1305_KEY_SIZE = 32 - - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -class _Poly1305Context: - def __init__(self, backend: "Backend", key: bytes) -> None: - self._backend = backend - - key_ptr = self._backend._ffi.from_buffer(key) - # This function copies the key into OpenSSL-owned memory so we don't - # need to retain it ourselves - evp_pkey = self._backend._lib.EVP_PKEY_new_raw_private_key( - self._backend._lib.NID_poly1305, - self._backend._ffi.NULL, - key_ptr, - len(key), - ) - self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL) - self._evp_pkey = self._backend._ffi.gc( - evp_pkey, self._backend._lib.EVP_PKEY_free - ) - ctx = self._backend._lib.EVP_MD_CTX_new() - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - self._ctx = self._backend._ffi.gc( - ctx, self._backend._lib.EVP_MD_CTX_free - ) - res = self._backend._lib.EVP_DigestSignInit( - self._ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._evp_pkey, - ) - self._backend.openssl_assert(res == 1) - - def update(self, data: bytes) -> None: - data_ptr = self._backend._ffi.from_buffer(data) - res = self._backend._lib.EVP_DigestSignUpdate( - self._ctx, data_ptr, len(data) - ) - self._backend.openssl_assert(res != 0) - - def finalize(self) -> bytes: - buf = self._backend._ffi.new("unsigned char[]", _POLY1305_TAG_SIZE) - outlen = self._backend._ffi.new("size_t *", _POLY1305_TAG_SIZE) - res = self._backend._lib.EVP_DigestSignFinal(self._ctx, buf, outlen) - self._backend.openssl_assert(res != 0) - self._backend.openssl_assert(outlen[0] == _POLY1305_TAG_SIZE) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def verify(self, tag: bytes) -> None: - mac = self.finalize() - if not constant_time.bytes_eq(mac, tag): - raise InvalidSignature("Value did not match computed tag.") diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 31cff16..ef27d4e 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -2,6 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations + import threading import typing @@ -14,15 +16,13 @@ _calculate_digest_and_algorithm, ) from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ( - utils as asym_utils, -) +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.asymmetric.padding import ( - AsymmetricPadding, MGF1, OAEP, - PKCS1v15, PSS, + AsymmetricPadding, + PKCS1v15, _Auto, _DigestLength, _MaxLength, @@ -35,13 +35,12 @@ RSAPublicNumbers, ) - if typing.TYPE_CHECKING: from cryptography.hazmat.backends.openssl.backend import Backend def _get_rsa_pss_salt_length( - backend: "Backend", + backend: Backend, pss: PSS, key: typing.Union[RSAPrivateKey, RSAPublicKey], hash_algorithm: hashes.HashAlgorithm, @@ -63,8 +62,8 @@ def _get_rsa_pss_salt_length( def _enc_dec_rsa( - backend: "Backend", - key: typing.Union["_RSAPrivateKey", "_RSAPublicKey"], + backend: Backend, + key: typing.Union[_RSAPrivateKey, _RSAPublicKey], data: bytes, padding: AsymmetricPadding, ) -> bytes: @@ -91,7 +90,7 @@ def _enc_dec_rsa( else: raise UnsupportedAlgorithm( - "{} is not supported by this backend.".format(padding.name), + f"{padding.name} is not supported by this backend.", _Reasons.UNSUPPORTED_PADDING, ) @@ -99,8 +98,8 @@ def _enc_dec_rsa( def _enc_dec_rsa_pkey_ctx( - backend: "Backend", - key: typing.Union["_RSAPrivateKey", "_RSAPublicKey"], + backend: Backend, + key: typing.Union[_RSAPrivateKey, _RSAPublicKey], data: bytes, padding_enum: int, padding: AsymmetricPadding, @@ -166,8 +165,8 @@ def _enc_dec_rsa_pkey_ctx( def _rsa_sig_determine_padding( - backend: "Backend", - key: typing.Union["_RSAPrivateKey", "_RSAPublicKey"], + backend: Backend, + key: typing.Union[_RSAPrivateKey, _RSAPublicKey], padding: AsymmetricPadding, algorithm: typing.Optional[hashes.HashAlgorithm], ) -> int: @@ -202,7 +201,7 @@ def _rsa_sig_determine_padding( padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING else: raise UnsupportedAlgorithm( - "{} is not supported by this backend.".format(padding.name), + f"{padding.name} is not supported by this backend.", _Reasons.UNSUPPORTED_PADDING, ) @@ -214,10 +213,10 @@ def _rsa_sig_determine_padding( # padding type, where it means that the signature data is encoded/decoded # as provided, without being wrapped in a DigestInfo structure. def _rsa_sig_setup( - backend: "Backend", + backend: Backend, padding: AsymmetricPadding, algorithm: typing.Optional[hashes.HashAlgorithm], - key: typing.Union["_RSAPublicKey", "_RSAPrivateKey"], + key: typing.Union[_RSAPublicKey, _RSAPrivateKey], init_func: typing.Callable[[typing.Any], int], ): padding_enum = _rsa_sig_determine_padding(backend, key, padding, algorithm) @@ -267,10 +266,10 @@ def _rsa_sig_setup( def _rsa_sig_sign( - backend: "Backend", + backend: Backend, padding: AsymmetricPadding, algorithm: hashes.HashAlgorithm, - private_key: "_RSAPrivateKey", + private_key: _RSAPrivateKey, data: bytes, ) -> bytes: pkey_ctx = _rsa_sig_setup( @@ -288,7 +287,7 @@ def _rsa_sig_sign( buf = backend._ffi.new("unsigned char[]", buflen[0]) res = backend._lib.EVP_PKEY_sign(pkey_ctx, buf, buflen, data, len(data)) if res != 1: - errors = backend._consume_errors_with_text() + errors = backend._consume_errors() raise ValueError( "Digest or salt length too long for key size. Use a larger key " "or shorter salt length if you are specifying a PSS salt", @@ -299,10 +298,10 @@ def _rsa_sig_sign( def _rsa_sig_verify( - backend: "Backend", + backend: Backend, padding: AsymmetricPadding, algorithm: hashes.HashAlgorithm, - public_key: "_RSAPublicKey", + public_key: _RSAPublicKey, signature: bytes, data: bytes, ) -> None: @@ -326,10 +325,10 @@ def _rsa_sig_verify( def _rsa_sig_recover( - backend: "Backend", + backend: Backend, padding: AsymmetricPadding, algorithm: typing.Optional[hashes.HashAlgorithm], - public_key: "_RSAPublicKey", + public_key: _RSAPublicKey, signature: bytes, ) -> bytes: pkey_ctx = _rsa_sig_setup( @@ -367,7 +366,12 @@ class _RSAPrivateKey(RSAPrivateKey): _key_size: int def __init__( - self, backend: "Backend", rsa_cdata, evp_pkey, _skip_check_key: bool + self, + backend: Backend, + rsa_cdata, + evp_pkey, + *, + unsafe_skip_rsa_key_validation: bool, ): res: int # RSA_check_key is slower in OpenSSL 3.0.0 due to improved @@ -375,10 +379,10 @@ def __init__( # since users don't load new keys constantly, but for TESTING we've # added an init arg that allows skipping the checks. You should not # use this in production code unless you understand the consequences. - if not _skip_check_key: + if not unsafe_skip_rsa_key_validation: res = backend._lib.RSA_check_key(rsa_cdata) if res != 1: - errors = backend._consume_errors_with_text() + errors = backend._consume_errors() raise ValueError("Invalid private key", errors) # 2 is prime and passes an RSA key check, so we also check # if p and q are odd just to be safe. @@ -390,7 +394,7 @@ def __init__( p_odd = backend._lib.BN_is_odd(p[0]) q_odd = backend._lib.BN_is_odd(q[0]) if p_odd != 1 or q_odd != 1: - errors = backend._consume_errors_with_text() + errors = backend._consume_errors() raise ValueError("Invalid private key", errors) self._backend = backend @@ -514,7 +518,7 @@ class _RSAPublicKey(RSAPublicKey): _rsa_cdata: object _key_size: int - def __init__(self, backend: "Backend", rsa_cdata, evp_pkey): + def __init__(self, backend: Backend, rsa_cdata, evp_pkey): self._backend = backend self._rsa_cdata = rsa_cdata self._evp_pkey = evp_pkey @@ -533,6 +537,15 @@ def __init__(self, backend: "Backend", rsa_cdata, evp_pkey): def key_size(self) -> int: return self._key_size + def __eq__(self, other: object) -> bool: + if not isinstance(other, _RSAPublicKey): + return NotImplemented + + return ( + self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) + == 1 + ) + def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: return _enc_dec_rsa(self._backend, self, plaintext, padding) diff --git a/src/cryptography/hazmat/backends/openssl/utils.py b/src/cryptography/hazmat/backends/openssl/utils.py index 3a70a58..5b404de 100644 --- a/src/cryptography/hazmat/backends/openssl/utils.py +++ b/src/cryptography/hazmat/backends/openssl/utils.py @@ -2,6 +2,8 @@ # 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 hashes @@ -11,14 +13,23 @@ from cryptography.hazmat.backends.openssl.backend import Backend -def _evp_pkey_derive(backend: "Backend", evp_pkey, peer_public_key) -> bytes: +def _evp_pkey_derive(backend: Backend, evp_pkey, peer_public_key) -> bytes: ctx = backend._lib.EVP_PKEY_CTX_new(evp_pkey, backend._ffi.NULL) backend.openssl_assert(ctx != backend._ffi.NULL) ctx = backend._ffi.gc(ctx, backend._lib.EVP_PKEY_CTX_free) res = backend._lib.EVP_PKEY_derive_init(ctx) backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_derive_set_peer(ctx, peer_public_key._evp_pkey) + + if backend._lib.Cryptography_HAS_EVP_PKEY_SET_PEER_EX: + res = backend._lib.EVP_PKEY_derive_set_peer_ex( + ctx, peer_public_key._evp_pkey, 0 + ) + else: + res = backend._lib.EVP_PKEY_derive_set_peer( + ctx, peer_public_key._evp_pkey + ) backend.openssl_assert(res == 1) + keylen = backend._ffi.new("size_t *") res = backend._lib.EVP_PKEY_derive(ctx, backend._ffi.NULL, keylen) backend.openssl_assert(res == 1) @@ -26,8 +37,8 @@ def _evp_pkey_derive(backend: "Backend", evp_pkey, peer_public_key) -> bytes: buf = backend._ffi.new("unsigned char[]", keylen[0]) res = backend._lib.EVP_PKEY_derive(ctx, buf, keylen) if res != 1: - errors_with_text = backend._consume_errors_with_text() - raise ValueError("Error computing shared key.", errors_with_text) + errors = backend._consume_errors() + raise ValueError("Error computing shared key.", errors) return backend._ffi.buffer(buf, keylen[0])[:] diff --git a/src/cryptography/hazmat/backends/openssl/x25519.py b/src/cryptography/hazmat/backends/openssl/x25519.py deleted file mode 100644 index f68501a..0000000 --- a/src/cryptography/hazmat/backends/openssl/x25519.py +++ /dev/null @@ -1,132 +0,0 @@ -# This file is dual licensed under the terms 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.backends.openssl.utils import _evp_pkey_derive -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.x25519 import ( - X25519PrivateKey, - X25519PublicKey, -) - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -_X25519_KEY_SIZE = 32 - - -class _X25519PublicKey(X25519PublicKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self) -> bytes: - ucharpp = self._backend._ffi.new("unsigned char **") - res = self._backend._lib.EVP_PKEY_get1_tls_encodedpoint( - self._evp_pkey, ucharpp - ) - self._backend.openssl_assert(res == 32) - self._backend.openssl_assert(ucharpp[0] != self._backend._ffi.NULL) - data = self._backend._ffi.gc( - ucharpp[0], self._backend._lib.OPENSSL_free - ) - return self._backend._ffi.buffer(data, res)[:] - - -class _X25519PrivateKey(X25519PrivateKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self) -> X25519PublicKey: - bio = self._backend._create_mem_bio_gc() - res = self._backend._lib.i2d_PUBKEY_bio(bio, self._evp_pkey) - self._backend.openssl_assert(res == 1) - evp_pkey = self._backend._lib.d2i_PUBKEY_bio( - bio, self._backend._ffi.NULL - ) - self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL) - evp_pkey = self._backend._ffi.gc( - evp_pkey, self._backend._lib.EVP_PKEY_free - ) - return _X25519PublicKey(self._backend, evp_pkey) - - def exchange(self, peer_public_key: X25519PublicKey) -> bytes: - if not isinstance(peer_public_key, X25519PublicKey): - raise TypeError("peer_public_key must be X25519PublicKey.") - - return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self) -> bytes: - # When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 we can - # switch this to EVP_PKEY_new_raw_private_key - # The trick we use here is serializing to a PKCS8 key and just - # using the last 32 bytes, which is the key itself. - bio = self._backend._create_mem_bio_gc() - res = self._backend._lib.i2d_PKCS8PrivateKey_bio( - bio, - self._evp_pkey, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - 0, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(res == 1) - pkcs8 = self._backend._read_mem_bio(bio) - self._backend.openssl_assert(len(pkcs8) == 48) - return pkcs8[-_X25519_KEY_SIZE:] diff --git a/src/cryptography/hazmat/backends/openssl/x448.py b/src/cryptography/hazmat/backends/openssl/x448.py deleted file mode 100644 index f45db56..0000000 --- a/src/cryptography/hazmat/backends/openssl/x448.py +++ /dev/null @@ -1,117 +0,0 @@ -# This file is dual licensed under the terms 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.backends.openssl.utils import _evp_pkey_derive -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.x448 import ( - X448PrivateKey, - X448PublicKey, -) - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - -_X448_KEY_SIZE = 56 - - -class _X448PublicKey(X448PublicKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - encoding is not serialization.Encoding.Raw - or format is not serialization.PublicFormat.Raw - ): - raise ValueError( - "When using Raw both encoding and format must be Raw" - ) - - return self._raw_public_bytes() - - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def _raw_public_bytes(self) -> bytes: - buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _X448_KEY_SIZE)[:] - - -class _X448PrivateKey(X448PrivateKey): - def __init__(self, backend: "Backend", evp_pkey): - self._backend = backend - self._evp_pkey = evp_pkey - - def public_key(self) -> X448PublicKey: - buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_public_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE) - public_bytes = self._backend._ffi.buffer(buf)[:] - return self._backend.x448_load_public_bytes(public_bytes) - - def exchange(self, peer_public_key: X448PublicKey) -> bytes: - if not isinstance(peer_public_key, X448PublicKey): - raise TypeError("peer_public_key must be X448PublicKey.") - - return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - if ( - encoding is serialization.Encoding.Raw - or format is serialization.PublicFormat.Raw - ): - if ( - format is not serialization.PrivateFormat.Raw - or encoding is not serialization.Encoding.Raw - or not isinstance( - encryption_algorithm, serialization.NoEncryption - ) - ): - raise ValueError( - "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption()" - ) - - return self._raw_private_bytes() - - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self, self._evp_pkey, None - ) - - def _raw_private_bytes(self) -> bytes: - buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) - buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) - res = self._backend._lib.EVP_PKEY_get_raw_private_key( - self._evp_pkey, buf, buflen - ) - self._backend.openssl_assert(res == 1) - self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE) - return self._backend._ffi.buffer(buf, _X448_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py deleted file mode 100644 index aa4ed10..0000000 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ /dev/null @@ -1,45 +0,0 @@ -# This file is dual licensed under the terms 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 warnings - -from cryptography import utils, x509 - - -# This exists for pyOpenSSL compatibility and SHOULD NOT BE USED -# WE WILL REMOVE THIS VERY SOON. -def _Certificate(backend, x509) -> x509.Certificate: # noqa: N802 - warnings.warn( - "This version of cryptography contains a temporary pyOpenSSL " - "fallback path. Upgrade pyOpenSSL now.", - utils.DeprecatedIn35, - ) - return backend._ossl2cert(x509) - - -# This exists for pyOpenSSL compatibility and SHOULD NOT BE USED -# WE WILL REMOVE THIS VERY SOON. -def _CertificateSigningRequest( # noqa: N802 - backend, x509_req -) -> x509.CertificateSigningRequest: - warnings.warn( - "This version of cryptography contains a temporary pyOpenSSL " - "fallback path. Upgrade pyOpenSSL now.", - utils.DeprecatedIn35, - ) - return backend._ossl2csr(x509_req) - - -# This exists for pyOpenSSL compatibility and SHOULD NOT BE USED -# WE WILL REMOVE THIS VERY SOON. -def _CertificateRevocationList( # noqa: N802 - backend, x509_crl -) -> x509.CertificateRevocationList: - warnings.warn( - "This version of cryptography contains a temporary pyOpenSSL " - "fallback path. Upgrade pyOpenSSL now.", - utils.DeprecatedIn35, - ) - return backend._ossl2crl(x509_crl) diff --git a/src/cryptography/hazmat/bindings/_rust/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/__init__.pyi index bab90a5..94a37a2 100644 --- a/src/cryptography/hazmat/bindings/_rust/__init__.pyi +++ b/src/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import types import typing def check_pkcs7_padding(data: bytes) -> bool: ... @@ -20,10 +21,14 @@ class FixedPool(typing.Generic[T]): def __init__( self, create: typing.Callable[[], T], - destroy: typing.Callable[[T], None], ) -> None: ... def acquire(self) -> PoolAcquisition[T]: ... class PoolAcquisition(typing.Generic[T]): def __enter__(self) -> T: ... - def __exit__(self, exc_type, exc_value, exc_tb) -> None: ... + def __exit__( + self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc_value: typing.Optional[BaseException], + exc_tb: typing.Optional[types.TracebackType], + ) -> None: ... diff --git a/src/cryptography/hazmat/bindings/_openssl.pyi b/src/cryptography/hazmat/bindings/_rust/_openssl.pyi similarity index 100% rename from src/cryptography/hazmat/bindings/_openssl.pyi rename to src/cryptography/hazmat/bindings/_rust/_openssl.pyi diff --git a/src/cryptography/hazmat/bindings/_rust/exceptions.pyi b/src/cryptography/hazmat/bindings/_rust/exceptions.pyi new file mode 100644 index 0000000..09f46b1 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/exceptions.pyi @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms 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 diff --git a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi index acdea3d..4671eb9 100644 --- a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi +++ b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -4,15 +4,14 @@ import typing -from cryptography.hazmat.primitives.asymmetric.types import PRIVATE_KEY_TYPES from cryptography.hazmat.primitives import hashes -from cryptography.x509 import Extension +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes from cryptography.x509.ocsp import ( OCSPRequest, OCSPRequestBuilder, OCSPResponse, - OCSPResponseStatus, OCSPResponseBuilder, + OCSPResponseStatus, ) def load_der_ocsp_request(data: bytes) -> OCSPRequest: ... @@ -21,6 +20,6 @@ def create_ocsp_request(builder: OCSPRequestBuilder) -> OCSPRequest: ... def create_ocsp_response( status: OCSPResponseStatus, builder: typing.Optional[OCSPResponseBuilder], - private_key: typing.Optional[PRIVATE_KEY_TYPES], + private_key: typing.Optional[PrivateKeyTypes], hash_algorithm: typing.Optional[hashes.HashAlgorithm], ) -> OCSPResponse: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi new file mode 100644 index 0000000..82f30d2 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -0,0 +1,47 @@ +# This file is dual licensed under the terms 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 ( + dh, + dsa, + ed448, + ed25519, + hashes, + hmac, + kdf, + poly1305, + x448, + x25519, +) + +__all__ = [ + "openssl_version", + "raise_openssl_error", + "dh", + "dsa", + "hashes", + "hmac", + "kdf", + "ed448", + "ed25519", + "poly1305", + "x448", + "x25519", +] + +def openssl_version() -> int: ... +def raise_openssl_error() -> typing.NoReturn: ... +def capture_error_stack() -> typing.List[OpenSSLError]: ... +def is_fips_enabled() -> bool: ... + +class OpenSSLError: + @property + def lib(self) -> int: ... + @property + def reason(self) -> int: ... + @property + def reason_text(self) -> bytes: ... + def _lib_reason_match(self, lib: int, reason: int) -> bool: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi new file mode 100644 index 0000000..bfd005d --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/dh.pyi @@ -0,0 +1,22 @@ +# This file is dual licensed under the terms 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 + +MIN_MODULUS_SIZE: int + +class DHPrivateKey: ... +class DHPublicKey: ... +class DHParameters: ... + +def generate_parameters(generator: int, key_size: int) -> dh.DHParameters: ... +def private_key_from_ptr(ptr: int) -> dh.DHPrivateKey: ... +def public_key_from_ptr(ptr: int) -> dh.DHPublicKey: ... +def from_pem_parameters(data: bytes) -> dh.DHParameters: ... +def from_der_parameters(data: bytes) -> dh.DHParameters: ... +def from_private_numbers(numbers: dh.DHPrivateNumbers) -> dh.DHPrivateKey: ... +def from_public_numbers(numbers: dh.DHPublicNumbers) -> dh.DHPublicKey: ... +def from_parameter_numbers( + numbers: dh.DHParameterNumbers, +) -> dh.DHParameters: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi new file mode 100644 index 0000000..5a56f25 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi @@ -0,0 +1,20 @@ +# This file is dual licensed under the terms 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 dsa + +class DSAPrivateKey: ... +class DSAPublicKey: ... +class DSAParameters: ... + +def generate_parameters(key_size: int) -> dsa.DSAParameters: ... +def private_key_from_ptr(ptr: int) -> dsa.DSAPrivateKey: ... +def public_key_from_ptr(ptr: int) -> dsa.DSAPublicKey: ... +def from_private_numbers( + numbers: dsa.DSAPrivateNumbers, +) -> dsa.DSAPrivateKey: ... +def from_public_numbers(numbers: dsa.DSAPublicNumbers) -> dsa.DSAPublicKey: ... +def from_parameter_numbers( + numbers: dsa.DSAParameterNumbers, +) -> dsa.DSAParameters: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi new file mode 100644 index 0000000..c7f127f --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms 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 private_key_from_ptr(ptr: int) -> ed25519.Ed25519PrivateKey: ... +def public_key_from_ptr(ptr: int) -> ed25519.Ed25519PublicKey: ... +def from_private_bytes(data: bytes) -> ed25519.Ed25519PrivateKey: ... +def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi new file mode 100644 index 0000000..1cf5f17 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms 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 private_key_from_ptr(ptr: int) -> ed448.Ed448PrivateKey: ... +def public_key_from_ptr(ptr: int) -> ed448.Ed448PublicKey: ... +def from_private_bytes(data: bytes) -> ed448.Ed448PrivateKey: ... +def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi new file mode 100644 index 0000000..ca5f42a --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms 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: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi new file mode 100644 index 0000000..e38d9b5 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi @@ -0,0 +1,21 @@ +# This file is dual licensed under the terms 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: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi new file mode 100644 index 0000000..034a8fe --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi @@ -0,0 +1,22 @@ +# This file is dual licensed under the terms 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: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi new file mode 100644 index 0000000..2e9b0a9 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms 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: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi new file mode 100644 index 0000000..90f7cbd --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms 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 private_key_from_ptr(ptr: int) -> x25519.X25519PrivateKey: ... +def public_key_from_ptr(ptr: int) -> x25519.X25519PublicKey: ... +def from_private_bytes(data: bytes) -> x25519.X25519PrivateKey: ... +def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi b/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi new file mode 100644 index 0000000..d326c8d --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl/x448.pyi @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms 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 private_key_from_ptr(ptr: int) -> x448.X448PrivateKey: ... +def public_key_from_ptr(ptr: int) -> x448.X448PublicKey: ... +def from_private_bytes(data: bytes) -> x448.X448PrivateKey: ... +def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi new file mode 100644 index 0000000..66bd850 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -0,0 +1,15 @@ +import typing + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import pkcs7 + +def serialize_certificates( + certs: typing.List[x509.Certificate], + encoding: serialization.Encoding, +) -> bytes: ... +def sign_and_serialize( + builder: pkcs7.PKCS7SignatureBuilder, + encoding: serialization.Encoding, + options: typing.Iterable[pkcs7.PKCS7Options], +) -> bytes: ... diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi index 317deb6..24b2f5e 100644 --- a/src/cryptography/hazmat/bindings/_rust/x509.pyi +++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi @@ -2,14 +2,17 @@ # 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.types import PRIVATE_KEY_TYPES +from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes def load_pem_x509_certificate(data: bytes) -> x509.Certificate: ... +def load_pem_x509_certificates( + data: bytes, +) -> typing.List[x509.Certificate]: ... def load_der_x509_certificate(data: bytes) -> x509.Certificate: ... def load_pem_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... def load_der_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... @@ -19,17 +22,18 @@ 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: PRIVATE_KEY_TYPES, + private_key: PrivateKeyTypes, hash_algorithm: typing.Optional[hashes.HashAlgorithm], + padding: typing.Optional[typing.Union[PKCS1v15, PSS]], ) -> x509.Certificate: ... def create_x509_csr( builder: x509.CertificateSigningRequestBuilder, - private_key: PRIVATE_KEY_TYPES, + private_key: PrivateKeyTypes, hash_algorithm: typing.Optional[hashes.HashAlgorithm], ) -> x509.CertificateSigningRequest: ... def create_x509_crl( builder: x509.CertificateRevocationListBuilder, - private_key: PRIVATE_KEY_TYPES, + private_key: PrivateKeyTypes, hash_algorithm: typing.Optional[hashes.HashAlgorithm], ) -> x509.CertificateRevocationList: ... diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index 10f307a..5e8ecd0 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -2,25 +2,9 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import typing - +from __future__ import annotations -def cryptography_has_ec2m() -> typing.List[str]: - return [ - "EC_POINT_get_affine_coordinates_GF2m", - ] - - -def cryptography_has_ssl3_method() -> typing.List[str]: - return [ - "SSLv3_method", - "SSLv3_client_method", - "SSLv3_server_method", - ] - - -def cryptography_has_110_verification_params() -> typing.List[str]: - return ["X509_CHECK_FLAG_NEVER_CHECK_SUBJECT"] +import typing def cryptography_has_set_cert_cb() -> typing.List[str]: @@ -46,12 +30,6 @@ def cryptography_has_tls_st() -> typing.List[str]: ] -def cryptography_has_scrypt() -> typing.List[str]: - return [ - "EVP_PBE_scrypt", - ] - - def cryptography_has_evp_pkey_dhx() -> typing.List[str]: return [ "EVP_PKEY_DHX", @@ -66,7 +44,6 @@ def cryptography_has_mem_functions() -> typing.List[str]: def cryptography_has_x509_store_ctx_get_issuer() -> typing.List[str]: return [ - "X509_STORE_get_get_issuer", "X509_STORE_set_get_issuer", ] @@ -92,26 +69,12 @@ def cryptography_has_poly1305() -> typing.List[str]: ] -def cryptography_has_oneshot_evp_digest_sign_verify() -> typing.List[str]: - return [ - "EVP_DigestSign", - "EVP_DigestVerify", - ] - - def cryptography_has_evp_digestfinal_xof() -> typing.List[str]: return [ "EVP_DigestFinalXOF", ] -def cryptography_has_evp_pkey_get_set_tls_encodedpoint() -> typing.List[str]: - return [ - "EVP_PKEY_get1_tls_encodedpoint", - "EVP_PKEY_set1_tls_encodedpoint", - ] - - def cryptography_has_fips() -> typing.List[str]: return [ "FIPS_mode_set", @@ -153,19 +116,6 @@ def cryptography_has_custom_ext() -> typing.List[str]: ] -def cryptography_has_openssl_cleanup() -> typing.List[str]: - return [ - "OPENSSL_cleanup", - ] - - -def cryptography_has_tlsv13() -> typing.List[str]: - return [ - "TLS1_3_VERSION", - "SSL_OP_NO_TLSv1_3", - ] - - def cryptography_has_tlsv13_functions() -> typing.List[str]: return [ "SSL_VERIFY_POST_HANDSHAKE", @@ -180,13 +130,6 @@ def cryptography_has_tlsv13_functions() -> typing.List[str]: ] -def cryptography_has_keylog() -> typing.List[str]: - return [ - "SSL_CTX_set_keylog_callback", - "SSL_CTX_get_keylog_callback", - ] - - def cryptography_has_raw_key() -> typing.List[str]: return [ "EVP_PKEY_new_raw_private_key", @@ -207,7 +150,6 @@ def cryptography_has_engine() -> typing.List[str]: "ENGINE_ctrl_cmd", "ENGINE_free", "ENGINE_get_name", - "Cryptography_add_osrandom_engine", "ENGINE_ctrl_cmd_string", "ENGINE_load_builtin_engines", "ENGINE_load_private_key", @@ -230,15 +172,6 @@ def cryptography_has_srtp() -> typing.List[str]: ] -def cryptography_has_get_proto_version() -> typing.List[str]: - return [ - "SSL_CTX_get_min_proto_version", - "SSL_CTX_get_max_proto_version", - "SSL_get_min_proto_version", - "SSL_get_max_proto_version", - ] - - def cryptography_has_providers() -> typing.List[str]: return [ "OSSL_PROVIDER_load", @@ -321,21 +254,34 @@ def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]: ] +def cryptography_has_get_extms_support() -> typing.List[str]: + return ["SSL_get_extms_support"] + + +def cryptography_has_evp_pkey_set_peer_ex() -> typing.List[str]: + return ["EVP_PKEY_derive_set_peer_ex"] + + +def cryptography_has_evp_aead() -> typing.List[str]: + return [ + "EVP_aead_chacha20_poly1305", + "EVP_AEAD_CTX_free", + "EVP_AEAD_CTX_seal", + "EVP_AEAD_CTX_open", + "EVP_AEAD_max_overhead", + "Cryptography_EVP_AEAD_CTX_new", + ] + + # 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_EC2M": cryptography_has_ec2m, - "Cryptography_HAS_SSL3_METHOD": cryptography_has_ssl3_method, - "Cryptography_HAS_110_VERIFICATION_PARAMS": ( - cryptography_has_110_verification_params - ), "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_SCRYPT": cryptography_has_scrypt, "Cryptography_HAS_EVP_PKEY_DHX": cryptography_has_evp_pkey_dhx, "Cryptography_HAS_MEM_FUNCTIONS": cryptography_has_mem_functions, "Cryptography_HAS_X509_STORE_CTX_GET_ISSUER": ( @@ -344,21 +290,12 @@ def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]: "Cryptography_HAS_ED448": cryptography_has_ed448, "Cryptography_HAS_ED25519": cryptography_has_ed25519, "Cryptography_HAS_POLY1305": cryptography_has_poly1305, - "Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY": ( - cryptography_has_oneshot_evp_digest_sign_verify - ), - "Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint": ( - cryptography_has_evp_pkey_get_set_tls_encodedpoint - ), "Cryptography_HAS_FIPS": cryptography_has_fips, "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_OPENSSL_CLEANUP": cryptography_has_openssl_cleanup, - "Cryptography_HAS_TLSv1_3": cryptography_has_tlsv13, "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, - "Cryptography_HAS_KEYLOG": cryptography_has_keylog, "Cryptography_HAS_RAW_KEY": cryptography_has_raw_key, "Cryptography_HAS_EVP_DIGESTFINAL_XOF": ( cryptography_has_evp_digestfinal_xof @@ -366,7 +303,6 @@ def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]: "Cryptography_HAS_ENGINE": cryptography_has_engine, "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, - "Cryptography_HAS_GET_PROTO_VERSION": cryptography_has_get_proto_version, "Cryptography_HAS_PROVIDERS": cryptography_has_providers, "Cryptography_HAS_OP_NO_RENEGOTIATION": ( cryptography_has_op_no_renegotiation @@ -385,4 +321,9 @@ def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]: "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_HAS_EVP_PKEY_SET_PEER_EX": ( + cryptography_has_evp_pkey_set_peer_ex + ), + "Cryptography_HAS_EVP_AEAD": (cryptography_has_evp_aead), } diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index 2b4c574..b50d631 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -2,90 +2,29 @@ # 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 import utils from cryptography.exceptions import InternalError -from cryptography.hazmat.bindings._openssl import ffi, lib +from cryptography.hazmat.bindings._rust import _openssl, openssl from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES -_OpenSSLErrorWithText = typing.NamedTuple( - "_OpenSSLErrorWithText", - [("code", int), ("lib", int), ("reason", int), ("reason_text", bytes)], -) - - -class _OpenSSLError: - def __init__(self, code: int, lib: int, reason: int): - self._code = code - self._lib = lib - self._reason = reason - - def _lib_reason_match(self, lib: int, reason: int) -> bool: - return lib == self.lib and reason == self.reason - - @property - def code(self) -> int: - return self._code - - @property - def lib(self) -> int: - return self._lib - - @property - def reason(self) -> int: - return self._reason - - -def _consume_errors(lib) -> typing.List[_OpenSSLError]: - errors = [] - while True: - code: int = lib.ERR_get_error() - if code == 0: - break - - err_lib: int = lib.ERR_GET_LIB(code) - err_reason: int = lib.ERR_GET_REASON(code) - - errors.append(_OpenSSLError(code, err_lib, err_reason)) - - return errors - - -def _errors_with_text( - errors: typing.List[_OpenSSLError], -) -> typing.List[_OpenSSLErrorWithText]: - errors_with_text = [] - for err in errors: - buf = ffi.new("char[]", 256) - lib.ERR_error_string_n(err.code, buf, len(buf)) - err_text_reason: bytes = ffi.string(buf) - - errors_with_text.append( - _OpenSSLErrorWithText( - err.code, err.lib, err.reason, err_text_reason - ) - ) - - return errors_with_text - - -def _consume_errors_with_text(lib): - return _errors_with_text(_consume_errors(lib)) - def _openssl_assert( - lib, ok: bool, errors: typing.Optional[typing.List[_OpenSSLError]] = None + lib, + ok: bool, + errors: typing.Optional[typing.List[openssl.OpenSSLError]] = None, ) -> None: if not ok: if errors is None: - errors = _consume_errors(lib) - errors_with_text = _errors_with_text(errors) + errors = openssl.capture_error_stack() raise InternalError( "Unknown OpenSSL error. This error is commonly encountered when " @@ -94,12 +33,26 @@ def _openssl_assert( "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. ({0!r})".format(errors_with_text), - errors_with_text, + "this. ({!r})".format(errors), + errors, + ) + + +def _legacy_provider_error(loaded: bool) -> None: + if not loaded: + raise RuntimeError( + "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." ) -def build_conditional_library(lib, conditional_names): +def build_conditional_library( + lib: typing.Any, + conditional_names: typing.Dict[str, typing.Callable[[], typing.List[str]]], +) -> typing.Any: conditional_lib = types.ModuleType("lib") conditional_lib._original_lib = lib # type: ignore[attr-defined] excluded_names = set() @@ -120,13 +73,14 @@ class Binding: """ lib: typing.ClassVar = None - ffi = ffi + ffi = _openssl.ffi _lib_loaded = False _init_lock = threading.Lock() - _legacy_provider: typing.Any = None - _default_provider: typing.Any = None + _legacy_provider: typing.Any = ffi.NULL + _legacy_provider_loaded = False + _default_provider: typing.Any = ffi.NULL - def __init__(self): + def __init__(self) -> None: self._ensure_ffi_initialized() def _enable_fips(self) -> None: @@ -146,36 +100,28 @@ def _enable_fips(self) -> None: _openssl_assert(self.lib, res == 1) @classmethod - def _register_osrandom_engine(cls): - # Clear any errors extant in the queue before we start. In many - # scenarios other things may be interacting with OpenSSL in the same - # process space and it has proven untenable to assume that they will - # reliably clear the error queue. Once we clear it here we will - # error on any subsequent unexpected item in the stack. - cls.lib.ERR_clear_error() - if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: - result = cls.lib.Cryptography_add_osrandom_engine() - _openssl_assert(cls.lib, result in (1, 2)) - - @classmethod - def _ensure_ffi_initialized(cls): + def _ensure_ffi_initialized(cls) -> None: with cls._init_lock: if not cls._lib_loaded: - cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES) + cls.lib = build_conditional_library( + _openssl.lib, CONDITIONAL_NAMES + ) cls._lib_loaded = True - cls._register_osrandom_engine() # 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. if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - cls._legacy_provider = cls.lib.OSSL_PROVIDER_load( - cls.ffi.NULL, b"legacy" - ) - _openssl_assert( - cls.lib, cls._legacy_provider != cls.ffi.NULL - ) + if not os.environ.get("CRYPTOGRAPHY_OPENSSL_NO_LEGACY"): + cls._legacy_provider = cls.lib.OSSL_PROVIDER_load( + cls.ffi.NULL, b"legacy" + ) + cls._legacy_provider_loaded = ( + cls._legacy_provider != cls.ffi.NULL + ) + _legacy_provider_error(cls._legacy_provider_loaded) + cls._default_provider = cls.lib.OSSL_PROVIDER_load( cls.ffi.NULL, b"default" ) @@ -184,25 +130,11 @@ def _ensure_ffi_initialized(cls): ) @classmethod - def init_static_locks(cls): + def init_static_locks(cls) -> None: cls._ensure_ffi_initialized() -def _verify_openssl_version(lib): - if ( - lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 - and not lib.CRYPTOGRAPHY_IS_LIBRESSL - and not lib.CRYPTOGRAPHY_IS_BORINGSSL - ): - warnings.warn( - "OpenSSL version 1.1.0 is no longer supported by the OpenSSL " - "project, please upgrade. The next release of cryptography will " - "drop support for OpenSSL 1.1.0.", - utils.DeprecatedIn37, - ) - - -def _verify_package_version(version): +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 @@ -210,7 +142,9 @@ def _verify_package_version(version): # 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 = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION) + 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 " @@ -222,9 +156,24 @@ def _verify_package_version(version): ) ) + _openssl_assert( + _openssl.lib, + _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), + ) + _verify_package_version(cryptography.__version__) Binding.init_static_locks() -_verify_openssl_version(Binding.lib) +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, + ) diff --git a/src/cryptography/hazmat/primitives/_asymmetric.py b/src/cryptography/hazmat/primitives/_asymmetric.py index cdadbde..ea55ffd 100644 --- a/src/cryptography/hazmat/primitives/_asymmetric.py +++ b/src/cryptography/hazmat/primitives/_asymmetric.py @@ -2,15 +2,17 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import abc +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): - @abc.abstractproperty + @property + @abc.abstractmethod def name(self) -> str: """ A string naming this padding (e.g. "PSS", "PKCS1"). diff --git a/src/cryptography/hazmat/primitives/_cipheralgorithm.py b/src/cryptography/hazmat/primitives/_cipheralgorithm.py index 7f32204..3b880b6 100644 --- a/src/cryptography/hazmat/primitives/_cipheralgorithm.py +++ b/src/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -2,38 +2,43 @@ # 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 - # This exists to break an import cycle. It is normally accessible from the # ciphers module. class CipherAlgorithm(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def name(self) -> str: """ A string naming this mode (e.g. "AES", "Camellia"). """ - @abc.abstractproperty + @property + @abc.abstractmethod def key_sizes(self) -> typing.FrozenSet[int]: """ Valid key sizes for this algorithm in bits """ - @abc.abstractproperty + @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(metaclass=abc.ABCMeta): +class BlockCipherAlgorithm(CipherAlgorithm): key: bytes - @abc.abstractproperty + @property + @abc.abstractmethod def block_size(self) -> int: """ The size of a block as an integer in bits (e.g. 64, 128). diff --git a/src/cryptography/hazmat/primitives/_serialization.py b/src/cryptography/hazmat/primitives/_serialization.py index fddb4c8..34f3fbc 100644 --- a/src/cryptography/hazmat/primitives/_serialization.py +++ b/src/cryptography/hazmat/primitives/_serialization.py @@ -2,6 +2,8 @@ # 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 @@ -33,7 +35,7 @@ class PrivateFormat(utils.Enum): OpenSSH = "OpenSSH" PKCS12 = "PKCS12" - def encryption_builder(self) -> "KeySerializationEncryptionBuilder": + def encryption_builder(self) -> KeySerializationEncryptionBuilder: if self not in (PrivateFormat.OpenSSH, PrivateFormat.PKCS12): raise ValueError( "encryption_builder only supported with PrivateFormat.OpenSSH" @@ -71,7 +73,7 @@ class NoEncryption(KeySerializationEncryption): pass -class KeySerializationEncryptionBuilder(object): +class KeySerializationEncryptionBuilder: def __init__( self, format: PrivateFormat, @@ -86,7 +88,7 @@ def __init__( self._hmac_hash = _hmac_hash self._key_cert_algorithm = _key_cert_algorithm - def kdf_rounds(self, rounds: int) -> "KeySerializationEncryptionBuilder": + def kdf_rounds(self, rounds: int) -> KeySerializationEncryptionBuilder: if self._kdf_rounds is not None: raise ValueError("kdf_rounds already set") @@ -105,7 +107,7 @@ def kdf_rounds(self, rounds: int) -> "KeySerializationEncryptionBuilder": def hmac_hash( self, algorithm: HashAlgorithm - ) -> "KeySerializationEncryptionBuilder": + ) -> KeySerializationEncryptionBuilder: if self._format is not PrivateFormat.PKCS12: raise TypeError( "hmac_hash only supported with PrivateFormat.PKCS12" @@ -122,7 +124,7 @@ def hmac_hash( def key_cert_algorithm( self, algorithm: PBES - ) -> "KeySerializationEncryptionBuilder": + ) -> KeySerializationEncryptionBuilder: if self._format is not PrivateFormat.PKCS12: raise TypeError( "key_cert_algorithm only supported with " diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py index 2093ad4..751bcc4 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -2,19 +2,18 @@ # 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 -_MIN_MODULUS_SIZE = 512 - - def generate_parameters( generator: int, key_size: int, backend: typing.Any = None -) -> "DHParameters": +) -> DHParameters: from cryptography.hazmat.backends.openssl.backend import backend as ossl return ossl.generate_dh_parameters(generator, key_size) @@ -30,9 +29,10 @@ def __init__(self, p: int, g: int, q: typing.Optional[int] = None) -> None: if g < 2: raise ValueError("DH generator must be 2 or greater") - if p.bit_length() < _MIN_MODULUS_SIZE: + if p.bit_length() < rust_openssl.dh.MIN_MODULUS_SIZE: raise ValueError( - "p (modulus) must be at least {}-bit".format(_MIN_MODULUS_SIZE) + f"p (modulus) must be at least " + f"{rust_openssl.dh.MIN_MODULUS_SIZE}-bit" ) self._p = p @@ -47,7 +47,7 @@ def __eq__(self, other: object) -> bool: self._p == other._p and self._g == other._g and self._q == other._q ) - def parameters(self, backend: typing.Any = None) -> "DHParameters": + def parameters(self, backend: typing.Any = None) -> DHParameters: from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) @@ -89,7 +89,7 @@ def __eq__(self, other: object) -> bool: and self._parameter_numbers == other._parameter_numbers ) - def public_key(self, backend: typing.Any = None) -> "DHPublicKey": + def public_key(self, backend: typing.Any = None) -> DHPublicKey: from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) @@ -127,7 +127,7 @@ def __eq__(self, other: object) -> bool: and self._public_numbers == other._public_numbers ) - def private_key(self, backend: typing.Any = None) -> "DHPrivateKey": + def private_key(self, backend: typing.Any = None) -> DHPrivateKey: from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) @@ -145,7 +145,7 @@ def x(self) -> int: class DHParameters(metaclass=abc.ABCMeta): @abc.abstractmethod - def generate_private_key(self) -> "DHPrivateKey": + def generate_private_key(self) -> DHPrivateKey: """ Generates and returns a DHPrivateKey. """ @@ -168,10 +168,12 @@ def parameter_numbers(self) -> DHParameterNumbers: DHParametersWithSerialization = DHParameters +DHParameters.register(rust_openssl.dh.DHParameters) class DHPublicKey(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. @@ -199,12 +201,20 @@ def public_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): - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. @@ -248,3 +258,4 @@ def private_bytes( DHPrivateKeyWithSerialization = DHPrivateKey +DHPrivateKey.register(rust_openssl.dh.DHPrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py index 5e58709..a8c52de 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -2,42 +2,44 @@ # 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, -) +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils class DSAParameters(metaclass=abc.ABCMeta): @abc.abstractmethod - def generate_private_key(self) -> "DSAPrivateKey": + def generate_private_key(self) -> DSAPrivateKey: """ Generates and returns a DSAPrivateKey. """ @abc.abstractmethod - def parameter_numbers(self) -> "DSAParameterNumbers": + def parameter_numbers(self) -> DSAParameterNumbers: """ Returns a DSAParameterNumbers. """ DSAParametersWithNumbers = DSAParameters +DSAParameters.register(rust_openssl.dsa.DSAParameters) class DSAPrivateKey(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. """ @abc.abstractmethod - def public_key(self) -> "DSAPublicKey": + def public_key(self) -> DSAPublicKey: """ The DSAPublicKey associated with this private key. """ @@ -59,7 +61,7 @@ def sign( """ @abc.abstractmethod - def private_numbers(self) -> "DSAPrivateNumbers": + def private_numbers(self) -> DSAPrivateNumbers: """ Returns a DSAPrivateNumbers. """ @@ -77,10 +79,12 @@ def private_bytes( DSAPrivateKeyWithSerialization = DSAPrivateKey +DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey) class DSAPublicKey(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ The bit length of the prime modulus. @@ -93,7 +97,7 @@ def parameters(self) -> DSAParameters: """ @abc.abstractmethod - def public_numbers(self) -> "DSAPublicNumbers": + def public_numbers(self) -> DSAPublicNumbers: """ Returns a DSAPublicNumbers. """ @@ -119,8 +123,15 @@ def verify( Verifies the signature of the data. """ + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + DSAPublicKeyWithSerialization = DSAPublicKey +DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) class DSAParameterNumbers: diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index 3aaa382..ddfaabf 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -2,17 +2,15 @@ # 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 -import warnings from cryptography import utils from cryptography.hazmat._oid import ObjectIdentifier from cryptography.hazmat.primitives import _serialization, hashes -from cryptography.hazmat.primitives.asymmetric import ( - utils as asym_utils, -) +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils class EllipticCurveOID: @@ -38,13 +36,15 @@ class EllipticCurveOID: class EllipticCurve(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def name(self) -> str: """ The name of the curve. e.g. secp256r1. """ - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ Bit size of a secret scalar for the curve. @@ -52,7 +52,8 @@ def key_size(self) -> int: class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def algorithm( self, ) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]: @@ -64,7 +65,7 @@ def algorithm( class EllipticCurvePrivateKey(metaclass=abc.ABCMeta): @abc.abstractmethod def exchange( - self, algorithm: "ECDH", peer_public_key: "EllipticCurvePublicKey" + self, algorithm: ECDH, peer_public_key: EllipticCurvePublicKey ) -> bytes: """ Performs a key exchange operation using the provided algorithm with the @@ -72,18 +73,20 @@ def exchange( """ @abc.abstractmethod - def public_key(self) -> "EllipticCurvePublicKey": + def public_key(self) -> EllipticCurvePublicKey: """ The EllipticCurvePublicKey for this private key. """ - @abc.abstractproperty + @property + @abc.abstractmethod def curve(self) -> EllipticCurve: """ The EllipticCurve that this key is on. """ - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ Bit size of a secret scalar for the curve. @@ -100,7 +103,7 @@ def sign( """ @abc.abstractmethod - def private_numbers(self) -> "EllipticCurvePrivateNumbers": + def private_numbers(self) -> EllipticCurvePrivateNumbers: """ Returns an EllipticCurvePrivateNumbers. """ @@ -121,20 +124,22 @@ def private_bytes( class EllipticCurvePublicKey(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def curve(self) -> EllipticCurve: """ The EllipticCurve that this key is on. """ - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ Bit size of a secret scalar for the curve. """ @abc.abstractmethod - def public_numbers(self) -> "EllipticCurvePublicNumbers": + def public_numbers(self) -> EllipticCurvePublicNumbers: """ Returns an EllipticCurvePublicNumbers. """ @@ -163,7 +168,7 @@ def verify( @classmethod def from_encoded_point( cls, curve: EllipticCurve, data: bytes - ) -> "EllipticCurvePublicKey": + ) -> EllipticCurvePublicKey: utils._check_bytes("data", data) if not isinstance(curve, EllipticCurve): @@ -179,6 +184,12 @@ def from_encoded_point( return backend.load_elliptic_curve_public_bytes(curve, data) + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey @@ -363,50 +374,6 @@ def public_key(self, backend: typing.Any = None) -> EllipticCurvePublicKey: return ossl.load_elliptic_curve_public_numbers(self) - def encode_point(self) -> bytes: - warnings.warn( - "encode_point has been deprecated on EllipticCurvePublicNumbers" - " and will be removed in a future version. Please use " - "EllipticCurvePublicKey.public_bytes to obtain both " - "compressed and uncompressed point encoding.", - utils.PersistentlyDeprecated2019, - stacklevel=2, - ) - # key_size is in bits. Convert to bytes and round up - byte_length = (self.curve.key_size + 7) // 8 - return ( - b"\x04" - + utils.int_to_bytes(self.x, byte_length) - + utils.int_to_bytes(self.y, byte_length) - ) - - @classmethod - def from_encoded_point( - cls, curve: EllipticCurve, data: bytes - ) -> "EllipticCurvePublicNumbers": - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must be an EllipticCurve instance") - - warnings.warn( - "Support for unsafe construction of public numbers from " - "encoded data will be removed in a future version. " - "Please use EllipticCurvePublicKey.from_encoded_point", - utils.PersistentlyDeprecated2019, - stacklevel=2, - ) - - if data.startswith(b"\x04"): - # key_size is in bits. Convert to bytes and round up - byte_length = (curve.key_size + 7) // 8 - if len(data) == 2 * byte_length + 1: - x = int.from_bytes(data[1 : byte_length + 1], "big") - y = int.from_bytes(data[byte_length + 1 :], "big") - return cls(x, y, curve) - else: - raise ValueError("Invalid elliptic curve point data length") - else: - raise ValueError("Unsupported elliptic curve point type") - @property def curve(self) -> EllipticCurve: return self._curve diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py index 4327702..f26e54d 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -2,20 +2,18 @@ # 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 -_ED25519_KEY_SIZE = 32 -_ED25519_SIG_SIZE = 64 - - class Ed25519PublicKey(metaclass=abc.ABCMeta): @classmethod - def from_public_bytes(cls, data: bytes) -> "Ed25519PublicKey": + def from_public_bytes(cls, data: bytes) -> Ed25519PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -36,16 +34,33 @@ def public_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, "ed25519"): + Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) + class Ed25519PrivateKey(metaclass=abc.ABCMeta): @classmethod - def generate(cls) -> "Ed25519PrivateKey": + def generate(cls) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -57,7 +72,7 @@ def generate(cls) -> "Ed25519PrivateKey": return backend.ed25519_generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> "Ed25519PrivateKey": + def from_private_bytes(cls, data: bytes) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -85,8 +100,19 @@ def private_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. """ + + +if hasattr(rust_openssl, "x25519"): + Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed448.py b/src/cryptography/hazmat/primitives/asymmetric/ed448.py index 27bc27c..a9a34b2 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ed448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -2,16 +2,18 @@ # 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": + def from_public_bytes(cls, data: bytes) -> Ed448PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -32,16 +34,33 @@ def public_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": + def generate(cls) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -52,7 +71,7 @@ def generate(cls) -> "Ed448PrivateKey": return backend.ed448_generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> "Ed448PrivateKey": + def from_private_bytes(cls, data: bytes) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -85,3 +104,14 @@ def private_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) diff --git a/src/cryptography/hazmat/primitives/asymmetric/padding.py b/src/cryptography/hazmat/primitives/asymmetric/padding.py index dd3c648..7198808 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/src/cryptography/hazmat/primitives/asymmetric/padding.py @@ -2,6 +2,7 @@ # 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 @@ -38,7 +39,7 @@ class PSS(AsymmetricPadding): def __init__( self, - mgf: "MGF", + mgf: MGF, salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength], ) -> None: self._mgf = mgf @@ -62,7 +63,7 @@ class OAEP(AsymmetricPadding): def __init__( self, - mgf: "MGF", + mgf: MGF, algorithm: hashes.HashAlgorithm, label: typing.Optional[bytes], ): @@ -89,7 +90,7 @@ def __init__(self, algorithm: hashes.HashAlgorithm): def calculate_max_pss_salt_length( - key: typing.Union["rsa.RSAPrivateKey", "rsa.RSAPublicKey"], + key: typing.Union[rsa.RSAPrivateKey, rsa.RSAPublicKey], hash_algorithm: hashes.HashAlgorithm, ) -> int: if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index 5ffe767..b740f01 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -2,6 +2,7 @@ # 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 @@ -9,9 +10,7 @@ from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding -from cryptography.hazmat.primitives.asymmetric import ( - utils as asym_utils, -) +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils class RSAPrivateKey(metaclass=abc.ABCMeta): @@ -21,14 +20,15 @@ def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: Decrypts the provided ciphertext. """ - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ The bit length of the public modulus. """ @abc.abstractmethod - def public_key(self) -> "RSAPublicKey": + def public_key(self) -> RSAPublicKey: """ The RSAPublicKey associated with this private key. """ @@ -45,7 +45,7 @@ def sign( """ @abc.abstractmethod - def private_numbers(self) -> "RSAPrivateNumbers": + def private_numbers(self) -> RSAPrivateNumbers: """ Returns an RSAPrivateNumbers. """ @@ -72,14 +72,15 @@ def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: Encrypts the given plaintext. """ - @abc.abstractproperty + @property + @abc.abstractmethod def key_size(self) -> int: """ The bit length of the public modulus. """ @abc.abstractmethod - def public_numbers(self) -> "RSAPublicNumbers": + def public_numbers(self) -> RSAPublicNumbers: """ Returns an RSAPublicNumbers """ @@ -117,6 +118,12 @@ def recover_data_from_signature( Recovers the original data from the signature. """ + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + RSAPublicKeyWithSerialization = RSAPublicKey @@ -297,7 +304,7 @@ def __init__( dmp1: int, dmq1: int, iqmp: int, - public_numbers: "RSAPublicNumbers", + public_numbers: RSAPublicNumbers, ): if ( not isinstance(p, int) @@ -351,15 +358,22 @@ def iqmp(self) -> int: return self._iqmp @property - def public_numbers(self) -> "RSAPublicNumbers": + def public_numbers(self) -> RSAPublicNumbers: return self._public_numbers - def private_key(self, backend: typing.Any = None) -> RSAPrivateKey: + def private_key( + self, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, + ) -> RSAPrivateKey: from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) - return ossl.load_rsa_private_numbers(self) + return ossl.load_rsa_private_numbers( + self, unsafe_skip_rsa_key_validation + ) def __eq__(self, other: object) -> bool: if not isinstance(other, RSAPrivateNumbers): diff --git a/src/cryptography/hazmat/primitives/asymmetric/types.py b/src/cryptography/hazmat/primitives/asymmetric/types.py index d497815..1fe4eaf 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/types.py +++ b/src/cryptography/hazmat/primitives/asymmetric/types.py @@ -2,22 +2,24 @@ # 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, - ed25519, ed448, + ed25519, rsa, - x25519, x448, + x25519, ) - # Every asymmetric key type -PUBLIC_KEY_TYPES = typing.Union[ +PublicKeyTypes = typing.Union[ dh.DHPublicKey, dsa.DSAPublicKey, rsa.RSAPublicKey, @@ -27,8 +29,16 @@ 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 -PRIVATE_KEY_TYPES = typing.Union[ +PrivateKeyTypes = typing.Union[ dh.DHPrivateKey, ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey, @@ -38,27 +48,51 @@ 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 -CERTIFICATE_PRIVATE_KEY_TYPES = typing.Union[ +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 -CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES = typing.Union[ +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. -CERTIFICATE_PUBLIC_KEY_TYPES = typing.Union[ +CertificatePublicKeyTypes = typing.Union[ dsa.DSAPublicKey, rsa.RSAPublicKey, ec.EllipticCurvePublicKey, @@ -67,3 +101,11 @@ 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", +) diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py index 638ecb3..826b956 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/utils.py +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -2,11 +2,11 @@ # 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 diff --git a/src/cryptography/hazmat/primitives/asymmetric/x25519.py b/src/cryptography/hazmat/primitives/asymmetric/x25519.py index 690af78..699054c 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -2,16 +2,18 @@ # 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": + def from_public_bytes(cls, data: bytes) -> X25519PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -32,10 +34,28 @@ def public_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. + """ + + +# For LibreSSL +if hasattr(rust_openssl, "x25519"): + X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) + class X25519PrivateKey(metaclass=abc.ABCMeta): @classmethod - def generate(cls) -> "X25519PrivateKey": + def generate(cls) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -46,7 +66,7 @@ def generate(cls) -> "X25519PrivateKey": return backend.x25519_generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> "X25519PrivateKey": + def from_private_bytes(cls, data: bytes) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -60,7 +80,7 @@ def from_private_bytes(cls, data: bytes) -> "X25519PrivateKey": @abc.abstractmethod def public_key(self) -> X25519PublicKey: """ - The serialized bytes of the public key. + Returns the public key assosciated with this private key """ @abc.abstractmethod @@ -74,8 +94,20 @@ def private_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. """ + + +# For LibreSSL +if hasattr(rust_openssl, "x25519"): + X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) diff --git a/src/cryptography/hazmat/primitives/asymmetric/x448.py b/src/cryptography/hazmat/primitives/asymmetric/x448.py index 7f71c27..abf7848 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x448.py @@ -2,16 +2,18 @@ # 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": + def from_public_bytes(cls, data: bytes) -> X448PublicKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -32,10 +34,27 @@ def public_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": + def generate(cls) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -46,7 +65,7 @@ def generate(cls) -> "X448PrivateKey": return backend.x448_generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> "X448PrivateKey": + def from_private_bytes(cls, data: bytes) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -60,7 +79,7 @@ def from_private_bytes(cls, data: bytes) -> "X448PrivateKey": @abc.abstractmethod def public_key(self) -> X448PublicKey: """ - The serialized bytes of the public key. + Returns the public key associated with this private key """ @abc.abstractmethod @@ -74,8 +93,19 @@ def private_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) diff --git a/src/cryptography/hazmat/primitives/ciphers/__init__.py b/src/cryptography/hazmat/primitives/ciphers/__init__.py index 874dbd4..cc88fbf 100644 --- a/src/cryptography/hazmat/primitives/ciphers/__init__.py +++ b/src/cryptography/hazmat/primitives/ciphers/__init__.py @@ -2,6 +2,7 @@ # 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, @@ -15,7 +16,6 @@ CipherContext, ) - __all__ = [ "Cipher", "CipherAlgorithm", diff --git a/src/cryptography/hazmat/primitives/ciphers/aead.py b/src/cryptography/hazmat/primitives/ciphers/aead.py index 3cdb3eb..957b2d2 100644 --- a/src/cryptography/hazmat/primitives/ciphers/aead.py +++ b/src/cryptography/hazmat/primitives/ciphers/aead.py @@ -2,6 +2,7 @@ # 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 typing @@ -9,6 +10,7 @@ from cryptography import exceptions, utils from cryptography.hazmat.backends.openssl import aead from cryptography.hazmat.backends.openssl.backend import backend +from cryptography.hazmat.bindings._rust import FixedPool class ChaCha20Poly1305: @@ -26,11 +28,15 @@ def __init__(self, key: bytes): raise ValueError("ChaCha20Poly1305 key must be 32 bytes.") self._key = key + self._pool = FixedPool(self._create_fn) @classmethod def generate_key(cls) -> bytes: return os.urandom(32) + def _create_fn(self): + return aead._aead_create_ctx(backend, self, self._key) + def encrypt( self, nonce: bytes, @@ -47,7 +53,10 @@ def encrypt( ) self._check_params(nonce, data, associated_data) - return aead._encrypt(backend, self, nonce, data, [associated_data], 16) + with self._pool.acquire() as ctx: + return aead._encrypt( + backend, self, nonce, data, [associated_data], 16, ctx + ) def decrypt( self, @@ -59,7 +68,10 @@ def decrypt( associated_data = b"" self._check_params(nonce, data, associated_data) - return aead._decrypt(backend, self, nonce, data, [associated_data], 16) + with self._pool.acquire() as ctx: + return aead._decrypt( + backend, self, nonce, data, [associated_data], 16, ctx + ) def _check_params( self, @@ -68,8 +80,8 @@ def _check_params( associated_data: bytes, ) -> None: utils._check_byteslike("nonce", nonce) - utils._check_bytes("data", data) - utils._check_bytes("associated_data", associated_data) + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) if len(nonce) != 12: raise ValueError("Nonce must be 12 bytes") @@ -153,8 +165,8 @@ def _check_params( self, nonce: bytes, data: bytes, associated_data: bytes ) -> None: utils._check_byteslike("nonce", nonce) - utils._check_bytes("data", data) - utils._check_bytes("associated_data", associated_data) + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) if not 7 <= len(nonce) <= 13: raise ValueError("Nonce must be between 7 and 13 bytes") @@ -216,8 +228,8 @@ def _check_params( associated_data: bytes, ) -> None: utils._check_byteslike("nonce", nonce) - utils._check_bytes("data", data) - utils._check_bytes("associated_data", associated_data) + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) if len(nonce) < 8 or len(nonce) > 128: raise ValueError("Nonce must be between 8 and 128 bytes") @@ -285,13 +297,13 @@ def _check_params( associated_data: bytes, ) -> None: utils._check_byteslike("nonce", nonce) - utils._check_bytes("data", data) - utils._check_bytes("associated_data", associated_data) + utils._check_byteslike("data", data) + utils._check_byteslike("associated_data", associated_data) if len(nonce) < 12 or len(nonce) > 15: raise ValueError("Nonce must be between 12 and 15 bytes") -class AESSIV(object): +class AESSIV: _MAX_SIZE = 2**31 - 1 def __init__(self, key: bytes): @@ -352,10 +364,15 @@ def decrypt( def _check_params( self, data: bytes, - associated_data: typing.List, + associated_data: typing.List[bytes], ) -> None: - utils._check_bytes("data", data) - if not isinstance(associated_data, list) or not all( - isinstance(x, bytes) for x in associated_data - ): - raise TypeError("associated_data must be a list of bytes or None") + utils._check_byteslike("data", data) + if len(data) == 0: + raise ValueError("data must not be zero length") + + if not isinstance(associated_data, list): + raise TypeError( + "associated_data must be a list of bytes-like objects or None" + ) + for x in associated_data: + utils._check_byteslike("associated_data elements", x) diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py index 6138542..4bfc5d8 100644 --- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -2,6 +2,7 @@ # 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.primitives.ciphers import ( @@ -24,7 +25,7 @@ def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: return key -class AES(CipherAlgorithm, BlockCipherAlgorithm): +class AES(BlockCipherAlgorithm): name = "AES" block_size = 128 # 512 added to support AES-256-XTS, which uses 512-bit keys @@ -38,7 +39,7 @@ def key_size(self) -> int: return len(self.key) * 8 -class AES128(CipherAlgorithm, BlockCipherAlgorithm): +class AES128(BlockCipherAlgorithm): name = "AES" block_size = 128 key_sizes = frozenset([128]) @@ -48,7 +49,7 @@ def __init__(self, key: bytes): self.key = _verify_key_size(self, key) -class AES256(CipherAlgorithm, BlockCipherAlgorithm): +class AES256(BlockCipherAlgorithm): name = "AES" block_size = 128 key_sizes = frozenset([256]) @@ -58,7 +59,7 @@ def __init__(self, key: bytes): self.key = _verify_key_size(self, key) -class Camellia(CipherAlgorithm, BlockCipherAlgorithm): +class Camellia(BlockCipherAlgorithm): name = "camellia" block_size = 128 key_sizes = frozenset([128, 192, 256]) @@ -71,7 +72,7 @@ def key_size(self) -> int: return len(self.key) * 8 -class TripleDES(CipherAlgorithm, BlockCipherAlgorithm): +class TripleDES(BlockCipherAlgorithm): name = "3DES" block_size = 64 key_sizes = frozenset([64, 128, 192]) @@ -88,7 +89,7 @@ def key_size(self) -> int: return len(self.key) * 8 -class Blowfish(CipherAlgorithm, BlockCipherAlgorithm): +class Blowfish(BlockCipherAlgorithm): name = "Blowfish" block_size = 64 key_sizes = frozenset(range(32, 449, 8)) @@ -111,7 +112,7 @@ def key_size(self) -> int: ) -class CAST5(CipherAlgorithm, BlockCipherAlgorithm): +class CAST5(BlockCipherAlgorithm): name = "CAST5" block_size = 64 key_sizes = frozenset(range(40, 129, 8)) @@ -146,7 +147,7 @@ def key_size(self) -> int: return len(self.key) * 8 -class IDEA(CipherAlgorithm, BlockCipherAlgorithm): +class IDEA(BlockCipherAlgorithm): name = "IDEA" block_size = 64 key_sizes = frozenset([128]) @@ -169,7 +170,7 @@ def key_size(self) -> int: ) -class SEED(CipherAlgorithm, BlockCipherAlgorithm): +class SEED(BlockCipherAlgorithm): name = "SEED" block_size = 128 key_sizes = frozenset([128]) @@ -214,7 +215,7 @@ def key_size(self) -> int: return len(self.key) * 8 -class SM4(CipherAlgorithm, BlockCipherAlgorithm): +class SM4(BlockCipherAlgorithm): name = "SM4" block_size = 128 key_sizes = frozenset([128]) diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index 2ea7fc6..38a2ebb 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -2,6 +2,7 @@ # 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 @@ -14,7 +15,6 @@ from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm from cryptography.hazmat.primitives.ciphers import modes - if typing.TYPE_CHECKING: from cryptography.hazmat.backends.openssl.ciphers import ( _CipherContext as _BackendCipherContext, @@ -61,7 +61,8 @@ def finalize_with_tag(self, tag: bytes) -> bytes: class AEADEncryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def tag(self) -> bytes: """ Returns tag bytes. This is only available after encryption is @@ -80,8 +81,7 @@ def __init__( algorithm: CipherAlgorithm, mode: Mode, backend: typing.Any = None, - ): - + ) -> None: if not isinstance(algorithm, CipherAlgorithm): raise TypeError("Expected interface of CipherAlgorithm.") @@ -96,13 +96,13 @@ def __init__( @typing.overload def encryptor( - self: "Cipher[modes.ModeWithAuthenticationTag]", + self: Cipher[modes.ModeWithAuthenticationTag], ) -> AEADEncryptionContext: ... @typing.overload def encryptor( - self: "_CIPHER_TYPE", + self: _CIPHER_TYPE, ) -> CipherContext: ... @@ -121,13 +121,13 @@ def encryptor(self): @typing.overload def decryptor( - self: "Cipher[modes.ModeWithAuthenticationTag]", + self: Cipher[modes.ModeWithAuthenticationTag], ) -> AEADDecryptionContext: ... @typing.overload def decryptor( - self: "_CIPHER_TYPE", + self: _CIPHER_TYPE, ) -> CipherContext: ... @@ -140,7 +140,7 @@ def decryptor(self): return self._wrap_ctx(ctx, encrypt=False) def _wrap_ctx( - self, ctx: "_BackendCipherContext", encrypt: bool + self, ctx: _BackendCipherContext, encrypt: bool ) -> typing.Union[ AEADEncryptionContext, AEADDecryptionContext, CipherContext ]: @@ -165,9 +165,9 @@ def _wrap_ctx( class _CipherContext(CipherContext): - _ctx: typing.Optional["_BackendCipherContext"] + _ctx: typing.Optional[_BackendCipherContext] - def __init__(self, ctx: "_BackendCipherContext") -> None: + def __init__(self, ctx: _BackendCipherContext) -> None: self._ctx = ctx def update(self, data: bytes) -> bytes: @@ -189,10 +189,10 @@ def finalize(self) -> bytes: class _AEADCipherContext(AEADCipherContext): - _ctx: typing.Optional["_BackendCipherContext"] + _ctx: typing.Optional[_BackendCipherContext] _tag: typing.Optional[bytes] - def __init__(self, ctx: "_BackendCipherContext") -> None: + def __init__(self, ctx: _BackendCipherContext) -> None: self._ctx = ctx self._bytes_processed = 0 self._aad_bytes_processed = 0 diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index d04e08c..d8ea188 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -2,6 +2,7 @@ # 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 @@ -16,7 +17,8 @@ class Mode(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def name(self) -> str: """ A string naming this mode (e.g. "ECB", "CBC"). @@ -31,7 +33,8 @@ def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def initialization_vector(self) -> bytes: """ The value of the initialization vector for this mode as bytes. @@ -39,7 +42,8 @@ def initialization_vector(self) -> bytes: class ModeWithTweak(Mode, metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def tweak(self) -> bytes: """ The value of the tweak for this mode as bytes. @@ -47,7 +51,8 @@ def tweak(self) -> bytes: class ModeWithNonce(Mode, metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def nonce(self) -> bytes: """ The value of the nonce for this mode as bytes. @@ -55,7 +60,8 @@ def nonce(self) -> bytes: class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def tag(self) -> typing.Optional[bytes]: """ The value of the tag supplied to the constructor of this mode. @@ -89,9 +95,7 @@ def _check_nonce_length( _Reasons.UNSUPPORTED_CIPHER, ) if len(nonce) * 8 != algorithm.block_size: - raise ValueError( - "Invalid nonce size ({}) for {}.".format(len(nonce), name) - ) + raise ValueError(f"Invalid nonce size ({len(nonce)}) for {name}.") def _check_iv_and_key_length( diff --git a/src/cryptography/hazmat/primitives/cmac.py b/src/cryptography/hazmat/primitives/cmac.py index e08d65e..8aa1d79 100644 --- a/src/cryptography/hazmat/primitives/cmac.py +++ b/src/cryptography/hazmat/primitives/cmac.py @@ -2,13 +2,12 @@ # 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, -) +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import ciphers if typing.TYPE_CHECKING: @@ -16,15 +15,15 @@ class CMAC: - _ctx: typing.Optional["_CMACContext"] + _ctx: typing.Optional[_CMACContext] _algorithm: ciphers.BlockCipherAlgorithm def __init__( self, algorithm: ciphers.BlockCipherAlgorithm, backend: typing.Any = None, - ctx: typing.Optional["_CMACContext"] = None, - ): + ctx: typing.Optional[_CMACContext] = None, + ) -> None: if not isinstance(algorithm, ciphers.BlockCipherAlgorithm): raise TypeError("Expected instance of BlockCipherAlgorithm.") self._algorithm = algorithm @@ -60,7 +59,7 @@ def verify(self, signature: bytes) -> None: ctx, self._ctx = self._ctx, None ctx.verify(signature) - def copy(self) -> "CMAC": + def copy(self) -> CMAC: if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") return CMAC(self._algorithm, ctx=self._ctx.copy()) diff --git a/src/cryptography/hazmat/primitives/constant_time.py b/src/cryptography/hazmat/primitives/constant_time.py index a02fa9c..3975c71 100644 --- a/src/cryptography/hazmat/primitives/constant_time.py +++ b/src/cryptography/hazmat/primitives/constant_time.py @@ -2,6 +2,7 @@ # 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 diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py index cc0771d..b6a7ff1 100644 --- a/src/cryptography/hazmat/primitives/hashes.py +++ b/src/cryptography/hazmat/primitives/hashes.py @@ -2,29 +2,55 @@ # 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 openssl as rust_openssl + +__all__ = [ + "HashAlgorithm", + "HashContext", + "Hash", + "ExtendableOutputFunction", + "SHA1", + "SHA512_224", + "SHA512_256", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA3_224", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "SHAKE128", + "SHAKE256", + "MD5", + "BLAKE2b", + "BLAKE2s", + "SM3", +] class HashAlgorithm(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def name(self) -> str: """ A string naming this algorithm (e.g. "sha256", "md5"). """ - @abc.abstractproperty + @property + @abc.abstractmethod def digest_size(self) -> int: """ The size of the resulting digest in bytes. """ - @abc.abstractproperty + @property + @abc.abstractmethod def block_size(self) -> typing.Optional[int]: """ The internal block size of the hash function, or None if the hash @@ -33,7 +59,8 @@ def block_size(self) -> typing.Optional[int]: class HashContext(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def algorithm(self) -> HashAlgorithm: """ A HashAlgorithm that will be used by this context. @@ -52,63 +79,22 @@ def finalize(self) -> bytes: """ @abc.abstractmethod - def copy(self) -> "HashContext": + 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 Hash(HashContext): - _ctx: typing.Optional[HashContext] - - def __init__( - self, - algorithm: HashAlgorithm, - backend: typing.Any = None, - ctx: typing.Optional["HashContext"] = None, - ): - if not isinstance(algorithm, HashAlgorithm): - raise TypeError("Expected instance of hashes.HashAlgorithm.") - self._algorithm = algorithm - - if ctx is None: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - self._ctx = ossl.create_hash_ctx(self.algorithm) - else: - self._ctx = ctx - - @property - def algorithm(self) -> HashAlgorithm: - return self._algorithm - - def update(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - utils._check_byteslike("data", data) - self._ctx.update(data) - - def copy(self) -> "Hash": - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return Hash(self.algorithm, ctx=self._ctx.copy()) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - digest = self._ctx.finalize() - self._ctx = None - return digest - - class SHA1(HashAlgorithm): name = "sha1" digest_size = 20 @@ -224,7 +210,6 @@ class BLAKE2b(HashAlgorithm): block_size = 128 def __init__(self, digest_size: int): - if digest_size != 64: raise ValueError("Digest size must be 64") @@ -242,7 +227,6 @@ class BLAKE2s(HashAlgorithm): _min_digest_size = 1 def __init__(self, digest_size: int): - if digest_size != 32: raise ValueError("Digest size must be 32") diff --git a/src/cryptography/hazmat/primitives/hmac.py b/src/cryptography/hazmat/primitives/hmac.py index 1577326..a9442d5 100644 --- a/src/cryptography/hazmat/primitives/hmac.py +++ b/src/cryptography/hazmat/primitives/hmac.py @@ -2,71 +2,12 @@ # 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, -) -from cryptography.hazmat.backends.openssl.hmac import _HMACContext +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import hashes +__all__ = ["HMAC"] -class HMAC(hashes.HashContext): - _ctx: typing.Optional[_HMACContext] - - def __init__( - self, - key: bytes, - algorithm: hashes.HashAlgorithm, - backend: typing.Any = None, - ctx=None, - ): - if not isinstance(algorithm, hashes.HashAlgorithm): - raise TypeError("Expected instance of hashes.HashAlgorithm.") - self._algorithm = algorithm - - self._key = key - if ctx is None: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - self._ctx = ossl.create_hmac_ctx(key, self.algorithm) - else: - self._ctx = ctx - - @property - def algorithm(self) -> hashes.HashAlgorithm: - return self._algorithm - - def update(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - utils._check_byteslike("data", data) - self._ctx.update(data) - - def copy(self) -> "HMAC": - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return HMAC( - self._key, - self.algorithm, - ctx=self._ctx.copy(), - ) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - digest = self._ctx.finalize() - self._ctx = None - return digest - - def verify(self, signature: bytes) -> None: - utils._check_bytes("signature", signature) - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - ctx, self._ctx = self._ctx, None - ctx.verify(signature) +HMAC = rust_openssl.hmac.HMAC +hashes.HashContext.register(HMAC) diff --git a/src/cryptography/hazmat/primitives/kdf/__init__.py b/src/cryptography/hazmat/primitives/kdf/__init__.py index 38e2f8b..79bb459 100644 --- a/src/cryptography/hazmat/primitives/kdf/__init__.py +++ b/src/cryptography/hazmat/primitives/kdf/__init__.py @@ -2,6 +2,7 @@ # 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 diff --git a/src/cryptography/hazmat/primitives/kdf/concatkdf.py b/src/cryptography/hazmat/primitives/kdf/concatkdf.py index 0b0262e..d5ea58a 100644 --- a/src/cryptography/hazmat/primitives/kdf/concatkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -2,14 +2,12 @@ # 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.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @@ -25,9 +23,7 @@ def _common_args_checks( ) -> None: max_length = algorithm.digest_size * (2**32 - 1) if length > max_length: - raise ValueError( - "Cannot derive keys larger than {} bits.".format(max_length) - ) + raise ValueError(f"Cannot derive keys larger than {max_length} bits.") if otherinfo is not None: utils._check_bytes("otherinfo", otherinfo) @@ -101,9 +97,7 @@ def __init__( self._otherinfo: bytes = otherinfo if otherinfo is not None else b"" if algorithm.block_size is None: - raise TypeError( - "{} is unsupported for ConcatKDF".format(algorithm.name) - ) + raise TypeError(f"{algorithm.name} is unsupported for ConcatKDF") if salt is None: salt = b"\x00" * algorithm.block_size diff --git a/src/cryptography/hazmat/primitives/kdf/hkdf.py b/src/cryptography/hazmat/primitives/kdf/hkdf.py index 44889b6..d476894 100644 --- a/src/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/hkdf.py @@ -2,14 +2,12 @@ # 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.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @@ -62,7 +60,7 @@ def __init__( if length > max_length: raise ValueError( - "Cannot derive keys larger than {} octets.".format(max_length) + f"Cannot derive keys larger than {max_length} octets." ) self._length = length diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py index 7f185a9..9677638 100644 --- a/src/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -2,6 +2,8 @@ # 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 diff --git a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py index 8d23f8c..623e1ca 100644 --- a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -2,6 +2,7 @@ # 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 @@ -12,6 +13,7 @@ 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 @@ -48,15 +50,12 @@ def derive(self, key_material: bytes) -> bytes: raise AlreadyFinalized("PBKDF2 instances can only be used once.") self._used = True - utils._check_byteslike("key_material", key_material) - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.derive_pbkdf2_hmac( + return rust_openssl.kdf.derive_pbkdf2_hmac( + key_material, self._algorithm, - self._length, self._salt, self._iterations, - key_material, + self._length, ) def verify(self, key_material: bytes, expected_key: bytes) -> None: diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py index ff81bbb..05a4f67 100644 --- a/src/cryptography/hazmat/primitives/kdf/scrypt.py +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -2,6 +2,7 @@ # 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 @@ -12,10 +13,10 @@ 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 @@ -62,10 +63,15 @@ def derive(self, key_material: bytes) -> bytes: self._used = True utils._check_byteslike("key_material", key_material) - from cryptography.hazmat.backends.openssl.backend import backend - return backend.derive_scrypt( - key_material, self._salt, self._length, self._n, self._r, self._p + 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: diff --git a/src/cryptography/hazmat/primitives/kdf/x963kdf.py b/src/cryptography/hazmat/primitives/kdf/x963kdf.py index aa6bcc1..17acc51 100644 --- a/src/cryptography/hazmat/primitives/kdf/x963kdf.py +++ b/src/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -2,14 +2,12 @@ # 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.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import constant_time, hashes from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @@ -28,9 +26,7 @@ def __init__( ): max_len = algorithm.digest_size * (2**32 - 1) if length > max_len: - raise ValueError( - "Cannot derive keys larger than {} bits.".format(max_len) - ) + raise ValueError(f"Cannot derive keys larger than {max_len} bits.") if sharedinfo is not None: utils._check_bytes("sharedinfo", sharedinfo) diff --git a/src/cryptography/hazmat/primitives/keywrap.py b/src/cryptography/hazmat/primitives/keywrap.py index 64771ca..59b0326 100644 --- a/src/cryptography/hazmat/primitives/keywrap.py +++ b/src/cryptography/hazmat/primitives/keywrap.py @@ -2,6 +2,7 @@ # 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 diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index d6c1d91..fde3094 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -2,6 +2,7 @@ # 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 diff --git a/src/cryptography/hazmat/primitives/poly1305.py b/src/cryptography/hazmat/primitives/poly1305.py index 7fcf4a5..7f5a77a 100644 --- a/src/cryptography/hazmat/primitives/poly1305.py +++ b/src/cryptography/hazmat/primitives/poly1305.py @@ -2,59 +2,10 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import typing +from __future__ import annotations -from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends.openssl.poly1305 import _Poly1305Context +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +__all__ = ["Poly1305"] -class Poly1305: - _ctx: typing.Optional[_Poly1305Context] - - def __init__(self, key: bytes): - from cryptography.hazmat.backends.openssl.backend import backend - - if not backend.poly1305_supported(): - raise UnsupportedAlgorithm( - "poly1305 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_MAC, - ) - self._ctx = backend.create_poly1305_ctx(key) - - def update(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - utils._check_byteslike("data", data) - self._ctx.update(data) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - mac = self._ctx.finalize() - self._ctx = None - return mac - - def verify(self, tag: bytes) -> None: - utils._check_bytes("tag", tag) - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - ctx, self._ctx = self._ctx, None - ctx.verify(tag) - - @classmethod - def generate_tag(cls, key: bytes, data: bytes) -> bytes: - p = Poly1305(key) - p.update(data) - return p.finalize() - - @classmethod - def verify_tag(cls, key: bytes, data: bytes, tag: bytes) -> None: - p = Poly1305(key) - p.update(data) - p.verify(tag) +Poly1305 = rust_openssl.poly1305.Poly1305 diff --git a/src/cryptography/hazmat/primitives/serialization/__init__.py b/src/cryptography/hazmat/primitives/serialization/__init__.py index 6024150..b6c9a5c 100644 --- a/src/cryptography/hazmat/primitives/serialization/__init__.py +++ b/src/cryptography/hazmat/primitives/serialization/__init__.py @@ -2,6 +2,7 @@ # 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, @@ -22,11 +23,18 @@ 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__ = [ "load_der_parameters", "load_der_private_key", @@ -35,6 +43,7 @@ "load_pem_private_key", "load_pem_public_key", "load_ssh_private_key", + "load_ssh_public_identity", "load_ssh_public_key", "Encoding", "PrivateFormat", @@ -44,4 +53,11 @@ "BestAvailableEncryption", "NoEncryption", "_KeySerializationEncryption", + "SSHCertificateBuilder", + "SSHCertificate", + "SSHCertificateType", + "SSHCertPublicKeyTypes", + "SSHCertPrivateKeyTypes", + "SSHPrivateKeyTypes", + "SSHPublicKeyTypes", ] diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py index 059b6e4..18a96cc 100644 --- a/src/cryptography/hazmat/primitives/serialization/base.py +++ b/src/cryptography/hazmat/primitives/serialization/base.py @@ -2,13 +2,14 @@ # 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.asymmetric import dh from cryptography.hazmat.primitives.asymmetric.types import ( - PRIVATE_KEY_TYPES, - PUBLIC_KEY_TYPES, + PrivateKeyTypes, + PublicKeyTypes, ) @@ -16,15 +17,19 @@ def load_pem_private_key( data: bytes, password: typing.Optional[bytes], backend: typing.Any = None, -) -> PRIVATE_KEY_TYPES: + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: from cryptography.hazmat.backends.openssl.backend import backend as ossl - return ossl.load_pem_private_key(data, password) + return ossl.load_pem_private_key( + data, password, unsafe_skip_rsa_key_validation + ) def load_pem_public_key( data: bytes, backend: typing.Any = None -) -> PUBLIC_KEY_TYPES: +) -> PublicKeyTypes: from cryptography.hazmat.backends.openssl.backend import backend as ossl return ossl.load_pem_public_key(data) @@ -32,7 +37,7 @@ def load_pem_public_key( def load_pem_parameters( data: bytes, backend: typing.Any = None -) -> "dh.DHParameters": +) -> dh.DHParameters: from cryptography.hazmat.backends.openssl.backend import backend as ossl return ossl.load_pem_parameters(data) @@ -42,15 +47,19 @@ def load_der_private_key( data: bytes, password: typing.Optional[bytes], backend: typing.Any = None, -) -> PRIVATE_KEY_TYPES: + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: from cryptography.hazmat.backends.openssl.backend import backend as ossl - return ossl.load_der_private_key(data, password) + return ossl.load_der_private_key( + data, password, unsafe_skip_rsa_key_validation + ) def load_der_public_key( data: bytes, backend: typing.Any = None -) -> PUBLIC_KEY_TYPES: +) -> PublicKeyTypes: from cryptography.hazmat.backends.openssl.backend import backend as ossl return ossl.load_der_public_key(data) @@ -58,7 +67,7 @@ def load_der_public_key( def load_der_parameters( data: bytes, backend: typing.Any = None -) -> "dh.DHParameters": +) -> dh.DHParameters: from cryptography.hazmat.backends.openssl.backend import backend as ossl return ossl.load_der_parameters(data) diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py index 662ea75..27133a3 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -2,6 +2,8 @@ # 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 @@ -10,16 +12,15 @@ from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, - ed25519, ed448, + ed25519, rsa, ) -from cryptography.hazmat.primitives.asymmetric.types import ( - PRIVATE_KEY_TYPES, -) +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes __all__ = [ "PBES", + "PKCS12PrivateKeyTypes", "PKCS12Certificate", "PKCS12KeyAndCertificates", "load_key_and_certificates", @@ -27,7 +28,7 @@ "serialize_key_and_certificates", ] -_ALLOWED_PKCS12_TYPES = typing.Union[ +PKCS12PrivateKeyTypes = typing.Union[ rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, @@ -78,7 +79,7 @@ def __repr__(self) -> str: class PKCS12KeyAndCertificates: def __init__( self, - key: typing.Optional[PRIVATE_KEY_TYPES], + key: typing.Optional[PrivateKeyTypes], cert: typing.Optional[PKCS12Certificate], additional_certs: typing.List[PKCS12Certificate], ): @@ -111,7 +112,7 @@ def __init__( self._additional_certs = additional_certs @property - def key(self) -> typing.Optional[PRIVATE_KEY_TYPES]: + def key(self) -> typing.Optional[PrivateKeyTypes]: return self._key @property @@ -147,7 +148,7 @@ def load_key_and_certificates( password: typing.Optional[bytes], backend: typing.Any = None, ) -> typing.Tuple[ - typing.Optional[PRIVATE_KEY_TYPES], + typing.Optional[PrivateKeyTypes], typing.Optional[x509.Certificate], typing.List[x509.Certificate], ]: @@ -166,7 +167,7 @@ def load_pkcs12( return ossl.load_pkcs12(data, password) -_PKCS12_CAS_TYPES = typing.Union[ +_PKCS12CATypes = typing.Union[ x509.Certificate, PKCS12Certificate, ] @@ -174,9 +175,9 @@ def load_pkcs12( def serialize_key_and_certificates( name: typing.Optional[bytes], - key: typing.Optional[_ALLOWED_PKCS12_TYPES], + key: typing.Optional[PKCS12PrivateKeyTypes], cert: typing.Optional[x509.Certificate], - cas: typing.Optional[typing.Iterable[_PKCS12_CAS_TYPES]], + cas: typing.Optional[typing.Iterable[_PKCS12CATypes]], encryption_algorithm: serialization.KeySerializationEncryption, ) -> bytes: if key is not None and not isinstance( diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py index 6641416..9998bca 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -2,10 +2,17 @@ # 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 -from cryptography import x509 +from cryptography import utils, x509 +from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.utils import _check_byteslike @@ -27,20 +34,17 @@ def serialize_certificates( certs: typing.List[x509.Certificate], encoding: serialization.Encoding, ) -> bytes: - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.pkcs7_serialize_certificates(certs, encoding) + return rust_pkcs7.serialize_certificates(certs, encoding) -_ALLOWED_PKCS7_HASH_TYPES = typing.Union[ - hashes.SHA1, +PKCS7HashTypes = typing.Union[ hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, ] -_ALLOWED_PRIVATE_KEY_TYPES = typing.Union[ +PKCS7PrivateKeyTypes = typing.Union[ rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey ] @@ -61,8 +65,8 @@ def __init__( signers: typing.List[ typing.Tuple[ x509.Certificate, - _ALLOWED_PRIVATE_KEY_TYPES, - _ALLOWED_PKCS7_HASH_TYPES, + PKCS7PrivateKeyTypes, + PKCS7HashTypes, ] ] = [], additional_certs: typing.List[x509.Certificate] = [], @@ -71,7 +75,7 @@ def __init__( self._signers = signers self._additional_certs = additional_certs - def set_data(self, data: bytes) -> "PKCS7SignatureBuilder": + 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") @@ -81,13 +85,12 @@ def set_data(self, data: bytes) -> "PKCS7SignatureBuilder": def add_signer( self, certificate: x509.Certificate, - private_key: _ALLOWED_PRIVATE_KEY_TYPES, - hash_algorithm: _ALLOWED_PKCS7_HASH_TYPES, - ) -> "PKCS7SignatureBuilder": + private_key: PKCS7PrivateKeyTypes, + hash_algorithm: PKCS7HashTypes, + ) -> PKCS7SignatureBuilder: if not isinstance( hash_algorithm, ( - hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, @@ -95,7 +98,7 @@ def add_signer( ), ): raise TypeError( - "hash_algorithm must be one of hashes.SHA1, SHA224, " + "hash_algorithm must be one of hashes.SHA224, " "SHA256, SHA384, or SHA512" ) if not isinstance(certificate, x509.Certificate): @@ -113,7 +116,7 @@ def add_signer( def add_certificate( self, certificate: x509.Certificate - ) -> "PKCS7SignatureBuilder": + ) -> PKCS7SignatureBuilder: if not isinstance(certificate, x509.Certificate): raise TypeError("certificate must be a x509.Certificate") @@ -173,8 +176,60 @@ def sign( "both values." ) - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) + return rust_pkcs7.sign_and_serialize(self, encoding, options) + - return ossl.pkcs7_sign(self, encoding, options) +def _smime_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() + + +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) diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index 7125bad..35e53c1 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -2,17 +2,34 @@ # 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.asymmetric import dsa, ec, ed25519, rsa -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +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, @@ -47,6 +64,11 @@ def _bcrypt_kdf( _ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" _CERT_SUFFIX = b"-cert-v01@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-----" @@ -62,18 +84,51 @@ def _bcrypt_kdf( # padding for max blocksize _PADDING = memoryview(bytearray(range(1, 1 + 16))) + +@dataclass +class _SSHCipher: + alg: typing.Type[algorithms.AES] + key_len: int + mode: typing.Union[ + typing.Type[modes.CTR], + typing.Type[modes.CBC], + typing.Type[modes.GCM], + ] + block_len: int + iv_len: int + tag_len: typing.Optional[int] + is_aead: bool + + # ciphers that are actually used in key wrapping -_SSH_CIPHERS: typing.Dict[ - bytes, - typing.Tuple[ - typing.Type[algorithms.AES], - int, - typing.Union[typing.Type[modes.CTR], typing.Type[modes.CBC]], - int, - ], -] = { - b"aes256-ctr": (algorithms.AES, 32, modes.CTR, 16), - b"aes256-cbc": (algorithms.AES, 32, modes.CBC, 16), +_SSH_CIPHERS: typing.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 @@ -84,6 +139,27 @@ def _bcrypt_kdf( } +def _get_ssh_key_type( + key: typing.Union[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 @@ -119,14 +195,19 @@ def _init_cipher( password: typing.Optional[bytes], salt: bytes, rounds: int, -) -> Cipher[typing.Union[modes.CBC, modes.CTR]]: +) -> Cipher[typing.Union[modes.CBC, modes.CTR, modes.GCM]]: """Generate key + iv and return cipher.""" if not password: raise ValueError("Key is password-protected.") - algo, key_len, mode, iv_len = _SSH_CIPHERS[ciphername] - seed = _bcrypt_kdf(password, salt, key_len + iv_len, rounds, True) - return Cipher(algo(seed[:key_len]), mode(seed[key_len:])) + 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) -> typing.Tuple[int, memoryview]: @@ -189,7 +270,11 @@ def put_u32(self, val: int) -> None: """Big-endian uint32""" self.flist.append(val.to_bytes(length=4, byteorder="big")) - def put_sshstr(self, val: typing.Union[bytes, "_FragList"]) -> None: + 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: typing.Union[bytes, _FragList]) -> None: """Bytes prefixed with u32 length""" if isinstance(val, (bytes, memoryview, bytearray)): self.put_u32(len(val)) @@ -520,7 +605,7 @@ def _lookup_kformat(key_type: bytes): raise UnsupportedAlgorithm(f"Unsupported key type: {key_type!r}") -_SSH_PRIVATE_KEY_TYPES = typing.Union[ +SSHPrivateKeyTypes = typing.Union[ ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey, dsa.DSAPrivateKey, @@ -532,7 +617,7 @@ def load_ssh_private_key( data: bytes, password: typing.Optional[bytes], backend: typing.Any = None, -) -> _SSH_PRIVATE_KEY_TYPES: +) -> SSHPrivateKeyTypes: """Load private key from OpenSSH custom encoding.""" utils._check_byteslike("data", data) if password is not None: @@ -563,10 +648,6 @@ def load_ssh_private_key( pubfields, pubdata = kformat.get_public(pubdata) _check_empty(pubdata) - # load secret data - edata, data = _get_sshstr(data) - _check_empty(data) - if (ciphername, kdfname) != (_NONE, _NONE): ciphername_bytes = ciphername.tobytes() if ciphername_bytes not in _SSH_CIPHERS: @@ -575,14 +656,36 @@ def load_ssh_private_key( ) if kdfname != _BCRYPT: raise UnsupportedAlgorithm(f"Unsupported KDF: {kdfname!r}") - blklen = _SSH_CIPHERS[ciphername_bytes][3] + 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) - edata = memoryview(ciph.decryptor().update(edata)) + 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) @@ -602,34 +705,40 @@ def load_ssh_private_key( 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: _SSH_PRIVATE_KEY_TYPES, + 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, + ) - if isinstance(private_key, ec.EllipticCurvePrivateKey): - key_type = _ecdsa_key_type(private_key.public_key()) - elif isinstance(private_key, rsa.RSAPrivateKey): - key_type = _SSH_RSA - elif isinstance(private_key, dsa.DSAPrivateKey): - key_type = _SSH_DSA - elif isinstance(private_key, ed25519.Ed25519PrivateKey): - key_type = _SSH_ED25519 - else: - raise ValueError("Unsupported key type") + 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][3] + blklen = _SSH_CIPHERS[ciphername].block_len kdfname = _BCRYPT rounds = _DEFAULT_ROUNDS if ( @@ -684,18 +793,168 @@ def _serialize_ssh_private_key( return _ssh_pem_encode(buf[:mlen]) -_SSH_PUBLIC_KEY_TYPES = typing.Union[ +SSHPublicKeyTypes = typing.Union[ ec.EllipticCurvePublicKey, rsa.RSAPublicKey, dsa.DSAPublicKey, ed25519.Ed25519PublicKey, ] +SSHCertPublicKeyTypes = typing.Union[ + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + ed25519.Ed25519PublicKey, +] + -def load_ssh_public_key( - data: bytes, backend: typing.Any = None -) -> _SSH_PUBLIC_KEY_TYPES: - """Load public key from OpenSSH one-line format.""" +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: typing.List[bytes], + _valid_after: int, + _valid_before: int, + _critical_options: typing.Dict[bytes, bytes], + _extensions: typing.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) -> typing.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) -> typing.Dict[bytes, bytes]: + return self._critical_options + + @property + def extensions(self) -> typing.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, +) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: utils._check_byteslike("data", data) m = _SSH_PUBKEY_RC.match(data) @@ -704,16 +963,22 @@ def load_ssh_public_key( key_type = orig_key_type = m.group(1) key_body = m.group(2) with_cert = False - if _CERT_SUFFIX == key_type[-len(_CERT_SUFFIX) :]: + 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 key format") + 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") @@ -725,29 +990,128 @@ def load_ssh_public_key( 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) - extensions, rest = _get_sshstr(rest) - reserved, rest = _get_sshstr(rest) - sig_key, rest = _get_sshstr(rest) - signature, rest = _get_sshstr(rest) - _check_empty(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, +) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: + return _load_ssh_public_identity(data) + + +def _parse_exts_opts(exts_opts: memoryview) -> typing.Dict[bytes, bytes]: + result: typing.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: + try: + value, extra = _get_sshstr(value) + except ValueError: + warnings.warn( + "This certificate has an incorrect encoding for critical " + "options or extensions. This will be an exception in " + "cryptography 42", + utils.DeprecatedIn41, + stacklevel=4, + ) + else: + 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: _SSH_PUBLIC_KEY_TYPES) -> bytes: +def serialize_ssh_public_key(public_key: SSHPublicKeyTypes) -> bytes: """One-line public key format for OpenSSH""" - if isinstance(public_key, ec.EllipticCurvePublicKey): - key_type = _ecdsa_key_type(public_key) - elif isinstance(public_key, rsa.RSAPublicKey): - key_type = _SSH_RSA - elif isinstance(public_key, dsa.DSAPublicKey): - key_type = _SSH_DSA - elif isinstance(public_key, ed25519.Ed25519PublicKey): - key_type = _SSH_ED25519 - else: - raise ValueError("Unsupported key type") + 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() @@ -756,3 +1120,415 @@ def serialize_ssh_public_key(public_key: _SSH_PUBLIC_KEY_TYPES) -> bytes: 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: typing.Optional[SSHCertPublicKeyTypes] = None, + _serial: typing.Optional[int] = None, + _type: typing.Optional[SSHCertificateType] = None, + _key_id: typing.Optional[bytes] = None, + _valid_principals: typing.List[bytes] = [], + _valid_for_all_principals: bool = False, + _valid_before: typing.Optional[int] = None, + _valid_after: typing.Optional[int] = None, + _critical_options: typing.List[typing.Tuple[bytes, bytes]] = [], + _extensions: typing.List[typing.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: typing.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: typing.Union[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: typing.Union[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])), + ) diff --git a/src/cryptography/hazmat/primitives/twofactor/__init__.py b/src/cryptography/hazmat/primitives/twofactor/__init__.py index 8a8b30f..c1af423 100644 --- a/src/cryptography/hazmat/primitives/twofactor/__init__.py +++ b/src/cryptography/hazmat/primitives/twofactor/__init__.py @@ -2,6 +2,8 @@ # 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 diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py index 9730af2..2067108 100644 --- a/src/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -2,6 +2,7 @@ # 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 @@ -11,12 +12,11 @@ from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 from cryptography.hazmat.primitives.twofactor import InvalidToken - -_ALLOWED_HASH_TYPES = typing.Union[SHA1, SHA256, SHA512] +HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512] def _generate_uri( - hotp: "HOTP", + hotp: HOTP, type_name: str, account_name: str, issuer: typing.Optional[str], @@ -46,7 +46,7 @@ def __init__( self, key: bytes, length: int, - algorithm: _ALLOWED_HASH_TYPES, + algorithm: HOTPHashTypes, backend: typing.Any = None, enforce_key_length: bool = True, ) -> None: @@ -57,7 +57,7 @@ def __init__( 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 to 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.") diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py index 317baba..daddcea 100644 --- a/src/cryptography/hazmat/primitives/twofactor/totp.py +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -2,13 +2,15 @@ # 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, - _ALLOWED_HASH_TYPES, + HOTPHashTypes, _generate_uri, ) @@ -18,7 +20,7 @@ def __init__( self, key: bytes, length: int, - algorithm: _ALLOWED_HASH_TYPES, + algorithm: HOTPHashTypes, time_step: int, backend: typing.Any = None, enforce_key_length: bool = True, diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 67d813b..7191681 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -2,10 +2,9 @@ # 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 enum -import inspect import sys import types import typing @@ -21,23 +20,22 @@ class CryptographyDeprecationWarning(UserWarning): # 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. -PersistentlyDeprecated2019 = CryptographyDeprecationWarning -DeprecatedIn35 = CryptographyDeprecationWarning DeprecatedIn36 = CryptographyDeprecationWarning DeprecatedIn37 = CryptographyDeprecationWarning -DeprecatedIn38 = CryptographyDeprecationWarning +DeprecatedIn40 = CryptographyDeprecationWarning +DeprecatedIn41 = CryptographyDeprecationWarning def _check_bytes(name: str, value: bytes) -> None: if not isinstance(value, bytes): - raise TypeError("{} must be bytes".format(name)) + raise TypeError(f"{name} must be bytes") def _check_byteslike(name: str, value: bytes) -> None: try: memoryview(value) except TypeError: - raise TypeError("{} must be bytes-like".format(name)) + raise TypeError(f"{name} must be bytes-like") def int_to_bytes(integer: int, length: typing.Optional[int] = None) -> bytes: @@ -46,41 +44,15 @@ def int_to_bytes(integer: int, length: typing.Optional[int] = None) -> bytes: ) -class InterfaceNotImplemented(Exception): - pass - +def _extract_buffer_length(obj: typing.Any) -> typing.Tuple[typing.Any, int]: + from cryptography.hazmat.bindings._rust import _openssl -def strip_annotation(signature: inspect.Signature) -> inspect.Signature: - return inspect.Signature( - [ - param.replace(annotation=inspect.Parameter.empty) - for param in signature.parameters.values() - ] - ) + buf = _openssl.ffi.from_buffer(obj) + return buf, int(_openssl.ffi.cast("uintptr_t", buf)) -def verify_interface( - iface: abc.ABCMeta, klass: object, *, check_annotations: bool = False -): - for method in iface.__abstractmethods__: - if not hasattr(klass, method): - raise InterfaceNotImplemented( - "{} is missing a {!r} method".format(klass, method) - ) - if isinstance(getattr(iface, method), abc.abstractproperty): - # Can't properly verify these yet. - continue - sig = inspect.signature(getattr(iface, method)) - actual = inspect.signature(getattr(klass, method)) - if check_annotations: - ok = sig == actual - else: - ok = strip_annotation(sig) == strip_annotation(actual) - if not ok: - raise InterfaceNotImplemented( - "{}.{}'s signature differs from the expected. Expected: " - "{!r}. Received: {!r}".format(klass, method, sig, actual) - ) +class InterfaceNotImplemented(Exception): + pass class _DeprecatedValue: @@ -134,7 +106,7 @@ def deprecated( def cached_property(func: typing.Callable) -> property: - cached_name = "_cached_{}".format(func) + cached_name = f"_cached_{func}" sentinel = object() def inner(instance: object): diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 906075d..d77694a 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -2,6 +2,7 @@ # 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 from cryptography.x509.base import ( @@ -22,6 +23,7 @@ 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, @@ -31,19 +33,19 @@ AuthorityInformationAccess, AuthorityKeyIdentifier, BasicConstraints, + CertificateIssuer, + CertificatePolicies, CRLDistributionPoints, CRLNumber, CRLReason, - CertificateIssuer, - CertificatePolicies, DeltaCRLIndicator, DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension, ExtensionNotFound, - ExtensionType, Extensions, + ExtensionType, FreshestCRL, GeneralNames, InhibitAnyPolicy, @@ -51,14 +53,16 @@ IssuerAlternativeName, IssuingDistributionPoint, KeyUsage, + MSCertificateTemplate, NameConstraints, NoticeReference, + OCSPAcceptableResponses, OCSPNoCheck, OCSPNonce, PolicyConstraints, PolicyInformation, - PrecertPoison, PrecertificateSignedCertificateTimestamps, + PrecertPoison, ReasonFlags, SignedCertificateTimestamps, SubjectAlternativeName, @@ -70,13 +74,13 @@ UserNotice, ) from cryptography.x509.general_name import ( - DNSName, DirectoryName, + DNSName, GeneralName, IPAddress, OtherName, - RFC822Name, RegisteredID, + RFC822Name, UniformResourceIdentifier, UnsupportedGeneralNameType, ) @@ -87,8 +91,8 @@ ) from cryptography.x509.oid import ( AuthorityInformationAccessOID, - CRLEntryExtensionOID, CertificatePoliciesOID, + CRLEntryExtensionOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, @@ -96,7 +100,6 @@ SignatureAlgorithmOID, ) - OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS OID_AUTHORITY_KEY_IDENTIFIER = ExtensionOID.AUTHORITY_KEY_IDENTIFIER OID_BASIC_CONSTRAINTS = ExtensionOID.BASIC_CONSTRAINTS @@ -169,6 +172,7 @@ __all__ = [ "certificate_transparency", "load_pem_x509_certificate", + "load_pem_x509_certificates", "load_der_x509_certificate", "load_pem_x509_csr", "load_der_x509_csr", @@ -195,6 +199,7 @@ "IssuingDistributionPoint", "TLSFeature", "TLSFeatureType", + "OCSPAcceptableResponses", "OCSPNoCheck", "BasicConstraints", "CRLNumber", @@ -246,4 +251,5 @@ "SignedCertificateTimestamps", "SignatureAlgorithmOID", "NameOID", + "MSCertificateTemplate", ] diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 9f6c41a..576385e 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -2,6 +2,7 @@ # 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 @@ -14,33 +15,46 @@ from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, - ed25519, ed448, + ed25519, + padding, rsa, - x25519, x448, + x25519, ) from cryptography.hazmat.primitives.asymmetric.types import ( - CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES, - CERTIFICATE_PRIVATE_KEY_TYPES, - CERTIFICATE_PUBLIC_KEY_TYPES, + CertificateIssuerPrivateKeyTypes, + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, ) from cryptography.x509.extensions import ( Extension, - ExtensionType, 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(AttributeNotFound, self).__init__(msg) + super().__init__(msg) self.oid = oid @@ -100,7 +114,7 @@ def value(self) -> bytes: return self._value def __repr__(self) -> str: - return "".format(self.oid, self.value) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, Attribute): @@ -126,14 +140,14 @@ def __init__( __len__, __iter__, __getitem__ = _make_sequence_methods("_attributes") def __repr__(self) -> str: - return "".format(self._attributes) + return f"" def get_attribute_for_oid(self, oid: ObjectIdentifier) -> Attribute: for attr in self: if attr.oid == oid: return attr - raise AttributeNotFound("No {} attribute was found".format(oid), oid) + raise AttributeNotFound(f"No {oid} attribute was found", oid) class Version(utils.Enum): @@ -143,7 +157,7 @@ class Version(utils.Enum): class InvalidVersion(Exception): def __init__(self, msg: str, parsed_version: int) -> None: - super(InvalidVersion, self).__init__(msg) + super().__init__(msg) self.parsed_version = parsed_version @@ -154,49 +168,56 @@ def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: Returns bytes using digest passed. """ - @abc.abstractproperty + @property + @abc.abstractmethod def serial_number(self) -> int: """ Returns certificate serial number """ - @abc.abstractproperty + @property + @abc.abstractmethod def version(self) -> Version: """ Returns the certificate version """ @abc.abstractmethod - def public_key(self) -> CERTIFICATE_PUBLIC_KEY_TYPES: + def public_key(self) -> CertificatePublicKeyTypes: """ Returns the public key """ - @abc.abstractproperty + @property + @abc.abstractmethod def not_valid_before(self) -> datetime.datetime: """ Not before time (represented as UTC datetime) """ - @abc.abstractproperty + @property + @abc.abstractmethod def not_valid_after(self) -> datetime.datetime: """ Not after time (represented as UTC datetime) """ - @abc.abstractproperty + @property + @abc.abstractmethod def issuer(self) -> Name: """ Returns the issuer name object. """ - @abc.abstractproperty + @property + @abc.abstractmethod def subject(self) -> Name: """ Returns the subject name object. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_hash_algorithm( self, ) -> typing.Optional[hashes.HashAlgorithm]: @@ -205,31 +226,45 @@ def signature_hash_algorithm( in the certificate. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ - @abc.abstractproperty + @property + @abc.abstractmethod + def signature_algorithm_parameters( + self, + ) -> typing.Union[None, padding.PSS, padding.PKCS1v15, ec.ECDSA]: + """ + Returns the signature algorithm parameters. + """ + + @property + @abc.abstractmethod def extensions(self) -> Extensions: """ Returns an Extensions object. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature bytes. """ - @abc.abstractproperty + @property + @abc.abstractmethod def tbs_certificate_bytes(self) -> bytes: """ Returns the tbsCertificate payload bytes as defined in RFC 5280. """ - @abc.abstractproperty + @property + @abc.abstractmethod def tbs_precertificate_bytes(self) -> bytes: """ Returns the tbsCertificate payload bytes with the SCT list extension @@ -254,25 +289,36 @@ 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): - @abc.abstractproperty + @property + @abc.abstractmethod def serial_number(self) -> int: """ Returns the serial number of the revoked certificate. """ - @abc.abstractproperty + @property + @abc.abstractmethod def revocation_date(self) -> datetime.datetime: """ Returns the date of when this certificate was revoked. """ - @abc.abstractproperty + @property + @abc.abstractmethod def extensions(self) -> Extensions: """ Returns an Extensions object containing a list of Revoked extensions. @@ -329,7 +375,8 @@ def get_revoked_certificate_by_serial_number( is not in the CRL. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_hash_algorithm( self, ) -> typing.Optional[hashes.HashAlgorithm]: @@ -338,43 +385,50 @@ def signature_hash_algorithm( in the certificate. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ - @abc.abstractproperty + @property + @abc.abstractmethod def issuer(self) -> Name: """ Returns the X509Name with the issuer of this CRL. """ - @abc.abstractproperty + @property + @abc.abstractmethod def next_update(self) -> typing.Optional[datetime.datetime]: """ Returns the date of next update for this CRL. """ - @abc.abstractproperty + @property + @abc.abstractmethod def last_update(self) -> datetime.datetime: """ Returns the date of last update for this CRL. """ - @abc.abstractproperty + @property + @abc.abstractmethod def extensions(self) -> Extensions: """ Returns an Extensions object containing a list of CRL extensions. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature bytes. """ - @abc.abstractproperty + @property + @abc.abstractmethod def tbs_certlist_bytes(self) -> bytes: """ Returns the tbsCertList payload bytes as defined in RFC 5280. @@ -416,7 +470,7 @@ def __iter__(self) -> typing.Iterator[RevokedCertificate]: @abc.abstractmethod def is_signature_valid( - self, public_key: CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES + self, public_key: CertificateIssuerPublicKeyTypes ) -> bool: """ Verifies signature of revocation list against given public key. @@ -440,18 +494,20 @@ def __hash__(self) -> int: """ @abc.abstractmethod - def public_key(self) -> CERTIFICATE_PUBLIC_KEY_TYPES: + def public_key(self) -> CertificatePublicKeyTypes: """ Returns the public key """ - @abc.abstractproperty + @property + @abc.abstractmethod def subject(self) -> Name: """ Returns the subject name object. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_hash_algorithm( self, ) -> typing.Optional[hashes.HashAlgorithm]: @@ -460,19 +516,22 @@ def signature_hash_algorithm( in the certificate. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_algorithm_oid(self) -> ObjectIdentifier: """ Returns the ObjectIdentifier of the signature algorithm. """ - @abc.abstractproperty + @property + @abc.abstractmethod def extensions(self) -> Extensions: """ Returns the extensions in the signing request. """ - @abc.abstractproperty + @property + @abc.abstractmethod def attributes(self) -> Attributes: """ Returns an Attributes object. @@ -484,20 +543,23 @@ def public_bytes(self, encoding: serialization.Encoding) -> bytes: Encodes the request to PEM or DER format. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature bytes. """ - @abc.abstractproperty + @property + @abc.abstractmethod def tbs_certrequest_bytes(self) -> bytes: """ Returns the PKCS#10 CertificationRequestInfo bytes as defined in RFC 2986. """ - @abc.abstractproperty + @property + @abc.abstractmethod def is_signature_valid(self) -> bool: """ Verifies signature of signing request. @@ -521,6 +583,10 @@ def load_pem_x509_certificate( return rust_x509.load_pem_x509_certificate(data) +def load_pem_x509_certificates(data: bytes) -> typing.List[Certificate]: + return rust_x509.load_pem_x509_certificates(data) + + # Backend argument preserved for API compatibility, but ignored. def load_der_x509_certificate( data: bytes, backend: typing.Any = None @@ -572,7 +638,7 @@ def __init__( self._extensions = extensions self._attributes = attributes - def subject_name(self, name: Name) -> "CertificateSigningRequestBuilder": + def subject_name(self, name: Name) -> CertificateSigningRequestBuilder: """ Sets the certificate requestor's distinguished name. """ @@ -586,7 +652,7 @@ def subject_name(self, name: Name) -> "CertificateSigningRequestBuilder": def add_extension( self, extval: ExtensionType, critical: bool - ) -> "CertificateSigningRequestBuilder": + ) -> CertificateSigningRequestBuilder: """ Adds an X.509 extension to the certificate request. """ @@ -608,7 +674,7 @@ def add_attribute( value: bytes, *, _tag: typing.Optional[_ASN1Type] = None, - ) -> "CertificateSigningRequestBuilder": + ) -> CertificateSigningRequestBuilder: """ Adds an X.509 attribute with an OID and associated value. """ @@ -636,8 +702,8 @@ def add_attribute( def sign( self, - private_key: CERTIFICATE_PRIVATE_KEY_TYPES, - algorithm: typing.Optional[hashes.HashAlgorithm], + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: typing.Optional[_AllowedHashTypes], backend: typing.Any = None, ) -> CertificateSigningRequest: """ @@ -655,7 +721,7 @@ def __init__( self, issuer_name: typing.Optional[Name] = None, subject_name: typing.Optional[Name] = None, - public_key: typing.Optional[CERTIFICATE_PUBLIC_KEY_TYPES] = None, + public_key: typing.Optional[CertificatePublicKeyTypes] = None, serial_number: typing.Optional[int] = None, not_valid_before: typing.Optional[datetime.datetime] = None, not_valid_after: typing.Optional[datetime.datetime] = None, @@ -670,7 +736,7 @@ def __init__( self._not_valid_after = not_valid_after self._extensions = extensions - def issuer_name(self, name: Name) -> "CertificateBuilder": + def issuer_name(self, name: Name) -> CertificateBuilder: """ Sets the CA's distinguished name. """ @@ -688,7 +754,7 @@ def issuer_name(self, name: Name) -> "CertificateBuilder": self._extensions, ) - def subject_name(self, name: Name) -> "CertificateBuilder": + def subject_name(self, name: Name) -> CertificateBuilder: """ Sets the requestor's distinguished name. """ @@ -708,8 +774,8 @@ def subject_name(self, name: Name) -> "CertificateBuilder": def public_key( self, - key: CERTIFICATE_PUBLIC_KEY_TYPES, - ) -> "CertificateBuilder": + key: CertificatePublicKeyTypes, + ) -> CertificateBuilder: """ Sets the requestor's public key (as found in the signing request). """ @@ -743,7 +809,7 @@ def public_key( self._extensions, ) - def serial_number(self, number: int) -> "CertificateBuilder": + def serial_number(self, number: int) -> CertificateBuilder: """ Sets the certificate serial number. """ @@ -770,9 +836,7 @@ def serial_number(self, number: int) -> "CertificateBuilder": self._extensions, ) - def not_valid_before( - self, time: datetime.datetime - ) -> "CertificateBuilder": + def not_valid_before(self, time: datetime.datetime) -> CertificateBuilder: """ Sets the certificate activation time. """ @@ -801,7 +865,7 @@ def not_valid_before( self._extensions, ) - def not_valid_after(self, time: datetime.datetime) -> "CertificateBuilder": + def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder: """ Sets the certificate expiration time. """ @@ -835,7 +899,7 @@ def not_valid_after(self, time: datetime.datetime) -> "CertificateBuilder": def add_extension( self, extval: ExtensionType, critical: bool - ) -> "CertificateBuilder": + ) -> CertificateBuilder: """ Adds an X.509 extension to the certificate. """ @@ -857,9 +921,13 @@ def add_extension( def sign( self, - private_key: CERTIFICATE_PRIVATE_KEY_TYPES, - algorithm: typing.Optional[hashes.HashAlgorithm], + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: typing.Optional[_AllowedHashTypes], backend: typing.Any = None, + *, + rsa_padding: typing.Optional[ + typing.Union[padding.PSS, padding.PKCS1v15] + ] = None, ) -> Certificate: """ Signs the certificate using the CA's private key. @@ -882,7 +950,15 @@ def sign( if self._public_key is None: raise ValueError("A certificate must have a public key") - return rust_x509.create_x509_certificate(self, private_key, algorithm) + 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: @@ -905,7 +981,7 @@ def __init__( def issuer_name( self, issuer_name: Name - ) -> "CertificateRevocationListBuilder": + ) -> CertificateRevocationListBuilder: if not isinstance(issuer_name, Name): raise TypeError("Expecting x509.Name object.") if self._issuer_name is not None: @@ -920,7 +996,7 @@ def issuer_name( def last_update( self, last_update: datetime.datetime - ) -> "CertificateRevocationListBuilder": + ) -> CertificateRevocationListBuilder: if not isinstance(last_update, datetime.datetime): raise TypeError("Expecting datetime object.") if self._last_update is not None: @@ -944,7 +1020,7 @@ def last_update( def next_update( self, next_update: datetime.datetime - ) -> "CertificateRevocationListBuilder": + ) -> CertificateRevocationListBuilder: if not isinstance(next_update, datetime.datetime): raise TypeError("Expecting datetime object.") if self._next_update is not None: @@ -968,7 +1044,7 @@ def next_update( def add_extension( self, extval: ExtensionType, critical: bool - ) -> "CertificateRevocationListBuilder": + ) -> CertificateRevocationListBuilder: """ Adds an X.509 extension to the certificate revocation list. """ @@ -987,7 +1063,7 @@ def add_extension( def add_revoked_certificate( self, revoked_certificate: RevokedCertificate - ) -> "CertificateRevocationListBuilder": + ) -> CertificateRevocationListBuilder: """ Adds a revoked certificate to the CRL. """ @@ -1004,8 +1080,8 @@ def add_revoked_certificate( def sign( self, - private_key: CERTIFICATE_PRIVATE_KEY_TYPES, - algorithm: typing.Optional[hashes.HashAlgorithm], + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: typing.Optional[_AllowedHashTypes], backend: typing.Any = None, ) -> CertificateRevocationList: if self._issuer_name is None: @@ -1031,7 +1107,7 @@ def __init__( self._revocation_date = revocation_date self._extensions = extensions - def serial_number(self, number: int) -> "RevokedCertificateBuilder": + 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: @@ -1051,7 +1127,7 @@ def serial_number(self, number: int) -> "RevokedCertificateBuilder": def revocation_date( self, time: datetime.datetime - ) -> "RevokedCertificateBuilder": + ) -> RevokedCertificateBuilder: if not isinstance(time, datetime.datetime): raise TypeError("Expecting datetime object.") if self._revocation_date is not None: @@ -1067,7 +1143,7 @@ def revocation_date( def add_extension( self, extval: ExtensionType, critical: bool - ) -> "RevokedCertificateBuilder": + ) -> RevokedCertificateBuilder: if not isinstance(extval, ExtensionType): raise TypeError("extension must be an ExtensionType") diff --git a/src/cryptography/x509/certificate_transparency.py b/src/cryptography/x509/certificate_transparency.py index 18c7cf7..73647ee 100644 --- a/src/cryptography/x509/certificate_transparency.py +++ b/src/cryptography/x509/certificate_transparency.py @@ -2,6 +2,7 @@ # 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 @@ -36,49 +37,57 @@ class SignatureAlgorithm(utils.Enum): class SignedCertificateTimestamp(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def version(self) -> Version: """ Returns the SCT version. """ - @abc.abstractproperty + @property + @abc.abstractmethod def log_id(self) -> bytes: """ Returns an identifier indicating which log this SCT is for. """ - @abc.abstractproperty + @property + @abc.abstractmethod def timestamp(self) -> datetime.datetime: """ Returns the timestamp for this SCT. """ - @abc.abstractproperty + @property + @abc.abstractmethod def entry_type(self) -> LogEntryType: """ Returns whether this is an SCT for a certificate or pre-certificate. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_hash_algorithm(self) -> HashAlgorithm: """ Returns the hash algorithm used for the SCT's signature. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_algorithm(self) -> SignatureAlgorithm: """ Returns the signing algorithm used for the SCT's signature. """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature(self) -> bytes: """ Returns the signature for this SCT. """ - @abc.abstractproperty + @property + @abc.abstractmethod def extension_bytes(self) -> bytes: """ Returns the raw bytes of any extensions for this SCT. diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index cc8f25e..ac99592 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -2,6 +2,7 @@ # 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 @@ -16,29 +17,29 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey from cryptography.hazmat.primitives.asymmetric.types import ( - CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES, - CERTIFICATE_PUBLIC_KEY_TYPES, + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, ) from cryptography.x509.certificate_transparency import ( SignedCertificateTimestamp, ) from cryptography.x509.general_name import ( - DNSName, DirectoryName, + DNSName, GeneralName, IPAddress, OtherName, - RFC822Name, RegisteredID, + RFC822Name, UniformResourceIdentifier, - _IPADDRESS_TYPES, + _IPAddressTypes, ) from cryptography.x509.name import Name, RelativeDistinguishedName from cryptography.x509.oid import ( CRLEntryExtensionOID, ExtensionOID, - OCSPExtensionOID, ObjectIdentifier, + OCSPExtensionOID, ) ExtensionTypeVar = typing.TypeVar( @@ -47,7 +48,7 @@ def _key_identifier_from_public_key( - public_key: CERTIFICATE_PUBLIC_KEY_TYPES, + public_key: CertificatePublicKeyTypes, ) -> bytes: if isinstance(public_key, RSAPublicKey): data = public_key.public_bytes( @@ -85,13 +86,13 @@ def getitem_method(self, idx): class DuplicateExtension(Exception): def __init__(self, msg: str, oid: ObjectIdentifier) -> None: - super(DuplicateExtension, self).__init__(msg) + super().__init__(msg) self.oid = oid class ExtensionNotFound(Exception): def __init__(self, msg: str, oid: ObjectIdentifier) -> None: - super(ExtensionNotFound, self).__init__(msg) + super().__init__(msg) self.oid = oid @@ -103,7 +104,7 @@ def public_bytes(self) -> bytes: Serializes the extension type to DER. """ raise NotImplementedError( - "public_bytes is not implemented for extension type {0!r}".format( + "public_bytes is not implemented for extension type {!r}".format( self ) ) @@ -111,22 +112,22 @@ def public_bytes(self) -> bytes: class Extensions: def __init__( - self, extensions: typing.Iterable["Extension[ExtensionType]"] + self, extensions: typing.Iterable[Extension[ExtensionType]] ) -> None: self._extensions = list(extensions) def get_extension_for_oid( self, oid: ObjectIdentifier - ) -> "Extension[ExtensionType]": + ) -> Extension[ExtensionType]: for ext in self: if ext.oid == oid: return ext - raise ExtensionNotFound("No {} extension was found".format(oid), oid) + raise ExtensionNotFound(f"No {oid} extension was found", oid) def get_extension_for_class( self, extclass: typing.Type[ExtensionTypeVar] - ) -> "Extension[ExtensionTypeVar]": + ) -> Extension[ExtensionTypeVar]: if extclass is UnrecognizedExtension: raise TypeError( "UnrecognizedExtension can't be used with " @@ -139,13 +140,13 @@ def get_extension_for_class( return ext raise ExtensionNotFound( - "No {} extension was found".format(extclass), extclass.oid + f"No {extclass} extension was found", extclass.oid ) __len__, __iter__, __getitem__ = _make_sequence_methods("_extensions") def __repr__(self) -> str: - return "".format(self._extensions) + return f"" class CRLNumber(ExtensionType): @@ -167,7 +168,7 @@ def __hash__(self) -> int: return hash(self.crl_number) def __repr__(self) -> str: - return "".format(self.crl_number) + return f"" @property def crl_number(self) -> int: @@ -213,15 +214,15 @@ def __init__( self._authority_cert_issuer = authority_cert_issuer self._authority_cert_serial_number = authority_cert_serial_number - # This takes a subset of CERTIFICATE_PUBLIC_KEY_TYPES because an issuer + # 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: CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES - ) -> "AuthorityKeyIdentifier": + cls, public_key: CertificateIssuerPublicKeyTypes + ) -> AuthorityKeyIdentifier: digest = _key_identifier_from_public_key(public_key) return cls( key_identifier=digest, @@ -231,8 +232,8 @@ def from_issuer_public_key( @classmethod def from_issuer_subject_key_identifier( - cls, ski: "SubjectKeyIdentifier" - ) -> "AuthorityKeyIdentifier": + cls, ski: SubjectKeyIdentifier + ) -> AuthorityKeyIdentifier: return cls( key_identifier=ski.digest, authority_cert_issuer=None, @@ -293,8 +294,8 @@ def __init__(self, digest: bytes) -> None: @classmethod def from_public_key( - cls, public_key: CERTIFICATE_PUBLIC_KEY_TYPES - ) -> "SubjectKeyIdentifier": + cls, public_key: CertificatePublicKeyTypes + ) -> SubjectKeyIdentifier: return cls(_key_identifier_from_public_key(public_key)) @property @@ -306,7 +307,7 @@ def key_identifier(self) -> bytes: return self._digest def __repr__(self) -> str: - return "".format(self.digest) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectKeyIdentifier): @@ -325,7 +326,7 @@ class AuthorityInformationAccess(ExtensionType): oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS def __init__( - self, descriptions: typing.Iterable["AccessDescription"] + self, descriptions: typing.Iterable[AccessDescription] ) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): @@ -339,7 +340,7 @@ def __init__( __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") def __repr__(self) -> str: - return "".format(self._descriptions) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, AuthorityInformationAccess): @@ -358,7 +359,7 @@ class SubjectInformationAccess(ExtensionType): oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS def __init__( - self, descriptions: typing.Iterable["AccessDescription"] + self, descriptions: typing.Iterable[AccessDescription] ) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): @@ -372,7 +373,7 @@ def __init__( __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") def __repr__(self) -> str: - return "".format(self._descriptions) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectInformationAccess): @@ -496,7 +497,7 @@ def __hash__(self) -> int: return hash(self.crl_number) def __repr__(self) -> str: - return "".format(self) + return f"" def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) @@ -506,7 +507,7 @@ class CRLDistributionPoints(ExtensionType): oid = ExtensionOID.CRL_DISTRIBUTION_POINTS def __init__( - self, distribution_points: typing.Iterable["DistributionPoint"] + self, distribution_points: typing.Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( @@ -524,7 +525,7 @@ def __init__( ) def __repr__(self) -> str: - return "".format(self._distribution_points) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CRLDistributionPoints): @@ -543,7 +544,7 @@ class FreshestCRL(ExtensionType): oid = ExtensionOID.FRESHEST_CRL def __init__( - self, distribution_points: typing.Iterable["DistributionPoint"] + self, distribution_points: typing.Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( @@ -561,7 +562,7 @@ def __init__( ) def __repr__(self) -> str: - return "".format(self._distribution_points) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, FreshestCRL): @@ -581,7 +582,7 @@ def __init__( self, full_name: typing.Optional[typing.Iterable[GeneralName]], relative_name: typing.Optional[RelativeDistinguishedName], - reasons: typing.Optional[typing.FrozenSet["ReasonFlags"]], + reasons: typing.Optional[typing.FrozenSet[ReasonFlags]], crl_issuer: typing.Optional[typing.Iterable[GeneralName]], ) -> None: if full_name and relative_name: @@ -589,6 +590,11 @@ def __init__( "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) @@ -625,12 +631,6 @@ def __init__( "DistributionPoint" ) - if reasons and not crl_issuer and not (full_name or relative_name): - raise ValueError( - "You must supply crl_issuer, full_name, or relative_name when " - "reasons is not None" - ) - self._full_name = full_name self._relative_name = relative_name self._reasons = reasons @@ -680,7 +680,7 @@ def relative_name(self) -> typing.Optional[RelativeDistinguishedName]: return self._relative_name @property - def reasons(self) -> typing.Optional[typing.FrozenSet["ReasonFlags"]]: + def reasons(self) -> typing.Optional[typing.FrozenSet[ReasonFlags]]: return self._reasons @property @@ -804,7 +804,7 @@ def public_bytes(self) -> bytes: class CertificatePolicies(ExtensionType): oid = ExtensionOID.CERTIFICATE_POLICIES - def __init__(self, policies: typing.Iterable["PolicyInformation"]) -> None: + def __init__(self, policies: typing.Iterable[PolicyInformation]) -> None: policies = list(policies) if not all(isinstance(x, PolicyInformation) for x in policies): raise TypeError( @@ -817,7 +817,7 @@ def __init__(self, policies: typing.Iterable["PolicyInformation"]) -> None: __len__, __iter__, __getitem__ = _make_sequence_methods("_policies") def __repr__(self) -> str: - return "".format(self._policies) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CertificatePolicies): @@ -837,7 +837,7 @@ def __init__( self, policy_identifier: ObjectIdentifier, policy_qualifiers: typing.Optional[ - typing.Iterable[typing.Union[str, "UserNotice"]] + typing.Iterable[typing.Union[str, UserNotice]] ], ) -> None: if not isinstance(policy_identifier, ObjectIdentifier): @@ -875,7 +875,7 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: if self.policy_qualifiers is not None: pq: typing.Optional[ - typing.Tuple[typing.Union[str, "UserNotice"], ...] + typing.Tuple[typing.Union[str, UserNotice], ...] ] = tuple(self.policy_qualifiers) else: pq = None @@ -889,14 +889,14 @@ def policy_identifier(self) -> ObjectIdentifier: @property def policy_qualifiers( self, - ) -> typing.Optional[typing.List[typing.Union[str, "UserNotice"]]]: + ) -> typing.Optional[typing.List[typing.Union[str, UserNotice]]]: return self._policy_qualifiers class UserNotice: def __init__( self, - notice_reference: typing.Optional["NoticeReference"], + notice_reference: typing.Optional[NoticeReference], explicit_text: typing.Optional[str], ) -> None: if notice_reference and not isinstance( @@ -928,7 +928,7 @@ def __hash__(self) -> int: return hash((self.notice_reference, self.explicit_text)) @property - def notice_reference(self) -> typing.Optional["NoticeReference"]: + def notice_reference(self) -> typing.Optional[NoticeReference]: return self._notice_reference @property @@ -991,7 +991,7 @@ def __init__(self, usages: typing.Iterable[ObjectIdentifier]) -> None: __len__, __iter__, __getitem__ = _make_sequence_methods("_usages") def __repr__(self) -> str: - return "".format(self._usages) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, ExtendedKeyUsage): @@ -1047,7 +1047,7 @@ def public_bytes(self) -> bytes: class TLSFeature(ExtensionType): oid = ExtensionOID.TLS_FEATURE - def __init__(self, features: typing.Iterable["TLSFeatureType"]) -> None: + def __init__(self, features: typing.Iterable[TLSFeatureType]) -> None: features = list(features) if ( not all(isinstance(x, TLSFeatureType) for x in features) @@ -1063,7 +1063,7 @@ def __init__(self, features: typing.Iterable["TLSFeatureType"]) -> None: __len__, __iter__, __getitem__ = _make_sequence_methods("_features") def __repr__(self) -> str: - return "".format(self) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, TLSFeature): @@ -1105,7 +1105,7 @@ def __init__(self, skip_certs: int) -> None: self._skip_certs = skip_certs def __repr__(self) -> str: - return "".format(self) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, InhibitAnyPolicy): @@ -1277,7 +1277,7 @@ def __init__( "or None" ) - self._validate_ip_name(permitted_subtrees) + self._validate_tree(permitted_subtrees) if excluded_subtrees is not None: excluded_subtrees = list(excluded_subtrees) @@ -1291,7 +1291,7 @@ def __init__( "or None" ) - self._validate_ip_name(excluded_subtrees) + self._validate_tree(excluded_subtrees) if permitted_subtrees is None and excluded_subtrees is None: raise ValueError( @@ -1311,6 +1311,10 @@ def __eq__(self, other: object) -> bool: 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) @@ -1324,6 +1328,15 @@ def _validate_ip_name(self, tree: typing.Iterable[GeneralName]) -> None: " 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 ( " typing.List[_IPADDRESS_TYPES]: + ) -> typing.List[_IPAddressTypes]: ... @typing.overload @@ -1473,7 +1486,7 @@ def get_values_for_type( typing.Type[UniformResourceIdentifier], ], ) -> typing.Union[ - typing.List[_IPADDRESS_TYPES], + typing.List[_IPAddressTypes], typing.List[str], typing.List[OtherName], typing.List[Name], @@ -1488,7 +1501,7 @@ def get_values_for_type( return list(objs) def __repr__(self) -> str: - return "".format(self._general_names) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, GeneralNames): @@ -1536,7 +1549,7 @@ def get_values_for_type( @typing.overload def get_values_for_type( self, type: typing.Type[IPAddress] - ) -> typing.List[_IPADDRESS_TYPES]: + ) -> typing.List[_IPAddressTypes]: ... @typing.overload @@ -1557,7 +1570,7 @@ def get_values_for_type( typing.Type[UniformResourceIdentifier], ], ) -> typing.Union[ - typing.List[_IPADDRESS_TYPES], + typing.List[_IPAddressTypes], typing.List[str], typing.List[OtherName], typing.List[Name], @@ -1566,7 +1579,7 @@ def get_values_for_type( return self._general_names.get_values_for_type(type) def __repr__(self) -> str: - return "".format(self._general_names) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, SubjectAlternativeName): @@ -1617,7 +1630,7 @@ def get_values_for_type( @typing.overload def get_values_for_type( self, type: typing.Type[IPAddress] - ) -> typing.List[_IPADDRESS_TYPES]: + ) -> typing.List[_IPAddressTypes]: ... @typing.overload @@ -1638,7 +1651,7 @@ def get_values_for_type( typing.Type[UniformResourceIdentifier], ], ) -> typing.Union[ - typing.List[_IPADDRESS_TYPES], + typing.List[_IPAddressTypes], typing.List[str], typing.List[OtherName], typing.List[Name], @@ -1647,7 +1660,7 @@ def get_values_for_type( return self._general_names.get_values_for_type(type) def __repr__(self) -> str: - return "".format(self._general_names) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, IssuerAlternativeName): @@ -1698,7 +1711,7 @@ def get_values_for_type( @typing.overload def get_values_for_type( self, type: typing.Type[IPAddress] - ) -> typing.List[_IPADDRESS_TYPES]: + ) -> typing.List[_IPAddressTypes]: ... @typing.overload @@ -1719,7 +1732,7 @@ def get_values_for_type( typing.Type[UniformResourceIdentifier], ], ) -> typing.Union[ - typing.List[_IPADDRESS_TYPES], + typing.List[_IPAddressTypes], typing.List[str], typing.List[OtherName], typing.List[Name], @@ -1728,7 +1741,7 @@ def get_values_for_type( return self._general_names.get_values_for_type(type) def __repr__(self) -> str: - return "".format(self._general_names) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CertificateIssuer): @@ -1753,7 +1766,7 @@ def __init__(self, reason: ReasonFlags) -> None: self._reason = reason def __repr__(self) -> str: - return "".format(self._reason) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, CRLReason): @@ -1873,7 +1886,7 @@ def __init__( ) def __repr__(self) -> str: - return "".format(list(self)) + return f"" def __hash__(self) -> int: return hash(tuple(self._signed_certificate_timestamps)) @@ -1910,7 +1923,7 @@ def __hash__(self) -> int: return hash(self.nonce) def __repr__(self) -> str: - return "".format(self) + return f"" @property def nonce(self) -> bytes: @@ -1920,6 +1933,35 @@ 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 @@ -2080,6 +2122,65 @@ 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: typing.Optional[int], + minor_version: typing.Optional[int], + ) -> 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) -> typing.Optional[int]: + return self._major_version + + @property + def minor_version(self) -> typing.Optional[int]: + 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): diff --git a/src/cryptography/x509/general_name.py b/src/cryptography/x509/general_name.py index 9939233..79271af 100644 --- a/src/cryptography/x509/general_name.py +++ b/src/cryptography/x509/general_name.py @@ -2,6 +2,7 @@ # 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 @@ -11,8 +12,7 @@ from cryptography.x509.name import Name from cryptography.x509.oid import ObjectIdentifier - -_IPADDRESS_TYPES = typing.Union[ +_IPAddressTypes = typing.Union[ ipaddress.IPv4Address, ipaddress.IPv6Address, ipaddress.IPv4Network, @@ -25,7 +25,8 @@ class UnsupportedGeneralNameType(Exception): class GeneralName(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def value(self) -> typing.Any: """ Return the value of the object @@ -59,13 +60,13 @@ def value(self) -> str: return self._value @classmethod - def _init_without_validation(cls, value: str) -> "RFC822Name": + def _init_without_validation(cls, value: str) -> RFC822Name: instance = cls.__new__(cls) instance._value = value return instance def __repr__(self) -> str: - return "".format(self.value) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, RFC822Name): @@ -98,13 +99,13 @@ def value(self) -> str: return self._value @classmethod - def _init_without_validation(cls, value: str) -> "DNSName": + def _init_without_validation(cls, value: str) -> DNSName: instance = cls.__new__(cls) instance._value = value return instance def __repr__(self) -> str: - return "".format(self.value) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, DNSName): @@ -137,15 +138,13 @@ def value(self) -> str: return self._value @classmethod - def _init_without_validation( - cls, value: str - ) -> "UniformResourceIdentifier": + def _init_without_validation(cls, value: str) -> UniformResourceIdentifier: instance = cls.__new__(cls) instance._value = value return instance def __repr__(self) -> str: - return "".format(self.value) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, UniformResourceIdentifier): @@ -169,7 +168,7 @@ def value(self) -> Name: return self._value def __repr__(self) -> str: - return "".format(self.value) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, DirectoryName): @@ -193,7 +192,7 @@ def value(self) -> ObjectIdentifier: return self._value def __repr__(self) -> str: - return "".format(self.value) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, RegisteredID): @@ -206,7 +205,7 @@ def __hash__(self) -> int: class IPAddress(GeneralName): - def __init__(self, value: _IPADDRESS_TYPES) -> None: + def __init__(self, value: _IPAddressTypes) -> None: if not isinstance( value, ( @@ -225,7 +224,7 @@ def __init__(self, value: _IPADDRESS_TYPES) -> None: self._value = value @property - def value(self) -> _IPADDRESS_TYPES: + def value(self) -> _IPAddressTypes: return self._value def _packed(self) -> bytes: @@ -239,7 +238,7 @@ def _packed(self) -> bytes: ) def __repr__(self) -> str: - return "".format(self.value) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, IPAddress): diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index 702fb4b..ff98e87 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -2,6 +2,8 @@ # 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 @@ -9,9 +11,7 @@ import warnings from cryptography import utils -from cryptography.hazmat.bindings._rust import ( - x509 as rust_x509, -) +from cryptography.hazmat.bindings._rust import x509 as rust_x509 from cryptography.x509.oid import NameOID, ObjectIdentifier @@ -261,7 +261,7 @@ def __len__(self) -> int: return len(self._attributes) def __repr__(self) -> str: - return "".format(self.rfc4514_string()) + return f"" class Name: @@ -302,7 +302,7 @@ def from_rfc4514_string( cls, data: str, attr_name_overrides: typing.Optional[_NameOidMap] = None, - ) -> "Name": + ) -> Name: return _RFC4514NameParser(data, attr_name_overrides or {}).parse() def rfc4514_string( @@ -356,7 +356,7 @@ def __len__(self) -> int: def __repr__(self) -> str: rdns = ",".join(attr.rfc4514_string() for attr in self._attributes) - return "".format(rdns) + return f"" class _RFC4514NameParser: diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py index c01e77a..7054795 100644 --- a/src/cryptography/x509/ocsp.py +++ b/src/cryptography/x509/ocsp.py @@ -2,17 +2,17 @@ # 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 -from cryptography import x509 +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 ( - CERTIFICATE_PRIVATE_KEY_TYPES, + CertificateIssuerPrivateKeyTypes, ) from cryptography.x509.base import ( _EARLIEST_UTC_TIME, @@ -128,25 +128,29 @@ def __init__( class OCSPRequest(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ - @abc.abstractproperty + @property + @abc.abstractmethod def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ - @abc.abstractproperty + @property + @abc.abstractmethod def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ - @abc.abstractproperty + @property + @abc.abstractmethod def serial_number(self) -> int: """ The serial number of the cert whose status is being checked @@ -158,7 +162,8 @@ def public_bytes(self, encoding: serialization.Encoding) -> bytes: Serializes the request to DER """ - @abc.abstractproperty + @property + @abc.abstractmethod def extensions(self) -> x509.Extensions: """ The list of request extensions. Not single request extensions. @@ -166,58 +171,67 @@ def extensions(self) -> x509.Extensions: class OCSPSingleResponse(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def certificate_status(self) -> OCSPCertStatus: """ The status of the certificate (an element from the OCSPCertStatus enum) """ - @abc.abstractproperty + @property + @abc.abstractmethod def revocation_time(self) -> typing.Optional[datetime.datetime]: """ The date of when the certificate was revoked or None if not revoked. """ - @abc.abstractproperty + @property + @abc.abstractmethod def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: """ The reason the certificate was revoked or None if not specified or not revoked. """ - @abc.abstractproperty + @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 """ - @abc.abstractproperty + @property + @abc.abstractmethod def next_update(self) -> typing.Optional[datetime.datetime]: """ The time when newer information will be available """ - @abc.abstractproperty + @property + @abc.abstractmethod def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ - @abc.abstractproperty + @property + @abc.abstractmethod def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ - @abc.abstractproperty + @property + @abc.abstractmethod def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ - @abc.abstractproperty + @property + @abc.abstractmethod def serial_number(self) -> int: """ The serial number of the cert whose status is being checked @@ -225,27 +239,31 @@ def serial_number(self) -> int: class OCSPResponse(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def responses(self) -> typing.Iterator[OCSPSingleResponse]: """ An iterator over the individual SINGLERESP structures in the response """ - @abc.abstractproperty + @property + @abc.abstractmethod def response_status(self) -> OCSPResponseStatus: """ The status of the response. This is a value from the OCSPResponseStatus enumeration """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_algorithm_oid(self) -> x509.ObjectIdentifier: """ The ObjectIdentifier of the signature algorithm """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature_hash_algorithm( self, ) -> typing.Optional[hashes.HashAlgorithm]: @@ -253,19 +271,22 @@ def signature_hash_algorithm( Returns a HashAlgorithm corresponding to the type of the digest signed """ - @abc.abstractproperty + @property + @abc.abstractmethod def signature(self) -> bytes: """ The signature bytes """ - @abc.abstractproperty + @property + @abc.abstractmethod def tbs_response_bytes(self) -> bytes: """ The tbsResponseData bytes """ - @abc.abstractproperty + @property + @abc.abstractmethod def certificates(self) -> typing.List[x509.Certificate]: """ A list of certificates used to help build a chain to verify the OCSP @@ -273,88 +294,102 @@ def certificates(self) -> typing.List[x509.Certificate]: certificate. """ - @abc.abstractproperty + @property + @abc.abstractmethod def responder_key_hash(self) -> typing.Optional[bytes]: """ The responder's key hash or None """ - @abc.abstractproperty + @property + @abc.abstractmethod def responder_name(self) -> typing.Optional[x509.Name]: """ The responder's Name or None """ - @abc.abstractproperty + @property + @abc.abstractmethod def produced_at(self) -> datetime.datetime: """ The time the response was produced """ - @abc.abstractproperty + @property + @abc.abstractmethod def certificate_status(self) -> OCSPCertStatus: """ The status of the certificate (an element from the OCSPCertStatus enum) """ - @abc.abstractproperty + @property + @abc.abstractmethod def revocation_time(self) -> typing.Optional[datetime.datetime]: """ The date of when the certificate was revoked or None if not revoked. """ - @abc.abstractproperty + @property + @abc.abstractmethod def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: """ The reason the certificate was revoked or None if not specified or not revoked. """ - @abc.abstractproperty + @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 """ - @abc.abstractproperty + @property + @abc.abstractmethod def next_update(self) -> typing.Optional[datetime.datetime]: """ The time when newer information will be available """ - @abc.abstractproperty + @property + @abc.abstractmethod def issuer_key_hash(self) -> bytes: """ The hash of the issuer public key """ - @abc.abstractproperty + @property + @abc.abstractmethod def issuer_name_hash(self) -> bytes: """ The hash of the issuer name """ - @abc.abstractproperty + @property + @abc.abstractmethod def hash_algorithm(self) -> hashes.HashAlgorithm: """ The hash algorithm used in the issuer name and key hashes """ - @abc.abstractproperty + @property + @abc.abstractmethod def serial_number(self) -> int: """ The serial number of the cert whose status is being checked """ - @abc.abstractproperty + @property + @abc.abstractmethod def extensions(self) -> x509.Extensions: """ The list of response extensions. Not single response extensions. """ - @abc.abstractproperty + @property + @abc.abstractmethod def single_extensions(self) -> x509.Extensions: """ The list of single response extensions. Not response extensions. @@ -375,9 +410,13 @@ def __init__( x509.Certificate, x509.Certificate, hashes.HashAlgorithm ] ] = None, + request_hash: typing.Optional[ + typing.Tuple[bytes, bytes, int, hashes.HashAlgorithm] + ] = None, extensions: typing.List[x509.Extension[x509.ExtensionType]] = [], ) -> None: self._request = request + self._request_hash = request_hash self._extensions = extensions def add_certificate( @@ -385,8 +424,8 @@ def add_certificate( cert: x509.Certificate, issuer: x509.Certificate, algorithm: hashes.HashAlgorithm, - ) -> "OCSPRequestBuilder": - if self._request is not None: + ) -> 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) @@ -395,11 +434,43 @@ def add_certificate( ): raise TypeError("cert and issuer must be a Certificate") - return OCSPRequestBuilder((cert, issuer, algorithm), self._extensions) + 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": + ) -> OCSPRequestBuilder: if not isinstance(extval, x509.ExtensionType): raise TypeError("extension must be an ExtensionType") @@ -407,11 +478,11 @@ def add_extension( _reject_duplicate_extension(extension, self._extensions) return OCSPRequestBuilder( - self._request, self._extensions + [extension] + self._request, self._request_hash, self._extensions + [extension] ) def build(self) -> OCSPRequest: - if self._request is None: + 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) @@ -442,7 +513,7 @@ def add_response( next_update: typing.Optional[datetime.datetime], revocation_time: typing.Optional[datetime.datetime], revocation_reason: typing.Optional[x509.ReasonFlags], - ) -> "OCSPResponseBuilder": + ) -> OCSPResponseBuilder: if self._response is not None: raise ValueError("Only one response per OCSPResponse.") @@ -465,7 +536,7 @@ def add_response( def responder_id( self, encoding: OCSPResponderEncoding, responder_cert: x509.Certificate - ) -> "OCSPResponseBuilder": + ) -> OCSPResponseBuilder: if self._responder_id is not None: raise ValueError("responder_id can only be set once") if not isinstance(responder_cert, x509.Certificate): @@ -484,7 +555,7 @@ def responder_id( def certificates( self, certs: typing.Iterable[x509.Certificate] - ) -> "OCSPResponseBuilder": + ) -> OCSPResponseBuilder: if self._certs is not None: raise ValueError("certificates may only be set once") certs = list(certs) @@ -501,7 +572,7 @@ def certificates( def add_extension( self, extval: x509.ExtensionType, critical: bool - ) -> "OCSPResponseBuilder": + ) -> OCSPResponseBuilder: if not isinstance(extval, x509.ExtensionType): raise TypeError("extension must be an ExtensionType") @@ -517,7 +588,7 @@ def add_extension( def sign( self, - private_key: CERTIFICATE_PRIVATE_KEY_TYPES, + private_key: CertificateIssuerPrivateKeyTypes, algorithm: typing.Optional[hashes.HashAlgorithm], ) -> OCSPResponse: if self._response is None: diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index 9bfac75..cda50cc 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -2,21 +2,22 @@ # 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, - CRLEntryExtensionOID, CertificatePoliciesOID, + CRLEntryExtensionOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, - OCSPExtensionOID, ObjectIdentifier, + OCSPExtensionOID, SignatureAlgorithmOID, SubjectInformationAccessOID, ) - __all__ = [ "AttributeOID", "AuthorityInformationAccessOID", diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 4a0ecfd..d92f67d 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -14,34 +14,24 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "asn1" -version = "0.12.2" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22c27c85cd71c1bf4373c7c1aa752b73d2df799277c0930af16fffbf3444f210" +checksum = "28c19b9324de5b815b6487e0f8098312791b09de0dbf3d5c2db1fe2d95bab973" dependencies = [ "asn1_derive", - "chrono", ] [[package]] name = "asn1_derive" -version = "0.12.2" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48d1854a01241e8d22f8f5ae4e2dc332f66c5946e1772f5576886d83e18e1b7" +checksum = "a045c3ccad89f244a86bd1e6cf1a7bf645296e7692698b056399b6efd4639407" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -52,9 +42,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" @@ -63,10 +53,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bumpalo" -version = "3.10.0" +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cc" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -75,145 +71,144 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +name = "cryptography-cffi" +version = "0.1.0" dependencies = [ - "iana-time-zone", - "num-integer", - "num-traits", - "winapi", + "cc", + "openssl-sys", + "pyo3", ] [[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +name = "cryptography-openssl" +version = "0.1.0" +dependencies = [ + "foreign-types", + "foreign-types-shared", + "openssl", + "openssl-sys", +] [[package]] name = "cryptography-rust" version = "0.1.0" dependencies = [ "asn1", - "chrono", + "cc", + "cryptography-cffi", + "cryptography-openssl", + "cryptography-x509", + "foreign-types-shared", "once_cell", + "openssl", + "openssl-sys", "ouroboros", "pem", "pyo3", ] [[package]] -name = "iana-time-zone" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "js-sys", - "once_cell", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "indoc" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" +name = "cryptography-x509" +version = "0.1.0" dependencies = [ - "indoc-impl", - "proc-macro-hack", + "asn1", ] [[package]] -name = "indoc-impl" -version = "0.3.6" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", - "unindent", + "foreign-types-shared", ] [[package]] -name = "instant" -version = "0.1.12" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "js-sys" -version = "0.3.59" +name = "indoc" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" -dependencies = [ - "wasm-bindgen", -] +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", ] [[package]] -name = "log" -version = "0.4.17" +name = "memoffset" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ - "cfg-if", + "autocfg", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "once_cell" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" + +[[package]] +name = "openssl" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ - "autocfg", - "num-traits", + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "num-traits" -version = "0.2.15" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "autocfg", + "proc-macro2", + "quote", + "syn 2.0.18", ] [[package]] -name = "once_cell" -version = "1.14.0" +name = "openssl-sys" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "ouroboros" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f56a2b0aa5fc88687aaf63e85a7974422790ce3419a2e1a15870f8a55227822" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" dependencies = [ "aliasable", "ouroboros_macro", @@ -221,69 +216,54 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c40641e27d0eb38cae3dee081d920104d2db47a8e853c1a592ef68d33f5ebf4" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", -] - -[[package]] -name = "paste" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", + "windows-sys", ] [[package]] -name = "paste-impl" -version = "0.1.18" +name = "pem" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "proc-macro-hack", + "base64", ] [[package]] -name = "pem" -version = "1.1.0" +name = "pkg-config" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" -dependencies = [ - "base64", -] +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro-error" @@ -294,7 +274,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -309,74 +289,80 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.15.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41d50a7271e08c7c8a54cd24af5d62f73ee3a6f6a314215281ebdec421d5752" +checksum = "e3b1ac5b3731ba34fdaa9785f8d74d17448cd18f30cf19e0c7e7b1fdb5272109" dependencies = [ "cfg-if", "indoc", "libc", + "memoffset", "parking_lot", - "paste", "pyo3-build-config", + "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.15.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779239fc40b8e18bc8416d3a37d280ca9b9fb04bda54b98037bb6748595c2410" +checksum = "9cb946f5ac61bb61a5014924910d936ebd2b23b705f7a4a3c40b05c720b079a3" dependencies = [ "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd4d7c5337821916ea2a1d21d1092e8443cf34879e53a0ac653fbb98f44ff65c" +dependencies = [ + "libc", + "pyo3-build-config", ] [[package]] name = "pyo3-macros" -version = "0.15.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b247e8c664be87998d8628e86f282c25066165f1f8dda66100c48202fdb93a" +checksum = "a9d39c55dab3fc5a4b25bbd1ac10a2da452c4aca13bb450f22818a002e29648d" dependencies = [ + "proc-macro2", "pyo3-macros-backend", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "pyo3-macros-backend" -version = "0.15.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8c2812c412e00e641d99eeb79dd478317d981d938aa60325dfa7157b607095" +checksum = "97daff08a4c48320587b5224cc98d609e3c27b6d437315bd40b605c98eeb5918" dependencies = [ "proc-macro2", - "pyo3-build-config", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -387,7 +373,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -398,32 +384,55 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "syn" -version = "1.0.99" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" + [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unindent" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" @@ -432,77 +441,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "wasm-bindgen" -version = "0.2.82" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "windows-targets", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" +name = "windows_aarch64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] -name = "wasm-bindgen-shared" -version = "0.2.82" +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 49e70a3..01fba14 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -2,16 +2,26 @@ name = "cryptography-rust" version = "0.1.0" authors = ["The cryptography developers "] -edition = "2018" +edition = "2021" publish = false +# This specifies the MSRV +rust-version = "1.56.0" [dependencies] once_cell = "1" -pyo3 = { version = "0.15.2" } -asn1 = { version = "0.12.2", default-features = false, features = ["derive"] } +pyo3 = { version = "0.18", features = ["abi3-py37"] } +asn1 = { version = "0.15.2", default-features = false } +cryptography-cffi = { path = "cryptography-cffi" } +cryptography-x509 = { path = "cryptography-x509" } +cryptography-openssl = { path = "cryptography-openssl" } pem = "1.1" -chrono = { version = "0.4.22", default-features = false, features = ["alloc", "clock"] } ouroboros = "0.15" +openssl = "0.10.54" +openssl-sys = "0.9.88" +foreign-types-shared = "0.1" + +[build-dependencies] +cc = "1.0.72" [features] extension-module = ["pyo3/extension-module"] @@ -22,5 +32,7 @@ name = "cryptography_rust" crate-type = ["cdylib"] [profile.release] -lto = "thin" overflow-checks = true + +[workspace] +members = ["cryptography-cffi", "cryptography-openssl", "cryptography-x509"] diff --git a/src/rust/build.rs b/src/rust/build.rs new file mode 100644 index 0000000..5745603 --- /dev/null +++ b/src/rust/build.rs @@ -0,0 +1,20 @@ +// This file is dual licensed under the terms 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() { + if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { + let version = u64::from_str_radix(&version, 16).unwrap(); + + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL"); + if version >= 0x3_07_00_00_0 { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER"); + } + } + if env::var("DEP_OPENSSL_BORINGSSL").is_ok() { + println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL"); + } +} diff --git a/src/rust/cryptography-cffi/Cargo.toml b/src/rust/cryptography-cffi/Cargo.toml new file mode 100644 index 0000000..65051c2 --- /dev/null +++ b/src/rust/cryptography-cffi/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cryptography-cffi" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.56.0" + +[dependencies] +pyo3 = { version = "0.18", features = ["abi3-py37"] } +openssl-sys = "0.9.88" + +[build-dependencies] +cc = "1.0.72" diff --git a/src/rust/cryptography-cffi/build.rs b/src/rust/cryptography-cffi/build.rs new file mode 100644 index 0000000..07590ad --- /dev/null +++ b/src/rust/cryptography-cffi/build.rs @@ -0,0 +1,128 @@ +// This file is dual licensed under the terms 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() { + 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_include = run_python_script( + &python, + "import sysconfig; print(sysconfig.get_path('include'), 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(python_include) + .include(openssl_include) + .flag_if_supported("-Wconversion") + .flag_if_supported("-Wno-error=sign-conversion") + .flag_if_supported("-Wno-unused-parameter"); + + // 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!("{}/lib/darwin", path)); + } + } + + println!("failed to determine link search path, continuing without it"); + None +} diff --git a/src/rust/cryptography-cffi/src/lib.rs b/src/rust/cryptography-cffi/src/lib.rs new file mode 100644 index 0000000..e263d53 --- /dev/null +++ b/src/rust/cryptography-cffi/src/lib.rs @@ -0,0 +1,31 @@ +// This file is dual licensed under the terms 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(python_implementation = "PyPy"))] +use pyo3::FromPyPointer; + +#[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<&pyo3::types::PyModule> { + #[cfg(python_implementation = "PyPy")] + let openssl_mod = unsafe { + let res = Cryptography_make_openssl_module(); + assert_eq!(res, 0); + pyo3::types::PyModule::import(py, "_openssl")? + }; + #[cfg(not(python_implementation = "PyPy"))] + let openssl_mod = unsafe { + let ptr = PyInit__openssl(); + pyo3::types::PyModule::from_owned_ptr(py, ptr) + }; + + Ok(openssl_mod) +} diff --git a/src/rust/cryptography-openssl/Cargo.toml b/src/rust/cryptography-openssl/Cargo.toml new file mode 100644 index 0000000..c85f406 --- /dev/null +++ b/src/rust/cryptography-openssl/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cryptography-openssl" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.56.0" + +[dependencies] +openssl = "0.10.54" +ffi = { package = "openssl-sys", version = "0.9.85" } +foreign-types = "0.3" +foreign-types-shared = "0.1" diff --git a/src/rust/cryptography-openssl/build.rs b/src/rust/cryptography-openssl/build.rs new file mode 100644 index 0000000..a0b4566 --- /dev/null +++ b/src/rust/cryptography-openssl/build.rs @@ -0,0 +1,24 @@ +// This file is dual licensed under the terms 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() { + 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 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"); + } +} diff --git a/src/rust/cryptography-openssl/src/fips.rs b/src/rust/cryptography-openssl/src/fips.rs new file mode 100644 index 0000000..29c4c78 --- /dev/null +++ b/src/rust/cryptography-openssl/src/fips.rs @@ -0,0 +1,32 @@ +// This file is dual licensed under the terms 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(all( + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, + not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) +))] +use std::ptr; + +pub fn is_enabled() -> bool { + #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] + { + return false; + } + + #[cfg(all( + CRYPTOGRAPHY_OPENSSL_300_OR_GREATER, + not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) + ))] + unsafe { + ffi::EVP_default_properties_is_fips_enabled(ptr::null_mut()) == 1 + } + + #[cfg(all( + not(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER), + not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)) + ))] + { + return openssl::fips::enabled(); + } +} diff --git a/src/rust/cryptography-openssl/src/hmac.rs b/src/rust/cryptography-openssl/src/hmac.rs new file mode 100644 index 0000000..b30de47 --- /dev/null +++ b/src/rust/cryptography-openssl/src/hmac.rs @@ -0,0 +1,93 @@ +// This file is dual licensed under the terms 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}; +use std::ptr; + +foreign_types::foreign_type! { + type CType = ffi::HMAC_CTX; + fn drop = ffi::HMAC_CTX_free; + + pub struct Hmac; + pub struct HmacRef; +} + +unsafe impl Sync for Hmac {} +unsafe impl Send for Hmac {} + +impl Hmac { + pub fn new(key: &[u8], md: openssl::hash::MessageDigest) -> OpenSSLResult { + 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<()> { + 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; + 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 { + 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 { + buf: [u8; ffi::EVP_MAX_MD_SIZE as usize], + 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"); + } +} diff --git a/src/rust/cryptography-openssl/src/lib.rs b/src/rust/cryptography-openssl/src/lib.rs new file mode 100644 index 0000000..0a2b481 --- /dev/null +++ b/src/rust/cryptography-openssl/src/lib.rs @@ -0,0 +1,37 @@ +// This file is dual licensed under the terms 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 mod fips; +pub mod hmac; + +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()); + } +} diff --git a/src/rust/cryptography-x509/Cargo.toml b/src/rust/cryptography-x509/Cargo.toml new file mode 100644 index 0000000..017d51d --- /dev/null +++ b/src/rust/cryptography-x509/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cryptography-x509" +version = "0.1.0" +authors = ["The cryptography developers "] +edition = "2021" +publish = false +# This specifies the MSRV +rust-version = "1.56.0" + +[dependencies] +asn1 = { version = "0.15.2", default-features = false } diff --git a/src/rust/cryptography-x509/src/certificate.rs b/src/rust/cryptography-x509/src/certificate.rs new file mode 100644 index 0000000..2a5616e --- /dev/null +++ b/src/rust/cryptography-x509/src/certificate.rs @@ -0,0 +1,48 @@ +// This file is dual licensed under the terms 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::Extensions; +use crate::name; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +pub struct Certificate<'a> { + pub tbs_cert: TbsCertificate<'a>, + pub signature_alg: common::AlgorithmIdentifier<'a>, + pub signature: asn1::BitString<'a>, +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, 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::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(&'a self) -> Result, asn1::ObjectIdentifier> { + Extensions::from_raw_extensions(self.raw_extensions.as_ref()) + } +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +pub struct Validity { + pub not_before: common::Time, + pub not_after: common::Time, +} diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs new file mode 100644 index 0000000..a882d98 --- /dev/null +++ b/src/rust/cryptography-x509/src/common.rs @@ -0,0 +1,333 @@ +// This file is dual licensed under the terms 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::oid; +use asn1::Asn1DefinedByWritable; +use std::marker::PhantomData; + +#[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, + + // 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_WITH_SHA224_OID)] + DsaWithSha224, + #[defined_by(oid::DSA_WITH_SHA256_OID)] + DsaWithSha256, + #[defined_by(oid::DSA_WITH_SHA384_OID)] + DsaWithSha384, + #[defined_by(oid::DSA_WITH_SHA512_OID)] + DsaWithSha512, + + #[default] + Other(asn1::ObjectIdentifier, Option>), +} + +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] +pub struct SubjectPublicKeyInfo<'a> { + _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, 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, Clone)] +pub enum Asn1ReadableOrWritable<'a, T, U> { + Read(T, PhantomData<&'a ()>), + Write(U, PhantomData<&'a ()>), +} + +impl<'a, T, U> Asn1ReadableOrWritable<'a, T, U> { + pub fn new_read(v: T) -> Self { + Asn1ReadableOrWritable::Read(v, PhantomData) + } + + pub fn new_write(v: U) -> Self { + Asn1ReadableOrWritable::Write(v, PhantomData) + } + + 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<'a, T, U> +{ + const TAG: asn1::Tag = T::TAG; + fn parse_data(data: &'a [u8]) -> asn1::ParseResult { + Ok(Self::new_read(T::parse_data(data)?)) + } +} + +impl<'a, T: asn1::SimpleAsn1Writable, U: asn1::SimpleAsn1Writable> asn1::SimpleAsn1Writable + for Asn1ReadableOrWritable<'a, T, U> +{ + 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>, +} +// RSA-PSS ASN.1 default hash algorithm +pub const PSS_SHA1_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: AlgorithmParameters::Sha1(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, +}; + +// 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, + #[explicit(3)] + #[default(1u8)] + pub _trailer_field: u8, +} + +/// 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!(); + } +} + +#[cfg(test)] +mod tests { + use super::{Asn1ReadableOrWritable, RawTlv, UnvalidatedVisibleString}; + use asn1::Asn1Readable; + + #[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)); + } +} diff --git a/src/rust/cryptography-x509/src/crl.rs b/src/rust/cryptography-x509/src/crl.rs new file mode 100644 index 0000000..c81a3c4 --- /dev/null +++ b/src/rust/cryptography-x509/src/crl.rs @@ -0,0 +1,73 @@ +// This file is dual licensed under the terms 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::{self}, + name, +}; + +pub type ReasonFlags<'a> = + Option, asn1::OwnedBitString>>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, 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< + 'a, + asn1::SequenceOf<'a, RevokedCertificate<'a>>, + asn1::SequenceOfWriter<'a, RevokedCertificate<'a>, Vec>>, + >, +>; + +#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, 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, 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; diff --git a/src/rust/cryptography-x509/src/csr.rs b/src/rust/cryptography-x509/src/csr.rs new file mode 100644 index 0000000..d2cf9b5 --- /dev/null +++ b/src/rust/cryptography-x509/src/csr.rs @@ -0,0 +1,70 @@ +// This file is dual licensed under the terms 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::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< + 'a, + 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< + 'a, + asn1::SetOf<'a, asn1::Tlv<'a>>, + asn1::SetOfWriter<'a, common::RawTlv<'a>, [common::RawTlv<'a>; 1]>, + >, +} diff --git a/src/rust/cryptography-x509/src/extensions.rs b/src/rust/cryptography-x509/src/extensions.rs new file mode 100644 index 0000000..51c283a --- /dev/null +++ b/src/rust/cryptography-x509/src/extensions.rs @@ -0,0 +1,255 @@ +// This file is dual licensed under the terms 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 type RawExtensions<'a> = common::Asn1ReadableOrWritable< + 'a, + 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(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.0 + .as_ref() + .and_then(|exts| exts.unwrap_read().clone().find(|ext| &ext.extn_id == oid)) + } + + /// Returns a reference to the underlying extensions. + pub fn as_raw(&self) -> &Option> { + &self.0 + } +} + +#[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], +} + +#[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< + 'a, + asn1::SequenceOf<'a, AccessDescription<'a>>, + asn1::SequenceOfWriter<'a, AccessDescription<'a>, Vec>>, +>; + +// Needed due to clippy type complexity warning. +type SequenceOfPolicyQualifiers<'a> = common::Asn1ReadableOrWritable< + 'a, + 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< + 'a, + 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< + 'a, + 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< + 'a, + 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, +} + +#[cfg(test)] +mod tests { + use asn1::SequenceOfWriter; + + use crate::oid::{AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID}; + + use super::{BasicConstraints, Extension, Extensions}; + + #[test] + fn test_get_extension() { + let extension_value = BasicConstraints { + ca: true, + path_length: Some(3), + }; + let extension = Extension { + extn_id: BASIC_CONSTRAINTS_OID, + critical: true, + extn_value: &asn1::write_single(&extension_value).unwrap(), + }; + let extensions = SequenceOfWriter::new(vec![extension]); + + let der = asn1::write_single(&extensions).unwrap(); + + let extensions: Extensions = + Extensions::from_raw_extensions(Some(&asn1::parse_single(&der).unwrap())).unwrap(); + + assert!(&extensions.get_extension(&BASIC_CONSTRAINTS_OID).is_some()); + assert!(&extensions + .get_extension(&AUTHORITY_KEY_IDENTIFIER_OID) + .is_none()); + } +} diff --git a/src/rust/cryptography-x509/src/lib.rs b/src/rust/cryptography-x509/src/lib.rs new file mode 100644 index 0000000..131c3fd --- /dev/null +++ b/src/rust/cryptography-x509/src/lib.rs @@ -0,0 +1,18 @@ +// This file is dual licensed under the terms 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)] +// These can be removed once our MSRV is >1.60 +#![allow(renamed_and_removed_lints, clippy::eval_order_dependence)] + +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 pkcs7; diff --git a/src/rust/cryptography-x509/src/name.rs b/src/rust/cryptography-x509/src/name.rs new file mode 100644 index 0000000..f53e342 --- /dev/null +++ b/src/rust/cryptography-x509/src/name.rs @@ -0,0 +1,88 @@ +// This file is dual licensed under the terms 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 Name<'a> = common::Asn1ReadableOrWritable< + 'a, + asn1::SequenceOf<'a, asn1::SetOf<'a, common::AttributeTypeValue<'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, 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< + 'a, + asn1::SequenceOf<'a, GeneralName<'a>>, + asn1::SequenceOfWriter<'a, GeneralName<'a>, Vec>>, +>; diff --git a/src/rust/cryptography-x509/src/ocsp_req.rs b/src/rust/cryptography-x509/src/ocsp_req.rs new file mode 100644 index 0000000..ba54d39 --- /dev/null +++ b/src/rust/cryptography-x509/src/ocsp_req.rs @@ -0,0 +1,50 @@ +// This file is dual licensed under the terms 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::{self}, + 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< + 'a, + 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>, +} diff --git a/src/rust/cryptography-x509/src/ocsp_resp.rs b/src/rust/cryptography-x509/src/ocsp_resp.rs new file mode 100644 index 0000000..21f01e2 --- /dev/null +++ b/src/rust/cryptography-x509/src/ocsp_resp.rs @@ -0,0 +1,91 @@ +// This file is dual licensed under the terms 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::{self}, + 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< + 'a, + 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< + 'a, + 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, +} diff --git a/src/rust/cryptography-x509/src/oid.rs b/src/rust/cryptography-x509/src/oid.rs new file mode 100644 index 0000000..ac80b9a --- /dev/null +++ b/src/rust/cryptography-x509/src/oid.rs @@ -0,0 +1,99 @@ +// This file is dual licensed under the terms 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 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_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); + +// 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_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 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); diff --git a/src/rust/cryptography-x509/src/pkcs7.rs b/src/rust/cryptography-x509/src/pkcs7.rs new file mode 100644 index 0000000..c5b7a9e --- /dev/null +++ b/src/rust/cryptography-x509/src/pkcs7.rs @@ -0,0 +1,60 @@ +// This file is dual licensed under the terms 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); + +#[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_SIGNED_DATA_OID)] + SignedData(asn1::Explicit<'a, Box>, 0>), + #[defined_by(PKCS7_DATA_OID)] + Data(Option>), +} + +#[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 IssuerAndSerialNumber<'a> { + pub issuer: name::Name<'a>, + pub serial_number: asn1::BigInt<'a>, +} diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs index 1ca443d..12827cc 100644 --- a/src/rust/src/asn1.rs +++ b/src/rust/src/asn1.rs @@ -2,82 +2,15 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::x509::Name; +use crate::error::{CryptographyError, CryptographyResult}; +use asn1::SimpleAsn1Readable; +use cryptography_x509::certificate::Certificate; +use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo, Time}; +use cryptography_x509::name::Name; use pyo3::basic::CompareOp; use pyo3::types::IntoPyDict; use pyo3::ToPyObject; -pub enum PyAsn1Error { - Asn1Parse(asn1::ParseError), - Asn1Write(asn1::WriteError), - Py(pyo3::PyErr), -} - -impl From for PyAsn1Error { - fn from(e: asn1::ParseError) -> PyAsn1Error { - PyAsn1Error::Asn1Parse(e) - } -} - -impl From for PyAsn1Error { - fn from(e: asn1::WriteError) -> PyAsn1Error { - PyAsn1Error::Asn1Write(e) - } -} - -impl From for PyAsn1Error { - fn from(e: pyo3::PyErr) -> PyAsn1Error { - PyAsn1Error::Py(e) - } -} - -impl From> for PyAsn1Error { - fn from(e: pyo3::PyDowncastError<'_>) -> PyAsn1Error { - PyAsn1Error::Py(e.into()) - } -} - -impl From for PyAsn1Error { - fn from(e: pem::PemError) -> PyAsn1Error { - PyAsn1Error::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 pyo3::PyErr { - fn from(e: PyAsn1Error) -> pyo3::PyErr { - match e { - PyAsn1Error::Asn1Parse(asn1_error) => pyo3::exceptions::PyValueError::new_err(format!( - "error parsing asn1 value: {:?}", - asn1_error - )), - PyAsn1Error::Asn1Write(asn1::WriteError::AllocationError) => { - pyo3::exceptions::PyMemoryError::new_err( - "failed to allocate memory while performing ASN.1 serialization", - ) - } - PyAsn1Error::Py(py_error) => py_error, - } - } -} - -impl PyAsn1Error { - pub(crate) fn add_location(self, loc: asn1::ParseLocation) -> Self { - match self { - PyAsn1Error::Py(e) => PyAsn1Error::Py(e), - PyAsn1Error::Asn1Parse(e) => PyAsn1Error::Asn1Parse(e.add_location(loc)), - PyAsn1Error::Asn1Write(e) => PyAsn1Error::Asn1Write(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 PyAsn1Result = Result; - pub(crate) fn py_oid_to_oid(py_oid: &pyo3::PyAny) -> pyo3::PyResult { Ok(py_oid .downcast::>()? @@ -93,32 +26,17 @@ pub(crate) fn oid_to_py_oid<'p>( Ok(pyo3::Py::new(py, crate::oid::ObjectIdentifier { oid: oid.clone() })?.into_ref(py)) } -#[derive(asn1::Asn1Read)] -struct AlgorithmIdentifier<'a> { - _oid: asn1::ObjectIdentifier, - _params: Option>, -} - -#[derive(asn1::Asn1Read)] -struct Spki<'a> { - _algorithm: AlgorithmIdentifier<'a>, - data: asn1::BitString<'a>, -} - #[pyo3::prelude::pyfunction] -fn parse_spki_for_data(py: pyo3::Python<'_>, data: &[u8]) -> Result { - let spki = asn1::parse_single::>(data)?; - if spki.data.padding_bits() != 0 { +fn parse_spki_for_data( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { + 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(py, spki.data.as_bytes()).to_object(py)) -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct DssSignature<'a> { - r: asn1::BigUint<'a>, - s: asn1::BigUint<'a>, + Ok(pyo3::types::PyBytes::new(py, spki.subject_public_key.as_bytes()).to_object(py)) } pub(crate) fn big_byte_slice_to_py_int<'p>( @@ -127,11 +45,14 @@ pub(crate) fn big_byte_slice_to_py_int<'p>( ) -> pyo3::PyResult<&'p pyo3::PyAny> { let int_type = py.get_type::(); let kwargs = [("signed", true)].into_py_dict(py); - int_type.call_method("from_bytes", (v, "big"), Some(kwargs)) + int_type.call_method(pyo3::intern!(py, "from_bytes"), (v, "big"), Some(kwargs)) } #[pyo3::prelude::pyfunction] -fn decode_dss_signature(py: pyo3::Python<'_>, data: &[u8]) -> Result { +fn decode_dss_signature( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { let sig = asn1::parse_single::>(data)?; Ok(( @@ -155,8 +76,50 @@ pub(crate) fn py_uint_to_big_endian_bytes<'p>( // 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("bit_length")?.extract::()? / 8 + 1; - v.call_method1("to_bytes", (n, "big"))?.extract() + 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: &'p pyo3::PyAny, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let encoding_class = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "Encoding"))?; + + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + Ok(pyo3::types::PyBytes::new(py, &data)) + } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + Ok(pyo3::types::PyBytes::new( + py, + &pem::encode_config( + &pem::Pem { + tag: pem_tag, + contents: data, + }, + pem::EncodeConfig { + line_ending: pem::LineEnding::LF, + }, + ) + .into_bytes(), + )) + } else { + Err( + pyo3::exceptions::PyTypeError::new_err("encoding must be Encoding.DER or Encoding.PEM") + .into(), + ) + } } #[pyo3::prelude::pyfunction] @@ -164,7 +127,7 @@ fn encode_dss_signature( py: pyo3::Python<'_>, r: &pyo3::types::PyLong, s: &pyo3::types::PyLong, -) -> PyAsn1Result { +) -> CryptographyResult { let sig = DssSignature { r: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, r)?).unwrap(), s: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, s)?).unwrap(), @@ -173,7 +136,7 @@ fn encode_dss_signature( Ok(pyo3::types::PyBytes::new(py, &result).to_object(py)) } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.asn1")] struct TestCertificate { #[pyo3(get)] not_before_tag: u8, @@ -185,40 +148,7 @@ struct TestCertificate { subject_value_tags: Vec, } -#[derive(asn1::Asn1Read)] -struct Asn1Certificate<'a> { - tbs_cert: TbsCertificate<'a>, - _signature_alg: asn1::Sequence<'a>, - _signature: asn1::BitString<'a>, -} - -#[derive(asn1::Asn1Read)] -struct TbsCertificate<'a> { - #[explicit(0)] - _version: Option, - _serial: asn1::BigUint<'a>, - _signature_alg: asn1::Sequence<'a>, - - issuer: Name<'a>, - validity: Validity<'a>, - subject: Name<'a>, - - _spki: asn1::Sequence<'a>, - #[implicit(1)] - _issuer_unique_id: Option>, - #[implicit(2)] - _subject_unique_id: Option>, - #[explicit(3)] - _extensions: Option>, -} - -#[derive(asn1::Asn1Read)] -struct Validity<'a> { - not_before: asn1::Tlv<'a>, - not_after: asn1::Tlv<'a>, -} - -fn parse_name_value_tags(rdns: &mut Name<'_>) -> 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::>(); @@ -229,63 +159,33 @@ fn parse_name_value_tags(rdns: &mut Name<'_>) -> Vec { 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::prelude::pyfunction] -fn test_parse_certificate(data: &[u8]) -> Result { - let mut asn1_cert = asn1::parse_single::>(data)?; +fn test_parse_certificate(data: &[u8]) -> Result { + let cert = asn1::parse_single::>(data)?; Ok(TestCertificate { - not_before_tag: asn1_cert - .tbs_cert - .validity - .not_before - .tag() - .as_u8() - .unwrap(), - not_after_tag: asn1_cert.tbs_cert.validity.not_after.tag().as_u8().unwrap(), - issuer_value_tags: parse_name_value_tags(&mut asn1_cert.tbs_cert.issuer), - subject_value_tags: parse_name_value_tags(&mut asn1_cert.tbs_cert.subject), + 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), }) } pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let submod = pyo3::prelude::PyModule::new(py, "asn1")?; - submod.add_wrapped(pyo3::wrap_pyfunction!(parse_spki_for_data))?; + submod.add_function(pyo3::wrap_pyfunction!(parse_spki_for_data, submod)?)?; - submod.add_wrapped(pyo3::wrap_pyfunction!(decode_dss_signature))?; - submod.add_wrapped(pyo3::wrap_pyfunction!(encode_dss_signature))?; + submod.add_function(pyo3::wrap_pyfunction!(decode_dss_signature, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(encode_dss_signature, submod)?)?; - submod.add_wrapped(pyo3::wrap_pyfunction!(test_parse_certificate))?; + submod.add_function(pyo3::wrap_pyfunction!(test_parse_certificate, submod)?)?; Ok(submod) } - -#[cfg(test)] -mod tests { - use super::PyAsn1Error; - - #[test] - fn test_pyasn1error_from() { - pyo3::prepare_freethreaded_python(); - pyo3::Python::with_gil(|py| { - let e: PyAsn1Error = asn1::WriteError::AllocationError.into(); - assert!(matches!( - e, - PyAsn1Error::Asn1Write(asn1::WriteError::AllocationError) - )); - let py_e: pyo3::PyErr = e.into(); - assert!(py_e.is_instance::(py)); - - let e: PyAsn1Error = pyo3::PyDowncastError::new(py.None().as_ref(py), "abc").into(); - assert!(matches!(e, PyAsn1Error::Py(_))); - }) - } - - #[test] - fn test_pyasn1error_add_location() { - let py_err = pyo3::PyErr::new::("Error!"); - PyAsn1Error::Py(py_err).add_location(asn1::ParseLocation::Field("meh")); - - let asn1_write_err = asn1::WriteError::AllocationError; - PyAsn1Error::Asn1Write(asn1_write_err).add_location(asn1::ParseLocation::Field("meh")); - } -} diff --git a/src/rust/src/backend/dh.rs b/src/rust/src/backend/dh.rs new file mode 100644 index 0000000..9612106 --- /dev/null +++ b/src/rust/src/backend/dh.rs @@ -0,0 +1,444 @@ +// This file is dual licensed under the terms 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::asn1::encode_der_data; +use crate::backend::utils; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509; +use cryptography_x509::common; +use foreign_types_shared::ForeignTypeRef; + +const MIN_MODULUS_SIZE: u32 = 512; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] +struct DHPrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] +struct DHPublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.dh")] +struct DHParameters { + dh: openssl::dh::Dh, +} + +#[pyo3::prelude::pyfunction] +fn generate_parameters(generator: u32, key_size: u32) -> CryptographyResult { + if key_size < MIN_MODULUS_SIZE { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "DH key_size must be at least {} bits", + MIN_MODULUS_SIZE + )), + )); + } + 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 }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> DHPrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DHPrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> DHPublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DHPublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn from_der_parameters(data: &[u8]) -> CryptographyResult { + 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::prelude::pyfunction] +fn from_pem_parameters(data: &[u8]) -> CryptographyResult { + 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) +} + +fn dh_parameters_from_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult> { + let p = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "p"))?)?; + let q = numbers + .getattr(pyo3::intern!(py, "q"))? + .extract::>()? + .map(|v| utils::py_int_to_bn(py, v)) + .transpose()?; + let g = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "g"))?)?; + + Ok(openssl::dh::Dh::from_pqg(p, q, g)?) +} + +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[pyo3::prelude::pyfunction] +fn from_private_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let public_numbers = numbers.getattr(pyo3::intern!(py, "public_numbers"))?; + let parameter_numbers = public_numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + + let dh = dh_parameters_from_numbers(py, parameter_numbers)?; + + let pub_key = utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "y"))?)?; + let priv_key = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "x"))?)?; + + 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 }) +} + +#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] +#[pyo3::prelude::pyfunction] +fn from_public_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let parameter_numbers = numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + let dh = dh_parameters_from_numbers(py, parameter_numbers)?; + + let pub_key = utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "y"))?)?; + + let pkey = openssl::pkey::PKey::from_dh(dh.set_public_key(pub_key)?)?; + + Ok(DHPublicKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_parameter_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let dh = dh_parameters_from_numbers(py, numbers)?; + Ok(DHParameters { dh }) +} + +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::prelude::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>, + public_key: &DHPublicKey, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; + deriver + .set_peer(&public_key.pkey) + .map_err(|_| pyo3::exceptions::PyValueError::new_err("Error computing shared key."))?; + + Ok(pyo3::types::PyBytes::new_with(py, deriver.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<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + 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 dh_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dh" + ))?; + + let parameter_numbers = + dh_mod.call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?; + let public_numbers = dh_mod.call_method1( + pyo3::intern!(py, "DHPublicNumbers"), + (py_pub_key, parameter_numbers), + )?; + + Ok(dh_mod.call_method1( + pyo3::intern!(py, "DHPrivateNumbers"), + (py_private_key, 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::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let private_format_class = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "PrivateFormat"))?; + if !format.is(private_format_class.getattr(pyo3::intern!(py, "PKCS8"))?) { + 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::prelude::pymethods] +impl DHPublicKey { + #[getter] + fn key_size(&self) -> i32 { + self.pkey.dh().unwrap().prime_p().num_bits() + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let public_format_class = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "PublicFormat"))?; + if !format.is(public_format_class.getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?) { + 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<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + 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 dh_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dh" + ))?; + + let parameter_numbers = + dh_mod.call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?; + + Ok(dh_mod.call_method1( + pyo3::intern!(py, "DHPublicNumbers"), + (py_pub_key, parameter_numbers), + )?) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, DHPublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), + pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), + _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), + } + } +} + +#[pyo3::prelude::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<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + 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(py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dh" + ))? + .call_method1(pyo3::intern!(py, "DHParameterNumbers"), (py_p, py_g, py_q))?) + } + + fn parameter_bytes<'p>( + &self, + py: pyo3::Python<'p>, + encoding: &'p pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let parameter_format_class = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "ParameterFormat"))?; + if !format.is(parameter_format_class.getattr(pyo3::intern!(py, "PKCS3"))?) { + 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) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "dh")?; + m.add_function(pyo3::wrap_pyfunction!(generate_parameters, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_der_parameters, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_pem_parameters, m)?)?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + m.add_function(pyo3::wrap_pyfunction!(from_private_numbers, m)?)?; + #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] + m.add_function(pyo3::wrap_pyfunction!(from_public_numbers, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_parameter_numbers, m)?)?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + m.add("MIN_MODULUS_SIZE", MIN_MODULUS_SIZE)?; + + Ok(m) +} diff --git a/src/rust/src/backend/dsa.rs b/src/rust/src/backend/dsa.rs new file mode 100644 index 0000000..59a5a67 --- /dev/null +++ b/src/rust/src/backend/dsa.rs @@ -0,0 +1,333 @@ +// This file is dual licensed under the terms 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::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.dsa", + name = "DSAPrivateKey" +)] +struct DsaPrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.dsa", + name = "DSAPublicKey" +)] +struct DsaPublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.dsa", + name = "DSAParameters" +)] +struct DsaParameters { + dsa: openssl::dsa::Dsa, +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> DsaPrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DsaPrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> DsaPublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + DsaPublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn generate_parameters(key_size: u32) -> CryptographyResult { + let dsa = openssl::dsa::Dsa::generate_params(key_size)?; + Ok(DsaParameters { dsa }) +} + +#[pyo3::prelude::pyfunction] +fn from_private_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let public_numbers = numbers.getattr(pyo3::intern!(py, "public_numbers"))?; + let parameter_numbers = public_numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + + let dsa = openssl::dsa::Dsa::from_private_components( + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "p"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "q"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "g"))?)?, + utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "x"))?)?, + utils::py_int_to_bn(py, public_numbers.getattr(pyo3::intern!(py, "y"))?)?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_dsa(dsa)?; + Ok(DsaPrivateKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_public_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let parameter_numbers = numbers.getattr(pyo3::intern!(py, "parameter_numbers"))?; + + let dsa = openssl::dsa::Dsa::from_public_components( + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "p"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "q"))?)?, + utils::py_int_to_bn(py, parameter_numbers.getattr(pyo3::intern!(py, "g"))?)?, + utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "y"))?)?, + ) + .unwrap(); + let pkey = openssl::pkey::PKey::from_dsa(dsa)?; + Ok(DsaPublicKey { pkey }) +} + +#[pyo3::prelude::pyfunction] +fn from_parameter_numbers( + py: pyo3::Python<'_>, + numbers: &pyo3::PyAny, +) -> CryptographyResult { + let dsa = openssl::dsa::Dsa::from_pqg( + utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "p"))?)?, + utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "q"))?)?, + utils::py_int_to_bn(py, numbers.getattr(pyo3::intern!(py, "g"))?)?, + ) + .unwrap(); + 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::prelude::pymethods] +impl DsaPrivateKey { + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + data: &pyo3::types::PyBytes, + algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let (data, _): (&[u8], &pyo3::PyAny) = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.backends.openssl.utils" + ))? + .call_method1( + pyo3::intern!(py, "_calculate_digest_and_algorithm"), + (data, algorithm), + )? + .extract()?; + + let mut signer = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + signer.sign_init()?; + let mut sig = vec![]; + signer.sign_to_vec(data, &mut sig)?; + Ok(pyo3::types::PyBytes::new(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<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + 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 dsa_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dsa" + ))?; + + let parameter_numbers = + dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?; + let public_numbers = dsa_mod.call_method1( + pyo3::intern!(py, "DSAPublicNumbers"), + (py_pub_key, parameter_numbers), + )?; + + Ok(dsa_mod.call_method1( + pyo3::intern!(py, "DSAPrivateNumbers"), + (py_private_key, public_numbers), + )?) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + true, + false, + ) + } +} + +#[pyo3::prelude::pymethods] +impl DsaPublicKey { + fn verify( + &self, + py: pyo3::Python<'_>, + signature: &[u8], + data: &pyo3::types::PyBytes, + algorithm: &pyo3::PyAny, + ) -> CryptographyResult<()> { + let (data, _): (&[u8], &pyo3::PyAny) = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.backends.openssl.utils" + ))? + .call_method1( + pyo3::intern!(py, "_calculate_digest_and_algorithm"), + (data, algorithm), + )? + .extract()?; + + let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; + verifier.verify_init()?; + let valid = verifier.verify(data, signature).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<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + 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 dsa_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dsa" + ))?; + + let parameter_numbers = + dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?; + Ok(dsa_mod.call_method1( + pyo3::intern!(py, "DSAPublicNumbers"), + (py_pub_key, parameter_numbers), + )?) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, false) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, DsaPublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), + pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), + _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), + } + } +} + +#[pyo3::prelude::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<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + 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())?; + + let dsa_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dsa" + ))?; + + Ok(dsa_mod.call_method1(pyo3::intern!(py, "DSAParameterNumbers"), (py_p, py_q, py_g))?) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "dsa")?; + m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(generate_parameters, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_private_numbers, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_numbers, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_parameter_numbers, m)?)?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/ed25519.rs b/src/rust/src/backend/ed25519.rs new file mode 100644 index 0000000..5ac4823 --- /dev/null +++ b/src/rust/src/backend/ed25519.rs @@ -0,0 +1,178 @@ +// This file is dual licensed under the terms 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 foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +struct Ed25519PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed25519")] +struct Ed25519PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(Ed25519PrivateKey { + pkey: openssl::pkey::PKey::generate_ed25519()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> Ed25519PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed25519PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> Ed25519PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed25519PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::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::prelude::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::prelude::pymethods] +impl Ed25519PrivateKey { + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + data: &[u8], + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; + Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { + let n = signer + .sign_oneshot(b, data) + .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<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + true, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl Ed25519PublicKey { + fn verify(&self, signature: &[u8], data: &[u8]) -> CryptographyResult<()> { + let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? + .verify_oneshot(signature, data) + .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<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, Ed25519PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), + pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), + _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "ed25519")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/ed448.rs b/src/rust/src/backend/ed448.rs new file mode 100644 index 0000000..c0c621a --- /dev/null +++ b/src/rust/src/backend/ed448.rs @@ -0,0 +1,175 @@ +// This file is dual licensed under the terms 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 foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +struct Ed448PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.ed448")] +struct Ed448PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(Ed448PrivateKey { + pkey: openssl::pkey::PKey::generate_ed448()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> Ed448PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed448PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> Ed448PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + Ed448PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::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::prelude::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::prelude::pymethods] +impl Ed448PrivateKey { + fn sign<'p>( + &self, + py: pyo3::Python<'p>, + data: &[u8], + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut signer = openssl::sign::Signer::new_without_digest(&self.pkey)?; + Ok(pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { + let n = signer + .sign_oneshot(b, data) + .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<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + true, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl Ed448PublicKey { + fn verify(&self, signature: &[u8], data: &[u8]) -> CryptographyResult<()> { + let valid = openssl::sign::Verifier::new_without_digest(&self.pkey)? + .verify_oneshot(signature, data)?; + + if !valid { + return Err(CryptographyError::from( + exceptions::InvalidSignature::new_err(()), + )); + } + + Ok(()) + } + + fn public_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, true, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, Ed448PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), + pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), + _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "ed448")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/hashes.rs b/src/rust/src/backend/hashes.rs new file mode 100644 index 0000000..d9157d6 --- /dev/null +++ b/src/rust/src/backend/hashes.rs @@ -0,0 +1,143 @@ +// This file is dual licensed under the terms 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; +use std::borrow::Cow; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.hashes")] +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::PyAny, +) -> CryptographyResult { + let hash_algorithm_class = py + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "HashAlgorithm"))?; + if !algorithm.is_instance(hash_algorithm_class)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err("Expected instance of hashes.HashAlgorithm."), + )); + } + + let name = algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?; + 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) + }; + + match openssl::hash::MessageDigest::from_name(&openssl_name) { + Some(md) => Ok(md), + None => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!("{} is not a supported hash on this backend", name), + exceptions::Reasons::UNSUPPORTED_HASH, + )), + )), + } +} + +#[pyo3::pymethods] +impl Hash { + #[new] + #[pyo3(signature = (algorithm, backend=None))] + fn new( + py: pyo3::Python<'_>, + algorithm: &pyo3::PyAny, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let md = message_digest_from_algorithm(py, algorithm)?; + let ctx = openssl::hash::Hasher::new(md)?; + + Ok(Hash { + algorithm: algorithm.into(), + 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<&'p pyo3::types::PyBytes> { + #[cfg(not(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL)))] + { + let xof_class = py + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "ExtendableOutputFunction"))?; + let algorithm = self.algorithm.clone_ref(py); + let algorithm = algorithm.as_ref(py); + if algorithm.is_instance(xof_class)? { + let ctx = self.get_mut_ctx()?; + let digest_size = algorithm + .getattr(pyo3::intern!(py, "digest_size"))? + .extract::()?; + let result = pyo3::types::PyBytes::new_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(py, &data)) + } + + fn copy(&self, py: pyo3::Python<'_>) -> CryptographyResult { + Ok(Hash { + algorithm: self.algorithm.clone_ref(py), + ctx: Some(self.get_ctx()?.clone()), + }) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "hashes")?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/hmac.rs b/src/rust/src/backend/hmac.rs new file mode 100644 index 0000000..13509b8 --- /dev/null +++ b/src/rust/src/backend/hmac.rs @@ -0,0 +1,95 @@ +// This file is dual licensed under the terms 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; + +#[pyo3::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.openssl.hmac", + name = "HMAC" +)] +struct Hmac { + #[pyo3(get)] + algorithm: pyo3::Py, + ctx: Option, +} + +impl Hmac { + 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::PyAny, + backend: Option<&pyo3::PyAny>, + ) -> CryptographyResult { + let _ = backend; + + let md = message_digest_from_algorithm(py, algorithm)?; + let ctx = cryptography_openssl::hmac::Hmac::new(key.as_bytes(), md)?; + + Ok(Hmac { + ctx: Some(ctx), + algorithm: algorithm.into(), + }) + } + + 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<&'p pyo3::types::PyBytes> { + let data = self.get_mut_ctx()?.finish()?; + self.ctx = None; + Ok(pyo3::types::PyBytes::new(py, &data)) + } + + fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { + let actual = self.finalize(py)?.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), + }) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "hmac")?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/kdf.rs b/src/rust/src/backend/kdf.rs new file mode 100644 index 0000000..de527f4 --- /dev/null +++ b/src/rust/src/backend/kdf.rs @@ -0,0 +1,60 @@ +// This file is dual licensed under the terms 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::prelude::pyfunction] +fn derive_pbkdf2_hmac<'p>( + py: pyo3::Python<'p>, + key_material: CffiBuf<'_>, + algorithm: &pyo3::PyAny, + salt: &[u8], + iterations: usize, + length: usize, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let md = hashes::message_digest_from_algorithm(py, algorithm)?; + + Ok(pyo3::types::PyBytes::new_with(py, length, |b| { + openssl::pkcs5::pbkdf2_hmac(key_material.as_bytes(), salt, iterations, md, b).unwrap(); + Ok(()) + })?) +} + +#[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] +#[pyo3::prelude::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<&'p pyo3::types::PyBytes> { + Ok(pyo3::types::PyBytes::new_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 {}MB of memory.", + min_memory + )) + }) + })?) +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "kdf")?; + + m.add_function(pyo3::wrap_pyfunction!(derive_pbkdf2_hmac, m)?)?; + #[cfg(not(CRYPTOGRAPHY_IS_LIBRESSL))] + m.add_function(pyo3::wrap_pyfunction!(derive_scrypt, m)?)?; + + Ok(m) +} diff --git a/src/rust/src/backend/mod.rs b/src/rust/src/backend/mod.rs new file mode 100644 index 0000000..765b0ab --- /dev/null +++ b/src/rust/src/backend/mod.rs @@ -0,0 +1,42 @@ +// This file is dual licensed under the terms 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 dh; +pub(crate) mod dsa; +#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] +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 poly1305; +pub(crate) mod utils; +#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] +pub(crate) mod x25519; +#[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] +pub(crate) mod x448; + +pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { + module.add_submodule(dh::create_module(module.py())?)?; + module.add_submodule(dsa::create_module(module.py())?)?; + + #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] + module.add_submodule(ed25519::create_module(module.py())?)?; + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + module.add_submodule(ed448::create_module(module.py())?)?; + + #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] + module.add_submodule(x25519::create_module(module.py())?)?; + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + module.add_submodule(x448::create_module(module.py())?)?; + + module.add_submodule(poly1305::create_module(module.py())?)?; + + module.add_submodule(hashes::create_module(module.py())?)?; + module.add_submodule(hmac::create_module(module.py())?)?; + module.add_submodule(kdf::create_module(module.py())?)?; + + Ok(()) +} diff --git a/src/rust/src/backend/poly1305.rs b/src/rust/src/backend/poly1305.rs new file mode 100644 index 0000000..17d279a --- /dev/null +++ b/src/rust/src/backend/poly1305.rs @@ -0,0 +1,127 @@ +// This file is dual licensed under the terms 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; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")] +struct Poly1305 { + signer: Option>, +} + +impl Poly1305 { + fn get_mut_signer(&mut self) -> CryptographyResult<&mut openssl::sign::Signer<'static>> { + if let Some(signer) = self.signer.as_mut() { + return Ok(signer); + }; + Err(already_finalized_error()) + } +} + +#[pyo3::pymethods] +impl Poly1305 { + #[new] + fn new(key: CffiBuf<'_>) -> CryptographyResult { + #[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))] + { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + "poly1305 is not supported by this version of OpenSSL.", + exceptions::Reasons::UNSUPPORTED_MAC, + )), + )); + } + + #[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))] + { + 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(Poly1305 { + signer: Some( + openssl::sign::Signer::new_without_digest(&pkey).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long") + })?, + ), + }) + } + } + + #[staticmethod] + fn generate_tag<'p>( + py: pyo3::Python<'p>, + key: CffiBuf<'_>, + data: CffiBuf<'_>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + 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.get_mut_signer()?.update(data.as_bytes())?; + Ok(()) + } + + fn finalize<'p>( + &mut self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let signer = self.get_mut_signer()?; + let result = pyo3::types::PyBytes::new_with(py, signer.len()?, |b| { + let n = signer.sign(b).unwrap(); + assert_eq!(n, b.len()); + Ok(()) + })?; + self.signer = None; + Ok(result) + } + + fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> { + let actual = self.finalize(py)?.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(()) + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "poly1305")?; + + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/utils.rs b/src/rust/src/backend/utils.rs new file mode 100644 index 0000000..dea3611 --- /dev/null +++ b/src/rust/src/backend/utils.rs @@ -0,0 +1,302 @@ +// This file is dual licensed under the terms 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::{CryptographyError, CryptographyResult}; + +pub(crate) fn py_int_to_bn( + py: pyo3::Python<'_>, + v: &pyo3::PyAny, +) -> CryptographyResult { + let n = v + .call_method0(pyo3::intern!(py, "bit_length"))? + .extract::()? + / 8 + + 1; + let bytes: &[u8] = 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<&'p pyo3::PyAny> { + assert!(!b.is_negative()); + + let int_type = py.get_type::(); + 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::PyAny, + pkey: &openssl::pkey::PKey, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + openssh_allowed: bool, + raw_allowed: bool, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let serialization_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))?; + let encoding_class: &pyo3::types::PyType = serialization_mod + .getattr(pyo3::intern!(py, "Encoding"))? + .extract()?; + let private_format_class: &pyo3::types::PyType = serialization_mod + .getattr(pyo3::intern!(py, "PrivateFormat"))? + .extract()?; + let key_serialization_encryption_class: &pyo3::types::PyType = serialization_mod + .getattr(pyo3::intern!(py, "KeySerializationEncryption"))? + .extract()?; + let no_encryption_class: &pyo3::types::PyType = serialization_mod + .getattr(pyo3::intern!(py, "NoEncryption"))? + .extract()?; + let best_available_encryption_class: &pyo3::types::PyType = serialization_mod + .getattr(pyo3::intern!(py, "BestAvailableEncryption"))? + .extract()?; + + if !encoding.is_instance(encoding_class)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "encoding must be an item from the Encoding enum", + ), + )); + } + if !format.is_instance(private_format_class)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "format must be an item from the PrivateFormat enum", + ), + )); + } + if !encryption_algorithm.is_instance(key_serialization_encryption_class)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "Encryption algorithm must be a KeySerializationEncryption instance", + ), + )); + } + + #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] + if raw_allowed + && (encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) + || format.is(private_format_class.getattr(pyo3::intern!(py, "Raw"))?)) + { + if !encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) + || !format.is(private_format_class.getattr(pyo3::intern!(py, "Raw"))?) + || !encryption_algorithm.is_instance(no_encryption_class)? + { + 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(py, &raw_bytes)); + } + + let password = if encryption_algorithm.is_instance(no_encryption_class)? { + b"" + } else if encryption_algorithm.is_instance(best_available_encryption_class)? { + encryption_algorithm + .getattr(pyo3::intern!(py, "password"))? + .extract::<&[u8]>()? + } 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(private_format_class.getattr(pyo3::intern!(py, "PKCS8"))?) { + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + 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(py, &pem_bytes)); + } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + 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(py, &der_bytes)); + } + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Unsupported encoding for PKCS8"), + )); + } + + if format.is(private_format_class.getattr(pyo3::intern!(py, "TraditionalOpenSSL"))?) { + if let Ok(dsa) = pkey.dsa() { + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + 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(py, &pem_bytes)); + } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + 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(py, &der_bytes)); + } + } + } + + // OpenSSH + PEM + if openssh_allowed && format.is(private_format_class.getattr(pyo3::intern!(py, "OpenSSH"))?) { + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + return Ok(py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization.ssh" + ))? + .call_method1( + pyo3::intern!(py, "_serialize_ssh_private_key"), + (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::PyAny, + pkey: &openssl::pkey::PKey, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + openssh_allowed: bool, + raw_allowed: bool, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let serialization_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))?; + let encoding_class: &pyo3::types::PyType = serialization_mod + .getattr(pyo3::intern!(py, "Encoding"))? + .extract()?; + let public_format_class: &pyo3::types::PyType = serialization_mod + .getattr(pyo3::intern!(py, "PublicFormat"))? + .extract()?; + + if !encoding.is_instance(encoding_class)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "encoding must be an item from the Encoding enum", + ), + )); + } + if !format.is_instance(public_format_class)? { + return Err(CryptographyError::from( + pyo3::exceptions::PyTypeError::new_err( + "format must be an item from the PublicFormat enum", + ), + )); + } + + #[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))] + if raw_allowed + && (encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) + || format.is(public_format_class.getattr(pyo3::intern!(py, "Raw"))?)) + { + if !encoding.is(encoding_class.getattr(pyo3::intern!(py, "Raw"))?) + || !format.is(public_format_class.getattr(pyo3::intern!(py, "Raw"))?) + { + 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(py, &raw_bytes)); + } + + // SubjectPublicKeyInfo + PEM/DER + if format.is(public_format_class.getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?) { + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "PEM"))?) { + let pem_bytes = pkey.public_key_to_pem()?; + return Ok(pyo3::types::PyBytes::new(py, &pem_bytes)); + } else if encoding.is(encoding_class.getattr(pyo3::intern!(py, "DER"))?) { + let der_bytes = pkey.public_key_to_der()?; + return Ok(pyo3::types::PyBytes::new(py, &der_bytes)); + } + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "SubjectPublicKeyInfo works only with PEM or DER encoding", + ), + )); + } + + // OpenSSH + OpenSSH + if openssh_allowed && format.is(public_format_class.getattr(pyo3::intern!(py, "OpenSSH"))?) { + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "OpenSSH"))?) { + return Ok(py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization.ssh" + ))? + .call_method1(pyo3::intern!(py, "serialize_ssh_public_key"), (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"), + )) +} diff --git a/src/rust/src/backend/x25519.rs b/src/rust/src/backend/x25519.rs new file mode 100644 index 0000000..f27c059 --- /dev/null +++ b/src/rust/src/backend/x25519.rs @@ -0,0 +1,166 @@ +// This file is dual licensed under the terms 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; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +struct X25519PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x25519")] +struct X25519PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(X25519PrivateKey { + pkey: openssl::pkey::PKey::generate_x25519()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> X25519PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X25519PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> X25519PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X25519PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::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::prelude::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::prelude::pymethods] +impl X25519PrivateKey { + fn exchange<'p>( + &self, + py: pyo3::Python<'p>, + public_key: &X25519PublicKey, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; + deriver.set_peer(&public_key.pkey)?; + + Ok(pyo3::types::PyBytes::new_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<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + false, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl X25519PublicKey { + fn public_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, X25519PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), + pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), + _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "x25519")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/backend/x448.rs b/src/rust/src/backend/x448.rs new file mode 100644 index 0000000..97e52ee --- /dev/null +++ b/src/rust/src/backend/x448.rs @@ -0,0 +1,165 @@ +// This file is dual licensed under the terms 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; +use foreign_types_shared::ForeignTypeRef; + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x448")] +struct X448PrivateKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.x448")] +struct X448PublicKey { + pkey: openssl::pkey::PKey, +} + +#[pyo3::prelude::pyfunction] +fn generate_key() -> CryptographyResult { + Ok(X448PrivateKey { + pkey: openssl::pkey::PKey::generate_x448()?, + }) +} + +#[pyo3::prelude::pyfunction] +fn private_key_from_ptr(ptr: usize) -> X448PrivateKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X448PrivateKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::pyfunction] +fn public_key_from_ptr(ptr: usize) -> X448PublicKey { + let pkey = unsafe { openssl::pkey::PKeyRef::from_ptr(ptr as *mut _) }; + X448PublicKey { + pkey: pkey.to_owned(), + } +} + +#[pyo3::prelude::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::prelude::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::prelude::pymethods] +impl X448PrivateKey { + fn exchange<'p>( + &self, + py: pyo3::Python<'p>, + public_key: &X448PublicKey, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let mut deriver = openssl::derive::Deriver::new(&self.pkey)?; + deriver.set_peer(&public_key.pkey)?; + + Ok(pyo3::types::PyBytes::new_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<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_private_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn private_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + encryption_algorithm: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_private_bytes( + py, + slf, + &slf.borrow().pkey, + encoding, + format, + encryption_algorithm, + false, + true, + ) + } +} + +#[pyo3::prelude::pymethods] +impl X448PublicKey { + fn public_bytes_raw<'p>( + &self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let raw_bytes = self.pkey.raw_public_key()?; + Ok(pyo3::types::PyBytes::new(py, &raw_bytes)) + } + + fn public_bytes<'p>( + slf: &pyo3::PyCell, + py: pyo3::Python<'p>, + encoding: &pyo3::PyAny, + format: &pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + utils::pkey_public_bytes(py, slf, &slf.borrow().pkey, encoding, format, false, true) + } + + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, X448PublicKey>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.pkey.public_eq(&other.pkey)), + pyo3::basic::CompareOp::Ne => Ok(!self.pkey.public_eq(&other.pkey)), + _ => Err(pyo3::exceptions::PyTypeError::new_err("Cannot be ordered")), + } + } +} + +pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let m = pyo3::prelude::PyModule::new(py, "x448")?; + m.add_function(pyo3::wrap_pyfunction!(generate_key, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(private_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(public_key_from_ptr, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_private_bytes, m)?)?; + m.add_function(pyo3::wrap_pyfunction!(from_public_bytes, m)?)?; + + m.add_class::()?; + m.add_class::()?; + + Ok(m) +} diff --git a/src/rust/src/buf.rs b/src/rust/src/buf.rs new file mode 100644 index 0000000..b7afcf0 --- /dev/null +++ b/src/rust/src/buf.rs @@ -0,0 +1,49 @@ +// This file is dual licensed under the terms 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, slice}; + +pub(crate) struct CffiBuf<'p> { + _pyobj: &'p pyo3::PyAny, + _bufobj: &'p pyo3::PyAny, + buf: &'p [u8], +} + +impl CffiBuf<'_> { + pub(crate) fn as_bytes(&self) -> &[u8] { + self.buf + } +} + +impl<'a> pyo3::conversion::FromPyObject<'a> for CffiBuf<'a> { + fn extract(pyobj: &'a pyo3::PyAny) -> pyo3::PyResult { + let py = pyobj.py(); + + let (bufobj, ptrval): (&pyo3::PyAny, usize) = py + .import(pyo3::intern!(py, "cryptography.utils"))? + .call_method1(pyo3::intern!(py, "_extract_buffer_length"), (pyobj,))? + .extract()?; + + let len = bufobj.len()?; + let ptr = if len == 0 { + ptr::NonNull::dangling().as_ptr() + } else { + ptrval as *const u8 + }; + + Ok(CffiBuf { + _pyobj: pyobj, + _bufobj: bufobj, + // 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. + buf: unsafe { slice::from_raw_parts(ptr, len) }, + }) + } +} diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs new file mode 100644 index 0000000..6699520 --- /dev/null +++ b/src/rust/src/error.rs @@ -0,0 +1,144 @@ +// This file is dual licensed under the terms 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::{exceptions, OpenSSLError}; +use pyo3::ToPyObject; + +pub enum CryptographyError { + Asn1Parse(asn1::ParseError), + Asn1Write(asn1::WriteError), + 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::PyDowncastError<'_>) -> 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 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::Py(py_error) => py_error, + CryptographyError::OpenSSL(error_stack) => pyo3::Python::with_gil(|py| { + let errors = pyo3::types::PyList::empty(py); + for e in error_stack.errors() { + errors + .append( + pyo3::PyCell::new(py, OpenSSLError { e: e.clone() }) + .expect("Failed to create OpenSSLError"), + ) + .expect("Failed to append to list"); + } + 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::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; + +#[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::PyDowncastError::new(py.None().as_ref(py), "abc").into(); + assert!(matches!(e, CryptographyError::Py(_))); + }) + } + + #[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")); + } +} diff --git a/src/rust/src/exceptions.rs b/src/rust/src/exceptions.rs new file mode 100644 index 0000000..ec1e18c --- /dev/null +++ b/src/rust/src/exceptions.rs @@ -0,0 +1,40 @@ +// This file is dual licensed under the terms 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::prelude::pyclass( + module = "cryptography.hazmat.bindings._rust.exceptions", + name = "_Reasons" +)] +#[allow(non_camel_case_types)] +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!(cryptography.exceptions, AlreadyFinalized); +pyo3::import_exception!(cryptography.exceptions, InternalError); +pyo3::import_exception!(cryptography.exceptions, InvalidSignature); +pyo3::import_exception!(cryptography.exceptions, UnsupportedAlgorithm); +pyo3::import_exception!(cryptography.x509, AttributeNotFound); +pyo3::import_exception!(cryptography.x509, DuplicateExtension); +pyo3::import_exception!(cryptography.x509, UnsupportedGeneralNameType); +pyo3::import_exception!(cryptography.x509, InvalidVersion); + +pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let submod = pyo3::prelude::PyModule::new(py, "exceptions")?; + + submod.add_class::()?; + + Ok(submod) +} diff --git a/src/rust/src/intern.rs b/src/rust/src/intern.rs deleted file mode 100644 index 94f2118..0000000 --- a/src/rust/src/intern.rs +++ /dev/null @@ -1,44 +0,0 @@ -// This file is dual licensed under the terms of the Apache License, Version -// 2.0, and the BSD License. See the LICENSE file in the root of this repository -// for complete details. - -// This file is a backport of `pyo3::intern!` from pyo3 0.16. - -#[macro_export] -macro_rules! intern { - ($py: expr, $text: expr) => {{ - static INTERNED: $crate::intern::Interned = $crate::intern::Interned::new($text); - INTERNED.get($py) - }}; -} - -#[doc(hidden)] -pub struct Interned( - &'static str, - pyo3::once_cell::GILOnceCell>, -); - -impl Interned { - pub const fn new(value: &'static str) -> Self { - Interned(value, pyo3::once_cell::GILOnceCell::new()) - } - - #[inline] - pub fn get<'py>(&'py self, py: pyo3::Python<'py>) -> &'py pyo3::types::PyString { - self.1 - .get_or_init(py, || pyo3::types::PyString::new(py, self.0).into()) - .as_ref(py) - } -} - -#[cfg(test)] -mod tests { - use super::Interned; - - #[test] - fn test_interned_new() { - for s in ["abc", "123"] { - Interned::new(s); - } - } -} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 067af4c..b58fa31 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -3,19 +3,19 @@ // for complete details. #![deny(rust_2018_idioms)] -// Temporarily allow `clippy::borrow_deref_ref` until we can upgrade to the -// latest pyo3: https://github.com/PyO3/pyo3/pull/2503 -// `unknown_lints` is required until GHA upgrades their rustc. -#![allow(unknown_lints, clippy::borrow_deref_ref)] +// Work-around for https://github.com/PyO3/pyo3/issues/3561 +#![allow(unknown_lints, clippy::unnecessary_fallible_conversions)] mod asn1; -mod intern; +mod backend; +mod buf; +mod error; +mod exceptions; pub(crate) mod oid; +mod pkcs7; mod pool; mod x509; -use std::convert::TryInto; - /// 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 { @@ -77,6 +77,67 @@ fn check_ansix923_padding(data: &[u8]) -> bool { (mismatch & 1) == 0 } +#[pyo3::prelude::pyfunction] +fn openssl_version() -> i64 { + openssl::version::number() +} + +#[pyo3::prelude::pyfunction] +fn raise_openssl_error() -> crate::error::CryptographyResult<()> { + Err(openssl::error::ErrorStack::get().into()) +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl")] +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 _lib_reason_match(&self, lib: i32, reason: i32) -> bool { + self.e.library_code() == lib && self.e.reason_code() == reason + } + + fn __repr__(&self) -> pyo3::PyResult { + Ok(format!( + "", + self.e.code(), + self.e.library_code(), + self.e.reason_code(), + self.e.reason().unwrap_or("") + )) + } +} + +#[pyo3::prelude::pyfunction] +fn capture_error_stack(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::types::PyList> { + let errs = pyo3::types::PyList::empty(py); + for e in openssl::error::ErrorStack::get().errors() { + errs.append(pyo3::PyCell::new(py, OpenSSLError { e: e.clone() })?)?; + } + Ok(errs) +} + +#[pyo3::prelude::pyfunction] +fn is_fips_enabled() -> bool { + cryptography_openssl::fips::is_enabled() +} + #[pyo3::prelude::pymodule] fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> { m.add_function(pyo3::wrap_pyfunction!(check_pkcs7_padding, m)?)?; @@ -85,6 +146,8 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> m.add_class::()?; m.add_submodule(asn1::create_submodule(py)?)?; + m.add_submodule(pkcs7::create_submodule(py)?)?; + m.add_submodule(exceptions::create_submodule(py)?)?; let x509_mod = pyo3::prelude::PyModule::new(py, "x509")?; crate::x509::certificate::add_to_module(x509_mod)?; @@ -99,6 +162,17 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> crate::x509::ocsp_resp::add_to_module(ocsp_mod)?; m.add_submodule(ocsp_mod)?; + m.add_submodule(cryptography_cffi::create_module(py)?)?; + + let openssl_mod = pyo3::prelude::PyModule::new(py, "openssl")?; + openssl_mod.add_function(pyo3::wrap_pyfunction!(openssl_version, m)?)?; + openssl_mod.add_function(pyo3::wrap_pyfunction!(raise_openssl_error, m)?)?; + openssl_mod.add_function(pyo3::wrap_pyfunction!(capture_error_stack, m)?)?; + openssl_mod.add_function(pyo3::wrap_pyfunction!(is_fips_enabled, m)?)?; + openssl_mod.add_class::()?; + crate::backend::add_to_module(openssl_mod)?; + m.add_submodule(openssl_mod)?; + Ok(()) } diff --git a/src/rust/src/oid.rs b/src/rust/src/oid.rs index bc65daf..f6dae61 100644 --- a/src/rust/src/oid.rs +++ b/src/rust/src/oid.rs @@ -2,11 +2,11 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::PyAsn1Result; +use crate::error::CryptographyResult; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] pub(crate) struct ObjectIdentifier { pub(crate) oid: asn1::ObjectIdentifier, } @@ -14,7 +14,7 @@ pub(crate) struct ObjectIdentifier { #[pyo3::pymethods] impl ObjectIdentifier { #[new] - fn new(value: &str) -> PyAsn1Result { + fn new(value: &str) -> CryptographyResult { let oid = asn1::ObjectIdentifier::from_string(value) .ok_or_else(|| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?; Ok(ObjectIdentifier { oid }) @@ -31,18 +31,16 @@ impl ObjectIdentifier { py: pyo3::Python<'p>, ) -> pyo3::PyResult<&'p pyo3::PyAny> { let oid_names = py - .import("cryptography.hazmat._oid")? - .getattr(crate::intern!(py, "_OID_NAMES"))?; - oid_names.call_method1("get", (slf, "Unknown OID")) + .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? + .getattr(pyo3::intern!(py, "_OID_NAMES"))?; + oid_names.call_method1(pyo3::intern!(py, "get"), (slf, "Unknown OID")) } -} -#[pyo3::prelude::pyproto] -impl pyo3::PyObjectProtocol for ObjectIdentifier { - fn __repr__(&self) -> pyo3::PyResult { - let gil = pyo3::Python::acquire_gil(); - let py = gil.python(); + fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { + slf + } + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let self_clone = pyo3::PyCell::new( py, ObjectIdentifier { @@ -58,7 +56,7 @@ impl pyo3::PyObjectProtocol for ObjectIdentifier { fn __richcmp__( &self, - other: pyo3::PyRef, + other: pyo3::PyRef<'_, ObjectIdentifier>, op: pyo3::basic::CompareOp, ) -> pyo3::PyResult { match op { diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs new file mode 100644 index 0000000..d2c500a --- /dev/null +++ b/src/rust/src/pkcs7.rs @@ -0,0 +1,389 @@ +// This file is dual licensed under the terms 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::asn1::encode_der_data; +use crate::buf::CffiBuf; +use crate::error::CryptographyResult; +use crate::x509; +use cryptography_x509::csr::Attribute; +use cryptography_x509::{common, oid, pkcs7}; +use once_cell::sync::Lazy; +use std::borrow::Cow; +use std::collections::HashMap; +use std::ops::Deref; + +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); + +const AES_256_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 42); +const AES_192_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 22); +const AES_128_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 2); + +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::prelude::pyfunction] +fn serialize_certificates<'p>( + py: pyo3::Python<'p>, + py_certs: Vec>, + encoding: &'p pyo3::PyAny, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + 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_value_public()) + .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(Some(asn1::Explicit::new(b""))), + }, + 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::prelude::pyfunction] +fn sign_and_serialize<'p>( + py: pyo3::Python<'p>, + builder: &'p pyo3::PyAny, + encoding: &'p pyo3::PyAny, + options: &'p pyo3::types::PyList, +) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let pkcs7_options = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization.pkcs7" + ))? + .getattr(pyo3::intern!(py, "PKCS7Options"))?; + + let raw_data: CffiBuf<'p> = builder.getattr(pyo3::intern!(py, "_data"))?.extract()?; + let text_mode = options.contains(pkcs7_options.getattr(pyo3::intern!(py, "Text"))?)?; + let (data_with_header, data_without_header) = + if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "Binary"))?)? { + ( + 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 + AES_256_CBC_OID, + AES_192_CBC_OID, + AES_128_CBC_OID, + ]))?; + + let py_signers: Vec<( + pyo3::PyRef<'p, x509::certificate::Certificate>, + &pyo3::PyAny, + &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_value_public()) + .collect::>(); + for (cert, py_private_key, py_hash_alg) in &py_signers { + let (authenticated_attrs, signature) = if options + .contains(pkcs7_options.getattr(pyo3::intern!(py, "NoAttributes"))?)? + { + ( + None, + x509::sign::sign_data( + py, + py_private_key, + py_hash_alg, + py.None().into_ref(py), + &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 = + asn1::write_single(&x509::ocsp::hash_data(py, py_hash_alg, &data_with_header)?)?; + // Gross hack: copy to PyBytes to extend the lifetime to 'p + let digest_bytes = pyo3::types::PyBytes::new(py, &digest); + authenticated_attrs.push(Attribute { + type_id: PKCS7_MESSAGE_DIGEST_OID, + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + asn1::parse_single(digest_bytes.as_bytes()).unwrap(), + ])), + }); + + if !options.contains(pkcs7_options.getattr(pyo3::intern!(py, "NoCapabilities"))?)? { + 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, + py_hash_alg, + py.None().into_ref(py), + &signed_data, + )?, + ) + }; + + let digest_alg = x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[py_hash_alg + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?] + .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_value_public()); + + signer_infos.push(pkcs7::SignerInfo { + version: 1, + issuer_and_serial_number: pkcs7::IssuerAndSerialNumber { + issuer: cert.raw.borrow_value_public().tbs_cert.issuer.clone(), + serial_number: cert.raw.borrow_value_public().tbs_cert.serial, + }, + digest_algorithm: digest_alg, + authenticated_attributes: authenticated_attrs, + digest_encryption_algorithm: x509::sign::compute_signature_algorithm( + py, + py_private_key, + py_hash_alg, + py.None().into_ref(py), + )?, + encrypted_digest: signature, + unauthenticated_attributes: None, + }); + } + + let data_tlv_bytes; + let content = + if options.contains(pkcs7_options.getattr(pyo3::intern!(py, "DetachedSignature"))?)? { + 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(pkcs7_options.getattr(pyo3::intern!(py, "NoCerts"))?)? { + 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)?; + + let encoding_class = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "Encoding"))?; + + if encoding.is(encoding_class.getattr(pyo3::intern!(py, "SMIME"))?) { + let mic_algs = digest_algs + .iter() + .map(|d| OIDS_TO_MIC_NAME[&d.oid()]) + .collect::>() + .join(","); + let smime_encode = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization.pkcs7" + ))? + .getattr(pyo3::intern!(py, "_smime_encode"))?; + Ok(smime_encode + .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 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)) + } +} + +pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let submod = pyo3::prelude::PyModule::new(py, "pkcs7")?; + + submod.add_function(pyo3::wrap_pyfunction!(serialize_certificates, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(sign_and_serialize, submod)?)?; + + Ok(submod) +} + +#[cfg(test)] +mod tests { + use super::smime_canonicalize; + use std::borrow::Cow; + use std::ops::Deref; + + #[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 + ); + } + } +} diff --git a/src/rust/src/pool.rs b/src/rust/src/pool.rs index 9dacd7f..0f45bed 100644 --- a/src/rust/src/pool.rs +++ b/src/rust/src/pool.rs @@ -7,15 +7,14 @@ use std::cell::Cell; // An object pool that can contain a single object and will dynamically // allocate new objects to fulfill requests if the pool'd object is already in // use. -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] pub(crate) struct FixedPool { create_fn: pyo3::PyObject, - destroy_fn: pyo3::PyObject, value: Cell>, } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust")] struct PoolAcquisition { pool: pyo3::Py, @@ -26,16 +25,11 @@ struct PoolAcquisition { #[pyo3::pymethods] impl FixedPool { #[new] - fn new( - py: pyo3::Python<'_>, - create: pyo3::PyObject, - destroy: pyo3::PyObject, - ) -> pyo3::PyResult { + fn new(py: pyo3::Python<'_>, create: pyo3::PyObject) -> pyo3::PyResult { let value = create.call0(py)?; Ok(FixedPool { create_fn: create, - destroy_fn: destroy, value: Cell::new(Some(value)), }) @@ -58,17 +52,10 @@ impl FixedPool { }) } } -} -impl Drop for FixedPool { - fn drop(&mut self) { - if let Some(value) = self.value.replace(None) { - let gil = pyo3::Python::acquire_gil(); - let py = gil.python(); - self.destroy_fn - .call1(py, (value,)) - .expect("FixedPool destroy function failed in destructor"); - } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.create_fn)?; + Ok(()) } } @@ -86,9 +73,7 @@ impl PoolAcquisition { _exc_tb: &pyo3::PyAny, ) -> pyo3::PyResult<()> { let pool = self.pool.as_ref(py).borrow(); - if self.fresh { - pool.destroy_fn.call1(py, (self.value.clone_ref(py),))?; - } else { + if !self.fresh { pool.value.replace(Some(self.value.clone_ref(py))); } Ok(()) diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs index 59841d7..9204d73 100644 --- a/src/rust/src/x509/certificate.rs +++ b/src/rust/src/x509/certificate.rs @@ -3,87 +3,58 @@ // for complete details. use crate::asn1::{ - big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes, PyAsn1Error, PyAsn1Result, + big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, }; -use crate::x509; -use crate::x509::{crl, extensions, oid, sct, Asn1ReadableOrWritable}; -use chrono::Datelike; -use pyo3::ToPyObject; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::{extensions, sct, sign}; +use crate::{exceptions, x509}; +use cryptography_x509::common::{AlgorithmParameters, Asn1ReadableOrWritable}; +use cryptography_x509::extensions::Extension; +use cryptography_x509::extensions::{ + AuthorityKeyIdentifier, BasicConstraints, DisplayText, DistributionPoint, + DistributionPointName, MSCertificateTemplate, NameConstraints, PolicyConstraints, + PolicyInformation, PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions, + SequenceOfSubtrees, UserNotice, +}; +use cryptography_x509::{common, name, oid}; +use pyo3::{IntoPy, ToPyObject}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] -pub(crate) struct RawCertificate<'a> { - pub(crate) tbs_cert: TbsCertificate<'a>, - signature_alg: x509::AlgorithmIdentifier<'a>, - signature: asn1::BitString<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] -pub(crate) struct TbsCertificate<'a> { - #[explicit(0)] - #[default(0)] - version: u8, - pub(crate) serial: asn1::BigInt<'a>, - signature_alg: x509::AlgorithmIdentifier<'a>, - - pub(crate) issuer: x509::Name<'a>, - validity: Validity, - pub(crate) subject: x509::Name<'a>, - - pub(crate) spki: SubjectPublicKeyInfo<'a>, - #[implicit(1)] - issuer_unique_id: Option>, - #[implicit(2)] - subject_unique_id: Option>, - #[explicit(3)] - extensions: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] -pub(crate) struct Validity { - not_before: x509::Time, - not_after: x509::Time, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)] -pub(crate) struct SubjectPublicKeyInfo<'a> { - _algorithm: x509::AlgorithmIdentifier<'a>, - pub(crate) subject_public_key: asn1::BitString<'a>, -} #[ouroboros::self_referencing] -pub(crate) struct OwnedRawCertificate { - data: Arc<[u8]>, +pub(crate) struct OwnedCertificate { + data: pyo3::Py, #[borrows(data)] #[covariant] - value: RawCertificate<'this>, + value: cryptography_x509::certificate::Certificate<'this>, } -impl OwnedRawCertificate { +impl OwnedCertificate { // Re-expose ::new with `pub(crate)` visibility. pub(crate) fn new_public( - data: Arc<[u8]>, - value_ref_builder: impl for<'this> FnOnce(&'this Arc<[u8]>) -> RawCertificate<'this>, - ) -> OwnedRawCertificate { - OwnedRawCertificate::new(data, value_ref_builder) + data: pyo3::Py, + value_ref_builder: impl for<'this> FnOnce( + &'this pyo3::Py, + ) + -> cryptography_x509::certificate::Certificate<'this>, + ) -> OwnedCertificate { + OwnedCertificate::new(data, value_ref_builder) } - pub(crate) fn borrow_value_public(&self) -> &RawCertificate<'_> { + pub(crate) fn borrow_value_public(&self) -> &cryptography_x509::certificate::Certificate<'_> { self.borrow_value() } } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Certificate { - pub(crate) raw: OwnedRawCertificate, + pub(crate) raw: OwnedCertificate, pub(crate) cached_extensions: Option, } -#[pyo3::prelude::pyproto] -impl pyo3::PyObjectProtocol for Certificate { +#[pyo3::prelude::pymethods] +impl Certificate { fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.raw.borrow_value().hash(&mut hasher); @@ -92,7 +63,7 @@ impl pyo3::PyObjectProtocol for Certificate { fn __richcmp__( &self, - other: pyo3::PyRef, + other: pyo3::PyRef<'_, Certificate>, op: pyo3::basic::CompareOp, ) -> pyo3::PyResult { match op { @@ -104,31 +75,28 @@ impl pyo3::PyObjectProtocol for Certificate { } } - fn __repr__(&self) -> pyo3::PyResult { - let gil = pyo3::Python::acquire_gil(); - let py = gil.python(); - + fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { let subject = self.subject(py)?; let subject_repr = subject.repr()?.extract::<&str>()?; Ok(format!("", subject_repr)) } -} -#[pyo3::prelude::pymethods] -impl Certificate { fn __deepcopy__(slf: pyo3::PyRef<'_, Self>, _memo: pyo3::PyObject) -> pyo3::PyRef<'_, Self> { slf } - fn public_key<'p>(&self, py: pyo3::Python<'p>) -> PyAsn1Result<&'p pyo3::PyAny> { + fn public_key<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { // This makes an unnecessary copy. It'd be nice to get rid of it. let serialized = pyo3::types::PyBytes::new( py, &asn1::write_single(&self.raw.borrow_value().tbs_cert.spki)?, ); Ok(py - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "load_der_public_key"))? + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "load_der_public_key"))? .call1((serialized,))?) } @@ -136,59 +104,40 @@ impl Certificate { &self, py: pyo3::Python<'p>, algorithm: pyo3::PyObject, - ) -> PyAsn1Result<&'p pyo3::PyAny> { + ) -> CryptographyResult<&'p pyo3::PyAny> { let hasher = py - .import("cryptography.hazmat.primitives.hashes")? - .getattr(crate::intern!(py, "Hash"))? + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "Hash"))? .call1((algorithm,))?; // This makes an unnecessary copy. It'd be nice to get rid of it. let serialized = pyo3::types::PyBytes::new(py, &asn1::write_single(&self.raw.borrow_value())?); - hasher.call_method1("update", (serialized,))?; - Ok(hasher.call_method0("finalize")?) + hasher.call_method1(pyo3::intern!(py, "update"), (serialized,))?; + Ok(hasher.call_method0(pyo3::intern!(py, "finalize"))?) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { - let encoding_class = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "Encoding"))?; - + encoding: &'p pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let result = asn1::write_single(self.raw.borrow_value())?; - if encoding == encoding_class.getattr(crate::intern!(py, "DER"))? { - Ok(pyo3::types::PyBytes::new(py, &result)) - } else if encoding == encoding_class.getattr(crate::intern!(py, "PEM"))? { - let pem = pem::encode_config( - &pem::Pem { - tag: "CERTIFICATE".to_string(), - contents: result, - }, - pem::EncodeConfig { - line_ending: pem::LineEnding::LF, - }, - ) - .into_bytes(); - Ok(pyo3::types::PyBytes::new(py, &pem)) - } else { - Err(pyo3::exceptions::PyTypeError::new_err( - "encoding must be Encoding.DER or Encoding.PEM", - ) - .into()) - } + + encode_der_data(py, "CERTIFICATE".to_string(), result, encoding) } #[getter] - fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { + fn serial_number<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { let bytes = self.raw.borrow_value().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<&'p pyo3::PyAny, PyAsn1Error> { + fn version<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, CryptographyError> { let version = &self.raw.borrow_value().tbs_cert.version; cert_version(py, *version) } @@ -213,7 +162,7 @@ impl Certificate { fn tbs_certificate_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let result = asn1::write_single(&self.raw.borrow_value().tbs_cert)?; Ok(pyo3::types::PyBytes::new(py, &result)) } @@ -222,32 +171,49 @@ impl Certificate { fn tbs_precertificate_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let val = self.raw.borrow_value(); let mut tbs_precert = val.tbs_cert.clone(); // Remove the SCT list extension - match tbs_precert.extensions { - Some(extensions) => { - let readable_extensions = extensions.unwrap_read().clone(); + match val.tbs_cert.extensions() { + Ok(extensions) => { + let readable_extensions = match extensions.as_raw() { + Some(raw_exts) => raw_exts.unwrap_read().clone(), + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Could not find any extensions in TBS certificate", + ), + )) + } + }; let ext_count = readable_extensions.len(); - let filtered_extensions: Vec> = readable_extensions + let filtered_extensions: Vec> = readable_extensions .filter(|x| x.extn_id != oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID) .collect(); if filtered_extensions.len() == ext_count { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Could not find pre-certificate SCT list extension", - ))); + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Could not find pre-certificate SCT list extension", + ), + )); } - let filtered_extensions: x509::Extensions<'_> = Asn1ReadableOrWritable::new_write( + let filtered_extensions: RawExtensions<'_> = Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(filtered_extensions), ); - tbs_precert.extensions = Some(filtered_extensions); + + tbs_precert.raw_extensions = Some(filtered_extensions); let result = asn1::write_single(&tbs_precert)?; Ok(pyo3::types::PyBytes::new(py, &result)) } - None => Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Could not find any extensions in TBS certificate", - ))), + Err(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()) + } } } @@ -258,69 +224,62 @@ impl Certificate { #[getter] fn not_valid_before<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let chrono = &self + let dt = &self .raw .borrow_value() .tbs_cert .validity .not_before - .as_chrono(); - x509::chrono_to_py(py, chrono) + .as_datetime(); + x509::datetime_to_py(py, dt) } #[getter] fn not_valid_after<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let chrono = &self + let dt = &self .raw .borrow_value() .tbs_cert .validity .not_after - .as_chrono(); - x509::chrono_to_py(py, chrono) + .as_datetime(); + x509::datetime_to_py(py, dt) } #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let sig_oids_to_hash = py - .import("cryptography.hazmat._oid")? - .getattr(crate::intern!(py, "_SIG_OIDS_TO_HASH"))?; - let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?); - match hash_alg { - Ok(data) => Ok(data), - Err(_) => Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - py.import("cryptography.exceptions")?.call_method1( - "UnsupportedAlgorithm", - (format!( - "Signature algorithm OID: {} not recognized", - self.raw.borrow_value().signature_alg.oid - ),), - )?, - ))), - } + ) -> Result<&'p pyo3::PyAny, CryptographyError> { + sign::identify_signature_hash_algorithm(py, &self.raw.borrow_value().signature_alg) } #[getter] fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - oid_to_py_oid(py, &self.raw.borrow_value().signature_alg.oid) + oid_to_py_oid(py, self.raw.borrow_value().signature_alg.oid()) + } + + #[getter] + fn signature_algorithm_parameters<'p>( + &'p self, + py: pyo3::Python<'p>, + ) -> CryptographyResult<&'p pyo3::PyAny> { + sign::identify_signature_algorithm_parameters(py, &self.raw.borrow_value().signature_alg) } #[getter] fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let x509_module = py.import("cryptography.x509")?; + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, &mut self.cached_extensions, - &self.raw.borrow_value().tbs_cert.extensions, + &self.raw.borrow_value().tbs_cert.raw_extensions, |oid, ext_data| match *oid { oid::PRECERT_POISON_OID => { asn1::parse_single::<()>(ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "PrecertPoison"))? + .getattr(pyo3::intern!(py, "PrecertPoison"))? .call0()?, )) } @@ -329,7 +288,7 @@ impl Certificate { let scts = sct::parse_scts(py, contents, sct::LogEntryType::PreCertificate)?; Ok(Some( x509_module - .getattr(crate::intern!( + .getattr(pyo3::intern!( py, "PrecertificateSignedCertificateTimestamps" ))? @@ -340,48 +299,54 @@ impl Certificate { }, ) } - // This getter exists for compatibility with pyOpenSSL and will be removed. - // DO NOT RELY ON IT. WE WILL BREAK YOU WHEN WE FEEL LIKE IT. - #[getter] - fn _x509<'p>( - slf: pyo3::PyRef<'_, Self>, - py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let cryptography_warning = py - .import("cryptography.utils")? - .getattr(crate::intern!(py, "DeprecatedIn35"))?; - pyo3::PyErr::warn( + + fn verify_directly_issued_by( + &self, + py: pyo3::Python<'_>, + issuer: pyo3::PyRef<'_, Certificate>, + ) -> CryptographyResult<()> { + if self.raw.borrow_value().tbs_cert.signature_alg != self.raw.borrow_value().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_value().tbs_cert.issuer != issuer.raw.borrow_value().tbs_cert.subject { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Issuer certificate subject does not match certificate issuer.", + ), + )); + }; + sign::verify_signature_with_signature_algorithm( py, - cryptography_warning, - "This version of cryptography contains a temporary pyOpenSSL fallback path. Upgrade pyOpenSSL now.", - 1 - )?; - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr(crate::intern!(py, "backend"))?; - Ok(backend.call_method1("_cert2ossl", (slf,))?) + issuer.public_key(py)?, + &self.raw.borrow_value().signature_alg, + self.raw.borrow_value().signature.as_bytes(), + &asn1::write_single(&self.raw.borrow_value().tbs_cert)?, + ) } } -fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, PyAsn1Error> { - let x509_module = py.import("cryptography.x509")?; +fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, CryptographyError> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; match version { 0 => Ok(x509_module - .getattr(crate::intern!(py, "Version"))? - .get_item("v1")?), + .getattr(pyo3::intern!(py, "Version"))? + .get_item(pyo3::intern!(py, "v1"))?), 2 => Ok(x509_module - .getattr(crate::intern!(py, "Version"))? - .get_item("v3")?), - _ => Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - x509_module - .getattr(crate::intern!(py, "InvalidVersion"))? - .call1((format!("{} is not a valid X509 version", version), version))?, - ))), + .getattr(pyo3::intern!(py, "Version"))? + .get_item(pyo3::intern!(py, "v3"))?), + _ => Err(CryptographyError::from( + exceptions::InvalidVersion::new_err(( + format!("{} is not a valid X509 version", version), + version, + )), + )), } } #[pyo3::prelude::pyfunction] -fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { +fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> CryptographyResult { // 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( @@ -389,17 +354,47 @@ fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result< |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, &parsed.contents) + load_der_x509_certificate( + py, + pyo3::types::PyBytes::new(py, &parsed.contents).into_py(py), + ) +} + +#[pyo3::prelude::pyfunction] +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(py, &p.contents).into_py(py)) + }) + .collect::, _>>()?; + + if certs.is_empty() { + return Err(CryptographyError::from(pem::PemError::MalformedFraming)); + } + + Ok(certs) } #[pyo3::prelude::pyfunction] -fn load_der_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { - let raw = OwnedRawCertificate::try_new(Arc::from(data), |data| asn1::parse_single(data))?; +fn load_der_x509_certificate( + py: pyo3::Python<'_>, + data: pyo3::Py, +) -> CryptographyResult { + 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_value().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_value().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 JDK11 and we want to drop support for it eventually. + warn_if_invalid_ecdsa_params(py, raw.borrow_value().signature_alg.params.clone())?; + warn_if_invalid_ecdsa_params(py, raw.borrow_value().tbs_cert.signature_alg.params.clone())?; Ok(Certificate { raw, @@ -410,8 +405,8 @@ fn load_der_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result< fn warn_if_negative_serial(py: pyo3::Python<'_>, bytes: &'_ [u8]) -> pyo3::PyResult<()> { if bytes[0] & 0x80 != 0 { let cryptography_warning = py - .import("cryptography.utils")? - .getattr(crate::intern!(py, "DeprecatedIn36"))?; + .import(pyo3::intern!(py, "cryptography.utils"))? + .getattr(pyo3::intern!(py, "DeprecatedIn36"))?; pyo3::PyErr::warn( py, cryptography_warning, @@ -422,55 +417,28 @@ fn warn_if_negative_serial(py: pyo3::Python<'_>, bytes: &'_ [u8]) -> pyo3::PyRes Ok(()) } -// Needed due to clippy type complexity warning. -type SequenceOfPolicyQualifiers<'a> = x509::Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, - asn1::SequenceOfWriter<'a, PolicyQualifierInfo<'a>, Vec>>, ->; - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct PolicyInformation<'a> { - pub policy_identifier: asn1::ObjectIdentifier, - pub policy_qualifiers: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct PolicyQualifierInfo<'a> { - pub policy_qualifier_id: asn1::ObjectIdentifier, - pub qualifier: Qualifier<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) enum Qualifier<'a> { - CpsUri(asn1::IA5String<'a>), - UserNotice(UserNotice<'a>), -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct UserNotice<'a> { - pub notice_ref: Option>, - pub explicit_text: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct NoticeReference<'a> { - pub organization: DisplayText<'a>, - pub notice_numbers: x509::Asn1ReadableOrWritable< - 'a, - 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(crate) enum DisplayText<'a> { - IA5String(asn1::IA5String<'a>), - Utf8String(asn1::Utf8String<'a>), - VisibleString(asn1::VisibleString<'a>), - BmpString(asn1::BMPString<'a>), +fn warn_if_invalid_ecdsa_params( + py: pyo3::Python<'_>, + params: AlgorithmParameters<'_>, +) -> pyo3::PyResult<()> { + match params { + AlgorithmParameters::EcDsaWithSha224(Some(..)) + | AlgorithmParameters::EcDsaWithSha256(Some(..)) + | AlgorithmParameters::EcDsaWithSha384(Some(..)) + | AlgorithmParameters::EcDsaWithSha512(Some(..)) => { + let cryptography_warning = py + .import(pyo3::intern!(py, "cryptography.utils"))? + .getattr(pyo3::intern!(py, "DeprecatedIn41"))?; + pyo3::PyErr::warn( + py, + cryptography_warning, + "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 JDK16+ or the latest JDK11 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 for more details.", + 2, + )?; + } + _ => {} + } + Ok(()) } fn parse_display_text( @@ -481,13 +449,27 @@ fn parse_display_text( DisplayText::IA5String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)), DisplayText::Utf8String(o) => Ok(pyo3::types::PyString::new(py, o.as_str()).to_object(py)), DisplayText::VisibleString(o) => { + if asn1::VisibleString::new(o.as_str()).is_none() { + let cryptography_warning = py + .import(pyo3::intern!(py, "cryptography.utils"))? + .getattr(pyo3::intern!(py, "DeprecatedIn41"))?; + pyo3::PyErr::warn( + py, + cryptography_warning, + "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(py, o.as_str()).to_object(py)) } DisplayText::BmpString(o) => { let py_bytes = pyo3::types::PyBytes::new(py, o.as_utf16_be_bytes()); // TODO: do the string conversion in rust perhaps Ok(py_bytes - .call_method1("decode", ("utf_16_be",))? + .call_method1( + pyo3::intern!(py, "decode"), + (pyo3::intern!(py, "utf_16_be"),), + )? .to_object(py)) } } @@ -496,8 +478,8 @@ fn parse_display_text( fn parse_user_notice( py: pyo3::Python<'_>, un: UserNotice<'_>, -) -> Result { - let x509_module = py.import("cryptography.x509")?; +) -> Result { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let et = match un.explicit_text { Some(data) => parse_display_text(py, data)?, None => py.None(), @@ -510,20 +492,20 @@ fn parse_user_notice( numbers.append(big_byte_slice_to_py_int(py, num.as_bytes())?.to_object(py))?; } x509_module - .call_method1("NoticeReference", (org, numbers))? + .call_method1(pyo3::intern!(py, "NoticeReference"), (org, numbers))? .to_object(py) } None => py.None(), }; Ok(x509_module - .call_method1("UserNotice", (nr, et))? + .call_method1(pyo3::intern!(py, "UserNotice"), (nr, et))? .to_object(py)) } fn parse_policy_qualifiers<'a>( py: pyo3::Python<'_>, policy_qualifiers: &asn1::SequenceOf<'a, PolicyQualifierInfo<'a>>, -) -> Result { +) -> Result { let py_pq = pyo3::types::PyList::empty(py); for pqi in policy_qualifiers.clone() { let qualifier = match pqi.qualifier { @@ -531,16 +513,20 @@ fn parse_policy_qualifiers<'a>( if pqi.policy_qualifier_id == oid::CP_CPS_URI_OID { pyo3::types::PyString::new(py, data.as_str()).to_object(py) } else { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "CpsUri ASN.1 structure found but OID did not match", - ))); + 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(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "UserNotice ASN.1 structure found but OID did not match", - ))); + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "UserNotice ASN.1 structure found but OID did not match", + ), + )); } parse_user_notice(py, un)? } @@ -550,9 +536,9 @@ fn parse_policy_qualifiers<'a>( Ok(py_pq.to_object(py)) } -fn parse_cp(py: pyo3::Python<'_>, ext_data: &[u8]) -> Result { +fn parse_cp(py: pyo3::Python<'_>, ext_data: &[u8]) -> Result { let cp = asn1::parse_single::>>(ext_data)?; - let x509_module = py.import("cryptography.x509")?; + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let certificate_policies = pyo3::types::PyList::empty(py); for policyinfo in cp { let pi_oid = oid_to_py_oid(py, &policyinfo.policy_identifier)?.to_object(py); @@ -563,45 +549,17 @@ fn parse_cp(py: pyo3::Python<'_>, ext_data: &[u8]) -> Result py.None(), }; let pi = x509_module - .call_method1("PolicyInformation", (pi_oid, py_pqis))? + .call_method1(pyo3::intern!(py, "PolicyInformation"), (pi_oid, py_pqis))? .to_object(py); certificate_policies.append(pi)?; } Ok(certificate_policies.to_object(py)) } -// Needed due to clippy type complexity warning. -pub(crate) type SequenceOfSubtrees<'a> = x509::Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, GeneralSubtree<'a>>, - asn1::SequenceOfWriter<'a, GeneralSubtree<'a>, Vec>>, ->; - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct NameConstraints<'a> { - #[implicit(0)] - pub permitted_subtrees: Option>, - - #[implicit(1)] - pub excluded_subtrees: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct GeneralSubtree<'a> { - pub base: x509::GeneralName<'a>, - - #[implicit(0)] - #[default(0u64)] - pub minimum: u64, - - #[implicit(1)] - pub maximum: Option, -} - fn parse_general_subtrees( py: pyo3::Python<'_>, subtrees: SequenceOfSubtrees<'_>, -) -> Result { +) -> Result { let gns = pyo3::types::PyList::empty(py); for gs in subtrees.unwrap_read().clone() { gns.append(x509::parse_general_name(py, gs.base)?)?; @@ -609,47 +567,10 @@ fn parse_general_subtrees( Ok(gns.to_object(py)) } -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) 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(crate) enum DistributionPointName<'a> { - #[implicit(0)] - FullName(x509::common::SequenceOfGeneralName<'a>), - - #[implicit(1)] - NameRelativeToCRLIssuer( - x509::Asn1ReadableOrWritable< - 'a, - asn1::SetOf<'a, x509::AttributeTypeValue<'a>>, - asn1::SetOfWriter<'a, x509::AttributeTypeValue<'a>, Vec>>, - >, - ), -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) 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>, -} - pub(crate) fn parse_distribution_point_name( py: pyo3::Python<'_>, dp: DistributionPointName<'_>, -) -> Result<(pyo3::PyObject, pyo3::PyObject), PyAsn1Error> { +) -> Result<(pyo3::PyObject, pyo3::PyObject), CryptographyError> { Ok(match dp { DistributionPointName::FullName(data) => ( x509::parse_general_names(py, data.unwrap_read())?, @@ -664,7 +585,7 @@ pub(crate) fn parse_distribution_point_name( fn parse_distribution_point( py: pyo3::Python<'_>, dp: DistributionPoint<'_>, -) -> Result { +) -> Result { let (full_name, relative_name) = match dp.distribution_point { Some(data) => parse_distribution_point_name(py, data)?, None => (py.None(), py.None()), @@ -675,9 +596,9 @@ fn parse_distribution_point( Some(aci) => x509::parse_general_names(py, aci.unwrap_read())?, None => py.None(), }; - let x509_module = py.import("cryptography.x509")?; + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; Ok(x509_module - .getattr(crate::intern!(py, "DistributionPoint"))? + .getattr(pyo3::intern!(py, "DistributionPoint"))? .call1((full_name, relative_name, reasons, crl_issuer))? .to_object(py)) } @@ -685,7 +606,7 @@ fn parse_distribution_point( pub(crate) fn parse_distribution_points( py: pyo3::Python<'_>, data: &[u8], -) -> Result { +) -> Result { let dps = asn1::parse_single::>>(data)?; let py_dps = pyo3::types::PyList::empty(py); for dp in dps { @@ -698,10 +619,10 @@ pub(crate) fn parse_distribution_points( pub(crate) fn parse_distribution_point_reasons( py: pyo3::Python<'_>, reasons: Option<&asn1::BitString<'_>>, -) -> Result { +) -> Result { let reason_bit_mapping = py - .import("cryptography.x509.extensions")? - .getattr(crate::intern!(py, "_REASON_BIT_MAPPING"))?; + .import(pyo3::intern!(py, "cryptography.x509.extensions"))? + .getattr(pyo3::intern!(py, "_REASON_BIT_MAPPING"))?; Ok(match reasons { Some(bs) => { let mut vec = Vec::new(); @@ -721,8 +642,8 @@ pub(crate) fn encode_distribution_point_reasons( py_reasons: &pyo3::PyAny, ) -> pyo3::PyResult { let reason_flag_mapping = py - .import("cryptography.x509.extensions")? - .getattr(crate::intern!(py, "_CRLREASONFLAGS"))?; + .import(pyo3::intern!(py, "cryptography.x509.extensions"))? + .getattr(pyo3::intern!(py, "_CRLREASONFLAGS"))?; let mut bits = vec![0, 0]; for py_reason in py_reasons.iter()? { @@ -738,26 +659,11 @@ pub(crate) fn encode_distribution_point_reasons( Ok(asn1::OwnedBitString::new(bits, unused_bits).unwrap()) } -#[derive(asn1::Asn1Read, asn1::Asn1Write, pyo3::prelude::FromPyObject)] -pub(crate) struct BasicConstraints { - #[default(false)] - pub ca: bool, - pub path_length: Option, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct PolicyConstraints { - #[implicit(0)] - pub require_explicit_policy: Option, - #[implicit(1)] - pub inhibit_policy_mapping: Option, -} - pub(crate) fn parse_authority_key_identifier<'p>( py: pyo3::Python<'p>, ext_data: &[u8], -) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let x509_module = py.import("cryptography.x509")?; +) -> Result<&'p pyo3::PyAny, CryptographyError> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let aki = asn1::parse_single::>(ext_data)?; let serial = match aki.authority_cert_serial_number { Some(biguint) => big_byte_slice_to_py_int(py, biguint.as_bytes())?.to_object(py), @@ -768,22 +674,22 @@ pub(crate) fn parse_authority_key_identifier<'p>( None => py.None(), }; Ok(x509_module - .getattr(crate::intern!(py, "AuthorityKeyIdentifier"))? + .getattr(pyo3::intern!(py, "AuthorityKeyIdentifier"))? .call1((aki.key_identifier, issuer, serial))?) } pub(crate) fn parse_access_descriptions( py: pyo3::Python<'_>, ext_data: &[u8], -) -> Result { - let x509_module = py.import("cryptography.x509")?; +) -> Result { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let ads = pyo3::types::PyList::empty(py); - let parsed = asn1::parse_single::>(ext_data)?; + let parsed = asn1::parse_single::>(ext_data)?; 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 = x509_module - .getattr(crate::intern!(py, "AccessDescription"))? + .getattr(pyo3::intern!(py, "AccessDescription"))? .call1((py_oid, gn))? .to_object(py); ads.append(ad)?; @@ -795,33 +701,33 @@ pub fn parse_cert_ext<'p>( py: pyo3::Python<'p>, oid: asn1::ObjectIdentifier, ext_data: &[u8], -) -> PyAsn1Result> { - let x509_module = py.import("cryptography.x509")?; +) -> CryptographyResult> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; match oid { oid::SUBJECT_ALTERNATIVE_NAME_OID => { let gn_seq = - asn1::parse_single::>>(ext_data)?; + asn1::parse_single::>>(ext_data)?; let sans = x509::parse_general_names(py, &gn_seq)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "SubjectAlternativeName"))? + .getattr(pyo3::intern!(py, "SubjectAlternativeName"))? .call1((sans,))?, )) } oid::ISSUER_ALTERNATIVE_NAME_OID => { let gn_seq = - asn1::parse_single::>>(ext_data)?; + asn1::parse_single::>>(ext_data)?; let ians = x509::parse_general_names(py, &gn_seq)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "IssuerAlternativeName"))? + .getattr(pyo3::intern!(py, "IssuerAlternativeName"))? .call1((ians,))?, )) } oid::TLS_FEATURE_OID => { let tls_feature_type_to_enum = py - .import("cryptography.x509.extensions")? - .getattr(crate::intern!(py, "_TLS_FEATURE_TYPE_TO_ENUM"))?; + .import(pyo3::intern!(py, "cryptography.x509.extensions"))? + .getattr(pyo3::intern!(py, "_TLS_FEATURE_TYPE_TO_ENUM"))?; let features = pyo3::types::PyList::empty(py); for feature in asn1::parse_single::>(ext_data)? { @@ -830,7 +736,7 @@ pub fn parse_cert_ext<'p>( } Ok(Some( x509_module - .getattr(crate::intern!(py, "TLSFeature"))? + .getattr(pyo3::intern!(py, "TLSFeature"))? .call1((features,))?, )) } @@ -838,7 +744,7 @@ pub fn parse_cert_ext<'p>( let identifier = asn1::parse_single::<&[u8]>(ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "SubjectKeyIdentifier"))? + .getattr(pyo3::intern!(py, "SubjectKeyIdentifier"))? .call1((identifier,))?, )) } @@ -851,7 +757,7 @@ pub fn parse_cert_ext<'p>( } Ok(Some( x509_module - .getattr(crate::intern!(py, "ExtendedKeyUsage"))? + .getattr(pyo3::intern!(py, "ExtendedKeyUsage"))? .call1((ekus,))?, )) } @@ -867,26 +773,24 @@ pub fn parse_cert_ext<'p>( let encipher_only = kus.has_bit_set(7); let decipher_only = kus.has_bit_set(8); Ok(Some( - x509_module - .getattr(crate::intern!(py, "KeyUsage"))? - .call1(( - digital_signature, - content_comitment, - key_encipherment, - data_encipherment, - key_agreement, - key_cert_sign, - crl_sign, - encipher_only, - decipher_only, - ))?, + x509_module.getattr(pyo3::intern!(py, "KeyUsage"))?.call1(( + digital_signature, + content_comitment, + key_encipherment, + data_encipherment, + key_agreement, + key_cert_sign, + crl_sign, + encipher_only, + decipher_only, + ))?, )) } oid::AUTHORITY_INFORMATION_ACCESS_OID => { let ads = parse_access_descriptions(py, ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "AuthorityInformationAccess"))? + .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))? .call1((ads,))?, )) } @@ -894,21 +798,22 @@ pub fn parse_cert_ext<'p>( let ads = parse_access_descriptions(py, ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "SubjectInformationAccess"))? + .getattr(pyo3::intern!(py, "SubjectInformationAccess"))? .call1((ads,))?, )) } oid::CERTIFICATE_POLICIES_OID => { let cp = parse_cp(py, ext_data)?; - Ok(Some( - x509_module.call_method1("CertificatePolicies", (cp,))?, - )) + Ok(Some(x509_module.call_method1( + pyo3::intern!(py, "CertificatePolicies"), + (cp,), + )?)) } oid::POLICY_CONSTRAINTS_OID => { let pc = asn1::parse_single::(ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "PolicyConstraints"))? + .getattr(pyo3::intern!(py, "PolicyConstraints"))? .call1((pc.require_explicit_policy, pc.inhibit_policy_mapping))?, )) } @@ -916,7 +821,7 @@ pub fn parse_cert_ext<'p>( asn1::parse_single::<()>(ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "OCSPNoCheck"))? + .getattr(pyo3::intern!(py, "OCSPNoCheck"))? .call0()?, )) } @@ -925,7 +830,7 @@ pub fn parse_cert_ext<'p>( let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; Ok(Some( x509_module - .getattr(crate::intern!(py, "InhibitAnyPolicy"))? + .getattr(pyo3::intern!(py, "InhibitAnyPolicy"))? .call1((pynum,))?, )) } @@ -933,7 +838,7 @@ pub fn parse_cert_ext<'p>( let bc = asn1::parse_single::(ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "BasicConstraints"))? + .getattr(pyo3::intern!(py, "BasicConstraints"))? .call1((bc.ca, bc.path_length))?, )) } @@ -944,7 +849,7 @@ pub fn parse_cert_ext<'p>( let dp = parse_distribution_points(py, ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "CRLDistributionPoints"))? + .getattr(pyo3::intern!(py, "CRLDistributionPoints"))? .call1((dp,))?, )) } @@ -952,7 +857,7 @@ pub fn parse_cert_ext<'p>( let dp = parse_distribution_points(py, ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "FreshestCRL"))? + .getattr(pyo3::intern!(py, "FreshestCRL"))? .call1((dp,))?, )) } @@ -968,20 +873,38 @@ pub fn parse_cert_ext<'p>( }; Ok(Some( x509_module - .getattr(crate::intern!(py, "NameConstraints"))? + .getattr(pyo3::intern!(py, "NameConstraints"))? .call1((permitted_subtrees, excluded_subtrees))?, )) } + oid::MS_CERTIFICATE_TEMPLATE => { + let ms_cert_tpl = asn1::parse_single::(ext_data)?; + let py_oid = oid_to_py_oid(py, &ms_cert_tpl.template_id)?; + Ok(Some( + x509_module + .getattr(pyo3::intern!(py, "MSCertificateTemplate"))? + .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::PyAny) -> PyAsn1Result { - let dt = x509::py_to_chrono(py, val)?; +pub(crate) fn time_from_py( + py: pyo3::Python<'_>, + val: &pyo3::PyAny, +) -> CryptographyResult { + let dt = x509::py_to_datetime(py, val)?; + time_from_datetime(dt) +} + +pub(crate) fn time_from_datetime(dt: asn1::DateTime) -> CryptographyResult { if dt.year() >= 2050 { - Ok(x509::Time::GeneralizedTime(asn1::GeneralizedTime::new(dt)?)) + Ok(common::Time::GeneralizedTime(asn1::GeneralizedTime::new( + dt, + )?)) } else { - Ok(x509::Time::UtcTime(asn1::UtcTime::new(dt).unwrap())) + Ok(common::Time::UtcTime(asn1::UtcTime::new(dt).unwrap())) } } @@ -991,39 +914,47 @@ fn create_x509_certificate( builder: &pyo3::PyAny, private_key: &pyo3::PyAny, hash_algorithm: &pyo3::PyAny, -) -> PyAsn1Result { - let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?; - let serialization_mod = py.import("cryptography.hazmat.primitives.serialization")?; + rsa_padding: &pyo3::PyAny, +) -> CryptographyResult { + let sigalg = + x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?; + let serialization_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))?; let der_encoding = serialization_mod - .getattr(crate::intern!(py, "Encoding"))? - .getattr(crate::intern!(py, "DER"))?; + .getattr(pyo3::intern!(py, "Encoding"))? + .getattr(pyo3::intern!(py, "DER"))?; let spki_format = serialization_mod - .getattr(crate::intern!(py, "PublicFormat"))? - .getattr(crate::intern!(py, "SubjectPublicKeyInfo"))?; + .getattr(pyo3::intern!(py, "PublicFormat"))? + .getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?; let spki_bytes = builder - .getattr(crate::intern!(py, "_public_key"))? - .call_method1("public_bytes", (der_encoding, spki_format))? + .getattr(pyo3::intern!(py, "_public_key"))? + .call_method1( + pyo3::intern!(py, "public_bytes"), + (der_encoding, spki_format), + )? .extract::<&[u8]>()?; let py_serial = builder - .getattr(crate::intern!(py, "_serial_number"))? + .getattr(pyo3::intern!(py, "_serial_number"))? .extract()?; - let py_issuer_name = builder.getattr(crate::intern!(py, "_issuer_name"))?; - let py_subject_name = builder.getattr(crate::intern!(py, "_subject_name"))?; - let py_not_before = builder.getattr(crate::intern!(py, "_not_valid_before"))?; - let py_not_after = builder.getattr(crate::intern!(py, "_not_valid_after"))?; + 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 tbs_cert = TbsCertificate { + let tbs_cert = cryptography_x509::certificate::TbsCertificate { version: builder - .getattr(crate::intern!(py, "_version"))? - .getattr(crate::intern!(py, "value"))? + .getattr(pyo3::intern!(py, "_version"))? + .getattr(pyo3::intern!(py, "value"))? .extract()?, serial: asn1::BigInt::new(py_uint_to_big_endian_bytes(py, py_serial)?).unwrap(), signature_alg: sigalg.clone(), issuer: x509::common::encode_name(py, py_issuer_name)?, - validity: Validity { + validity: cryptography_x509::certificate::Validity { not_before: time_from_py(py, py_not_before)?, not_after: time_from_py(py, py_not_after)?, }, @@ -1031,22 +962,22 @@ fn create_x509_certificate( spki: asn1::parse_single(spki_bytes)?, issuer_unique_id: None, subject_unique_id: None, - extensions: x509::common::encode_extensions( + raw_extensions: x509::common::encode_extensions( py, - builder.getattr(crate::intern!(py, "_extensions"))?, + 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, hash_algorithm, &tbs_bytes)?; - let data = asn1::write_single(&RawCertificate { + let signature = + x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?; + let data = asn1::write_single(&cryptography_x509::certificate::Certificate { tbs_cert, signature_alg: sigalg, signature: asn1::BitString::new(signature, 0).unwrap(), })?; - // TODO: extra copy as we round-trip through a slice - load_der_x509_certificate(py, &data) + load_der_x509_certificate(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) } pub(crate) fn set_bit(vals: &mut [u8], n: usize, set: bool) { @@ -1058,9 +989,10 @@ pub(crate) fn set_bit(vals: &mut [u8], n: usize, set: bool) { } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_wrapped(pyo3::wrap_pyfunction!(load_der_x509_certificate))?; - module.add_wrapped(pyo3::wrap_pyfunction!(load_pem_x509_certificate))?; - module.add_wrapped(pyo3::wrap_pyfunction!(create_x509_certificate))?; + module.add_function(pyo3::wrap_pyfunction!(load_der_x509_certificate, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_certificate, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_certificates, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_x509_certificate, module)?)?; module.add_class::()?; diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index 5cc8338..8ceb518 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -2,14 +2,14 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{oid_to_py_oid, py_oid_to_oid, PyAsn1Error, PyAsn1Result}; -use crate::x509; -use chrono::{Datelike, TimeZone, Timelike}; +use crate::asn1::{oid_to_py_oid, py_oid_to_oid}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::{exceptions, x509}; +use cryptography_x509::common::{Asn1ReadableOrWritable, AttributeTypeValue, RawTlv}; +use cryptography_x509::extensions::{AccessDescription, Extension, Extensions, RawExtensions}; +use cryptography_x509::name::{GeneralName, Name, OtherName, UnvalidatedIA5String}; use pyo3::types::IntoPyDict; -use pyo3::ToPyObject; -use std::collections::HashSet; -use std::convert::TryInto; -use std::marker::PhantomData; +use pyo3::{IntoPy, ToPyObject}; /// Parse all sections in a PEM file and return the first matching section. /// If no matching sections are found, return an error. @@ -17,67 +17,14 @@ pub(crate) fn find_in_pem( data: &[u8], filter_fn: fn(&pem::Pem) -> bool, no_match_err: &'static str, -) -> Result { +) -> Result { let all_sections = pem::parse_many(data)?; if all_sections.is_empty() { - return Err(PyAsn1Error::from(pem::PemError::MalformedFraming)); - } - all_sections - .into_iter() - .find(filter_fn) - .ok_or_else(|| PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err(no_match_err))) -} - -pub(crate) type Name<'a> = Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, asn1::SetOf<'a, AttributeTypeValue<'a>>>, - asn1::SequenceOfWriter< - 'a, - asn1::SetOfWriter<'a, AttributeTypeValue<'a>, Vec>>, - Vec, Vec>>>, - >, ->; - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] -pub(crate) struct AttributeTypeValue<'a> { - pub(crate) type_id: asn1::ObjectIdentifier, - pub(crate) value: RawTlv<'a>, -} - -// Like `asn1::Tlv` but doesn't store `full_data` so it can be constucted from -// an un-encoded tag and value. -#[derive(Hash, PartialEq, Eq, Clone)] -pub(crate) struct RawTlv<'a> { - tag: asn1::Tag, - value: &'a [u8], -} - -impl<'a> RawTlv<'a> { - pub(crate) fn new(tag: asn1::Tag, value: &'a [u8]) -> Self { - RawTlv { tag, value } - } - - pub(crate) fn tag(&self) -> asn1::Tag { - self.tag - } - pub(crate) 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)) + 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>( @@ -86,7 +33,7 @@ pub(crate) fn encode_name<'p>( ) -> pyo3::PyResult> { let mut rdns = vec![]; - for py_rdn in py_name.getattr(crate::intern!(py, "rdns"))?.iter()? { + for py_rdn in py_name.getattr(pyo3::intern!(py, "rdns"))?.iter()? { let py_rdn = py_rdn?; let mut attrs = vec![]; @@ -103,33 +50,33 @@ pub(crate) fn encode_name<'p>( pub(crate) fn encode_name_entry<'p>( py: pyo3::Python<'p>, py_name_entry: &'p pyo3::PyAny, -) -> PyAsn1Result> { +) -> CryptographyResult> { let asn1_type = py - .import("cryptography.x509.name")? - .getattr(crate::intern!(py, "_ASN1Type"))?; + .import(pyo3::intern!(py, "cryptography.x509.name"))? + .getattr(pyo3::intern!(py, "_ASN1Type"))?; - let attr_type = py_name_entry.getattr(crate::intern!(py, "_type"))?; + let attr_type = py_name_entry.getattr(pyo3::intern!(py, "_type"))?; let tag = attr_type - .getattr(crate::intern!(py, "value"))? + .getattr(pyo3::intern!(py, "value"))? .extract::()?; - let value: &[u8] = if attr_type != asn1_type.getattr(crate::intern!(py, "BitString"))? { - let encoding = if attr_type == asn1_type.getattr(crate::intern!(py, "BMPString"))? { + let value: &[u8] = if !attr_type.is(asn1_type.getattr(pyo3::intern!(py, "BitString"))?) { + let encoding = if attr_type.is(asn1_type.getattr(pyo3::intern!(py, "BMPString"))?) { "utf_16_be" - } else if attr_type == asn1_type.getattr(crate::intern!(py, "UniversalString"))? { + } else if attr_type.is(asn1_type.getattr(pyo3::intern!(py, "UniversalString"))?) { "utf_32_be" } else { "utf8" }; py_name_entry - .getattr(crate::intern!(py, "value"))? - .call_method1("encode", (encoding,))? + .getattr(pyo3::intern!(py, "value"))? + .call_method1(pyo3::intern!(py, "encode"), (encoding,))? .extract()? } else { py_name_entry - .getattr(crate::intern!(py, "value"))? + .getattr(pyo3::intern!(py, "value"))? .extract()? }; - let oid = py_oid_to_oid(py_name_entry.getattr(crate::intern!(py, "oid"))?)?; + let oid = py_oid_to_oid(py_name_entry.getattr(pyo3::intern!(py, "oid"))?)?; Ok(AttributeTypeValue { type_id: oid, @@ -141,83 +88,16 @@ pub(crate) fn encode_name_entry<'p>( fn encode_name_bytes<'p>( py: pyo3::Python<'p>, py_name: &'p pyo3::PyAny, -) -> PyAsn1Result<&'p pyo3::types::PyBytes> { +) -> CryptographyResult<&'p pyo3::types::PyBytes> { let name = encode_name(py, py_name)?; let result = asn1::write_single(&name)?; Ok(pyo3::types::PyBytes::new(py, &result)) } -/// 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(crate) struct UnvalidatedIA5String<'a>(pub(crate) &'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, Hash)] -pub(crate) struct OtherName<'a> { - pub(crate) type_id: asn1::ObjectIdentifier, - #[explicit(0, required)] - pub(crate) value: asn1::Tlv<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) 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> = Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, GeneralName<'a>>, - asn1::SequenceOfWriter<'a, GeneralName<'a>, Vec>>, ->; - pub(crate) fn encode_general_names<'a>( py: pyo3::Python<'a>, py_gns: &'a pyo3::PyAny, -) -> Result>, PyAsn1Error> { +) -> Result>, CryptographyError> { let mut gns = vec![]; for el in py_gns.iter()? { let gn = encode_general_name(py, el?)?; @@ -229,24 +109,24 @@ pub(crate) fn encode_general_names<'a>( pub(crate) fn encode_general_name<'a>( py: pyo3::Python<'a>, gn: &'a pyo3::PyAny, -) -> Result, PyAsn1Error> { - let gn_module = py.import("cryptography.x509.general_name")?; +) -> Result, CryptographyError> { + let gn_module = py.import(pyo3::intern!(py, "cryptography.x509.general_name"))?; let gn_type = gn.get_type().as_ref(); - let gn_value = gn.getattr(crate::intern!(py, "value"))?; - if gn_type == gn_module.getattr(crate::intern!(py, "DNSName"))? { + let gn_value = gn.getattr(pyo3::intern!(py, "value"))?; + if gn_type.is(gn_module.getattr(pyo3::intern!(py, "DNSName"))?) { Ok(GeneralName::DNSName(UnvalidatedIA5String( gn_value.extract::<&str>()?, ))) - } else if gn_type == gn_module.getattr(crate::intern!(py, "RFC822Name"))? { + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RFC822Name"))?) { Ok(GeneralName::RFC822Name(UnvalidatedIA5String( gn_value.extract::<&str>()?, ))) - } else if gn_type == gn_module.getattr(crate::intern!(py, "DirectoryName"))? { + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "DirectoryName"))?) { let name = encode_name(py, gn_value)?; Ok(GeneralName::DirectoryName(name)) - } else if gn_type == gn_module.getattr(crate::intern!(py, "OtherName"))? { + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "OtherName"))?) { Ok(GeneralName::OtherName(OtherName { - type_id: py_oid_to_oid(gn.getattr(crate::intern!(py, "type_id"))?)?, + type_id: py_oid_to_oid(gn.getattr(pyo3::intern!(py, "type_id"))?)?, value: asn1::parse_single(gn_value.extract::<&[u8]>()?).map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!( "OtherName value must be valid DER: {:?}", @@ -254,119 +134,71 @@ pub(crate) fn encode_general_name<'a>( )) })?, })) - } else if gn_type == gn_module.getattr(crate::intern!(py, "UniformResourceIdentifier"))? { + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "UniformResourceIdentifier"))?) { Ok(GeneralName::UniformResourceIdentifier( UnvalidatedIA5String(gn_value.extract::<&str>()?), )) - } else if gn_type == gn_module.getattr(crate::intern!(py, "IPAddress"))? { + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "IPAddress"))?) { Ok(GeneralName::IPAddress( - gn.call_method0("_packed")?.extract::<&[u8]>()?, + gn.call_method0(pyo3::intern!(py, "_packed"))? + .extract::<&[u8]>()?, )) - } else if gn_type == gn_module.getattr(crate::intern!(py, "RegisteredID"))? { + } else if gn_type.is(gn_module.getattr(pyo3::intern!(py, "RegisteredID"))?) { let oid = py_oid_to_oid(gn_value)?; Ok(GeneralName::RegisteredID(oid)) } else { - Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Unsupported GeneralName type", - ))) + Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Unsupported GeneralName type"), + )) } } -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct AccessDescription<'a> { - pub(crate) access_method: asn1::ObjectIdentifier, - pub(crate) access_location: GeneralName<'a>, -} - -pub(crate) type SequenceOfAccessDescriptions<'a> = Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, AccessDescription<'a>>, - asn1::SequenceOfWriter<'a, AccessDescription<'a>, Vec>>, ->; - pub(crate) fn encode_access_descriptions<'a>( py: pyo3::Python<'a>, py_ads: &'a pyo3::PyAny, -) -> Result, PyAsn1Error> { +) -> CryptographyResult> { let mut ads = vec![]; for py_ad in py_ads.iter()? { let py_ad = py_ad?; - let access_method = py_oid_to_oid(py_ad.getattr(crate::intern!(py, "access_method"))?)?; + let access_method = py_oid_to_oid(py_ad.getattr(pyo3::intern!(py, "access_method"))?)?; let access_location = - encode_general_name(py, py_ad.getattr(crate::intern!(py, "access_location"))?)?; + encode_general_name(py, py_ad.getattr(pyo3::intern!(py, "access_location"))?)?; ads.push(AccessDescription { access_method, access_location, }); } - Ok(Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(ads), - )) -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] -pub(crate) enum Time { - UtcTime(asn1::UtcTime), - GeneralizedTime(asn1::GeneralizedTime), -} - -impl Time { - pub(crate) fn as_chrono(&self) -> &chrono::DateTime { - match self { - Time::UtcTime(data) => data.as_chrono(), - Time::GeneralizedTime(data) => data.as_chrono(), - } - } -} - -pub(crate) type Extensions<'a> = Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, Extension<'a>>, - asn1::SequenceOfWriter<'a, Extension<'a>, Vec>>, ->; - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] -pub(crate) struct AlgorithmIdentifier<'a> { - pub(crate) oid: asn1::ObjectIdentifier, - pub(crate) params: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)] -pub(crate) struct Extension<'a> { - pub(crate) extn_id: asn1::ObjectIdentifier, - #[default(false)] - pub(crate) critical: bool, - pub(crate) extn_value: &'a [u8], + Ok(asn1::write_single(&asn1::SequenceOfWriter::new(ads))?) } pub(crate) fn parse_name<'p>( py: pyo3::Python<'p>, name: &Name<'_>, -) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let x509_module = py.import("cryptography.x509")?; +) -> Result<&'p pyo3::PyAny, CryptographyError> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let py_rdns = pyo3::types::PyList::empty(py); for rdn in name.unwrap_read().clone() { let py_rdn = parse_rdn(py, &rdn)?; py_rdns.append(py_rdn)?; } - Ok(x509_module.call_method1("Name", (py_rdns,))?) + Ok(x509_module.call_method1(pyo3::intern!(py, "Name"), (py_rdns,))?) } fn parse_name_attribute( py: pyo3::Python<'_>, attribute: AttributeTypeValue<'_>, -) -> Result { - let x509_module = py.import("cryptography.x509")?; +) -> Result { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let oid = oid_to_py_oid(py, &attribute.type_id)?.to_object(py); let tag_enum = py - .import("cryptography.x509.name")? - .getattr(crate::intern!(py, "_ASN1_TYPE_TO_ENUM"))?; + .import(pyo3::intern!(py, "cryptography.x509.name"))? + .getattr(pyo3::intern!(py, "_ASN1_TYPE_TO_ENUM"))?; let tag_val = attribute .value .tag() .as_u8() .ok_or_else(|| { - PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( + CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Long-form tags are not supported in NameAttribute values", )) })? @@ -378,12 +210,12 @@ fn parse_name_attribute( // BMPString tag value Some(30) => { let py_bytes = pyo3::types::PyBytes::new(py, attribute.value.data()); - py_bytes.call_method1("decode", ("utf_16_be",))? + py_bytes.call_method1(pyo3::intern!(py, "decode"), ("utf_16_be",))? } // UniversalString Some(28) => { let py_bytes = pyo3::types::PyBytes::new(py, attribute.value.data()); - py_bytes.call_method1("decode", ("utf_32_be",))? + py_bytes.call_method1(pyo3::intern!(py, "decode"), ("utf_32_be",))? } _ => { let parsed = std::str::from_utf8(attribute.value.data()) @@ -393,61 +225,70 @@ fn parse_name_attribute( }; let kwargs = [("_validate", false)].into_py_dict(py); Ok(x509_module - .call_method("NameAttribute", (oid, py_data, py_tag), Some(kwargs))? + .call_method( + pyo3::intern!(py, "NameAttribute"), + (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 x509_module = py.import("cryptography.x509")?; - let py_attrs = pyo3::types::PySet::empty(py)?; +) -> Result { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; + let py_attrs = pyo3::types::PyList::empty(py); for attribute in rdn.clone() { let na = parse_name_attribute(py, attribute)?; - py_attrs.add(na)?; + py_attrs.append(na)?; } Ok(x509_module - .call_method1("RelativeDistinguishedName", (py_attrs,))? + .call_method1(pyo3::intern!(py, "RelativeDistinguishedName"), (py_attrs,))? .to_object(py)) } pub(crate) fn parse_general_name( py: pyo3::Python<'_>, gn: GeneralName<'_>, -) -> Result { - let x509_module = py.import("cryptography.x509")?; +) -> Result { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let py_gn = match gn { GeneralName::OtherName(data) => { let oid = oid_to_py_oid(py, &data.type_id)?.to_object(py); x509_module - .call_method1("OtherName", (oid, data.value.full_data()))? + .call_method1( + pyo3::intern!(py, "OtherName"), + (oid, data.value.full_data()), + )? .to_object(py) } GeneralName::RFC822Name(data) => x509_module - .getattr(crate::intern!(py, "RFC822Name"))? - .call_method1("_init_without_validation", (data.0,))? + .getattr(pyo3::intern!(py, "RFC822Name"))? + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::DNSName(data) => x509_module - .getattr(crate::intern!(py, "DNSName"))? - .call_method1("_init_without_validation", (data.0,))? + .getattr(pyo3::intern!(py, "DNSName"))? + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::DirectoryName(data) => { let py_name = parse_name(py, &data)?; x509_module - .call_method1("DirectoryName", (py_name,))? + .call_method1(pyo3::intern!(py, "DirectoryName"), (py_name,))? .to_object(py) } GeneralName::UniformResourceIdentifier(data) => x509_module - .getattr(crate::intern!(py, "UniformResourceIdentifier"))? - .call_method1("_init_without_validation", (data.0,))? + .getattr(pyo3::intern!(py, "UniformResourceIdentifier"))? + .call_method1(pyo3::intern!(py, "_init_without_validation"), (data.0,))? .to_object(py), GeneralName::IPAddress(data) => { - let ip_module = py.import("ipaddress")?; + let ip_module = py.import(pyo3::intern!(py, "ipaddress"))?; if data.len() == 4 || data.len() == 16 { - let addr = ip_module.call_method1("ip_address", (data,))?.to_object(py); + let addr = ip_module + .call_method1(pyo3::intern!(py, "ip_address"), (data,))? + .to_object(py); x509_module - .call_method1("IPAddress", (addr,))? + .call_method1(pyo3::intern!(py, "IPAddress"), (addr,))? .to_object(py) } else { // if it's not an IPv4 or IPv6 we assume it's an IPNetwork and @@ -458,16 +299,15 @@ pub(crate) fn parse_general_name( GeneralName::RegisteredID(data) => { let oid = oid_to_py_oid(py, &data)?.to_object(py); x509_module - .call_method1("RegisteredID", (oid,))? + .call_method1(pyo3::intern!(py, "RegisteredID"), (oid,))? .to_object(py) } _ => { - return Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - x509_module.call_method1( - "UnsupportedGeneralNameType", - ("x400Address/EDIPartyName are not supported types",), - )?, - ))) + return Err(CryptographyError::from( + exceptions::UnsupportedGeneralNameType::new_err( + "x400Address/EDIPartyName are not supported types", + ), + )) } }; Ok(py_gn) @@ -476,7 +316,7 @@ pub(crate) fn parse_general_name( pub(crate) fn parse_general_names<'a>( py: pyo3::Python<'_>, gn_seq: &asn1::SequenceOf<'a, GeneralName<'a>>, -) -> Result { +) -> Result { let gns = pyo3::types::PyList::empty(py); for gn in gn_seq.clone() { let py_gn = parse_general_name(py, gn)?; @@ -485,9 +325,12 @@ pub(crate) fn parse_general_names<'a>( Ok(gns.to_object(py)) } -fn create_ip_network(py: pyo3::Python<'_>, data: &[u8]) -> Result { - let ip_module = py.import("ipaddress")?; - let x509_module = py.import("cryptography.x509")?; +fn create_ip_network( + py: pyo3::Python<'_>, + data: &[u8], +) -> Result { + let ip_module = py.import(pyo3::intern!(py, "ipaddress"))?; + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let prefix = match data.len() { 8 => { let num = u32::from_be_bytes(data[4..].try_into().unwrap()); @@ -497,7 +340,7 @@ fn create_ip_network(py: pyo3::Python<'_>, data: &[u8]) -> Result Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( + _ => 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()), ))), }; @@ -507,81 +350,86 @@ fn create_ip_network(py: pyo3::Python<'_>, data: &[u8]) -> Result()?, prefix? ); - let addr = ip_module.call_method1("ip_network", (net,))?.to_object(py); + let addr = ip_module + .call_method1(pyo3::intern!(py, "ip_network"), (net,))? + .to_object(py); Ok(x509_module - .call_method1("IPAddress", (addr,))? + .call_method1(pyo3::intern!(py, "IPAddress"), (addr,))? .to_object(py)) } -fn ipv4_netmask(num: u32) -> Result { +fn ipv4_netmask(num: u32) -> Result { // we invert and check leading zeros because leading_ones wasn't stabilized // until 1.46.0. When we raise our MSRV we should change this if (!num).leading_zeros() + num.trailing_zeros() != 32 { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Invalid netmask", - ))); + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err("Invalid netmask"), + )); } Ok((!num).leading_zeros()) } -fn ipv6_netmask(num: u128) -> Result { +fn ipv6_netmask(num: u128) -> Result { // we invert and check leading zeros because leading_ones wasn't stabilized // until 1.46.0. When we raise our MSRV we should change this if (!num).leading_zeros() + num.trailing_zeros() != 128 { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Invalid netmask", - ))); + 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(&asn1::ObjectIdentifier, &[u8]) -> Result, PyAsn1Error>, + F: Fn(&asn1::ObjectIdentifier, &[u8]) -> Result, CryptographyError>, >( py: pyo3::Python<'p>, cached_extensions: &mut Option, - raw_exts: &Option>, + raw_extensions: &Option>, parse_ext: F, ) -> pyo3::PyResult { if let Some(cached) = cached_extensions { return Ok(cached.clone_ref(py)); } - let x509_module = py.import("cryptography.x509")?; + let extensions = match Extensions::from_raw_extensions(raw_extensions.as_ref()) { + Ok(extensions) => extensions, + Err(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 x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let exts = pyo3::types::PyList::empty(py); - let mut seen_oids = HashSet::new(); - if let Some(raw_exts) = raw_exts { - for raw_ext in raw_exts.unwrap_read().clone() { + if let Some(extensions) = extensions.as_raw() { + for raw_ext in extensions.unwrap_read().clone() { let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?; - if seen_oids.contains(&raw_ext.extn_id) { - return Err(pyo3::PyErr::from_instance(x509_module.call_method1( - "DuplicateExtension", - ( - format!("Duplicate {} extension found", raw_ext.extn_id), - oid_obj, - ), - )?)); - } - let extn_value = match parse_ext(&raw_ext.extn_id, raw_ext.extn_value)? { Some(e) => e, - None => x509_module - .call_method1("UnrecognizedExtension", (oid_obj, raw_ext.extn_value))?, + None => x509_module.call_method1( + pyo3::intern!(py, "UnrecognizedExtension"), + (oid_obj, raw_ext.extn_value), + )?, }; - let ext_obj = - x509_module.call_method1("Extension", (oid_obj, raw_ext.critical, extn_value))?; + let ext_obj = x509_module.call_method1( + pyo3::intern!(py, "Extension"), + (oid_obj, raw_ext.critical, extn_value), + )?; exts.append(ext_obj)?; - seen_oids.insert(raw_ext.extn_id); } } let extensions = x509_module - .call_method1("Extensions", (exts,))? + .call_method1(pyo3::intern!(py, "Extensions"), (exts,))? .to_object(py); *cached_extensions = Some(extensions.clone_ref(py)); Ok(extensions) @@ -589,29 +437,33 @@ pub(crate) fn parse_and_cache_extensions< pub(crate) fn encode_extensions< 'p, - F: Fn(pyo3::Python<'_>, &asn1::ObjectIdentifier, &pyo3::PyAny) -> PyAsn1Result>>, + F: Fn( + pyo3::Python<'_>, + &asn1::ObjectIdentifier, + &pyo3::PyAny, + ) -> CryptographyResult>>, >( py: pyo3::Python<'p>, py_exts: &'p pyo3::PyAny, encode_ext: F, -) -> pyo3::PyResult>> { +) -> pyo3::PyResult>> { let unrecognized_extension_type: &pyo3::types::PyType = py - .import("cryptography.x509")? - .getattr(crate::intern!(py, "UnrecognizedExtension"))? + .import(pyo3::intern!(py, "cryptography.x509"))? + .getattr(pyo3::intern!(py, "UnrecognizedExtension"))? .extract()?; let mut exts = vec![]; for py_ext in py_exts.iter()? { let py_ext = py_ext?; - let oid = py_oid_to_oid(py_ext.getattr(crate::intern!(py, "oid"))?)?; + let oid = py_oid_to_oid(py_ext.getattr(pyo3::intern!(py, "oid"))?)?; - let ext_val = py_ext.getattr(crate::intern!(py, "value"))?; - if unrecognized_extension_type.is_instance(ext_val)? { + let ext_val = py_ext.getattr(pyo3::intern!(py, "value"))?; + if ext_val.is_instance(unrecognized_extension_type)? { exts.push(Extension { extn_id: oid, - critical: py_ext.getattr(crate::intern!(py, "critical"))?.extract()?, + critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, extn_value: ext_val - .getattr(crate::intern!(py, "value"))? + .getattr(pyo3::intern!(py, "value"))? .extract::<&[u8]>()?, }); continue; @@ -622,7 +474,7 @@ pub(crate) fn encode_extensions< let py_data = pyo3::types::PyBytes::new(py, &data); exts.push(Extension { extn_id: oid, - critical: py_ext.getattr(crate::intern!(py, "critical"))?.extract()?, + critical: py_ext.getattr(pyo3::intern!(py, "critical"))?.extract()?, extn_value: py_data.as_bytes(), }) } @@ -647,7 +499,7 @@ fn encode_extension_value<'p>( py: pyo3::Python<'p>, py_ext: &'p pyo3::PyAny, ) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { - let oid = py_oid_to_oid(py_ext.getattr(crate::intern!(py, "oid"))?)?; + 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 @@ -661,13 +513,13 @@ fn encode_extension_value<'p>( ))) } -pub(crate) fn chrono_to_py<'p>( +pub(crate) fn datetime_to_py<'p>( py: pyo3::Python<'p>, - dt: &chrono::DateTime, + dt: &asn1::DateTime, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let datetime_module = py.import("datetime")?; + let datetime_module = py.import(pyo3::intern!(py, "datetime"))?; datetime_module - .getattr(crate::intern!(py, "datetime"))? + .getattr(pyo3::intern!(py, "datetime"))? .call1(( dt.year(), dt.month(), @@ -678,94 +530,33 @@ pub(crate) fn chrono_to_py<'p>( )) } -pub(crate) fn py_to_chrono( +pub(crate) fn py_to_datetime( py: pyo3::Python<'_>, val: &pyo3::PyAny, -) -> pyo3::PyResult> { - Ok(chrono::Utc - .ymd( - val.getattr(crate::intern!(py, "year"))?.extract()?, - val.getattr(crate::intern!(py, "month"))?.extract()?, - val.getattr(crate::intern!(py, "day"))?.extract()?, - ) - .and_hms( - val.getattr(crate::intern!(py, "hour"))?.extract()?, - val.getattr(crate::intern!(py, "minute"))?.extract()?, - val.getattr(crate::intern!(py, "second"))?.extract()?, - )) -} - -#[derive(Hash, PartialEq, Clone)] -pub(crate) enum Asn1ReadableOrWritable<'a, T, U> { - Read(T, PhantomData<&'a ()>), - Write(U, PhantomData<&'a ()>), -} - -impl<'a, T, U> Asn1ReadableOrWritable<'a, T, U> { - pub(crate) fn new_read(v: T) -> Self { - Asn1ReadableOrWritable::Read(v, PhantomData) - } - - pub(crate) fn new_write(v: U) -> Self { - Asn1ReadableOrWritable::Write(v, PhantomData) - } - - pub(crate) 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<'a, T, U> -{ - const TAG: asn1::Tag = T::TAG; - fn parse_data(data: &'a [u8]) -> asn1::ParseResult { - Ok(Self::new_read(T::parse_data(data)?)) - } -} - -impl<'a, T: asn1::SimpleAsn1Writable, U: asn1::SimpleAsn1Writable> asn1::SimpleAsn1Writable - for Asn1ReadableOrWritable<'a, T, U> -{ - 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), - } - } +) -> pyo3::PyResult { + Ok(asn1::DateTime::new( + val.getattr(pyo3::intern!(py, "year"))?.extract()?, + val.getattr(pyo3::intern!(py, "month"))?.extract()?, + val.getattr(pyo3::intern!(py, "day"))?.extract()?, + val.getattr(pyo3::intern!(py, "hour"))?.extract()?, + val.getattr(pyo3::intern!(py, "minute"))?.extract()?, + val.getattr(pyo3::intern!(py, "second"))?.extract()?, + ) + .unwrap()) +} + +pub(crate) fn datetime_now(py: pyo3::Python<'_>) -> pyo3::PyResult { + py_to_datetime( + py, + py.import(pyo3::intern!(py, "datetime"))? + .getattr(pyo3::intern!(py, "datetime"))? + .call_method0(pyo3::intern!(py, "utcnow"))?, + ) } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_wrapped(pyo3::wrap_pyfunction!(encode_extension_value))?; - module.add_wrapped(pyo3::wrap_pyfunction!(encode_name_bytes))?; + module.add_function(pyo3::wrap_pyfunction!(encode_extension_value, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(encode_name_bytes, module)?)?; Ok(()) } - -#[cfg(test)] -mod tests { - use super::{Asn1ReadableOrWritable, RawTlv}; - use asn1::Asn1Readable; - - #[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)); - } -} diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs index b34de10..9230150 100644 --- a/src/rust/src/x509/crl.rs +++ b/src/rust/src/x509/crl.rs @@ -3,37 +3,37 @@ // for complete details. use crate::asn1::{ - big_byte_slice_to_py_int, oid_to_py_oid, py_uint_to_big_endian_bytes, PyAsn1Error, PyAsn1Result, + big_byte_slice_to_py_int, encode_der_data, oid_to_py_oid, py_uint_to_big_endian_bytes, }; -use crate::x509; -use crate::x509::{certificate, extensions, oid}; -use pyo3::ToPyObject; -use std::convert::TryInto; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::{certificate, extensions, sign}; +use crate::{exceptions, x509}; +use cryptography_x509::{common, crl, name, oid}; +use pyo3::{IntoPy, ToPyObject}; use std::sync::Arc; #[pyo3::prelude::pyfunction] fn load_der_x509_crl( py: pyo3::Python<'_>, - data: &[u8], -) -> Result { - let raw = OwnedRawCertificateRevocationList::try_new( - Arc::from(data), - |data| asn1::parse_single(data), - |_| Ok(pyo3::once_cell::GILOnceCell::new()), - )?; + data: pyo3::Py, +) -> Result { + let owned = OwnedCertificateRevocationList::try_new(data, |data| { + asn1::parse_single(data.as_bytes(py)) + })?; - let version = raw.borrow_value().tbs_cert_list.version.unwrap_or(1); + let version = owned.borrow_value().tbs_cert_list.version.unwrap_or(1); if version != 1 { - let x509_module = py.import("cryptography.x509")?; - return Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - x509_module - .getattr(crate::intern!(py, "InvalidVersion"))? - .call1((format!("{} is not a valid CRL version", version), version))?, - ))); + return Err(CryptographyError::from( + exceptions::InvalidVersion::new_err(( + format!("{} is not a valid CRL version", version), + version, + )), + )); } Ok(CertificateRevocationList { - raw: Arc::new(raw), + owned: Arc::new(owned), + revoked_certs: pyo3::once_cell::GILOnceCell::new(), cached_extensions: None, }) } @@ -42,52 +42,48 @@ fn load_der_x509_crl( fn load_pem_x509_crl( py: pyo3::Python<'_>, data: &[u8], -) -> Result { +) -> Result { 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?", )?; - // TODO: Produces an extra copy - load_der_x509_crl(py, &block.contents) + load_der_x509_crl( + py, + pyo3::types::PyBytes::new(py, &block.contents).into_py(py), + ) } #[ouroboros::self_referencing] -struct OwnedRawCertificateRevocationList { - data: Arc<[u8]>, +struct OwnedCertificateRevocationList { + data: pyo3::Py, #[borrows(data)] #[covariant] - value: RawCertificateRevocationList<'this>, - #[borrows(data)] - #[not_covariant] - revoked_certs: pyo3::once_cell::GILOnceCell>>, + value: crl::CertificateRevocationList<'this>, } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] struct CertificateRevocationList { - raw: Arc, + owned: Arc, + revoked_certs: pyo3::once_cell::GILOnceCell>, cached_extensions: Option, } impl CertificateRevocationList { - fn public_bytes_der(&self) -> PyAsn1Result> { - Ok(asn1::write_single(self.raw.borrow_value())?) + fn public_bytes_der(&self) -> CryptographyResult> { + Ok(asn1::write_single(self.owned.borrow_value())?) } - fn revoked_cert(&self, py: pyo3::Python<'_>, idx: usize) -> pyo3::PyResult { - let raw = try_map_arc_data_crl(&self.raw, |_crl, revoked_certs| { - let revoked_certs = revoked_certs.get(py).unwrap(); - Ok::<_, pyo3::PyErr>(revoked_certs[idx].clone()) - })?; - Ok(RevokedCertificate { - raw, + fn revoked_cert(&self, py: pyo3::Python<'_>, idx: usize) -> RevokedCertificate { + RevokedCertificate { + owned: self.revoked_certs.get(py).unwrap()[idx].clone(), cached_extensions: None, - }) + } } fn len(&self) -> usize { - self.raw + self.owned .borrow_value() .tbs_cert_list .revoked_certificates @@ -96,49 +92,66 @@ impl CertificateRevocationList { } } -#[pyo3::prelude::pyproto] -impl pyo3::PyObjectProtocol for CertificateRevocationList { +#[pyo3::prelude::pymethods] +impl CertificateRevocationList { fn __richcmp__( &self, - other: pyo3::PyRef, + other: pyo3::PyRef<'_, CertificateRevocationList>, op: pyo3::basic::CompareOp, ) -> pyo3::PyResult { match op { - pyo3::basic::CompareOp::Eq => Ok(self.raw.borrow_value() == other.raw.borrow_value()), - pyo3::basic::CompareOp::Ne => Ok(self.raw.borrow_value() != other.raw.borrow_value()), + pyo3::basic::CompareOp::Eq => { + Ok(self.owned.borrow_value() == other.owned.borrow_value()) + } + pyo3::basic::CompareOp::Ne => { + Ok(self.owned.borrow_value() != other.owned.borrow_value()) + } _ => Err(pyo3::exceptions::PyTypeError::new_err( "CRLs cannot be ordered", )), } } -} -#[pyo3::prelude::pyproto] -impl pyo3::PyMappingProtocol for CertificateRevocationList { fn __len__(&self) -> usize { self.len() } - fn __getitem__(&self, idx: &pyo3::PyAny) -> pyo3::PyResult { - let gil = pyo3::Python::acquire_gil(); - let py = gil.python(); + fn __iter__(&self) -> CRLIterator { + CRLIterator { + contents: OwnedCRLIteratorData::try_new(Arc::clone(&self.owned), |v| { + Ok::<_, ()>( + v.borrow_value() + .tbs_cert_list + .revoked_certificates + .as_ref() + .map(|v| v.unwrap_read().clone()), + ) + }) + .unwrap(), + } + } - self.raw.with(|val| { - val.revoked_certs.get_or_init(py, || { - match &val.value.tbs_cert_list.revoked_certificates { - Some(c) => c.unwrap_read().clone().collect(), - None => vec![], - } - }); + fn __getitem__( + &self, + py: pyo3::Python<'_>, + idx: &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::()? { + if idx.is_instance_of::()? { let indices = idx .downcast::()? .indices(self.len().try_into().unwrap())?; let result = pyo3::types::PyList::empty(py); for i in (indices.start..indices.stop).step_by(indices.step.try_into().unwrap()) { - let revoked_cert = pyo3::PyCell::new(py, self.revoked_cert(py, i as usize)?)?; + let revoked_cert = pyo3::PyCell::new(py, self.revoked_cert(py, i as usize))?; result.append(revoked_cert)?; } Ok(result.to_object(py)) @@ -150,29 +163,28 @@ impl pyo3::PyMappingProtocol for CertificateRevocationList { if idx >= (self.len() as isize) || idx < 0 { return Err(pyo3::exceptions::PyIndexError::new_err(())); } - Ok(pyo3::PyCell::new(py, self.revoked_cert(py, idx as usize)?)?.to_object(py)) + Ok(pyo3::PyCell::new(py, self.revoked_cert(py, idx as usize))?.to_object(py)) } } -} -#[pyo3::prelude::pymethods] -impl CertificateRevocationList { fn fingerprint<'p>( &self, py: pyo3::Python<'p>, algorithm: pyo3::PyObject, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let hashes_mod = py.import("cryptography.hazmat.primitives.hashes")?; + let hashes_mod = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; let h = hashes_mod - .getattr(crate::intern!(py, "Hash"))? + .getattr(pyo3::intern!(py, "Hash"))? .call1((algorithm,))?; - h.call_method1("update", (self.public_bytes_der()?.as_slice(),))?; - h.call_method0("finalize") + + let data = self.public_bytes_der()?; + h.call_method1(pyo3::intern!(py, "update"), (data.as_slice(),))?; + h.call_method0(pyo3::intern!(py, "finalize")) } #[getter] fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - oid_to_py_oid(py, &self.raw.borrow_value().signature_algorithm.oid) + oid_to_py_oid(py, self.owned.borrow_value().signature_algorithm.oid()) } #[getter] @@ -181,111 +193,87 @@ impl CertificateRevocationList { py: pyo3::Python<'p>, ) -> pyo3::PyResult<&'p pyo3::PyAny> { let oid = self.signature_algorithm_oid(py)?; - let oid_module = py.import("cryptography.hazmat._oid")?; - let exceptions_module = py.import("cryptography.exceptions")?; + let oid_module = py.import(pyo3::intern!(py, "cryptography.hazmat._oid"))?; match oid_module - .getattr(crate::intern!(py, "_SIG_OIDS_TO_HASH"))? + .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))? .get_item(oid) { Ok(v) => Ok(v), - Err(_) => Err(pyo3::PyErr::from_instance(exceptions_module.call_method1( - "UnsupportedAlgorithm", - (format!( - "Signature algorithm OID:{} not recognized", - self.raw.borrow_value().signature_algorithm.oid - ),), - )?)), + Err(_) => Err(exceptions::UnsupportedAlgorithm::new_err(format!( + "Signature algorithm OID: {} not recognized", + self.owned.borrow_value().signature_algorithm.oid(), + ))), } } #[getter] fn signature(&self) -> &[u8] { - self.raw.borrow_value().signature_value.as_bytes() + self.owned.borrow_value().signature_value.as_bytes() } #[getter] fn tbs_certlist_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { - let b = asn1::write_single(&self.raw.borrow_value().tbs_cert_list)?; + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let b = asn1::write_single(&self.owned.borrow_value().tbs_cert_list)?; Ok(pyo3::types::PyBytes::new(py, &b)) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { - let encoding_class = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "Encoding"))?; - - let result = asn1::write_single(self.raw.borrow_value())?; - if encoding == encoding_class.getattr(crate::intern!(py, "DER"))? { - Ok(pyo3::types::PyBytes::new(py, &result)) - } else if encoding == encoding_class.getattr(crate::intern!(py, "PEM"))? { - let pem = pem::encode_config( - &pem::Pem { - tag: "X509 CRL".to_string(), - contents: result, - }, - pem::EncodeConfig { - line_ending: pem::LineEnding::LF, - }, - ) - .into_bytes(); - Ok(pyo3::types::PyBytes::new(py, &pem)) - } else { - Err(pyo3::exceptions::PyTypeError::new_err( - "encoding must be Encoding.DER or Encoding.PEM", - ) - .into()) - } + encoding: &'p pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { + let result = asn1::write_single(self.owned.borrow_value())?; + + encode_der_data(py, "X509 CRL".to_string(), result, encoding) } #[getter] fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { Ok(x509::parse_name( py, - &self.raw.borrow_value().tbs_cert_list.issuer, + &self.owned.borrow_value().tbs_cert_list.issuer, )?) } #[getter] fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - match &self.raw.borrow_value().tbs_cert_list.next_update { - Some(t) => x509::chrono_to_py(py, t.as_chrono()), + match &self.owned.borrow_value().tbs_cert_list.next_update { + Some(t) => x509::datetime_to_py(py, t.as_datetime()), None => Ok(py.None().into_ref(py)), } } #[getter] fn last_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - x509::chrono_to_py( + x509::datetime_to_py( py, - self.raw + self.owned .borrow_value() .tbs_cert_list .this_update - .as_chrono(), + .as_datetime(), ) } #[getter] fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let x509_module = py.import("cryptography.x509")?; + let tbs_cert_list = &self.owned.borrow_value().tbs_cert_list; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, &mut self.cached_extensions, - &self.raw.borrow_value().tbs_cert_list.crl_extensions, + &tbs_cert_list.raw_crl_extensions, |oid, ext_data| match *oid { oid::CRL_NUMBER_OID => { let bignum = asn1::parse_single::>(ext_data)?; let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; Ok(Some( x509_module - .getattr(crate::intern!(py, "CRLNumber"))? + .getattr(pyo3::intern!(py, "CRLNumber"))? .call1((pynum,))?, )) } @@ -294,18 +282,18 @@ impl CertificateRevocationList { let pynum = big_byte_slice_to_py_int(py, bignum.as_bytes())?; Ok(Some( x509_module - .getattr(crate::intern!(py, "DeltaCRLIndicator"))? + .getattr(pyo3::intern!(py, "DeltaCRLIndicator"))? .call1((pynum,))?, )) } oid::ISSUER_ALTERNATIVE_NAME_OID => { - let gn_seq = asn1::parse_single::>>( + let gn_seq = asn1::parse_single::>>( ext_data, )?; let ians = x509::parse_general_names(py, &gn_seq)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "IssuerAlternativeName"))? + .getattr(pyo3::intern!(py, "IssuerAlternativeName"))? .call1((ians,))?, )) } @@ -313,7 +301,7 @@ impl CertificateRevocationList { let ads = certificate::parse_access_descriptions(py, ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "AuthorityInformationAccess"))? + .getattr(pyo3::intern!(py, "AuthorityInformationAccess"))? .call1((ads,))?, )) } @@ -321,7 +309,7 @@ impl CertificateRevocationList { certificate::parse_authority_key_identifier(py, ext_data)?, )), oid::ISSUING_DISTRIBUTION_POINT_OID => { - let idp = asn1::parse_single::>(ext_data)?; + let idp = asn1::parse_single::>(ext_data)?; let (full_name, relative_name) = match idp.distribution_point { Some(data) => certificate::parse_distribution_point_name(py, data)?, None => (py.None(), py.None()), @@ -336,7 +324,7 @@ impl CertificateRevocationList { }; Ok(Some( x509_module - .getattr(crate::intern!(py, "IssuingDistributionPoint"))? + .getattr(pyo3::intern!(py, "IssuingDistributionPoint"))? .call1(( full_name, relative_name, @@ -352,7 +340,7 @@ impl CertificateRevocationList { let dp = certificate::parse_distribution_points(py, ext_data)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "FreshestCRL"))? + .getattr(pyo3::intern!(py, "FreshestCRL"))? .call1((dp,))?, )) } @@ -367,7 +355,7 @@ impl CertificateRevocationList { serial: &pyo3::types::PyLong, ) -> pyo3::PyResult> { let serial_bytes = py_uint_to_big_endian_bytes(py, serial)?; - let owned = OwnedRawRevokedCertificate::try_new(Arc::clone(&self.raw), |v| { + let owned = OwnedRevokedCertificate::try_new(Arc::clone(&self.owned), |v| { let certs = match &v.borrow_value().tbs_cert_list.revoked_certificates { Some(certs) => certs.unwrap_read().clone(), None => return Err(()), @@ -383,7 +371,7 @@ impl CertificateRevocationList { }); match owned { Ok(o) => Ok(Some(RevokedCertificate { - raw: o, + owned: o, cached_extensions: None, })), Err(()) => Ok(None), @@ -394,104 +382,67 @@ impl CertificateRevocationList { slf: pyo3::PyRef<'_, Self>, py: pyo3::Python<'p>, public_key: &'p pyo3::PyAny, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr(crate::intern!(py, "backend"))?; - backend.call_method1("_crl_is_signature_valid", (slf, public_key)) - } + ) -> CryptographyResult { + if slf.owned.borrow_value().tbs_cert_list.signature + != slf.owned.borrow_value().signature_algorithm + { + return Ok(false); + }; - // This getter exists for compatibility with pyOpenSSL and will be removed. - // DO NOT RELY ON IT. WE WILL BREAK YOU WHEN WE FEEL LIKE IT. - #[getter] - fn _x509_crl<'p>( - slf: pyo3::PyRef<'_, Self>, - py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let cryptography_warning = py - .import("cryptography.utils")? - .getattr(crate::intern!(py, "DeprecatedIn35"))?; - pyo3::PyErr::warn( - py, - cryptography_warning, - "This version of cryptography contains a temporary pyOpenSSL fallback path. Upgrade pyOpenSSL now.", - 1 - )?; - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr(crate::intern!(py, "backend"))?; - Ok(backend.call_method1("_crl2ossl", (slf,))?) - } -} + // Error on invalid public key -- below we treat any error as just + // being an invalid signature. + sign::identify_public_key_type(py, public_key)?; -#[pyo3::prelude::pyproto] -impl pyo3::PyIterProtocol<'_> for CertificateRevocationList { - fn __iter__(slf: pyo3::PyRef<'p, Self>) -> CRLIterator { - CRLIterator { - contents: OwnedCRLIteratorData::try_new(Arc::clone(&slf.raw), |v| { - Ok::<_, ()>( - v.borrow_value() - .tbs_cert_list - .revoked_certificates - .as_ref() - .map(|v| v.unwrap_read().clone()), - ) - }) - .unwrap(), - } + Ok(sign::verify_signature_with_signature_algorithm( + py, + public_key, + &slf.owned.borrow_value().signature_algorithm, + slf.owned.borrow_value().signature_value.as_bytes(), + &asn1::write_single(&slf.owned.borrow_value().tbs_cert_list)?, + ) + .is_ok()) } } #[ouroboros::self_referencing] struct OwnedCRLIteratorData { - data: Arc, + data: Arc, #[borrows(data)] #[covariant] - value: Option>>, + value: Option>>, } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::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_crl( - crl: &Arc, - f: impl for<'this> FnOnce( - &'this OwnedRawCertificateRevocationList, - &pyo3::once_cell::GILOnceCell>>, - ) -> Result, E>, -) -> Result { - OwnedRawRevokedCertificate::try_new(Arc::clone(crl), |inner_crl| { - crl.with(|value| { - f(inner_crl, unsafe { - std::mem::transmute(value.revoked_certs) - }) - }) - }) -} fn try_map_arc_data_mut_crl_iterator( it: &mut OwnedCRLIteratorData, f: impl for<'this> FnOnce( - &'this OwnedRawCertificateRevocationList, - &mut Option>>, - ) -> Result, E>, -) -> Result { - OwnedRawRevokedCertificate::try_new(Arc::clone(it.borrow_data()), |inner_it| { + &'this OwnedCertificateRevocationList, + &mut Option>>, + ) -> Result, E>, +) -> Result { + OwnedRevokedCertificate::try_new(Arc::clone(it.borrow_data()), |inner_it| { it.with_value_mut(|value| f(inner_it, unsafe { std::mem::transmute(value) })) }) } -#[pyo3::prelude::pyproto] -impl pyo3::PyIterProtocol<'_> for CRLIterator { - fn __iter__(slf: pyo3::PyRef<'p, Self>) -> pyo3::PyRef<'p, Self> { +#[pyo3::prelude::pymethods] +impl CRLIterator { + fn __len__(&self) -> usize { + self.contents.borrow_value().clone().map_or(0, |v| v.len()) + } + + fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } - fn __next__(mut slf: pyo3::PyRefMut<'p, Self>) -> Option { - let revoked = try_map_arc_data_mut_crl_iterator(&mut slf.contents, |_data, v| match v { + 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(()), @@ -500,64 +451,35 @@ impl pyo3::PyIterProtocol<'_> for CRLIterator { }) .ok()?; Some(RevokedCertificate { - raw: revoked, + owned: revoked, cached_extensions: None, }) } } -#[pyo3::prelude::pyproto] -impl pyo3::PySequenceProtocol<'_> for CRLIterator { - fn __len__(&self) -> usize { - self.contents.borrow_value().clone().map_or(0, |v| v.len()) - } -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] -struct RawCertificateRevocationList<'a> { - tbs_cert_list: TBSCertList<'a>, - signature_algorithm: x509::AlgorithmIdentifier<'a>, - signature_value: asn1::BitString<'a>, -} - -type RevokedCertificates<'a> = Option< - x509::Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, RawRevokedCertificate<'a>>, - asn1::SequenceOfWriter<'a, RawRevokedCertificate<'a>, Vec>>, - >, ->; - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] -struct TBSCertList<'a> { - version: Option, - signature: x509::AlgorithmIdentifier<'a>, - issuer: x509::Name<'a>, - this_update: x509::Time, - next_update: Option, - revoked_certificates: RevokedCertificates<'a>, - #[explicit(0)] - crl_extensions: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] -struct RawRevokedCertificate<'a> { - user_certificate: asn1::BigUint<'a>, - revocation_date: x509::Time, - crl_entry_extensions: Option>, -} - #[ouroboros::self_referencing] -struct OwnedRawRevokedCertificate { - data: Arc, +struct OwnedRevokedCertificate { + data: Arc, #[borrows(data)] #[covariant] - value: RawRevokedCertificate<'this>, + value: crl::RevokedCertificate<'this>, } -#[pyo3::prelude::pyclass] +impl Clone for OwnedRevokedCertificate { + fn clone(&self) -> OwnedRevokedCertificate { + // 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_data()), |_| unsafe { + std::mem::transmute(self.borrow_value().clone()) + }) + } +} + +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] struct RevokedCertificate { - raw: OwnedRawRevokedCertificate, + owned: OwnedRevokedCertificate, cached_extensions: Option, } @@ -565,12 +487,12 @@ struct RevokedCertificate { impl RevokedCertificate { #[getter] fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - big_byte_slice_to_py_int(py, self.raw.borrow_value().user_certificate.as_bytes()) + big_byte_slice_to_py_int(py, self.owned.borrow_value().user_certificate.as_bytes()) } #[getter] fn revocation_date<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - x509::chrono_to_py(py, self.raw.borrow_value().revocation_date.as_chrono()) + x509::datetime_to_py(py, self.owned.borrow_value().revocation_date.as_datetime()) } #[getter] @@ -578,47 +500,17 @@ impl RevokedCertificate { x509::parse_and_cache_extensions( py, &mut self.cached_extensions, - &self.raw.borrow_value().crl_entry_extensions, + &self.owned.borrow_value().raw_crl_entry_extensions, |oid, ext_data| parse_crl_entry_ext(py, oid.clone(), ext_data), ) } } -pub(crate) type ReasonFlags<'a> = - Option, asn1::OwnedBitString>>; - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) 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(crate) type CRLReason = asn1::Enumerated; - pub(crate) fn parse_crl_reason_flags<'p>( py: pyo3::Python<'p>, - reason: &CRLReason, -) -> PyAsn1Result<&'p pyo3::PyAny> { - let x509_module = py.import("cryptography.x509")?; + reason: &crl::CRLReason, +) -> CryptographyResult<&'p pyo3::PyAny> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; let flag_name = match reason.value() { 0 => "unspecified", 1 => "key_compromise", @@ -631,13 +523,16 @@ pub(crate) fn parse_crl_reason_flags<'p>( 9 => "privilege_withdrawn", 10 => "aa_compromise", value => { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - format!("Unsupported reason code: {}", value), - ))) + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "Unsupported reason code: {}", + value + )), + )) } }; Ok(x509_module - .getattr(crate::intern!(py, "ReasonFlags"))? + .getattr(pyo3::intern!(py, "ReasonFlags"))? .getattr(flag_name)?) } @@ -645,32 +540,32 @@ pub fn parse_crl_entry_ext<'p>( py: pyo3::Python<'p>, oid: asn1::ObjectIdentifier, data: &[u8], -) -> PyAsn1Result> { - let x509_module = py.import("cryptography.x509")?; +) -> CryptographyResult> { + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; match oid { oid::CRL_REASON_OID => { - let flags = parse_crl_reason_flags(py, &asn1::parse_single::(data)?)?; + let flags = parse_crl_reason_flags(py, &asn1::parse_single::(data)?)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "CRLReason"))? + .getattr(pyo3::intern!(py, "CRLReason"))? .call1((flags,))?, )) } oid::CERTIFICATE_ISSUER_OID => { - let gn_seq = asn1::parse_single::>>(data)?; + let gn_seq = asn1::parse_single::>>(data)?; let gns = x509::parse_general_names(py, &gn_seq)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "CertificateIssuer"))? + .getattr(pyo3::intern!(py, "CertificateIssuer"))? .call1((gns,))?, )) } oid::INVALIDITY_DATE_OID => { let time = asn1::parse_single::(data)?; - let py_dt = x509::chrono_to_py(py, time.as_chrono())?; + let py_dt = x509::datetime_to_py(py, time.as_datetime())?; Ok(Some( x509_module - .getattr(crate::intern!(py, "InvalidityDate"))? + .getattr(pyo3::intern!(py, "InvalidityDate"))? .call1((py_dt,))?, )) } @@ -684,35 +579,39 @@ fn create_x509_crl( builder: &pyo3::PyAny, private_key: &pyo3::PyAny, hash_algorithm: &pyo3::PyAny, -) -> PyAsn1Result { - let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?; - +) -> CryptographyResult { + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + )?; let mut revoked_certs = vec![]; for py_revoked_cert in builder - .getattr(crate::intern!(py, "_revoked_certificates"))? + .getattr(pyo3::intern!(py, "_revoked_certificates"))? .iter()? { let py_revoked_cert = py_revoked_cert?; let serial_number = py_revoked_cert - .getattr(crate::intern!(py, "serial_number"))? + .getattr(pyo3::intern!(py, "serial_number"))? .extract()?; - let py_revocation_date = py_revoked_cert.getattr(crate::intern!(py, "revocation_date"))?; - revoked_certs.push(RawRevokedCertificate { + let py_revocation_date = py_revoked_cert.getattr(pyo3::intern!(py, "revocation_date"))?; + revoked_certs.push(crl::RevokedCertificate { user_certificate: asn1::BigUint::new(py_uint_to_big_endian_bytes(py, serial_number)?) .unwrap(), revocation_date: x509::certificate::time_from_py(py, py_revocation_date)?, - crl_entry_extensions: x509::common::encode_extensions( + raw_crl_entry_extensions: x509::common::encode_extensions( py, - py_revoked_cert.getattr(crate::intern!(py, "extensions"))?, + py_revoked_cert.getattr(pyo3::intern!(py, "extensions"))?, extensions::encode_extension, )?, }); } - let py_issuer_name = builder.getattr(crate::intern!(py, "_issuer_name"))?; - let py_this_update = builder.getattr(crate::intern!(py, "_last_update"))?; - let py_next_update = builder.getattr(crate::intern!(py, "_next_update"))?; - let tbs_cert_list = TBSCertList { + 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, py_issuer_name)?, @@ -721,32 +620,37 @@ fn create_x509_crl( revoked_certificates: if revoked_certs.is_empty() { None } else { - Some(x509::Asn1ReadableOrWritable::new_write( + Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(revoked_certs), )) }, - crl_extensions: x509::common::encode_extensions( + raw_crl_extensions: x509::common::encode_extensions( py, - builder.getattr(crate::intern!(py, "_extensions"))?, + 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, hash_algorithm, &tbs_bytes)?; - let data = asn1::write_single(&RawCertificateRevocationList { + let signature = x509::sign::sign_data( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + &tbs_bytes, + )?; + let data = asn1::write_single(&crl::CertificateRevocationList { tbs_cert_list, signature_algorithm: sigalg, signature_value: asn1::BitString::new(signature, 0).unwrap(), })?; - // TODO: extra copy as we round-trip through a slice - load_der_x509_crl(py, &data) + load_der_x509_crl(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_wrapped(pyo3::wrap_pyfunction!(load_der_x509_crl))?; - module.add_wrapped(pyo3::wrap_pyfunction!(load_pem_x509_crl))?; - module.add_wrapped(pyo3::wrap_pyfunction!(create_x509_crl))?; + module.add_function(pyo3::wrap_pyfunction!(load_der_x509_crl, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_crl, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_x509_crl, module)?)?; module.add_class::()?; module.add_class::()?; diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs index 7579bcc..2e7797f 100644 --- a/src/rust/src/x509/csr.rs +++ b/src/rust/src/x509/csr.rs @@ -2,117 +2,70 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{oid_to_py_oid, py_oid_to_oid, PyAsn1Error, PyAsn1Result}; -use crate::x509; -use crate::x509::{certificate, oid}; +use crate::asn1::{encode_der_data, oid_to_py_oid, py_oid_to_oid}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::{certificate, sign}; +use crate::{exceptions, x509}; use asn1::SimpleAsn1Readable; +use cryptography_x509::csr::{check_attribute_length, Attribute, CertificationRequestInfo, Csr}; +use cryptography_x509::{common, oid}; +use pyo3::IntoPy; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct RawCsr<'a> { - csr_info: CertificationRequestInfo<'a>, - signature_alg: x509::AlgorithmIdentifier<'a>, - signature: asn1::BitString<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct CertificationRequestInfo<'a> { - version: u8, - subject: x509::Name<'a>, - spki: certificate::SubjectPublicKeyInfo<'a>, - #[implicit(0, required)] - attributes: x509::Asn1ReadableOrWritable< - 'a, - asn1::SetOf<'a, Attribute<'a>>, - asn1::SetOfWriter<'a, Attribute<'a>, Vec>>, - >, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct Attribute<'a> { - type_id: asn1::ObjectIdentifier, - values: x509::Asn1ReadableOrWritable< - 'a, - asn1::SetOf<'a, asn1::Tlv<'a>>, - asn1::SetOfWriter<'a, x509::common::RawTlv<'a>, [x509::common::RawTlv<'a>; 1]>, - >, -} - -fn check_attribute_length<'a>(values: asn1::SetOf<'a, asn1::Tlv<'a>>) -> Result<(), PyAsn1Error> { - if values.count() > 1 { - Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Only single-valued attributes are supported", - ))) - } else { - Ok(()) - } -} - -impl CertificationRequestInfo<'_> { - fn get_extension_attribute(&self) -> Result>, PyAsn1Error> { - 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) - } -} - #[ouroboros::self_referencing] -struct OwnedRawCsr { - data: Vec, +struct OwnedCsr { + data: pyo3::Py, #[borrows(data)] #[covariant] - value: RawCsr<'this>, + value: Csr<'this>, } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] struct CertificateSigningRequest { - raw: OwnedRawCsr, + raw: OwnedCsr, cached_extensions: Option, } -#[pyo3::prelude::pyproto] -impl pyo3::basic::PyObjectProtocol for CertificateSigningRequest { - fn __hash__(&self) -> u64 { +#[pyo3::prelude::pymethods] +impl CertificateSigningRequest { + fn __hash__(&self, py: pyo3::Python<'_>) -> u64 { let mut hasher = DefaultHasher::new(); - self.raw.borrow_data().hash(&mut hasher); + self.raw.borrow_data().as_bytes(py).hash(&mut hasher); hasher.finish() } fn __richcmp__( &self, - other: pyo3::PyRef, + py: pyo3::Python<'_>, + other: pyo3::PyRef<'_, CertificateSigningRequest>, op: pyo3::basic::CompareOp, ) -> pyo3::PyResult { match op { - pyo3::basic::CompareOp::Eq => Ok(self.raw.borrow_data() == other.raw.borrow_data()), - pyo3::basic::CompareOp::Ne => Ok(self.raw.borrow_data() != other.raw.borrow_data()), + pyo3::basic::CompareOp::Eq => { + Ok(self.raw.borrow_data().as_bytes(py) == other.raw.borrow_data().as_bytes(py)) + } + pyo3::basic::CompareOp::Ne => { + Ok(self.raw.borrow_data().as_bytes(py) != other.raw.borrow_data().as_bytes(py)) + } _ => Err(pyo3::exceptions::PyTypeError::new_err( "CSRs cannot be ordered", )), } } -} -#[pyo3::prelude::pymethods] -impl CertificateSigningRequest { - fn public_key<'p>(&self, py: pyo3::Python<'p>) -> PyAsn1Result<&'p pyo3::PyAny> { + fn public_key<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { // This makes an unnecessary copy. It'd be nice to get rid of it. let serialized = pyo3::types::PyBytes::new( py, &asn1::write_single(&self.raw.borrow_value().csr_info.spki)?, ); Ok(py - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "load_der_public_key"))? + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "load_der_public_key"))? .call1((serialized,))?) } @@ -128,7 +81,7 @@ impl CertificateSigningRequest { fn tbs_certrequest_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let result = asn1::write_single(&self.raw.borrow_value().csr_info)?; Ok(pyo3::types::PyBytes::new(py, &result)) } @@ -142,60 +95,35 @@ impl CertificateSigningRequest { fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { + ) -> Result<&'p pyo3::PyAny, CryptographyError> { let sig_oids_to_hash = py - .import("cryptography.hazmat._oid")? - .getattr(crate::intern!(py, "_SIG_OIDS_TO_HASH"))?; + .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? + .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?; let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?); match hash_alg { Ok(data) => Ok(data), - Err(_) => Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - py.import("cryptography.exceptions")?.call_method1( - "UnsupportedAlgorithm", - (format!( - "Signature algorithm OID: {} not recognized", - self.raw.borrow_value().signature_alg.oid - ),), - )?, - ))), + Err(_) => Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(format!( + "Signature algorithm OID: {} not recognized", + self.raw.borrow_value().signature_alg.oid() + )), + )), } } #[getter] fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - oid_to_py_oid(py, &self.raw.borrow_value().signature_alg.oid) + oid_to_py_oid(py, self.raw.borrow_value().signature_alg.oid()) } fn public_bytes<'p>( &self, py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { - let encoding_class = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "Encoding"))?; - + encoding: &'p pyo3::PyAny, + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let result = asn1::write_single(self.raw.borrow_value())?; - if encoding == encoding_class.getattr(crate::intern!(py, "DER"))? { - Ok(pyo3::types::PyBytes::new(py, &result)) - } else if encoding == encoding_class.getattr(crate::intern!(py, "PEM"))? { - let pem = pem::encode_config( - &pem::Pem { - tag: "CERTIFICATE REQUEST".to_string(), - contents: result, - }, - pem::EncodeConfig { - line_ending: pem::LineEnding::LF, - }, - ) - .into_bytes(); - Ok(pyo3::types::PyBytes::new(py, &pem)) - } else { - Err(pyo3::exceptions::PyTypeError::new_err( - "encoding must be Encoding.DER or Encoding.PEM", - ) - .into()) - } + + encode_der_data(py, "CERTIFICATE REQUEST".to_string(), result, encoding) } fn get_attribute_for_oid<'p>( @@ -204,8 +132,8 @@ impl CertificateSigningRequest { oid: &pyo3::PyAny, ) -> pyo3::PyResult<&'p pyo3::PyAny> { let cryptography_warning = py - .import("cryptography.utils")? - .getattr(crate::intern!(py, "DeprecatedIn36"))?; + .import(pyo3::intern!(py, "cryptography.utils"))? + .getattr(pyo3::intern!(py, "DeprecatedIn36"))?; pyo3::PyErr::warn( py, cryptography_warning, @@ -222,7 +150,11 @@ impl CertificateSigningRequest { .clone() { if rust_oid == attribute.type_id { - check_attribute_length(attribute.values.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 val = attribute.values.unwrap_read().clone().next().unwrap(); // We allow utf8string, printablestring, and ia5string at this time if val.tag() == asn1::Utf8String::TAG @@ -230,21 +162,18 @@ impl CertificateSigningRequest { || val.tag() == asn1::IA5String::TAG { return Ok(pyo3::types::PyBytes::new(py, val.data())); - } else { - return Err(pyo3::exceptions::PyValueError::new_err(format!( - "OID {} has a disallowed ASN.1 type: {:?}", - oid, - val.tag() - ))); } + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "OID {} has a disallowed ASN.1 type: {:?}", + oid, + val.tag() + ))); } } - Err(pyo3::PyErr::from_instance( - py.import("cryptography.x509")?.call_method1( - "AttributeNotFound", - (format!("No {} attribute was found", oid), oid), - )?, - )) + Err(exceptions::AttributeNotFound::new_err(( + format!("No {} attribute was found", oid), + oid.into_py(py), + ))) } #[getter] @@ -258,69 +187,71 @@ impl CertificateSigningRequest { .unwrap_read() .clone() { - check_attribute_length(attribute.values.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(py, val.data()); let tag = val.tag().as_u8().ok_or_else(|| { - PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( + CryptographyError::from(pyo3::exceptions::PyValueError::new_err( "Long-form tags are not supported in CSR attribute values", )) })?; let pyattr = py - .import("cryptography.x509")? - .call_method1("Attribute", (oid, serialized, tag))?; + .import(pyo3::intern!(py, "cryptography.x509"))? + .call_method1(pyo3::intern!(py, "Attribute"), (oid, serialized, tag))?; pyattrs.append(pyattr)?; } - py.import("cryptography.x509")? - .call_method1("Attributes", (pyattrs,)) + py.import(pyo3::intern!(py, "cryptography.x509"))? + .call_method1(pyo3::intern!(py, "Attributes"), (pyattrs,)) } #[getter] fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let exts = self.raw.borrow_value().csr_info.get_extension_attribute()?; - - x509::parse_and_cache_extensions(py, &mut self.cached_extensions, &exts, |oid, ext_data| { - certificate::parse_cert_ext(py, oid.clone(), ext_data) - }) - } + let raw_exts = self + .raw + .borrow_value() + .csr_info + .get_extension_attribute() + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Only single-valued attributes are supported", + ) + })?; - #[getter] - fn is_signature_valid<'p>( - slf: pyo3::PyRef<'_, Self>, - py: pyo3::Python<'p>, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr(crate::intern!(py, "backend"))?; - backend.call_method1("_csr_is_signature_valid", (slf,)) + x509::parse_and_cache_extensions( + py, + &mut self.cached_extensions, + &raw_exts, + |oid, ext_data| certificate::parse_cert_ext(py, oid.clone(), ext_data), + ) } - // This getter exists for compatibility with pyOpenSSL and will be removed. - // DO NOT RELY ON IT. WE WILL BREAK YOU WHEN WE FEEL LIKE IT. #[getter] - fn _x509_req<'p>( + fn is_signature_valid( slf: pyo3::PyRef<'_, Self>, - py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let cryptography_warning = py - .import("cryptography.utils")? - .getattr(crate::intern!(py, "DeprecatedIn35"))?; - pyo3::PyErr::warn( + py: pyo3::Python<'_>, + ) -> CryptographyResult { + let public_key = slf.public_key(py)?; + Ok(sign::verify_signature_with_signature_algorithm( py, - cryptography_warning, - "This version of cryptography contains a temporary pyOpenSSL fallback path. Upgrade pyOpenSSL now.", - 1, - )?; - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr(crate::intern!(py, "backend"))?; - Ok(backend.call_method1("_csr2ossl", (slf,))?) + public_key, + &slf.raw.borrow_value().signature_alg, + slf.raw.borrow_value().signature.as_bytes(), + &asn1::write_single(&slf.raw.borrow_value().csr_info)?, + ) + .is_ok()) } } #[pyo3::prelude::pyfunction] -fn load_pem_x509_csr(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { +fn load_pem_x509_csr( + py: pyo3::Python<'_>, + data: &[u8], +) -> CryptographyResult { // 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( @@ -328,21 +259,27 @@ fn load_pem_x509_csr(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result, data: &[u8]) -> PyAsn1Result { - let raw = OwnedRawCsr::try_new(data.to_vec(), |data| asn1::parse_single(data))?; +fn load_der_x509_csr( + py: pyo3::Python<'_>, + data: pyo3::Py, +) -> CryptographyResult { + let raw = OwnedCsr::try_new(data, |data| asn1::parse_single(data.as_bytes(py)))?; let version = raw.borrow_value().csr_info.version; if version != 0 { - let x509_module = py.import("cryptography.x509")?; - return Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - x509_module - .getattr(crate::intern!(py, "InvalidVersion"))? - .call1((format!("{} is not a valid CSR version", version), version))?, - ))); + return Err(CryptographyError::from( + exceptions::InvalidVersion::new_err(( + format!("{} is not a valid CSR version", version), + version, + )), + )); } Ok(CertificateSigningRequest { @@ -357,83 +294,101 @@ fn create_x509_csr( builder: &pyo3::PyAny, private_key: &pyo3::PyAny, hash_algorithm: &pyo3::PyAny, -) -> PyAsn1Result { - let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?; - let serialization_mod = py.import("cryptography.hazmat.primitives.serialization")?; +) -> CryptographyResult { + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + )?; + let serialization_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))?; let der_encoding = serialization_mod - .getattr(crate::intern!(py, "Encoding"))? - .getattr(crate::intern!(py, "DER"))?; + .getattr(pyo3::intern!(py, "Encoding"))? + .getattr(pyo3::intern!(py, "DER"))?; let spki_format = serialization_mod - .getattr(crate::intern!(py, "PublicFormat"))? - .getattr(crate::intern!(py, "SubjectPublicKeyInfo"))?; + .getattr(pyo3::intern!(py, "PublicFormat"))? + .getattr(pyo3::intern!(py, "SubjectPublicKeyInfo"))?; let spki_bytes = private_key - .call_method0("public_key")? - .call_method1("public_bytes", (der_encoding, spki_format))? + .call_method0(pyo3::intern!(py, "public_key"))? + .call_method1( + pyo3::intern!(py, "public_bytes"), + (der_encoding, spki_format), + )? .extract::<&[u8]>()?; let mut attrs = vec![]; let ext_bytes; if let Some(exts) = x509::common::encode_extensions( py, - builder.getattr(crate::intern!(py, "_extensions"))?, + 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: x509::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ asn1::parse_single(&ext_bytes)?, ])), }) } - for py_attr in builder.getattr(crate::intern!(py, "_attributes"))?.iter()? { + for py_attr in builder.getattr(pyo3::intern!(py, "_attributes"))?.iter()? { let (py_oid, value, tag): (&pyo3::PyAny, &[u8], 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(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Attribute values must be valid utf-8.", - ))); + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Attribute values must be valid utf-8.", + ), + )); } asn1::Utf8String::TAG }; attrs.push(Attribute { type_id: oid, - values: x509::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ - x509::common::RawTlv::new(tag, value), + values: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new([ + common::RawTlv::new(tag, value), ])), }) } - let py_subject_name = builder.getattr(crate::intern!(py, "_subject_name"))?; + let py_subject_name = builder.getattr(pyo3::intern!(py, "_subject_name"))?; let csr_info = CertificationRequestInfo { version: 0, subject: x509::common::encode_name(py, py_subject_name)?, spki: asn1::parse_single(spki_bytes)?, - attributes: x509::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(attrs)), + 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, hash_algorithm, &tbs_bytes)?; - let data = asn1::write_single(&RawCsr { + let signature = x509::sign::sign_data( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + &tbs_bytes, + )?; + let data = asn1::write_single(&Csr { csr_info, signature_alg: sigalg, signature: asn1::BitString::new(signature, 0).unwrap(), })?; - // TODO: extra copy as we round-trip through a slice - load_der_x509_csr(py, &data) + load_der_x509_csr(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_wrapped(pyo3::wrap_pyfunction!(load_der_x509_csr))?; - module.add_wrapped(pyo3::wrap_pyfunction!(load_pem_x509_csr))?; - module.add_wrapped(pyo3::wrap_pyfunction!(create_x509_csr))?; + module.add_function(pyo3::wrap_pyfunction!(load_der_x509_csr, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(load_pem_x509_csr, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_x509_csr, module)?)?; module.add_class::()?; diff --git a/src/rust/src/x509/extensions.rs b/src/rust/src/x509/extensions.rs index 537106a..98d1bd6 100644 --- a/src/rust/src/x509/extensions.rs +++ b/src/rust/src/x509/extensions.rs @@ -2,27 +2,29 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{py_oid_to_oid, py_uint_to_big_endian_bytes, PyAsn1Error, PyAsn1Result}; +use crate::asn1::{py_oid_to_oid, py_uint_to_big_endian_bytes}; +use crate::error::{CryptographyError, CryptographyResult}; use crate::x509; -use crate::x509::{certificate, crl, oid, sct}; +use crate::x509::{certificate, sct}; +use cryptography_x509::{common, crl, extensions, oid}; fn encode_general_subtrees<'a>( py: pyo3::Python<'a>, subtrees: &'a pyo3::PyAny, -) -> Result>, PyAsn1Error> { +) -> 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, name?)?; - subtree_seq.push(certificate::GeneralSubtree { + subtree_seq.push(extensions::GeneralSubtree { base: gn, minimum: 0, maximum: None, }); } - Ok(Some(x509::Asn1ReadableOrWritable::new_write( + Ok(Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(subtree_seq), ))) } @@ -31,7 +33,7 @@ fn encode_general_subtrees<'a>( pub(crate) fn encode_authority_key_identifier<'a>( py: pyo3::Python<'a>, py_aki: &'a pyo3::PyAny, -) -> pyo3::PyResult> { +) -> CryptographyResult> { #[derive(pyo3::prelude::FromPyObject)] struct PyAuthorityKeyIdentifier<'a> { key_identifier: Option<&'a [u8]>, @@ -41,7 +43,7 @@ pub(crate) fn encode_authority_key_identifier<'a>( let aki = py_aki.extract::>()?; let authority_cert_issuer = if let Some(authority_cert_issuer) = aki.authority_cert_issuer { let gns = x509::common::encode_general_names(py, authority_cert_issuer)?; - Some(x509::Asn1ReadableOrWritable::new_write( + Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(gns), )) } else { @@ -54,17 +56,17 @@ pub(crate) fn encode_authority_key_identifier<'a>( } else { None }; - Ok(certificate::AuthorityKeyIdentifier { + Ok(asn1::write_single(&extensions::AuthorityKeyIdentifier { authority_cert_issuer, authority_cert_serial_number, key_identifier: aki.key_identifier, - }) + })?) } pub(crate) fn encode_distribution_points<'p>( py: pyo3::Python<'p>, py_dps: &'p pyo3::PyAny, -) -> pyo3::PyResult>> { +) -> CryptographyResult> { #[derive(pyo3::prelude::FromPyObject)] struct PyDistributionPoint<'a> { crl_issuer: Option<&'a pyo3::PyAny>, @@ -79,7 +81,7 @@ pub(crate) fn encode_distribution_points<'p>( let crl_issuer = if let Some(py_crl_issuer) = py_dp.crl_issuer { let gns = x509::common::encode_general_names(py, py_crl_issuer)?; - Some(x509::Asn1ReadableOrWritable::new_write( + Some(common::Asn1ReadableOrWritable::new_write( asn1::SequenceOfWriter::new(gns), )) } else { @@ -87,244 +89,341 @@ pub(crate) fn encode_distribution_points<'p>( }; let distribution_point = if let Some(py_full_name) = py_dp.full_name { let gns = x509::common::encode_general_names(py, py_full_name)?; - Some(certificate::DistributionPointName::FullName( - x509::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), + 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()? { name_entries.push(x509::common::encode_name_entry(py, py_name_entry?)?); } - Some(certificate::DistributionPointName::NameRelativeToCRLIssuer( - x509::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), + 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(x509::Asn1ReadableOrWritable::new_write(reasons)) + Some(common::Asn1ReadableOrWritable::new_write(reasons)) } else { None }; - dps.push(certificate::DistributionPoint { + dps.push(extensions::DistributionPoint { crl_issuer, distribution_point, reasons, }); } - Ok(dps) + Ok(asn1::write_single(&asn1::SequenceOfWriter::new(dps))?) +} + +fn encode_basic_constraints(ext: &pyo3::PyAny) -> CryptographyResult> { + #[derive(pyo3::prelude::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::PyAny) -> CryptographyResult> { + let mut bs = [0, 0]; + certificate::set_bit( + &mut bs, + 0, + ext.getattr(pyo3::intern!(py, "digital_signature"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 1, + ext.getattr(pyo3::intern!(py, "content_commitment"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 2, + ext.getattr(pyo3::intern!(py, "key_encipherment"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 3, + ext.getattr(pyo3::intern!(py, "data_encipherment"))? + .is_true()?, + ); + certificate::set_bit( + &mut bs, + 4, + ext.getattr(pyo3::intern!(py, "key_agreement"))?.is_true()?, + ); + certificate::set_bit( + &mut bs, + 5, + ext.getattr(pyo3::intern!(py, "key_cert_sign"))?.is_true()?, + ); + certificate::set_bit( + &mut bs, + 6, + ext.getattr(pyo3::intern!(py, "crl_sign"))?.is_true()?, + ); + if ext.getattr(pyo3::intern!(py, "key_agreement"))?.is_true()? { + certificate::set_bit( + &mut bs, + 7, + ext.getattr(pyo3::intern!(py, "encipher_only"))?.is_true()?, + ); + certificate::set_bit( + &mut bs, + 8, + ext.getattr(pyo3::intern!(py, "decipher_only"))?.is_true()?, + ); + } + 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::PyAny, +) -> CryptographyResult> { + let mut policy_informations = vec![]; + 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_true()? { + 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 cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { + 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_true()? { + let mut notice_numbers = vec![]; + for py_num in py_notice + .getattr(pyo3::intern!(py, "notice_numbers"))? + .iter()? + { + let bytes = py_uint_to_big_endian_bytes(ext.py(), py_num?.downcast()?)?; + notice_numbers.push(asn1::BigUint::new(bytes).unwrap()); + } + + Some(extensions::NoticeReference { + organization: extensions::DisplayText::Utf8String( + asn1::Utf8String::new( + py_notice + .getattr(pyo3::intern!(py, "organization"))? + .extract()?, + ), + ), + 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_true()? { + Some(extensions::DisplayText::Utf8String(asn1::Utf8String::new( + py_explicit_text.extract()?, + ))) + } 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::PyAny, +) -> CryptographyResult> { + let only_some_reasons = if ext + .getattr(pyo3::intern!(py, "only_some_reasons"))? + .is_true()? + { + 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_true()? { + let py_full_name = ext.getattr(pyo3::intern!(py, "full_name"))?; + let gns = x509::common::encode_general_names(ext.py(), 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_true()? { + let mut name_entries = vec![]; + for py_name_entry in ext.getattr(pyo3::intern!(py, "relative_name"))?.iter()? { + name_entries.push(x509::common::encode_name_entry(ext.py(), py_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::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::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::PyAny) -> CryptographyResult> { + let mut length = 0; + for sct in ext.iter()? { + let sct = sct?.downcast::>()?; + length += sct.borrow().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::>()?; + result.extend_from_slice(&(sct.borrow().sct_data.len() as u16).to_be_bytes()); + result.extend_from_slice(&sct.borrow().sct_data); + } + Ok(asn1::write_single(&result.as_slice())?) } pub(crate) fn encode_extension( py: pyo3::Python<'_>, oid: &asn1::ObjectIdentifier, ext: &pyo3::PyAny, -) -> PyAsn1Result>> { +) -> CryptographyResult>> { match oid { &oid::BASIC_CONSTRAINTS_OID => { - let bc = ext.extract::()?; - Ok(Some(asn1::write_single(&bc)?)) + let der = encode_basic_constraints(ext)?; + Ok(Some(der)) } &oid::SUBJECT_KEY_IDENTIFIER_OID => { let digest = ext - .getattr(crate::intern!(py, "digest"))? + .getattr(pyo3::intern!(py, "digest"))? .extract::<&[u8]>()?; Ok(Some(asn1::write_single(&digest)?)) } &oid::KEY_USAGE_OID => { - let mut bs = [0, 0]; - certificate::set_bit( - &mut bs, - 0, - ext.getattr(crate::intern!(py, "digital_signature"))? - .is_true()?, - ); - certificate::set_bit( - &mut bs, - 1, - ext.getattr(crate::intern!(py, "content_commitment"))? - .is_true()?, - ); - certificate::set_bit( - &mut bs, - 2, - ext.getattr(crate::intern!(py, "key_encipherment"))? - .is_true()?, - ); - certificate::set_bit( - &mut bs, - 3, - ext.getattr(crate::intern!(py, "data_encipherment"))? - .is_true()?, - ); - certificate::set_bit( - &mut bs, - 4, - ext.getattr(crate::intern!(py, "key_agreement"))? - .is_true()?, - ); - certificate::set_bit( - &mut bs, - 5, - ext.getattr(crate::intern!(py, "key_cert_sign"))? - .is_true()?, - ); - certificate::set_bit( - &mut bs, - 6, - ext.getattr(crate::intern!(py, "crl_sign"))?.is_true()?, - ); - if ext - .getattr(crate::intern!(py, "key_agreement"))? - .is_true()? - { - certificate::set_bit( - &mut bs, - 7, - ext.getattr(crate::intern!(py, "encipher_only"))? - .is_true()?, - ); - certificate::set_bit( - &mut bs, - 8, - ext.getattr(crate::intern!(py, "decipher_only"))? - .is_true()?, - ); - } - 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(Some(asn1::write_single(&v)?)) + let der = encode_key_usage(py, ext)?; + Ok(Some(der)) } &oid::AUTHORITY_INFORMATION_ACCESS_OID | &oid::SUBJECT_INFORMATION_ACCESS_OID => { - let ads = x509::common::encode_access_descriptions(ext.py(), ext)?; - Ok(Some(asn1::write_single(&ads)?)) + let der = x509::common::encode_access_descriptions(ext.py(), ext)?; + Ok(Some(der)) } - &oid::EXTENDED_KEY_USAGE_OID => { - let mut oids = vec![]; - for el in ext.iter()? { - let oid = py_oid_to_oid(el?)?; - oids.push(oid); - } - Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new( - oids, - ))?)) + &oid::EXTENDED_KEY_USAGE_OID | &oid::ACCEPTABLE_RESPONSES_OID => { + let der = encode_oid_sequence(ext)?; + Ok(Some(der)) } &oid::CERTIFICATE_POLICIES_OID => { - let mut policy_informations = vec![]; - for py_policy_info in ext.iter()? { - let py_policy_info = py_policy_info?; - let py_policy_qualifiers = - py_policy_info.getattr(crate::intern!(py, "policy_qualifiers"))?; - let qualifiers = if py_policy_qualifiers.is_true()? { - let mut qualifiers = vec![]; - for py_qualifier in py_policy_qualifiers.iter()? { - let py_qualifier = py_qualifier?; - let qualifier = if py_qualifier.is_instance::()? { - let cps_uri = match asn1::IA5String::new(py_qualifier.extract()?) { - Some(s) => s, - None => { - return Err(pyo3::exceptions::PyValueError::new_err( - "Qualifier must be an ASCII-string.", - ) - .into()) - } - }; - certificate::PolicyQualifierInfo { - policy_qualifier_id: (oid::CP_CPS_URI_OID).clone(), - qualifier: certificate::Qualifier::CpsUri(cps_uri), - } - } else { - let py_notice = - py_qualifier.getattr(crate::intern!(py, "notice_reference"))?; - let notice_ref = if py_notice.is_true()? { - let mut notice_numbers = vec![]; - for py_num in py_notice - .getattr(crate::intern!(py, "notice_numbers"))? - .iter()? - { - let bytes = - py_uint_to_big_endian_bytes(ext.py(), py_num?.downcast()?)?; - notice_numbers.push(asn1::BigUint::new(bytes).unwrap()); - } - - Some(certificate::NoticeReference { - organization: certificate::DisplayText::Utf8String( - asn1::Utf8String::new( - py_notice - .getattr(crate::intern!(py, "organization"))? - .extract()?, - ), - ), - notice_numbers: x509::Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(notice_numbers), - ), - }) - } else { - None - }; - let py_explicit_text = - py_qualifier.getattr(crate::intern!(py, "explicit_text"))?; - let explicit_text = if py_explicit_text.is_true()? { - Some(certificate::DisplayText::Utf8String(asn1::Utf8String::new( - py_explicit_text.extract()?, - ))) - } else { - None - }; - - certificate::PolicyQualifierInfo { - policy_qualifier_id: (oid::CP_USER_NOTICE_OID).clone(), - qualifier: certificate::Qualifier::UserNotice( - certificate::UserNotice { - notice_ref, - explicit_text, - }, - ), - } - }; - qualifiers.push(qualifier); - } - Some(x509::Asn1ReadableOrWritable::new_write( - asn1::SequenceOfWriter::new(qualifiers), - )) - } else { - None - }; - let py_policy_id = - py_policy_info.getattr(crate::intern!(py, "policy_identifier"))?; - policy_informations.push(certificate::PolicyInformation { - policy_identifier: py_oid_to_oid(py_policy_id)?, - policy_qualifiers: qualifiers, - }); - } - Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new( - policy_informations, - ))?)) + let der = encode_certificate_policies(py, ext)?; + Ok(Some(der)) } &oid::POLICY_CONSTRAINTS_OID => { - let pc = certificate::PolicyConstraints { + let pc = extensions::PolicyConstraints { require_explicit_policy: ext - .getattr(crate::intern!(py, "require_explicit_policy"))? + .getattr(pyo3::intern!(py, "require_explicit_policy"))? .extract()?, inhibit_policy_mapping: ext - .getattr(crate::intern!(py, "inhibit_policy_mapping"))? + .getattr(pyo3::intern!(py, "inhibit_policy_mapping"))? .extract()?, }; Ok(Some(asn1::write_single(&pc)?)) } &oid::NAME_CONSTRAINTS_OID => { - let permitted = ext.getattr(crate::intern!(py, "permitted_subtrees"))?; - let excluded = ext.getattr(crate::intern!(py, "excluded_subtrees"))?; - let nc = certificate::NameConstraints { + 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(), permitted)?, excluded_subtrees: encode_general_subtrees(ext.py(), excluded)?, }; @@ -332,7 +431,7 @@ pub(crate) fn encode_extension( } &oid::INHIBIT_ANY_POLICY_OID => { let intval = ext - .getattr(crate::intern!(py, "skip_certs"))? + .getattr(pyo3::intern!(py, "skip_certs"))? .downcast::()?; let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; Ok(Some(asn1::write_single( @@ -344,50 +443,33 @@ pub(crate) fn encode_extension( Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) } &oid::AUTHORITY_KEY_IDENTIFIER_OID => { - let aki = encode_authority_key_identifier(ext.py(), ext)?; - Ok(Some(asn1::write_single(&aki)?)) + let der = encode_authority_key_identifier(ext.py(), ext)?; + Ok(Some(der)) } &oid::FRESHEST_CRL_OID | &oid::CRL_DISTRIBUTION_POINTS_OID => { - let dps = encode_distribution_points(ext.py(), ext)?; - Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(dps))?)) + 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 => { - // 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(crate::intern!(py, "value"))?.extract::()?); - } - - Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(els))?)) + 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 mut length = 0; - for sct in ext.iter()? { - let sct = sct?.downcast::>()?; - length += sct.borrow().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::>()?; - result.extend_from_slice(&(sct.borrow().sct_data.len() as u16).to_be_bytes()); - result.extend_from_slice(&sct.borrow().sct_data); - } - Ok(Some(asn1::write_single(&result.as_slice())?)) + let der = encode_scts(ext)?; + Ok(Some(der)) } &oid::CRL_REASON_OID => { let value = ext .py() - .import("cryptography.hazmat.backends.openssl.decode_asn1")? - .getattr(crate::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))? - .get_item(ext.getattr(crate::intern!(py, "reason"))?)? + .import(pyo3::intern!( + py, + "cryptography.hazmat.backends.openssl.decode_asn1" + ))? + .getattr(pyo3::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))? + .get_item(ext.getattr(pyo3::intern!(py, "reason"))?)? .extract::()?; Ok(Some(asn1::write_single(&asn1::Enumerated::new(value))?)) } @@ -396,15 +478,12 @@ pub(crate) fn encode_extension( Ok(Some(asn1::write_single(&asn1::SequenceOfWriter::new(gns))?)) } &oid::INVALIDITY_DATE_OID => { - let chrono_dt = - x509::py_to_chrono(py, ext.getattr(crate::intern!(py, "invalidity_date"))?)?; - Ok(Some(asn1::write_single(&asn1::GeneralizedTime::new( - chrono_dt, - )?)?)) + let dt = x509::py_to_datetime(py, ext.getattr(pyo3::intern!(py, "invalidity_date"))?)?; + Ok(Some(asn1::write_single(&asn1::GeneralizedTime::new(dt)?)?)) } &oid::CRL_NUMBER_OID | &oid::DELTA_CRL_INDICATOR_OID => { let intval = ext - .getattr(crate::intern!(py, "crl_number"))? + .getattr(pyo3::intern!(py, "crl_number"))? .downcast::()?; let bytes = py_uint_to_big_endian_bytes(ext.py(), intval)?; Ok(Some(asn1::write_single( @@ -412,59 +491,24 @@ pub(crate) fn encode_extension( )?)) } &oid::ISSUING_DISTRIBUTION_POINT_OID => { - let only_some_reasons = if ext - .getattr(crate::intern!(py, "only_some_reasons"))? - .is_true()? - { - let py_reasons = ext.getattr(crate::intern!(py, "only_some_reasons"))?; - let reasons = certificate::encode_distribution_point_reasons(ext.py(), py_reasons)?; - Some(x509::Asn1ReadableOrWritable::new_write(reasons)) - } else { - None - }; - let distribution_point = if ext.getattr(crate::intern!(py, "full_name"))?.is_true()? { - let py_full_name = ext.getattr(crate::intern!(py, "full_name"))?; - let gns = x509::common::encode_general_names(ext.py(), py_full_name)?; - Some(certificate::DistributionPointName::FullName( - x509::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(gns)), - )) - } else if ext - .getattr(crate::intern!(py, "relative_name"))? - .is_true()? - { - let mut name_entries = vec![]; - for py_name_entry in ext.getattr(crate::intern!(py, "relative_name"))?.iter()? { - name_entries.push(x509::common::encode_name_entry(ext.py(), py_name_entry?)?); - } - Some(certificate::DistributionPointName::NameRelativeToCRLIssuer( - x509::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(name_entries)), - )) - } else { - None - }; - - let idp = crl::IssuingDistributionPoint { - distribution_point, - indirect_crl: ext.getattr(crate::intern!(py, "indirect_crl"))?.extract()?, - only_contains_attribute_certs: ext - .getattr(crate::intern!(py, "only_contains_attribute_certs"))? - .extract()?, - only_contains_ca_certs: ext - .getattr(crate::intern!(py, "only_contains_ca_certs"))? - .extract()?, - only_contains_user_certs: ext - .getattr(crate::intern!(py, "only_contains_user_certs"))? - .extract()?, - only_some_reasons, - }; - Ok(Some(asn1::write_single(&idp)?)) + let der = encode_issuing_distribution_point(py, ext)?; + Ok(Some(der)) } &oid::NONCE_OID => { let nonce = ext - .getattr(crate::intern!(py, "nonce"))? + .getattr(pyo3::intern!(py, "nonce"))? .extract::<&[u8]>()?; Ok(Some(asn1::write_single(&nonce)?)) } + &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), } } diff --git a/src/rust/src/x509/mod.rs b/src/rust/src/x509/mod.rs index ee6c8d7..c43bf90 100644 --- a/src/rust/src/x509/mod.rs +++ b/src/rust/src/x509/mod.rs @@ -7,16 +7,13 @@ pub(crate) mod common; pub(crate) mod crl; pub(crate) mod csr; pub(crate) mod extensions; -mod ocsp; +pub(crate) mod ocsp; pub(crate) mod ocsp_req; pub(crate) mod ocsp_resp; -mod oid; pub(crate) mod sct; pub(crate) mod sign; -pub(crate) use certificate::Certificate; pub(crate) use common::{ - chrono_to_py, find_in_pem, parse_and_cache_extensions, parse_general_name, parse_general_names, - parse_name, parse_rdn, py_to_chrono, AlgorithmIdentifier, Asn1ReadableOrWritable, - AttributeTypeValue, Extensions, GeneralName, Name, Time, + datetime_to_py, find_in_pem, parse_and_cache_extensions, parse_general_name, + parse_general_names, parse_name, parse_rdn, py_to_datetime, }; diff --git a/src/rust/src/x509/ocsp.rs b/src/rust/src/x509/ocsp.rs index 67bdca0..05ea096 100644 --- a/src/rust/src/x509/ocsp.rs +++ b/src/rust/src/x509/ocsp.rs @@ -2,74 +2,115 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::PyAsn1Result; +use crate::error::CryptographyResult; use crate::x509; -use crate::x509::oid; +use crate::x509::certificate::Certificate; +use cryptography_x509::common; +use cryptography_x509::ocsp_req::CertID; use once_cell::sync::Lazy; use std::collections::HashMap; -pub(crate) static OIDS_TO_HASH: Lazy> = Lazy::new(|| { +pub(crate) static ALGORITHM_PARAMETERS_TO_HASH: Lazy< + HashMap, &str>, +> = 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(common::AlgorithmParameters::Sha1(Some(())), "SHA1"); + h.insert(common::AlgorithmParameters::Sha224(Some(())), "SHA224"); + h.insert(common::AlgorithmParameters::Sha256(Some(())), "SHA256"); + h.insert(common::AlgorithmParameters::Sha384(Some(())), "SHA384"); + h.insert(common::AlgorithmParameters::Sha512(Some(())), "SHA512"); h }); -pub(crate) static HASH_NAME_TO_OIDS: Lazy> = - Lazy::new(|| { - let mut h = HashMap::new(); - h.insert("sha1", &oid::SHA1_OID); - h.insert("sha224", &oid::SHA224_OID); - h.insert("sha256", &oid::SHA256_OID); - h.insert("sha384", &oid::SHA384_OID); - h.insert("sha512", &oid::SHA512_OID); - h - }); -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -pub(crate) struct CertID<'a> { - pub(crate) hash_algorithm: x509::AlgorithmIdentifier<'a>, - pub(crate) issuer_name_hash: &'a [u8], - pub(crate) issuer_key_hash: &'a [u8], - pub(crate) serial_number: asn1::BigInt<'a>, -} +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 +}); -impl CertID<'_> { - pub(crate) fn new<'p>( - py: pyo3::Python<'p>, - cert: &'p x509::Certificate, - issuer: &'p x509::Certificate, - hash_algorithm: &'p pyo3::PyAny, - ) -> PyAsn1Result> { - let issuer_der = asn1::write_single(&cert.raw.borrow_value_public().tbs_cert.issuer)?; - let issuer_name_hash = hash_data(py, hash_algorithm, &issuer_der)?; - let issuer_key_hash = hash_data( - py, - hash_algorithm, - issuer - .raw - .borrow_value_public() - .tbs_cert - .spki - .subject_public_key - .as_bytes(), - )?; +pub(crate) fn certid_new<'p>( + py: pyo3::Python<'p>, + cert: &'p Certificate, + issuer: &'p Certificate, + hash_algorithm: &'p pyo3::PyAny, +) -> CryptographyResult> { + let issuer_der = asn1::write_single(&cert.raw.borrow_value_public().tbs_cert.issuer)?; + let issuer_name_hash = hash_data(py, hash_algorithm, &issuer_der)?; + let issuer_key_hash = hash_data( + py, + hash_algorithm, + issuer + .raw + .borrow_value_public() + .tbs_cert + .spki + .subject_public_key + .as_bytes(), + )?; + + Ok(CertID { + hash_algorithm: x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[hash_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?] + .clone(), + issuer_name_hash, + issuer_key_hash, + serial_number: cert.raw.borrow_value_public().tbs_cert.serial, + }) +} - Ok(CertID { - hash_algorithm: x509::AlgorithmIdentifier { - oid: HASH_NAME_TO_OIDS[hash_algorithm - .getattr(crate::intern!(py, "name"))? - .extract::<&str>()?] - .clone(), - params: Some(*x509::sign::NULL_TLV), - }, - issuer_name_hash, - issuer_key_hash, - serial_number: cert.raw.borrow_value_public().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: &'p pyo3::PyAny, +) -> CryptographyResult> { + Ok(CertID { + hash_algorithm: x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS[hash_algorithm + .getattr(pyo3::intern!(py, "name"))? + .extract::<&str>()?] + .clone(), + issuer_name_hash, + issuer_key_hash, + serial_number, + }) } pub(crate) fn hash_data<'p>( @@ -78,9 +119,9 @@ pub(crate) fn hash_data<'p>( data: &[u8], ) -> pyo3::PyResult<&'p [u8]> { let hash = py - .import("cryptography.hazmat.primitives.hashes")? - .getattr(crate::intern!(py, "Hash"))? + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "Hash"))? .call1((py_hash_alg,))?; - hash.call_method1("update", (data,))?; - hash.call_method0("finalize")?.extract() + hash.call_method1(pyo3::intern!(py, "update"), (data,))?; + hash.call_method0(pyo3::intern!(py, "finalize"))?.extract() } diff --git a/src/rust/src/x509/ocsp_req.rs b/src/rust/src/x509/ocsp_req.rs index 92fe96f..1571524 100644 --- a/src/rust/src/x509/ocsp_req.rs +++ b/src/rust/src/x509/ocsp_req.rs @@ -2,22 +2,27 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{big_byte_slice_to_py_int, PyAsn1Error, PyAsn1Result}; -use crate::x509; -use crate::x509::{extensions, ocsp, oid}; -use std::sync::Arc; +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, x509}; +use cryptography_x509::{common, ocsp_req, oid}; +use pyo3::IntoPy; #[ouroboros::self_referencing] -struct OwnedRawOCSPRequest { - data: Arc<[u8]>, +struct OwnedOCSPRequest { + data: pyo3::Py, #[borrows(data)] #[covariant] - value: RawOCSPRequest<'this>, + value: ocsp_req::OCSPRequest<'this>, } #[pyo3::prelude::pyfunction] -fn load_der_ocsp_request(_py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { - let raw = OwnedRawOCSPRequest::try_new(Arc::from(data), |data| asn1::parse_single(data))?; +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_value() @@ -27,7 +32,7 @@ fn load_der_ocsp_request(_py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result, data: &[u8]) -> PyAsn1Result, } impl OCSPRequest { - fn cert_id(&self) -> ocsp::CertID<'_> { + fn cert_id(&self) -> ocsp_req::CertID<'_> { self.raw .borrow_value() .tbs_request @@ -74,42 +79,45 @@ impl OCSPRequest { } #[getter] - fn hash_algorithm<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { + fn hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { let cert_id = self.cert_id(); - let hashes = py.import("cryptography.hazmat.primitives.hashes")?; - match ocsp::OIDS_TO_HASH.get(&cert_id.hash_algorithm.oid) { - Some(alg_name) => Ok(hashes.getattr(alg_name)?.call0()?), - None => { - let exceptions = py.import("cryptography.exceptions")?; - Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - exceptions - .getattr(crate::intern!(py, "UnsupportedAlgorithm"))? - .call1((format!( - "Signature algorithm OID: {} not recognized", - cert_id.hash_algorithm.oid - ),))?, - ))) - } + let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; + match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&cert_id.hash_algorithm.params) { + Some(alg_name) => Ok(hashes.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<&'p pyo3::PyAny, PyAsn1Error> { + fn serial_number<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { let bytes = self.cert_id().serial_number.as_bytes(); Ok(big_byte_slice_to_py_int(py, bytes)?) } #[getter] fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let x509_module = py.import("cryptography.x509")?; + let tbs_request = &self.raw.borrow_value().tbs_request; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, &mut self.cached_extensions, - &self.raw.borrow_value().tbs_request.request_extensions, + &tbs_request.raw_request_extensions, |oid, value| { - match oid { - &oid::NONCE_OID => { + match *oid { + 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 @@ -117,7 +125,23 @@ impl OCSPRequest { // the nonce. So we try parsing as a TLV and fall back to just using // the raw value. let nonce = asn1::parse_single::<&[u8]>(value).unwrap_or(value); - Ok(Some(x509_module.call_method1("OCSPNonce", (nonce,))?)) + Ok(Some( + x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?, + )) + } + oid::ACCEPTABLE_RESPONSES_OID => { + let oids = asn1::parse_single::< + asn1::SequenceOf<'_, asn1::ObjectIdentifier>, + >(value)?; + let py_oids = pyo3::types::PyList::empty(py); + for oid in oids { + py_oids.append(oid_to_py_oid(py, &oid)?)?; + } + + Ok(Some(x509_module.call_method1( + pyo3::intern!(py, "OCSPAcceptableResponses"), + (py_oids,), + )?)) } _ => Ok(None), } @@ -129,12 +153,15 @@ impl OCSPRequest { &self, py: pyo3::Python<'p>, encoding: &pyo3::PyAny, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let der = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "Encoding"))? - .getattr(crate::intern!(py, "DER"))?; - if encoding != der { + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "Encoding"))? + .getattr(pyo3::intern!(py, "DER"))?; + if !encoding.is(der) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) @@ -145,75 +172,75 @@ impl OCSPRequest { } } -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct RawOCSPRequest<'a> { - 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)] - optional_signature: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct TBSRequest<'a> { - #[explicit(0)] - #[default(0)] - version: u8, - #[explicit(1)] - requestor_name: Option>, - request_list: x509::Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, Request<'a>>, - asn1::SequenceOfWriter<'a, Request<'a>>, - >, - #[explicit(2)] - request_extensions: Option>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct Request<'a> { - req_cert: ocsp::CertID<'a>, - #[explicit(0)] - single_request_extensions: Option>, -} - #[pyo3::prelude::pyfunction] -fn create_ocsp_request(py: pyo3::Python<'_>, builder: &pyo3::PyAny) -> PyAsn1Result { +fn create_ocsp_request( + py: pyo3::Python<'_>, + builder: &pyo3::PyAny, +) -> CryptographyResult { + let builder_request = builder.getattr(pyo3::intern!(py, "_request"))?; + + // Declare outside the if-block so the lifetimes are right. let (py_cert, py_issuer, py_hash): ( - pyo3::PyRef<'_, x509::Certificate>, - pyo3::PyRef<'_, x509::Certificate>, + pyo3::PyRef<'_, x509::certificate::Certificate>, + pyo3::PyRef<'_, x509::certificate::Certificate>, &pyo3::PyAny, - ) = builder.getattr(crate::intern!(py, "_request"))?.extract()?; + ); + let req_cert = if !builder_request.is_none() { + let tuple = builder_request.extract::<( + pyo3::PyRef<'_, x509::certificate::Certificate>, + pyo3::PyRef<'_, x509::certificate::Certificate>, + &pyo3::PyAny, + )>()?; + py_cert = tuple.0; + py_issuer = tuple.1; + py_hash = tuple.2; + ocsp::certid_new(py, &py_cert, &py_issuer, py_hash)? + } else { + let (issuer_name_hash, issuer_key_hash, py_serial, py_hash): ( + &[u8], + &[u8], + &pyo3::types::PyLong, + &pyo3::PyAny, + ) = builder + .getattr(pyo3::intern!(py, "_request_hash"))? + .extract()?; + let serial_number = asn1::BigInt::new(py_uint_to_big_endian_bytes(py, py_serial)?).unwrap(); + ocsp::certid_new_from_hash( + py, + issuer_name_hash, + issuer_key_hash, + serial_number, + py_hash, + )? + }; let extensions = x509::common::encode_extensions( py, - builder.getattr(crate::intern!(py, "_extensions"))?, + builder.getattr(pyo3::intern!(py, "_extensions"))?, extensions::encode_extension, )?; - let reqs = [Request { - req_cert: ocsp::CertID::new(py, &py_cert, &py_issuer, py_hash)?, + let reqs = [ocsp_req::Request { + req_cert, single_request_extensions: None, }]; - let ocsp_req = RawOCSPRequest { - tbs_request: TBSRequest { + let ocsp_req = ocsp_req::OCSPRequest { + tbs_request: ocsp_req::TBSRequest { version: 0, requestor_name: None, - request_list: x509::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( + request_list: common::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( &reqs, )), - request_extensions: extensions, + raw_request_extensions: extensions, }, optional_signature: None, }; let data = asn1::write_single(&ocsp_req)?; - // TODO: extra copy as we round-trip through a slice - load_der_ocsp_request(py, &data) + load_der_ocsp_request(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_wrapped(pyo3::wrap_pyfunction!(load_der_ocsp_request))?; - module.add_wrapped(pyo3::wrap_pyfunction!(create_ocsp_request))?; + module.add_function(pyo3::wrap_pyfunction!(load_der_ocsp_request, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_ocsp_request, module)?)?; Ok(()) } diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs index 22d2940..721e031 100644 --- a/src/rust/src/x509/ocsp_resp.rs +++ b/src/rust/src/x509/ocsp_resp.rs @@ -2,45 +2,55 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::{big_byte_slice_to_py_int, oid_to_py_oid, PyAsn1Error, PyAsn1Result}; -use crate::x509; -use crate::x509::{certificate, crl, extensions, ocsp, oid, py_to_chrono, sct}; -use chrono::Timelike; +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, x509}; +use cryptography_x509::ocsp_resp::SingleResponse; +use cryptography_x509::{common, ocsp_resp, oid}; +use pyo3::IntoPy; use std::sync::Arc; const BASIC_RESPONSE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 1); #[pyo3::prelude::pyfunction] -fn load_der_ocsp_response(_py: pyo3::Python<'_>, data: &[u8]) -> Result { - let raw = OwnedRawOCSPResponse::try_new( - Arc::from(data), - |data| Ok(asn1::parse_single(data)?), - |_data, response| match response.response_status.value() { - SUCCESSFUL_RESPONSE => match response.response_bytes { - Some(ref bytes) => { - if bytes.response_type == BASIC_RESPONSE_OID { - Ok(asn1::parse_single(bytes.response)?) - } else { - Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( +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_value(); + 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 => Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Successful OCSP response does not contain a BasicResponse", - ))), - }, - MALFORMED_REQUEST_RESPOSNE - | INTERNAL_ERROR_RESPONSE - | TRY_LATER_RESPONSE - | SIG_REQUIRED_RESPONSE - | UNAUTHORIZED_RESPONSE => Ok(None), - _ => Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "OCSP response has an unknown status code", - ))), + } + None => { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "Successful OCSP response does not contain a BasicResponse", + ), + )) + } }, - )?; - + MALFORMED_REQUEST_RESPOSNE + | 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: None, @@ -49,29 +59,25 @@ fn load_der_ocsp_response(_py: pyo3::Python<'_>, data: &[u8]) -> Result, +struct OwnedOCSPResponse { + data: pyo3::Py, #[borrows(data)] #[covariant] - value: RawOCSPResponse<'this>, - - #[borrows(data, value)] - #[covariant] - basic_response: Option>, + value: ocsp_resp::OCSPResponse<'this>, } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPResponse { - raw: Arc, + raw: Arc, cached_extensions: Option, cached_single_extensions: Option, } impl OCSPResponse { - fn requires_successful_response(&self) -> pyo3::PyResult<&BasicOCSPResponse<'_>> { - match self.raw.borrow_basic_response() { - Some(b) => Ok(b), + fn requires_successful_response(&self) -> pyo3::PyResult<&ocsp_resp::BasicOCSPResponse<'_>> { + match self.raw.borrow_value().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", )), @@ -90,14 +96,17 @@ const UNAUTHORIZED_RESPONSE: u32 = 6; #[pyo3::prelude::pymethods] impl OCSPResponse { #[getter] - fn responses(&self) -> Result { + fn responses(&self) -> Result { self.requires_successful_response()?; Ok(OCSPResponseIterator { contents: OwnedOCSPResponseIteratorData::try_new(Arc::clone(&self.raw), |v| { Ok::<_, ()>( - v.borrow_basic_response() + v.borrow_value() + .response_bytes .as_ref() .unwrap() + .response + .get() .tbs_response_data .responses .unwrap_read() @@ -125,8 +134,8 @@ impl OCSPResponse { assert_eq!(status, UNAUTHORIZED_RESPONSE); "UNAUTHORIZED" }; - py.import("cryptography.x509.ocsp")? - .getattr(crate::intern!(py, "OCSPResponseStatus"))? + py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))? + .getattr(pyo3::intern!(py, "OCSPResponseStatus"))? .getattr(attr) } @@ -134,8 +143,8 @@ impl OCSPResponse { fn responder_name<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; match resp.tbs_response_data.responder_id { - ResponderId::ByName(ref name) => Ok(x509::parse_name(py, name)?), - ResponderId::ByKey(_) => Ok(py.None().into_ref(py)), + ocsp_resp::ResponderId::ByName(ref name) => Ok(x509::parse_name(py, name)?), + ocsp_resp::ResponderId::ByKey(_) => Ok(py.None().into_ref(py)), } } @@ -143,43 +152,46 @@ impl OCSPResponse { fn responder_key_hash<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; match resp.tbs_response_data.responder_id { - ResponderId::ByKey(key_hash) => Ok(pyo3::types::PyBytes::new(py, key_hash).as_ref()), - ResponderId::ByName(_) => Ok(py.None().into_ref(py)), + ocsp_resp::ResponderId::ByKey(key_hash) => { + Ok(pyo3::types::PyBytes::new(py, key_hash).as_ref()) + } + ocsp_resp::ResponderId::ByName(_) => Ok(py.None().into_ref(py)), } } #[getter] fn produced_at<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - x509::chrono_to_py(py, resp.tbs_response_data.produced_at.as_chrono()) + x509::datetime_to_py(py, resp.tbs_response_data.produced_at.as_datetime()) } #[getter] fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - oid_to_py_oid(py, &resp.signature_algorithm.oid) + oid_to_py_oid(py, resp.signature_algorithm.oid()) } #[getter] fn signature_hash_algorithm<'p>( &self, py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { + ) -> Result<&'p pyo3::PyAny, CryptographyError> { let sig_oids_to_hash = py - .import("cryptography.hazmat._oid")? - .getattr(crate::intern!(py, "_SIG_OIDS_TO_HASH"))?; + .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? + .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?; let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?); match hash_alg { Ok(data) => Ok(data), Err(_) => { let exc_messsage = format!( "Signature algorithm OID: {} not recognized", - self.requires_successful_response()?.signature_algorithm.oid + self.requires_successful_response()? + .signature_algorithm + .oid() ); - Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - py.import("cryptography.exceptions")? - .call_method1("UnsupportedAlgorithm", (exc_messsage,))?, - ))) + Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(exc_messsage), + )) } } } @@ -194,14 +206,14 @@ impl OCSPResponse { fn tbs_response_bytes<'p>( &self, py: pyo3::Python<'p>, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let resp = self.requires_successful_response()?; let result = asn1::write_single(&resp.tbs_response_data)?; Ok(pyo3::types::PyBytes::new(py, &result)) } #[getter] - fn certificates<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { + fn certificates<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, CryptographyError> { let resp = self.requires_successful_response()?; let py_certs = pyo3::types::PyList::empty(py); let certs = match &resp.certs { @@ -210,10 +222,12 @@ impl OCSPResponse { }; for i in 0..certs.len() { // TODO: O(n^2), don't have too many certificates! - let raw_cert = map_arc_data_ocsp_response(&self.raw, |_data, _resp, basic_response| { - basic_response + 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() @@ -224,7 +238,7 @@ impl OCSPResponse { }); py_certs.append(pyo3::PyCell::new( py, - x509::Certificate { + x509::certificate::Certificate { raw: raw_cert, cached_extensions: None, }, @@ -236,79 +250,88 @@ impl OCSPResponse { #[getter] fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_response()?; - single_resp.py_serial_number(py) + let single_resp = single_response(resp)?; + singleresp_py_serial_number(&single_resp, py) } #[getter] - fn issuer_key_hash(&self) -> Result<&[u8], PyAsn1Error> { + fn issuer_key_hash(&self) -> Result<&[u8], CryptographyError> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_response()?; + let single_resp = single_response(resp)?; Ok(single_resp.cert_id.issuer_key_hash) } #[getter] - fn issuer_name_hash(&self) -> Result<&[u8], PyAsn1Error> { + fn issuer_name_hash(&self) -> Result<&[u8], CryptographyError> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_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<&'p pyo3::PyAny, PyAsn1Error> { + fn hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, CryptographyError> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_response()?; - single_resp.py_hash_algorithm(py) + 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<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - resp.single_response()?.py_certificate_status(py) + 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<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_response()?; - single_resp.py_revocation_time(py) + let single_resp = single_response(resp)?; + singleresp_py_revocation_time(&single_resp, py) } #[getter] - fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> PyAsn1Result<&'p pyo3::PyAny> { + fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_response()?; - single_resp.py_revocation_reason(py) + 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<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_response()?; - single_resp.py_this_update(py) + let single_resp = single_response(resp)?; + singleresp_py_this_update(&single_resp, py) } #[getter] fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let resp = self.requires_successful_response()?; - let single_resp = resp.single_response()?; - single_resp.py_next_update(py) + let single_resp = single_response(resp)?; + singleresp_py_next_update(&single_resp, py) } #[getter] fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { self.requires_successful_response()?; - let x509_module = py.import("cryptography.x509")?; + + let response_data = &self + .raw + .borrow_value() + .response_bytes + .as_ref() + .unwrap() + .response + .get() + .tbs_response_data; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, &mut self.cached_extensions, - &self - .raw - .borrow_basic_response() - .as_ref() - .unwrap() - .tbs_response_data - .response_extensions, + &response_data.raw_response_extensions, |oid, ext_data| { match oid { &oid::NONCE_OID => { @@ -319,7 +342,9 @@ impl OCSPResponse { // the nonce. So we try parsing as a TLV and fall back to just using // the raw value. let nonce = asn1::parse_single::<&[u8]>(ext_data).unwrap_or(ext_data); - Ok(Some(x509_module.call_method1("OCSPNonce", (nonce,))?)) + Ok(Some( + x509_module.call_method1(pyo3::intern!(py, "OCSPNonce"), (nonce,))?, + )) } _ => Ok(None), } @@ -330,24 +355,28 @@ impl OCSPResponse { #[getter] fn single_extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { self.requires_successful_response()?; - let single_resp = self - .raw - .borrow_basic_response() - .as_ref() - .unwrap() - .single_response()?; - let x509_module = py.import("cryptography.x509")?; + let single_resp = single_response( + self.raw + .borrow_value() + .response_bytes + .as_ref() + .unwrap() + .response + .get(), + )?; + + let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?; x509::parse_and_cache_extensions( py, &mut self.cached_single_extensions, - &single_resp.single_extensions, + &single_resp.raw_single_extensions, |oid, ext_data| match oid { &oid::SIGNED_CERTIFICATE_TIMESTAMPS_OID => { let contents = asn1::parse_single::<&[u8]>(ext_data)?; let scts = sct::parse_scts(py, contents, sct::LogEntryType::Certificate)?; Ok(Some( x509_module - .getattr(crate::intern!(py, "SignedCertificateTimestamps"))? + .getattr(pyo3::intern!(py, "SignedCertificateTimestamps"))? .call1((scts,))?, )) } @@ -360,12 +389,15 @@ impl OCSPResponse { &self, py: pyo3::Python<'p>, encoding: &pyo3::PyAny, - ) -> PyAsn1Result<&'p pyo3::types::PyBytes> { + ) -> CryptographyResult<&'p pyo3::types::PyBytes> { let der = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr(crate::intern!(py, "Encoding"))? - .getattr(crate::intern!(py, "DER"))?; - if encoding != der { + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.serialization" + ))? + .getattr(pyo3::intern!(py, "Encoding"))? + .getattr(pyo3::intern!(py, "DER"))?; + if !encoding.is(der) { return Err(pyo3::exceptions::PyValueError::new_err( "The only allowed encoding value is Encoding.DER", ) @@ -379,421 +411,361 @@ impl OCSPResponse { // Open-coded implementation of the API discussed in // https://github.com/joshua-maros/ouroboros/issues/38 fn map_arc_data_ocsp_response( - it: &OwnedRawOCSPResponse, + py: pyo3::Python<'_>, + it: &OwnedOCSPResponse, f: impl for<'this> FnOnce( &'this [u8], - &RawOCSPResponse<'this>, - &Option>, - ) -> certificate::RawCertificate<'this>, -) -> certificate::OwnedRawCertificate { - certificate::OwnedRawCertificate::new_public(Arc::clone(it.borrow_data()), |inner_it| { + &ocsp_resp::OCSPResponse<'this>, + ) -> cryptography_x509::certificate::Certificate<'this>, +) -> certificate::OwnedCertificate { + certificate::OwnedCertificate::new_public(it.borrow_data().clone_ref(py), |inner_it| { it.with(|value| { - f( - inner_it, - unsafe { std::mem::transmute(value.value) }, - unsafe { std::mem::transmute(value.basic_response) }, - ) + f(inner_it.as_bytes(py), unsafe { + std::mem::transmute(value.value) + }) }) }) } fn try_map_arc_data_mut_ocsp_response_iterator( it: &mut OwnedOCSPResponseIteratorData, f: impl for<'this> FnOnce( - &'this OwnedRawOCSPResponse, - &mut asn1::SequenceOf<'this, SingleResponse<'this>>, - ) -> Result, E>, + &'this OwnedOCSPResponse, + &mut asn1::SequenceOf<'this, ocsp_resp::SingleResponse<'this>>, + ) -> Result, E>, ) -> Result { OwnedSingleResponse::try_new(Arc::clone(it.borrow_data()), |inner_it| { it.with_value_mut(|value| f(inner_it, unsafe { std::mem::transmute(value) })) }) } -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct RawOCSPResponse<'a> { - response_status: asn1::Enumerated, - #[explicit(0)] - response_bytes: Option>, -} +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(); -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct ResponseBytes<'a> { - response_type: asn1::ObjectIdentifier, - response: &'a [u8], -} - -type OCSPCerts<'a> = Option< - x509::Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, certificate::RawCertificate<'a>>, - asn1::SequenceOfWriter< - 'a, - certificate::RawCertificate<'a>, - Vec>, - >, - >, ->; - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct BasicOCSPResponse<'a> { - tbs_response_data: ResponseData<'a>, - signature_algorithm: x509::AlgorithmIdentifier<'a>, - signature: asn1::BitString<'a>, - #[explicit(0)] - certs: OCSPCerts<'a>, -} - -impl BasicOCSPResponse<'_> { - fn single_response(&self) -> Result, PyAsn1Error> { - let responses = self.tbs_response_data.responses.unwrap_read(); - let num_responses = responses.len(); - - if num_responses != 1 { - return Err(PyAsn1Error::from( - pyo3::exceptions::PyValueError::new_err(format!( - "OCSP response contains {} SINGLERESP structures. Use .response_iter to iterate through them", - num_responses - )) - )); - } - - Ok(responses.clone().next().unwrap()) + if num_responses != 1 { + return Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err(format!( + "OCSP response contains {} SINGLERESP structures. Use .response_iter to iterate through them", + num_responses + )) + )); } -} -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct ResponseData<'a> { - #[explicit(0)] - #[default(0)] - version: u8, - responder_id: ResponderId<'a>, - produced_at: asn1::GeneralizedTime, - responses: x509::Asn1ReadableOrWritable< - 'a, - asn1::SequenceOf<'a, SingleResponse<'a>>, - asn1::SequenceOfWriter<'a, SingleResponse<'a>, Vec>>, - >, - #[explicit(1)] - response_extensions: Option>, + Ok(responses.clone().next().unwrap()) } -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -enum ResponderId<'a> { - #[explicit(1)] - ByName(x509::Name<'a>), - #[explicit(2)] - ByKey(&'a [u8]), +fn singleresp_py_serial_number<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + big_byte_slice_to_py_int(py, resp.cert_id.serial_number.as_bytes()) } -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct SingleResponse<'a> { - cert_id: ocsp::CertID<'a>, - cert_status: CertStatus, - this_update: asn1::GeneralizedTime, - #[explicit(0)] - next_update: Option, - #[explicit(1)] - single_extensions: Option>, +fn singleresp_py_certificate_status<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + 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"), + }; + py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))? + .getattr(pyo3::intern!(py, "OCSPCertStatus"))? + .getattr(attr) } -impl SingleResponse<'_> { - fn py_serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - big_byte_slice_to_py_int(py, self.cert_id.serial_number.as_bytes()) - } - - fn py_certificate_status<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let attr = match self.cert_status { - CertStatus::Good(_) => "GOOD", - CertStatus::Revoked(_) => "REVOKED", - CertStatus::Unknown(_) => "UNKNOWN", - }; - py.import("cryptography.x509.ocsp")? - .getattr(crate::intern!(py, "OCSPCertStatus"))? - .getattr(attr) +fn singleresp_py_hash_algorithm<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> Result<&'p pyo3::PyAny, CryptographyError> { + let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; + match ocsp::ALGORITHM_PARAMETERS_TO_HASH.get(&resp.cert_id.hash_algorithm.params) { + Some(alg_name) => Ok(hashes.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 py_hash_algorithm<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let hashes = py.import("cryptography.hazmat.primitives.hashes")?; - match ocsp::OIDS_TO_HASH.get(&self.cert_id.hash_algorithm.oid) { - Some(alg_name) => Ok(hashes.getattr(alg_name)?.call0()?), - None => { - let exceptions = py.import("cryptography.exceptions")?; - Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - exceptions - .getattr(crate::intern!(py, "UnsupportedAlgorithm"))? - .call1((format!( - "Signature algorithm OID: {} not recognized", - self.cert_id.hash_algorithm.oid - ),))?, - ))) - } - } - } +fn singleresp_py_this_update<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + x509::datetime_to_py(py, resp.this_update.as_datetime()) +} - fn py_this_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - x509::chrono_to_py(py, self.this_update.as_chrono()) +fn singleresp_py_next_update<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + match &resp.next_update { + Some(v) => x509::datetime_to_py(py, v.as_datetime()), + None => Ok(py.None().into_ref(py)), } +} - fn py_next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - match &self.next_update { - Some(v) => x509::chrono_to_py(py, v.as_chrono()), +fn singleresp_py_revocation_reason<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> CryptographyResult<&'p pyo3::PyAny> { + match &resp.cert_status { + ocsp_resp::CertStatus::Revoked(revoked_info) => match revoked_info.revocation_reason { + Some(ref v) => crl::parse_crl_reason_flags(py, v), None => Ok(py.None().into_ref(py)), + }, + ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { + Ok(py.None().into_ref(py)) } } +} - fn py_revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> PyAsn1Result<&'p pyo3::PyAny> { - match &self.cert_status { - CertStatus::Revoked(revoked_info) => match revoked_info.revocation_reason { - Some(ref v) => crl::parse_crl_reason_flags(py, v), - None => Ok(py.None().into_ref(py)), - }, - CertStatus::Good(_) | CertStatus::Unknown(_) => Ok(py.None().into_ref(py)), +fn singleresp_py_revocation_time<'p>( + resp: &ocsp_resp::SingleResponse<'_>, + py: pyo3::Python<'p>, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + match &resp.cert_status { + ocsp_resp::CertStatus::Revoked(revoked_info) => { + x509::datetime_to_py(py, revoked_info.revocation_time.as_datetime()) } - } - - fn py_revocation_time<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - match &self.cert_status { - CertStatus::Revoked(revoked_info) => { - x509::chrono_to_py(py, revoked_info.revocation_time.as_chrono()) - } - CertStatus::Good(_) | CertStatus::Unknown(_) => Ok(py.None().into_ref(py)), + ocsp_resp::CertStatus::Good(_) | ocsp_resp::CertStatus::Unknown(_) => { + Ok(py.None().into_ref(py)) } } } -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -enum CertStatus { - #[implicit(0)] - Good(()), - #[implicit(1)] - Revoked(RevokedInfo), - #[implicit(2)] - Unknown(()), -} +#[pyo3::prelude::pyfunction] +fn create_ocsp_response( + py: pyo3::Python<'_>, + status: &pyo3::PyAny, + builder: &pyo3::PyAny, + private_key: &pyo3::PyAny, + hash_algorithm: &pyo3::PyAny, +) -> CryptographyResult { + let response_status = status + .getattr(pyo3::intern!(py, "value"))? + .extract::()?; -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct RevokedInfo { - revocation_time: asn1::GeneralizedTime, - #[explicit(0)] - revocation_reason: Option, -} + let py_cert: pyo3::PyRef<'_, x509::certificate::Certificate>; + let py_issuer: pyo3::PyRef<'_, x509::certificate::Certificate>; + let borrowed_cert; + let py_certs: Option>>; + let response_bytes = if response_status == SUCCESSFUL_RESPONSE { + let ocsp_mod = py.import(pyo3::intern!(py, "cryptography.x509.ocsp"))?; -fn create_ocsp_basic_response<'p>( - py: pyo3::Python<'p>, - builder: &'p pyo3::PyAny, - private_key: &'p pyo3::PyAny, - hash_algorithm: &'p pyo3::PyAny, -) -> PyAsn1Result> { - let ocsp_mod = py.import("cryptography.x509.ocsp")?; - - let py_single_resp = builder.getattr(crate::intern!(py, "_response"))?; - let py_cert: pyo3::PyRef<'_, x509::Certificate> = py_single_resp - .getattr(crate::intern!(py, "_cert"))? - .extract()?; - let py_issuer: pyo3::PyRef<'_, x509::Certificate> = py_single_resp - .getattr(crate::intern!(py, "_issuer"))? - .extract()?; - let py_cert_hash_algorithm = py_single_resp.getattr(crate::intern!(py, "_algorithm"))?; - let (responder_cert, responder_encoding): (&pyo3::PyCell, &pyo3::PyAny) = - builder - .getattr(crate::intern!(py, "_responder_id"))? + 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::PyCell, + &pyo3::PyAny, + ) = builder + .getattr(pyo3::intern!(py, "_responder_id"))? .extract()?; - let py_cert_status = py_single_resp.getattr(crate::intern!(py, "_cert_status"))?; - let cert_status = if py_cert_status - == ocsp_mod - .getattr(crate::intern!(py, "OCSPCertStatus"))? - .getattr(crate::intern!(py, "GOOD"))? - { - CertStatus::Good(()) - } else if py_cert_status - == ocsp_mod - .getattr(crate::intern!(py, "OCSPCertStatus"))? - .getattr(crate::intern!(py, "UNKNOWN"))? - { - CertStatus::Unknown(()) - } else { - let revocation_reason = if !py_single_resp - .getattr(crate::intern!(py, "_revocation_reason"))? + let py_cert_status = py_single_resp.getattr(pyo3::intern!(py, "_cert_status"))?; + let cert_status = if py_cert_status.is(ocsp_mod + .getattr(pyo3::intern!(py, "OCSPCertStatus"))? + .getattr(pyo3::intern!(py, "GOOD"))?) + { + ocsp_resp::CertStatus::Good(()) + } else if py_cert_status.is(ocsp_mod + .getattr(pyo3::intern!(py, "OCSPCertStatus"))? + .getattr(pyo3::intern!(py, "UNKNOWN"))?) + { + ocsp_resp::CertStatus::Unknown(()) + } else { + let revocation_reason = if !py_single_resp + .getattr(pyo3::intern!(py, "_revocation_reason"))? + .is_none() + { + let value = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.backends.openssl.decode_asn1" + ))? + .getattr(pyo3::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))? + .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 value = py - .import("cryptography.hazmat.backends.openssl.decode_asn1")? - .getattr(crate::intern!(py, "_CRL_ENTRY_REASON_ENUM_TO_CODE"))? - .get_item(py_single_resp.getattr(crate::intern!(py, "_revocation_reason"))?)? - .extract::()?; - Some(asn1::Enumerated::new(value)) + 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 }; - // REVOKED - let py_revocation_time = py_single_resp.getattr(crate::intern!(py, "_revocation_time"))?; - let revocation_time = asn1::GeneralizedTime::new(py_to_chrono(py, py_revocation_time)?)?; - CertStatus::Revoked(RevokedInfo { - revocation_time, - revocation_reason, - }) - }; - let next_update = if !py_single_resp - .getattr(crate::intern!(py, "_next_update"))? - .is_none() - { - let py_next_update = py_single_resp.getattr(crate::intern!(py, "_next_update"))?; - Some(asn1::GeneralizedTime::new(py_to_chrono( - py, - py_next_update, - )?)?) - } else { - None - }; - let py_this_update = py_single_resp.getattr(crate::intern!(py, "_this_update"))?; - let this_update = asn1::GeneralizedTime::new(py_to_chrono(py, py_this_update)?)?; - - let responses = vec![SingleResponse { - cert_id: ocsp::CertID::new(py, &py_cert, &py_issuer, py_cert_hash_algorithm)?, - cert_status, - next_update, - this_update, - single_extensions: None, - }]; - - let borrowed_cert = responder_cert.borrow(); - let responder_id = if responder_encoding - == ocsp_mod - .getattr(crate::intern!(py, "OCSPResponderEncoding"))? - .getattr(crate::intern!(py, "HASH"))? - { - let sha1 = py - .import("cryptography.hazmat.primitives.hashes")? - .getattr(crate::intern!(py, "SHA1"))? - .call0()?; - ResponderId::ByKey(ocsp::hash_data( - py, - sha1, - borrowed_cert - .raw - .borrow_value_public() - .tbs_cert - .spki - .subject_public_key - .as_bytes(), - )?) - } else { - ResponderId::ByName( - borrowed_cert - .raw - .borrow_value_public() - .tbs_cert - .subject - .clone(), - ) - }; + 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 responses = vec![SingleResponse { + cert_id: ocsp::certid_new(py, &py_cert, &py_issuer, py_cert_hash_algorithm)?, + cert_status, + next_update, + this_update, + raw_single_extensions: None, + }]; + + borrowed_cert = responder_cert.borrow(); + let responder_id = if responder_encoding.is(ocsp_mod + .getattr(pyo3::intern!(py, "OCSPResponderEncoding"))? + .getattr(pyo3::intern!(py, "HASH"))?) + { + let sha1 = py + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "SHA1"))? + .call0()?; + ocsp_resp::ResponderId::ByKey(ocsp::hash_data( + py, + sha1, + borrowed_cert + .raw + .borrow_value_public() + .tbs_cert + .spki + .subject_public_key + .as_bytes(), + )?) + } else { + ocsp_resp::ResponderId::ByName( + borrowed_cert + .raw + .borrow_value_public() + .tbs_cert + .subject + .clone(), + ) + }; - let tbs_response_data = ResponseData { - version: 0, - produced_at: asn1::GeneralizedTime::new(chrono::Utc::now().with_nanosecond(0).unwrap())?, - responder_id, - responses: x509::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(responses)), - response_extensions: x509::common::encode_extensions( - py, - builder.getattr(crate::intern!(py, "_extensions"))?, - extensions::encode_extension, - )?, - }; + 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, + builder.getattr(pyo3::intern!(py, "_extensions"))?, + extensions::encode_extension, + )?, + }; - let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?; - let tbs_bytes = asn1::write_single(&tbs_response_data)?; - let signature = x509::sign::sign_data(py, private_key, hash_algorithm, &tbs_bytes)?; - - py.import("cryptography.hazmat.backends.openssl.backend")? - .getattr(crate::intern!(py, "backend"))? - .call_method1( - "_check_keys_correspond", - ( - responder_cert.call_method0("public_key")?, - private_key.call_method0("public_key")?, - ), + let sigalg = x509::sign::compute_signature_algorithm( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + )?; + let tbs_bytes = asn1::write_single(&tbs_response_data)?; + let signature = x509::sign::sign_data( + py, + private_key, + hash_algorithm, + py.None().into_ref(py), + &tbs_bytes, )?; - let py_certs: Option>> = - builder.getattr(crate::intern!(py, "_certs"))?.extract()?; - let certs = py_certs.as_ref().map(|py_certs| { - x509::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new( - py_certs - .iter() - .map(|c| c.raw.borrow_value_public().clone()) - .collect(), - )) - }); - - let basic_resp = BasicOCSPResponse { - tbs_response_data, - signature: asn1::BitString::new(signature, 0).unwrap(), - signature_algorithm: sigalg, - certs, - }; - Ok(asn1::write_single(&basic_resp)?) -} + 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", + ), + )); + } -#[pyo3::prelude::pyfunction] -fn create_ocsp_response( - py: pyo3::Python<'_>, - status: &pyo3::PyAny, - builder: &pyo3::PyAny, - private_key: &pyo3::PyAny, - hash_algorithm: &pyo3::PyAny, -) -> PyAsn1Result { - let response_status = status - .getattr(crate::intern!(py, "value"))? - .extract::()?; - let basic_resp_bytes; - let response_bytes = if response_status == SUCCESSFUL_RESPONSE { - basic_resp_bytes = create_ocsp_basic_response(py, builder, private_key, hash_algorithm)?; - Some(ResponseBytes { + 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_value_public().clone()) + .collect(), + )) + }); + + let basic_resp = ocsp_resp::BasicOCSPResponse { + tbs_response_data, + signature: asn1::BitString::new(signature, 0).unwrap(), + signature_algorithm: sigalg, + certs, + }; + Some(ocsp_resp::ResponseBytes { response_type: (BASIC_RESPONSE_OID).clone(), - response: &basic_resp_bytes, + response: asn1::OctetStringEncoded::new(basic_resp), }) } else { None }; - let resp = RawOCSPResponse { + let resp = ocsp_resp::OCSPResponse { response_status: asn1::Enumerated::new(response_status), response_bytes, }; let data = asn1::write_single(&resp)?; - // TODO: extra copy as we round-trip through a slice - load_der_ocsp_response(py, &data) + load_der_ocsp_response(py, pyo3::types::PyBytes::new(py, &data).into_py(py)) } pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> { - module.add_wrapped(pyo3::wrap_pyfunction!(load_der_ocsp_response))?; - module.add_wrapped(pyo3::wrap_pyfunction!(create_ocsp_response))?; + module.add_function(pyo3::wrap_pyfunction!(load_der_ocsp_response, module)?)?; + module.add_function(pyo3::wrap_pyfunction!(create_ocsp_response, module)?)?; Ok(()) } #[ouroboros::self_referencing] struct OwnedOCSPResponseIteratorData { - data: Arc, + data: Arc, #[borrows(data)] #[covariant] value: asn1::SequenceOf<'this, SingleResponse<'this>>, } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPResponseIterator { contents: OwnedOCSPResponseIteratorData, } -#[pyo3::prelude::pyproto] -impl pyo3::PyIterProtocol<'_> for OCSPResponseIterator { - fn __iter__(slf: pyo3::PyRef<'p, Self>) -> pyo3::PyRef<'p, Self> { +#[pyo3::prelude::pymethods] +impl OCSPResponseIterator { + fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { slf } - fn __next__(mut slf: pyo3::PyRefMut<'p, Self>) -> Option { + fn __next__(&mut self) -> Option { let single_resp = - try_map_arc_data_mut_ocsp_response_iterator(&mut slf.contents, |_data, v| { + try_map_arc_data_mut_ocsp_response_iterator(&mut self.contents, |_data, v| { match v.next() { Some(single_resp) => Ok(single_resp), None => Err(()), @@ -806,13 +778,13 @@ impl pyo3::PyIterProtocol<'_> for OCSPResponseIterator { #[ouroboros::self_referencing] struct OwnedSingleResponse { - data: Arc, + data: Arc, #[borrows(data)] #[covariant] - value: SingleResponse<'this>, + value: ocsp_resp::SingleResponse<'this>, } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.ocsp")] struct OCSPSingleResponse { raw: OwnedSingleResponse, } @@ -827,7 +799,7 @@ impl OCSPSingleResponse { impl OCSPSingleResponse { #[getter] fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - self.single_response().py_serial_number(py) + singleresp_py_serial_number(self.single_response(), py) } #[getter] @@ -843,32 +815,41 @@ impl OCSPSingleResponse { } #[getter] - fn hash_algorithm<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - self.single_response().py_hash_algorithm(py) + fn hash_algorithm<'p>( + &self, + py: pyo3::Python<'p>, + ) -> Result<&'p pyo3::PyAny, 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<&'p pyo3::PyAny> { - self.single_response().py_certificate_status(py) + 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<&'p pyo3::PyAny> { - self.single_response().py_revocation_time(py) + let single_resp = self.single_response(); + singleresp_py_revocation_time(single_resp, py) } #[getter] - fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> PyAsn1Result<&'p pyo3::PyAny> { - self.single_response().py_revocation_reason(py) + fn revocation_reason<'p>(&self, py: pyo3::Python<'p>) -> CryptographyResult<&'p pyo3::PyAny> { + 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<&'p pyo3::PyAny> { - self.single_response().py_this_update(py) + let single_resp = self.single_response(); + singleresp_py_this_update(single_resp, py) } #[getter] fn next_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - self.single_response().py_next_update(py) + let single_resp = self.single_response(); + singleresp_py_next_update(single_resp, py) } } diff --git a/src/rust/src/x509/oid.rs b/src/rust/src/x509/oid.rs deleted file mode 100644 index 45cfc15..0000000 --- a/src/rust/src/x509/oid.rs +++ /dev/null @@ -1,101 +0,0 @@ -// This file is dual licensed under the terms 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) const EXTENSION_REQUEST: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 113549, 1, 9, 14); -pub(crate) const MS_EXTENSION_REQUEST: asn1::ObjectIdentifier = - asn1::oid!(1, 3, 6, 1, 4, 1, 311, 2, 1, 14); -pub(crate) const PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 2); -pub(crate) const PRECERT_POISON_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 3); -pub(crate) const SIGNED_CERTIFICATE_TIMESTAMPS_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 3, 6, 1, 4, 1, 11129, 2, 4, 5); -pub(crate) const AUTHORITY_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 1); -pub(crate) const SUBJECT_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 11); -pub(crate) const TLS_FEATURE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 1, 24); -pub(crate) const CP_CPS_URI_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 2, 1); -pub(crate) const CP_USER_NOTICE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 2, 2); -pub(crate) const NONCE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 2); -pub(crate) const OCSP_NO_CHECK_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 5); -pub(crate) const SUBJECT_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 14); -pub(crate) const KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 15); -pub(crate) const SUBJECT_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 17); -pub(crate) const ISSUER_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 18); -pub(crate) const BASIC_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 19); -pub(crate) const CRL_NUMBER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 20); -pub(crate) const CRL_REASON_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 21); -pub(crate) const INVALIDITY_DATE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 24); -pub(crate) const DELTA_CRL_INDICATOR_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 27); -pub(crate) const ISSUING_DISTRIBUTION_POINT_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 28); -pub(crate) const CERTIFICATE_ISSUER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 29); -pub(crate) const NAME_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 30); -pub(crate) const CRL_DISTRIBUTION_POINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 31); -pub(crate) const CERTIFICATE_POLICIES_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 32); -pub(crate) const AUTHORITY_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 35); -pub(crate) const POLICY_CONSTRAINTS_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 36); -pub(crate) const EXTENDED_KEY_USAGE_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37); -pub(crate) const FRESHEST_CRL_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 46); -pub(crate) const INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 54); - -// Signing methods -pub(crate) const ECDSA_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 4, 1); -pub(crate) const ECDSA_WITH_SHA224_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 10045, 4, 3, 1); -pub(crate) const ECDSA_WITH_SHA256_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 10045, 4, 3, 2); -pub(crate) const ECDSA_WITH_SHA384_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 10045, 4, 3, 3); -pub(crate) const ECDSA_WITH_SHA512_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 10045, 4, 3, 4); -pub(crate) const ECDSA_WITH_SHA3_224_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 9); -pub(crate) const ECDSA_WITH_SHA3_256_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 10); -pub(crate) const ECDSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 11); -pub(crate) const ECDSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 12); - -pub(crate) const RSA_WITH_MD5_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 4); -pub(crate) const RSA_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 5); -pub(crate) const RSA_WITH_SHA224_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 113549, 1, 1, 14); -pub(crate) const RSA_WITH_SHA256_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 113549, 1, 1, 11); -pub(crate) const RSA_WITH_SHA384_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 113549, 1, 1, 12); -pub(crate) const RSA_WITH_SHA512_OID: asn1::ObjectIdentifier = - asn1::oid!(1, 2, 840, 113549, 1, 1, 13); -pub(crate) const RSA_WITH_SHA3_224_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 13); -pub(crate) const RSA_WITH_SHA3_256_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 14); -pub(crate) const RSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 15); -pub(crate) const RSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 16); - -pub(crate) const DSA_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10040, 4, 3); -pub(crate) const DSA_WITH_SHA224_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 1); -pub(crate) const DSA_WITH_SHA256_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 2); -pub(crate) const DSA_WITH_SHA384_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 3); -pub(crate) const DSA_WITH_SHA512_OID: asn1::ObjectIdentifier = - asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 4); - -pub(crate) const ED25519_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 112); -pub(crate) const ED448_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 113); - -// Hashes -pub(crate) const SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 14, 3, 2, 26); -pub(crate) const SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 4); -pub(crate) const SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 1); -pub(crate) const SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 2); -pub(crate) const SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 3); diff --git a/src/rust/src/x509/sct.rs b/src/rust/src/x509/sct.rs index aaa374b..a13785b 100644 --- a/src/rust/src/x509/sct.rs +++ b/src/rust/src/x509/sct.rs @@ -2,11 +2,10 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::asn1::PyAsn1Error; +use crate::error::CryptographyError; use pyo3::types::IntoPyDict; use pyo3::ToPyObject; use std::collections::hash_map::DefaultHasher; -use std::convert::{TryFrom, TryInto}; use std::hash::{Hash, Hasher}; struct TLSReader<'a> { @@ -22,22 +21,22 @@ impl<'a> TLSReader<'a> { self.data.is_empty() } - fn read_byte(&mut self) -> Result { + fn read_byte(&mut self) -> Result { Ok(self.read_exact(1)?[0]) } - fn read_exact(&mut self, length: usize) -> Result<&'a [u8], PyAsn1Error> { + fn read_exact(&mut self, length: usize) -> Result<&'a [u8], CryptographyError> { if length > self.data.len() { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Invalid SCT length", - ))); + 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, PyAsn1Error> { + 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())?)) } @@ -128,7 +127,7 @@ impl TryFrom for SignatureAlgorithm { } } -#[pyo3::prelude::pyclass] +#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.x509")] pub(crate) struct Sct { log_id: [u8; 32], timestamp: u64, @@ -143,11 +142,34 @@ pub(crate) struct Sct { #[pyo3::prelude::pymethods] impl Sct { + fn __richcmp__( + &self, + other: pyo3::PyRef<'_, Sct>, + op: pyo3::basic::CompareOp, + ) -> pyo3::PyResult { + match op { + pyo3::basic::CompareOp::Eq => Ok(self.sct_data == other.sct_data), + pyo3::basic::CompareOp::Ne => Ok(self.sct_data != other.sct_data), + _ => Err(pyo3::exceptions::PyTypeError::new_err( + "SCTs cannot be ordered", + )), + } + } + + 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<&'p pyo3::PyAny> { - py.import("cryptography.x509.certificate_transparency")? - .getattr(crate::intern!(py, "Version"))? - .getattr(crate::intern!(py, "v1")) + py.import(pyo3::intern!( + py, + "cryptography.x509.certificate_transparency" + ))? + .getattr(pyo3::intern!(py, "Version"))? + .getattr(pyo3::intern!(py, "v1")) } #[getter] @@ -158,10 +180,13 @@ impl Sct { #[getter] fn timestamp<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let datetime_class = py - .import("datetime")? - .getattr(crate::intern!(py, "datetime"))?; + .import(pyo3::intern!(py, "datetime"))? + .getattr(pyo3::intern!(py, "datetime"))?; datetime_class - .call_method1("utcfromtimestamp", (self.timestamp / 1000,))? + .call_method1( + pyo3::intern!(py, "utcfromtimestamp"), + (self.timestamp / 1000,), + )? .call_method( "replace", (), @@ -172,8 +197,11 @@ impl Sct { #[getter] fn entry_type<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let et_class = py - .import("cryptography.x509.certificate_transparency")? - .getattr(crate::intern!(py, "LogEntryType"))?; + .import(pyo3::intern!( + py, + "cryptography.x509.certificate_transparency" + ))? + .getattr(pyo3::intern!(py, "LogEntryType"))?; let attr_name = match self.entry_type { LogEntryType::Certificate => "X509_CERTIFICATE", LogEntryType::PreCertificate => "PRE_CERTIFICATE", @@ -186,15 +214,18 @@ impl Sct { &self, py: pyo3::Python<'p>, ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let hashes_mod = py.import("cryptography.hazmat.primitives.hashes")?; + let hashes_mod = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; hashes_mod.call_method0(self.hash_algorithm.to_attr()) } #[getter] fn signature_algorithm<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { let sa_class = py - .import("cryptography.x509.certificate_transparency")? - .getattr(crate::intern!(py, "SignatureAlgorithm"))?; + .import(pyo3::intern!( + py, + "cryptography.x509.certificate_transparency" + ))? + .getattr(pyo3::intern!(py, "SignatureAlgorithm"))?; sa_class.getattr(self.signature_algorithm.to_attr()) } @@ -209,34 +240,11 @@ impl Sct { } } -#[pyo3::prelude::pyproto] -impl pyo3::PyObjectProtocol for Sct { - fn __richcmp__( - &self, - other: pyo3::PyRef, - op: pyo3::basic::CompareOp, - ) -> pyo3::PyResult { - match op { - pyo3::basic::CompareOp::Eq => Ok(self.sct_data == other.sct_data), - pyo3::basic::CompareOp::Ne => Ok(self.sct_data != other.sct_data), - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "SCTs cannot be ordered", - )), - } - } - - fn __hash__(&self) -> u64 { - let mut hasher = DefaultHasher::new(); - self.sct_data.hash(&mut hasher); - hasher.finish() - } -} - pub(crate) fn parse_scts( py: pyo3::Python<'_>, data: &[u8], entry_type: LogEntryType, -) -> Result { +) -> Result { let mut reader = TLSReader::new(data).read_length_prefixed()?; let py_scts = pyo3::types::PyList::empty(py); @@ -245,9 +253,9 @@ pub(crate) fn parse_scts( let raw_sct_data = sct_data.data.to_vec(); let version = sct_data.read_byte()?; if version != 0 { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Invalid SCT version", - ))); + 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()); diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs index 4d91575..4b03a2d 100644 --- a/src/rust/src/x509/sign.rs +++ b/src/rust/src/x509/sign.rs @@ -2,19 +2,31 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. -use crate::x509; -use crate::x509::oid; - +use crate::asn1::oid_to_py_oid; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::exceptions; +use cryptography_x509::{common, oid}; use once_cell::sync::Lazy; +use std::collections::HashMap; -static NULL_DER: Lazy> = Lazy::new(|| { - // TODO: kind of verbose way to say "\x05\x00". - asn1::write_single(&()).unwrap() +// 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 }); -pub(crate) static NULL_TLV: Lazy> = - Lazy::new(|| asn1::parse_single(&NULL_DER).unwrap()); -enum KeyType { +#[derive(Debug, PartialEq)] +pub(crate) enum KeyType { Rsa, Dsa, Ec, @@ -24,8 +36,6 @@ enum KeyType { enum HashType { None, - Md5, - Sha1, Sha224, Sha256, Sha384, @@ -38,35 +48,50 @@ enum HashType { fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::PyResult { let rsa_private_key: &pyo3::types::PyType = py - .import("cryptography.hazmat.primitives.asymmetric.rsa")? - .getattr(crate::intern!(py, "RSAPrivateKey"))? + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.rsa" + ))? + .getattr(pyo3::intern!(py, "RSAPrivateKey"))? .extract()?; let dsa_key_type: &pyo3::types::PyType = py - .import("cryptography.hazmat.primitives.asymmetric.dsa")? - .getattr(crate::intern!(py, "DSAPrivateKey"))? + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dsa" + ))? + .getattr(pyo3::intern!(py, "DSAPrivateKey"))? .extract()?; let ec_key_type: &pyo3::types::PyType = py - .import("cryptography.hazmat.primitives.asymmetric.ec")? - .getattr(crate::intern!(py, "EllipticCurvePrivateKey"))? + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ec" + ))? + .getattr(pyo3::intern!(py, "EllipticCurvePrivateKey"))? .extract()?; let ed25519_key_type: &pyo3::types::PyType = py - .import("cryptography.hazmat.primitives.asymmetric.ed25519")? - .getattr(crate::intern!(py, "Ed25519PrivateKey"))? + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ed25519" + ))? + .getattr(pyo3::intern!(py, "Ed25519PrivateKey"))? .extract()?; let ed448_key_type: &pyo3::types::PyType = py - .import("cryptography.hazmat.primitives.asymmetric.ed448")? - .getattr(crate::intern!(py, "Ed448PrivateKey"))? + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ed448" + ))? + .getattr(pyo3::intern!(py, "Ed448PrivateKey"))? .extract()?; - if rsa_private_key.is_instance(private_key)? { + if private_key.is_instance(rsa_private_key)? { Ok(KeyType::Rsa) - } else if dsa_key_type.is_instance(private_key)? { + } else if private_key.is_instance(dsa_key_type)? { Ok(KeyType::Dsa) - } else if ec_key_type.is_instance(private_key)? { + } else if private_key.is_instance(ec_key_type)? { Ok(KeyType::Ec) - } else if ed25519_key_type.is_instance(private_key)? { + } else if private_key.is_instance(ed25519_key_type)? { Ok(KeyType::Ed25519) - } else if ed448_key_type.is_instance(private_key)? { + } else if private_key.is_instance(ed448_key_type)? { Ok(KeyType::Ed448) } else { Err(pyo3::exceptions::PyTypeError::new_err( @@ -84,45 +109,19 @@ fn identify_hash_type( } let hash_algorithm_type: &pyo3::types::PyType = py - .import("cryptography.hazmat.primitives.hashes")? - .getattr(crate::intern!(py, "HashAlgorithm"))? + .import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))? + .getattr(pyo3::intern!(py, "HashAlgorithm"))? .extract()?; - if !hash_algorithm_type.is_instance(hash_algorithm)? { + if !hash_algorithm.is_instance(hash_algorithm_type)? { return Err(pyo3::exceptions::PyTypeError::new_err( "Algorithm must be a registered hash algorithm.", )); } match hash_algorithm - .getattr(crate::intern!(py, "name"))? + .getattr(pyo3::intern!(py, "name"))? .extract()? { - "md5" => { - let cryptography_warning = py - .import("cryptography.utils")? - .getattr(crate::intern!(py, "DeprecatedIn38"))?; - pyo3::PyErr::warn( - py, - cryptography_warning, - "MD5 signatures are deprecated and support for them will be removed in the next version.", - 1 - )?; - - Ok(HashType::Md5) - } - "sha1" => { - let cryptography_warning = py - .import("cryptography.utils")? - .getattr(crate::intern!(py, "DeprecatedIn38"))?; - pyo3::PyErr::warn( - py, - cryptography_warning, - "SHA1 signatures are deprecated and support for them will be removed in the next version.", - 1 - )?; - - Ok(HashType::Sha1) - } "sha224" => Ok(HashType::Sha224), "sha256" => Ok(HashType::Sha256), "sha384" => Ok(HashType::Sha384), @@ -131,147 +130,199 @@ fn identify_hash_type( "sha3-256" => Ok(HashType::Sha3_256), "sha3-384" => Ok(HashType::Sha3_384), "sha3-512" => Ok(HashType::Sha3_512), - name => Err(pyo3::exceptions::PyValueError::new_err(format!( + name => Err(exceptions::UnsupportedAlgorithm::new_err(format!( "Hash algorithm {:?} not supported for signatures", name ))), } } +fn compute_pss_salt_length<'p>( + py: pyo3::Python<'p>, + private_key: &'p pyo3::PyAny, + hash_algorithm: &'p pyo3::PyAny, + rsa_padding: &'p pyo3::PyAny, +) -> pyo3::PyResult { + let padding_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.padding" + ))?; + let maxlen = padding_mod.getattr(pyo3::intern!(py, "_MaxLength"))?; + let digestlen = padding_mod.getattr(pyo3::intern!(py, "_DigestLength"))?; + let py_saltlen = rsa_padding.getattr(pyo3::intern!(py, "_salt_length"))?; + if py_saltlen.is_instance(maxlen)? { + padding_mod + .getattr(pyo3::intern!(py, "calculate_max_pss_salt_length"))? + .call1((private_key, hash_algorithm))? + .extract::() + } else if py_saltlen.is_instance(digestlen)? { + hash_algorithm + .getattr(pyo3::intern!(py, "digest_size"))? + .extract::() + } else if py_saltlen.is_instance(py.get_type::())? { + 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: &'p pyo3::PyAny, hash_algorithm: &'p pyo3::PyAny, -) -> pyo3::PyResult> { + rsa_padding: &'p pyo3::PyAny, +) -> pyo3::PyResult> { let key_type = identify_key_type(py, private_key)?; let hash_type = identify_hash_type(py, hash_algorithm)?; + let pss_type: &pyo3::types::PyType = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.padding" + ))? + .getattr(pyo3::intern!(py, "PSS"))? + .extract()?; + // If this is RSA-PSS we need to compute the signature algorithm from the + // parameters provided in rsa_padding. + if !rsa_padding.is_none() && rsa_padding.is_instance(pss_type)? { + 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)?; + 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: 1, + }))); + + 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(x509::AlgorithmIdentifier { - oid: (oid::ED25519_OID).clone(), - params: None, + (KeyType::Ed25519, HashType::None) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::Ed25519, }), - (KeyType::Ed448, HashType::None) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ED448_OID).clone(), - params: None, + (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::Ed25519 | KeyType::Ed448, _) => Err(pyo3::exceptions::PyValueError::new_err( + "Algorithm must be None when signing via ed25519 or ed448", + )), - (KeyType::Ec, HashType::Sha1) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA1_OID).clone(), - params: None, - }), - (KeyType::Ec, HashType::Sha224) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA224_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha224(None), }), - (KeyType::Ec, HashType::Sha256) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA256_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha256(None), }), - (KeyType::Ec, HashType::Sha384) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA384_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha384(None), }), - (KeyType::Ec, HashType::Sha512) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA512_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha512(None), }), - (KeyType::Ec, HashType::Sha3_224) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA3_224_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha3_224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_224, }), - (KeyType::Ec, HashType::Sha3_256) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA3_256_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha3_256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_256, }), - (KeyType::Ec, HashType::Sha3_384) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA3_384_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha3_384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_384, }), - (KeyType::Ec, HashType::Sha3_512) => Ok(x509::AlgorithmIdentifier { - oid: (oid::ECDSA_WITH_SHA3_512_OID).clone(), - params: None, + (KeyType::Ec, HashType::Sha3_512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::EcDsaWithSha3_512, }), - (KeyType::Rsa, HashType::Md5) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_MD5_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha224(Some(())), }), - (KeyType::Rsa, HashType::Sha1) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA1_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha256(Some(())), }), - (KeyType::Rsa, HashType::Sha224) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA224_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha384(Some(())), }), - (KeyType::Rsa, HashType::Sha256) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA256_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha512(Some(())), }), - (KeyType::Rsa, HashType::Sha384) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA384_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha3_224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_224(Some(())), }), - (KeyType::Rsa, HashType::Sha512) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA512_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha3_256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_256(Some(())), }), - (KeyType::Rsa, HashType::Sha3_224) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA3_224_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha3_384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_384(Some(())), }), - (KeyType::Rsa, HashType::Sha3_256) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA3_256_OID).clone(), - params: Some(*NULL_TLV), - }), - (KeyType::Rsa, HashType::Sha3_384) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA3_384_OID).clone(), - params: Some(*NULL_TLV), - }), - (KeyType::Rsa, HashType::Sha3_512) => Ok(x509::AlgorithmIdentifier { - oid: (oid::RSA_WITH_SHA3_512_OID).clone(), - params: Some(*NULL_TLV), + (KeyType::Rsa, HashType::Sha3_512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::RsaWithSha3_512(Some(())), }), - (KeyType::Dsa, HashType::Sha1) => Ok(x509::AlgorithmIdentifier { - oid: (oid::DSA_WITH_SHA1_OID).clone(), - params: None, - }), - (KeyType::Dsa, HashType::Sha224) => Ok(x509::AlgorithmIdentifier { - oid: (oid::DSA_WITH_SHA224_OID).clone(), - params: None, + (KeyType::Dsa, HashType::Sha224) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha224, }), - (KeyType::Dsa, HashType::Sha256) => Ok(x509::AlgorithmIdentifier { - oid: (oid::DSA_WITH_SHA256_OID).clone(), - params: None, + (KeyType::Dsa, HashType::Sha256) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha256, }), - (KeyType::Dsa, HashType::Sha384) => Ok(x509::AlgorithmIdentifier { - oid: (oid::DSA_WITH_SHA384_OID).clone(), - params: None, + (KeyType::Dsa, HashType::Sha384) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha384, }), - (KeyType::Dsa, HashType::Sha512) => Ok(x509::AlgorithmIdentifier { - oid: (oid::DSA_WITH_SHA512_OID).clone(), - params: None, + (KeyType::Dsa, HashType::Sha512) => Ok(common::AlgorithmIdentifier { + oid: asn1::DefinedByMarker::marker(), + params: common::AlgorithmParameters::DsaWithSha512, }), - (KeyType::Dsa, HashType::Sha3_224) - | (KeyType::Dsa, HashType::Sha3_256) - | (KeyType::Dsa, HashType::Sha3_384) - | (KeyType::Dsa, HashType::Sha3_512) => Err(pyo3::exceptions::PyValueError::new_err( + ( + 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.", )), - (_, HashType::Md5) => Err(pyo3::exceptions::PyValueError::new_err( - "MD5 hash algorithm is only supported with RSA keys", - )), } } @@ -279,27 +330,436 @@ pub(crate) fn sign_data<'p>( py: pyo3::Python<'p>, private_key: &'p pyo3::PyAny, hash_algorithm: &'p pyo3::PyAny, + rsa_padding: &'p pyo3::PyAny, data: &[u8], ) -> pyo3::PyResult<&'p [u8]> { let key_type = identify_key_type(py, private_key)?; let signature = match key_type { - KeyType::Ed25519 | KeyType::Ed448 => private_key.call_method1("sign", (data,))?, + KeyType::Ed25519 | KeyType::Ed448 => { + private_key.call_method1(pyo3::intern!(py, "sign"), (data,))? + } KeyType::Ec => { - let ec_mod = py.import("cryptography.hazmat.primitives.asymmetric.ec")?; + let ec_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ec" + ))?; let ecdsa = ec_mod - .getattr(crate::intern!(py, "ECDSA"))? + .getattr(pyo3::intern!(py, "ECDSA"))? .call1((hash_algorithm,))?; - private_key.call_method1("sign", (data, ecdsa))? + private_key.call_method1(pyo3::intern!(py, "sign"), (data, ecdsa))? } KeyType::Rsa => { - let padding_mod = py.import("cryptography.hazmat.primitives.asymmetric.padding")?; - let pkcs1v15 = padding_mod - .getattr(crate::intern!(py, "PKCS1v15"))? - .call0()?; - private_key.call_method1("sign", (data, pkcs1v15, hash_algorithm))? + let mut padding = rsa_padding; + if padding.is_none() { + let padding_mod = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.padding" + ))?; + padding = padding_mod + .getattr(pyo3::intern!(py, "PKCS1v15"))? + .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))? } - KeyType::Dsa => private_key.call_method1("sign", (data, hash_algorithm))?, }; signature.extract() } + +pub(crate) fn verify_signature_with_signature_algorithm<'p>( + py: pyo3::Python<'p>, + issuer_public_key: &'p pyo3::PyAny, + signature_algorithm: &common::AlgorithmIdentifier<'_>, + signature: &[u8], + data: &[u8], +) -> CryptographyResult<()> { + let key_type = identify_public_key_type(py, issuer_public_key)?; + 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::PyAny, +) -> pyo3::PyResult { + let rsa_key_type: &pyo3::types::PyType = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.rsa" + ))? + .getattr(pyo3::intern!(py, "RSAPublicKey"))? + .extract()?; + let dsa_key_type: &pyo3::types::PyType = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.dsa" + ))? + .getattr(pyo3::intern!(py, "DSAPublicKey"))? + .extract()?; + let ec_key_type: &pyo3::types::PyType = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ec" + ))? + .getattr(pyo3::intern!(py, "EllipticCurvePublicKey"))? + .extract()?; + let ed25519_key_type: &pyo3::types::PyType = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ed25519" + ))? + .getattr(pyo3::intern!(py, "Ed25519PublicKey"))? + .extract()?; + let ed448_key_type: &pyo3::types::PyType = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ed448" + ))? + .getattr(pyo3::intern!(py, "Ed448PublicKey"))? + .extract()?; + + if public_key.is_instance(rsa_key_type)? { + Ok(KeyType::Rsa) + } else if public_key.is_instance(dsa_key_type)? { + Ok(KeyType::Dsa) + } else if public_key.is_instance(ec_key_type)? { + Ok(KeyType::Ec) + } else if public_key.is_instance(ed25519_key_type)? { + Ok(KeyType::Ed25519) + } else if public_key.is_instance(ed448_key_type)? { + 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<&pyo3::PyAny> { + let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?; + match HASH_OIDS_TO_HASH.get(&oid) { + Some(alg_name) => Ok(hashes.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<&'p pyo3::PyAny> { + let sig_oids_to_hash = py + .import(pyo3::intern!(py, "cryptography.hazmat._oid"))? + .getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?; + 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<&'p pyo3::PyAny> { + 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 padding = py.import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.padding" + ))?; + let py_mgf = padding + .getattr(pyo3::intern!(py, "MGF1"))? + .call1((py_mask_gen_hash_alg,))?; + Ok(padding + .getattr(pyo3::intern!(py, "PSS"))? + .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(_) => { + let pkcs = py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.padding" + ))? + .getattr(pyo3::intern!(py, "PKCS1v15"))? + .call0()?; + Ok(pkcs) + } + 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(py + .import(pyo3::intern!( + py, + "cryptography.hazmat.primitives.asymmetric.ec" + ))? + .getattr(pyo3::intern!(py, "ECDSA"))? + .call1((signature_hash_algorithm,))?) + } + _ => Ok(py.None().into_ref(py)), + } +} + +#[cfg(test)] +mod tests { + use super::{ + identify_alg_params_for_hash_type, identify_key_type_for_algorithm_params, HashType, + KeyType, + }; + use cryptography_x509::{common, oid}; + + #[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, KeyType::Dsa), + (&common::AlgorithmParameters::DsaWithSha256, KeyType::Dsa), + (&common::AlgorithmParameters::DsaWithSha384, KeyType::Dsa), + (&common::AlgorithmParameters::DsaWithSha512, 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); + } + } +} diff --git a/tests/bench/test_aead.py b/tests/bench/test_aead.py index 9eb3fc1..f93c4e8 100644 --- a/tests/bench/test_aead.py +++ b/tests/bench/test_aead.py @@ -4,7 +4,13 @@ import pytest -from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 +from cryptography.hazmat.primitives.ciphers.aead import ( + AESCCM, + AESGCM, + AESOCB3, + AESSIV, + ChaCha20Poly1305, +) from ..hazmat.primitives.test_aead import _aead_supported @@ -13,6 +19,84 @@ not _aead_supported(ChaCha20Poly1305), reason="Requires OpenSSL with ChaCha20Poly1305 support", ) -def test_chacha20poly1305(benchmark): +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) diff --git a/tests/bench/test_ec_load.py b/tests/bench/test_ec_load.py new file mode 100644 index 0000000..568dbd9 --- /dev/null +++ b/tests/bench/test_ec_load.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms 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) diff --git a/tests/bench/test_hashes.py b/tests/bench/test_hashes.py new file mode 100644 index 0000000..49ca5be --- /dev/null +++ b/tests/bench/test_hashes.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms 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) diff --git a/tests/bench/test_hmac.py b/tests/bench/test_hmac.py new file mode 100644 index 0000000..b5b1e33 --- /dev/null +++ b/tests/bench/test_hmac.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms 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) diff --git a/tests/bench/test_x509.py b/tests/bench/test_x509.py index d689a00..87a60af 100644 --- a/tests/bench/test_x509.py +++ b/tests/bench/test_x509.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. @@ -23,6 +22,16 @@ def test_aki_public_bytes(benchmark): 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"), diff --git a/tests/conftest.py b/tests/conftest.py index 9049922..0e128a1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import contextlib import pytest @@ -18,8 +19,8 @@ def pytest_configure(config): def pytest_report_header(config): return "\n".join( [ - "OpenSSL: {}".format(openssl_backend.openssl_version_text()), - "FIPS Enabled: {}".format(openssl_backend._fips_enabled), + f"OpenSSL: {openssl_backend.openssl_version_text()}", + f"FIPS Enabled: {openssl_backend._fips_enabled}", ] ) @@ -40,18 +41,27 @@ def backend(request): check_backend_support(openssl_backend, request) # Ensure the error stack is clear before the test - errors = openssl_backend._consume_errors_with_text() + 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_with_text() + errors = openssl_backend._consume_errors() assert not errors -@pytest.fixture -def disable_rsa_checks(backend): - # Use this fixture to skip RSA key checks in tests that need the - # performance. - backend._rsa_skip_check_key = True - yield - backend._rsa_skip_check_key = False +@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 diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 6cc4499..c8fa1ef 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -5,24 +5,19 @@ import itertools import os -import subprocess -import sys -import textwrap import pytest -from cryptography import utils, x509 from cryptography.exceptions import InternalError, _Reasons from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import dh, padding +from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import CBC -from ..primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 from ...doubles import ( DummyAsymmetricPadding, DummyBlockCipherAlgorithm, @@ -30,12 +25,15 @@ DummyHashAlgorithm, DummyMode, ) +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, ) -from ...x509.test_x509 import _load_cert + +# Make ruff happy since we're importing fixtures that pytest patches in as +# func args +__all__ = ["rsa_key_512", "rsa_key_2048"] def skip_if_libre_ssl(openssl_version): @@ -136,7 +134,7 @@ def test_consume_errors(self): assert len(errors) == 10 def test_ssl_ciphers_registered(self): - meth = backend._lib.SSLv23_method() + meth = backend._lib.TLS_method() ctx = backend._lib.SSL_CTX_new(meth) assert ctx != backend._ffi.NULL backend._lib.SSL_CTX_free(ctx) @@ -162,173 +160,11 @@ def test_int_to_bn(self): assert bn assert backend._bn_to_int(bn) == value - def test_int_to_bn_inplace(self): - value = (2**4242) - 4242 - bn_ptr = backend._lib.BN_new() - assert bn_ptr != backend._ffi.NULL - bn_ptr = backend._ffi.gc(bn_ptr, backend._lib.BN_free) - bn = backend._int_to_bn(value, bn_ptr) - - assert bn == bn_ptr - assert backend._bn_to_int(bn_ptr) == value - def test_bn_to_int(self): bn = backend._int_to_bn(0) assert backend._bn_to_int(bn) == 0 -@pytest.mark.skipif( - not backend._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE, - reason="Requires OpenSSL with ENGINE support and OpenSSL < 1.1.1d", -) -@pytest.mark.skip_fips(reason="osrandom engine disabled for FIPS") -class TestOpenSSLRandomEngine: - def setup(self): - # The default RAND engine is global and shared between - # tests. We make sure that the default engine is osrandom - # before we start each test and restore the global state to - # that engine in teardown. - current_default = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(current_default) - assert name == backend._lib.Cryptography_osrandom_engine_name - - def teardown(self): - # we need to reset state to being default. backend is a shared global - # for all these tests. - backend.activate_osrandom_engine() - current_default = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(current_default) - assert name == backend._lib.Cryptography_osrandom_engine_name - - @pytest.mark.skipif( - sys.executable is None, reason="No Python interpreter available." - ) - def test_osrandom_engine_is_default(self, tmpdir): - engine_printer = textwrap.dedent( - """ - import sys - from cryptography.hazmat.backends.openssl.backend import backend - - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - sys.stdout.write(backend._ffi.string(name).decode('ascii')) - res = backend._lib.ENGINE_free(e) - assert res == 1 - """ - ) - engine_name = tmpdir.join("engine_name") - - # If we're running tests via ``python setup.py test`` in a clean - # environment then all of our dependencies are going to be installed - # into either the current directory or the .eggs directory. However the - # subprocess won't know to activate these dependencies, so we'll get it - # to do so by passing our entire sys.path into the subprocess via the - # PYTHONPATH environment variable. - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join(sys.path) - - with engine_name.open("w") as out: - subprocess.check_call( - [sys.executable, "-c", engine_printer], - env=env, - stdout=out, - stderr=subprocess.PIPE, - ) - - osrandom_engine_name = backend._ffi.string( - backend._lib.Cryptography_osrandom_engine_name - ) - - assert engine_name.read().encode("ascii") == osrandom_engine_name - - def test_osrandom_sanity_check(self): - # This test serves as a check against catastrophic failure. - buf = backend._ffi.new("unsigned char[]", 500) - res = backend._lib.RAND_bytes(buf, 500) - assert res == 1 - assert backend._ffi.buffer(buf)[:] != "\x00" * 500 - - def test_activate_osrandom_no_default(self): - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - backend.activate_osrandom_engine() - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - - def test_activate_builtin_random(self): - e = backend._lib.ENGINE_get_default_RAND() - assert e != backend._ffi.NULL - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - - def test_activate_builtin_random_already_active(self): - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - backend.activate_builtin_random() - e = backend._lib.ENGINE_get_default_RAND() - assert e == backend._ffi.NULL - - def test_osrandom_engine_implementation(self): - name = backend.osrandom_engine_implementation() - assert name in [ - "/dev/urandom", - "CryptGenRandom", - "getentropy", - "getrandom", - ] - if sys.platform.startswith("linux"): - assert name in ["getrandom", "/dev/urandom"] - if sys.platform == "darwin": - assert name in ["getentropy", "/dev/urandom"] - if sys.platform == "win32": - assert name == "CryptGenRandom" - - def test_activate_osrandom_already_default(self): - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - backend.activate_osrandom_engine() - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - - -@pytest.mark.skipif( - backend._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE, - reason="Requires OpenSSL without ENGINE support or OpenSSL >=1.1.1d", -) -class TestOpenSSLNoEngine: - def test_no_engine_support(self): - assert ( - backend._ffi.string(backend._lib.Cryptography_osrandom_engine_id) - == b"no-engine-support" - ) - assert ( - backend._ffi.string(backend._lib.Cryptography_osrandom_engine_name) - == b"osrandom_engine disabled" - ) - - def test_activate_builtin_random_does_nothing(self): - backend.activate_builtin_random() - - def test_activate_osrandom_does_nothing(self): - backend.activate_osrandom_engine() - - class TestOpenSSLRSA: def test_generate_rsa_parameters_supported(self): assert backend.generate_rsa_parameters_supported(1, 1024) is False @@ -382,8 +218,8 @@ def test_rsa_padding_supported_oaep(self): assert ( backend.rsa_padding_supported( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), ) @@ -399,6 +235,12 @@ def test_rsa_padding_supported_oaep_sha2_combinations(self): 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( @@ -429,10 +271,9 @@ def test_rsa_padding_unsupported_mgf(self): is False ) - def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_mgf1_hash_algorithm_md5_decrypt(self, rsa_key_512): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): - private_key.decrypt( + rsa_key_512.decrypt( b"0" * 64, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.MD5()), @@ -479,7 +320,9 @@ def test_pem_password_cb_no_password(self): def test_unsupported_evp_pkey_type(self): key = backend._create_evp_pkey_gc() with raises_unsupported_algorithm(None): - backend._evp_pkey_to_private_key(key) + backend._evp_pkey_to_private_key( + key, unsafe_skip_rsa_key_validation=False + ) with raises_unsupported_algorithm(None): backend._evp_pkey_to_public_key(key) @@ -495,7 +338,9 @@ def test_very_long_pem_serialization_password(self): ), lambda pemfile: ( backend.load_pem_private_key( - pemfile.read().encode(), password + pemfile.read().encode(), + password, + unsafe_skip_rsa_key_validation=False, ) ), ) @@ -508,11 +353,10 @@ def test_sn_to_elliptic_curve_not_supported(self): class TestRSAPEMSerialization: - def test_password_length_limit(self): + def test_password_length_limit(self, rsa_key_2048): password = b"x" * 1024 - key = RSA_KEY_2048.private_key(backend) with pytest.raises(ValueError): - key.private_bytes( + rsa_key_2048.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.BestAvailableEncryption(password), @@ -528,36 +372,6 @@ def test_password_length_limit(self): skip_message="Requires DH support", ) class TestOpenSSLDHSerialization: - @pytest.mark.parametrize( - "vector", - load_vectors_from_file( - os.path.join("asymmetric", "DH", "RFC5114.txt"), load_nist_vectors - ), - ) - def test_dh_serialization_with_q_unsupported(self, backend, vector): - parameters = dh.DHParameterNumbers( - int(vector["p"], 16), int(vector["g"], 16), int(vector["q"], 16) - ) - public = dh.DHPublicNumbers(int(vector["ystatcavs"], 16), parameters) - private = dh.DHPrivateNumbers(int(vector["xstatcavs"], 16), public) - private_key = private.private_key(backend) - public_key = private_key.public_key() - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - private_key.private_bytes( - serialization.Encoding.PEM, - serialization.PrivateFormat.PKCS8, - serialization.NoEncryption(), - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - public_key.public_bytes( - serialization.Encoding.PEM, - serialization.PublicFormat.SubjectPublicKeyInfo, - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): - parameters.parameters(backend).parameter_bytes( - serialization.Encoding.PEM, serialization.ParameterFormat.PKCS3 - ) - @pytest.mark.parametrize( ("key_path", "loader_func"), [ @@ -599,55 +413,3 @@ def test_public_load_dhx_unsupported(self, key_path, loader_func, backend): ) with pytest.raises(ValueError): loader_func(key_bytes, backend) - - -def test_pyopenssl_cert_fallback(): - cert = _load_cert( - os.path.join("x509", "cryptography.io.pem"), - x509.load_pem_x509_certificate, - ) - x509_ossl = None - with pytest.warns(utils.CryptographyDeprecationWarning): - x509_ossl = cert._x509 # type:ignore[attr-defined] - assert x509_ossl is not None - - from cryptography.hazmat.backends.openssl.x509 import _Certificate - - with pytest.warns(utils.CryptographyDeprecationWarning): - _Certificate(backend, x509_ossl) - - -def test_pyopenssl_csr_fallback(): - cert = _load_cert( - os.path.join("x509", "requests", "rsa_sha256.pem"), - x509.load_pem_x509_csr, - ) - req_ossl = None - with pytest.warns(utils.CryptographyDeprecationWarning): - req_ossl = cert._x509_req # type:ignore[attr-defined] - assert req_ossl is not None - - from cryptography.hazmat.backends.openssl.x509 import ( - _CertificateSigningRequest, - ) - - with pytest.warns(utils.CryptographyDeprecationWarning): - _CertificateSigningRequest(backend, req_ossl) - - -def test_pyopenssl_crl_fallback(): - cert = _load_cert( - os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), - x509.load_der_x509_crl, - ) - req_crl = None - with pytest.warns(utils.CryptographyDeprecationWarning): - req_crl = cert._x509_crl # type:ignore[attr-defined] - assert req_crl is not None - - from cryptography.hazmat.backends.openssl.x509 import ( - _CertificateRevocationList, - ) - - with pytest.warns(utils.CryptographyDeprecationWarning): - _CertificateRevocationList(backend, req_crl) diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py index 2605566..05e8f94 100644 --- a/tests/hazmat/backends/test_openssl_memleak.py +++ b/tests/hazmat/backends/test_openssl_memleak.py @@ -5,6 +5,7 @@ import json import os +import platform import subprocess import sys import textwrap @@ -13,7 +14,6 @@ from cryptography.hazmat.bindings.openssl.binding import Binding - MEMORY_LEAK_SCRIPT = """ import sys @@ -24,9 +24,11 @@ def main(argv): import cffi - from cryptography.hazmat.bindings._openssl import ffi, lib + from cryptography.hazmat.bindings._rust import _openssl heap = {} + start_heap = {} + start_heap_realloc_delta = [0] # 1-item list so callbacks can mutate it BACKTRACE_ENABLED = False if BACKTRACE_ENABLED: @@ -49,7 +51,9 @@ def symbolize_backtrace(trace): backtrace_ffi.string(symbols[i]).decode() for i in range(length) ] - lib.Cryptography_free_wrapper(symbols, backtrace_ffi.NULL, 0) + _openssl.lib.Cryptography_free_wrapper( + symbols, backtrace_ffi.NULL, 0 + ) return stack else: def backtrace(): @@ -58,27 +62,45 @@ def backtrace(): def symbolize_backtrace(trace): return None - @ffi.callback("void *(size_t, const char *, int)") + @_openssl.ffi.callback("void *(size_t, const char *, int)") def malloc(size, path, line): - ptr = lib.Cryptography_malloc_wrapper(size, path, line) + ptr = _openssl.lib.Cryptography_malloc_wrapper(size, path, line) heap[ptr] = (size, path, line, backtrace()) return ptr - @ffi.callback("void *(void *, size_t, const char *, int)") + @_openssl.ffi.callback("void *(void *, size_t, const char *, int)") def realloc(ptr, size, path, line): - if ptr != ffi.NULL: + if ptr != _openssl.ffi.NULL: del heap[ptr] - new_ptr = lib.Cryptography_realloc_wrapper(ptr, size, path, line) + new_ptr = _openssl.lib.Cryptography_realloc_wrapper( + ptr, size, path, line + ) heap[new_ptr] = (size, path, line, backtrace()) + + # It is possible that something during the test will cause a + # realloc of memory allocated during the startup phase. (This + # was observed in conda-forge Windows builds of this package with + # provider operation_bits pointers in crypto/provider_core.c.) If + # we don't pay attention to that, the realloc'ed pointer will show + # up as a leak; but we also don't want to allow this kind of realloc + # to consume large amounts of additional memory. So we track the + # realloc and the change in memory consumption. + startup_info = start_heap.pop(ptr, None) + if startup_info is not None: + start_heap[new_ptr] = heap[new_ptr] + start_heap_realloc_delta[0] += size - startup_info[0] + return new_ptr - @ffi.callback("void(void *, const char *, int)") + @_openssl.ffi.callback("void(void *, const char *, int)") def free(ptr, path, line): - if ptr != ffi.NULL: + if ptr != _openssl.ffi.NULL: del heap[ptr] - lib.Cryptography_free_wrapper(ptr, path, line) + _openssl.lib.Cryptography_free_wrapper(ptr, path, line) - result = lib.Cryptography_CRYPTO_set_mem_functions(malloc, realloc, free) + result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( + malloc, realloc, free + ) assert result == 1 # Trigger a bunch of initialization stuff. @@ -87,7 +109,7 @@ def free(ptr, path, line): hashlib.sha256() - start_heap = set(heap) + start_heap.update(heap) try: func(*argv[1:]) @@ -96,35 +118,42 @@ def free(ptr, path, line): gc.collect() gc.collect() - if lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - lib.OSSL_PROVIDER_unload(backend._binding._legacy_provider) - lib.OSSL_PROVIDER_unload(backend._binding._default_provider) + if _openssl.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + _openssl.lib.OSSL_PROVIDER_unload(backend._binding._legacy_provider) + _openssl.lib.OSSL_PROVIDER_unload(backend._binding._default_provider) - if lib.Cryptography_HAS_OPENSSL_CLEANUP: - lib.OPENSSL_cleanup() + _openssl.lib.OPENSSL_cleanup() # Swap back to the original functions so that if OpenSSL tries to free # something from its atexit handle it won't be going through a Python # function, which will be deallocated when this function returns - result = lib.Cryptography_CRYPTO_set_mem_functions( - ffi.addressof(lib, "Cryptography_malloc_wrapper"), - ffi.addressof(lib, "Cryptography_realloc_wrapper"), - ffi.addressof(lib, "Cryptography_free_wrapper"), + result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( + _openssl.ffi.addressof( + _openssl.lib, "Cryptography_malloc_wrapper" + ), + _openssl.ffi.addressof( + _openssl.lib, "Cryptography_realloc_wrapper" + ), + _openssl.ffi.addressof(_openssl.lib, "Cryptography_free_wrapper"), ) assert result == 1 - remaining = set(heap) - start_heap + remaining = set(heap) - set(start_heap) - if remaining: - sys.stdout.write(json.dumps(dict( - (int(ffi.cast("size_t", ptr)), { + # The constant here is the number of additional bytes of memory + # consumption that are allowed in reallocs of start_heap memory. + if remaining or start_heap_realloc_delta[0] > 3072: + info = dict( + (int(_openssl.ffi.cast("size_t", ptr)), { "size": heap[ptr][0], - "path": ffi.string(heap[ptr][1]).decode(), + "path": _openssl.ffi.string(heap[ptr][1]).decode(), "line": heap[ptr][2], "backtrace": symbolize_backtrace(heap[ptr][3]), }) for ptr in remaining - ))) + ) + info["start_heap_realloc_delta"] = start_heap_realloc_delta[0] + sys.stdout.write(json.dumps(info)) sys.stdout.flush() sys.exit(255) @@ -146,7 +175,7 @@ def assert_no_memory_leaks(s, argv=[]): argv = [ sys.executable, "-c", - "{}\n\n{}".format(s, MEMORY_LEAK_SCRIPT), + f"{s}\n\n{MEMORY_LEAK_SCRIPT}", ] + argv # Shell out to a fresh Python process because OpenSSL does not allow you to # install new memory hooks after the first malloc/free occurs. @@ -175,8 +204,9 @@ def assert_no_memory_leaks(s, argv=[]): def skip_if_memtesting_not_supported(): return pytest.mark.skipif( - not Binding().lib.Cryptography_HAS_MEM_FUNCTIONS, - reason="Requires OpenSSL memory functions (>=1.1.0)", + not Binding().lib.Cryptography_HAS_MEM_FUNCTIONS + or platform.python_implementation() == "PyPy", + reason="Requires OpenSSL memory functions (>=1.1.0) and not PyPy", ) @@ -249,76 +279,6 @@ def func(): @pytest.mark.skip_fips(reason="FIPS self-test sets allow_customize = 0") @skip_if_memtesting_not_supported() class TestOpenSSLMemoryLeaks: - @pytest.mark.parametrize( - "path", ["x509/PKITS_data/certs/ValidcRLIssuerTest28EE.crt"] - ) - def test_der_x509_certificate_extensions(self, path): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(path): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - - import cryptography_vectors - - with cryptography_vectors.open_vector_file(path, "rb") as f: - cert = x509.load_der_x509_certificate( - f.read(), backend - ) - - cert.extensions - """ - ), - [path], - ) - - @pytest.mark.parametrize("path", ["x509/cryptography.io.pem"]) - def test_pem_x509_certificate_extensions(self, path): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(path): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - - import cryptography_vectors - - with cryptography_vectors.open_vector_file(path, "rb") as f: - cert = x509.load_pem_x509_certificate( - f.read(), backend - ) - - cert.extensions - """ - ), - [path], - ) - - def test_x509_csr_extensions(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import rsa - - private_key = rsa.generate_private_key( - key_size=2048, public_exponent=65537, backend=backend - ) - cert = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([]) - ).add_extension( - x509.OCSPNoCheck(), critical=False - ).sign(private_key, hashes.SHA256(), backend) - - cert.extensions - """ - ) - ) - def test_ec_private_numbers_private_key(self): assert_no_memory_leaks( textwrap.dedent( @@ -372,31 +332,6 @@ def func(): ) ) - def test_create_ocsp_request(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.x509 import ocsp - import cryptography_vectors - - path = "x509/PKITS_data/certs/ValidcRLIssuerTest28EE.crt" - with cryptography_vectors.open_vector_file(path, "rb") as f: - cert = x509.load_der_x509_certificate( - f.read(), backend - ) - builder = ocsp.OCSPRequestBuilder() - builder = builder.add_certificate( - cert, cert, hashes.SHA1() - ).add_extension(x509.OCSPNonce(b"0000"), False) - req = builder.build() - """ - ) - ) - @pytest.mark.parametrize( "path", ["pkcs12/cert-aes256cbc-no-key.p12", "pkcs12/cert-key-aes256cbc.p12"], @@ -420,119 +355,6 @@ def func(path): [path], ) - def test_create_crl_with_idp(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - import datetime - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.x509.oid import NameOID - - key = ec.generate_private_key(ec.SECP256R1(), backend) - last_update = datetime.datetime(2002, 1, 1, 12, 1) - next_update = datetime.datetime(2030, 1, 1, 12, 1) - idp = x509.IssuingDistributionPoint( - full_name=None, - relative_name=x509.RelativeDistinguishedName([ - x509.NameAttribute( - oid=x509.NameOID.ORGANIZATION_NAME, value=u"PyCA") - ]), - only_contains_user_certs=False, - only_contains_ca_certs=True, - only_some_reasons=None, - indirect_crl=False, - only_contains_attribute_certs=False, - ) - builder = x509.CertificateRevocationListBuilder().issuer_name( - x509.Name([ - x509.NameAttribute( - NameOID.COMMON_NAME, u"cryptography.io CA" - ) - ]) - ).last_update( - last_update - ).next_update( - next_update - ).add_extension( - idp, True - ) - - crl = builder.sign(key, hashes.SHA256(), backend) - crl.extensions.get_extension_for_class( - x509.IssuingDistributionPoint - ) - """ - ) - ) - - def test_create_certificate_with_extensions(self): - assert_no_memory_leaks( - textwrap.dedent( - """ - def func(): - import datetime - - from cryptography import x509 - from cryptography.hazmat.backends.openssl import backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.x509.oid import ( - AuthorityInformationAccessOID, ExtendedKeyUsageOID, NameOID - ) - - private_key = ec.generate_private_key(ec.SECP256R1(), backend) - - not_valid_before = datetime.datetime.now() - not_valid_after = not_valid_before + datetime.timedelta(days=365) - - aia = x509.AuthorityInformationAccess([ - x509.AccessDescription( - AuthorityInformationAccessOID.OCSP, - x509.UniformResourceIdentifier(u"http://ocsp.domain.com") - ), - x509.AccessDescription( - AuthorityInformationAccessOID.CA_ISSUERS, - x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") - ) - ]) - sans = [u'*.example.org', u'foobar.example.net'] - san = x509.SubjectAlternativeName(list(map(x509.DNSName, sans))) - - ski = x509.SubjectKeyIdentifier.from_public_key( - private_key.public_key() - ) - eku = x509.ExtendedKeyUsage([ - ExtendedKeyUsageOID.CLIENT_AUTH, - ExtendedKeyUsageOID.SERVER_AUTH, - ExtendedKeyUsageOID.CODE_SIGNING, - ]) - - builder = x509.CertificateBuilder().serial_number( - 777 - ).issuer_name(x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), - ])).subject_name(x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), - ])).public_key( - private_key.public_key() - ).add_extension( - aia, critical=False - ).not_valid_before( - not_valid_before - ).not_valid_after( - not_valid_after - ) - - cert = builder.sign(private_key, hashes.SHA256(), backend) - cert.extensions - """ - ) - ) - def test_write_pkcs12_key_and_certificates(self): assert_no_memory_leaks( textwrap.dedent( diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index abc0e15..c061c9b 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -2,13 +2,13 @@ # 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, - _consume_errors, + _legacy_provider_error, _openssl_assert, _verify_package_version, ) @@ -21,18 +21,13 @@ def test_binding_loads(self): assert binding.lib assert binding.ffi - def test_add_engine_more_than_once(self): - b = Binding() - b._register_osrandom_engine() - assert b.lib.ERR_get_error() == 0 - 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 b.lib.CRYPTOGRAPHY_IS_BORINGSSL: assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) + 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) @@ -47,7 +42,7 @@ def test_ssl_options(self): # SSL_OP_ALL is 0 on BoringSSL if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_method()) + 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) @@ -58,23 +53,6 @@ def test_ssl_options(self): assert resp == expected_options assert b.lib.SSL_get_options(ssl) == expected_options - def test_ssl_mode(self): - # Test that we're properly handling 32-bit unsigned on all platforms. - b = Binding() - # SSL_OP_ALL is 0 on BoringSSL - if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: - assert b.lib.SSL_OP_ALL > 0 - ctx = b.lib.SSL_CTX_new(b.lib.SSLv23_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_mode(ssl) - resp = b.lib.SSL_set_mode(ssl, b.lib.SSL_OP_ALL) - expected_options = current_options | b.lib.SSL_OP_ALL - assert resp == expected_options - assert b.lib.SSL_get_mode(ssl) == expected_options - def test_conditional_removal(self): b = Binding() @@ -102,7 +80,22 @@ def test_openssl_assert_error_on_stack(self): if not b.lib.CRYPTOGRAPHY_IS_BORINGSSL: assert b"data not multiple of block length" in error.reason_text - def test_check_startup_errors_are_allowed(self): + def test_version_mismatch(self): + with pytest.raises(ImportError): + _verify_package_version("nottherightversion") + + def test_legacy_provider_error(self): + with pytest.raises(RuntimeError): + _legacy_provider_error(False) + + _legacy_provider_error(True) + + 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, @@ -111,9 +104,11 @@ def test_check_startup_errors_are_allowed(self): b"", -1, ) - b._register_osrandom_engine() - assert _consume_errors(b.lib) == [] + with pytest.raises(InternalError) as exc_info: + rust_openssl.raise_openssl_error() - def test_version_mismatch(self): - with pytest.raises(ImportError): - _verify_package_version("nottherightversion") + 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 b.lib.CRYPTOGRAPHY_IS_BORINGSSL: + assert b"data not multiple of block length" in error.reason_text diff --git a/tests/hazmat/primitives/fixtures_dsa.py b/tests/hazmat/primitives/fixtures_dsa.py index eca0ec4..6675a2c 100644 --- a/tests/hazmat/primitives/fixtures_dsa.py +++ b/tests/hazmat/primitives/fixtures_dsa.py @@ -9,7 +9,6 @@ DSAPublicNumbers, ) - DSA_KEY_1024 = DSAPrivateNumbers( public_numbers=DSAPublicNumbers( parameter_numbers=DSAParameterNumbers( diff --git a/tests/hazmat/primitives/fixtures_ec.py b/tests/hazmat/primitives/fixtures_ec.py index 317c2ab..fa671ac 100644 --- a/tests/hazmat/primitives/fixtures_ec.py +++ b/tests/hazmat/primitives/fixtures_ec.py @@ -5,7 +5,6 @@ from cryptography.hazmat.primitives.asymmetric import ec - EC_KEY_SECT571R1 = ec.EllipticCurvePrivateNumbers( private_value=int( "213997069697108634621868251335076179190383272087548888968788698953" diff --git a/tests/hazmat/primitives/fixtures_rsa.py b/tests/hazmat/primitives/fixtures_rsa.py index f6b5c3b..09b32ab 100644 --- a/tests/hazmat/primitives/fixtures_rsa.py +++ b/tests/hazmat/primitives/fixtures_rsa.py @@ -8,7 +8,6 @@ RSAPublicNumbers, ) - RSA_KEY_512 = RSAPrivateNumbers( p=int( "d57846898d5c0de249c08467586cb458fa9bc417cdf297f73cfc52281b787cd9", 16 diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py index ea39a21..007ecfe 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/test_3des.py @@ -14,8 +14,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py index dcbf76b..c6811a4 100644 --- a/tests/hazmat/primitives/test_aead.py +++ b/tests/hazmat/primitives/test_aead.py @@ -17,13 +17,13 @@ ChaCha20Poly1305, ) -from .utils import _load_all_params from ...utils import ( load_nist_ccm_vectors, load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, ) +from .utils import _load_all_params class FakeData(bytes): @@ -464,10 +464,22 @@ def test_buffer_protocol(self, backend): computed_pt = aesgcm.decrypt(nonce, ct, ad) assert computed_pt == pt aesgcm2 = AESGCM(bytearray(key)) - ct2 = aesgcm2.encrypt(bytearray(nonce), pt, ad) + ct2 = aesgcm2.encrypt(bytearray(nonce), bytearray(pt), bytearray(ad)) assert ct2 == ct - computed_pt2 = aesgcm2.decrypt(bytearray(nonce), ct2, ad) + 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( @@ -616,7 +628,7 @@ def test_buffer_protocol(self, backend): not _aead_supported(AESSIV), reason="Does not support AESSIV", ) -class TestAESSIV(object): +class TestAESSIV: def test_data_too_large(self): key = AESSIV.generate_key(256) aessiv = AESSIV(key) @@ -625,7 +637,17 @@ def test_data_too_large(self): aessiv.encrypt(FakeData(), None) with pytest.raises(OverflowError): - aessiv.encrypt(b"", [FakeData()]) + aessiv.encrypt(b"irrelevant", [FakeData()]) + + 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(ValueError): + aessiv.decrypt(b"", None) def test_vectors(self, backend, subtests): vectors = load_vectors_from_file( diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 9d68ef2..1f3dfd0 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -10,9 +10,9 @@ from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from .utils import _load_all_params, generate_encrypt_test from ...doubles import DummyMode from ...utils import load_nist_vectors +from .utils import _load_all_params, generate_encrypt_test @pytest.mark.supported( @@ -61,9 +61,7 @@ def test_xts_too_short(self, backend): enc.update(b"0" * 15) @pytest.mark.supported( - only_if=lambda backend: ( - backend._lib.CRYPTOGRAPHY_OPENSSL_111D_OR_GREATER - ), + only_if=lambda backend: (not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL), skip_message="duplicate key encryption error added in OpenSSL 1.1.1d", ) def test_xts_no_duplicate_keys_encryption(self, backend): @@ -274,7 +272,7 @@ 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("AES in {} mode not supported".format(mode.name)) + 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() @@ -298,7 +296,7 @@ def test_buffer_protocol_alternate_modes(mode, backend): 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("AES in {} mode not supported".format(mode.name)) + 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() diff --git a/tests/hazmat/primitives/test_aes_gcm.py b/tests/hazmat/primitives/test_aes_gcm.py index 9220e9e..c1154a9 100644 --- a/tests/hazmat/primitives/test_aes_gcm.py +++ b/tests/hazmat/primitives/test_aes_gcm.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, base, modes -from .utils import generate_aead_test from ...utils import load_nist_vectors +from .utils import generate_aead_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py index 5bc23f9..b589518 100644 --- a/tests/hazmat/primitives/test_arc4.py +++ b/tests/hazmat/primitives/test_arc4.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms -from .utils import generate_stream_encryption_test from ...utils import load_nist_vectors +from .utils import generate_stream_encryption_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index fa2c4f5..b831de1 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -15,12 +15,12 @@ modes, ) +from ...doubles import DummyCipherAlgorithm, DummyMode +from ...utils import raises_unsupported_algorithm from .utils import ( generate_aead_exception_test, generate_aead_tag_exception_test, ) -from ...doubles import DummyCipherAlgorithm, DummyMode -from ...utils import raises_unsupported_algorithm class TestCipher: diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py index 4ff8c1f..b8f34df 100644 --- a/tests/hazmat/primitives/test_blowfish.py +++ b/tests/hazmat/primitives/test_blowfish.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_camellia.py b/tests/hazmat/primitives/test_camellia.py index 8ec7551..d6f1fca 100644 --- a/tests/hazmat/primitives/test_camellia.py +++ b/tests/hazmat/primitives/test_camellia.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_cryptrec_vectors, load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py index 6c6f0c8..327a463 100644 --- a/tests/hazmat/primitives/test_cast5.py +++ b/tests/hazmat/primitives/test_cast5.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( @@ -26,7 +26,7 @@ class TestCAST5ModeECB: os.path.join("ciphers", "CAST5"), ["cast5-ecb.txt"], lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda **kwargs: modes.ECB(), ) @@ -44,7 +44,7 @@ class TestCAST5ModeCBC: os.path.join("ciphers", "CAST5"), ["cast5-cbc.txt"], lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @@ -62,7 +62,7 @@ class TestCAST5ModeOFB: os.path.join("ciphers", "CAST5"), ["cast5-ofb.txt"], lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @@ -80,7 +80,7 @@ class TestCAST5ModeCFB: os.path.join("ciphers", "CAST5"), ["cast5-cfb.txt"], lambda key, **kwargs: algorithms._CAST5Internal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py index 4dd1b08..5337465 100644 --- a/tests/hazmat/primitives/test_chacha20.py +++ b/tests/hazmat/primitives/test_chacha20.py @@ -11,8 +11,8 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms -from .utils import _load_all_params from ...utils import load_nist_vectors +from .utils import _load_all_params @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index 02127dd..bf3b047 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -318,6 +318,14 @@ def test_update_into_buffer_too_small(self, backend): with pytest.raises(ValueError): encryptor.update_into(b"testing", buf) + def test_update_into_immutable(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = b"\x00" * 32 + with pytest.raises((TypeError, BufferError)): + encryptor.update_into(b"testing", buf) + @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py index 9596705..c9e7fdd 100644 --- a/tests/hazmat/primitives/test_cmac.py +++ b/tests/hazmat/primitives/test_cmac.py @@ -7,10 +7,7 @@ import pytest -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidSignature, -) +from cryptography.exceptions import AlreadyFinalized, InvalidSignature from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, @@ -18,11 +15,7 @@ ) from cryptography.hazmat.primitives.cmac import CMAC -from ...utils import ( - load_nist_vectors, - load_vectors_from_file, -) - +from ...utils import load_nist_vectors, load_vectors_from_file vectors_aes128 = load_vectors_from_file( "CMAC/nist-800-38b-aes128.txt", load_nist_vectors diff --git a/tests/hazmat/primitives/test_concatkdf.py b/tests/hazmat/primitives/test_concatkdf.py index bd0117c..f0dd188 100644 --- a/tests/hazmat/primitives/test_concatkdf.py +++ b/tests/hazmat/primitives/test_concatkdf.py @@ -9,8 +9,10 @@ from cryptography.exceptions import AlreadyFinalized, InvalidKey from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHMAC -from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash +from cryptography.hazmat.primitives.kdf.concatkdf import ( + ConcatKDFHash, + ConcatKDFHMAC, +) class TestConcatKDFHash: diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index b8216a9..098d6e1 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -13,9 +13,9 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh -from .fixtures_dh import FFDH3072_P from ...doubles import DummyKeySerializationEncryption from ...utils import load_nist_vectors, load_vectors_from_file +from .fixtures_dh import FFDH3072_P # RFC 3526 P_1536 = int( @@ -148,6 +148,10 @@ def test_unsupported_generator_generate_dh(self, backend): with pytest.raises(ValueError): dh.generate_parameters(7, 512, backend) + def test_large_key_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(2, 1 << 30) + @pytest.mark.skip_fips(reason="non-FIPS parameters") def test_dh_parameters_supported(self, backend): valid_p = int( @@ -460,6 +464,31 @@ def test_dh_vectors_with_q(self, backend, vector): assert int.from_bytes(symkey1, "big") == int(vector["z"], 16) assert int.from_bytes(symkey2, "big") == int(vector["z"], 16) + @pytest.mark.supported( + only_if=lambda backend: backend.dh_x942_serialization_supported(), + skip_message="DH X9.42 not supported", + ) + def test_public_key_equality(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhpub.pem"), + lambda pemfile: pemfile.read(), + mode="rb", + ) + key_bytes_2 = load_vectors_from_file( + os.path.join("asymmetric", "DH", "dhpub_rfc5114_2.pem"), + lambda pemfile: pemfile.read(), + mode="rb", + ) + key1 = serialization.load_pem_public_key(key_bytes) + key2 = serialization.load_pem_public_key(key_bytes) + key3 = serialization.load_pem_public_key(key_bytes_2) + 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.dh_supported(), diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 424ffce..0092086 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -9,6 +9,7 @@ import pytest +from cryptography import utils from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa @@ -17,14 +18,14 @@ encode_dss_signature, ) -from .fixtures_dsa import DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 -from .utils import skip_fips_traditional_openssl from ...doubles import DummyHashAlgorithm, DummyKeySerializationEncryption from ...utils import ( load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, load_vectors_from_file, ) +from .fixtures_dsa import DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 +from .utils import skip_fips_traditional_openssl _ALGORITHMS_DICT: typing.Dict[str, hashes.HashAlgorithm] = { "SHA1": hashes.SHA1(), @@ -383,6 +384,20 @@ def test_large_p(self, backend): 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] + @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), @@ -698,6 +713,10 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): (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): @@ -920,9 +939,11 @@ def test_public_bytes_openssh(self, backend): ) key = serialization.load_pem_public_key(key_bytes, backend) - ssh_bytes = key.public_bytes( - serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH - ) + 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" diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index e484486..601edcc 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -12,17 +12,14 @@ import pytest -from cryptography import exceptions, utils, x509 +from cryptography import exceptions, x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( Prehashed, encode_dss_signature, ) -from cryptography.utils import CryptographyDeprecationWarning -from .fixtures_ec import EC_KEY_SECP384R1 -from .utils import skip_fips_traditional_openssl from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_fips_ecdsa_key_pair_vectors, @@ -32,6 +29,8 @@ 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, @@ -171,73 +170,6 @@ def test_invalid_private_numbers_public_numbers(): ec.EllipticCurvePrivateNumbers(1, None) # type: ignore[arg-type] -def test_encode_point(): - # secp256r1 point - x = int( - "233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec", 16 - ) - y = int( - "3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e", 16 - ) - pn = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()) - with pytest.warns(utils.PersistentlyDeprecated2019): - data = pn.encode_point() - assert data == binascii.unhexlify( - "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" - "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" - ) - - -def test_from_encoded_point(): - # secp256r1 point - data = binascii.unhexlify( - "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" - "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" - ) - with pytest.warns(CryptographyDeprecationWarning): - pn = ec.EllipticCurvePublicNumbers.from_encoded_point( - ec.SECP256R1(), data - ) - assert pn.x == int( - "233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec", 16 - ) - assert pn.y == int( - "3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e", 16 - ) - - -def test_from_encoded_point_invalid_length(): - bad_data = binascii.unhexlify( - "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" - "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460" - ) - with pytest.raises(ValueError): - with pytest.warns(CryptographyDeprecationWarning): - ec.EllipticCurvePublicNumbers.from_encoded_point( - ec.SECP384R1(), bad_data - ) - - -def test_from_encoded_point_unsupported_point_no_backend(): - # set to point type 2. - unsupported_type = binascii.unhexlify( - "02233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22a" - ) - with pytest.raises(ValueError): - with pytest.warns(CryptographyDeprecationWarning): - ec.EllipticCurvePublicNumbers.from_encoded_point( - ec.SECP256R1(), unsupported_type - ) - - -def test_from_encoded_point_not_a_curve(): - with pytest.raises(TypeError): - with pytest.warns(CryptographyDeprecationWarning): - ec.EllipticCurvePublicNumbers.from_encoded_point( - "notacurve", b"\x04data" # type: ignore[arg-type] - ) - - def test_ec_public_numbers_repr(): pn = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) assert repr(pn) == "" @@ -633,7 +565,7 @@ def test_verify_prehashed_digest_mismatch(self, backend): ) -class TestECNumbersEquality: +class TestECEquality: def test_public_numbers_eq(self): pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) assert pub == ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) @@ -669,6 +601,19 @@ def test_private_numbers_ne(self): ) 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() + class TestECSerialization: @pytest.mark.parametrize( diff --git a/tests/hazmat/primitives/test_ed25519.py b/tests/hazmat/primitives/test_ed25519.py index f3b6ed8..2501f1c 100644 --- a/tests/hazmat/primitives/test_ed25519.py +++ b/tests/hazmat/primitives/test_ed25519.py @@ -15,6 +15,7 @@ Ed25519PublicKey, ) +from ...doubles import DummyKeySerializationEncryption from ...utils import load_vectors_from_file, raises_unsupported_algorithm @@ -92,6 +93,20 @@ def test_sign_verify_input(self, backend, subtests): ) 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") @@ -142,18 +157,24 @@ def test_invalid_length_from_private_bytes(self, backend): def test_invalid_private_bytes(self, backend): key = Ed25519PrivateKey.generate() - with pytest.raises(ValueError): + 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, - None, # type: ignore[arg-type] + DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): @@ -163,6 +184,13 @@ def test_invalid_private_bytes(self, backend): 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): @@ -181,6 +209,11 @@ def test_invalid_public_bytes(self, backend): 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"), [ @@ -212,6 +245,13 @@ def test_invalid_public_bytes(self, backend): 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( @@ -233,3 +273,24 @@ def test_buffer_protocol(self, backend): ) == 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] diff --git a/tests/hazmat/primitives/test_ed448.py b/tests/hazmat/primitives/test_ed448.py index cf96880..650cdda 100644 --- a/tests/hazmat/primitives/test_ed448.py +++ b/tests/hazmat/primitives/test_ed448.py @@ -15,6 +15,7 @@ Ed448PublicKey, ) +from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_nist_vectors, load_vectors_from_file, @@ -108,12 +109,14 @@ def test_pub_priv_bytes_raw(self, vector, backend): ) == 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( @@ -121,6 +124,7 @@ def test_pub_priv_bytes_raw(self, vector, backend): ) == pk ) + assert public_key.public_bytes_raw() == pk @pytest.mark.parametrize( ("encoding", "fmt", "encryption", "passwd", "load_func"), @@ -189,18 +193,24 @@ def test_invalid_length_from_private_bytes(self, backend): def test_invalid_private_bytes(self, backend): key = Ed448PrivateKey.generate() - with pytest.raises(ValueError): + 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, - None, # type: ignore[arg-type] + DummyKeySerializationEncryption(), ) with pytest.raises(ValueError): @@ -257,3 +267,24 @@ def test_malleability(self, backend): 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] diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py index d5916f0..bde8111 100644 --- a/tests/hazmat/primitives/test_hash_vectors.py +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives import hashes -from .utils import _load_all_params, generate_hash_test from ...utils import load_hash_vectors, load_nist_vectors +from .utils import _load_all_params, generate_hash_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index 37744e7..1d09677 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -10,9 +10,9 @@ from cryptography.exceptions import AlreadyFinalized, _Reasons from cryptography.hazmat.primitives import hashes -from .utils import generate_base_hash_test from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm +from .utils import generate_base_hash_test class TestHashContext: diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index cc001ba..0bd5c97 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -12,10 +12,7 @@ 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, -) +from ...utils import load_nist_vectors, load_vectors_from_file class TestHKDF: diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py index 711d1b5..080aa1b 100644 --- a/tests/hazmat/primitives/test_hkdf_vectors.py +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -9,8 +9,8 @@ from cryptography.hazmat.primitives import hashes -from .utils import generate_hkdf_test from ...utils import load_nist_vectors +from .utils import generate_hkdf_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 414385c..78bb262 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -14,9 +14,9 @@ ) from cryptography.hazmat.primitives import hashes, hmac -from .utils import generate_base_hmac_test from ...doubles import DummyHashAlgorithm from ...utils import raises_unsupported_algorithm +from .utils import generate_base_hmac_test @pytest.mark.supported( @@ -88,3 +88,8 @@ def test_buffer_protocol(self, backend): 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 diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py index 703065a..790993a 100644 --- a/tests/hazmat/primitives/test_hmac_vectors.py +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -9,8 +9,8 @@ from cryptography.hazmat.primitives import hashes, hmac -from .utils import generate_hmac_test from ...utils import load_hash_vectors +from .utils import generate_hmac_test @pytest.mark.supported( diff --git a/tests/hazmat/primitives/test_idea.py b/tests/hazmat/primitives/test_idea.py index d591fe4..6631a93 100644 --- a/tests/hazmat/primitives/test_idea.py +++ b/tests/hazmat/primitives/test_idea.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( @@ -26,7 +26,7 @@ class TestIDEAModeECB: os.path.join("ciphers", "IDEA"), ["idea-ecb.txt"], lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda **kwargs: modes.ECB(), ) @@ -44,7 +44,7 @@ class TestIDEAModeCBC: os.path.join("ciphers", "IDEA"), ["idea-cbc.txt"], lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @@ -62,7 +62,7 @@ class TestIDEAModeOFB: os.path.join("ciphers", "IDEA"), ["idea-ofb.txt"], lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @@ -80,7 +80,7 @@ class TestIDEAModeCFB: os.path.join("ciphers", "IDEA"), ["idea-cfb.txt"], lambda key, **kwargs: algorithms._IDEAInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) diff --git a/tests/hazmat/primitives/test_kbkdf.py b/tests/hazmat/primitives/test_kbkdf.py index bb8ebea..4329e3d 100644 --- a/tests/hazmat/primitives/test_kbkdf.py +++ b/tests/hazmat/primitives/test_kbkdf.py @@ -11,9 +11,9 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.kdf.kbkdf import ( - CounterLocation, KBKDFCMAC, KBKDFHMAC, + CounterLocation, Mode, ) diff --git a/tests/hazmat/primitives/test_kbkdf_vectors.py b/tests/hazmat/primitives/test_kbkdf_vectors.py index fef75fe..cab817b 100644 --- a/tests/hazmat/primitives/test_kbkdf_vectors.py +++ b/tests/hazmat/primitives/test_kbkdf_vectors.py @@ -5,8 +5,8 @@ import os -from .utils import generate_kbkdf_counter_mode_test from ...utils import load_nist_kbkdf_vectors +from .utils import generate_kbkdf_counter_mode_test class TestCounterKDFCounterMode: diff --git a/tests/hazmat/primitives/test_keywrap.py b/tests/hazmat/primitives/test_keywrap.py index b2fa05d..7dfb809 100644 --- a/tests/hazmat/primitives/test_keywrap.py +++ b/tests/hazmat/primitives/test_keywrap.py @@ -11,8 +11,8 @@ from cryptography.hazmat.primitives import keywrap from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import _load_all_params from ...utils import load_nist_vectors +from .utils import _load_all_params class TestAESKeyWrap: diff --git a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py index f092894..db44114 100644 --- a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py +++ b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py @@ -2,23 +2,36 @@ # 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 generate_pbkdf2_test -from ...utils import load_nist_vectors +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", ) -class TestPBKDF2HMACSHA1: - test_pbkdf2_sha1 = generate_pbkdf2_test( +def test_pbkdf2_hmacsha1_vectors(subtests, backend): + params = load_vectors_from_file( + os.path.join("KDF", "rfc-6070-PBKDF2-SHA1.txt"), load_nist_vectors, - "KDF", - ["rfc-6070-PBKDF2-SHA1.txt"], - hashes.SHA1(), ) + 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"] diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index c9ef57e..f44fdd1 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -15,8 +15,8 @@ from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, - ed25519, ed448, + ed25519, rsa, ) from cryptography.hazmat.primitives.serialization import ( @@ -186,9 +186,9 @@ def test_buffer_protocol(self, backend): (None, b"name2", None, "name-2-no-pwd.p12", None), (None, None, b"name3", "name-3-no-pwd.p12", None), ( - "☺".encode("utf-8"), - "ä".encode("utf-8"), - "ç".encode("utf-8"), + "☺".encode(), + "ä".encode(), + "ç".encode(), "name-unicode-no-pwd.p12", None, ), @@ -199,9 +199,9 @@ def test_buffer_protocol(self, backend): (None, b"name2", None, "name-2-pwd.p12", b"password"), (None, None, b"name3", "name-3-pwd.p12", b"password"), ( - "☺".encode("utf-8"), - "ä".encode("utf-8"), - "ç".encode("utf-8"), + "☺".encode(), + "ä".encode(), + "ç".encode(), "name-unicode-pwd.p12", b"password", ), @@ -240,8 +240,8 @@ def test_load_object( (b"name2", None, "no-cert-name-2-no-pwd.p12", None), (None, b"name3", "no-cert-name-3-no-pwd.p12", None), ( - "☹".encode("utf-8"), - "ï".encode("utf-8"), + "☹".encode(), + "ï".encode(), "no-cert-name-unicode-no-pwd.p12", None, ), @@ -250,8 +250,8 @@ def test_load_object( (b"name2", None, "no-cert-name-2-pwd.p12", b"password"), (None, b"name3", "no-cert-name-3-pwd.p12", b"password"), ( - "☹".encode("utf-8"), - "ï".encode("utf-8"), + "☹".encode(), + "ï".encode(), "no-cert-name-unicode-pwd.p12", b"password", ), @@ -794,12 +794,14 @@ def test_certificate_hash(self, backend): def test_certificate_repr(self, backend): cert = _load_cert(backend, os.path.join("x509", "cryptography.io.pem")) - assert repr( - PKCS12Certificate(cert, None) - ) == "".format(repr(cert)) - assert repr( - PKCS12Certificate(cert, b"a") - ) == "".format(repr(cert)) + 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): @@ -944,9 +946,9 @@ def test_key_and_certificates_repr(self, backend): [PKCS12Certificate(cert2, b"name2")], ) ) - == ", additional_certs=[])>".format( + == ", additional_certs=[])>".format( key, cert, cert2, diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 138bc0f..f6ad641 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -3,6 +3,7 @@ # for complete details. +import email.parser import os import typing @@ -14,7 +15,6 @@ from cryptography.hazmat.primitives.asymmetric import ed25519, rsa from cryptography.hazmat.primitives.serialization import pkcs7 -from .utils import skip_signature_hash from ...utils import load_vectors_from_file, raises_unsupported_algorithm @@ -89,6 +89,12 @@ def test_load_pkcs7_unsupported_type(self, backend): mode="rb", ) + def test_load_pkcs7_empty_certificates(self, backend): + der = b"\x30\x0B\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02" + + certificates = pkcs7.load_der_pkcs7_certificates(der) + assert certificates == [] + # We have no public verification API and won't be adding one until we get # some requirements from users so this function exists to give us basic @@ -133,8 +139,11 @@ def _pkcs7_verify(encoding, sig, msg, certs, options, backend): ) else: msg_bio = backend._bytes_to_bio(msg) + # libressl 3.7.0 has a bug when NULL is passed as an `out_bio`. Work + # around it for now. + out_bio = backend._create_mem_bio_gc() res = backend._lib.PKCS7_verify( - p7, backend._ffi.NULL, store, msg_bio.bio, backend._ffi.NULL, flags + p7, backend._ffi.NULL, store, msg_bio.bio, out_bio, flags ) backend.openssl_assert(res == 1) # OpenSSL 3.0 leaves a random bio error on the stack: @@ -147,7 +156,7 @@ 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 + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -287,6 +296,7 @@ def test_smime_sign_detached(self, backend): 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. @@ -296,8 +306,17 @@ def test_smime_sign_detached(self, backend): # 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) + signed_data = message.get_payload()[0].get_payload().encode() _pkcs7_verify( - serialization.Encoding.SMIME, sig, data, [cert], options, backend + serialization.Encoding.SMIME, + sig, + signed_data, + [cert], + options, + backend, ) assert data not in sig_binary _pkcs7_verify( @@ -321,6 +340,31 @@ def test_sign_byteslike(self, backend): sig = builder.sign(serialization.Encoding.SMIME, options) assert bytes(data) in sig + _pkcs7_verify( + serialization.Encoding.SMIME, + sig, + data, + [cert], + options, + backend, + ) + + data = bytearray(b"") + builder = ( + pkcs7.PKCS7SignatureBuilder() + .set_data(data) + .add_signer(cert, key, hashes.SHA256()) + ) + + sig = builder.sign(serialization.Encoding.SMIME, options) + _pkcs7_verify( + serialization.Encoding.SMIME, + sig, + data, + [cert], + options, + backend, + ) def test_sign_pem(self, backend): data = b"hello world" @@ -345,7 +389,6 @@ def test_sign_pem(self, backend): @pytest.mark.parametrize( ("hash_alg", "expected_value"), [ - (hashes.SHA1(), b"\x06\x05+\x0e\x03\x02\x1a"), (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"), @@ -354,8 +397,6 @@ def test_sign_pem(self, backend): def test_sign_alternate_digests_der( self, hash_alg, expected_value, backend ): - skip_signature_hash(backend, hash_alg) - data = b"hello world" cert, key = _load_cert_key() builder = ( @@ -373,7 +414,6 @@ def test_sign_alternate_digests_der( @pytest.mark.parametrize( ("hash_alg", "expected_value"), [ - (hashes.SHA1(), b"sha1"), (hashes.SHA256(), b"sha-256"), (hashes.SHA384(), b"sha-384"), (hashes.SHA512(), b"sha-512"), @@ -382,8 +422,6 @@ def test_sign_alternate_digests_der( def test_sign_alternate_digests_detached( self, hash_alg, expected_value, backend ): - skip_signature_hash(backend, hash_alg) - data = b"hello world" cert, key = _load_cert_key() builder = ( @@ -496,10 +534,14 @@ def test_sign_text(self, backend): # 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 b"text/plain" in sig_pem - # When passing the Text option the header is prepended so the actual - # signed data is this. - signed_data = b"Content-Type: text/plain\r\n\r\nhello world" + 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) + signed_data = message.get_payload()[0].as_bytes( + policy=message.policy.clone(linesep="\r\n") + ) _pkcs7_verify( serialization.Encoding.SMIME, sig_pem, @@ -588,7 +630,7 @@ def test_multiple_signers(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 + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -625,7 +667,7 @@ def test_multiple_signers_different_hash_algs(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 + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -741,7 +783,7 @@ def test_ordering(self, backend): list(reversed(certs)), serialization.Encoding.DER ) certs2 = pkcs7.load_der_pkcs7_certificates(p7) - assert certs != certs2 + assert certs == certs2 def test_pem_matches_vector(self, backend): p7_pem = load_vectors_from_file( @@ -771,7 +813,7 @@ def test_invalid_types(self): ) with pytest.raises(TypeError): pkcs7.serialize_certificates( - "not a list of certs", # type: ignore[arg-type] + object(), # type: ignore[arg-type] serialization.Encoding.PEM, ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 6f083cb..017e02d 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -15,17 +15,30 @@ _Reasons, ) from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ( - padding, - rsa, - utils as asym_utils, -) +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, @@ -37,10 +50,6 @@ RSA_KEY_1536, RSA_KEY_2048, RSA_KEY_2048_ALT, - RSA_KEY_512, - RSA_KEY_522, - RSA_KEY_599, - RSA_KEY_745, RSA_KEY_CORRUPTED, ) from .utils import ( @@ -48,18 +57,16 @@ generate_rsa_verification_test, skip_fips_traditional_openssl, ) -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, -) + + +@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): @@ -72,9 +79,7 @@ def _check_fips_key_length(backend, private_key): backend._fips_enabled and private_key.key_size < backend._fips_rsa_min_key_size ): - pytest.skip( - "Key size not FIPS compliant: {}".format(private_key.key_size) - ) + pytest.skip(f"Key size not FIPS compliant: {private_key.key_size}") def _check_rsa_private_numbers_if_serializable(key): @@ -118,7 +123,7 @@ def _build_oaep_sha2_vectors(): load_vectors_from_file( os.path.join( base_path, - "oaep-{}-{}.txt".format(mgf1alg.name, oaepalg.name), + f"oaep-{mgf1alg.name}-{oaepalg.name}.txt", ), load_pkcs1_vectors, ) @@ -137,9 +142,7 @@ def _skip_pss_hash_algorithm_unsupported(backend, hash_alg): mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH ) ): - pytest.skip( - "Does not support {} in MGF1 using PSS.".format(hash_alg.name) - ) + pytest.skip(f"Does not support {hash_alg.name} in MGF1 using PSS.") def test_skip_pss_hash_algorithm_unsupported(backend): @@ -182,11 +185,9 @@ class TestRSA: 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("Key size not FIPS compliant: {}".format(key_size)) + pytest.skip(f"Key size not FIPS compliant: {key_size}") if public_exponent < backend._fips_rsa_min_public_exponent: - pytest.skip( - "Exponent not FIPS compliant: {}".format(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 @@ -283,7 +284,7 @@ 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 + p.read(), password=None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -361,7 +362,10 @@ def test_load_pss_unsupported(self, backend): ) def test_oaep_label_decrypt(self, vector, backend): private_key = serialization.load_der_private_key( - binascii.unhexlify(vector["key"]), None, backend + binascii.unhexlify(vector["key"]), + None, + backend, + unsafe_skip_rsa_key_validation=True, ) assert isinstance(private_key, rsa.RSAPrivateKey) assert vector["oaepdigest"] == b"SHA512" @@ -392,8 +396,8 @@ def test_oaep_label_decrypt(self, vector, backend): ), skip_message="Does not support RSA OAEP labels", ) - def test_oaep_label_roundtrip(self, msg, label, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -426,8 +430,8 @@ def test_oaep_label_roundtrip(self, msg, label, backend): ), skip_message="Does not support RSA OAEP labels", ) - def test_oaep_wrong_label(self, enclabel, declabel, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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, @@ -447,8 +451,21 @@ def test_oaep_wrong_label(self, enclabel, declabel, backend): ), ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_encryption_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5.", + ) def test_lazy_blinding(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + # We don't want to reuse the rsa_key_2048 fixture here because lazy + # blinding mutates the object to add the blinding factor on + # the first call to decrypt/sign. Since we reuse rsa_key_2048 in + # many tests we can't properly test blinding, which will (likely) + # already be set on the fixture. + private_key = RSA_KEY_2048.private_key( + unsafe_skip_rsa_key_validation=True + ) public_key = private_key.public_key() msg = b"encrypt me!" ct = public_key.encrypt( @@ -487,7 +504,7 @@ class TestRSASignature: ), skip_message="Does not support SHA1 signature.", ) - def test_pkcs1v15_signing(self, backend, disable_rsa_checks, subtests): + def test_pkcs1v15_signing(self, backend, subtests): vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), @@ -506,7 +523,7 @@ def test_pkcs1v15_signing(self, backend, disable_rsa_checks, subtests): public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), - ).private_key(backend) + ).private_key(backend, unsafe_skip_rsa_key_validation=True) signature = private_key.sign( binascii.unhexlify(example["message"]), padding.PKCS1v15(), @@ -549,7 +566,7 @@ def test_pss_signing(self, subtests, backend): public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), - ).private_key(backend) + ).private_key(backend, unsafe_skip_rsa_key_validation=True) public_key = rsa.RSAPublicNumbers( e=public["public_exponent"], n=public["modulus"] ).public_key(backend) @@ -579,9 +596,9 @@ def test_pss_signing(self, subtests, backend): "hash_alg", [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()], ) - def test_pss_signing_sha2(self, hash_alg, backend): + 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.private_key(backend) + 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 @@ -599,8 +616,8 @@ def test_pss_signing_sha2(self, hash_alg, backend): ), skip_message="Does not support PSS.", ) - def test_pss_digest_length(self, backend): - private_key = RSA_KEY_2048.private_key() + def test_pss_digest_length(self, rsa_key_2048, backend): + private_key = rsa_key_2048 signature = private_key.sign( b"some data", padding.PSS( @@ -643,7 +660,9 @@ def test_pss_digest_length(self, backend): ) @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) + private_key = RSA_KEY_522.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) private_key.sign( b"no failure", padding.PSS( @@ -667,8 +686,10 @@ def test_pss_minimum_key_size_for_digest(self, backend): 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, backend): - private_key = RSA_KEY_512.private_key(backend) + 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", @@ -688,8 +709,10 @@ def test_pss_signing_digest_too_large_for_key_size(self, backend): ), skip_message="Does not support PSS.", ) - def test_pss_signing_salt_length_too_long(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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", @@ -699,13 +722,17 @@ def test_pss_signing_salt_length_too_long(self, backend): hashes.SHA256(), ) - def test_unsupported_padding(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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.SHA1()) - def test_padding_incorrect_type(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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", @@ -719,8 +746,10 @@ def test_padding_incorrect_type(self, backend): ), skip_message="Does not support PSS.", ) - def test_unsupported_pss_mgf(self, backend): - private_key = RSA_KEY_512.private_key(backend) + def test_unsupported_pss_mgf( + self, rsa_key_512: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_512 with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.sign( b"msg", @@ -740,8 +769,10 @@ def test_unsupported_pss_mgf(self, backend): ), skip_message="Does not support PSS.", ) - def test_pss_sign_unsupported_auto(self, backend): - private_key = RSA_KEY_2048.private_key() + 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", @@ -760,7 +791,9 @@ def test_pss_sign_unsupported_auto(self, backend): ) @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) + 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() @@ -774,11 +807,13 @@ def test_pkcs1_digest_too_large_for_key_size(self, backend): ) @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) + private_key = RSA_KEY_745.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) private_key.sign(b"no failure", padding.PKCS1v15(), hashes.SHA512()) - def test_sign(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 message = b"one little message" pkcs = padding.PKCS1v15() algorithm = hashes.SHA256() @@ -792,8 +827,8 @@ def test_sign(self, backend): ), skip_message="Does not support PSS.", ) - def test_prehashed_sign(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -813,8 +848,10 @@ def test_prehashed_sign(self, backend): ), skip_message="Does not support PSS.", ) - def test_prehashed_digest_length(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -840,8 +877,8 @@ def test_prehashed_digest_length(self, backend): ), skip_message="Does not support PSS.", ) - def test_unsupported_hash(self, backend): - private_key = RSA_KEY_512.private_key(backend) + 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): @@ -853,8 +890,10 @@ def test_unsupported_hash(self, backend): ), skip_message="Does not support PSS.", ) - def test_prehashed_digest_mismatch(self, backend): - private_key = RSA_KEY_512.private_key(backend) + 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) @@ -864,8 +903,10 @@ def test_prehashed_digest_mismatch(self, backend): with pytest.raises(ValueError): private_key.sign(digest, pss, prehashed_alg) - def test_prehashed_unsupported_in_signature_recover(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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() @@ -939,8 +980,10 @@ def test_pkcs1v15_verification(self, backend, subtests): ), skip_message="Does not support PKCS1v1.5.", ) - def test_invalid_pkcs1v15_signature_wrong_data(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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() @@ -953,8 +996,10 @@ def test_invalid_pkcs1v15_signature_wrong_data(self, backend): hashes.SHA256(), ) - def test_invalid_pkcs1v15_signature_recover_wrong_hash_alg(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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() @@ -1006,9 +1051,13 @@ def test_invalid_signature_sequence_removed(self, backend): ), skip_message="Does not support PKCS1v1.5.", ) - def test_invalid_pkcs1v15_signature_wrong_key(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - private_key2 = RSA_KEY_2048_ALT.private_key(backend) + 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()) @@ -1061,8 +1110,10 @@ def test_pss_verification(self, subtests, backend): ), skip_message="Does not support PSS.", ) - def test_pss_verify_auto_salt_length(self, backend): - private_key = RSA_KEY_2048.private_key() + 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( @@ -1194,7 +1245,9 @@ def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): b"ac462c50a488dca486031a3dc8c4cdbbc53e9f71d64732e1533a5d1249b833ce" ) # 1024 bit key - public_key = RSA_KEY_1024.private_key(backend).public_key() + public_key = RSA_KEY_1024.private_key( + unsafe_skip_rsa_key_validation=True + ).public_key() with pytest.raises(InvalidSignature): public_key.verify( signature, @@ -1212,8 +1265,10 @@ def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): ), skip_message="Does not support SHA1 signature.", ) - def test_invalid_pss_signature_recover(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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.SHA1()), @@ -1233,16 +1288,20 @@ def test_invalid_pss_signature_recover(self, backend): signature, pss_padding, hashes.SHA256() ) - def test_unsupported_padding(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -1258,8 +1317,10 @@ def test_padding_incorrect_type(self, backend): ), skip_message="Does not support PSS.", ) - def test_unsupported_pss_mgf(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -1285,8 +1346,10 @@ def test_unsupported_pss_mgf(self, backend): 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, backend): - private_key = RSA_KEY_512.private_key(backend) + 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" @@ -1346,8 +1409,8 @@ def test_pss_verify_salt_length_too_long(self, backend): hashes.SHA1(), ) - def test_verify(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_verify(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 message = b"one little message" pkcs = padding.PKCS1v15() algorithm = hashes.SHA256() @@ -1355,8 +1418,8 @@ def test_verify(self, backend): public_key = private_key.public_key() public_key.verify(signature, message, pkcs, algorithm) - def test_prehashed_verify(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -1367,8 +1430,10 @@ def test_prehashed_verify(self, backend): public_key = private_key.public_key() public_key.verify(signature, digest, pkcs, prehashed_alg) - def test_prehashed_digest_mismatch(self, backend): - public_key = RSA_KEY_2048.private_key(backend).public_key() + 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) @@ -1677,14 +1742,12 @@ def test_invalid_algorithm(self): class TestRSADecryption: @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_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, disable_rsa_checks, subtests - ): + def test_decrypt_pkcs1v15_vectors(self, backend, subtests): vectors = _flatten_pkcs1_examples( load_vectors_from_file( os.path.join("asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"), @@ -1703,47 +1766,56 @@ def test_decrypt_pkcs1v15_vectors( public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), - ).private_key(backend) + ).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, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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_padding_supported( - padding.PKCS1v15() + 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, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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_padding_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, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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_padding_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, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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" @@ -1752,7 +1824,7 @@ def test_decrypt_ciphertext_too_small(self, backend): private_key.decrypt(ct, padding.PKCS1v15()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1761,7 +1833,7 @@ def test_decrypt_ciphertext_too_small(self, backend): ), skip_message="Does not support OAEP.", ) - def test_decrypt_oaep_vectors(self, subtests, backend): + def test_decrypt_oaep_sha1_vectors(self, subtests, backend): for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( @@ -1781,7 +1853,7 @@ def test_decrypt_oaep_vectors(self, subtests, backend): public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), - ).private_key(backend) + ).private_key(backend, unsafe_skip_rsa_key_validation=True) message = skey.decrypt( binascii.unhexlify(example["encryption"]), padding.OAEP( @@ -1792,24 +1864,20 @@ def test_decrypt_oaep_vectors(self, subtests, backend): ) assert message == binascii.unhexlify(example["message"]) - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA224()), - algorithm=hashes.SHA224(), - label=None, - ) - ), - skip_message=( - "Does not support OAEP using SHA224 MGF1 and SHA224 hash." - ), - ) - def test_decrypt_oaep_sha2_vectors( - self, backend, disable_rsa_checks, subtests - ): + 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"], @@ -1820,19 +1888,15 @@ def test_decrypt_oaep_sha2_vectors( public_numbers=rsa.RSAPublicNumbers( e=private["public_exponent"], n=private["modulus"] ), - ).private_key(backend) + ).private_key(backend, unsafe_skip_rsa_key_validation=True) message = skey.decrypt( binascii.unhexlify(example["encryption"]), - padding.OAEP( - mgf=padding.MGF1(algorithm=mgf1_alg), - algorithm=hash_alg, - label=None, - ), + pad, ) assert message == binascii.unhexlify(example["message"]) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1841,11 +1905,13 @@ def test_decrypt_oaep_sha2_vectors( ), skip_message="Does not support OAEP.", ) - def test_invalid_oaep_decryption(self, backend): + 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.private_key(backend) + private_key = rsa_key_2048 ciphertext = private_key.public_key().encrypt( b"secure data", @@ -1856,7 +1922,9 @@ def test_invalid_oaep_decryption(self, backend): ), ) - private_key_alt = RSA_KEY_2048_ALT.private_key(backend) + private_key_alt = RSA_KEY_2048_ALT.private_key( + backend, unsafe_skip_rsa_key_validation=True + ) with pytest.raises(ValueError): private_key_alt.decrypt( @@ -1869,7 +1937,7 @@ def test_invalid_oaep_decryption(self, backend): ) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1879,7 +1947,9 @@ def test_invalid_oaep_decryption(self, backend): 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) + 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" @@ -1906,8 +1976,10 @@ def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): ), ) - def test_unsupported_oaep_mgf(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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, @@ -1921,7 +1993,7 @@ def test_unsupported_oaep_mgf(self, backend): class TestRSAEncryption: @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1955,7 +2027,7 @@ class TestRSAEncryption: ), ) def test_rsa_encrypt_oaep(self, key_data, pad, backend): - private_key = key_data.private_key(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() @@ -1965,18 +2037,6 @@ def test_rsa_encrypt_oaep(self, key_data, pad, backend): recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA512(), - label=None, - ) - ), - skip_message=( - "Does not support OAEP using SHA256 MGF1 and SHA512 hash." - ), - ) @pytest.mark.parametrize( ("mgf1hash", "oaephash"), itertools.product( @@ -1996,13 +2056,20 @@ def test_rsa_encrypt_oaep(self, key_data, pad, backend): ], ), ) - def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): + 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, ) - private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -2012,7 +2079,7 @@ def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): assert recovered_pt == pt @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", @@ -2036,7 +2103,7 @@ def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): ), ) def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): - private_key = key_data.private_key(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() @@ -2063,8 +2130,8 @@ def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): ), ( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), padding.PKCS1v15(), @@ -2072,7 +2139,9 @@ def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): ), ) def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): - private_key = key_data.private_key(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. @@ -2087,13 +2156,14 @@ def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): only_if=lambda backend: backend._fips_enabled, skip_message="Requires FIPS", ) - def test_rsa_fips_small_key(self, backend): - key = RSA_KEY_512.private_key(backend) + def test_rsa_fips_small_key(self, rsa_key_512: rsa.RSAPrivateKey, backend): with pytest.raises(ValueError): - key.sign(b"somedata", padding.PKCS1v15(), hashes.SHA512()) + rsa_key_512.sign(b"somedata", padding.PKCS1v15(), hashes.SHA512()) - def test_unsupported_padding(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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): @@ -2103,8 +2173,10 @@ def test_unsupported_padding(self, backend): b"somedata", padding=object() # type: ignore[arg-type] ) - def test_unsupported_oaep_mgf(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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): @@ -2145,7 +2217,9 @@ def test_rsa_private_numbers(self): assert private_numbers.public_numbers == public_numbers def test_rsa_private_numbers_create_key(self, backend): - private_key = RSA_KEY_1024.private_key(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): @@ -2359,16 +2433,18 @@ class TestRSAPrivateKeySerialization: ], ), ) - def test_private_bytes_encrypted_pem(self, backend, fmt, password): + 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.private_key(backend) + 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 + serialized, password, backend, unsafe_skip_rsa_key_validation=True ) assert isinstance(loaded_key, rsa.RSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() @@ -2384,8 +2460,10 @@ def test_private_bytes_encrypted_pem(self, backend, fmt, password): (serialization.Encoding.X962, serialization.PrivateFormat.PKCS8), ], ) - def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): - key = RSA_KEY_2048.private_key(backend) + 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()) @@ -2398,15 +2476,17 @@ def test_private_bytes_rejects_invalid(self, encoding, fmt, backend): [serialization.PrivateFormat.PKCS8, b"\x01" * 1000], ], ) - def test_private_bytes_encrypted_der(self, backend, fmt, password): - key = RSA_KEY_2048.private_key(backend) + 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 + serialized, password, backend, unsafe_skip_rsa_key_validation=True ) assert isinstance(loaded_key, rsa.RSAPrivateKey) loaded_priv_num = loaded_key.private_numbers() @@ -2439,13 +2519,20 @@ def test_private_bytes_encrypted_der(self, backend, fmt, password): ], ) def test_private_bytes_unencrypted( - self, backend, encoding, fmt, loader_func + self, + rsa_key_2048: rsa.RSAPrivateKey, + backend, + encoding, + fmt, + loader_func, ): - key = RSA_KEY_2048.private_key(backend) + key = rsa_key_2048 serialized = key.private_bytes( encoding, fmt, serialization.NoEncryption() ) - loaded_key = loader_func(serialized, None, backend) + 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 @@ -2478,7 +2565,9 @@ def test_private_bytes_traditional_openssl_unencrypted( key_bytes = load_vectors_from_file( key_path, lambda pemfile: pemfile.read(), mode="rb" ) - key = loader_func(key_bytes, None, backend) + key = loader_func( + key_bytes, None, backend, unsafe_skip_rsa_key_validation=True + ) serialized = key.private_bytes( encoding, serialization.PrivateFormat.TraditionalOpenSSL, @@ -2486,8 +2575,10 @@ def test_private_bytes_traditional_openssl_unencrypted( ) assert serialized == key_bytes - def test_private_bytes_traditional_der_encrypted_invalid(self, backend): - key = RSA_KEY_2048.private_key(backend) + 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, @@ -2495,8 +2586,10 @@ def test_private_bytes_traditional_der_encrypted_invalid(self, backend): serialization.BestAvailableEncryption(b"password"), ) - def test_private_bytes_invalid_encoding(self, backend): - key = RSA_KEY_2048.private_key(backend) + 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] @@ -2504,8 +2597,10 @@ def test_private_bytes_invalid_encoding(self, backend): serialization.NoEncryption(), ) - def test_private_bytes_invalid_format(self, backend): - key = RSA_KEY_2048.private_key(backend) + 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, @@ -2513,8 +2608,10 @@ def test_private_bytes_invalid_format(self, backend): serialization.NoEncryption(), ) - def test_private_bytes_invalid_encryption_algorithm(self, backend): - key = RSA_KEY_2048.private_key(backend) + 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, @@ -2522,8 +2619,10 @@ def test_private_bytes_invalid_encryption_algorithm(self, backend): "notanencalg", # type: ignore[arg-type] ) - def test_private_bytes_unsupported_encryption_type(self, backend): - key = RSA_KEY_2048.private_key(backend) + 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, @@ -2613,16 +2712,20 @@ def test_public_bytes_openssh(self, backend): serialization.PublicFormat.SubjectPublicKeyInfo, ) - def test_public_bytes_invalid_encoding(self, backend): - key = RSA_KEY_2048.private_key(backend).public_key() + 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, backend): - key = RSA_KEY_2048.private_key(backend).public_key() + 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, @@ -2654,7 +2757,21 @@ def test_public_bytes_invalid_format(self, backend): ) ), ) - def test_public_bytes_rejects_invalid(self, encoding, fmt, backend): - key = RSA_KEY_2048.private_key(backend).public_key() + 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() diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py index 6e95a1f..4b46418 100644 --- a/tests/hazmat/primitives/test_scrypt.py +++ b/tests/hazmat/primitives/test_scrypt.py @@ -8,12 +8,8 @@ import pytest -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidKey, -) -from cryptography.hazmat.primitives.kdf.scrypt import Scrypt, _MEM_LIMIT - +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, diff --git a/tests/hazmat/primitives/test_seed.py b/tests/hazmat/primitives/test_seed.py index eb0b88c..f36ce1e 100644 --- a/tests/hazmat/primitives/test_seed.py +++ b/tests/hazmat/primitives/test_seed.py @@ -10,8 +10,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test from ...utils import load_nist_vectors +from .utils import generate_encrypt_test @pytest.mark.supported( @@ -26,7 +26,7 @@ class TestSEEDModeECB: os.path.join("ciphers", "SEED"), ["rfc-4269.txt"], lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda **kwargs: modes.ECB(), ) @@ -44,7 +44,7 @@ class TestSEEDModeCBC: os.path.join("ciphers", "SEED"), ["rfc-4196.txt"], lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), ) @@ -62,7 +62,7 @@ class TestSEEDModeOFB: os.path.join("ciphers", "SEED"), ["seed-ofb.txt"], lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), ) @@ -80,7 +80,7 @@ class TestSEEDModeCFB: os.path.join("ciphers", "SEED"), ["seed-cfb.txt"], lambda key, **kwargs: algorithms._SEEDInternal( - binascii.unhexlify((key)) + binascii.unhexlify(key) ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 3a08d55..58693a4 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -13,17 +13,16 @@ from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, - ed25519, ed448, + ed25519, rsa, - x25519, x448, + x25519, ) from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.hazmat.primitives.serialization import ( BestAvailableEncryption, Encoding, - KeySerializationEncryption, NoEncryption, PrivateFormat, PublicFormat, @@ -33,21 +32,17 @@ load_pem_parameters, load_pem_private_key, load_pem_public_key, - load_ssh_private_key, - load_ssh_public_key, - ssh, ) from cryptography.hazmat.primitives.serialization.pkcs12 import PBES - -from .fixtures_rsa import RSA_KEY_2048 +from ...utils import load_vectors_from_file from .test_ec import _skip_curve_unsupported -from .utils import ( - _check_dsa_private_numbers, - _check_rsa_private_numbers, -) -from ...doubles import DummyKeySerializationEncryption -from ...utils import load_vectors_from_file, raises_unsupported_algorithm +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): @@ -86,7 +81,9 @@ def test_load_der_rsa_private_key(self, key_path, password, backend): lambda derfile: derfile.read(), mode="rb", ) - key = load_der_private_key(bytearray(data), password, backend) + 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()) @@ -114,7 +111,9 @@ def test_load_pem_rsa_private_key(self, key_path, password, backend): lambda pemfile: pemfile.read(), mode="rb", ) - key = load_pem_private_key(bytearray(data), password, backend) + 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()) @@ -135,7 +134,7 @@ def test_load_der_rsa_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 + derfile.read(), password, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -396,6 +395,10 @@ def test_load_ec_public_key(self, backend): 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" @@ -435,7 +438,9 @@ def test_load_pem_rsa_private_key(self, 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, backend + pemfile.read().encode(), + password, + unsafe_skip_rsa_key_validation=True, ), ) @@ -515,13 +520,15 @@ def test_load_pem_rsa_public_key(self, key_file, backend): numbers = key.public_numbers() assert numbers.e == 65537 - def test_load_priv_key_with_public_key_api_fails(self, backend): + 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_key().private_bytes( + priv_key_serialized = rsa_key_2048.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, BestAvailableEncryption(b"password"), @@ -576,7 +583,9 @@ def test_rsa_traditional_encrypted_values(self, backend): "asymmetric", "Traditional_OpenSSL_Serialization", "key1.pem" ), lambda pemfile: load_pem_private_key( - pemfile.read().encode(), b"123456", backend + pemfile.read().encode(), + b"123456", + unsafe_skip_rsa_key_validation=True, ), ) assert isinstance(pkey, rsa.RSAPrivateKey) @@ -640,7 +649,7 @@ def test_invalid_encoding_with_traditional(self, backend): key = load_vectors_from_file( key_file, lambda pemfile: load_pem_private_key( - pemfile.read(), None, backend + pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) @@ -729,6 +738,10 @@ def test_wrong_public_format(self, backend): 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" @@ -875,7 +888,9 @@ 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", backend + pemfile.read().encode(), + b"foobar", + unsafe_skip_rsa_key_validation=True, ), ) assert isinstance(pkey, rsa.RSAPrivateKey) @@ -1014,410 +1029,6 @@ def test_load_bad_encryption_oid_key(self, key_file, password, backend): ) -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" - ) - - 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" - ) - - 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" - assert str(TestEnum.value) == "TestEnum.value" + assert isinstance(TestEnum.something, enum.Enum) + assert repr(TestEnum.something) == "" + assert str(TestEnum.something) == "TestEnum.something" diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 58dd772..89908e2 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -4,22 +4,18 @@ import base64 -import calendar +import datetime import json import os import time -import iso8601 - import pretend - import pytest +import cryptography_vectors from cryptography.fernet import Fernet, InvalidToken, MultiFernet from cryptography.hazmat.primitives.ciphers import algorithms, modes -import cryptography_vectors - def json_parametrize(keys, filename): vector_file = cryptography_vectors.open_vector_file( @@ -49,7 +45,7 @@ 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"), - calendar.timegm(iso8601.parse_date(now).utctimetuple()), + int(datetime.datetime.fromisoformat(now).timestamp()), bytes(iv), ) assert actual_token == token.encode("ascii") @@ -63,7 +59,7 @@ def test_verify( ): # secret & token are both str f = Fernet(secret.encode("ascii"), backend=backend) - current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + current_time = int(datetime.datetime.fromisoformat(now).timestamp()) payload = f.decrypt_at_time( token, # str ttl=ttl_sec, @@ -89,7 +85,7 @@ def test_verify( @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 = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + current_time = int(datetime.datetime.fromisoformat(now).timestamp()) with pytest.raises(InvalidToken): f.decrypt_at_time( token.encode("ascii"), diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py deleted file mode 100644 index 3e09334..0000000 --- a/tests/test_interfaces.py +++ /dev/null @@ -1,80 +0,0 @@ -# This file is dual licensed under the terms 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 abc - -import pytest - -from cryptography.utils import ( - InterfaceNotImplemented, - verify_interface, -) - - -class TestVerifyInterface: - def test_verify_missing_method(self): - class SimpleInterface(metaclass=abc.ABCMeta): - @abc.abstractmethod - def method(self): - """A simple method""" - - class NonImplementer: - pass - - with pytest.raises(InterfaceNotImplemented): - verify_interface(SimpleInterface, NonImplementer) - - def test_different_arguments(self): - class SimpleInterface(metaclass=abc.ABCMeta): - @abc.abstractmethod - def method(self, a): - """Method with one argument""" - - class NonImplementer: - def method(self): - """Method with no arguments""" - - # Invoke this to ensure the line is covered - NonImplementer().method() - with pytest.raises(InterfaceNotImplemented): - verify_interface(SimpleInterface, NonImplementer) - - def test_handles_abstract_property(self): - class SimpleInterface(metaclass=abc.ABCMeta): - @abc.abstractproperty - def property(self): - """An abstract property""" - - class NonImplementer: - @property - def property(self): - """A concrete property""" - - # Invoke this to ensure the line is covered - NonImplementer().property - verify_interface(SimpleInterface, NonImplementer) - - def test_signature_mismatch(self): - class SimpleInterface(metaclass=abc.ABCMeta): - @abc.abstractmethod - def method(self, other: object): - """Method with signature""" - - class ClassWithoutSignature: - def method(self, other): - """Method without signature""" - - class ClassWithSignature: - def method(self, other: object): - """Method with signature""" - - verify_interface(SimpleInterface, ClassWithoutSignature) - verify_interface(SimpleInterface, ClassWithSignature) - with pytest.raises(InterfaceNotImplemented): - verify_interface( - SimpleInterface, ClassWithoutSignature, check_annotations=True - ) - verify_interface( - SimpleInterface, ClassWithSignature, check_annotations=True - ) diff --git a/tests/test_rust_utils.py b/tests/test_rust_utils.py index 99ddfb0..1ee6854 100644 --- a/tests/test_rust_utils.py +++ b/tests/test_rust_utils.py @@ -19,10 +19,7 @@ def create(): events.append(("create", c)) return c - def destroy(c): - events.append(("destroy", c)) - - pool = FixedPool(create, destroy) + pool = FixedPool(create) assert events == [("create", 1)] with pool.acquire() as c: assert c == 1 @@ -32,9 +29,9 @@ def destroy(c): assert c == 2 assert events == [("create", 1), ("create", 2)] - assert events == [("create", 1), ("create", 2), ("destroy", 2)] + assert events == [("create", 1), ("create", 2)] - assert events == [("create", 1), ("create", 2), ("destroy", 2)] + assert events == [("create", 1), ("create", 2)] del pool gc.collect() @@ -44,18 +41,13 @@ def destroy(c): assert events == [ ("create", 1), ("create", 2), - ("destroy", 2), - ("destroy", 1), ] def test_thread_stress(self): def create(): return None - def destroy(c): - pass - - pool = FixedPool(create, destroy) + pool = FixedPool(create) def thread_fn(): with pool.acquire(): diff --git a/tests/test_utils.py b/tests/test_utils.py index 8b07c91..9f6e271 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,14 +9,12 @@ import textwrap import pretend - import pytest import cryptography import cryptography.utils -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons - import cryptography_vectors +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from . import deprecated_module from .utils import ( diff --git a/tests/utils.py b/tests/utils.py index 6119d3f..bad0f87 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -13,10 +13,8 @@ import pytest -from cryptography.exceptions import UnsupportedAlgorithm - import cryptography_vectors - +from cryptography.exceptions import UnsupportedAlgorithm HashVector = collections.namedtuple("HashVector", ["message", "digest"]) KeyedHashVector = collections.namedtuple( @@ -35,7 +33,7 @@ def raises_unsupported_algorithm(reason): with pytest.raises(UnsupportedAlgorithm) as exc_info: yield exc_info - assert exc_info.value._reason is reason + assert exc_info.value._reason == reason T = typing.TypeVar("T") @@ -68,7 +66,7 @@ def load_nist_vectors(vector_data): continue # Build our data using a simple Key = Value format - name, value = [c.strip() for c in line.split("=")] + 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. @@ -109,7 +107,7 @@ def load_cryptrec_vectors(vector_data): {"key": key, "plaintext": pt, "ciphertext": ct} ) else: - raise ValueError("Invalid line in file '{}'".format(line)) + raise ValueError(f"Invalid line in file '{line}'") return cryptrec_list @@ -245,9 +243,6 @@ def load_pkcs1_vectors(vector_data): attr = None if private_key_vector is None or public_key_vector is None: - # Random garbage to defeat CPython's peephole optimizer so that - # coverage records correctly: https://bugs.python.org/issue2506 - 1 + 1 continue if line.startswith("# Private key"): @@ -302,7 +297,7 @@ def load_rsa_nist_vectors(vector_data): continue # Build our data using a simple Key = Value format - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) if name == "n": n = int(value, 16) @@ -398,7 +393,7 @@ def load_fips_dsa_sig_vectors(vector_data): if line.startswith("[mod"): continue - name, value = [c.strip() for c in line.split("=")] + name, value = (c.strip() for c in line.split("=")) if name == "P": vectors.append( @@ -773,7 +768,7 @@ def load_nist_kbkdf_vectors(vector_data): if line.startswith("[") and line.endswith("]"): tag_data = line[1:-1] - name, value = [c.strip() for c in tag_data.split("=")] + name, value = (c.strip() for c in tag_data.split("=")) if value.endswith("_BITS"): value = int(value.split("_")[0]) tag.update({name.lower(): value}) @@ -785,10 +780,10 @@ def load_nist_kbkdf_vectors(vector_data): test_data.update(tag) vectors.append(test_data) elif line.startswith(("L", "DataBeforeCtrLen", "DataAfterCtrLen")): - name, value = [c.strip() for c in line.split("=")] + 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("=")] + name, value = (c.strip() for c in line.split("=")) test_data[name.lower()] = value.encode("ascii") return vectors @@ -830,7 +825,7 @@ def load_nist_ccm_vectors(vector_data): # 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("=")] + name, value = (c.strip() for c in line.split("=")) global_data[name.lower()] = int(value) continue @@ -841,11 +836,11 @@ def load_nist_ccm_vectors(vector_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("=")] + 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("=")] + 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") @@ -901,20 +896,27 @@ def __repr__(self): ) @property - def valid(self): + def valid(self) -> bool: return self.testcase["result"] == "valid" @property - def acceptable(self): + def acceptable(self) -> bool: return self.testcase["result"] == "acceptable" @property - def invalid(self): + def invalid(self) -> bool: return self.testcase["result"] == "invalid" - def has_flag(self, flag): + 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): path = os.path.join(wycheproof, "testvectors", test_file) diff --git a/tests/wycheproof/test_aes.py b/tests/wycheproof/test_aes.py index 891d8df..ce83fe3 100644 --- a/tests/wycheproof/test_aes.py +++ b/tests/wycheproof/test_aes.py @@ -12,8 +12,8 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESGCM -from .utils import wycheproof_tests from ..hazmat.primitives.test_aead import _aead_supported +from .utils import wycheproof_tests @wycheproof_tests("aes_cbc_pkcs5_test.json") diff --git a/tests/wycheproof/test_chacha20poly1305.py b/tests/wycheproof/test_chacha20poly1305.py index 7cb8ff5..06d6fc7 100644 --- a/tests/wycheproof/test_chacha20poly1305.py +++ b/tests/wycheproof/test_chacha20poly1305.py @@ -10,8 +10,8 @@ from cryptography.exceptions import InvalidTag from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 -from .utils import wycheproof_tests from ..hazmat.primitives.test_aead import _aead_supported +from .utils import wycheproof_tests @pytest.mark.skipif( diff --git a/tests/wycheproof/test_dsa.py b/tests/wycheproof/test_dsa.py index 3d31ee1..fd76a93 100644 --- a/tests/wycheproof/test_dsa.py +++ b/tests/wycheproof/test_dsa.py @@ -13,7 +13,6 @@ from .utils import wycheproof_tests - _DIGESTS = { "SHA-1": hashes.SHA1(), "SHA-224": hashes.SHA224(), @@ -21,6 +20,10 @@ } +@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", diff --git a/tests/wycheproof/test_ecdh.py b/tests/wycheproof/test_ecdh.py index 672863f..e2624a4 100644 --- a/tests/wycheproof/test_ecdh.py +++ b/tests/wycheproof/test_ecdh.py @@ -11,9 +11,8 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec -from .utils import wycheproof_tests from ..hazmat.primitives.test_ec import _skip_exchange_algorithm_unsupported - +from .utils import wycheproof_tests _CURVES = { "secp224r1": ec.SECP224R1(), @@ -22,6 +21,12 @@ "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, @@ -32,6 +37,7 @@ "brainpoolP320t1": None, "brainpoolP384t1": None, "brainpoolP512t1": None, + "FRP256v1": None, } @@ -47,6 +53,12 @@ "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"]] @@ -55,12 +67,15 @@ def test_ecdh(backend, wycheproof): "Unsupported curve ({})".format(wycheproof.testgroup["curve"]) ) _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) - - private_key = ec.derive_private_key( - int(wycheproof.testcase["private"], 16), curve, backend + 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 ) @@ -91,8 +106,11 @@ def test_ecdh_ecpoint(backend, wycheproof): assert isinstance(curve, ec.EllipticCurve) _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), curve) - private_key = ec.derive_private_key( - int(wycheproof.testcase["private"], 16), curve, backend + 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: @@ -103,6 +121,7 @@ def test_ecdh_ecpoint(backend, wycheproof): 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"]) ) diff --git a/tests/wycheproof/test_ecdsa.py b/tests/wycheproof/test_ecdsa.py index b2ec9df..0b03083 100644 --- a/tests/wycheproof/test_ecdsa.py +++ b/tests/wycheproof/test_ecdsa.py @@ -13,7 +13,6 @@ from .utils import wycheproof_tests - _DIGESTS = { "SHA-1": hashes.SHA1(), "SHA-224": hashes.SHA224(), @@ -54,11 +53,19 @@ "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 = serialization.load_der_public_key( - binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + 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): @@ -73,7 +80,7 @@ def test_ecdsa_signature(backend, wycheproof): digest = _DIGESTS[wycheproof.testgroup["sha"]] if not backend.hash_supported(digest): - pytest.skip("Hash {} not supported".format(digest)) + pytest.skip(f"Hash {digest} not supported") if wycheproof.valid or ( wycheproof.acceptable and not wycheproof.has_flag("MissingZero") diff --git a/tests/wycheproof/test_eddsa.py b/tests/wycheproof/test_eddsa.py index 2de695f..3b5dae3 100644 --- a/tests/wycheproof/test_eddsa.py +++ b/tests/wycheproof/test_eddsa.py @@ -8,8 +8,8 @@ import pytest from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from .utils import wycheproof_tests diff --git a/tests/wycheproof/test_hkdf.py b/tests/wycheproof/test_hkdf.py index 4886be0..3d54e44 100644 --- a/tests/wycheproof/test_hkdf.py +++ b/tests/wycheproof/test_hkdf.py @@ -12,7 +12,6 @@ from .utils import wycheproof_tests - _HASH_ALGORITHMS = { "HKDF-SHA-1": hashes.SHA1(), "HKDF-SHA-256": hashes.SHA256(), diff --git a/tests/wycheproof/test_hmac.py b/tests/wycheproof/test_hmac.py index 84b0c19..4a42dc1 100644 --- a/tests/wycheproof/test_hmac.py +++ b/tests/wycheproof/test_hmac.py @@ -12,7 +12,6 @@ from .utils import wycheproof_tests - _HMAC_ALGORITHMS = { "HMACSHA1": hashes.SHA1(), "HMACSHA224": hashes.SHA224(), @@ -42,7 +41,7 @@ def test_hmac(backend, wycheproof): if wycheproof.testgroup["tagSize"] // 8 != hash_algo.digest_size: pytest.skip("Truncated HMAC not supported") if not backend.hmac_supported(hash_algo): - pytest.skip("Hash {} not supported".format(hash_algo.name)) + pytest.skip(f"Hash {hash_algo.name} not supported") h = hmac.HMAC( key=binascii.unhexlify(wycheproof.testcase["key"]), diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index 7925a5b..48d20f3 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -13,7 +13,6 @@ from .utils import wycheproof_tests - _DIGESTS = { "SHA-1": hashes.SHA1(), "SHA-224": hashes.SHA224(), @@ -64,8 +63,11 @@ def should_verify(backend, wycheproof): "rsa_signature_4096_sha512_256_test.json", ) def test_rsa_pkcs1v15_signature(backend, wycheproof): - key = serialization.load_der_public_key( - binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + 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"]] @@ -94,16 +96,22 @@ def test_rsa_pkcs1v15_signature(backend, wycheproof): @wycheproof_tests("rsa_sig_gen_misc_test.json") def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): - key = serialization.load_pem_private_key( - wycheproof.testgroup["privateKeyPem"].encode(), - password=None, - backend=backend, + 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 < 2048 or isinstance(digest, hashes.SHA1): + if key.key_size < backend._fips_rsa_min_key_size or isinstance( + digest, hashes.SHA1 + ): pytest.skip( "Invalid params for FIPS. key: {} bits, digest: {}".format( key.key_size, digest.name @@ -130,11 +138,17 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): "rsa_pss_misc_test.json", ) def test_rsa_pss_signature(backend, wycheproof): - key = serialization.load_der_public_key( - binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend + 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) - digest = _DIGESTS[wycheproof.testgroup["sha"]] mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] if digest is None or mgf_digest is None: @@ -189,22 +203,32 @@ def test_rsa_pss_signature(backend, wycheproof): "rsa_oaep_misc_test.json", ) def test_rsa_oaep_encryption(backend, wycheproof): - key = serialization.load_pem_private_key( - wycheproof.testgroup["privateKeyPem"].encode("ascii"), - password=None, - backend=backend, - ) - assert isinstance(key, rsa.RSAPrivateKey) 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( @@ -218,16 +242,25 @@ def test_rsa_oaep_encryption(backend, wycheproof): ) +@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 = serialization.load_pem_private_key( - wycheproof.testgroup["privateKeyPem"].encode("ascii"), - password=None, - backend=backend, + 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) @@ -237,8 +270,18 @@ def test_rsa_pkcs1_encryption(backend, wycheproof): ) assert pt == binascii.unhexlify(wycheproof.testcase["msg"]) else: - with pytest.raises(ValueError): - key.decrypt( - binascii.unhexlify(wycheproof.testcase["ct"]), - padding.PKCS1v15(), - ) + 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(), + ) diff --git a/tests/wycheproof/utils.py b/tests/wycheproof/utils.py index 3c18e62..eebbe7c 100644 --- a/tests/wycheproof/utils.py +++ b/tests/wycheproof/utils.py @@ -3,9 +3,7 @@ def wycheproof_tests(*paths): def wrapper(func): - def run_wycheproof( - backend, disable_rsa_checks, subtests, pytestconfig - ): + def run_wycheproof(backend, subtests, pytestconfig): wycheproof_root = pytestconfig.getoption( "--wycheproof-root", skip=True ) diff --git a/tests/x509/test_name.py b/tests/x509/test_name.py index de47a7a..4c9ccc3 100644 --- a/tests/x509/test_name.py +++ b/tests/x509/test_name.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py index 9b27678..2c595db 100644 --- a/tests/x509/test_ocsp.py +++ b/tests/x509/test_ocsp.py @@ -10,14 +10,15 @@ import pytest from cryptography import x509 +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448, rsa +from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.x509 import ocsp -from .test_x509 import DummyExtension, _load_cert 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): @@ -27,17 +28,13 @@ def _load_data(filename, loader): def _cert_and_issuer(): - from cryptography.hazmat.backends.openssl.backend import backend - cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) issuer = _load_cert( os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), x509.load_pem_x509_certificate, - backend, ) return cert, issuer @@ -105,6 +102,18 @@ def test_load_request_with_extensions(self): 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"), @@ -162,13 +171,56 @@ def test_invalid_serialize_encoding(self): class TestOCSPRequestBuilder: - def test_add_two_certs(self): + 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): @@ -251,6 +303,28 @@ def test_create_ocsp_request_with_extension(self, ext, critical): assert req.extensions[0].oid == ext.oid assert req.extensions[0].critical is critical + def test_add_cert_by_hash(self): + cert, issuer = _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): @@ -852,7 +926,7 @@ def test_sign_unrecognized_hash_algorithm(self, backend): None, ) - with pytest.raises(ValueError): + with pytest.raises(UnsupportedAlgorithm): builder.sign(private_key, hashes.BLAKE2b(digest_size=64)) def test_sign_none_hash_not_eddsa(self): @@ -980,12 +1054,9 @@ def test_load_response(self): os.path.join("x509", "ocsp", "resp-sha256.der"), ocsp.load_der_ocsp_response, ) - from cryptography.hazmat.backends.openssl.backend import backend - issuer = _load_cert( os.path.join("x509", "letsencryptx3.pem"), x509.load_pem_x509_certificate, - backend, ) assert resp.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL assert ( diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index aecf6f8..0bac1c2 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. @@ -13,21 +12,21 @@ import pytest -import pytz - from cryptography import utils, x509 +from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.bindings._rust import asn1 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dh, dsa, ec, - ed25519, ed448, + ed25519, padding, rsa, - x25519, + types, x448, + x25519, ) from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, @@ -42,16 +41,23 @@ SubjectInformationAccessOID, ) -from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 +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, RSA_KEY_512 +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") @@ -69,13 +75,64 @@ def value(self): T = typing.TypeVar("T") -def _load_cert(filename, loader: typing.Callable[..., T], backend=None) -> T: - cert = load_vectors_from_file( +def _load_cert(filename, loader: typing.Callable[..., T]) -> T: + return load_vectors_from_file( filename=filename, - loader=lambda pemfile: loader(pemfile.read(), backend), + loader=lambda pemfile: loader(pemfile.read()), mode="rb", ) - return cert + + +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)) class TestCertificateRevocationList: @@ -83,7 +140,6 @@ def test_load_pem_crl(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert isinstance(crl, x509.CertificateRevocationList) @@ -99,7 +155,6 @@ def test_load_der_crl(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) assert isinstance(crl, x509.CertificateRevocationList) @@ -111,7 +166,6 @@ def test_load_large_crl(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_almost_10k.pem"), x509.load_pem_x509_crl, - backend, ) assert len(crl) == 9999 @@ -121,7 +175,6 @@ def test_empty_crl_no_sequence(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty_no_sequence.der"), x509.load_der_x509_crl, - backend, ) assert len(crl) == 0 @@ -136,8 +189,7 @@ def test_invalid_pem(self, backend): pem_bytes = _load_cert( os.path.join("x509", "custom", "valid_signature_cert.pem"), - lambda data, backend: data, - backend, + lambda data: data, ) with pytest.raises(ValueError): x509.load_pem_x509_crl(pem_bytes, backend) @@ -151,7 +203,6 @@ def test_invalid_time(self, backend): _load_cert( os.path.join("x509", "custom", "crl_invalid_time.der"), x509.load_der_x509_crl, - backend, ) def test_unknown_signature_algorithm(self, backend): @@ -160,7 +211,6 @@ def test_unknown_signature_algorithm(self, backend): "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" ), x509.load_pem_x509_crl, - backend, ) with raises_unsupported_algorithm(None): @@ -171,14 +221,12 @@ def test_invalid_version(self, backend): _load_cert( os.path.join("x509", "custom", "crl_bad_version.pem"), x509.load_pem_x509_crl, - backend, ) def test_issuer(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) assert isinstance(crl.issuer, x509.Name) @@ -197,19 +245,16 @@ def test_equality(self, backend): crl1 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) crl2 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) crl3 = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert crl1 == crl2 @@ -220,7 +265,6 @@ def test_comparison(self, backend): crl1 = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) with pytest.raises(TypeError): crl1 < crl1 # type: ignore[operator] @@ -229,7 +273,6 @@ def test_update_dates(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert isinstance(crl.next_update, datetime.datetime) @@ -242,7 +285,6 @@ 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, - backend, ) assert crl.next_update is None @@ -250,7 +292,6 @@ def test_unrecognized_extension(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_unrecognized_extension.der"), x509.load_der_x509_crl, - backend, ) unrecognized = x509.UnrecognizedExtension( x509.ObjectIdentifier("1.2.3.4.5"), @@ -263,7 +304,6 @@ def test_revoked_cert_retrieval(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) for r in crl: @@ -282,7 +322,6 @@ def test_get_revoked_certificate_by_serial_number(self, backend): "x509", "PKITS_data", "crls", "LongSerialNumberCACRL.crl" ), x509.load_der_x509_crl, - backend, ) serial_number = 725064303890588110203033396814564464046290047507 revoked = crl.get_revoked_certificate_by_serial_number(serial_number) @@ -299,7 +338,6 @@ def test_revoked_cert_retrieval_retain_only_revoked(self, backend): revoked = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, )[11] assert revoked.revocation_date == datetime.datetime(2015, 1, 1, 0, 0) assert revoked.serial_number == 11 @@ -308,7 +346,6 @@ def test_extensions(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_ian_aia_aki.pem"), x509.load_pem_x509_crl, - backend, ) crl_number = crl.extensions.get_extension_for_oid( @@ -346,7 +383,6 @@ 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, - backend, ) dci = crl.extensions.get_extension_for_oid( @@ -359,7 +395,6 @@ def test_signature(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) assert crl.signature == binascii.unhexlify( @@ -378,13 +413,11 @@ def test_tbs_certlist_bytes(self, backend): crl = _load_cert( os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), x509.load_der_x509_crl, - backend, ) ca_cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) public_key = ca_cert.public_key() @@ -401,7 +434,6 @@ def test_public_bytes_pem(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, - backend, ) # Encode it to PEM and load it back. @@ -409,7 +441,6 @@ def test_public_bytes_pem(self, backend): crl.public_bytes( encoding=serialization.Encoding.PEM, ), - backend, ) assert len(crl) == 0 @@ -420,7 +451,6 @@ def test_public_bytes_der(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) # Encode it to DER and load it back. @@ -428,7 +458,6 @@ def test_public_bytes_der(self, backend): crl.public_bytes( encoding=serialization.Encoding.DER, ), - backend, ) assert len(crl) == 12 @@ -464,7 +493,6 @@ def test_public_bytes_invalid_encoding(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(TypeError): @@ -474,28 +502,30 @@ def test_verify_bad(self, backend): crl = _load_cert( os.path.join("x509", "custom", "invalid_signature_crl.pem"), x509.load_pem_x509_crl, - backend, ) crt = _load_cert( os.path.join("x509", "custom", "invalid_signature_cert.pem"), x509.load_pem_x509_certificate, - backend, ) 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, - backend, ) crt = _load_cert( os.path.join("x509", "custom", "valid_signature_cert.pem"), x509.load_pem_x509_certificate, - backend, ) public_key = crt.public_key() @@ -506,7 +536,6 @@ 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, - backend, ) with pytest.raises(TypeError): @@ -523,7 +552,6 @@ def test_revoked_basics(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) for i, rev in enumerate(crl): @@ -539,7 +567,6 @@ def test_revoked_extensions(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) exp_issuer = [ @@ -602,7 +629,6 @@ def test_no_revoked_certs(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_empty.pem"), x509.load_pem_x509_crl, - backend, ) assert len(crl) == 0 @@ -610,7 +636,6 @@ 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, - backend, ) with pytest.raises(x509.DuplicateExtension): @@ -622,7 +647,6 @@ def test_unsupported_crit_entry_ext(self, backend): "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" ), x509.load_pem_x509_crl, - backend, ) ext = crl[0].extensions.get_extension_for_oid( @@ -635,7 +659,6 @@ def test_unsupported_reason(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_unsupported_reason.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(ValueError): @@ -647,7 +670,6 @@ def test_invalid_cert_issuer_ext(self, backend): "x509", "custom", "crl_inval_cert_issuer_entry_ext.pem" ), x509.load_pem_x509_crl, - backend, ) with pytest.raises(ValueError): @@ -657,7 +679,6 @@ def test_indexing(self, backend): crl = _load_cert( os.path.join("x509", "custom", "crl_all_reasons.pem"), x509.load_pem_x509_crl, - backend, ) with pytest.raises(IndexError): @@ -670,8 +691,10 @@ def test_indexing(self, backend): 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, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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 = ( @@ -706,31 +729,101 @@ def test_get_revoked_certificate_doesnt_reorder(self, backend): assert crl[2].serial_number == 3 +@pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL + and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E + ), + skip_message="Does not support RSA PSS loading", +) class TestRSAPSSCertificate: - @pytest.mark.supported( - only_if=lambda backend: ( - not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E - ), - skip_message="Does not support RSA PSS loading", - ) 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, - backend, ) 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, - backend, ) assert isinstance(expected_pub_key, rsa.RSAPublicKey) pub_key = cert.public_key() assert isinstance(pub_key, rsa.RSAPublicKey) - assert pub_key.public_numbers() == expected_pub_key.public_numbers() + 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: @@ -738,7 +831,6 @@ def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 11559813051657483483 @@ -748,12 +840,33 @@ def test_load_pem_cert(self, backend): assert ( cert.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1 ) + assert isinstance( + cert.signature_algorithm_parameters, padding.PKCS1v15 + ) + + 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, - backend, ) assert isinstance(cert, x509.Certificate) @@ -761,7 +874,12 @@ 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, - backend, + ) + 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) @@ -772,12 +890,10 @@ def test_load_multiple_sections(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.chain.pem"), x509.load_pem_x509_certificate, - backend, ) cert2 = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert == cert2 @@ -788,7 +904,6 @@ def test_negative_serial_number(self, backend): cert = _load_cert( os.path.join("x509", "custom", "negative_serial.pem"), x509.load_pem_x509_certificate, - backend, ) with pytest.warns(utils.DeprecatedIn36): @@ -798,7 +913,6 @@ 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, - backend, ) with pytest.warns(UserWarning): assert ( @@ -820,7 +934,6 @@ 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, - backend, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) assert ( @@ -832,7 +945,6 @@ def test_load_bmpstring_explicittext(self, backend): cert = _load_cert( os.path.join("x509", "accvraiz1.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_class(x509.CertificatePolicies) et = ext.value[0].policy_qualifiers[0].explicit_text @@ -846,7 +958,6 @@ def test_load_der_cert(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) assert isinstance(cert, x509.Certificate) assert cert.serial_number == 2 @@ -858,7 +969,6 @@ def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.signature == binascii.unhexlify( b"8e0f72fcbebe4755abcaf76c8ce0bae17cde4db16291638e1b1ce04a93cdb4c" @@ -885,7 +995,6 @@ def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"308202d8a003020102020900a06cb4b955f7f4db300d06092a864886f70d010" @@ -923,11 +1032,24 @@ def test_tbs_certificate_bytes(self, backend): 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, - backend, ) with pytest.raises( @@ -940,7 +1062,6 @@ 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, - backend, ) # This cert doesn't have an SCT list extension, so it will throw a @@ -955,7 +1076,6 @@ def test_tbs_precertificate_bytes_strips_scts(self, backend): cert = _load_cert( os.path.join("x509", "cryptography-scts.pem"), x509.load_pem_x509_certificate, - backend, ) expected_tbs_precertificate_bytes = load_vectors_from_file( @@ -977,7 +1097,6 @@ def test_issuer(self, backend): "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, - backend, ) issuer = cert.issuer assert isinstance(issuer, x509.Name) @@ -996,7 +1115,6 @@ 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, - backend, ) issuer = cert.issuer @@ -1043,7 +1161,6 @@ def test_subject(self, backend): "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, - backend, ) subject = cert.subject assert isinstance(subject, x509.Name) @@ -1068,7 +1185,6 @@ def test_unicode_name(self, backend): cert = _load_cert( os.path.join("x509", "custom", "utf8_common_name.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ x509.NameAttribute(NameOID.COMMON_NAME, "We heart UTF8!\u2122") @@ -1081,7 +1197,6 @@ 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, - backend, ) with pytest.raises(ValueError, match="subject"): cert.subject @@ -1092,7 +1207,6 @@ def test_non_ascii_dns_name(self, backend): cert = _load_cert( os.path.join("x509", "utf8-dnsname.pem"), x509.load_pem_x509_certificate, - backend, ) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName @@ -1114,7 +1228,6 @@ 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, - backend, ) subject = cert.subject assert isinstance(subject, x509.Name) @@ -1159,7 +1272,6 @@ 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, - backend, ) assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) @@ -1180,7 +1292,6 @@ def test_utc_pre_2000_not_before_cert(self, backend): "Validpre2000UTCnotBeforeDateTest3EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime(1950, 1, 1, 12, 1) @@ -1194,7 +1305,6 @@ def test_pre_2000_utc_not_after_cert(self, backend): "Invalidpre2000UTCEEnotAfterDateTest7EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_after == datetime.datetime(1999, 1, 1, 12, 1) @@ -1203,7 +1313,6 @@ def test_post_2000_utc_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime( 2014, 11, 26, 21, 41, 20 @@ -1221,7 +1330,6 @@ def test_generalized_time_not_before_cert(self, backend): "ValidGeneralizedTimenotBeforeDateTest4EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime(2002, 1, 1, 12, 1) assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) @@ -1236,7 +1344,6 @@ def test_generalized_time_not_after_cert(self, backend): "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) assert cert.not_valid_after == datetime.datetime(2050, 1, 1, 12, 1) @@ -1247,21 +1354,37 @@ def test_invalid_version_cert(self, backend): _load_cert( os.path.join("x509", "custom", "invalid_version.pem"), x509.load_pem_x509_certificate, - backend, ) 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, - backend, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert == cert2 @@ -1269,7 +1392,6 @@ def test_ne(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert2 = _load_cert( os.path.join( @@ -1279,7 +1401,6 @@ def test_ne(self, backend): "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert cert != cert2 assert cert != object() @@ -1288,12 +1409,10 @@ def test_ordering_unsupported(self, backend): cert = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) with pytest.raises(TypeError, match="cannot be ordered"): cert > cert2 # type: ignore[operator] @@ -1302,12 +1421,10 @@ def test_hash(self, backend): cert1 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert2 = _load_cert( os.path.join("x509", "custom", "post2000utctime.pem"), x509.load_pem_x509_certificate, - backend, ) cert3 = _load_cert( os.path.join( @@ -1317,7 +1434,6 @@ def test_hash(self, backend): "ValidGeneralizedTimenotAfterDateTest8EE.crt", ), x509.load_der_x509_certificate, - backend, ) assert hash(cert1) == hash(cert2) @@ -1327,7 +1443,6 @@ def test_version_1_cert(self, backend): cert = _load_cert( os.path.join("x509", "v1_cert.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.version is x509.Version.v1 @@ -1351,7 +1466,6 @@ 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, - backend, ) with raises_unsupported_algorithm(None): cert.signature_hash_algorithm @@ -1361,7 +1475,6 @@ def test_public_bytes_pem(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) # Encode it to PEM and load it back. @@ -1387,7 +1500,6 @@ def test_public_bytes_der(self, backend): cert = _load_cert( os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), x509.load_der_x509_certificate, - backend, ) # Encode it to DER and load it back. @@ -1395,7 +1507,6 @@ def test_public_bytes_der(self, backend): cert.public_bytes( encoding=serialization.Encoding.DER, ), - backend, ) # We should recover what we had to start with. @@ -1412,7 +1523,6 @@ 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, - backend, ) with pytest.raises(TypeError): @@ -1439,7 +1549,7 @@ def test_public_bytes_match( cert_bytes = load_vectors_from_file( cert_path, lambda pemfile: pemfile.read(), mode="rb" ) - cert = loader_func(cert_bytes, backend) + cert = loader_func(cert_bytes) serialized = cert.public_bytes(encoding) assert serialized == cert_bytes @@ -1447,7 +1557,6 @@ def test_certificate_repr(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) assert repr(cert) == ( " csr2 # type: ignore[operator] @@ -1845,17 +2086,14 @@ def test_hash(self, backend): request1 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) request2 = _load_cert( os.path.join("x509", "requests", "rsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) request3 = _load_cert( os.path.join("x509", "requests", "san_rsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) assert hash(request1) == hash(request2) @@ -1864,7 +2102,6 @@ def test_hash(self, backend): @pytest.mark.parametrize( ("hashalg", "hashalg_oid"), [ - (hashes.SHA1, x509.SignatureAlgorithmOID.RSA_WITH_SHA1), (hashes.SHA224, x509.SignatureAlgorithmOID.RSA_WITH_SHA224), (hashes.SHA256, x509.SignatureAlgorithmOID.RSA_WITH_SHA256), (hashes.SHA384, x509.SignatureAlgorithmOID.RSA_WITH_SHA384), @@ -1875,12 +2112,14 @@ def test_hash(self, backend): (hashes.SHA3_512, x509.SignatureAlgorithmOID.RSA_WITH_SHA3_512), ], ) - def test_build_cert(self, hashalg, hashalg_oid, backend): + 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.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(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) @@ -1935,6 +2174,7 @@ def test_build_cert(self, hashalg, hashalg_oid, backend): assert cert.version is x509.Version.v3 assert cert.signature_algorithm_oid == hashalg_oid + assert type(cert.signature_hash_algorithm) is hashalg assert cert.not_valid_before == not_valid_before assert cert.not_valid_after == not_valid_after basic_constraints = cert.extensions.get_extension_for_oid( @@ -1953,9 +2193,11 @@ def test_build_cert(self, hashalg, hashalg_oid, backend): x509.DNSName("cryptography.io"), ] - def test_build_cert_private_type_encoding(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -2002,9 +2244,11 @@ def test_build_cert_private_type_encoding(self, backend): == _ASN1Type.UTF8String ) - def test_build_cert_printable_string_country_name(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -2057,8 +2301,10 @@ def test_build_cert_printable_string_country_name(self, backend): class TestCertificateBuilder: - def test_checks_for_unsupported_extensions(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_checks_for_unsupported_extensions( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .subject_name( @@ -2075,10 +2321,12 @@ def test_checks_for_unsupported_extensions(self, backend): ) with pytest.raises(NotImplementedError): - builder.sign(private_key, hashes.SHA1(), backend) + builder.sign(private_key, hashes.SHA256(), backend) - def test_encode_nonstandard_aia(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_encode_nonstandard_aia( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 aia = x509.AuthorityInformationAccess( [ @@ -2106,8 +2354,10 @@ def test_encode_nonstandard_aia(self, backend): builder.sign(private_key, hashes.SHA256(), backend) - def test_encode_nonstandard_sia(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_encode_nonstandard_sia( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 sia = x509.SubjectInformationAccess( [ @@ -2126,78 +2376,282 @@ def test_encode_nonstandard_sia(self, backend): .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) - .public_key(private_key.public_key()) + .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) + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + parsed = asn1.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(2015, 1, 1)) - .not_valid_after(datetime.datetime(2040, 1, 1)) - .add_extension(sia, False) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) ) - - cert = builder.sign(private_key, hashes.SHA256(), backend) - ext = cert.extensions.get_extension_for_oid( - ExtensionOID.SUBJECT_INFORMATION_ACCESS + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO ) - assert ext.value == sia - - def test_subject_dn_asn1_types(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + with pytest.raises(TypeError): + builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) - 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 = ( + def test_sign_invalid_padding( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + builder = ( x509.CertificateBuilder() - .subject_name(name) - .issuer_name(name) - .public_key(private_key.public_key()) + .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(1999, 1, 1)) - .not_valid_after(datetime.datetime(2020, 1, 1)) - .sign(private_key, hashes.SHA256(), backend) + .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() + ) - 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, not_valid_before, not_valid_after, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_sign_pss_hash_none( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): builder = ( x509.CertificateBuilder() .subject_name( @@ -2206,24 +2660,17 @@ def test_extreme_times(self, not_valid_before, not_valid_after, backend): .issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) - .public_key(private_key.public_key()) + .public_key(rsa_key_2048.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) - assert cert.not_valid_before == not_valid_before - assert cert.not_valid_after == not_valid_after - parsed = asn1.test_parse_certificate( - cert.public_bytes(serialization.Encoding.DER) + .not_valid_before(datetime.datetime(2020, 1, 1)) + .not_valid_after(datetime.datetime(2038, 1, 1)) ) - # UTC TIME - assert parsed.not_before_tag == 0x17 - # GENERALIZED TIME - assert parsed.not_after_tag == 0x18 + 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, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_no_subject_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(777) @@ -2237,8 +2684,8 @@ def test_no_subject_name(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - def test_no_issuer_name(self, backend): - subject_private_key = RSA_KEY_2048.private_key(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) @@ -2252,8 +2699,8 @@ def test_no_issuer_name(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - def test_no_public_key(self, backend): - subject_private_key = RSA_KEY_2048.private_key(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) @@ -2269,8 +2716,10 @@ def test_no_public_key(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - def test_no_not_valid_before(self, backend): - subject_private_key = RSA_KEY_2048.private_key(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) @@ -2286,8 +2735,10 @@ def test_no_not_valid_before(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - def test_no_not_valid_after(self, backend): - subject_private_key = RSA_KEY_2048.private_key(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) @@ -2303,8 +2754,8 @@ def test_no_not_valid_after(self, backend): with pytest.raises(ValueError): builder.sign(subject_private_key, hashes.SHA256(), backend) - def test_no_serial_number(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_no_serial_number(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .issuer_name( @@ -2368,15 +2819,19 @@ def test_not_valid_after_before_not_valid_before(self): with pytest.raises(ValueError): builder.not_valid_after(datetime.datetime(2001, 1, 1, 12, 1)) - def test_public_key_must_be_public_key(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -2397,8 +2852,10 @@ def test_serial_number_must_be_positive(self): with pytest.raises(ValueError): x509.CertificateBuilder().serial_number(0) - def test_minimal_serial_number(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + def test_minimal_serial_number( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + subject_private_key = rsa_key_2048 builder = ( x509.CertificateBuilder() .serial_number(1) @@ -2415,8 +2872,10 @@ def test_minimal_serial_number(self, backend): cert = builder.sign(subject_private_key, hashes.SHA256(), backend) assert cert.serial_number == 1 - def test_biggest_serial_number(self, backend): - subject_private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -2443,12 +2902,13 @@ def test_serial_number_may_only_be_set_once(self): with pytest.raises(ValueError): builder.serial_number(20) - def test_aware_not_valid_after(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - time = tz.localize(time) + 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.private_key(backend) + private_key = rsa_key_2048 cert_builder = x509.CertificateBuilder().not_valid_after(time) cert_builder = ( cert_builder.subject_name( @@ -2465,9 +2925,9 @@ def test_aware_not_valid_after(self, backend): cert = cert_builder.sign(private_key, hashes.SHA256(), backend) assert cert.not_valid_after == utc_time - def test_earliest_time(self, backend): + def test_earliest_time(self, rsa_key_2048: rsa.RSAPrivateKey, backend): time = datetime.datetime(1950, 1, 1) - private_key = RSA_KEY_2048.private_key(backend) + private_key = rsa_key_2048 cert_builder = ( x509.CertificateBuilder() .subject_name( @@ -2515,12 +2975,13 @@ def test_not_valid_after_may_only_be_set_once(self): with pytest.raises(ValueError): builder.not_valid_after(datetime.datetime.now()) - def test_aware_not_valid_before(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - time = tz.localize(time) + 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.private_key(backend) + private_key = rsa_key_2048 cert_builder = x509.CertificateBuilder().not_valid_before(time) cert_builder = ( cert_builder.subject_name( @@ -2583,8 +3044,10 @@ def test_add_invalid_extension_type(self): ) @pytest.mark.parametrize("algorithm", [object(), None]) - def test_sign_with_unsupported_hash(self, algorithm, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -2648,28 +3111,6 @@ def test_sign_with_unsupported_hash_ed448(self, backend): 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", - ) - def test_sign_rsa_with_md5(self, backend): - private_key = RSA_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)) - ) - cert = builder.sign(private_key, hashes.MD5(), backend) - assert isinstance(cert.signature_hash_algorithm, hashes.MD5) - @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Requires OpenSSL with MD5 support", @@ -2703,7 +3144,7 @@ def test_sign_dsa_with_unsupported_hash(self, hash_algorithm, backend): .not_valid_before(datetime.datetime(2002, 1, 1, 12, 1)) .not_valid_after(datetime.datetime(2032, 1, 1, 12, 1)) ) - with pytest.raises(ValueError): + with pytest.raises(UnsupportedAlgorithm): builder.sign(private_key, hash_algorithm, backend) @pytest.mark.supported( @@ -2726,8 +3167,10 @@ def test_sign_ec_with_md5(self, backend): .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.MD5(), backend) + with pytest.raises(UnsupportedAlgorithm): + builder.sign( + private_key, hashes.MD5(), backend # type: ignore[arg-type] + ) @pytest.mark.supported( only_if=lambda backend: backend.dsa_supported(), @@ -2736,7 +3179,6 @@ def test_sign_ec_with_md5(self, backend): @pytest.mark.parametrize( ("hashalg", "hashalg_oid"), [ - (hashes.SHA1, x509.SignatureAlgorithmOID.DSA_WITH_SHA1), (hashes.SHA224, x509.SignatureAlgorithmOID.DSA_WITH_SHA224), (hashes.SHA256, x509.SignatureAlgorithmOID.DSA_WITH_SHA256), (hashes.SHA384, x509.SignatureAlgorithmOID.DSA_WITH_SHA384), @@ -2799,7 +3241,6 @@ def test_build_cert_with_dsa_private_key( @pytest.mark.parametrize( ("hashalg", "hashalg_oid"), [ - (hashes.SHA1, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA1), (hashes.SHA224, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA224), (hashes.SHA256, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA256), (hashes.SHA384, x509.SignatureAlgorithmOID.ECDSA_WITH_SHA384), @@ -2849,6 +3290,7 @@ def test_build_cert_with_ec_private_key( assert cert.version is x509.Version.v3 assert cert.signature_algorithm_oid == hashalg_oid + assert type(cert.signature_hash_algorithm) is hashalg assert cert.not_valid_before == not_valid_before assert cert.not_valid_after == not_valid_after basic_constraints = cert.extensions.get_extension_for_oid( @@ -2867,8 +3309,10 @@ def test_build_cert_with_ec_private_key( x509.DNSName("cryptography.io"), ] - def test_build_cert_with_bmpstring_universalstring_name(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -2965,8 +3409,10 @@ def test_build_cert_with_ed25519(self, backend): only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) - def test_build_cert_with_public_ed25519_rsa_sig(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -3063,8 +3509,10 @@ def test_build_cert_with_ed448(self, backend): only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) - def test_build_cert_with_public_ed448_rsa_sig(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -3112,9 +3560,13 @@ def test_build_cert_with_public_ed448_rsa_sig(self, backend): ], ) def test_build_cert_with_public_x25519_x448_rsa_sig( - self, priv_key_cls, pub_key_cls, backend + self, + rsa_key_2048: rsa.RSAPrivateKey, + priv_key_cls, + pub_key_cls, + backend, ): - issuer_private_key = RSA_KEY_2048.private_key(backend) + issuer_private_key = rsa_key_2048 subject_private_key = priv_key_cls.generate() not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) @@ -3148,9 +3600,11 @@ def test_build_cert_with_public_x25519_x448_rsa_sig( assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), pub_key_cls) - def test_build_cert_with_rsa_key_too_small(self, backend): - issuer_private_key = RSA_KEY_512.private_key(backend) - subject_private_key = RSA_KEY_512.private_key(backend) + 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) @@ -3617,9 +4071,11 @@ def test_build_cert_with_rsa_key_too_small(self, backend): x509.SubjectKeyIdentifier, ], ) - def test_extensions(self, add_ext, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -3663,8 +4119,10 @@ def test_extensions(self, add_ext, backend): assert ext.critical is False assert ext.value == add_ext - def test_build_ca_request_with_path_length_none(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_ca_request_with_path_length_none( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() @@ -3699,8 +4157,10 @@ def test_build_ca_request_with_path_length_none(self, backend): ) ], ) - def test_unrecognized_extension(self, backend, unrecognized): - private_key = RSA_KEY_2048.private_key(backend) + def test_unrecognized_extension( + self, rsa_key_2048: rsa.RSAPrivateKey, backend, unrecognized + ): + private_key = rsa_key_2048 cert = ( x509.CertificateBuilder() @@ -3724,8 +4184,10 @@ def test_unrecognized_extension(self, backend, unrecognized): class TestCertificateSigningRequestBuilder: - def test_sign_invalid_hash_algorithm(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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([]) @@ -3761,57 +4223,17 @@ def test_request_with_unsupported_hash_ed448(self, backend): 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", - ) - def test_sign_rsa_with_md5(self, backend): - private_key = RSA_KEY_2048.private_key(backend) - - builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")]) - ) - request = builder.sign(private_key, hashes.MD5(), backend) - assert isinstance(request.signature_hash_algorithm, hashes.MD5) - - @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.", - ) - def test_sign_dsa_with_md5(self, backend): - private_key = DSA_KEY_2048.private_key(backend) - builder = x509.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")]) - ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), 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.CertificateSigningRequestBuilder().subject_name( - x509.Name([x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA")]) - ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) - - def test_no_subject_name(self, backend): - private_key = RSA_KEY_2048.private_key(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, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_ca_request_with_rsa( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() @@ -3841,8 +4263,10 @@ def test_build_ca_request_with_rsa(self, backend): assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 - def test_build_ca_request_with_unicode(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_ca_request_with_unicode( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() @@ -3870,8 +4294,10 @@ def test_build_ca_request_with_unicode(self, backend): x509.NameAttribute(NameOID.ORGANIZATION_NAME, "PyCA\U0001f37a"), ] - def test_subject_dn_asn1_types(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_subject_dn_asn1_types( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() @@ -3928,8 +4354,10 @@ def test_subject_dn_asn1_types(self, backend): == asn1_type ) - def test_build_ca_request_with_multivalue_rdns(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -3957,8 +4385,10 @@ def test_build_ca_request_with_multivalue_rdns(self, backend): assert isinstance(loaded_request.subject, x509.Name) assert loaded_request.subject == subject - def test_build_nonca_request_with_rsa(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_build_nonca_request_with_rsa( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 request = ( x509.CertificateSigningRequestBuilder() @@ -4055,11 +4485,8 @@ def test_build_ca_request_with_ed25519(self, backend): assert list(subject) == [ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] - basic_constraints = typing.cast( - x509.Extension[x509.BasicConstraints], - request.extensions.get_extension_for_oid( - ExtensionOID.BASIC_CONSTRAINTS - ), + basic_constraints = request.extensions.get_extension_for_class( + x509.BasicConstraints ) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @@ -4096,11 +4523,8 @@ def test_build_ca_request_with_ed448(self, backend): assert list(subject) == [ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Texas"), ] - basic_constraints = typing.cast( - x509.Extension[x509.BasicConstraints], - request.extensions.get_extension_for_oid( - ExtensionOID.BASIC_CONSTRAINTS - ), + basic_constraints = request.extensions.get_extension_for_class( + x509.BasicConstraints ) assert basic_constraints.value.ca is True assert basic_constraints.value.path_length == 2 @@ -4163,8 +4587,10 @@ def test_add_invalid_extension_type(self): False, ) - def test_add_unsupported_extension(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_add_unsupported_extension( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = x509.CertificateSigningRequestBuilder() builder = ( builder.subject_name( @@ -4179,8 +4605,10 @@ def test_add_unsupported_extension(self, backend): with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) - def test_add_two_extensions(self, backend): - private_key = RSA_KEY_2048.private_key(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( @@ -4415,8 +4843,10 @@ def test_set_subject_twice(self): ), ], ) - def test_extensions(self, add_ext, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_extensions( + self, rsa_key_2048: rsa.RSAPrivateKey, add_ext, backend + ): + private_key = rsa_key_2048 csr = ( x509.CertificateSigningRequestBuilder() @@ -4435,8 +4865,10 @@ def test_extensions(self, add_ext, backend): assert not ext.critical assert ext.value == add_ext - def test_invalid_asn1_othername(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_invalid_asn1_othername( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = ( x509.CertificateSigningRequestBuilder() @@ -4459,8 +4891,10 @@ def test_invalid_asn1_othername(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - def test_subject_alt_name_unsupported_general_name(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_subject_alt_name_unsupported_general_name( + self, rsa_key_2048: rsa.RSAPrivateKey, backend + ): + private_key = rsa_key_2048 builder = ( x509.CertificateSigningRequestBuilder() @@ -4476,8 +4910,8 @@ def test_subject_alt_name_unsupported_general_name(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - def test_rsa_key_too_small(self, backend): - private_key = RSA_KEY_512.private_key(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")]) @@ -4502,11 +4936,11 @@ def test_load_dsa_cert(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, - backend, ) 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" @@ -4552,7 +4986,6 @@ def test_signature(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.signature == binascii.unhexlify( b"302c021425c4a84a936ab311ee017d3cbd9a3c650bb3ae4a02145d30c64b4326" @@ -4566,7 +4999,6 @@ def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"3082051aa003020102020900a37352e0b2142f86300906072a8648ce3804033" @@ -4621,6 +5053,24 @@ def test_tbs_certificate_bytes(self, backend): 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(), @@ -4645,7 +5095,7 @@ class TestDSACertificateRequest: ], ) def test_load_dsa_request(self, path, loader_func, backend): - request = _load_cert(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) @@ -4663,7 +5113,6 @@ def test_signature(self, backend): request = _load_cert( os.path.join("x509", "requests", "dsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) assert request.signature == binascii.unhexlify( b"302c021461d58dc028d0110818a7d817d74235727c4acfdf0214097b52e198e" @@ -4674,7 +5123,6 @@ def test_tbs_certrequest_bytes(self, backend): request = _load_cert( os.path.join("x509", "requests", "dsa_sha1.pem"), x509.load_pem_x509_csr, - backend, ) assert request.tbs_certrequest_bytes == binascii.unhexlify( b"3082021802010030573118301606035504030c0f63727970746f677261706879" @@ -4725,7 +5173,6 @@ def test_load_ecdsa_cert(self, backend): cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, - backend, ) assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) public_key = cert.public_key() @@ -4742,12 +5189,35 @@ def test_load_ecdsa_cert(self, backend): 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, - backend, ) assert cert.subject == x509.Name( [ @@ -4770,18 +5240,32 @@ 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, - backend, ) 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, - backend, ) assert cert.signature == binascii.unhexlify( b"3065023100adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085" @@ -4806,7 +5290,6 @@ def test_tbs_certificate_bytes(self, backend): cert = _load_cert( os.path.join("x509", "ecdsa_root.pem"), x509.load_pem_x509_certificate, - backend, ) assert cert.tbs_certificate_bytes == binascii.unhexlify( b"308201c5a0030201020210055556bcf25ea43535c3a40fd5ab4572300a06082" @@ -4839,7 +5322,6 @@ def test_load_ecdsa_no_named_curve(self, backend): cert = _load_cert( os.path.join("x509", "custom", "ec_no_named_curve.pem"), x509.load_pem_x509_certificate, - backend, ) # This test can trigger three different value errors depending # on OpenSSL/BoringSSL and versions. Match on the text to ensure @@ -4847,6 +5329,24 @@ def test_load_ecdsa_no_named_curve(self, backend): 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( @@ -4864,7 +5364,7 @@ class TestECDSACertificateRequest: ) def test_load_ecdsa_certificate_request(self, path, loader_func, backend): _skip_curve_unsupported(backend, ec.SECP384R1()) - request = _load_cert(path, loader_func, backend) + 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) @@ -4883,7 +5383,6 @@ def test_signature(self, backend): request = _load_cert( os.path.join("x509", "requests", "ec_sha256.pem"), x509.load_pem_x509_csr, - backend, ) assert request.signature == binascii.unhexlify( b"306502302c1a9f7de8c1787332d2307a886b476a59f172b9b0e250262f3238b1" @@ -4897,7 +5396,6 @@ def test_tbs_certrequest_bytes(self, backend): request = _load_cert( os.path.join("x509", "requests", "ec_sha256.pem"), x509.load_pem_x509_csr, - backend, ) assert request.tbs_certrequest_bytes == binascii.unhexlify( b"3081d602010030573118301606035504030c0f63727970746f6772617068792" @@ -4925,7 +5423,6 @@ def test_unsupported_subject_public_key_info(self, backend): "x509", "custom", "unsupported_subject_public_key_info.pem" ), x509.load_pem_x509_certificate, - backend, ) with pytest.raises(ValueError): @@ -4936,7 +5433,6 @@ def test_bad_time_in_validity(self, backend): _load_cert( os.path.join("x509", "badasn1time.pem"), x509.load_pem_x509_certificate, - backend, ) @@ -5451,7 +5947,6 @@ def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "ed25519", "root-ed25519.pem"), x509.load_pem_x509_certificate, - backend, ) # self-signed, so this will work public_key = cert.public_key() @@ -5461,15 +5956,33 @@ def test_load_pem_cert(self, backend): 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, - backend, ) 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(), @@ -5480,7 +5993,6 @@ def test_load_pem_cert(self, backend): cert = _load_cert( os.path.join("x509", "ed448", "root-ed448.pem"), x509.load_pem_x509_certificate, - backend, ) # self-signed, so this will work public_key = cert.public_key() @@ -5490,6 +6002,25 @@ def test_load_pem_cert(self, backend): 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( @@ -5509,9 +6040,9 @@ def load_key(self, backend): param = params.parameters(backend) return param.generate_private_key() - def test_crt_signing_check(self, backend): + def test_crt_signing_check(self, rsa_key_2048: rsa.RSAPrivateKey, backend): issuer_private_key = self.load_key(backend) - public_key = RSA_KEY_2048.private_key(backend).public_key() + 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 = ( @@ -5715,7 +6246,6 @@ def test_get_attribute_for_oid_challenge(self, backend): request = _load_cert( os.path.join("x509", "requests", "challenge.pem"), x509.load_pem_x509_csr, - backend, ) with pytest.warns(utils.DeprecatedIn36): assert ( @@ -5736,7 +6266,6 @@ 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, - backend, ) with pytest.warns(utils.DeprecatedIn36): assert ( @@ -5772,7 +6301,6 @@ 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, - backend, ) # Unsupported in the legacy path @@ -5793,7 +6321,6 @@ 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, - backend, ) with pytest.raises(ValueError, match="Long-form"): request.attributes @@ -5805,7 +6332,6 @@ def test_challenge_multivalued(self, backend): request = _load_cert( os.path.join("x509", "requests", "challenge-multi-valued.der"), x509.load_der_x509_csr, - backend, ) with pytest.raises(ValueError, match="Only single-valued"): with pytest.warns(utils.DeprecatedIn36): @@ -5820,7 +6346,6 @@ def test_no_challenge_password(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, - backend, ) with pytest.raises(x509.AttributeNotFound) as exc: with pytest.warns(utils.DeprecatedIn36): @@ -5839,6 +6364,30 @@ def test_no_attributes(self, backend): request = _load_cert( os.path.join("x509", "requests", "rsa_sha256.pem"), x509.load_pem_x509_csr, - backend, ) 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 diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py index e3e3428..95c0677 100644 --- a/tests/x509/test_x509_crlbuilder.py +++ b/tests/x509/test_x509_crlbuilder.py @@ -7,22 +7,25 @@ import pytest -import pytz - from cryptography import x509 +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448 +from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa from cryptography.x509.oid import ( AuthorityInformationAccessOID, NameOID, SignatureAlgorithmOID, ) -from .test_x509 import DummyExtension from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 -from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 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: @@ -40,13 +43,12 @@ def test_set_issuer_name_twice(self): x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) - def test_aware_last_update(self, backend): - last_time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - last_time = tz.localize(last_time) + 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.private_key(backend) + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( @@ -82,13 +84,12 @@ def test_set_last_update_twice(self): with pytest.raises(ValueError): builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) - def test_aware_next_update(self, backend): - next_time = datetime.datetime(2022, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - next_time = tz.localize(next_time) + 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.private_key(backend) + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( @@ -158,8 +159,8 @@ def test_add_invalid_revoked_certificate(self): with pytest.raises(TypeError): builder.add_revoked_certificate(object()) # type:ignore[arg-type] - def test_no_issuer_name(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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)) @@ -169,8 +170,8 @@ def test_no_issuer_name(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - def test_no_last_update(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_no_last_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( @@ -182,8 +183,8 @@ def test_no_last_update(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - def test_no_next_update(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + def test_no_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend): + private_key = rsa_key_2048 builder = ( x509.CertificateRevocationListBuilder() .issuer_name( @@ -195,8 +196,8 @@ def test_no_next_update(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) - def test_sign_empty_list(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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 = ( @@ -243,8 +244,10 @@ def test_sign_empty_list(self, backend): ), ], ) - def test_sign_extensions(self, backend, extension): - private_key = RSA_KEY_2048.private_key(backend) + 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 = ( @@ -270,8 +273,10 @@ def test_sign_extensions(self, backend, extension): assert ext.critical is False assert ext.value == extension - def test_sign_multiple_extensions_critical(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -307,8 +312,10 @@ def test_sign_multiple_extensions_critical(self, backend): assert ext2.critical is True assert ext2.value == ian - def test_freshestcrl_extension(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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( @@ -349,8 +356,10 @@ def test_freshestcrl_extension(self, backend): assert isinstance(uri, x509.UniformResourceIdentifier) assert uri.value == "http://d.om/delta" - def test_add_unsupported_extension(self, backend): - private_key = RSA_KEY_2048.private_key(backend) + 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 = ( @@ -371,8 +380,10 @@ def test_add_unsupported_extension(self, backend): with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) - def test_add_unsupported_entry_extension(self, backend): - private_key = RSA_KEY_2048.private_key(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 = ( @@ -399,8 +410,10 @@ def test_add_unsupported_entry_extension(self, backend): with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) - def test_sign_rsa_key_too_small(self, backend): - private_key = RSA_KEY_512.private_key(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 = ( @@ -421,8 +434,10 @@ def test_sign_rsa_key_too_small(self, backend): with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) - def test_sign_with_invalid_hash(self, backend): - private_key = RSA_KEY_2048.private_key(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 = ( @@ -509,6 +524,10 @@ def test_sign_with_invalid_hash_ed448(self, 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( @@ -733,8 +752,10 @@ def test_dsa_key_sign_md5(self, backend): .next_update(next_time) ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) + with pytest.raises(UnsupportedAlgorithm): + builder.sign( + private_key, hashes.MD5(), backend # type: ignore[arg-type] + ) def test_ec_key_sign_md5(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) @@ -756,11 +777,15 @@ def test_ec_key_sign_md5(self, backend): .next_update(next_time) ) - with pytest.raises(ValueError): - builder.sign(private_key, hashes.MD5(), backend) + with pytest.raises(UnsupportedAlgorithm): + builder.sign( + private_key, hashes.MD5(), backend # type: ignore[arg-type] + ) - def test_sign_with_revoked_certificates(self, backend): - private_key = RSA_KEY_2048.private_key(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( diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py index fd09b09..fd7ff95 100644 --- a/tests/x509/test_x509_ext.py +++ b/tests/x509/test_x509_ext.py @@ -10,7 +10,6 @@ import typing import pretend - import pytest from cryptography import x509 @@ -37,10 +36,14 @@ SubjectInformationAccessOID, ) -from .test_x509 import _load_cert -from ..hazmat.primitives.fixtures_rsa import RSA_KEY_2048 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): @@ -677,7 +680,6 @@ def test_long_oid(self, backend): cert = _load_cert( os.path.join("x509", "bigoid.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_class(x509.CertificatePolicies) @@ -708,7 +710,6 @@ 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, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -730,7 +731,6 @@ def test_user_notice_with_notice_reference(self, backend): "x509", "custom", "cp_user_notice_with_notice_reference.pem" ), x509.load_pem_x509_certificate, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -759,7 +759,6 @@ def test_user_notice_with_explicit_text(self, backend): "x509", "custom", "cp_user_notice_with_explicit_text.pem" ), x509.load_pem_x509_certificate, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -781,7 +780,6 @@ def test_user_notice_no_explicit_text(self, backend): "x509", "custom", "cp_user_notice_no_explicit_text.pem" ), x509.load_pem_x509_certificate, - backend, ) cp = cert.extensions.get_extension_for_oid( @@ -801,9 +799,11 @@ def test_user_notice_no_explicit_text(self, backend): ] ) - def test_non_ascii_qualifier(self, backend): - issuer_private_key = RSA_KEY_2048.private_key(backend) - subject_private_key = RSA_KEY_2048.private_key(backend) + 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) @@ -1482,7 +1482,6 @@ def test_no_extensions(self, backend): cert = _load_cert( os.path.join("x509", "verisign_md2_root.pem"), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions assert len(ext) == 0 @@ -1498,7 +1497,6 @@ def test_one_extension(self, backend): "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None @@ -1508,7 +1506,6 @@ def test_duplicate_extension(self, backend): cert = _load_cert( os.path.join("x509", "custom", "two_basic_constraints.pem"), x509.load_pem_x509_certificate, - backend, ) with pytest.raises(x509.DuplicateExtension) as exc: cert.extensions @@ -1521,7 +1518,6 @@ def test_unsupported_critical_extension(self, backend): "x509", "custom", "unsupported_extension_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_oid( x509.ObjectIdentifier("1.2.3.4") @@ -1533,7 +1529,6 @@ def test_unsupported_extension(self, backend): cert = _load_cert( os.path.join("x509", "custom", "unsupported_extension_2.pem"), x509.load_pem_x509_certificate, - backend, ) extensions = cert.extensions assert len(extensions) == 2 @@ -1557,7 +1552,6 @@ def test_no_extensions_get_for_class(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) exts = cert.extensions with pytest.raises(x509.ExtensionNotFound) as exc: @@ -1573,7 +1567,6 @@ def test_indexing(self, backend): cert = _load_cert( os.path.join("x509", "cryptography.io.pem"), x509.load_pem_x509_certificate, - backend, ) exts = cert.extensions assert exts[-1] == exts[7] @@ -1585,7 +1578,6 @@ def test_one_extension_get_for_class(self, backend): "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) assert ext is not None @@ -1596,7 +1588,6 @@ def test_repr(self, backend): "x509", "custom", "basic_constraints_not_critical.pem" ), x509.load_pem_x509_certificate, - backend, ) assert repr(cert.extensions) == ( "" + ) + + 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", None, None # type:ignore[arg-type] + ) + oid = x509.ObjectIdentifier("1.2.3.4") + with pytest.raises(TypeError): + x509.MSCertificateTemplate( + oid, "notanint", None # type:ignore[arg-type] + ) + 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("__"): diff --git a/tests/x509/test_x509_revokedcertbuilder.py b/tests/x509/test_x509_revokedcertbuilder.py index b2facfa..e0f53f8 100644 --- a/tests/x509/test_x509_revokedcertbuilder.py +++ b/tests/x509/test_x509_revokedcertbuilder.py @@ -7,8 +7,6 @@ import pytest -import pytz - from cryptography import x509 @@ -59,9 +57,8 @@ def test_set_serial_number_twice(self): builder.serial_number(4) def test_aware_revocation_date(self, backend): - time = datetime.datetime(2012, 1, 16, 22, 43) - tz = pytz.timezone("US/Pacific") - time = tz.localize(time) + 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 = (