From 334e456eae39b810216f977fb59c95f66b0e82a4 Mon Sep 17 00:00:00 2001 From: Patrick Rauscher Date: Sun, 31 Aug 2025 11:03:26 +0200 Subject: [PATCH 1/4] add test vectors for pkcs7_x509_extension_policies from PR #12465 of @nitneuqr --- docs/development/test-vectors.rst | 6 ++ tests/hazmat/primitives/test_pkcs7.py | 147 +++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 4162809d27df..08e8ce379495 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -1002,6 +1002,12 @@ Custom PKCS7 Test Vectors * ``pkcs7/enveloped-no-content.der``- A DER encoded PKCS7 file with enveloped data, without encrypted content, with key encrypted under the public key of ``x509/custom/ca/rsa_ca.pem``. +* ``pkcs7/ca.pem`` - A certificate adapted for S/MIME signature & verification. + Its private key is ``pkcs7/ca_key.pem``. +* ``pkcs7/ca_ascii_san.pem`` - An invalid certificate adapted for S/MIME signature + & verification. It has an ASCII subject alternative name stored as `otherName`. +* ``pkcs7/ca_non_ascii_san.pem`` - An invalid certificate adapted for S/MIME signature + & verification. It has an non-ASCII subject alternative name stored as `rfc822Name`. Custom OpenSSH Test Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 1496a23e1b2e..8d04a5419810 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -18,6 +18,16 @@ from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.serialization import pkcs7 +from cryptography.x509.oid import ( + ExtendedKeyUsageOID, + ExtensionOID, + ObjectIdentifier, +) +from cryptography.x509.verification import ( + PolicyBuilder, + Store, + VerificationError, +) from tests.x509.test_x509 import _generate_ca_and_leaf from ...hazmat.primitives.fixtures_rsa import ( @@ -125,20 +135,153 @@ def test_load_pkcs7_empty_certificates(self): def _load_cert_key(): key = load_vectors_from_file( - os.path.join("x509", "custom", "ca", "ca_key.pem"), + os.path.join("pkcs7", "ca_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) cert = load_vectors_from_file( - os.path.join("x509", "custom", "ca", "ca.pem"), + os.path.join("pkcs7", "ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate(pemfile.read()), mode="rb", ) return cert, key +class TestPKCS7VerifyCertificate: + @staticmethod + def build_pkcs7_certificate( + ca: bool = False, + digital_signature: bool = True, + usages: typing.Optional[typing.List[ObjectIdentifier]] = None, + ) -> x509.Certificate: + """ + This static method is a helper to build certificates allowing us + to test all cases in PKCS#7 certificate verification. + """ + # Load the standard certificate and private key + certificate, private_key = _load_cert_key() + + # Basic certificate builder + certificate_builder = ( + x509.CertificateBuilder() + .serial_number(certificate.serial_number) + .subject_name(certificate.subject) + .issuer_name(certificate.issuer) + .public_key(private_key.public_key()) + .not_valid_before(certificate.not_valid_before) + .not_valid_after(certificate.not_valid_after) + ) + + # Add AuthorityKeyIdentifier extension + aki = certificate.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + certificate_builder = certificate_builder.add_extension( + aki.value, critical=False + ) + + # Add SubjectAlternativeName extension + san = certificate.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + certificate_builder = certificate_builder.add_extension( + san.value, critical=True + ) + + # Add BasicConstraints extension + bc_extension = x509.BasicConstraints(ca=ca, path_length=None) + certificate_builder = certificate_builder.add_extension( + bc_extension, False + ) + + # Add KeyUsage extension + ku_extension = x509.KeyUsage( + digital_signature=digital_signature, + content_commitment=False, + key_encipherment=True, + data_encipherment=True, + key_agreement=True, + key_cert_sign=True, + crl_sign=True, + encipher_only=False, + decipher_only=False, + ) + certificate_builder = certificate_builder.add_extension( + ku_extension, True + ) + + # Add valid ExtendedKeyUsage extension + usages = usages or [ExtendedKeyUsageOID.EMAIL_PROTECTION] + certificate_builder = certificate_builder.add_extension( + x509.ExtendedKeyUsage(usages), True + ) + + # Build the certificate + return certificate_builder.sign( + private_key, certificate.signature_hash_algorithm, None + ) + + def test_verify_pkcs7_certificate(self): + # Prepare the parameters + certificate = self.build_pkcs7_certificate() + ca_policy, ee_policy = pkcs7.pkcs7_x509_extension_policies() + + # Verify the certificate + verifier = ( + PolicyBuilder() + .store(Store([certificate])) + .extension_policies(ca_policy=ca_policy, ee_policy=ee_policy) + .build_client_verifier() + ) + verifier.verify(certificate, []) + + @pytest.mark.parametrize( + "arguments", + [ + {"ca": True}, + {"digital_signature": False}, + {"usages": [ExtendedKeyUsageOID.CLIENT_AUTH]}, + ], + ) + def test_verify_invalid_pkcs7_certificate(self, arguments: dict): + # Prepare the parameters + certificate = self.build_pkcs7_certificate(**arguments) + + # Verify the certificate + self.verify_invalid_pkcs7_certificate(certificate) + + @staticmethod + def verify_invalid_pkcs7_certificate(certificate: x509.Certificate): + ca_policy, ee_policy = pkcs7.pkcs7_x509_extension_policies() + verifier = ( + PolicyBuilder() + .store(Store([certificate])) + .extension_policies(ca_policy=ca_policy, ee_policy=ee_policy) + .build_client_verifier() + ) + + with pytest.raises(VerificationError): + verifier.verify(certificate, []) + + @pytest.mark.parametrize( + "filename", ["ca_non_ascii_san.pem", "ca_ascii_san.pem"] + ) + def test_verify_pkcs7_certificate_wrong_san(self, filename): + # Read a certificate with an invalid SAN + pkcs7_certificate = load_vectors_from_file( + os.path.join("pkcs7", filename), + loader=lambda pemfile: x509.load_pem_x509_certificate( + pemfile.read() + ), + mode="rb", + ) + + # Verify the certificate + self.verify_invalid_pkcs7_certificate(pkcs7_certificate) + + @pytest.mark.supported( only_if=lambda backend: backend.pkcs7_supported(), skip_message="Requires OpenSSL with PKCS7 support", From ee6041ea663211ffaa3dd6f31734e06eadb46f21 Mon Sep 17 00:00:00 2001 From: Patrick Rauscher Date: Sun, 31 Aug 2025 11:22:45 +0200 Subject: [PATCH 2/4] avoid interference with other tests --- tests/hazmat/primitives/test_pkcs7.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 8d04a5419810..ed55abcc08e0 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -133,16 +133,21 @@ def test_load_pkcs7_empty_certificates(self): pkcs7.load_der_pkcs7_certificates(der) -def _load_cert_key(): +def _load_cert_key(ca_directory: str | None = None): + if ca_directory is not None: + _ca_directory = ca_directory + else: + _ca_directory = os.path.join("x509", "custom", "ca") + key = load_vectors_from_file( - os.path.join("pkcs7", "ca_key.pem"), + os.path.join(_ca_directory, "ca_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) cert = load_vectors_from_file( - os.path.join("pkcs7", "ca.pem"), + os.path.join(_ca_directory, "ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate(pemfile.read()), mode="rb", ) @@ -161,7 +166,7 @@ def build_pkcs7_certificate( to test all cases in PKCS#7 certificate verification. """ # Load the standard certificate and private key - certificate, private_key = _load_cert_key() + certificate, private_key = _load_cert_key("pkcs7") # Basic certificate builder certificate_builder = ( From 55333b1dd5cfb803c1dea54e5942d9e5a55539ae Mon Sep 17 00:00:00 2001 From: Patrick Rauscher Date: Sun, 31 Aug 2025 11:24:26 +0200 Subject: [PATCH 3/4] Revert "avoid interference with other tests" This reverts commit ee6041ea663211ffaa3dd6f31734e06eadb46f21. --- tests/hazmat/primitives/test_pkcs7.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index ed55abcc08e0..8d04a5419810 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -133,21 +133,16 @@ def test_load_pkcs7_empty_certificates(self): pkcs7.load_der_pkcs7_certificates(der) -def _load_cert_key(ca_directory: str | None = None): - if ca_directory is not None: - _ca_directory = ca_directory - else: - _ca_directory = os.path.join("x509", "custom", "ca") - +def _load_cert_key(): key = load_vectors_from_file( - os.path.join(_ca_directory, "ca_key.pem"), + os.path.join("pkcs7", "ca_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None, unsafe_skip_rsa_key_validation=True ), mode="rb", ) cert = load_vectors_from_file( - os.path.join(_ca_directory, "ca.pem"), + os.path.join("pkcs7", "ca.pem"), loader=lambda pemfile: x509.load_pem_x509_certificate(pemfile.read()), mode="rb", ) @@ -166,7 +161,7 @@ def build_pkcs7_certificate( to test all cases in PKCS#7 certificate verification. """ # Load the standard certificate and private key - certificate, private_key = _load_cert_key("pkcs7") + certificate, private_key = _load_cert_key() # Basic certificate builder certificate_builder = ( From 2e8b4bc3d24baac22fb3f657f3e94885e38e5cdf Mon Sep 17 00:00:00 2001 From: Patrick Rauscher Date: Sun, 31 Aug 2025 11:27:13 +0200 Subject: [PATCH 4/4] add missing test vectors --- vectors/cryptography_vectors/pkcs7/ca.pem | 11 +++++++++ .../pkcs7/ca_ascii_san.pem | 23 +++++++++++++++++++ vectors/cryptography_vectors/pkcs7/ca_key.pem | 5 ++++ .../pkcs7/ca_non_ascii_san.pem | 23 +++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 vectors/cryptography_vectors/pkcs7/ca.pem create mode 100644 vectors/cryptography_vectors/pkcs7/ca_ascii_san.pem create mode 100644 vectors/cryptography_vectors/pkcs7/ca_key.pem create mode 100644 vectors/cryptography_vectors/pkcs7/ca_non_ascii_san.pem diff --git a/vectors/cryptography_vectors/pkcs7/ca.pem b/vectors/cryptography_vectors/pkcs7/ca.pem new file mode 100644 index 000000000000..d11b0ec59b35 --- /dev/null +++ b/vectors/cryptography_vectors/pkcs7/ca.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBhjCCASygAwIBAgICAwkwCgYIKoZIzj0EAwIwJzELMAkGA1UEBhMCVVMxGDAW +BgNVBAMMD2NyeXB0b2dyYXBoeSBDQTAgFw0xNzAxMDEwMTAwMDBaGA8yMTAwMDEw +MTAwMDAwMFowJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBD +QTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBj/z7v5Obj13cPuwECLBnUGq0/N +2CxSJE4f4BBGZ7VfFblivTvPDG++Gve0oQ+0uctuhrNQ+WxRv8GC177F+QWjRjBE +MCEGA1UdEQEB/wQXMBWBE2V4YW1wbGVAZXhhbXBsZS5jb20wHwYDVR0jBBgwFoAU +/Ou02BLyyT2Zwzxn9H03feYT7fowCgYIKoZIzj0EAwIDSAAwRQIgUwIdC0Emkd6f +17DeOXTlmTAhwSDJ2FTuyHESwei7wJcCIQCnr9NpBxbtJfEzxHGGyd7PxgpOLi5u +rk+8QfzGMmg/fw== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/pkcs7/ca_ascii_san.pem b/vectors/cryptography_vectors/pkcs7/ca_ascii_san.pem new file mode 100644 index 000000000000..7e184abcbe3c --- /dev/null +++ b/vectors/cryptography_vectors/pkcs7/ca_ascii_san.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID3DCCAsSgAwIBAgIUGJw032ss5tmRmaY8x41pL5lqqRYwDQYJKoZIhvcNAQEL +BQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEWMBQGA1UECwwN +SVQgRGVwYXJ0bWVudDEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjUwNjA5MTg0 +NzQ1WhcNMjYwNjA5MTg0NzQ1WjB/MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs +aWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEVMBMGA1UECgwMRXhhbXBs +ZSBDb3JwMRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxl +LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALLWXuy3atOjhb8g +fa5AC5me9PqRqcqV63e+NIe8IaKioCM5Sl+3jhKb5DdPIjfQYbHbwPtY+rFSP364 +dBZoJpCDG4gcD6H3eS5JGc8Uz62l+oBNuFoU3EZiUNMF0k17vs/6CGeyt53+D9DJ +PG6Wv87nAAoK97r1rLdC8Of97QpUV/st+YDP7/LOH8CxJZOnbiUdekzo0dCQkk7n +17hJCYN1Y98VrlZFY25ny2TURUgK7lIjduEUb0dugYiepjzp7ZV8184kpAD/PtLT +czA1S8e6kySd5wbJSFcKxrk/j/cccUGLMyKPlMZgsHZUm/2DOLWLljxbEjCOxb1G +8+EpR9kCAwEAAaNQME4wLQYDVR0RBCYwJKAiBggrBgEFBQcICaAWDBRyZXRvdXJu +ZUBleGFtcGxlLmNvbTAdBgNVHQ4EFgQUm24AOQAmOInCPZPDUagXXw+BEl0wDQYJ +KoZIhvcNAQELBQADggEBAGgLqsx27sS28t1okxT1MU6QhfAn/Yw07Nhk3cpNKGnh +edrPPTXvJc05qHuQIqOiFIJ4SojbQ2+bVZwo7V3Jhspx9T+Gkb/Dn3rHpAfOXuaJ +RqJ777Cor2seAKv07jerGnEULYW8JcezZDGbv6ViC0oEgazwTzahfynrUMJ2DJRX +tnNdczDsGw+DVMvOBzcSE/aEzhd4ghgVq5aFS05wzhN/fTWKiN4tpEAG6y95gU73 +29O3y1W3dLjblTZJvXNtgCjMT6R3OVeWAsqyXDprFrZWZucCj8opIxRf6jpZlRfJ +qW+57pkefhg3q4MFjn08BOKpYwOdRouGE4l96dGBDwM= +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/pkcs7/ca_key.pem b/vectors/cryptography_vectors/pkcs7/ca_key.pem new file mode 100644 index 000000000000..2fb5394195cb --- /dev/null +++ b/vectors/cryptography_vectors/pkcs7/ca_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgA8Zqz5vLeR0ePZUe +jBfdyMmnnI4U5uAJApWTsMn/RuWhRANCAAQY/8+7+Tm49d3D7sBAiwZ1BqtPzdgs +UiROH+AQRme1XxW5Yr07zwxvvhr3tKEPtLnLboazUPlsUb/Bgte+xfkF +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/pkcs7/ca_non_ascii_san.pem b/vectors/cryptography_vectors/pkcs7/ca_non_ascii_san.pem new file mode 100644 index 000000000000..f590d881e68e --- /dev/null +++ b/vectors/cryptography_vectors/pkcs7/ca_non_ascii_san.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIUAX/xKTtlMllrK5ng0+OkmnxxIugwDQYJKoZIhvcNAQEL +BQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEWMBQGA1UECwwN +SVQgRGVwYXJ0bWVudDEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjUwNjA5MTgw +NzE4WhcNMjYwNjA5MTgwNzE4WjB/MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs +aWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEVMBMGA1UECgwMRXhhbXBs +ZSBDb3JwMRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50MRQwEgYDVQQDDAtleGFtcGxl +LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOxyV/ZsaGn7dOcZ +6ODFcnmwjPCKRASFeDtOMYoGrlALb9zA+UMuMB63dTZ8ofWsDgLLGhw86njfSYad +RslOw8Bki9lKiS1RhS/RbnDSBWB2wJzniyFn/qI2F93WbgqHMOnzzJcAkc/YPU0T +iyvNpjD3Q/xObcp7ouBJJmFSvLybSTJtFrVzkpIbDZYrn0KyKtgTCPc/r9D04u+u +scSACvTRjePsEZIgRkVgfVpdBmy1KeJmx2NqS8Yev+y+0e9q3t8Ga/j/CnPFXlEl +iBHciFtkKdd2HrPLJMXBKhMn2KagLJSSdABNApi8qULIpOnrEE8FepKCzkptFyS1 +5g0H3u0CAwEAAaNDMEEwIAYDVR0RBBkwF4EVcmV0b3VybsOpQGV4YW1wbGUuY29t +MB0GA1UdDgQWBBTthtqdM0IoehNymXnqMPX1joF1LzANBgkqhkiG9w0BAQsFAAOC +AQEApQZ3vOuBgNg1U26c4l0VSCU5q73Lecbgjc42AhEp9FyP7ratj4MyH7RGr4io +vl0wWROFBnzliW5ZA8CP3Ux4AbqgtxcFPBRHACjmrpoSFHmW7bpzRnqwJKwXsOGJ +ZhjA/2o91lEJr0UNhpvSGyR+xCkuvw83mvM1rmE19yNMElv96x/DPVQV2ocsffOb +kS7pIpvXX3pSIj7Up0Xrz+bSyhJlsO3sO5bREshyvuiRivm9AjBVRY/BtbFY6DcV +9javEitCw93BgImIs0CXGpZUrvphX8muWVct5xpKj64/Yo0hIYystX+xVl3EjTRf +B7pH2DE+cXg99p7L6RoYtlOeRA== +-----END CERTIFICATE-----