Skip to content

Commit d6cac75

Browse files
nitneuqralex
andauthored
Add support for decrypting S/MIME messages (#11555)
* first python API proposition first round-trip tests feat: made asn1 structures readable refacto: adapted existing functions accordingly feat/pkcs12: added symmetric_decrypt feat: deserialize 3 possible encodings feat: handling AES-128 feat: raise error when no recipient is found feat/pkcs7: added decanonicalize function feat/asn1: added decode_der_data feat/pkcs7: added smime_enveloped_decode tests are the round-trip (encrypt & decrypt) more tests for 100% python coverage test support pkcs7_encrypt with openssl added algorithm to pkcs7_encrypt signature refacto: decrypt function is clearer flow is more natural refacto: added all rust error tests refacto: added another CA chain for checking fix: const handling Refactor PKCS7Decryptor to pkcs7_decrypt refacto: removed SMIME_ENVELOPED_DECODE from rust code refacto: removed decode_der_data adapted tests accordingly removed the PEM tag check added tests for smime_decnonicalize one more test case Update src/rust/src/pkcs7.rs Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> took comments into account pem to der is now outside of decrypt fix: removed test_support pkcs7_encrypt added vector for aes_256_cbc encrypted pkcs7 feat: not using test_support decrypt anymore added new vectors for PKCS7 tests feat: using pkcs7 vectors removed previous ones fix: changed wrong function feat: added certificate issuer check test: generating the RSA chain removed the vectors accordingly moved symmetric_decrypt to pkcs7.rs * Update src/cryptography/hazmat/primitives/serialization/pkcs7.py Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> * fix: removed use of deprecated new_bound for PyBytes * corrected some error types * updated tests accordingly * fix: handling other key encryption algorithms added vectors & tests accordingly * first attempts raising error when no header to remove * one more test to handle text data without header * fix: went back to the previous implementation * refacto: removed the return part * feat: Binary option does not seem useful for decryption removed decanonicalization function adapted tests accordingly * moved logic into rust only left some checks (for now?) * removed pyfunction for the inner decrypt one * added checks in rust now :) changed name for clarity * removed unused function * some checks not needed anymore * removed a parameter * took comments into account * removed unused import removed excess get_type * added first unwrap corrections cleaned tests, added some others added more vectors * no more unwrap for parameter checks * removing headers is Python now added tests accordingly will compare with OpenSSL * final corrections? * first version of documentation some minor refactoring * corrected doctests * better indentation * doctest: added RSA private key * oops --------- Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com>
1 parent b8e5bfd commit d6cac75

File tree

11 files changed

+886
-80
lines changed

11 files changed

+886
-80
lines changed

CHANGELOG.rst

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Changelog
2626
* Added support for :class:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id`
2727
when using OpenSSL 3.2.0+.
2828
* Added support for the :class:`~cryptography.x509.Admissions` certificate extension.
29+
* Added basic support for PKCS7 decryption (including S/MIME 3.2) via
30+
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_der`,
31+
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_pem`, and
32+
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_smime`.
2933

3034
.. _v43-0-3:
3135

docs/development/test-vectors.rst

+3
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,9 @@ Custom PKCS7 Test Vectors
882882
* ``pkcs7/enveloped-rsa-oaep.pem``- A PEM encoded PKCS7 file with
883883
enveloped data, with key encrypted using RSA-OAEP, under the public key of
884884
``x509/custom/ca/rsa_ca.pem``.
885+
* ``pkcs7/enveloped-no-content.der``- A DER encoded PKCS7 file with
886+
enveloped data, without encrypted content, with key encrypted under the
887+
public key of ``x509/custom/ca/rsa_ca.pem``.
885888

886889
Custom OpenSSH Test Vectors
887890
~~~~~~~~~~~~~~~~~~~~~~~~~~~

docs/hazmat/primitives/asymmetric/serialization.rst

+236-11
Original file line numberDiff line numberDiff line change
@@ -1001,11 +1001,6 @@ PKCS7 is a format described in :rfc:`2315`, among other specifications. It can
10011001
contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
10021002
``p7m``, or ``p7s`` file suffix but other suffixes are also seen in the wild.
10031003

1004-
.. note::
1005-
1006-
``cryptography`` only supports parsing certificates from PKCS7 files at
1007-
this time.
1008-
10091004
.. data:: PKCS7HashTypes
10101005

