Skip to content

Add support for decrypting S/MIME messages #11555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
24a89b8
first python API proposition
nitneuqr Nov 19, 2024
370ddfb
Update src/cryptography/hazmat/primitives/serialization/pkcs7.py
nitneuqr Nov 19, 2024
5da5053
fix: removed use of deprecated new_bound for PyBytes
nitneuqr Nov 19, 2024
f5cb0d5
corrected some error types
nitneuqr Nov 19, 2024
aa1cf3d
updated tests accordingly
nitneuqr Nov 19, 2024
3bfa0ec
fix: handling other key encryption algorithms
nitneuqr Nov 19, 2024
1be21b9
first attempts raising error when no header to remove
nitneuqr Nov 19, 2024
aac5dc3
one more test to handle text data without header
nitneuqr Nov 19, 2024
008a67d
fix: went back to the previous implementation
nitneuqr Nov 19, 2024
d20b72d
refacto: removed the return part
nitneuqr Nov 19, 2024
79ccf96
feat: Binary option does not seem useful for decryption
nitneuqr Nov 20, 2024
d318990
moved logic into rust
nitneuqr Nov 20, 2024
3ffcfa2
removed pyfunction for the inner decrypt one
nitneuqr Nov 20, 2024
cd11be3
added checks in rust now :)
nitneuqr Nov 20, 2024
fa92695
removed unused function
nitneuqr Nov 20, 2024
4defb66
some checks not needed anymore
nitneuqr Nov 20, 2024
be4db81
removed a parameter
nitneuqr Nov 20, 2024
c398b60
took comments into account
nitneuqr Nov 21, 2024
cde2c71
removed unused import
nitneuqr Nov 21, 2024
8ef2ac0
added first unwrap corrections
nitneuqr Nov 24, 2024
4ba77de
no more unwrap for parameter checks
nitneuqr Nov 24, 2024
47e5004
removing headers is Python now
nitneuqr Nov 25, 2024
0f39f75
final corrections?
nitneuqr Nov 26, 2024
4cd74b8
first version of documentation
nitneuqr Nov 26, 2024
115eada
corrected doctests
nitneuqr Nov 26, 2024
8c8523b
better indentation
nitneuqr Nov 26, 2024
7b8a7c9
doctest: added RSA private key
nitneuqr Nov 26, 2024
d521ebd
oops
nitneuqr Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Changelog
* Added support for :class:`~cryptography.hazmat.primitives.kdf.argon2.Argon2id`
when using OpenSSL 3.2.0+.
* Added support for the :class:`~cryptography.x509.Admissions` certificate extension.
* Added basic support for PKCS7 decryption (including S/MIME 3.2) via
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_der`,
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_pem`, and
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_smime`.

.. _v43-0-3:

Expand Down
3 changes: 3 additions & 0 deletions docs/development/test-vectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,9 @@ Custom PKCS7 Test Vectors
* ``pkcs7/enveloped-rsa-oaep.pem``- A PEM encoded PKCS7 file with
enveloped data, with key encrypted using RSA-OAEP, under the public key of
``x509/custom/ca/rsa_ca.pem``.
* ``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``.

Custom OpenSSH Test Vectors
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
247 changes: 236 additions & 11 deletions docs/hazmat/primitives/asymmetric/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1001,11 +1001,6 @@ PKCS7 is a format described in :rfc:`2315`, among other specifications. It can
contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
``p7m``, or ``p7s`` file suffix but other suffixes are also seen in the wild.

.. note::

``cryptography`` only supports parsing certificates from PKCS7 files at
this time.

.. data:: PKCS7HashTypes

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

ca_key_rsa = b"""
-----BEGIN PRIVATE KEY-----
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
fDWXtBuybAb5u+ZaVwHgqaaPCkKkVQ==
-----END PRIVATE KEY-----
""".strip()

.. class:: PKCS7SignatureBuilder

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

:returns bytes: The enveloped PKCS7 message.

.. function:: pkcs7_decrypt_der(data, certificate, private_key, options)

.. versionadded:: 44.0.0

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import serialization
>>> from cryptography.hazmat.primitives.serialization import pkcs7
>>> cert = x509.load_pem_x509_certificate(ca_cert_rsa)
>>> key = serialization.load_pem_private_key(ca_key_rsa, None)
>>> options = [pkcs7.PKCS7Options.Text]
>>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data(
... b"data to encrypt"
... ).add_recipient(
... cert
... ).encrypt(
... serialization.Encoding.DER, options
... )
>>> pkcs7.pkcs7_decrypt_der(enveloped, cert, key, options)
b'data to encrypt'

Deserialize and decrypt a DER-encoded PKCS7 message. PKCS7 (or S/MIME) has multiple versions,
but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2.

:param data: The data, encoded in DER format.
:type data: bytes

:param certificate: A :class:`~cryptography.x509.Certificate` for an intended
recipient of the encrypted message. Only certificates with public RSA keys
are currently supported.

:param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`
associated with the certificate provided. Only private RSA keys are supported.

