Skip to content

Add support for verifying S/MIME messages #12267

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

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ Changelog
* Removed the deprecated ``CAST5``, ``SEED``, ``IDEA``, and ``Blowfish``
classes from the cipher module. These are still available in
:doc:`/hazmat/decrepit/index`.
* Added support for PKCS7 decryption & encryption using AES-256 as content algorithm,
in addition to AES-128.
* Added basic support for PKCS7 verification (including S/MIME 3.2) via
:func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_verify_der`,
:func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_verify_pem`, and
:func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_verify_smime`.

.. _v45-0-4:

Expand Down
9 changes: 9 additions & 0 deletions docs/development/test-vectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,15 @@ 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`.
* ``pkcs7/signed-opaque.msg``- A PKCS7 signed message, signed using opaque
signing (``application/pkcs7-mime`` content type), signed under the
private key of ``x509/custom/ca/ca.pem``, ``x509/custom/ca/ca_key.pem``.

Custom OpenSSH Test Vectors
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
167 changes: 167 additions & 0 deletions docs/hazmat/primitives/asymmetric/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,28 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
-----END PRIVATE KEY-----
""".strip()

verify_cert = b"""
-----BEGIN CERTIFICATE-----
MIIBhjCCASygAwIBAgICAwkwCgYIKoZIzj0EAwIwJzELMAkGA1UEBhMCVVMxGDAW
BgNVBAMMD2NyeXB0b2dyYXBoeSBDQTAgFw0xNzAxMDEwMTAwMDBaGA8yMTAwMDEw
MTAwMDAwMFowJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeSBD
QTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBj/z7v5Obj13cPuwECLBnUGq0/N
2CxSJE4f4BBGZ7VfFblivTvPDG++Gve0oQ+0uctuhrNQ+WxRv8GC177F+QWjRjBE
MCEGA1UdEQEB/wQXMBWBE2V4YW1wbGVAZXhhbXBsZS5jb20wHwYDVR0jBBgwFoAU
/Ou02BLyyT2Zwzxn9H03feYT7fowCgYIKoZIzj0EAwIDSAAwRQIgUwIdC0Emkd6f
17DeOXTlmTAhwSDJ2FTuyHESwei7wJcCIQCnr9NpBxbtJfEzxHGGyd7PxgpOLi5u
rk+8QfzGMmg/fw==
-----END CERTIFICATE-----
""".strip()

verify_key = b"""
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgA8Zqz5vLeR0ePZUe
jBfdyMmnnI4U5uAJApWTsMn/RuWhRANCAAQY/8+7+Tm49d3D7sBAiwZ1BqtPzdgs
UiROH+AQRme1XxW5Yr07zwxvvhr3tKEPtLnLboazUPlsUb/Bgte+xfkF
-----END PRIVATE KEY-----
""".strip()

.. class:: PKCS7SignatureBuilder

The PKCS7 signature builder can create both basic PKCS7 signed messages as
Expand Down Expand Up @@ -1340,6 +1362,150 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
:returns bytes: The signed PKCS7 message.


.. function:: pkcs7_verify_der(data, content=None, certificate=None, options=None)

.. versionadded:: 45.0.0

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import hashes, serialization
>>> from cryptography.hazmat.primitives.serialization import pkcs7
>>> cert = x509.load_pem_x509_certificate(verify_cert)
>>> key = serialization.load_pem_private_key(verify_key, None)
>>> signed = pkcs7.PKCS7SignatureBuilder().set_data(
... b"data to sign"
... ).add_signer(
... cert, key, hashes.SHA256()
... ).sign(
... serialization.Encoding.DER, []
... )
>>> pkcs7.pkcs7_verify_der(signed)

Deserialize and verify a DER-encoded PKCS7 signed message. PKCS7 (or S/MIME) has multiple
versions, but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2. If the
verification succeeds, does not return anything. If the verification fails, raises an exception.

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

:param content: if specified, the content to verify against the signed message. If the content
is not specified, the function will look for the content in the signed message. Defaults to
None.
:type content: bytes or None

:param certificate: if specified, a :class:`~cryptography.x509.Certificate` to verify against
the signed message. If None, the function will look for the signer certificate in the signed
message. Defaults to None.
:type certificate: :class:`~cryptography.x509.Certificate` or None

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

:raises ValueError: If no content is specified and no content is found in the PKCS7 data.

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


.. function:: pkcs7_verify_pem(data, content=None, certificate=None, options=None)

.. versionadded:: 45.0.0

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import hashes, serialization
>>> from cryptography.hazmat.primitives.serialization import pkcs7
>>> cert = x509.load_pem_x509_certificate(verify_cert)
>>> key = serialization.load_pem_private_key(verify_key, None)
>>> signed = pkcs7.PKCS7SignatureBuilder().set_data(
... b"data to sign"
... ).add_signer(
... cert, key, hashes.SHA256()
... ).sign(
... serialization.Encoding.PEM, []
... )
>>> pkcs7.pkcs7_verify_pem(signed)

Deserialize and verify a PEM-encoded PKCS7 signed message. PKCS7 (or S/MIME) has multiple
versions, but this supports a subset of :rfc:`5751`, also known as S/MIME Version 3.2. If the
verification succeeds, does not return anything. If the verification fails, raises an exception.

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

:param content: if specified, the content to verify against the signed message. If the content
is not specified, the function will look for the content in the signed message. Defaults to
None.
:type content: bytes or None

:param certificate: if specified, a :class:`~cryptography.x509.Certificate` to verify against
the signed message. If None, the function will look for the signer certificate in the signed
message. Defaults to None.
:type certificate: :class:`~cryptography.x509.Certificate` or None

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

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

:raises ValueError: If no content is specified and no content is found in the PKCS7 data.

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


.. function:: pkcs7_verify_smime(data, content=None, certificate=None, options=None)

.. versionadded:: 45.0.0

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import hashes, serialization
>>> from cryptography.hazmat.primitives.serialization import pkcs7
>>> cert = x509.load_pem_x509_certificate(verify_cert)
>>> key = serialization.load_pem_private_key(verify_key, None)
>>> signed = pkcs7.PKCS7SignatureBuilder().set_data(
... b"data to sign"
... ).add_signer(
... cert, key, hashes.SHA256()
... ).sign(
... serialization.Encoding.SMIME, []
... )
>>> pkcs7.pkcs7_verify_smime(signed)

Verify a PKCS7 signed message stored in a MIME message, by reading it, extracting the content
(if any) and signature, deserializing the signature and verifying it against the content. PKCS7
(or S/MIME) has multiple versions, but this supports a subset of :rfc:`5751`, also known as
S/MIME Version 3.2. If the verification succeeds, does not return anything. If the verification
fails, raises an exception.

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

:param content: if specified, the content to verify against the signed message. If the content
is not specified, the function will look for the content in the MIME message and in the
signature. Defaults to None.
:type content: bytes or None

:param certificate: if specified, a :class:`~cryptography.x509.Certificate` to verify against
the signed message. If None, the function will look for the signer certificate in the signed
message. Defaults to None.
:type certificate: :class:`~cryptography.x509.Certificate` or None

:raises ValueError: If the MIME message is not a S/MIME signed message: content type is
different than ``multipart/signed`` or ``application/pkcs7-mime``.

:raises ValueError: If the MIME message is a malformed ``multipart/signed`` S/MIME message: not
multipart, or multipart with more than 2 parts (content & signature).

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

:raises ValueError: If no content is specified and no content is found in the PKCS7 data.

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

.. class:: PKCS7EnvelopeBuilder

The PKCS7 envelope builder can create encrypted S/MIME messages,
Expand Down Expand Up @@ -1633,6 +1799,7 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
obtain the signer's certificate by other means (for example from a
previously signed message).


Serialization Formats
~~~~~~~~~~~~~~~~~~~~~

Expand Down
25 changes: 20 additions & 5 deletions src/cryptography/hazmat/bindings/_rust/pkcs7.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ def encrypt_and_serialize(
encoding: serialization.Encoding,
options: Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def sign_and_serialize(
builder: pkcs7.PKCS7SignatureBuilder,
encoding: serialization.Encoding,
options: Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def decrypt_der(
data: bytes,
certificate: x509.Certificate,
Expand All @@ -42,6 +37,26 @@ def decrypt_smime(
private_key: rsa.RSAPrivateKey,
options: Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def sign_and_serialize(
builder: pkcs7.PKCS7SignatureBuilder,
encoding: serialization.Encoding,
options: Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def verify_der(
signature: bytes,
content: bytes | None = None,
certificate: x509.Certificate | None = None,
) -> None: ...
def verify_pem(
signature: bytes,
content: bytes | None = None,
certificate: x509.Certificate | None = None,
) -> None: ...
def verify_smime(
signature: bytes,
content: bytes | None = None,
certificate: x509.Certificate | None = None,
) -> None: ...
def load_pem_pkcs7_certificates(
data: bytes,
) -> list[x509.Certificate]: ...
Expand Down
Loading