10111006
.. versionadded:: 40.0.0
@@ -1126,6 +1121,60 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
11261121
-----END CERTIFICATE-----
11271122
""".strip()
11281123

1124+
ca_key_rsa = b"""
1125+
-----BEGIN PRIVATE KEY-----
1126+
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
1175+
fDWXtBuybAb5u+ZaVwHgqaaPCkKkVQ==
1176+
-----END PRIVATE KEY-----
1177+
""".strip()
11291178

11301179
.. class:: PKCS7SignatureBuilder
11311180

@@ -1261,28 +1310,204 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
12611310
this operation only
12621311
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` and
12631312
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Binary`
1264-
are supported.
1313+
are supported, and cannot be used at the same time.
12651314

12661315
:returns bytes: The enveloped PKCS7 message.
12671316

1317+
.. function:: pkcs7_decrypt_der(data, certificate, private_key, options)
1318+
1319+
.. versionadded:: 44.0.0
1320+
1321+
.. doctest::
1322+
1323+
>>> from cryptography import x509
1324+
>>> from cryptography.hazmat.primitives import serialization
1325+
>>> from cryptography.hazmat.primitives.serialization import pkcs7
1326+
>>> cert = x509.load_pem_x509_certificate(ca_cert_rsa)
1327+
>>> key = serialization.load_pem_private_key(ca_key_rsa, None)
1328+
>>> options = [pkcs7.PKCS7Options.Text]
1329+
>>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data(
1330+
... b"data to encrypt"
1331+
... ).add_recipient(
1332+
... cert
1333+
... ).encrypt(
1334+
... serialization.Encoding.DER, options
1335+
... )
1336+
>>> pkcs7.pkcs7_decrypt_der(enveloped, cert, key, options)
1337+
b'data to encrypt'
1338+
1339+
Deserialize and decrypt a DER-encoded PKCS7 message. PKCS7 (or S/MIME) has multiple versions,
1340+
but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2.
1341+
1342+
:param data: The data, encoded in DER format.
1343+
:type data: bytes
1344+
1345+
:param certificate: A :class:`~cryptography.x509.Certificate` for an intended
1346+
recipient of the encrypted message. Only certificates with public RSA keys
1347+
are currently supported.
1348+
1349+
:param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`
1350+
associated with the certificate provided. Only private RSA keys are supported.
1351+
1352+
:param options: A list of
1353+
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For
1354+
this operation only
1355+
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported.
1356+
1357+
:returns bytes: The decrypted message.
1358+
1359+
:raises ValueError: If the recipient certificate does not match any of the encrypted keys in the
1360+
PKCS7 data.
1361+
1362+
:raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted
1363+
with another algorithm than RSA with PKCS1 v1.5 padding.
1364+
1365+
:raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with
1366+
another algorithm than AES-128-CBC.
1367+
1368+
:raises ValueError: If the PKCS7 data does not contain encrypted content.
1369+
1370+
:raises ValueError: If the PKCS7 data is not of the enveloped data type.
1371+
1372+
.. function:: pkcs7_decrypt_pem(data, certificate, private_key, options)
1373+
1374+
.. versionadded:: 44.0.0
1375+
1376+
.. doctest::
1377+
1378+
>>> from cryptography import x509
1379+
>>> from cryptography.hazmat.primitives import serialization
1380+
>>> from cryptography.hazmat.primitives.serialization import pkcs7
1381+
>>> cert = x509.load_pem_x509_certificate(ca_cert_rsa)
1382+
>>> key = serialization.load_pem_private_key(ca_key_rsa, None)
1383+
>>> options = [pkcs7.PKCS7Options.Text]
1384+
>>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data(
1385+
... b"data to encrypt"
1386+
... ).add_recipient(
1387+
... cert
1388+
... ).encrypt(
1389+
... serialization.Encoding.PEM, options
1390+
... )
1391+
>>> pkcs7.pkcs7_decrypt_pem(enveloped, cert, key, options)
1392+
b'data to encrypt'
1393+
1394+
Deserialize and decrypt a PEM-encoded PKCS7E message. PKCS7 (or S/MIME) has multiple versions,
1395+
but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2.
1396+
1397+
:param data: The data, encoded in PEM format.
1398+
:type data: bytes
1399+
1400+
:param certificate: A :class:`~cryptography.x509.Certificate` for an intended
1401+
recipient of the encrypted message. Only certificates with public RSA keys
1402+
are currently supported.
1403+
1404+
:param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`
1405+
associated with the certificate provided. Only private RSA keys are supported.
1406+
1407+
:param options: A list of
1408+
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For
1409+
this operation only
1410+
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported.
1411+
1412+
:returns bytes: The decrypted message.
1413+
1414+
:raises ValueError: If the PEM data does not have the PKCS7 tag.
1415+
1416+
:raises ValueError: If the recipient certificate does not match any of the encrypted keys in the
1417+
PKCS7 data.
1418+
1419+
:raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted
1420+
with another algorithm than RSA with PKCS1 v1.5 padding.
1421+
1422+
:raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with
1423+
another algorithm than AES-128-CBC.
1424+
1425+
:raises ValueError: If the PKCS7 data does not contain encrypted content.
1426+
1427+
:raises ValueError: If the PKCS7 data is not of the enveloped data type.
1428+
1429+
.. function:: pkcs7_decrypt_smime(data, certificate, private_key, options)
1430+
1431+
.. versionadded:: 44.0.0
1432+
1433+
.. doctest::
1434+
1435+
>>> from cryptography import x509
1436+
>>> from cryptography.hazmat.primitives import serialization
1437+
>>> from cryptography.hazmat.primitives.serialization import pkcs7
1438+
>>> cert = x509.load_pem_x509_certificate(ca_cert_rsa)
1439+
>>> key = serialization.load_pem_private_key(ca_key_rsa, None)
1440+
>>> options = [pkcs7.PKCS7Options.Text]
1441+
>>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data(
1442+
... b"data to encrypt"
1443+
... ).add_recipient(
1444+
... cert
1445+
... ).encrypt(
1446+
... serialization.Encoding.SMIME, options
1447+
... )
1448+
>>> pkcs7.pkcs7_decrypt_smime(enveloped, cert, key, options)
1449+
b'data to encrypt'
1450+
1451+
Deserialize and decrypt a S/MIME-encoded PKCS7 message. PKCS7 (or S/MIME) has multiple versions,
1452+
but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2.
1453+
1454+
:param data: The data. It should be in S/MIME format, meaning MIME with content type
1455+
``application/pkcs7-mime`` or ``application/x-pkcs7-mime``.
1456+
:type data: bytes
1457+
1458+
:param certificate: A :class:`~cryptography.x509.Certificate` for an intended
1459+
recipient of the encrypted message. Only certificates with public RSA keys
1460+
are currently supported.
1461+
1462+
:param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`
1463+
associated with the certificate provided. Only private RSA keys are supported.
1464+
1465+
:param options: A list of
1466+
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For
1467+
this operation only
1468+
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported.
1469+
1470+
:returns bytes: The decrypted message.
1471+
1472+
:raises ValueError: If the S/MIME data is not one of the correct content types.
1473+
1474+
:raises ValueError: If the recipient certificate does not match any of the encrypted keys in the
1475+
PKCS7 data.
1476+
1477+
:raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted
1478+
with another algorithm than RSA with PKCS1 v1.5 padding.
1479+
1480+
:raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with
1481+
another algorithm than AES-128-CBC.
1482+
1483+
:raises ValueError: If the PKCS7 data does not contain encrypted content.
1484+
1485+
:raises ValueError: If the PKCS7 data is not of the enveloped data type.
1486+
12681487

12691488
.. class:: PKCS7Options
12701489

12711490
.. versionadded:: 3.2
12721491

1273-
An enumeration of options for PKCS7 signature and envelope creation.
1492+
An enumeration of options for PKCS7 signature, envelope creation, and decryption.
12741493

12751494
.. attribute:: Text
12761495

1277-
The text option adds ``text/plain`` headers to an S/MIME message when
1278-
serializing to
1496+
For signing, the text option adds ``text/plain`` headers to an S/MIME message when
1497+
serializing to
12791498
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.SMIME`.
12801499
This option is disallowed with ``DER`` serialization.
1500+
For envelope creation, it adds ``text/plain`` headers to the encrypted content, regardless
1501+
of the specified encoding.
1502+
For envelope decryption, it parses the decrypted content headers (if any), checks if the
1503+
content type is 'text/plain', then removes all headers (keeping only the payload) of this
1504+
decrypted content. If there is no header, or the content type is not "text/plain", it
1505+
raises an error.
12811506

12821507
.. attribute:: Binary
12831508

1284-
Signing normally converts line endings (LF to CRLF). When
1285-
passing this option the data will not be converted.
1509+
Signature and envelope creation normally converts line endings (LF to CRLF). When
1510+
passing this option, the data will not be converted.
12861511

12871512
.. attribute:: DetachedSignature
12881513

src/cryptography/hazmat/bindings/_rust/pkcs7.pyi

+19
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import typing
66

77
from cryptography import x509
88
from cryptography.hazmat.primitives import serialization
9+
from cryptography.hazmat.primitives.asymmetric import rsa
910
from cryptography.hazmat.primitives.serialization import pkcs7
1011

1112
def serialize_certificates(
@@ -22,6 +23,24 @@ def sign_and_serialize(
2223
encoding: serialization.Encoding,
2324
options: typing.Iterable[pkcs7.PKCS7Options],
2425
) -> bytes: ...
26+
def decrypt_der(
27+
data: bytes,
28+
certificate: x509.Certificate,
29+
private_key: rsa.RSAPrivateKey,
30+
options: typing.Iterable[pkcs7.PKCS7Options],
31+
) -> bytes: ...
32+
def decrypt_pem(
33+
data: bytes,
34+
certificate: x509.Certificate,
35+
private_key: rsa.RSAPrivateKey,
36+
options: typing.Iterable[pkcs7.PKCS7Options],
37+
) -> bytes: ...
38+
def decrypt_smime(
39+
data: bytes,
40+
certificate: x509.Certificate,
41+
private_key: rsa.RSAPrivateKey,
42+
options: typing.Iterable[pkcs7.PKCS7Options],
43+
) -> bytes: ...
2544
def load_pem_pkcs7_certificates(
2645
data: bytes,
2746
) -> list[x509.Certificate]: ...

src/cryptography/hazmat/bindings/_rust/test_support.pyi

-7
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,6 @@ class TestCertificate:
1313
subject_value_tags: list[int]
1414

1515
def test_parse_certificate(data: bytes) -> TestCertificate: ...
16-
def pkcs7_decrypt(
17-
encoding: serialization.Encoding,
18-
msg: bytes,
19-
pkey: serialization.pkcs7.PKCS7PrivateKeyTypes,
20-
cert_recipient: x509.Certificate,
21-
options: list[pkcs7.PKCS7Options],
22-
) -> bytes: ...
2316
def pkcs7_verify(
2417
encoding: serialization.Encoding,
2518
sig: bytes,

src/cryptography/hazmat/primitives/serialization/pkcs7.py

+33
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ def encrypt(
263263
return rust_pkcs7.encrypt_and_serialize(self, encoding, options)
264264

265265

266+
pkcs7_decrypt_der = rust_pkcs7.decrypt_der
267+
pkcs7_decrypt_pem = rust_pkcs7.decrypt_pem
268+
pkcs7_decrypt_smime = rust_pkcs7.decrypt_smime
269+
270+
266271
def _smime_signed_encode(
267272
data: bytes, signature: bytes, micalg: str, text_mode: bool
268273
) -> bytes:
@@ -328,6 +333,34 @@ def _smime_enveloped_encode(data: bytes) -> bytes:
328333
return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0))
329334

330335

336+
def _smime_enveloped_decode(data: bytes) -> bytes:
337+
m = email.message_from_bytes(data)
338+
if m.get_content_type() not in {
339+
"application/x-pkcs7-mime",
340+
"application/pkcs7-mime",
341+
}:
342+
raise ValueError("Not an S/MIME enveloped message")
343+
return bytes(m.get_payload(decode=True))
344+
345+
346+
def _smime_remove_text_headers(data: bytes) -> bytes:
347+
m = email.message_from_bytes(data)
348+
# Using get() instead of get_content_type() since it has None as default,
349+
# where the latter has "text/plain". Both methods are case-insensitive.
350+
content_type = m.get("content-type")
351+
if content_type is None:
352+
raise ValueError(
353+
"Decrypted MIME data has no 'Content-Type' header. "
354+
"Please remove the 'Text' option to parse it manually."
355+
)
356+
if "text/plain" not in content_type:
357+
raise ValueError(
358+
f"Decrypted MIME data content type is '{content_type}', not "
359+
"'text/plain'. Remove the 'Text' option to parse it manually."
360+
)
361+
return bytes(m.get_payload(decode=True))
362+
363+
331364
class OpenSSLMimePart(email.message.MIMEPart):
332365
# A MIMEPart subclass that replicates OpenSSL's behavior of not including
333366
# a newline if there are no headers.

0 commit comments

Comments
 (0)