:param options: A list of
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For
this operation only
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported.

:returns bytes: The decrypted message.

:raises ValueError: If the recipient certificate does not match any of the encrypted keys in the
PKCS7 data.

:raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted
with another algorithm than RSA with PKCS1 v1.5 padding.

:raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with
another algorithm than AES-128-CBC.

:raises ValueError: If the PKCS7 data does not contain encrypted content.

:raises ValueError: If the PKCS7 data is not of the enveloped data type.

.. function:: pkcs7_decrypt_pem(data, certificate, private_key, options)

.. versionadded:: 44.0.0

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import serialization
>>> from cryptography.hazmat.primitives.serialization import pkcs7
>>> cert = x509.load_pem_x509_certificate(ca_cert_rsa)
>>> key = serialization.load_pem_private_key(ca_key_rsa, None)
>>> options = [pkcs7.PKCS7Options.Text]
>>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data(
... b"data to encrypt"
... ).add_recipient(
... cert
... ).encrypt(
... serialization.Encoding.PEM, options
... )
>>> pkcs7.pkcs7_decrypt_pem(enveloped, cert, key, options)
b'data to encrypt'

Deserialize and decrypt a PEM-encoded PKCS7E message. PKCS7 (or S/MIME) has multiple versions,
but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2.

:param data: The data, encoded in PEM format.
:type data: bytes

:param certificate: A :class:`~cryptography.x509.Certificate` for an intended
recipient of the encrypted message. Only certificates with public RSA keys
are currently supported.

:param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`
associated with the certificate provided. Only private RSA keys are supported.

:param options: A list of
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For
this operation only
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported.

:returns bytes: The decrypted message.

:raises ValueError: If the PEM data does not have the PKCS7 tag.

:raises ValueError: If the recipient certificate does not match any of the encrypted keys in the
PKCS7 data.

:raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted
with another algorithm than RSA with PKCS1 v1.5 padding.

:raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with
another algorithm than AES-128-CBC.

:raises ValueError: If the PKCS7 data does not contain encrypted content.

:raises ValueError: If the PKCS7 data is not of the enveloped data type.

.. function:: pkcs7_decrypt_smime(data, certificate, private_key, options)

.. versionadded:: 44.0.0

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import serialization
>>> from cryptography.hazmat.primitives.serialization import pkcs7
>>> cert = x509.load_pem_x509_certificate(ca_cert_rsa)
>>> key = serialization.load_pem_private_key(ca_key_rsa, None)
>>> options = [pkcs7.PKCS7Options.Text]
>>> enveloped = pkcs7.PKCS7EnvelopeBuilder().set_data(
... b"data to encrypt"
... ).add_recipient(
... cert
... ).encrypt(
... serialization.Encoding.SMIME, options
... )
>>> pkcs7.pkcs7_decrypt_smime(enveloped, cert, key, options)
b'data to encrypt'

Deserialize and decrypt a S/MIME-encoded PKCS7 message. PKCS7 (or S/MIME) has multiple versions,
but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2.

:param data: The data. It should be in S/MIME format, meaning MIME with content type
``application/pkcs7-mime`` or ``application/x-pkcs7-mime``.
:type data: bytes

:param certificate: A :class:`~cryptography.x509.Certificate` for an intended
recipient of the encrypted message. Only certificates with public RSA keys
are currently supported.

:param private_key: The :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`
associated with the certificate provided. Only private RSA keys are supported.

