From 77974235a9a76797e06aa9c4d5dc62369bd56a5d Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Mon, 1 Jul 2024 18:15:32 +0200 Subject: [PATCH] Test S/MIME encryption by decrypting and comparing with plaintext --- src/_cffi_src/openssl/pkcs7.py | 2 + .../hazmat/bindings/openssl/_conditional.py | 1 + tests/hazmat/primitives/test_pkcs7.py | 100 +++++++++++++++++- 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/_cffi_src/openssl/pkcs7.py b/src/_cffi_src/openssl/pkcs7.py index 8e93a61b4e60a..09b70b7d3fc78 100644 --- a/src/_cffi_src/openssl/pkcs7.py +++ b/src/_cffi_src/openssl/pkcs7.py @@ -20,6 +20,7 @@ https://github.com/pyca/cryptography/issues/5433 */ int PKCS7_verify(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, BIO *, int); +int PKCS7_decrypt(PKCS7 *, EVP_PKEY *, X509 *, BIO *, int); PKCS7 *SMIME_read_PKCS7(BIO *, BIO **); """ @@ -29,6 +30,7 @@ int (*PKCS7_verify)(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, BIO *, int) = NULL; +int (*PKCS7_decrypt)(PKCS7 *, EVP_PKEY *, X509 *, BIO *, int) = NULL; PKCS7 *(*SMIME_read_PKCS7)(BIO *, BIO **) = NULL; #else static const long Cryptography_HAS_PKCS7_FUNCS = 1; diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index 805991c560c32..a8954727ec496 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -133,6 +133,7 @@ def cryptography_has_ssl_cookie() -> list[str]: def cryptography_has_pkcs7_funcs() -> list[str]: return [ "PKCS7_verify", + "PKCS7_decrypt", "SMIME_read_PKCS7", ] diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 4338e51c70260..c9d0e87ca5cbe 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -153,6 +153,35 @@ def _pkcs7_verify(encoding, sig, msg, certs, options, backend): backend._consume_errors() +def _pkcs7_decrypt(encoding, msg, pkey, cert_recipient, options, backend): + msg_bio = backend._bytes_to_bio(msg) + if encoding is serialization.Encoding.DER: + p7 = backend._lib.d2i_PKCS7_bio(msg_bio.bio, backend._ffi.NULL) + elif encoding is serialization.Encoding.PEM: + p7 = backend._lib.PEM_read_bio_PKCS7( + msg_bio.bio, + backend._ffi.NULL, + backend._ffi.NULL, + backend._ffi.NULL, + ) + else: + p7 = backend._lib.SMIME_read_PKCS7(msg_bio.bio, backend._ffi.NULL) + backend.openssl_assert(p7 != backend._ffi.NULL) + p7 = backend._ffi.gc(p7, backend._lib.PKCS7_free) + flags = 0 + for option in options: + if option is pkcs7.PKCS7Options.Text: + flags |= backend._lib.PKCS7_TEXT + + ossl_cert = backend._cert2ossl(cert_recipient) + ossl_pkey = backend._key2ossl(pkey) + out_bio = backend._create_mem_bio_gc() + res = backend._lib.PKCS7_decrypt(p7, ossl_pkey, ossl_cert, out_bio, flags) + backend.openssl_assert(res == 1) + + return backend._read_mem_bio(out_bio) + + def _load_cert_key(): key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "ca_key.pem"), @@ -1009,7 +1038,7 @@ def test_encrypt_invalid_encryption_options( ) def test_smime_encrypt_smime_encoding(self, backend, options): data = b"hello world\n" - cert, _ = _load_rsa_cert_key() + cert, private_key = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) ) @@ -1038,6 +1067,22 @@ def test_smime_encrypt_smime_encoding(self, backend, options): b"\x20\x43\x41" ) in payload + decrypted_bytes = _pkcs7_decrypt( + serialization.Encoding.SMIME, + enveloped, + private_key, + cert, + options, + backend, + ) + # New lines are canonicalized to '\r\n' when not using Binary + expected_data = ( + data + if pkcs7.PKCS7Options.Binary in options + else data.replace(b"\n", b"\r\n") + ) + assert decrypted_bytes == expected_data + @pytest.mark.parametrize( "options", [ @@ -1047,7 +1092,7 @@ def test_smime_encrypt_smime_encoding(self, backend, options): ) def test_smime_encrypt_der_encoding(self, backend, options): data = b"hello world\n" - cert, _ = _load_rsa_cert_key() + cert, private_key = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) ) @@ -1065,9 +1110,58 @@ def test_smime_encrypt_der_encoding(self, backend, options): b"\x20\x43\x41" ) in enveloped + decrypted_bytes = _pkcs7_decrypt( + serialization.Encoding.DER, + enveloped, + private_key, + cert, + options, + backend, + ) + # New lines are canonicalized to '\r\n' when not using Binary + expected_data = ( + data + if pkcs7.PKCS7Options.Binary in options + else data.replace(b"\n", b"\r\n") + ) + assert decrypted_bytes == expected_data + + @pytest.mark.parametrize( + "options", + [ + [pkcs7.PKCS7Options.Text], + [pkcs7.PKCS7Options.Binary], + ], + ) + def test_smime_encrypt_pem_encoding(self, backend, options): + data = b"hello world\n" + cert, private_key = _load_rsa_cert_key() + builder = ( + pkcs7.PKCS7EnvelopeBuilder().set_data(data).add_recipient(cert) + ) + enveloped = builder.encrypt(serialization.Encoding.PEM, options) + with open("msg.p7m", "wb") as f: + f.write(enveloped) + + decrypted_bytes = _pkcs7_decrypt( + serialization.Encoding.PEM, + enveloped, + private_key, + cert, + options, + backend, + ) + # New lines are canonicalized to '\r\n' when not using Binary + expected_data = ( + data + if pkcs7.PKCS7Options.Binary in options + else data.replace(b"\n", b"\r\n") + ) + assert decrypted_bytes == expected_data + def test_smime_encrypt_multiple_recipients(self, backend): data = b"hello world\n" - cert, _ = _load_rsa_cert_key() + cert, private_key = _load_rsa_cert_key() builder = ( pkcs7.PKCS7EnvelopeBuilder() .set_data(data)