From ecaf2e508dc773d9251eeb271c9f051ce0c83456 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 25 Oct 2023 23:18:58 -0400 Subject: [PATCH 01/12] Make X509_ALGOR opaque (#9738) --- src/_cffi_src/openssl/x509.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index 5c5d7335df7e..120a23eb35e8 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -24,11 +24,7 @@ typedef ... Cryptography_STACK_OF_X509_CRL; typedef ... Cryptography_STACK_OF_X509_REVOKED; -typedef struct { - ASN1_OBJECT *algorithm; - ...; -} X509_ALGOR; - +typedef ... X509_ALGOR; typedef ... X509_ATTRIBUTE; typedef ... X509_EXTENSION; typedef ... X509_EXTENSIONS; From 5aef6fe9b789a4c83ef636abba7a6d3d8ec9a98e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 07:20:21 -0400 Subject: [PATCH 02/12] Bump pyopenssl from 23.2.0 to 23.3.0 in /.github/requirements (#9782) Bumps [pyopenssl](https://github.com/pyca/pyopenssl) from 23.2.0 to 23.3.0. - [Changelog](https://github.com/pyca/pyopenssl/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/pyopenssl/compare/23.2.0...23.3.0) --- updated-dependencies: - dependency-name: pyopenssl dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/requirements/publish-requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/requirements/publish-requirements.txt b/.github/requirements/publish-requirements.txt index 7cf244cc2ed5..0a8068fa6d2f 100644 --- a/.github/requirements/publish-requirements.txt +++ b/.github/requirements/publish-requirements.txt @@ -490,9 +490,9 @@ pyjwt==2.8.0 \ --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 # via sigstore -pyopenssl==23.2.0 \ - --hash=sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2 \ - --hash=sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac +pyopenssl==23.3.0 \ + --hash=sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2 \ + --hash=sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12 # via sigstore python-dateutil==2.8.2 \ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ From b9e6ac740bec095f1ffb4c12dd61f2bbd30aa410 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 26 Oct 2023 08:54:56 -0400 Subject: [PATCH 03/12] Bump setuptools rust (#9784) * Bump setuptools-rust from 1.7.0 to 1.8.0 in /.github/requirements Bumps [setuptools-rust](https://github.com/PyO3/setuptools-rust) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/PyO3/setuptools-rust/releases) - [Changelog](https://github.com/PyO3/setuptools-rust/blob/main/CHANGELOG.md) - [Commits](https://github.com/PyO3/setuptools-rust/compare/v1.7.0...v1.8.0) --- updated-dependencies: - dependency-name: setuptools-rust dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump setuptools-rust --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/requirements/build-requirements.txt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/requirements/build-requirements.txt b/.github/requirements/build-requirements.txt index beec7a1754eb..ca671a99d910 100644 --- a/.github/requirements/build-requirements.txt +++ b/.github/requirements/build-requirements.txt @@ -66,18 +66,14 @@ semantic-version==2.10.0 \ --hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \ --hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177 # via setuptools-rust -setuptools-rust==1.7.0 \ - --hash=sha256:071099885949132a2180d16abf907b60837e74b4085047ba7e9c0f5b365310c1 \ - --hash=sha256:c7100999948235a38ae7e555fe199aa66c253dc384b125f5d85473bf81eae3a3 +setuptools-rust==1.8.0 \ + --hash=sha256:5e02b7a80058853bf64127314f6b97d0efed11e08b94c88ca639a20976f6adc4 \ + --hash=sha256:95ec67edee2ca73233c9e75250e9d23a302aa23b4c8413dfd19c14c30d08f703 # via -r build-requirements.in tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via setuptools-rust -typing-extensions==4.8.0 \ - --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ - --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef - # via setuptools-rust wheel==0.41.2 \ --hash=sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985 \ --hash=sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8 From 7abf1f0ce2ac5d531ecd6e5a10bdf3aa1aa472fd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 26 Oct 2023 10:34:55 -0500 Subject: [PATCH 04/12] openssl 3.2.0-beta1 (#9786) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 193c4d517652..23f2050cbb08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.4", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct no-psk"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.1.4", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.1.4"}} - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.0-alpha2"}} + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.0-beta1"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.7.3"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.8.1"}} - {VERSION: "3.12", NOXSESSION: "tests-randomorder"} From 7f4bd0b3501ef436fe1531c8c5cf0414b2191739 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 26 Oct 2023 18:45:23 -0400 Subject: [PATCH 05/12] Trim the PKCS7 bindings (#9787) --- src/_cffi_src/openssl/pkcs7.py | 21 +------------------ .../hazmat/bindings/openssl/_conditional.py | 1 - 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/_cffi_src/openssl/pkcs7.py b/src/_cffi_src/openssl/pkcs7.py index b656f96e7239..cce06c6ec0c8 100644 --- a/src/_cffi_src/openssl/pkcs7.py +++ b/src/_cffi_src/openssl/pkcs7.py @@ -13,16 +13,10 @@ typedef struct { Cryptography_STACK_OF_X509 *cert; - Cryptography_STACK_OF_X509_CRL *crl; ...; } PKCS7_SIGNED; -typedef struct { - Cryptography_STACK_OF_X509 *cert; - Cryptography_STACK_OF_X509_CRL *crl; - ...; -} PKCS7_SIGN_ENVELOPE; - +typedef ... PKCS7_SIGN_ENVELOPE; typedef ... PKCS7_DIGEST; typedef ... PKCS7_ENCRYPT; typedef ... PKCS7_ENVELOPE; @@ -53,16 +47,6 @@ int PKCS7_verify(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, BIO *, int); PKCS7 *SMIME_read_PKCS7(BIO *, BIO **); -/* Included due to external consumer, see - https://github.com/pyca/pyopenssl/issues/1031 */ -Cryptography_STACK_OF_X509 *PKCS7_get0_signers(PKCS7 *, - Cryptography_STACK_OF_X509 *, - int); - -int PKCS7_type_is_signed(PKCS7 *); -int PKCS7_type_is_enveloped(PKCS7 *); -int PKCS7_type_is_signedAndEnveloped(PKCS7 *); -int PKCS7_type_is_data(PKCS7 *); """ CUSTOMIZATIONS = """ @@ -72,9 +56,6 @@ int (*PKCS7_verify)(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, BIO *, int) = NULL; PKCS7 *(*SMIME_read_PKCS7)(BIO *, BIO **) = NULL; -Cryptography_STACK_OF_X509 *(*PKCS7_get0_signers)(PKCS7 *, - Cryptography_STACK_OF_X509 *, - int) = NULL; #else static const long Cryptography_HAS_PKCS7_FUNCS = 1; #endif diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index ebd287b51f17..d40cbd8f963e 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -174,7 +174,6 @@ def cryptography_has_pkcs7_funcs() -> list[str]: return [ "PKCS7_verify", "SMIME_read_PKCS7", - "PKCS7_get0_signers", ] From 52e7fee583b5cce842af7470a3b1d9a8ebbc53b9 Mon Sep 17 00:00:00 2001 From: "pyca-boringbot[bot]" <106132319+pyca-boringbot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 00:17:24 +0000 Subject: [PATCH 06/12] Bump BoringSSL and/or OpenSSL in CI (#9788) Co-authored-by: pyca-boringbot[bot] --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23f2050cbb08..129d30c0e990 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,10 +42,10 @@ jobs: - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.7.3"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "libressl", VERSION: "3.8.1"}} - {VERSION: "3.12", NOXSESSION: "tests-randomorder"} - # Latest commit on the BoringSSL master branch, as of Oct 26, 2023. - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "c38dc29860a72540eb2c4fdb8a8bfb27ef94ddf3"}} - # Latest commit on the OpenSSL master branch, as of Oct 26, 2023. - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "6a0ae393dd554eb718e5148696e8f437d4faae5b"}} + # Latest commit on the BoringSSL master branch, as of Oct 27, 2023. + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "3309ca66385ecb0c37f1ac1be9f88712e25aa8ec"}} + # Latest commit on the OpenSSL master branch, as of Oct 27, 2023. + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "09298141592c579504966f1907a44cb95f37cc6e"}} # Builds with various Rust versions. Includes MSRV and next # potential future MSRV: # 1.64 - maturin From 5838299787f5f72a30b8b2840baafcaaedd4cba2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 07:04:14 -0400 Subject: [PATCH 07/12] Bump ruff from 0.1.2 to 0.1.3 (#9790) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.2 to 0.1.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.2...v0.1.3) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ci-constraints-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-constraints-requirements.txt b/ci-constraints-requirements.txt index 641533a10d80..2087f30e4b47 100644 --- a/ci-constraints-requirements.txt +++ b/ci-constraints-requirements.txt @@ -132,7 +132,7 @@ rfc3986==2.0.0 # via twine rich==13.6.0 # via twine -ruff==0.1.2 +ruff==0.1.3 # via cryptography (pyproject.toml) six==1.16.0 # via bleach From bb28549247a33418374c9f40a513cf59835e915f Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 27 Oct 2023 10:44:41 -0400 Subject: [PATCH 08/12] Avoid building ourselves in the flake job (#9789) --- noxfile.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index f53d026875a6..98ecdb619a4a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -152,7 +152,21 @@ def docs_linkcheck(session: nox.Session) -> None: @nox.session def flake(session: nox.Session) -> None: - install(session, ".[pep8test,test,ssh,nox]") + # Just install the dependencies needed for these tests - basically + # `pip install .[pep8test,test,ssh,nox]`, but without installing `.` + # TODO: Ideally there'd be a pip flag to install just our dependencies, + # but not install us. + install( + session, + "ruff", + "check-sdist", + "mypy", + "bcrypt", + "click", + "pytest", + "nox", + ) + install(session, "-e", "vectors/") session.run("ruff", ".") session.run("ruff", "format", "--check", ".") From 1e190d33c404115d6b96a1c29bb5cb436df8c167 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 27 Oct 2023 14:01:32 -0400 Subject: [PATCH 09/12] Run check-sdist with --no-isolation (#9791) * Run check-sdist with --no-isolation This is primarily useful for quick dev-cycles locally * Update noxfile.py --- noxfile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 98ecdb619a4a..a8b10a6fbf25 100644 --- a/noxfile.py +++ b/noxfile.py @@ -158,6 +158,9 @@ def flake(session: nox.Session) -> None: # but not install us. install( session, + "setuptools-rust", + "cffi>=1.12; platform_python_implementation != 'PyPy'", + "wheel", "ruff", "check-sdist", "mypy", @@ -170,7 +173,7 @@ def flake(session: nox.Session) -> None: session.run("ruff", ".") session.run("ruff", "format", "--check", ".") - session.run("check-sdist") + session.run("check-sdist", "--no-isolation") session.run( "mypy", "src/cryptography/", From 8cce93bb492392ea4a1cd168511b9ccaf20cb7eb Mon Sep 17 00:00:00 2001 From: "pyca-boringbot[bot]" <106132319+pyca-boringbot[bot]@users.noreply.github.com> Date: Sat, 28 Oct 2023 00:17:53 +0000 Subject: [PATCH 10/12] Bump BoringSSL and/or OpenSSL in CI (#9793) Co-authored-by: pyca-boringbot[bot] --- .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 129d30c0e990..9195ff66d8cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,8 +44,8 @@ jobs: - {VERSION: "3.12", NOXSESSION: "tests-randomorder"} # Latest commit on the BoringSSL master branch, as of Oct 27, 2023. - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "boringssl", VERSION: "3309ca66385ecb0c37f1ac1be9f88712e25aa8ec"}} - # Latest commit on the OpenSSL master branch, as of Oct 27, 2023. - - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "09298141592c579504966f1907a44cb95f37cc6e"}} + # Latest commit on the OpenSSL master branch, as of Oct 28, 2023. + - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "186b3f6a016de8fcf8573be111e3d174ca20f1bc"}} # Builds with various Rust versions. Includes MSRV and next # potential future MSRV: # 1.64 - maturin From ed57fbb118a26346d92af95e98e39c82dc0768b8 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 27 Oct 2023 20:54:05 -0400 Subject: [PATCH 11/12] Simplify code with new pyo3 method (#9794) --- src/rust/src/asn1.rs | 3 +-- src/rust/src/backend/ec.rs | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs index 5d8f2e1a95f2..6bed105518d8 100644 --- a/src/rust/src/asn1.rs +++ b/src/rust/src/asn1.rs @@ -8,7 +8,6 @@ use asn1::SimpleAsn1Readable; use cryptography_x509::certificate::Certificate; use cryptography_x509::common::{DssSignature, SubjectPublicKeyInfo, Time}; use cryptography_x509::name::Name; -use pyo3::basic::CompareOp; use pyo3::types::IntoPyDict; use pyo3::ToPyObject; @@ -68,7 +67,7 @@ pub(crate) fn py_uint_to_big_endian_bytes<'p>( v: &'p pyo3::types::PyLong, ) -> pyo3::PyResult<&'p [u8]> { let zero = (0).to_object(py); - if v.rich_compare(zero, CompareOp::Lt)?.is_true()? { + if v.lt(zero)? { return Err(pyo3::exceptions::PyValueError::new_err( "Negative integers are not supported", )); diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs index 96e42f4ec3ec..885a5cbf4dc2 100644 --- a/src/rust/src/backend/ec.rs +++ b/src/rust/src/backend/ec.rs @@ -6,7 +6,6 @@ use crate::backend::utils; use crate::error::{CryptographyError, CryptographyResult}; use crate::{exceptions, types}; use foreign_types_shared::ForeignTypeRef; -use pyo3::basic::CompareOp; use pyo3::ToPyObject; #[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl.ec")] @@ -214,9 +213,7 @@ fn public_key_from_numbers( let py_y = numbers.getattr(pyo3::intern!(py, "y"))?; let zero = (0).to_object(py); - if py_x.rich_compare(&zero, CompareOp::Lt)?.is_true()? - || py_y.rich_compare(&zero, CompareOp::Lt)?.is_true()? - { + if py_x.lt(&zero)? || py_y.lt(&zero)? { return Err(CryptographyError::from( pyo3::exceptions::PyValueError::new_err( "Invalid EC key. Both x and y must be non-negative.", From 3b39f657cb2a694e8fc289958ca723af7667c755 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Sat, 28 Oct 2023 12:44:18 +1100 Subject: [PATCH 12/12] validation: add Rust-side extension validation helpers (#9781) * validation: add Rust-side extension validation helpers * rust: Add unit tests for criticality and basic extension policy flow * rust: Remove validators, these can be in a separate PR * rust: Fix comment * rust: Collapse criticality unit tests * rust: Test case where maybe present validator detects incorrect criticality * rust: Remove unused `PolicyError` variants * rust: Add unit test exercising formatting of `DuplicateExtensionsError` * rust: Remove the need for printing `DuplicateExtensionsError` --- .../src/policy/extension.rs | 400 ++++++++++++++++++ .../src/policy/mod.rs | 6 + src/rust/cryptography-x509/src/extensions.rs | 2 +- 3 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 src/rust/cryptography-x509-validation/src/policy/extension.rs diff --git a/src/rust/cryptography-x509-validation/src/policy/extension.rs b/src/rust/cryptography-x509-validation/src/policy/extension.rs new file mode 100644 index 000000000000..06d88c4e3ad7 --- /dev/null +++ b/src/rust/cryptography-x509-validation/src/policy/extension.rs @@ -0,0 +1,400 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use asn1::ObjectIdentifier; +use cryptography_x509::{ + certificate::Certificate, + extensions::{Extension, Extensions}, +}; + +use crate::ops::CryptoOps; + +use super::{Policy, PolicyError}; + +// TODO: Remove `dead_code` attributes once we start using these helpers. + +/// Represents different criticality states for an extension. +#[allow(dead_code)] +pub(crate) enum Criticality { + /// The extension MUST be marked as critical. + Critical, + /// The extension MAY be marked as critical. + Agnostic, + /// The extension MUST NOT be marked as critical. + NonCritical, +} + +#[allow(dead_code)] +impl Criticality { + pub(crate) fn permits(&self, critical: bool) -> bool { + match (self, critical) { + (Criticality::Critical, true) => true, + (Criticality::Critical, false) => false, + (Criticality::Agnostic, _) => true, + (Criticality::NonCritical, true) => false, + (Criticality::NonCritical, false) => true, + } + } +} + +#[allow(dead_code)] +type PresentExtensionValidatorCallback = + fn(&Policy<'_, B>, &Certificate<'_>, &Extension<'_>) -> Result<(), PolicyError>; + +#[allow(dead_code)] +type MaybeExtensionValidatorCallback = + fn(&Policy<'_, B>, &Certificate<'_>, Option<&Extension<'_>>) -> Result<(), PolicyError>; + +/// Represents different validation states for an extension. +#[allow(dead_code)] +pub(crate) enum ExtensionValidator { + /// The extension MUST NOT be present. + NotPresent, + /// The extension MUST be present. + Present { + /// The extension's criticality. + criticality: Criticality, + /// An optional validator over the extension's inner contents, with + /// the surrounding `Policy` as context. + validator: Option>, + }, + /// The extension MAY be present; the interior validator is + /// always called if supplied, including if the extension is not present. + MaybePresent { + criticality: Criticality, + validator: Option>, + }, +} + +/// A "policy" for validating a specific X.509v3 extension, identified by +/// its OID. +#[allow(dead_code)] +pub(crate) struct ExtensionPolicy { + pub(crate) oid: asn1::ObjectIdentifier, + pub(crate) validator: ExtensionValidator, +} + +#[allow(dead_code)] +impl ExtensionPolicy { + pub(crate) fn not_present(oid: ObjectIdentifier) -> Self { + Self { + oid, + validator: ExtensionValidator::NotPresent, + } + } + + pub(crate) fn present( + oid: ObjectIdentifier, + criticality: Criticality, + validator: Option>, + ) -> Self { + Self { + oid, + validator: ExtensionValidator::Present { + criticality, + validator, + }, + } + } + + pub(crate) fn maybe_present( + oid: ObjectIdentifier, + criticality: Criticality, + validator: Option>, + ) -> Self { + Self { + oid, + validator: ExtensionValidator::MaybePresent { + criticality, + validator, + }, + } + } + + pub(crate) fn permits( + &self, + policy: &Policy<'_, B>, + cert: &Certificate<'_>, + extensions: &Extensions<'_>, + ) -> Result<(), PolicyError> { + match (&self.validator, extensions.get_extension(&self.oid)) { + // Extension MUST NOT be present and isn't; OK. + (ExtensionValidator::NotPresent, None) => Ok(()), + // Extension MUST NOT be present but is; NOT OK. + (ExtensionValidator::NotPresent, Some(_)) => Err(PolicyError::Other( + "EE certificate contains prohibited extension", + )), + // Extension MUST be present but is not; NOT OK. + (ExtensionValidator::Present { .. }, None) => Err(PolicyError::Other( + "EE certificate is missing required extension", + )), + // Extension MUST be present and is; check it. + ( + ExtensionValidator::Present { + criticality, + validator, + }, + Some(extn), + ) => { + if !criticality.permits(extn.critical) { + return Err(PolicyError::Other( + "EE certificate extension has incorrect criticality", + )); + } + + // If a custom validator is supplied, apply it. + validator.map_or(Ok(()), |v| v(policy, cert, &extn)) + } + // Extension MAY be present. + ( + ExtensionValidator::MaybePresent { + criticality, + validator, + }, + extn, + ) => { + // If the extension is present, apply our criticality check. + if extn + .as_ref() + .map_or(false, |extn| !criticality.permits(extn.critical)) + { + return Err(PolicyError::Other( + "EE certificate extension has incorrect criticality", + )); + } + + // If a custom validator is supplied, apply it. + validator.map_or(Ok(()), |v| v(policy, cert, extn.as_ref())) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{Criticality, ExtensionPolicy}; + use crate::ops::tests::{cert, v1_cert_pem, NullOps}; + use crate::ops::CryptoOps; + use crate::policy::{Policy, PolicyError}; + use asn1::{ObjectIdentifier, SimpleAsn1Writable}; + use cryptography_x509::certificate::Certificate; + use cryptography_x509::extensions::{BasicConstraints, Extension, Extensions}; + use cryptography_x509::oid::BASIC_CONSTRAINTS_OID; + + #[test] + fn test_criticality_variants() { + let criticality = Criticality::Critical; + assert!(criticality.permits(true)); + assert!(!criticality.permits(false)); + + let criticality = Criticality::Agnostic; + assert!(criticality.permits(true)); + assert!(criticality.permits(false)); + + let criticality = Criticality::NonCritical; + assert!(!criticality.permits(true)); + assert!(criticality.permits(false)); + } + + fn epoch() -> asn1::DateTime { + asn1::DateTime::new(1970, 1, 1, 0, 0, 0).unwrap() + } + + fn create_encoded_extensions( + oid: ObjectIdentifier, + critical: bool, + ext: &T, + ) -> Vec { + let ext_value = asn1::write_single(&ext).unwrap(); + let exts = vec![Extension { + extn_id: oid, + critical, + extn_value: &ext_value, + }]; + let der_exts = asn1::write_single(&asn1::SequenceOfWriter::new(exts)).unwrap(); + der_exts + } + + fn create_empty_encoded_extensions() -> Vec { + let exts: Vec> = vec![]; + let der_exts = asn1::write_single(&asn1::SequenceOfWriter::new(exts)).unwrap(); + der_exts + } + + fn present_extension_validator( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + _ext: &Extension<'_>, + ) -> Result<(), PolicyError> { + Ok(()) + } + + #[test] + fn test_extension_policy_present() { + // The certificate doesn't get used for this validator, so the certificate we use isn't important. + let cert_pem = v1_cert_pem(); + let cert = cert(&cert_pem); + let ops = NullOps {}; + let policy = Policy::new(ops, None, epoch()); + + // Test a policy that stipulates that a given extension MUST be present. + let extension_policy = ExtensionPolicy::present( + BASIC_CONSTRAINTS_OID, + Criticality::Critical, + Some(present_extension_validator), + ); + + // Check the case where the extension is present. + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + let der_exts = create_encoded_extensions(BASIC_CONSTRAINTS_OID, true, &bc); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_ok()); + + // Check the case where the extension isn't present. + let der_exts: Vec = create_empty_encoded_extensions(); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_err()); + } + + fn maybe_extension_validator( + _policy: &Policy<'_, B>, + _cert: &Certificate<'_>, + _ext: Option<&Extension<'_>>, + ) -> Result<(), PolicyError> { + Ok(()) + } + + #[test] + fn test_extension_policy_maybe() { + // The certificate doesn't get used for this validator, so the certificate we use isn't important. + let cert_pem = v1_cert_pem(); + let cert = cert(&cert_pem); + let ops = NullOps {}; + let policy = Policy::new(ops, None, epoch()); + + // Test a policy that stipulates that a given extension CAN be present. + let extension_policy = ExtensionPolicy::maybe_present( + BASIC_CONSTRAINTS_OID, + Criticality::Critical, + Some(maybe_extension_validator), + ); + + // Check the case where the extension is present. + let bc = BasicConstraints { + ca: false, + path_length: Some(3), + }; + let der_exts = create_encoded_extensions(BASIC_CONSTRAINTS_OID, true, &bc); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_ok()); + + // Check the case where the extension isn't present. + let der_exts: Vec = create_empty_encoded_extensions(); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_ok()); + } + + #[test] + fn test_extension_policy_not_present() { + // The certificate doesn't get used for this validator, so the certificate we use isn't important. + let cert_pem = v1_cert_pem(); + let cert = cert(&cert_pem); + let ops = NullOps {}; + let policy = Policy::new(ops, None, epoch()); + + // Test a policy that stipulates that a given extension MUST NOT be present. + let extension_policy = ExtensionPolicy::not_present(BASIC_CONSTRAINTS_OID); + + // Check the case where the extension is present. + let bc = BasicConstraints { + ca: false, + path_length: Some(3), + }; + let der_exts = create_encoded_extensions(BASIC_CONSTRAINTS_OID, true, &bc); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_err()); + + // Check the case where the extension isn't present. + let der_exts: Vec = create_empty_encoded_extensions(); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_ok()); + } + + #[test] + fn test_extension_policy_present_incorrect_criticality() { + // The certificate doesn't get used for this validator, so the certificate we use isn't important. + let cert_pem = v1_cert_pem(); + let cert = cert(&cert_pem); + let ops = NullOps {}; + let policy = Policy::new(ops, None, epoch()); + + // Test a present policy that stipulates that a given extension MUST be critical. + let extension_policy = ExtensionPolicy::present( + BASIC_CONSTRAINTS_OID, + Criticality::Critical, + Some(present_extension_validator), + ); + + // Mark the extension as non-critical despite our policy stipulating that it must be critical. + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + let der_exts = create_encoded_extensions(BASIC_CONSTRAINTS_OID, false, &bc); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_err()); + } + + #[test] + fn test_extension_policy_maybe_present_incorrect_criticality() { + // The certificate doesn't get used for this validator, so the certificate we use isn't important. + let cert_pem = v1_cert_pem(); + let cert = cert(&cert_pem); + let ops = NullOps {}; + let policy = Policy::new(ops, None, epoch()); + + // Test a maybe present policy that stipulates that a given extension MUST be critical. + let extension_policy = ExtensionPolicy::maybe_present( + BASIC_CONSTRAINTS_OID, + Criticality::Critical, + Some(maybe_extension_validator), + ); + + // Mark the extension as non-critical despite our policy stipulating that it must be critical. + let bc = BasicConstraints { + ca: true, + path_length: Some(3), + }; + let der_exts = create_encoded_extensions(BASIC_CONSTRAINTS_OID, false, &bc); + let raw_exts = asn1::parse_single(&der_exts).unwrap(); + let exts = Extensions::from_raw_extensions(Some(&raw_exts)) + .ok() + .unwrap(); + assert!(extension_policy.permits(&policy, &cert, &exts).is_err()); + } +} diff --git a/src/rust/cryptography-x509-validation/src/policy/mod.rs b/src/rust/cryptography-x509-validation/src/policy/mod.rs index b9bc437901b3..725020c6a2b6 100644 --- a/src/rust/cryptography-x509-validation/src/policy/mod.rs +++ b/src/rust/cryptography-x509-validation/src/policy/mod.rs @@ -2,6 +2,8 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +mod extension; + use std::collections::HashSet; use asn1::ObjectIdentifier; @@ -111,6 +113,10 @@ const RFC5280_CRITICAL_CA_EXTENSIONS: &[asn1::ObjectIdentifier] = const RFC5280_CRITICAL_EE_EXTENSIONS: &[asn1::ObjectIdentifier] = &[BASIC_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID]; +pub enum PolicyError { + Other(&'static str), +} + /// Represents a logical certificate "subject," i.e. a principal matching /// one of the names listed in a certificate's `subjectAltNames` extension. pub enum Subject<'a> { diff --git a/src/rust/cryptography-x509/src/extensions.rs b/src/rust/cryptography-x509/src/extensions.rs index f4deb7c8451f..fd7a3aaa0a3a 100644 --- a/src/rust/cryptography-x509/src/extensions.rs +++ b/src/rust/cryptography-x509/src/extensions.rs @@ -295,7 +295,7 @@ impl KeyUsage<'_> { mod tests { use crate::oid::{AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID}; - use super::{BasicConstraints, Extension, Extensions, KeyUsage}; + use super::{BasicConstraints, DuplicateExtensionsError, Extension, Extensions, KeyUsage}; #[test] fn test_get_extension() {