:param options: A list of
:class:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options`. For
this operation only
:attr:`~cryptography.hazmat.primitives.serialization.pkcs7.PKCS7Options.Text` is supported.

:returns bytes: The decrypted message.

:raises ValueError: If the S/MIME data is not one of the correct content types.

:raises ValueError: If the recipient certificate does not match any of the encrypted keys in the
PKCS7 data.

:raises cryptography.exceptions.UnsupportedAlgorithm: If any of the PKCS7 keys are encrypted
with another algorithm than RSA with PKCS1 v1.5 padding.

:raises cryptography.exceptions.UnsupportedAlgorithm: If the content is encrypted with
another algorithm than AES-128-CBC.

:raises ValueError: If the PKCS7 data does not contain encrypted content.

:raises ValueError: If the PKCS7 data is not of the enveloped data type.


.. class:: PKCS7Options

.. versionadded:: 3.2

An enumeration of options for PKCS7 signature and envelope creation.
An enumeration of options for PKCS7 signature, envelope creation, and decryption.

.. attribute:: Text

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

.. attribute:: Binary

Signing normally converts line endings (LF to CRLF). When
passing this option the data will not be converted.
Signature and envelope creation normally converts line endings (LF to CRLF). When
passing this option, the data will not be converted.

.. attribute:: DetachedSignature

Expand Down
19 changes: 19 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/pkcs7.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import typing

from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import pkcs7

def serialize_certificates(
Expand All @@ -22,6 +23,24 @@ def sign_and_serialize(
encoding: serialization.Encoding,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def decrypt_der(
data: bytes,
certificate: x509.Certificate,
private_key: rsa.RSAPrivateKey,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def decrypt_pem(
data: bytes,
certificate: x509.Certificate,
private_key: rsa.RSAPrivateKey,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def decrypt_smime(
data: bytes,
certificate: x509.Certificate,
private_key: rsa.RSAPrivateKey,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def load_pem_pkcs7_certificates(
data: bytes,
) -> list[x509.Certificate]: ...
Expand Down
7 changes: 0 additions & 7 deletions src/cryptography/hazmat/bindings/_rust/test_support.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,6 @@ class TestCertificate:
subject_value_tags: list[int]

def test_parse_certificate(data: bytes) -> TestCertificate: ...
def pkcs7_decrypt(
encoding: serialization.Encoding,
msg: bytes,
pkey: serialization.pkcs7.PKCS7PrivateKeyTypes,
cert_recipient: x509.Certificate,
options: list[pkcs7.PKCS7Options],
) -> bytes: ...
def pkcs7_verify(
encoding: serialization.Encoding,
sig: bytes,
Expand Down
33 changes: 33 additions & 0 deletions src/cryptography/hazmat/primitives/serialization/pkcs7.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ def encrypt(
return rust_pkcs7.encrypt_and_serialize(self, encoding, options)


pkcs7_decrypt_der = rust_pkcs7.decrypt_der
pkcs7_decrypt_pem = rust_pkcs7.decrypt_pem
pkcs7_decrypt_smime = rust_pkcs7.decrypt_smime


def _smime_signed_encode(
data: bytes, signature: bytes, micalg: str, text_mode: bool
) -> bytes:
Expand Down Expand Up @@ -328,6 +333,34 @@ def _smime_enveloped_encode(data: bytes) -> bytes:
return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0))


def _smime_enveloped_decode(data: bytes) -> bytes:
m = email.message_from_bytes(data)
if m.get_content_type() not in {
"application/x-pkcs7-mime",
"application/pkcs7-mime",
}:
raise ValueError("Not an S/MIME enveloped message")
return bytes(m.get_payload(decode=True))


def _smime_remove_text_headers(data: bytes) -> bytes:
m = email.message_from_bytes(data)
# Using get() instead of get_content_type() since it has None as default,
# where the latter has "text/plain". Both methods are case-insensitive.
content_type = m.get("content-type")
if content_type is None:
raise ValueError(
"Decrypted MIME data has no 'Content-Type' header. "
"Please remove the 'Text' option to parse it manually."
)
if "text/plain" not in content_type:
raise ValueError(
f"Decrypted MIME data content type is '{content_type}', not "
"'text/plain'. Remove the 'Text' option to parse it manually."
)
return bytes(m.get_payload(decode=True))


class OpenSSLMimePart(email.message.MIMEPart):
# A MIMEPart subclass that replicates OpenSSL's behavior of not including
# a newline if there are no headers.
Expand Down
Loading