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 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 \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 193c4d517652..9195ff66d8cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,14 +38,14 @@ 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"} - # 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 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 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 diff --git a/noxfile.py b/noxfile.py index f53d026875a6..a8b10a6fbf25 100644 --- a/noxfile.py +++ b/noxfile.py @@ -152,11 +152,28 @@ 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, + "setuptools-rust", + "cffi>=1.12; platform_python_implementation != 'PyPy'", + "wheel", + "ruff", + "check-sdist", + "mypy", + "bcrypt", + "click", + "pytest", + "nox", + ) + install(session, "-e", "vectors/") session.run("ruff", ".") session.run("ruff", "format", "--check", ".") - session.run("check-sdist") + session.run("check-sdist", "--no-isolation") session.run( "mypy", "src/cryptography/", 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/_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; 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", ] diff --git a/src/rust/cryptography-x509-validation/src/policy/extension.rs b/src/rust/cryptography-x509-validation/src/policy/extension.rs index ed416cdc7ebc..ae1a54318c78 100644 --- a/src/rust/cryptography-x509-validation/src/policy/extension.rs +++ b/src/rust/cryptography-x509-validation/src/policy/extension.rs @@ -12,7 +12,10 @@ 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, @@ -22,6 +25,7 @@ pub(crate) enum Criticality { NonCritical, } +#[allow(dead_code)] impl Criticality { pub(crate) fn permits(&self, critical: bool) -> bool { match (self, critical) { @@ -34,13 +38,16 @@ impl Criticality { } } +#[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, @@ -62,11 +69,13 @@ pub(crate) enum ExtensionValidator { /// 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 { @@ -298,3 +307,231 @@ pub(crate) mod common { Ok(()) } } + +#[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/src/extensions.rs b/src/rust/cryptography-x509/src/extensions.rs index 99575a46b290..c3845d451bc5 100644 --- a/src/rust/cryptography-x509/src/extensions.rs +++ b/src/rust/cryptography-x509/src/extensions.rs @@ -296,7 +296,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() { 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.",