From d3e80d141dc29de27de1cd7a7c993f7be2b763b0 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 30 Sep 2021 10:18:44 +0800 Subject: [PATCH 001/206] reopen main for 36th release (#6335) --- CHANGELOG.rst | 7 +++++++ src/cryptography/__about__.py | 2 +- vectors/cryptography_vectors/__about__.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 32aae4f4b0ae..c907a859257a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ Changelog ========= +.. _v36-0-0: + +36.0.0 - `main`_ +~~~~~~~~~~~~~~~~ + + .. note:: This version is not yet released and is under active development. + .. _v35-0-0: 35.0.0 - 2021-09-29 diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py index 4d9cbfc815f2..d1f9d67448b7 100644 --- a/src/cryptography/__about__.py +++ b/src/cryptography/__about__.py @@ -9,7 +9,7 @@ "__copyright__", ] -__version__ = "35.0.0" +__version__ = "36.0.0.dev1" __author__ = "The Python Cryptographic Authority and individual contributors" __copyright__ = "Copyright 2013-2021 {}".format(__author__) diff --git a/vectors/cryptography_vectors/__about__.py b/vectors/cryptography_vectors/__about__.py index 5fd831e45a66..451f1358d9a7 100644 --- a/vectors/cryptography_vectors/__about__.py +++ b/vectors/cryptography_vectors/__about__.py @@ -6,4 +6,4 @@ "__version__", ] -__version__ = "35.0.0" +__version__ = "36.0.0.dev1" From 5ca120918fe45d306273b5adfe76e37e6a8b508b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 30 Sep 2021 10:18:50 +0800 Subject: [PATCH 002/206] release.py event we want is push not create (#6334) --- release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.py b/release.py index 1aa69efa84f4..5bc996aab02e 100644 --- a/release.py +++ b/release.py @@ -84,7 +84,7 @@ def fetch_github_actions_wheels(token, version): response = session.get( ( "https://api.github.com/repos/pyca/cryptography/actions/workflows/" - "wheel-builder.yml/runs?event=create" + "wheel-builder.yml/runs?event=push" ), headers={ "Content-Type": "application/json", From 0392f7851530fd84b4897d2af6ed92f6dc6c6edb Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 30 Sep 2021 14:04:46 +0300 Subject: [PATCH 003/206] Require asn1 >= 0.6.4 (#6338) Cryptography fails to build with asn 0.6.1. Require at least 0.6.4 Closes: https://github.com/pyca/cryptography/issues/6337 Signed-off-by: Christian Heimes --- 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 6d5907273657..4d2f8aca76ee 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] lazy_static = "1" pyo3 = { version = "0.14.5" } -asn1 = { version = "0.6", default-features = false, features = ["derive"] } +asn1 = { version = "0.6.4", default-features = false, features = ["derive"] } pem = "0.8" chrono = { version = "0.4", default-features = false, features = ["alloc"] } ouroboros = "0.11" From 3f6a359125af8b73e3dde76d95b852d6746d1519 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Sep 2021 08:25:03 -0400 Subject: [PATCH 004/206] Bump ouroboros from 0.11.1 to 0.12.0 in /src/rust (#6341) Bumps [ouroboros](https://github.com/joshua-maros/ouroboros) from 0.11.1 to 0.12.0. - [Release notes](https://github.com/joshua-maros/ouroboros/releases) - [Commits](https://github.com/joshua-maros/ouroboros/commits) --- updated-dependencies: - dependency-name: ouroboros dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/rust/Cargo.lock | 8 ++++---- src/rust/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index b6e6d333c86c..e6aeb213c993 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -161,9 +161,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "ouroboros" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3518a68fc597f6a42f83a31e41c039c3cbaa10fa8bb239c936c235e81cce873f" +checksum = "c711f35b4e881534535e7d943aa158ed673baf8e73b06bdd93b31703cf968cc3" dependencies = [ "aliasable", "ouroboros_macro", @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e23813b1bcb2d41a838849a2bbae40ae5c03c85ecabf04ba97086f438484714" +checksum = "adbdedd935f91827ea19bb8b97c20dd5870221eac3e9d9a2e70367ecc813479d" dependencies = [ "Inflector", "proc-macro-error", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 4d2f8aca76ee..c80934667b43 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -11,7 +11,7 @@ pyo3 = { version = "0.14.5" } asn1 = { version = "0.6.4", default-features = false, features = ["derive"] } pem = "0.8" chrono = { version = "0.4", default-features = false, features = ["alloc"] } -ouroboros = "0.11" +ouroboros = "0.12" [features] extension-module = ["pyo3/extension-module"] From 481bf97237211d73ddf9b34bbcf28c2171b9ec62 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 1 Oct 2021 07:55:22 -0400 Subject: [PATCH 005/206] fixes #6351 -- document PEM parser requirement (#6352) * fixes #6351 -- document PEM parser requirement * Update CHANGELOG.rst * Update CHANGELOG.rst --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c907a859257a..85742ea936da 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,10 @@ Changelog * Changed the :ref:`version scheme `. This will result in us incrementing the major version more frequently, but does not change our existing backwards compatibility policy. +* **BACKWARDS INCOMPATIBLE:** The :doc:`/x509/index` PEM parsers now require + that the PEM string passed have PEM delimiters of the correct type. For + example, parsing a private key PEM concatenated with a certificate PEM will + no longer be accepted by the PEM certificate parser. * **BACKWARDS INCOMPATIBLE:** The X.509 certificate parser no longer allows negative serial numbers. :rfc:`5280` has always prohibited these. * **BACKWARDS INCOMPATIBLE:** Invalid ASN.1 found during :doc:`/x509/index` From 8882ab80f4caf5c95bd16036c824f11238042ffe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 18:30:26 -0400 Subject: [PATCH 006/206] Bump pem from 0.8.3 to 1.0.0 in /src/rust (#6354) Bumps [pem](https://github.com/jcreekmore/pem-rs) from 0.8.3 to 1.0.0. - [Release notes](https://github.com/jcreekmore/pem-rs/releases) - [Changelog](https://github.com/jcreekmore/pem-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jcreekmore/pem-rs/compare/v0.8.3...v1.0.0) --- updated-dependencies: - dependency-name: pem dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/rust/Cargo.lock | 4 ++-- src/rust/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index e6aeb213c993..c6fb27a713ef 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -229,9 +229,9 @@ dependencies = [ [[package]] name = "pem" -version = "0.8.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +checksum = "3f2373df5233932a893d3bc2c78a0bf3f6d12590a1edd546b4fbefcac32c5c0f" dependencies = [ "base64", "once_cell", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index c80934667b43..4f323a29455d 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -9,7 +9,7 @@ publish = false lazy_static = "1" pyo3 = { version = "0.14.5" } asn1 = { version = "0.6.4", default-features = false, features = ["derive"] } -pem = "0.8" +pem = "1.0" chrono = { version = "0.4", default-features = false, features = ["alloc"] } ouroboros = "0.12" From 51221b2c48cd04fa6e31099d949f5d7bd564984d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 2 Oct 2021 21:52:54 +0800 Subject: [PATCH 007/206] support legacy PEM headers for certificate and CSR (#6356) --- docs/development/test-vectors.rst | 5 +++ src/rust/src/x509.rs | 8 +++-- tests/x509/test_x509.py | 16 +++++++++ .../x509/cryptography.io.old_header.pem | 33 +++++++++++++++++++ .../x509/requests/ec_sha256_old_header.pem | 10 ++++++ 5 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 vectors/cryptography_vectors/x509/cryptography.io.old_header.pem create mode 100644 vectors/cryptography_vectors/x509/requests/ec_sha256_old_header.pem diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index d8249926f495..2fd3470f3ca5 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -202,6 +202,9 @@ X.509 tree. * ``cryptography.io.pem`` - A leaf certificate issued by RapidSSL for the cryptography website. +* ``cryptography.io.old_header.pem`` - A leaf certificate issued by RapidSSL + for the cryptography website. This certificate uses the ``X509 CERTIFICATE`` + legacy PEM header format. * ``rapidssl_sha256_ca_g3.pem`` - The intermediate CA that issued the ``cryptography.io.pem`` certificate. * ``cryptography.io.precert.pem`` - A pre-certificate with the CT poison @@ -450,6 +453,8 @@ Custom X.509 Request Vectors using 2048 bit RSA and SHA256 generated using OpenSSL. * ``ec_sha256.pem`` and ``ec_sha256.der`` - Contain a certificate request using EC (``secp384r1``) and SHA256 generated using OpenSSL. +* ``ec_sha256_old_header.pem`` - Identical to ``ec_sha256.pem``, but uses + the ``-----BEGIN NEW CERTIFICATE REQUEST-----`` legacy PEM header format. * ``san_rsa_sha1.pem`` and ``san_rsa_sha1.der`` - Contain a certificate request using RSA and SHA1 with a subject alternative name extension generated using OpenSSL. diff --git a/src/rust/src/x509.rs b/src/rust/src/x509.rs index 1cbbce94bd61..605e12814ba6 100644 --- a/src/rust/src/x509.rs +++ b/src/rust/src/x509.rs @@ -392,7 +392,9 @@ fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, PyAsn #[pyo3::prelude::pyfunction] fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { let parsed = pem::parse(data)?; - if parsed.tag != "CERTIFICATE" { + // We support both PEM header strings that OpenSSL does + // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L32-L33 + if parsed.tag != "CERTIFICATE" && parsed.tag != "X509 CERTIFICATE" { return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( "Valid PEM but no BEGIN CERTIFICATE/END CERTIFICATE delimiters. Are you sure this is a certificate?" ))); @@ -671,7 +673,9 @@ impl CertificateSigningRequest { #[pyo3::prelude::pyfunction] fn load_pem_x509_csr(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { let parsed = pem::parse(data)?; - if parsed.tag != "CERTIFICATE REQUEST" { + // We support both PEM header strings that OpenSSL does + // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L35-L36 + if parsed.tag != "CERTIFICATE REQUEST" && parsed.tag != "NEW CERTIFICATE REQUEST" { // TODO: The old errors had the following URL: // See https://cryptography.io/en/latest/faq.html#why-can-t-i-import-my-pem-file for more details. return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 4241d1b9d7f1..d872316e41e2 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -686,6 +686,14 @@ def test_load_pem_cert(self, backend): cert.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1 ) + 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) + def test_negative_serial_number(self, backend): with pytest.raises(ValueError, match="TbsCertificate::serial"): _load_cert( @@ -1301,6 +1309,14 @@ def test_load_rsa_certificate_request(self, path, loader_func, backend): assert isinstance(extensions, x509.Extensions) assert list(extensions) == [] + def test_load_legacy_pem_header(self, backend): + cert = _load_cert( + os.path.join("x509", "requests", "ec_sha256_old_header.pem"), + x509.load_pem_x509_csr, + backend, + ) + assert isinstance(cert, x509.CertificateSigningRequest) + def test_invalid_pem(self, backend): with pytest.raises(ValueError, match="Unable to load"): x509.load_pem_x509_csr(b"notacsr", backend) diff --git a/vectors/cryptography_vectors/x509/cryptography.io.old_header.pem b/vectors/cryptography_vectors/x509/cryptography.io.old_header.pem new file mode 100644 index 000000000000..84470d96bbb7 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.old_header.pem @@ -0,0 +1,33 @@ +-----BEGIN X509 CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END X509 CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/requests/ec_sha256_old_header.pem b/vectors/cryptography_vectors/x509/requests/ec_sha256_old_header.pem new file mode 100644 index 000000000000..f9c932180ff8 --- /dev/null +++ b/vectors/cryptography_vectors/x509/requests/ec_sha256_old_header.pem @@ -0,0 +1,10 @@ +-----BEGIN NEW CERTIFICATE REQUEST----- +MIIBTzCB1gIBADBXMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8xDTALBgNVBAoM +BFB5Q0ExCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVz +dGluMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3hm1FMCzw66bOY6j4mtegWvc+RAs +rY8S/gL55MkkhySzkpftdYLgTYsypVEDjQkIaAOm0/uRoaEWfsAhWLAO+tOck5ZG +L6zP8P+vcVWBKQnTcmvVn94AHP9LubL1r4y6oAAwCgYIKoZIzj0EAwIDaAAwZQIw +LBqffejBeHMy0jB6iGtHalnxcrmw4lAmLzI4sbRe4RK7brNbD7VqEjuSlushLf/D +AjEAlM9EDJXFKCfVVq5tdlAOMAglXUfCn37ngu11WOUb/XaqRd9tmZ7VxGM0f+I4 +LRdR +-----END NEW CERTIFICATE REQUEST----- From ba880f4dde6c09f0888c083f68099ac36e9e3f6e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 2 Oct 2021 17:01:28 -0400 Subject: [PATCH 008/206] remove unused bindings (#6358) --- src/_cffi_src/openssl/x509v3.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 2cfb8308f3d5..4f659765869a 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -8,13 +8,11 @@ /* * This is part of a work-around for the difficulty cffi has in dealing with - * `LHASH_OF(foo)` as the name of a type. We invent a new, simpler name that + * `STACK_OF(foo)` as the name of a type. We invent a new, simpler name that * will be an alias for this type and use the alias throughout. This works * together with another opaque typedef for the same name in the TYPES section. * Note that the result is an opaque type. */ -typedef LHASH_OF(CONF_VALUE) Cryptography_LHASH_OF_CONF_VALUE; - typedef STACK_OF(ACCESS_DESCRIPTION) Cryptography_STACK_OF_ACCESS_DESCRIPTION; typedef STACK_OF(DIST_POINT) Cryptography_STACK_OF_DIST_POINT; typedef STACK_OF(POLICYQUALINFO) Cryptography_STACK_OF_POLICYQUALINFO; @@ -119,8 +117,6 @@ GENERAL_NAME *location; } ACCESS_DESCRIPTION; -typedef ... Cryptography_LHASH_OF_CONF_VALUE; - typedef ... Cryptography_STACK_OF_DIST_POINT; @@ -181,15 +177,11 @@ FUNCTIONS = """ -int X509V3_EXT_add_alias(int, int); void X509V3_set_ctx(X509V3_CTX *, X509 *, X509 *, X509_REQ *, X509_CRL *, int); int GENERAL_NAME_print(BIO *, GENERAL_NAME *); GENERAL_NAMES *GENERAL_NAMES_new(void); void GENERAL_NAMES_free(GENERAL_NAMES *); void *X509V3_EXT_d2i(X509_EXTENSION *); -int X509_check_ca(X509 *); -/* X509 became a const arg in 1.1.0 */ -void *X509_get_ext_d2i(X509 *, int, int *, int *); /* The last two char * args became const char * in 1.1.0 */ X509_EXTENSION *X509V3_EXT_nconf(CONF *, X509V3_CTX *, char *, char *); /* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the @@ -213,8 +205,6 @@ void *X509V3_set_ctx_nodb(X509V3_CTX *); int i2d_GENERAL_NAMES(GENERAL_NAMES *, unsigned char **); -GENERAL_NAMES *d2i_GENERAL_NAMES(GENERAL_NAMES **, const unsigned char **, - long); int sk_GENERAL_NAME_num(struct stack_st_GENERAL_NAME *); int sk_GENERAL_NAME_push(struct stack_st_GENERAL_NAME *, GENERAL_NAME *); @@ -236,9 +226,6 @@ ACCESS_DESCRIPTION *ACCESS_DESCRIPTION_new(void); void ACCESS_DESCRIPTION_free(ACCESS_DESCRIPTION *); -X509_EXTENSION *X509V3_EXT_conf_nid(Cryptography_LHASH_OF_CONF_VALUE *, - X509V3_CTX *, int, char *); - Cryptography_STACK_OF_DIST_POINT *sk_DIST_POINT_new_null(void); void sk_DIST_POINT_free(Cryptography_STACK_OF_DIST_POINT *); int sk_DIST_POINT_num(Cryptography_STACK_OF_DIST_POINT *); From 69fbf6eb0d7e5b97ed69b70fd37a3ac370366138 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 2 Oct 2021 17:44:32 -0400 Subject: [PATCH 009/206] remove unused bindings (#6357) --- src/_cffi_src/openssl/x509_vfy.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/_cffi_src/openssl/x509_vfy.py b/src/_cffi_src/openssl/x509_vfy.py index 16c0ae0beb50..2487c999de9c 100644 --- a/src/_cffi_src/openssl/x509_vfy.py +++ b/src/_cffi_src/openssl/x509_vfy.py @@ -164,12 +164,8 @@ Cryptography_STACK_OF_X509 *); void X509_STORE_CTX_trusted_stack(X509_STORE_CTX *, Cryptography_STACK_OF_X509 *); -void X509_STORE_CTX_set0_trusted_stack(X509_STORE_CTX *, - Cryptography_STACK_OF_X509 *); void X509_STORE_CTX_set_cert(X509_STORE_CTX *, X509 *); void X509_STORE_CTX_set_chain(X509_STORE_CTX *,Cryptography_STACK_OF_X509 *); -void X509_STORE_CTX_set0_untrusted(X509_STORE_CTX *, - Cryptography_STACK_OF_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 *); @@ -199,9 +195,6 @@ 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 *); -/* this CRYPTO_EX_DATA function became a macro in 1.1.0 */ -int X509_STORE_CTX_get_ex_new_index(long, void *, CRYPTO_EX_new *, - CRYPTO_EX_dup *, CRYPTO_EX_free *); /* X509_STORE_CTX */ void X509_STORE_CTX_set0_crls(X509_STORE_CTX *, From 94f232fad1ee4dafffbdbce0f278e93fedf657b2 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Sun, 3 Oct 2021 00:10:10 +0200 Subject: [PATCH 010/206] Replace broken viewcode with linkcode in doc (#6207) --- .readthedocs.yml | 9 +++ docs/{ => _ext}/cryptography-docs.py | 0 docs/_ext/linkcode_res.py | 106 +++++++++++++++++++++++++++ docs/conf.py | 7 +- 4 files changed, 120 insertions(+), 2 deletions(-) rename docs/{ => _ext}/cryptography-docs.py (100%) create mode 100644 docs/_ext/linkcode_res.py diff --git a/.readthedocs.yml b/.readthedocs.yml index 4fb69f672da2..e615d30d52c1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,3 +1,5 @@ +# https://docs.readthedocs.io/en/stable/config-file/v2.html#supported-settings + version: 2 sphinx: @@ -10,4 +12,11 @@ build: os: "ubuntu-20.04" tools: python: "3.9" + rust: "1.55" +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/docs/cryptography-docs.py b/docs/_ext/cryptography-docs.py similarity index 100% rename from docs/cryptography-docs.py rename to docs/_ext/cryptography-docs.py diff --git a/docs/_ext/linkcode_res.py b/docs/_ext/linkcode_res.py new file mode 100644 index 000000000000..9b6f427d4e88 --- /dev/null +++ b/docs/_ext/linkcode_res.py @@ -0,0 +1,106 @@ +import importlib +import inspect +import os +import sys + +import cryptography + +# -- Linkcode resolver ----------------------------------------------------- + +# This is HEAVILY inspired by numpy's +# https://github.com/numpy/numpy/blob/73fe877ff967f279d470b81ad447b9f3056c1335/doc/source/conf.py#L390 + +# Copyright (c) 2005-2020, NumPy Developers. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# * Neither the name of the NumPy Developers nor the names of any +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +def linkcode_resolve(domain, info): + """ + Determine the url corresponding to Python object + """ + if domain != "py": + return None + + modname = info["module"] + fullname = info["fullname"] + + try: + importlib.import_module(modname) + except Exception: + return None + submod = sys.modules.get(modname) + if submod is None: + return None + + obj = submod + for part in fullname.split("."): + try: + obj = getattr(obj, part) + except Exception: + return None + + # strip decorators, which would resolve to the source of the decorator + # possibly an upstream bug in getsourcefile, bpo-1764286 + try: + unwrap = inspect.unwrap + except AttributeError: + pass + else: + obj = unwrap(obj) + + fn = None + lineno = None + + try: + fn = inspect.getsourcefile(obj) + except Exception: + fn = None + if not fn: + return None + + try: + source, lineno = inspect.getsourcelines(obj) + except Exception: + lineno = None + + fn = os.path.relpath(fn, start=os.path.dirname(cryptography.__file__)) + + if lineno: + linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) + else: + linespec = "" + + url = "https://github.com/pyca/cryptography/blob/%s/src/cryptography/%s%s" + if "dev" in cryptography.__version__: + return url % ("main", fn, linespec) + else: + version = ".".join(cryptography.__version__.split(".")[:2] + ["x"]) + return url % (version, fn, linespec) diff --git a/docs/conf.py b/docs/conf.py index f79db277abfa..86d610168b14 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,7 +33,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath(".")) +sys.path.insert(0, os.path.abspath("_ext")) # -- General configuration ---------------------------------------------------- @@ -47,13 +47,16 @@ "sphinx.ext.autosectionlabel", "sphinx.ext.doctest", "sphinx.ext.intersphinx", - "sphinx.ext.viewcode", + "sphinx.ext.linkcode", "cryptography-docs", ] if spelling is not None: extensions.append("sphinxcontrib.spelling") +# Linkcode resolver +from linkcode_res import linkcode_resolve # noqa: E402, F401 + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] From 5c2fe56875eae822cd4f75436a7204efaae82ba5 Mon Sep 17 00:00:00 2001 From: Charlie Li Date: Sun, 3 Oct 2021 00:20:31 -0400 Subject: [PATCH 011/206] Support LibreSSL 3.4.0 (#6360) * Add LibreSSL 3.4.0 to CI * Add a LibreSSL 3.4.0 guard Since LibreSSL 3.4.0 makes most of the TLSv1.3 API available, redefine CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 to LibreSSL versions below 3.4.0. * DTLS_get_data_mtu does not exist in LibreSSL * Only EVP_Digest{Sign,Verify} exist in LibreSSL 3.4.0+ * SSL_CTX_{set,get}_keylog_callback does not exist in LibreSSL * Do not pollute CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 with LibreSSL While LibreSSL 3.4.0 supports more of TLSv1.3 API, the guard redefinition caused the X448 tests to run when not intended. --- .github/workflows/ci.yml | 1 + src/_cffi_src/openssl/cryptography.py | 3 +++ src/_cffi_src/openssl/evp.py | 15 ++++++++++----- src/_cffi_src/openssl/ssl.py | 3 ++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6962e99c927e..78b7b1a11573 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,7 @@ jobs: - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.1.5"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.2.6"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.3.4"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.4.0"}} - {VERSION: "3.10-dev", TOXENV: "py310"} RUST: - stable diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index 310b78baf165..8d21403644a2 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -33,9 +33,12 @@ #endif #if CRYPTOGRAPHY_IS_LIBRESSL +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 \ + (LIBRESSL_VERSION_NUMBER < 0x3040000f) #define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_332 \ (LIBRESSL_VERSION_NUMBER < 0x3030200f) #else +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 (0) #define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_332 (0) #endif diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py index 735b8c37cfa2..4c72fab84f0c 100644 --- a/src/_cffi_src/openssl/evp.py +++ b/src/_cffi_src/openssl/evp.py @@ -207,15 +207,21 @@ size_t) = NULL; #endif -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 +#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; -static const long Cryptography_HAS_RAW_KEY = 0; -static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 0; -int (*EVP_DigestFinalXOF)(EVP_MD_CTX *, unsigned char *, size_t) = NULL; 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; +static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 0; +int (*EVP_DigestFinalXOF)(EVP_MD_CTX *, unsigned char *, size_t) = NULL; 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 *, @@ -225,7 +231,6 @@ int (*EVP_PKEY_get_raw_public_key)(const EVP_PKEY *, unsigned char *, size_t *) = NULL; #else -static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 1; static const long Cryptography_HAS_RAW_KEY = 1; static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF = 1; #endif diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py index 34d0283894f3..50767a26b26a 100644 --- a/src/_cffi_src/openssl/ssl.py +++ b/src/_cffi_src/openssl/ssl.py @@ -711,7 +711,8 @@ SRTP_PROTECTION_PROFILE * (*SSL_get_selected_srtp_profile)(SSL *) = NULL; #endif -#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 +#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; From c45b6466db9e64d603d567f6a333f30cdd912bd6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 3 Oct 2021 00:43:44 -0400 Subject: [PATCH 012/206] order defines consistently (#6361) --- src/_cffi_src/openssl/cryptography.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index 8d21403644a2..d11a02a384ab 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -33,13 +33,13 @@ #endif #if CRYPTOGRAPHY_IS_LIBRESSL -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 \ - (LIBRESSL_VERSION_NUMBER < 0x3040000f) #define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_332 \ (LIBRESSL_VERSION_NUMBER < 0x3030200f) +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 \ + (LIBRESSL_VERSION_NUMBER < 0x3040000f) #else -#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 (0) #define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_332 (0) +#define CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340 (0) #endif #if OPENSSL_VERSION_NUMBER < 0x10100000 From 4feca6d03bb6a183c531a6fcf21411ca209e8f19 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 3 Oct 2021 01:29:54 -0400 Subject: [PATCH 013/206] bump libressl in CI (#6362) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78b7b1a11573..74ef112af128 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,8 +37,8 @@ jobs: - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "2.9.2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.0.2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.1.5"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.2.6"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.3.4"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.2.7"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.3.5"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.4.0"}} - {VERSION: "3.10-dev", TOXENV: "py310"} RUST: From 703de3afa0977404e4607faf45fa0f6fff78625d Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 3 Oct 2021 19:26:44 +0200 Subject: [PATCH 014/206] Accept combined PEM files with multiple sections (#6365) * accept combined PEM files with multiple sections * pass error messages into `find_in_pem` --- docs/development/test-vectors.rst | 4 ++ src/rust/src/x509.rs | 62 +++++++++++------ tests/x509/test_x509.py | 16 +++++ .../x509/cryptography.io.repeated_twice.pem | 66 +++++++++++++++++++ .../x509/cryptography.io.with_garbage.pem | 49 ++++++++++++++ 5 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 vectors/cryptography_vectors/x509/cryptography.io.repeated_twice.pem create mode 100644 vectors/cryptography_vectors/x509/cryptography.io.with_garbage.pem diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 2fd3470f3ca5..7dd8db083964 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -205,6 +205,10 @@ X.509 * ``cryptography.io.old_header.pem`` - A leaf certificate issued by RapidSSL for the cryptography website. This certificate uses the ``X509 CERTIFICATE`` legacy PEM header format. +* ``cryptography.io.repeated_twice.pem`` - The same as ``cryptography.io.pem``, + but the certificate is repeated twice. +* ``cryptography.io.with_garbage.pem`` - The same as ``cryptography.io.pem``, + but with other sections and text around it. * ``rapidssl_sha256_ca_g3.pem`` - The intermediate CA that issued the ``cryptography.io.pem`` certificate. * ``cryptography.io.precert.pem`` - A pre-certificate with the CT poison diff --git a/src/rust/src/x509.rs b/src/rust/src/x509.rs index 605e12814ba6..dab2ea463337 100644 --- a/src/rust/src/x509.rs +++ b/src/rust/src/x509.rs @@ -389,16 +389,40 @@ fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, PyAsn } } +/// parse all sections in a PEM file and return the only matching section. +/// If no or multiple matching sections are found, return an error. +fn find_in_pem( + data: &[u8], + filter_fn: fn(&pem::Pem) -> bool, + no_match_err: &'static str, + multiple_match_err: &'static str, +) -> Result { + let all_sections = pem::parse_many(data)?; + if all_sections.is_empty() { + return Err(PyAsn1Error::from(pem::PemError::MalformedFraming)); + } + let matching_sections: Vec = all_sections.into_iter().filter(filter_fn).collect(); + if matching_sections.len() > 1 { + return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( + multiple_match_err, + ))); + } + matching_sections + .into_iter() + .next() + .ok_or_else(|| PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err(no_match_err))) +} + #[pyo3::prelude::pyfunction] fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { - let parsed = pem::parse(data)?; // We support both PEM header strings that OpenSSL does // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L32-L33 - if parsed.tag != "CERTIFICATE" && parsed.tag != "X509 CERTIFICATE" { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Valid PEM but no BEGIN CERTIFICATE/END CERTIFICATE delimiters. Are you sure this is a certificate?" - ))); - } + let parsed = find_in_pem( + data, + |p| p.tag == "CERTIFICATE" || p.tag == "X509 CERTIFICATE", + "Valid PEM but no BEGIN CERTIFICATE/END CERTIFICATE delimiters. Are you sure this is a certificate?", + "Valid PEM but multiple BEGIN CERTIFICATE/END CERTIFICATE delimiters." + )?; load_der_x509_certificate(py, &parsed.contents) } @@ -672,16 +696,14 @@ impl CertificateSigningRequest { #[pyo3::prelude::pyfunction] fn load_pem_x509_csr(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { - let parsed = pem::parse(data)?; // We support both PEM header strings that OpenSSL does // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L35-L36 - if parsed.tag != "CERTIFICATE REQUEST" && parsed.tag != "NEW CERTIFICATE REQUEST" { - // TODO: The old errors had the following URL: - // See https://cryptography.io/en/latest/faq.html#why-can-t-i-import-my-pem-file for more details. - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Valid PEM but no BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters. Are you sure this is a CSR?" - ))); - } + let parsed = find_in_pem( + data, + |p| p.tag == "CERTIFICATE REQUEST" || p.tag == "NEW CERTIFICATE REQUEST", + "Valid PEM but no BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters. Are you sure this is a CSR?", + "Valid PEM but multiple BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters.", + )?; load_der_x509_csr(py, &parsed.contents) } @@ -719,12 +741,12 @@ fn load_pem_x509_crl( py: pyo3::Python<'_>, data: &[u8], ) -> Result { - let block = pem::parse(data)?; - if block.tag != "X509 CRL" { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - "Valid PEM but no BEGIN X509 CRL/END X509 delimiters. Are you sure this is a CRL?", - ))); - } + let block = 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?", + "Valid PEM but multiple BEGIN X509 CRL/END X509 delimiters.", + )?; // TODO: Produces an extra copy load_der_x509_crl(py, &block.contents) } diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index d872316e41e2..09329a4e6e12 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -694,6 +694,22 @@ def test_load_legacy_pem_header(self, backend): ) assert isinstance(cert, x509.Certificate) + def test_load_with_other_sections(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.with_garbage.pem"), + x509.load_pem_x509_certificate, + backend, + ) + assert isinstance(cert, x509.Certificate) + + def test_load_multiple_sections(self, backend): + with pytest.raises(ValueError, match="Valid PEM but multiple"): + _load_cert( + os.path.join("x509", "cryptography.io.repeated_twice.pem"), + x509.load_pem_x509_certificate, + backend, + ) + def test_negative_serial_number(self, backend): with pytest.raises(ValueError, match="TbsCertificate::serial"): _load_cert( diff --git a/vectors/cryptography_vectors/x509/cryptography.io.repeated_twice.pem b/vectors/cryptography_vectors/x509/cryptography.io.repeated_twice.pem new file mode 100644 index 000000000000..3ceb4f9b0714 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.repeated_twice.pem @@ -0,0 +1,66 @@ +-----BEGIN CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/cryptography.io.with_garbage.pem b/vectors/cryptography_vectors/x509/cryptography.io.with_garbage.pem new file mode 100644 index 000000000000..b85c5d1a5466 --- /dev/null +++ b/vectors/cryptography_vectors/x509/cryptography.io.with_garbage.pem @@ -0,0 +1,49 @@ +This file also contains text before... + +-----BEGIN PRIVATE KEY----- +aHR0cHM6Ly9iaXQubHkvM3VKOXpZZw== +-----END PRIVATE KEY----- + +...and... + +-----BEGIN CERTIFICATE----- +MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 +NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR +BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t +L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh +bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 +LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s +itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR +PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ +CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu +6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y +3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ +r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW +ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx +diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi +gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu +YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 +FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc +8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT +aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi +LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw +dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv +bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw +LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G +CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc +dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt +Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF +7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH +aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i +GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP +-----END CERTIFICATE----- + +...between... + +-----BEGIN PRIVATE KEY----- +aHR0cHM6Ly9iaXQubHkvM3VKOXpZZw== +-----END PRIVATE KEY----- + +...sections. From 65e2055cbb0a93740d14480c85e36016d7113840 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 3 Oct 2021 16:42:52 -0400 Subject: [PATCH 015/206] fixes #6363 - restores cert repr to its pre-oxidized form (#6364) --- src/rust/src/x509.rs | 18 ++++++------------ tests/x509/test_x509.py | 6 +++--- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/rust/src/x509.rs b/src/rust/src/x509.rs index dab2ea463337..b1b82391255b 100644 --- a/src/rust/src/x509.rs +++ b/src/rust/src/x509.rs @@ -153,18 +153,12 @@ impl pyo3::PyObjectProtocol for Certificate { } fn __repr__(&self) -> pyo3::PyResult { - let mut repr = String::from(", ext: &pyo3::PyAny) -> pyo3::PyResult { - // 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("value")?.extract::()?); - } - - let result = asn1::write_single(&asn1::SequenceOfWriter::new(&els)); - Ok(pyo3::types::PyBytes::new(py, &result).to_object(py)) -} - -#[pyo3::prelude::pyfunction] -fn encode_precert_poison(py: pyo3::Python<'_>, _ext: &pyo3::PyAny) -> pyo3::PyObject { - let result = asn1::write_single(&()); - pyo3::types::PyBytes::new(py, &result).to_object(py) -} - #[derive(asn1::Asn1Read)] struct AlgorithmIdentifier<'a> { _oid: asn1::ObjectIdentifier<'a>, @@ -219,8 +198,6 @@ fn test_parse_certificate(data: &[u8]) -> Result { 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!(encode_tls_feature))?; - submod.add_wrapped(pyo3::wrap_pyfunction!(encode_precert_poison))?; submod.add_wrapped(pyo3::wrap_pyfunction!(parse_spki_for_data))?; submod.add_wrapped(pyo3::wrap_pyfunction!(decode_dss_signature))?; diff --git a/src/rust/src/ocsp.rs b/src/rust/src/ocsp.rs index afb2bb8151dc..c7b5f59fbb18 100644 --- a/src/rust/src/ocsp.rs +++ b/src/rust/src/ocsp.rs @@ -689,11 +689,30 @@ struct RevokedInfo { revocation_reason: Option, } +#[pyo3::prelude::pyfunction] +fn encode_ocsp_request_extension<'p>(ext: &pyo3::PyAny) -> pyo3::PyResult<&'p pyo3::PyAny> { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + ext.getattr("oid")?.repr()?.extract::<&str>()? + ))) +} + +#[pyo3::prelude::pyfunction] +fn encode_ocsp_basic_response_extension<'p>(ext: &pyo3::PyAny) -> pyo3::PyResult<&'p pyo3::PyAny> { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + ext.getattr("oid")?.repr()?.extract::<&str>()? + ))) +} + pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let submod = pyo3::prelude::PyModule::new(py, "ocsp")?; submod.add_wrapped(pyo3::wrap_pyfunction!(load_der_ocsp_request))?; submod.add_wrapped(pyo3::wrap_pyfunction!(load_der_ocsp_response))?; + submod.add_wrapped(pyo3::wrap_pyfunction!(encode_ocsp_request_extension))?; + submod.add_wrapped(pyo3::wrap_pyfunction!(encode_ocsp_basic_response_extension))?; + Ok(submod) } diff --git a/src/rust/src/x509.rs b/src/rust/src/x509.rs index b1b82391255b..6a3689f9f427 100644 --- a/src/rust/src/x509.rs +++ b/src/rust/src/x509.rs @@ -1985,27 +1985,6 @@ impl pyo3::PyObjectProtocol for Sct { } } -#[pyo3::prelude::pyfunction] -fn encode_precertificate_signed_certificate_timestamps( - py: pyo3::Python<'_>, - extension: &pyo3::PyAny, -) -> pyo3::PyResult { - let mut length = 0; - for sct in extension.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 extension.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(pyo3::types::PyBytes::new(py, &asn1::write_single(&result.as_slice())).to_object(py)) -} - pub(crate) fn parse_scts( py: pyo3::Python<'_>, data: &[u8], @@ -2236,6 +2215,74 @@ pub fn parse_cert_ext<'p>( } } +#[pyo3::prelude::pyfunction] +fn encode_certificate_extension<'p>( + py: pyo3::Python<'p>, + ext: &pyo3::PyAny, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + let oid = asn1::ObjectIdentifier::from_string( + ext.getattr("oid")? + .getattr("dotted_string")? + .extract::<&str>()?, + ) + .unwrap(); + if 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.getattr("value")?.iter()? { + els.push(el?.getattr("value")?.extract::()?); + } + + let result = asn1::write_single(&asn1::SequenceOfWriter::new(&els)); + Ok(pyo3::types::PyBytes::new(py, &result)) + } else if oid == *PRECERT_POISON_OID { + let result = asn1::write_single(&()); + Ok(pyo3::types::PyBytes::new(py, &result)) + } else if oid == *PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID { + let mut length = 0; + for sct in ext.getattr("value")?.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.getattr("value")?.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(pyo3::types::PyBytes::new( + py, + &asn1::write_single(&result.as_slice()), + )) + } else { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + ext.getattr("oid")?.repr()?.extract::<&str>()? + ))) + } +} + +#[pyo3::prelude::pyfunction] +fn encode_crl_extension<'p>(ext: &pyo3::PyAny) -> pyo3::PyResult<&'p pyo3::PyAny> { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + ext.getattr("oid")?.repr()?.extract::<&str>()? + ))) +} + +#[pyo3::prelude::pyfunction] +fn encode_crl_entry_extension<'p>(ext: &pyo3::PyAny) -> pyo3::PyResult<&'p pyo3::PyAny> { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + ext.getattr("oid")?.repr()?.extract::<&str>()? + ))) +} + pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let submod = pyo3::prelude::PyModule::new(py, "x509")?; @@ -2246,9 +2293,9 @@ pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::pr submod.add_wrapped(pyo3::wrap_pyfunction!(load_der_x509_csr))?; submod.add_wrapped(pyo3::wrap_pyfunction!(load_pem_x509_csr))?; - submod.add_wrapped(pyo3::wrap_pyfunction!( - encode_precertificate_signed_certificate_timestamps - ))?; + submod.add_wrapped(pyo3::wrap_pyfunction!(encode_certificate_extension))?; + submod.add_wrapped(pyo3::wrap_pyfunction!(encode_crl_extension))?; + submod.add_wrapped(pyo3::wrap_pyfunction!(encode_crl_entry_extension))?; submod.add_class::()?; submod.add_class::()?; diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py index 2a6852cd0ec6..a9029001f0f5 100644 --- a/tests/x509/test_ocsp.py +++ b/tests/x509/test_ocsp.py @@ -16,7 +16,7 @@ from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.x509 import ocsp -from .test_x509 import _load_cert +from .test_x509 import DummyExtension, _load_cert from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 from ..utils import load_vectors_from_file @@ -195,6 +195,16 @@ def test_add_invalid_extension(self): False, ) + def test_unsupported_extension(self): + cert, issuer = _cert_and_issuer() + builder = ( + ocsp.OCSPRequestBuilder() + .add_extension(DummyExtension(), critical=False) + .add_certificate(cert, issuer, hashes.SHA256()) + ) + with pytest.raises(NotImplementedError): + builder.build() + def test_create_ocsp_request_invalid_cert(self): cert, issuer = _cert_and_issuer() builder = ocsp.OCSPRequestBuilder() @@ -434,6 +444,31 @@ def test_invalid_extension(self): "notanextension", True # type: ignore[arg-type] ) + def test_unsupported_extension(self): + root_cert, private_key = _generate_root() + cert, issuer = _cert_and_issuer() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + + builder = ( + ocsp.OCSPResponseBuilder() + .responder_id(ocsp.OCSPResponderEncoding.NAME, root_cert) + .add_response( + cert, + issuer, + hashes.SHA256(), + ocsp.OCSPCertStatus.GOOD, + this_update, + next_update, + None, + None, + ) + .add_extension(DummyExtension(), critical=False) + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256()) + def test_sign_no_response(self): builder = ocsp.OCSPResponseBuilder() root_cert, private_key = _generate_root() diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py index cc09db78f1f9..a209ffad5e32 100644 --- a/tests/x509/test_x509_crlbuilder.py +++ b/tests/x509/test_x509_crlbuilder.py @@ -18,6 +18,7 @@ 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 @@ -370,6 +371,34 @@ 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) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = ( + x509.CertificateRevocationListBuilder() + .issuer_name( + x509.Name( + [ + x509.NameAttribute( + NameOID.COMMON_NAME, "cryptography.io CA" + ) + ] + ) + ) + .last_update(last_update) + .next_update(next_update) + .add_revoked_certificate( + x509.RevokedCertificateBuilder() + .serial_number(1234) + .revocation_date(datetime.datetime.utcnow()) + .add_extension(DummyExtension(), critical=False) + .build() + ) + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256(), backend) + def test_sign_rsa_key_too_small(self, backend): private_key = RSA_KEY_512.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) From d28553d5b3fb055c623a304639257d52183cc4f1 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 4 Oct 2021 19:31:45 -0400 Subject: [PATCH 019/206] Migrate nonce and basic constraint extensions to Rust (#6375) * Migrate nonce and basic constraint extensions to Rust * clippy --- src/_cffi_src/openssl/x509v3.py | 9 ----- .../hazmat/backends/openssl/backend.py | 12 +----- .../hazmat/backends/openssl/encode_asn1.py | 33 +-------------- src/rust/src/ocsp.rs | 40 ++++++++++++++----- src/rust/src/x509.rs | 16 ++++++-- 5 files changed, 46 insertions(+), 64 deletions(-) diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py index 4f659765869a..945e6d3648cf 100644 --- a/src/_cffi_src/openssl/x509v3.py +++ b/src/_cffi_src/openssl/x509v3.py @@ -57,11 +57,6 @@ ...; } EDIPARTYNAME; -typedef struct { - int ca; - ASN1_INTEGER *pathlen; -} BASIC_CONSTRAINTS; - typedef struct { Cryptography_STACK_OF_GENERAL_SUBTREE *permittedSubtrees; Cryptography_STACK_OF_GENERAL_SUBTREE *excludedSubtrees; @@ -184,10 +179,6 @@ 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 *); -/* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the - x509v3.h header. */ -BASIC_CONSTRAINTS *BASIC_CONSTRAINTS_new(void); -void BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *); /* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the x509v3.h header. */ AUTHORITY_KEYID *AUTHORITY_KEYID_new(void); diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 10a3c030653a..6d03aa38078d 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -47,8 +47,6 @@ _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, _CRL_EXTENSION_ENCODE_HANDLERS, _EXTENSION_ENCODE_HANDLERS, - _OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS, - _OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS, _encode_asn1_int_gc, _encode_asn1_str_gc, _encode_name_gc, @@ -423,12 +421,6 @@ def _register_x509_encoders(self): self._crl_entry_extension_encode_handlers = ( _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS.copy() ) - self._ocsp_request_extension_encode_handlers = ( - _OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS.copy() - ) - self._ocsp_basicresp_extension_encode_handlers = ( - _OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS.copy() - ) def create_symmetric_encryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) @@ -1664,7 +1656,7 @@ def create_ocsp_request(self, builder): self.openssl_assert(onereq != self._ffi.NULL) self._create_x509_extensions( extensions=builder._extensions, - handlers=self._ocsp_request_extension_encode_handlers, + handlers={}, rust_handler=rust_ocsp.encode_ocsp_request_extension, x509_obj=ocsp_req, add_func=self._lib.OCSP_REQUEST_add_ext, @@ -1743,7 +1735,7 @@ def _create_ocsp_basic_response(self, builder, private_key, algorithm): self._create_x509_extensions( extensions=builder._extensions, - handlers=self._ocsp_basicresp_extension_encode_handlers, + handlers={}, rust_handler=rust_ocsp.encode_ocsp_basic_response_extension, x509_obj=basic, add_func=self._lib.OCSP_BASICRESP_add_ext, diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 97080abea1ee..683e079be4e6 100644 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -13,11 +13,7 @@ _DISTPOINT_TYPE_RELATIVENAME, ) from cryptography.x509.name import _ASN1Type -from cryptography.x509.oid import ( - CRLEntryExtensionOID, - ExtensionOID, - OCSPExtensionOID, -) +from cryptography.x509.oid import CRLEntryExtensionOID, ExtensionOID def _encode_asn1_int(backend, x): @@ -329,20 +325,6 @@ def _encode_authority_key_identifier(backend, authority_keyid): return akid -def _encode_basic_constraints(backend, basic_constraints): - constraints = backend._lib.BASIC_CONSTRAINTS_new() - constraints = backend._ffi.gc( - constraints, backend._lib.BASIC_CONSTRAINTS_free - ) - constraints.ca = 255 if basic_constraints.ca else 0 - if basic_constraints.ca and basic_constraints.path_length is not None: - constraints.pathlen = _encode_asn1_int( - backend, basic_constraints.path_length - ) - - return constraints - - def _encode_information_access(backend, info_access): aia = backend._lib.sk_ACCESS_DESCRIPTION_new_null() backend.openssl_assert(aia != backend._ffi.NULL) @@ -606,12 +588,7 @@ def _encode_general_subtree(backend, subtrees): return general_subtrees -def _encode_nonce(backend, nonce): - return _encode_asn1_str_gc(backend, nonce.nonce) - - _EXTENSION_ENCODE_HANDLERS = { - ExtensionOID.BASIC_CONSTRAINTS: _encode_basic_constraints, ExtensionOID.SUBJECT_KEY_IDENTIFIER: _encode_subject_key_identifier, ExtensionOID.KEY_USAGE: _encode_key_usage, ExtensionOID.SUBJECT_ALTERNATIVE_NAME: _encode_alt_name, @@ -644,11 +621,3 @@ def _encode_nonce(backend, nonce): CRLEntryExtensionOID.CRL_REASON: _encode_crl_reason, CRLEntryExtensionOID.INVALIDITY_DATE: _encode_invalidity_date, } - -_OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS = { - OCSPExtensionOID.NONCE: _encode_nonce, -} - -_OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS = { - OCSPExtensionOID.NONCE: _encode_nonce, -} diff --git a/src/rust/src/ocsp.rs b/src/rust/src/ocsp.rs index c7b5f59fbb18..f1d190276761 100644 --- a/src/rust/src/ocsp.rs +++ b/src/rust/src/ocsp.rs @@ -690,19 +690,39 @@ struct RevokedInfo { } #[pyo3::prelude::pyfunction] -fn encode_ocsp_request_extension<'p>(ext: &pyo3::PyAny) -> pyo3::PyResult<&'p pyo3::PyAny> { - Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( - "Extension not supported: {}", - ext.getattr("oid")?.repr()?.extract::<&str>()? - ))) +fn encode_ocsp_request_extension(ext: &pyo3::PyAny) -> pyo3::PyResult<&pyo3::PyAny> { + let oid = asn1::ObjectIdentifier::from_string( + ext.getattr("oid")? + .getattr("dotted_string")? + .extract::<&str>()?, + ) + .unwrap(); + if oid == *NONCE_OID { + Ok(ext.getattr("value")?.getattr("nonce")?) + } else { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + oid + ))) + } } #[pyo3::prelude::pyfunction] -fn encode_ocsp_basic_response_extension<'p>(ext: &pyo3::PyAny) -> pyo3::PyResult<&'p pyo3::PyAny> { - Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( - "Extension not supported: {}", - ext.getattr("oid")?.repr()?.extract::<&str>()? - ))) +fn encode_ocsp_basic_response_extension(ext: &pyo3::PyAny) -> pyo3::PyResult<&pyo3::PyAny> { + let oid = asn1::ObjectIdentifier::from_string( + ext.getattr("oid")? + .getattr("dotted_string")? + .extract::<&str>()?, + ) + .unwrap(); + if oid == *NONCE_OID { + Ok(ext.getattr("value")?.getattr("nonce")?) + } else { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + oid + ))) + } } pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { diff --git a/src/rust/src/x509.rs b/src/rust/src/x509.rs index 6a3689f9f427..bd4a94de6c0b 100644 --- a/src/rust/src/x509.rs +++ b/src/rust/src/x509.rs @@ -1636,7 +1636,7 @@ enum GeneralName<'a> { RegisteredID(asn1::ObjectIdentifier<'a>), } -#[derive(asn1::Asn1Read)] +#[derive(asn1::Asn1Read, asn1::Asn1Write)] struct BasicConstraints { #[default(false)] ca: bool, @@ -2226,7 +2226,17 @@ fn encode_certificate_extension<'p>( .extract::<&str>()?, ) .unwrap(); - if oid == *TLS_FEATURE_OID { + if oid == *BASIC_CONSTRAINTS_OID { + let bc = BasicConstraints { + ca: ext.getattr("value")?.getattr("ca")?.extract::()?, + path_length: ext + .getattr("value")? + .getattr("path_length")? + .extract::>()?, + }; + let result = asn1::write_single(&bc); + Ok(pyo3::types::PyBytes::new(py, &result)) + } else if 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 @@ -2262,7 +2272,7 @@ fn encode_certificate_extension<'p>( } else { Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( "Extension not supported: {}", - ext.getattr("oid")?.repr()?.extract::<&str>()? + oid ))) } } From 79fe5e0a0647589894511d7497fb306f1c79ba70 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 4 Oct 2021 21:59:53 -0400 Subject: [PATCH 020/206] Convert a few more simple extensions to Rust encoding (#6376) --- src/_cffi_src/openssl/asn1.py | 3 -- .../hazmat/backends/openssl/encode_asn1.py | 22 --------- src/rust/src/x509.rs | 47 +++++++++++++++++-- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py index 0841a115d82a..a315c2f959ed 100644 --- a/src/_cffi_src/openssl/asn1.py +++ b/src/_cffi_src/openssl/asn1.py @@ -32,7 +32,6 @@ } ASN1_TYPE; typedef ... ASN1_GENERALIZEDTIME; typedef ... ASN1_ENUMERATED; -typedef ... ASN1_NULL; static const int V_ASN1_GENERALIZEDTIME; @@ -100,8 +99,6 @@ int i2d_ASN1_TYPE(ASN1_TYPE *, unsigned char **); ASN1_TYPE *d2i_ASN1_TYPE(ASN1_TYPE **, const unsigned char **, long); - -ASN1_NULL *ASN1_NULL_new(void); """ CUSTOMIZATIONS = """ diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 683e079be4e6..32fd5997b066 100644 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -72,10 +72,6 @@ def _encode_asn1_str_gc(backend, data): return s -def _encode_inhibit_any_policy(backend, inhibit_any_policy): - return _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs) - - def _encode_name(backend, name): """ The X509_NAME created will not be gc'd. Use _encode_name_gc if needed. @@ -131,10 +127,6 @@ def _encode_name_entry(backend, attribute): return name_entry -def _encode_crl_number_delta_crl_indicator(backend, ext): - return _encode_asn1_int_gc(backend, ext.crl_number) - - def _encode_issuing_dist_point(backend, ext): idp = backend._lib.ISSUING_DIST_POINT_new() backend.openssl_assert(idp != backend._ffi.NULL) @@ -265,11 +257,6 @@ def _txt2obj_gc(backend, name): return obj -def _encode_ocsp_nocheck(backend, ext): - # Doesn't need to be GC'd - return backend._lib.ASN1_NULL_new() - - def _encode_key_usage(backend, key_usage): set_bit = backend._lib.ASN1_BIT_STRING_set_bit ku = backend._lib.ASN1_BIT_STRING_new() @@ -371,10 +358,6 @@ def _encode_alt_name(backend, san): return general_names -def _encode_subject_key_identifier(backend, ski): - return _encode_asn1_str_gc(backend, ski.digest) - - def _encode_general_name(backend, name): gn = backend._lib.GENERAL_NAME_new() _encode_general_name_preallocated(backend, name, gn) @@ -589,7 +572,6 @@ def _encode_general_subtree(backend, subtrees): _EXTENSION_ENCODE_HANDLERS = { - ExtensionOID.SUBJECT_KEY_IDENTIFIER: _encode_subject_key_identifier, ExtensionOID.KEY_USAGE: _encode_key_usage, ExtensionOID.SUBJECT_ALTERNATIVE_NAME: _encode_alt_name, ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, @@ -600,8 +582,6 @@ def _encode_general_subtree(backend, subtrees): ExtensionOID.SUBJECT_INFORMATION_ACCESS: _encode_information_access, ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_cdps_freshest_crl, ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, - ExtensionOID.INHIBIT_ANY_POLICY: _encode_inhibit_any_policy, - ExtensionOID.OCSP_NO_CHECK: _encode_ocsp_nocheck, ExtensionOID.NAME_CONSTRAINTS: _encode_name_constraints, ExtensionOID.POLICY_CONSTRAINTS: _encode_policy_constraints, } @@ -610,8 +590,6 @@ def _encode_general_subtree(backend, subtrees): ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access, - ExtensionOID.CRL_NUMBER: _encode_crl_number_delta_crl_indicator, - ExtensionOID.DELTA_CRL_INDICATOR: _encode_crl_number_delta_crl_indicator, ExtensionOID.ISSUING_DISTRIBUTION_POINT: _encode_issuing_dist_point, ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, } diff --git a/src/rust/src/x509.rs b/src/rust/src/x509.rs index bd4a94de6c0b..60f4cd47fcb9 100644 --- a/src/rust/src/x509.rs +++ b/src/rust/src/x509.rs @@ -2236,6 +2236,24 @@ fn encode_certificate_extension<'p>( }; let result = asn1::write_single(&bc); Ok(pyo3::types::PyBytes::new(py, &result)) + } else if oid == *SUBJECT_KEY_IDENTIFIER_OID { + let result = asn1::write_single( + &ext.getattr("value")? + .getattr("digest")? + .extract::<&[u8]>()?, + ); + Ok(pyo3::types::PyBytes::new(py, &result)) + } else if oid == *INHIBIT_ANY_POLICY_OID { + let intval = ext + .getattr("value")? + .getattr("skip_certs")? + .downcast::()?; + let bytes = py_uint_to_big_endian_bytes(py, intval)?; + let result = asn1::write_single(&asn1::BigUint::new(bytes).unwrap()); + Ok(pyo3::types::PyBytes::new(py, &result)) + } else if oid == *OCSP_NO_CHECK_OID { + let result = asn1::write_single(&()); + Ok(pyo3::types::PyBytes::new(py, &result)) } else if 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 @@ -2278,11 +2296,30 @@ fn encode_certificate_extension<'p>( } #[pyo3::prelude::pyfunction] -fn encode_crl_extension<'p>(ext: &pyo3::PyAny) -> pyo3::PyResult<&'p pyo3::PyAny> { - Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( - "Extension not supported: {}", - ext.getattr("oid")?.repr()?.extract::<&str>()? - ))) +fn encode_crl_extension<'p>( + py: pyo3::Python<'p>, + ext: &pyo3::PyAny, +) -> pyo3::PyResult<&'p pyo3::PyAny> { + let oid = asn1::ObjectIdentifier::from_string( + ext.getattr("oid")? + .getattr("dotted_string")? + .extract::<&str>()?, + ) + .unwrap(); + if oid == *CRL_NUMBER_OID || oid == *DELTA_CRL_INDICATOR_OID { + let intval = ext + .getattr("value")? + .getattr("crl_number")? + .downcast::()?; + let bytes = py_uint_to_big_endian_bytes(py, intval)?; + let result = asn1::write_single(&asn1::BigUint::new(bytes).unwrap()); + Ok(pyo3::types::PyBytes::new(py, &result)) + } else { + Err(pyo3::exceptions::PyNotImplementedError::new_err(format!( + "Extension not supported: {}", + oid + ))) + } } #[pyo3::prelude::pyfunction] From 6a6ed3ee77e22b5ddfa5b3140708be2111419992 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 5 Oct 2021 14:38:42 +0200 Subject: [PATCH 021/206] add downstream tests for mitmproxy (#6377) --- .github/downstream.d/mitmproxy.sh | 17 +++++++++++++++++ .github/workflows/ci.yml | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100755 .github/downstream.d/mitmproxy.sh diff --git a/.github/downstream.d/mitmproxy.sh b/.github/downstream.d/mitmproxy.sh new file mode 100755 index 000000000000..df7669aa9336 --- /dev/null +++ b/.github/downstream.d/mitmproxy.sh @@ -0,0 +1,17 @@ +#!/bin/bash -ex + +case "${1}" in + install) + git clone --depth=1 https://github.com/mitmproxy/mitmproxy + cd mitmproxy + git rev-parse HEAD + pip install -e ".[dev]" + ;; + run) + cd mitmproxy + pytest test + ;; + *) + exit 1 + ;; +esac diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74ef112af128..12bbe5138d0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -433,10 +433,11 @@ jobs: - dynamodb-encryption-sdk - certbot - certbot-josepy + - mitmproxy RUST: - stable PYTHON: - - 3.7 + - 3.8 name: "Downstream tests for ${{ matrix.DOWNSTREAM }}" timeout-minutes: 20 steps: From 7dd2252a89a3b8a0ff067af6479b713ad5d9e4da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 08:42:21 -0400 Subject: [PATCH 022/206] Bump quote from 1.0.9 to 1.0.10 in /src/rust (#6379) Bumps [quote](https://github.com/dtolnay/quote) from 1.0.9 to 1.0.10. - [Release notes](https://github.com/dtolnay/quote/releases) - [Commits](https://github.com/dtolnay/quote/compare/1.0.9...1.0.10) --- updated-dependencies: - dependency-name: quote dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/rust/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 2a342f76cdbf..eb6e0ce6e226 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -327,9 +327,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] From 4e21adc88046383bbdfb4b26ce57e9d249db62d7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 5 Oct 2021 16:12:48 -0400 Subject: [PATCH 023/206] remove non-real type declaration in x509.pyi (#6381) --- src/cryptography/hazmat/bindings/_rust/x509.pyi | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi index 9a579e7574bc..2a6897b1dd5f 100644 --- a/src/cryptography/hazmat/bindings/_rust/x509.pyi +++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi @@ -3,9 +3,6 @@ import typing from cryptography import x509 -def parse_csr_extension( - der_oid: bytes, ext_data: bytes -) -> x509.ExtensionType: ... def load_pem_x509_certificate(data: bytes) -> x509.Certificate: ... def load_der_x509_certificate(data: bytes) -> x509.Certificate: ... def load_pem_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... From b6734858e534f10435adaebd33d79f12c9dd7389 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 5 Oct 2021 20:25:04 -0400 Subject: [PATCH 024/206] Split Rust x.509 support into multiple files for readability (#6382) * Split Rust x.509 support into multiple files for readability * poke GHA * License headers --- src/rust/src/lib.rs | 15 +- src/rust/src/x509.rs | 2354 ------------------- src/rust/src/x509/certificate.rs | 922 ++++++++ src/rust/src/x509/common.rs | 356 +++ src/rust/src/x509/crl.rs | 669 ++++++ src/rust/src/x509/csr.rs | 306 +++ src/rust/src/x509/mod.rs | 19 + src/rust/src/x509/ocsp.rs | 34 + src/rust/src/x509/ocsp_req.rs | 189 ++ src/rust/src/{ocsp.rs => x509/ocsp_resp.rs} | 245 +- src/rust/src/x509/sct.rs | 160 ++ 11 files changed, 2687 insertions(+), 2582 deletions(-) delete mode 100644 src/rust/src/x509.rs create mode 100644 src/rust/src/x509/certificate.rs create mode 100644 src/rust/src/x509/common.rs create mode 100644 src/rust/src/x509/crl.rs create mode 100644 src/rust/src/x509/csr.rs create mode 100644 src/rust/src/x509/mod.rs create mode 100644 src/rust/src/x509/ocsp.rs create mode 100644 src/rust/src/x509/ocsp_req.rs rename src/rust/src/{ocsp.rs => x509/ocsp_resp.rs} (68%) create mode 100644 src/rust/src/x509/sct.rs diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 9e7315c40ff5..1affeba662d2 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -5,7 +5,6 @@ #![deny(rust_2018_idioms)] mod asn1; -mod ocsp; mod x509; use std::convert::TryInto; @@ -77,8 +76,18 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> m.add_function(pyo3::wrap_pyfunction!(check_ansix923_padding, m)?)?; m.add_submodule(asn1::create_submodule(py)?)?; - m.add_submodule(ocsp::create_submodule(py)?)?; - m.add_submodule(x509::create_submodule(py)?)?; + + let x509_mod = pyo3::prelude::PyModule::new(py, "x509")?; + crate::x509::certificate::add_to_module(x509_mod)?; + crate::x509::crl::add_to_module(x509_mod)?; + crate::x509::csr::add_to_module(x509_mod)?; + crate::x509::sct::add_to_module(x509_mod)?; + m.add_submodule(x509_mod)?; + + let ocsp_mod = pyo3::prelude::PyModule::new(py, "ocsp")?; + crate::x509::ocsp_req::add_to_module(ocsp_mod)?; + crate::x509::ocsp_resp::add_to_module(ocsp_mod)?; + m.add_submodule(ocsp_mod)?; Ok(()) } diff --git a/src/rust/src/x509.rs b/src/rust/src/x509.rs deleted file mode 100644 index 60f4cd47fcb9..000000000000 --- a/src/rust/src/x509.rs +++ /dev/null @@ -1,2354 +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. - -use crate::asn1::{big_asn1_uint_to_py, py_uint_to_big_endian_bytes, PyAsn1Error, PyAsn1Result}; -use asn1::SimpleAsn1Readable; -use chrono::{Datelike, Timelike}; -use pyo3::types::IntoPyDict; -use pyo3::ToPyObject; -use std::collections::hash_map::DefaultHasher; -use std::collections::HashSet; -use std::convert::TryInto; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -lazy_static::lazy_static! { - static ref TLS_FEATURE_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.5.5.7.1.24").unwrap(); - static ref PRECERT_POISON_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.4.1.11129.2.4.3").unwrap(); - static ref OCSP_NO_CHECK_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.5.5.7.48.1.5").unwrap(); - static ref AUTHORITY_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.5.5.7.1.1").unwrap(); - static ref SUBJECT_INFORMATION_ACCESS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.5.5.7.1.11").unwrap(); - - static ref KEY_USAGE_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.15").unwrap(); - static ref POLICY_CONSTRAINTS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.36").unwrap(); - static ref AUTHORITY_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.35").unwrap(); - static ref EXTENDED_KEY_USAGE_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.37").unwrap(); - static ref BASIC_CONSTRAINTS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.19").unwrap(); - static ref SUBJECT_KEY_IDENTIFIER_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.14").unwrap(); - static ref INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.54").unwrap(); - static ref CRL_REASON_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.21").unwrap(); - static ref ISSUING_DISTRIBUTION_POINT_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.28").unwrap(); - static ref CERTIFICATE_ISSUER_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.29").unwrap(); - static ref NAME_CONSTRAINTS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.30").unwrap(); - static ref CRL_DISTRIBUTION_POINTS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.31").unwrap(); - static ref CERTIFICATE_POLICIES_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.32").unwrap(); - static ref FRESHEST_CRL_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.46").unwrap(); - static ref CRL_NUMBER_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.20").unwrap(); - static ref INVALIDITY_DATE_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.24").unwrap(); - static ref DELTA_CRL_INDICATOR_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.27").unwrap(); - static ref SUBJECT_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.17").unwrap(); - static ref ISSUER_ALTERNATIVE_NAME_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.18").unwrap(); - static ref PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.4.1.11129.2.4.2").unwrap(); - - static ref CP_CPS_URI_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.5.5.7.2.1").unwrap(); - static ref CP_USER_NOTICE_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.5.5.7.2.2").unwrap(); - - static ref MS_EXTENSION_REQUEST: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.6.1.4.1.311.2.1.14").unwrap(); - static ref EXTENSION_REQUEST: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.2.840.113549.1.9.14").unwrap(); -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq)] -pub(crate) struct RawCertificate<'a> { - tbs_cert: TbsCertificate<'a>, - signature_alg: AlgorithmIdentifier<'a>, - signature: asn1::BitString<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq)] -struct TbsCertificate<'a> { - #[explicit(0)] - #[default(0)] - version: u8, - serial: asn1::BigUint<'a>, - _signature_alg: asn1::Sequence<'a>, - - issuer: Name<'a>, - validity: Validity, - subject: Name<'a>, - - spki: asn1::Sequence<'a>, - #[implicit(1)] - _issuer_unique_id: Option>, - #[implicit(2)] - _subject_unique_id: Option>, - #[explicit(3)] - extensions: Option>, -} - -pub(crate) type Name<'a> = asn1::SequenceOf<'a, asn1::SetOf<'a, AttributeTypeValue<'a>>>; - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] -pub(crate) struct AttributeTypeValue<'a> { - pub(crate) type_id: asn1::ObjectIdentifier<'a>, - pub(crate) value: asn1::Tlv<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)] -enum Time { - UtcTime(asn1::UtcTime), - GeneralizedTime(asn1::GeneralizedTime), -} - -impl Time { - fn as_chrono(&self) -> &chrono::DateTime { - match self { - Time::UtcTime(data) => data.as_chrono(), - Time::GeneralizedTime(data) => data.as_chrono(), - } - } -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq)] -pub(crate) struct Validity { - not_before: Time, - not_after: Time, -} - -#[ouroboros::self_referencing] -pub(crate) struct OwnedRawCertificate { - data: Arc<[u8]>, - - #[borrows(data)] - #[covariant] - value: RawCertificate<'this>, -} - -impl OwnedRawCertificate { - // 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) - } -} - -#[pyo3::prelude::pyclass] -pub(crate) struct Certificate { - pub(crate) raw: OwnedRawCertificate, - pub(crate) cached_extensions: Option, -} - -#[pyo3::prelude::pyproto] -impl pyo3::PyObjectProtocol for Certificate { - fn __hash__(&self) -> u64 { - let mut hasher = DefaultHasher::new(); - self.raw.borrow_value().hash(&mut hasher); - hasher.finish() - } - - fn __richcmp__( - &self, - other: pyo3::PyRef, - 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()), - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "Certificates cannot be ordered", - )), - } - } - - fn __repr__(&self) -> pyo3::PyResult { - let gil = pyo3::Python::acquire_gil(); - let py = gil.python(); - - 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>) -> pyo3::PyResult<&'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), - ); - py.import("cryptography.hazmat.primitives.serialization")? - .getattr("load_der_public_key")? - .call1((serialized,)) - } - - fn fingerprint<'p>( - &self, - py: pyo3::Python<'p>, - algorithm: pyo3::PyObject, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let hasher = py - .import("cryptography.hazmat.primitives.hashes")? - .getattr("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,))?; - hasher.call_method0("finalize") - } - - fn public_bytes<'p>( - &self, - py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { - let encoding_class = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr("Encoding")?; - - let result = asn1::write_single(self.raw.borrow_value()); - if encoding == encoding_class.getattr("DER")? { - Ok(pyo3::types::PyBytes::new(py, &result)) - } else if encoding == encoding_class.getattr("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", - )) - } - } - - #[getter] - fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - Ok(big_asn1_uint_to_py( - py, - self.raw.borrow_value().tbs_cert.serial, - )?) - } - - #[getter] - fn version<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let version = &self.raw.borrow_value().tbs_cert.version; - cert_version(py, *version) - } - - #[getter] - fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - parse_name(py, &self.raw.borrow_value().tbs_cert.issuer) - } - - #[getter] - fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - parse_name(py, &self.raw.borrow_value().tbs_cert.subject) - } - - #[getter] - fn tbs_certificate_bytes<'p>( - &self, - py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::types::PyBytes, PyAsn1Error> { - let result = asn1::write_single(&self.raw.borrow_value().tbs_cert); - Ok(pyo3::types::PyBytes::new(py, &result)) - } - - #[getter] - fn signature<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::types::PyBytes, PyAsn1Error> { - Ok(pyo3::types::PyBytes::new( - py, - self.raw.borrow_value().signature.as_bytes(), - )) - } - - #[getter] - fn not_valid_before<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let chrono = &self - .raw - .borrow_value() - .tbs_cert - .validity - .not_before - .as_chrono(); - chrono_to_py(py, chrono) - } - - #[getter] - fn not_valid_after<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let chrono = &self - .raw - .borrow_value() - .tbs_cert - .validity - .not_after - .as_chrono(); - chrono_to_py(py, chrono) - } - - #[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("_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.to_string() - ),), - )?, - ))), - } - } - - #[getter] - fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - py.import("cryptography.x509")?.call_method1( - "ObjectIdentifier", - (self.raw.borrow_value().signature_alg.oid.to_string(),), - ) - } - - #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let x509_module = py.import("cryptography.x509")?; - parse_and_cache_extensions( - py, - &mut self.cached_extensions, - &self.raw.borrow_value().tbs_cert.extensions, - |oid, ext_data| { - if oid == &*PRECERT_POISON_OID { - asn1::parse_single::<()>(ext_data)?; - Ok(Some(x509_module.getattr("PrecertPoison")?.call0()?)) - } else if oid == &*PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID { - let contents = asn1::parse_single::<&[u8]>(ext_data)?; - let scts = parse_scts(py, contents, LogEntryType::PreCertificate)?; - Ok(Some( - x509_module - .getattr("PrecertificateSignedCertificateTimestamps")? - .call1((scts,))?, - )) - } else { - 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<'p>( - slf: pyo3::PyRef<'_, Self>, - py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let cryptography_warning = py.import("cryptography.utils")?.getattr("DeprecatedIn35")?; - let warnings = py.import("warnings")?; - warnings.call_method1( - "warn", - ( - "This version of cryptography contains a temporary pyOpenSSL fallback path. Upgrade pyOpenSSL now.", - cryptography_warning, - ), - )?; - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr("backend")?; - Ok(backend.call_method1("_cert2ossl", (slf,))?) - } -} - -fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, PyAsn1Error> { - let x509_module = py.import("cryptography.x509")?; - match version { - 0 => Ok(x509_module.getattr("Version")?.get_item("v1")?), - 2 => Ok(x509_module.getattr("Version")?.get_item("v3")?), - _ => Err(PyAsn1Error::from(pyo3::PyErr::from_instance( - x509_module - .getattr("InvalidVersion")? - .call1((format!("{} is not a valid X509 version", version), version))?, - ))), - } -} - -/// parse all sections in a PEM file and return the only matching section. -/// If no or multiple matching sections are found, return an error. -fn find_in_pem( - data: &[u8], - filter_fn: fn(&pem::Pem) -> bool, - no_match_err: &'static str, - multiple_match_err: &'static str, -) -> Result { - let all_sections = pem::parse_many(data)?; - if all_sections.is_empty() { - return Err(PyAsn1Error::from(pem::PemError::MalformedFraming)); - } - let matching_sections: Vec = all_sections.into_iter().filter(filter_fn).collect(); - if matching_sections.len() > 1 { - return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err( - multiple_match_err, - ))); - } - matching_sections - .into_iter() - .next() - .ok_or_else(|| PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err(no_match_err))) -} - -#[pyo3::prelude::pyfunction] -fn load_pem_x509_certificate(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { - // We support both PEM header strings that OpenSSL does - // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L32-L33 - let parsed = find_in_pem( - data, - |p| p.tag == "CERTIFICATE" || p.tag == "X509 CERTIFICATE", - "Valid PEM but no BEGIN CERTIFICATE/END CERTIFICATE delimiters. Are you sure this is a certificate?", - "Valid PEM but multiple BEGIN CERTIFICATE/END CERTIFICATE delimiters." - )?; - load_der_x509_certificate(py, &parsed.contents) -} - -#[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))?; - // Parse cert version immediately so we can raise error on parse if it is invalid. - cert_version(py, raw.borrow_value().tbs_cert.version)?; - Ok(Certificate { - raw, - cached_extensions: None, - }) -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct RawCsr<'a> { - csr_info: CertificationRequestInfo<'a>, - signature_alg: AlgorithmIdentifier<'a>, - signature: asn1::BitString<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct CertificationRequestInfo<'a> { - version: u8, - subject: Name<'a>, - spki: asn1::Sequence<'a>, - #[implicit(0, required)] - attributes: asn1::SetOf<'a, Attribute<'a>>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write)] -struct Attribute<'a> { - type_id: asn1::ObjectIdentifier<'a>, - values: asn1::SetOf<'a, asn1::Tlv<'a>>, -} - -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<'a>(&'a self) -> Result>, PyAsn1Error> { - for attribute in self.attributes.clone() { - if attribute.type_id == *EXTENSION_REQUEST || attribute.type_id == *MS_EXTENSION_REQUEST - { - check_attribute_length(attribute.values.clone())?; - let val = attribute.values.clone().next().unwrap(); - let exts = asn1::parse_single::>(val.full_data())?; - return Ok(Some(exts)); - } - } - Ok(None) - } -} - -#[ouroboros::self_referencing] -struct OwnedRawCsr { - data: Vec, - #[borrows(data)] - #[covariant] - value: RawCsr<'this>, -} - -#[pyo3::prelude::pyclass] -struct CertificateSigningRequest { - raw: OwnedRawCsr, - cached_extensions: Option, -} - -#[pyo3::prelude::pyproto] -impl pyo3::basic::PyObjectProtocol for CertificateSigningRequest { - fn __hash__(&self) -> u64 { - let mut hasher = DefaultHasher::new(); - self.raw.borrow_data().hash(&mut hasher); - hasher.finish() - } - - fn __richcmp__( - &self, - other: pyo3::PyRef, - 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()), - _ => Err(pyo3::exceptions::PyTypeError::new_err( - "CSRs cannot be ordered", - )), - } - } -} - -#[pyo3::prelude::pymethods] -impl CertificateSigningRequest { - fn public_key<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'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), - ); - py.import("cryptography.hazmat.primitives.serialization")? - .getattr("load_der_public_key")? - .call1((serialized,)) - } - - #[getter] - fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - parse_name(py, &self.raw.borrow_value().csr_info.subject) - } - - #[getter] - fn tbs_certrequest_bytes<'p>( - &self, - py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::types::PyBytes, PyAsn1Error> { - let result = asn1::write_single(&self.raw.borrow_value().csr_info); - Ok(pyo3::types::PyBytes::new(py, &result)) - } - - #[getter] - fn signature<'p>(&self, py: pyo3::Python<'p>) -> Result<&'p pyo3::types::PyBytes, PyAsn1Error> { - Ok(pyo3::types::PyBytes::new( - py, - self.raw.borrow_value().signature.as_bytes(), - )) - } - - #[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("_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.to_string() - ),), - )?, - ))), - } - } - - #[getter] - fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - py.import("cryptography.x509")?.call_method1( - "ObjectIdentifier", - (self.raw.borrow_value().signature_alg.oid.to_string(),), - ) - } - - fn public_bytes<'p>( - &self, - py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { - let encoding_class = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr("Encoding")?; - - let result = asn1::write_single(self.raw.borrow_value()); - if encoding == encoding_class.getattr("DER")? { - Ok(pyo3::types::PyBytes::new(py, &result)) - } else if encoding == encoding_class.getattr("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", - )) - } - } - - fn get_attribute_for_oid<'p>( - &self, - py: pyo3::Python<'p>, - oid: &pyo3::PyAny, - ) -> pyo3::PyResult<&'p pyo3::PyAny> { - let oid_str = oid.getattr("dotted_string")?.extract::<&str>()?; - let rust_oid = asn1::ObjectIdentifier::from_string(oid_str).unwrap(); - for attribute in self.raw.borrow_value().csr_info.attributes.clone() { - if rust_oid == attribute.type_id { - check_attribute_length(attribute.values.clone())?; - let val = attribute.values.clone().next().unwrap(); - // We allow utf8string, printablestring, and ia5string at this time - if val.tag() == asn1::Utf8String::TAG - || val.tag() == asn1::PrintableString::TAG - || val.tag() == asn1::IA5String::TAG - { - return Ok(pyo3::types::PyBytes::new(py, val.data())); - } else { - 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_str), oid), - )?, - )) - } - - #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let exts = self.raw.borrow_value().csr_info.get_extension_attribute()?; - parse_and_cache_extensions(py, &mut self.cached_extensions, &exts, |oid, ext_data| { - parse_cert_ext(py, oid.clone(), ext_data) - }) - } - - #[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("backend")?; - backend.call_method1("_csr_is_signature_valid", (slf,)) - } - - // 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>( - slf: pyo3::PyRef<'_, Self>, - py: pyo3::Python<'p>, - ) -> Result<&'p pyo3::PyAny, PyAsn1Error> { - let cryptography_warning = py.import("cryptography.utils")?.getattr("DeprecatedIn35")?; - let warnings = py.import("warnings")?; - warnings.call_method1( - "warn", - ( - "This version of cryptography contains a temporary pyOpenSSL fallback path. Upgrade pyOpenSSL now.", - cryptography_warning, - ), - )?; - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr("backend")?; - Ok(backend.call_method1("_csr2ossl", (slf,))?) - } -} - -#[pyo3::prelude::pyfunction] -fn load_pem_x509_csr(py: pyo3::Python<'_>, data: &[u8]) -> PyAsn1Result { - // We support both PEM header strings that OpenSSL does - // https://github.com/openssl/openssl/blob/5e2d22d53ed322a7124e26a4fbd116a8210eb77a/include/openssl/pem.h#L35-L36 - let parsed = find_in_pem( - data, - |p| p.tag == "CERTIFICATE REQUEST" || p.tag == "NEW CERTIFICATE REQUEST", - "Valid PEM but no BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters. Are you sure this is a CSR?", - "Valid PEM but multiple BEGIN CERTIFICATE REQUEST/END CERTIFICATE REQUEST delimiters.", - )?; - load_der_x509_csr(py, &parsed.contents) -} - -#[pyo3::prelude::pyfunction] -fn load_der_x509_csr( - _py: pyo3::Python<'_>, - data: &[u8], -) -> PyAsn1Result { - let raw = OwnedRawCsr::try_new(data.to_vec(), |data| asn1::parse_single(data))?; - Ok(CertificateSigningRequest { - raw, - cached_extensions: None, - }) -} - -#[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()), - )?; - - Ok(CertificateRevocationList { - raw: Arc::new(raw), - cached_extensions: None, - }) -} - -#[pyo3::prelude::pyfunction] -fn load_pem_x509_crl( - py: pyo3::Python<'_>, - data: &[u8], -) -> Result { - let block = 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?", - "Valid PEM but multiple BEGIN X509 CRL/END X509 delimiters.", - )?; - // TODO: Produces an extra copy - load_der_x509_crl(py, &block.contents) -} - -#[ouroboros::self_referencing] -struct OwnedRawCertificateRevocationList { - data: Arc<[u8]>, - #[borrows(data)] - #[covariant] - value: RawCertificateRevocationList<'this>, - #[borrows(data)] - #[not_covariant] - revoked_certs: pyo3::once_cell::GILOnceCell>>, -} - -#[pyo3::prelude::pyclass] -struct CertificateRevocationList { - raw: Arc, - - cached_extensions: Option, -} - -impl CertificateRevocationList { - fn public_bytes_der(&self) -> Vec { - asn1::write_single(self.raw.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, - cached_extensions: None, - }) - } - - fn len(&self) -> usize { - self.raw - .borrow_value() - .tbs_cert_list - .revoked_certificates - .as_ref() - .map_or(0, |v| v.len()) - } -} - -#[pyo3::prelude::pyproto] -impl pyo3::PyObjectProtocol for CertificateRevocationList { - fn __richcmp__( - &self, - other: pyo3::PyRef, - 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()), - _ => 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(); - - self.raw.with(|val| { - val.revoked_certs.get_or_init(py, || { - match &val.value.tbs_cert_list.revoked_certificates { - Some(c) => c.clone().collect(), - None => vec![], - } - }); - }); - - if idx.is_instance::()? { - 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)?)?; - result.append(revoked_cert)?; - } - Ok(result.to_object(py)) - } else { - let mut idx = idx.extract::()?; - if idx < 0 { - idx += self.len() as isize; - } - if idx >= (self.len() as isize) || idx < 0 { - return Err(pyo3::exceptions::PyIndexError::new_err(())); - } - Ok(pyo3::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 h = hashes_mod.getattr("Hash")?.call1((algorithm,))?; - h.call_method1("update", (self.public_bytes_der().as_slice(),))?; - h.call_method0("finalize") - } - - #[getter] - fn signature_algorithm_oid<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - let x509_module = py.import("cryptography.x509")?; - x509_module.call_method1( - "ObjectIdentifier", - (self.raw.borrow_value().signature_algorithm.oid.to_string(),), - ) - } - - #[getter] - fn signature_hash_algorithm<'p>( - &self, - 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")?; - match oid_module.getattr("_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 - ),), - )?)), - } - } - - #[getter] - fn signature(&self) -> &[u8] { - self.raw.borrow_value().signature_value.as_bytes() - } - - #[getter] - fn tbs_certlist_bytes<'p>(&self, py: pyo3::Python<'p>) -> &'p pyo3::types::PyBytes { - let b = asn1::write_single(&self.raw.borrow_value().tbs_cert_list); - pyo3::types::PyBytes::new(py, &b) - } - - fn public_bytes<'p>( - &self, - py: pyo3::Python<'p>, - encoding: &pyo3::PyAny, - ) -> pyo3::PyResult<&'p pyo3::types::PyBytes> { - let encoding_class = py - .import("cryptography.hazmat.primitives.serialization")? - .getattr("Encoding")?; - - let result = asn1::write_single(self.raw.borrow_value()); - if encoding == encoding_class.getattr("DER")? { - Ok(pyo3::types::PyBytes::new(py, &result)) - } else if encoding == encoding_class.getattr("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", - )) - } - } - - #[getter] - fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - parse_name(py, &self.raw.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) => chrono_to_py(py, t.as_chrono()), - None => Ok(py.None().into_ref(py)), - } - } - - #[getter] - fn last_update<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<&'p pyo3::PyAny> { - chrono_to_py( - py, - self.raw - .borrow_value() - .tbs_cert_list - .this_update - .as_chrono(), - ) - } - - #[getter] - fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult { - let x509_module = py.import("cryptography.x509")?; - parse_and_cache_extensions( - py, - &mut self.cached_extensions, - &self.raw.borrow_value().tbs_cert_list.crl_extensions, - |oid, ext_data| { - if oid == &*CRL_NUMBER_OID { - let bignum = asn1::parse_single::>(ext_data)?; - let pynum = big_asn1_uint_to_py(py, bignum)?; - Ok(Some(x509_module.getattr("CRLNumber")?.call1((pynum,))?)) - } else if oid == &*DELTA_CRL_INDICATOR_OID { - let bignum = asn1::parse_single::>(ext_data)?; - let pynum = big_asn1_uint_to_py(py, bignum)?; - Ok(Some( - x509_module.getattr("DeltaCRLIndicator")?.call1((pynum,))?, - )) - } else if oid == &*ISSUER_ALTERNATIVE_NAME_OID { - let gn_seq = - asn1::parse_single::>>(ext_data)?; - let ians = parse_general_names(py, gn_seq)?; - Ok(Some( - x509_module - .getattr("IssuerAlternativeName")? - .call1((ians,))?, - )) - } else if oid == &*AUTHORITY_INFORMATION_ACCESS_OID { - let ads = parse_access_descriptions(py, ext_data)?; - Ok(Some( - x509_module - .getattr("AuthorityInformationAccess")? - .call1((ads,))?, - )) - } else if oid == &*AUTHORITY_KEY_IDENTIFIER_OID { - Ok(Some(parse_authority_key_identifier(py, ext_data)?)) - } else if oid == &*ISSUING_DISTRIBUTION_POINT_OID { - let idp = asn1::parse_single::>(ext_data)?; - let (full_name, relative_name) = match idp.distribution_point { - Some(data) => parse_distribution_point_name(py, data)?, - None => (py.None(), py.None()), - }; - let reasons = parse_distribution_point_reasons(py, idp.only_some_reasons)?; - Ok(Some( - x509_module.getattr("IssuingDistributionPoint")?.call1(( - full_name, - relative_name, - idp.only_contains_user_certs, - idp.only_contains_ca_certs, - reasons, - idp.indirect_crl, - idp.only_contains_attribute_certs, - ))?, - )) - } else if oid == &*FRESHEST_CRL_OID { - Ok(Some( - x509_module - .getattr("FreshestCRL")? - .call1((parse_distribution_points(py, ext_data)?,))?, - )) - } else { - Ok(None) - } - }, - ) - } - - fn get_revoked_certificate_by_serial_number( - &mut self, - py: pyo3::Python<'_>, - 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 certs = match v.borrow_value().tbs_cert_list.revoked_certificates.clone() { - Some(certs) => certs, - None => return Err(()), - }; - - // TODO: linear scan. Make a hash or bisect! - for cert in certs { - if serial_bytes == cert.user_certificate.as_bytes() { - return Ok(cert); - } - } - Err(()) - }); - match owned { - Ok(o) => Ok(Some(RevokedCertificate { - raw: o, - cached_extensions: None, - })), - Err(()) => Ok(None), - } - } - - fn is_signature_valid<'p>( - 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("backend")?; - backend.call_method1("_crl_is_signature_valid", (slf, public_key)) - } - - // 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("DeprecatedIn35")?; - let warnings = py.import("warnings")?; - warnings.call_method1( - "warn", - ( - "This version of cryptography contains a temporary pyOpenSSL fallback path. Upgrade pyOpenSSL now.", - cryptography_warning, - ), - )?; - let backend = py - .import("cryptography.hazmat.backends.openssl.backend")? - .getattr("backend")?; - Ok(backend.call_method1("_crl2ossl", (slf,))?) - } -} - -#[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.clone()) - }) - .unwrap(), - } - } -} - -#[ouroboros::self_referencing] -struct OwnedCRLIteratorData { - data: Arc, - #[borrows(data)] - #[covariant] - value: Option>>, -} - -#[pyo3::prelude::pyclass] -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| { - 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> { - 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 { - Some(v) => match v.next() { - Some(revoked) => Ok(revoked), - None => Err(()), - }, - None => Err(()), - }) - .ok()?; - Some(RevokedCertificate { - raw: 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: AlgorithmIdentifier<'a>, - signature_value: asn1::BitString<'a>, -} - -#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)] -struct TBSCertList<'a> { - version: Option, - signature: AlgorithmIdentifier<'a>, - issuer: Name<'a>, - this_update: Time, - next_update: Option