Repository: pyca/cryptography

Issue:
"Access Violation Error" on creating legacy pkcs12 file with wrong private key
`serialize_key_and_certificates` exits the main thread with `-1073741819 (0xC0000005)` when called with a private key not belonging to the certificate.

Versions:
* Python: `3.10.11` 
* Pip: `24.0`
* cryptography: `42.0.3`
* OS: `Windows 11`

cryptography was installed via pip `pip install cryptography `

min steps to reproduce:

```python
def min_example():
    from cryptography import x509
    from cryptography.x509.oid import NameOID
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.primitives._serialization import PrivateFormat
    from cryptography.hazmat.primitives.serialization import pkcs12
    from datetime import datetime, timedelta

    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048
    )
    name = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, "COMMON_NAME")
    ])
    
    basic_contraints = x509.BasicConstraints(ca=True, path_length=0)
    now = datetime.utcnow()
    cert = (
        x509.CertificateBuilder()
        .subject_name(name)
        .issuer_name(name)
        .public_key(private_key.public_key())
        .serial_number(1000)
        .not_valid_before(now)
        .not_valid_after(now + timedelta(days=10*365))
        .add_extension(basic_contraints, False)
        .sign(private_key, hashes.SHA256(), default_backend())
    )
    false_private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048
    )
    encryption = (
        PrivateFormat.PKCS12.encryption_builder().
        kdf_rounds(50000).
        key_cert_algorithm(pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC).
        hmac_hash(hashes.SHA1()).
        build("test12345678".encode())
    )
    
    ### program exits with: -1073741819 (0xC0000005). This "Access Violation Error" uncatchable with try except
    p12 = pkcs12.serialize_key_and_certificates(
        name="common_name".encode(), key=false_private_key, cert=cert, cas=None, encryption_algorithm=encryption
    )
```
I am working with a user maintained database of certs and cannot know if the certs and private keys have been stored correctly. I would like to have a descriptive exception that is catchable by python.

For now I'm building the cert first with the python `OpenSSL` lib which returns me the wrong kind of `.pfx` but throws catchable errors and if no error occurs I build the `.pfx` file with the `cryptography` lib. A sub optimal process but it works for now. 



Discussion:
I've reproduced this, thanks for reporting.
Stack trace:

```
#0  0x00007ffff71269cd in PKCS12_setup_mac ()
   from /home/alex/hacks/t/.venv/lib/python3.10/site-packages/cryptography/hazmat/bindings/_rust.abi3.so
#1  0x00007ffff7126bcf in PKCS12_set_mac ()
   from /home/alex/hacks/t/.venv/lib/python3.10/site-packages/cryptography/hazmat/bindings/_rust.abi3.so
#2  0x00007ffff6f6172e in _cffi_f_PKCS12_set_mac ()
   from /home/alex/hacks/t/.venv/lib/python3.10/site-packages/cryptography/hazmat/bindings/_rust.abi3.so
#3  0x00005555556ae138 in ?? ()
#4  0x00005555556a4a7b in _PyObject_MakeTpCall ()
#5  0x000055555569d629 in _PyEval_EvalFrameDefault ()
#6  0x00005555556ae9fc in _PyFunction_Vectorcall ()
#7  0x000055555569745c in _PyEval_EvalFrameDefault ()
#8  0x00005555556ae9fc in _PyFunction_Vectorcall ()
#9  0x000055555569853c in _PyEval_EvalFrameDefault ()
#10 0x00005555556ae9fc in _PyFunction_Vectorcall ()
#11 0x000055555569726d in _PyEval_EvalFrameDefault ()
#12 0x00005555556939c6 in ?? ()
#13 0x0000555555789256 in PyEval_EvalCode ()
#14 0x00005555557b4108 in ?? ()
#15 0x00005555557ad9cb in ?? ()
#16 0x00005555557b3e55 in ?? ()
#17 0x00005555557b3338 in _PyRun_SimpleFileObject ()
#18 0x00005555557b2f83 in _PyRun_AnyFileObject ()
#19 0x00005555557a5a5e in Py_RunMain ()
#20 0x000055555577c02d in Py_BytesMain ()
#21 0x00007ffff7c7bd90 in __libc_start_call_main (main=main@entry=0x55555577bff0, argc=argc@entry=2, argv=argv@entry=0x7fffffffeaa8)
    at ../sysdeps/nptl/libc_start_call_main.h:58
#22 0x00007ffff7c7be40 in __libc_start_main_impl (main=0x55555577bff0, argc=2, argv=0x7fffffffeaa8, init=<optimized out>, 
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffea98) at ../csu/libc-start.c:392
#23 0x000055555577bf25 in _start ()
```

Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -826,6 +826,15 @@ def serialize_key_and_certificates_to_pkcs12(
                     mac_iter,
                     0,
                 )
+                if p12 == self._ffi.NULL:
+                    errors = self._consume_errors()
+                    raise ValueError(
+                        (
+                            "Failed to create PKCS12 (does the key match the "
+                            "certificate?)"
+                        ),
+                        errors,
+                    )
 
             if (
                 self._lib.Cryptography_HAS_PKCS12_SET_MAC


Test patch:
diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py
--- a/tests/hazmat/primitives/test_pkcs12.py
+++ b/tests/hazmat/primitives/test_pkcs12.py
@@ -657,6 +657,24 @@ def test_key_serialization_encryption_set_mac_unsupported(
                 b"name", cakey, cacert, [], algorithm
             )
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend._lib.Cryptography_HAS_PKCS12_SET_MAC,
+        skip_message="Requires OpenSSL with PKCS12_set_mac",
+    )
+    def test_set_mac_key_certificate_mismatch(self, backend):
+        cacert, _ = _load_ca(backend)
+        key = ec.generate_private_key(ec.SECP256R1())
+        encryption = (
+            serialization.PrivateFormat.PKCS12.encryption_builder()
+            .hmac_hash(hashes.SHA256())
+            .build(b"password")
+        )
+
+        with pytest.raises(ValueError):
+            serialize_key_and_certificates(
+                b"name", key, cacert, [], encryption
+            )
+
 
 @pytest.mark.skip_fips(
     reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it."