mirror of
https://github.com/saymrwulf/cryptography.git
synced 2026-05-14 20:37:55 +00:00
PKCS7: added encryption with AES-256-CBC (#12172)
* feat: added encryption with AES-256-CBC added & updated tests accordingly updated documentation removed useless test vector * fixing coverage * last python coverage fix * restraining the number of classes changed name to content_encryption_algorithm simplified the rust code accordingly tried to simplify the documentation * python 3.8 artefacts * passed content encryption algo locally adapted rust code accordingly
This commit is contained in:
parent
97b388a0a9
commit
6143683d87
8 changed files with 104 additions and 33 deletions
|
|
@ -10,7 +10,8 @@ Changelog
|
|||
|
||||
* Support for Python 3.7 is deprecated and will be removed in the next
|
||||
``cryptography`` release.
|
||||
|
||||
* Added support for PKCS7 decryption & encryption using AES-256 as content algorithm,
|
||||
in addition to AES-128.
|
||||
|
||||
.. _v44-0-0:
|
||||
|
||||
|
|
|
|||
|
|
@ -878,9 +878,6 @@ Custom PKCS7 Test Vectors
|
|||
* ``pkcs7/amazon-roots.der`` - A DER encoded PCKS7 file containing Amazon Root
|
||||
CA 2 and 3 generated by OpenSSL.
|
||||
* ``pkcs7/enveloped.pem`` - A PEM encoded PKCS7 file with enveloped data.
|
||||
* ``pkcs7/enveloped-aes-256-cbc.pem`` - A PEM encoded PKCS7 file with
|
||||
enveloped data, with content encrypted using AES-256-CBC, under the public
|
||||
key of ``x509/custom/ca/rsa_ca.pem``.
|
||||
* ``pkcs7/enveloped-triple-des.pem`` - A PEM encoded PKCS7 file with
|
||||
enveloped data, with content encrypted using DES EDE3 CBC (also called
|
||||
Triple DES), under the public key of ``x509/custom/ca/rsa_ca.pem``.
|
||||
|
|
|
|||
|
|
@ -1268,10 +1268,13 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
|
|||
>>> from cryptography import x509
|
||||
>>> from cryptography.hazmat.primitives import serialization
|
||||
>>> from cryptography.hazmat.primitives.serialization import pkcs7
|
||||
>>> from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
>>> cert = x509.load_pem_x509_certificate(ca_cert_rsa)
|
||||
>>> options = [pkcs7.PKCS7Options.Text]
|
||||
>>> pkcs7.PKCS7EnvelopeBuilder().set_data(
|
||||
... b"data to encrypt"
|
||||
... ).set_content_encryption_algorithm(
|
||||
... algorithms.AES128
|
||||
... ).add_recipient(
|
||||
... cert
|
||||
... ).encrypt(
|
||||
|
|
@ -1284,6 +1287,14 @@ contain certificates, CRLs, and much more. PKCS7 files commonly have a ``p7b``,
|
|||
:param data: The data to be encrypted.
|
||||
:type data: :term:`bytes-like`
|
||||
|
||||
.. method:: set_content_encryption_algorithm(content_encryption_algorithm)
|
||||
|
||||
:param content_encryption_algorithm: the content encryption algorithm to use.
|
||||
Only AES is supported, with a key size of 128 or 256 bits.
|
||||
:type content_encryption_algorithm:
|
||||
:class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES128`
|
||||
or :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES256`
|
||||
|
||||
.. method:: add_recipient(certificate)
|
||||
|
||||
Add a recipient for the message. Recipients will be able to use their private keys
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ def serialize_certificates(
|
|||
) -> bytes: ...
|
||||
def encrypt_and_serialize(
|
||||
builder: pkcs7.PKCS7EnvelopeBuilder,
|
||||
content_encryption_algorithm: pkcs7.ContentEncryptionAlgorithm,
|
||||
encoding: serialization.Encoding,
|
||||
options: typing.Iterable[pkcs7.PKCS7Options],
|
||||
) -> bytes: ...
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
|||
from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
|
||||
from cryptography.hazmat.primitives.ciphers import (
|
||||
algorithms,
|
||||
)
|
||||
from cryptography.utils import _check_byteslike
|
||||
|
||||
load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates
|
||||
|
|
@ -35,6 +38,10 @@ PKCS7PrivateKeyTypes = typing.Union[
|
|||
rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey
|
||||
]
|
||||
|
||||
ContentEncryptionAlgorithm = typing.Union[
|
||||
typing.Type[algorithms.AES128], typing.Type[algorithms.AES256]
|
||||
]
|
||||
|
||||
|
||||
class PKCS7Options(utils.Enum):
|
||||
Text = "Add text/plain MIME type"
|
||||
|
|
@ -184,6 +191,8 @@ class PKCS7EnvelopeBuilder:
|
|||
*,
|
||||
_data: bytes | None = None,
|
||||
_recipients: list[x509.Certificate] | None = None,
|
||||
_content_encryption_algorithm: ContentEncryptionAlgorithm
|
||||
| None = None,
|
||||
):
|
||||
from cryptography.hazmat.backends.openssl.backend import (
|
||||
backend as ossl,
|
||||
|
|
@ -197,13 +206,18 @@ class PKCS7EnvelopeBuilder:
|
|||
)
|
||||
self._data = _data
|
||||
self._recipients = _recipients if _recipients is not None else []
|
||||
self._content_encryption_algorithm = _content_encryption_algorithm
|
||||
|
||||
def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder:
|
||||
_check_byteslike("data", data)
|
||||
if self._data is not None:
|
||||
raise ValueError("data may only be set once")
|
||||
|
||||
return PKCS7EnvelopeBuilder(_data=data, _recipients=self._recipients)
|
||||
return PKCS7EnvelopeBuilder(
|
||||
_data=data,
|
||||
_recipients=self._recipients,
|
||||
_content_encryption_algorithm=self._content_encryption_algorithm,
|
||||
)
|
||||
|
||||
def add_recipient(
|
||||
self,
|
||||
|
|
@ -221,6 +235,24 @@ class PKCS7EnvelopeBuilder:
|
|||
*self._recipients,
|
||||
certificate,
|
||||
],
|
||||
_content_encryption_algorithm=self._content_encryption_algorithm,
|
||||
)
|
||||
|
||||
def set_content_encryption_algorithm(
|
||||
self, content_encryption_algorithm: ContentEncryptionAlgorithm
|
||||
) -> PKCS7EnvelopeBuilder:
|
||||
if self._content_encryption_algorithm is not None:
|
||||
raise ValueError("Content encryption algo may only be set once")
|
||||
if content_encryption_algorithm not in {
|
||||
algorithms.AES128,
|
||||
algorithms.AES256,
|
||||
}:
|
||||
raise TypeError("Only AES128 and AES256 are supported")
|
||||
|
||||
return PKCS7EnvelopeBuilder(
|
||||
_data=self._data,
|
||||
_recipients=self._recipients,
|
||||
_content_encryption_algorithm=content_encryption_algorithm,
|
||||
)
|
||||
|
||||
def encrypt(
|
||||
|
|
@ -232,6 +264,13 @@ class PKCS7EnvelopeBuilder:
|
|||
raise ValueError("Must have at least one recipient")
|
||||
if self._data is None:
|
||||
raise ValueError("You must add data to encrypt")
|
||||
|
||||
# The default content encryption algorithm is AES-128, which the S/MIME
|
||||
# v3.2 RFC specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7)
|
||||
content_encryption_algorithm = (
|
||||
self._content_encryption_algorithm or algorithms.AES128
|
||||
)
|
||||
|
||||
options = list(options)
|
||||
if not all(isinstance(x, PKCS7Options) for x in options):
|
||||
raise ValueError("options must be from the PKCS7Options enum")
|
||||
|
|
@ -260,7 +299,9 @@ class PKCS7EnvelopeBuilder:
|
|||
"Cannot use Binary and Text options at the same time"
|
||||
)
|
||||
|
||||
return rust_pkcs7.encrypt_and_serialize(self, encoding, options)
|
||||
return rust_pkcs7.encrypt_and_serialize(
|
||||
self, content_encryption_algorithm, encoding, options
|
||||
)
|
||||
|
||||
|
||||
pkcs7_decrypt_der = rust_pkcs7.decrypt_der
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ fn serialize_certificates<'p>(
|
|||
fn encrypt_and_serialize<'p>(
|
||||
py: pyo3::Python<'p>,
|
||||
builder: &pyo3::Bound<'p, pyo3::PyAny>,
|
||||
content_encryption_algorithm: &pyo3::Bound<'p, pyo3::PyAny>,
|
||||
encoding: &pyo3::Bound<'p, pyo3::PyAny>,
|
||||
options: &pyo3::Bound<'p, pyo3::types::PyList>,
|
||||
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
|
||||
|
|
@ -95,14 +96,24 @@ fn encrypt_and_serialize<'p>(
|
|||
smime_canonicalize(raw_data.as_bytes(), text_mode).0
|
||||
};
|
||||
|
||||
// The message is encrypted with AES-128-CBC, which the S/MIME v3.2 RFC
|
||||
// specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7)
|
||||
let key = types::OS_URANDOM.get(py)?.call1((16,))?;
|
||||
let aes128_algorithm = types::AES128.get(py)?.call1((&key,))?;
|
||||
// Get the content encryption algorithm
|
||||
let content_encryption_algorithm_type = content_encryption_algorithm;
|
||||
let key_size = content_encryption_algorithm_type.getattr(pyo3::intern!(py, "key_size"))?;
|
||||
let key = types::OS_URANDOM
|
||||
.get(py)?
|
||||
.call1((key_size.floor_div(8)?,))?;
|
||||
let content_encryption_algorithm = content_encryption_algorithm_type.call1((&key,))?;
|
||||
|
||||
// Get the mode
|
||||
let iv = types::OS_URANDOM.get(py)?.call1((16,))?;
|
||||
let cbc_mode = types::CBC.get(py)?.call1((&iv,))?;
|
||||
|
||||
let encrypted_content = symmetric_encrypt(py, aes128_algorithm, cbc_mode, &data_with_header)?;
|
||||
let encrypted_content = symmetric_encrypt(
|
||||
py,
|
||||
content_encryption_algorithm,
|
||||
cbc_mode,
|
||||
&data_with_header,
|
||||
)?;
|
||||
|
||||
let py_recipients: Vec<pyo3::Bound<'p, x509::certificate::Certificate>> = builder
|
||||
.getattr(pyo3::intern!(py, "_recipients"))?
|
||||
|
|
@ -133,6 +144,13 @@ fn encrypt_and_serialize<'p>(
|
|||
});
|
||||
}
|
||||
|
||||
// Prepare the algorithm parameters
|
||||
let algorithm_parameters = if content_encryption_algorithm_type.eq(types::AES128.get(py)?)? {
|
||||
AlgorithmParameters::Aes128Cbc(iv.extract()?)
|
||||
} else {
|
||||
AlgorithmParameters::Aes256Cbc(iv.extract()?)
|
||||
};
|
||||
|
||||
let enveloped_data = pkcs7::EnvelopedData {
|
||||
version: 0,
|
||||
recipient_infos: common::Asn1ReadableOrWritable::new_write(asn1::SetOfWriter::new(
|
||||
|
|
@ -143,7 +161,7 @@ fn encrypt_and_serialize<'p>(
|
|||
content_type: PKCS7_DATA_OID,
|
||||
content_encryption_algorithm: AlgorithmIdentifier {
|
||||
oid: asn1::DefinedByMarker::marker(),
|
||||
params: AlgorithmParameters::Aes128Cbc(iv.extract()?),
|
||||
params: algorithm_parameters,
|
||||
},
|
||||
encrypted_content: Some(&encrypted_content),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from cryptography.exceptions import _Reasons
|
|||
from cryptography.hazmat.bindings._rust import test_support
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.serialization import pkcs7
|
||||
from tests.x509.test_x509 import _generate_ca_and_leaf
|
||||
|
||||
|
|
@ -899,6 +900,21 @@ class TestPKCS7EnvelopeBuilder:
|
|||
b"notacert", # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def test_set_content_encryption_algorithm_twice(self, backend):
|
||||
builder = pkcs7.PKCS7EnvelopeBuilder()
|
||||
builder = builder.set_content_encryption_algorithm(algorithms.AES128)
|
||||
with pytest.raises(ValueError):
|
||||
builder.set_content_encryption_algorithm(algorithms.AES128)
|
||||
|
||||
def test_invalid_content_encryption_algorithm(self, backend):
|
||||
class InvalidAlgorithm:
|
||||
pass
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
pkcs7.PKCS7EnvelopeBuilder().set_content_encryption_algorithm(
|
||||
InvalidAlgorithm, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def test_encrypt_invalid_options(self, backend):
|
||||
cert, _ = _load_rsa_cert_key()
|
||||
builder = (
|
||||
|
|
@ -1151,12 +1167,14 @@ class TestPKCS7Decrypt:
|
|||
def test_pkcs7_decrypt_aes_256_cbc_encrypted_content(
|
||||
self, backend, data, certificate, private_key
|
||||
):
|
||||
# Loading encrypted content (for now, not possible natively)
|
||||
enveloped = load_vectors_from_file(
|
||||
os.path.join("pkcs7", "enveloped-aes-256-cbc.pem"),
|
||||
loader=lambda pemfile: pemfile.read(),
|
||||
mode="rb",
|
||||
# Encryption
|
||||
builder = (
|
||||
pkcs7.PKCS7EnvelopeBuilder()
|
||||
.set_data(data)
|
||||
.set_content_encryption_algorithm(algorithms.AES256)
|
||||
.add_recipient(certificate)
|
||||
)
|
||||
enveloped = builder.encrypt(serialization.Encoding.PEM, [])
|
||||
|
||||
# Test decryption: new lines are canonicalized to '\r\n' when
|
||||
# encryption has no Binary option
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
-----BEGIN PKCS7-----
|
||||
MIICmwYJKoZIhvcNAQcDoIICjDCCAogCAQAxggJDMIICPwIBADAnMBoxGDAWBgNV
|
||||
BAMMD2NyeXB0b2dyYXBoeSBDQQIJAOcS06ClbtbJMA0GCSqGSIb3DQEBAQUABIIC
|
||||
ACTeTHyg8zwnBdhLFogSBMInoAqc8HHZ+3vRN57MJ9UA4MIkqgrUEMg2sYwNkpuS
|
||||
pT3B0tw3CbrJwL4SemPul1FuYMluTRdhJuI9wskR9BvE6d+BlmnFSjNGdt1y9RM+
|
||||
7ZqViXGA2t2HVRQ42Q43tkDUL7gMzveYZ1LxG1d+GNbfKLHVqJLokIe+IQYtyRay
|
||||
3Tck7l/cC2VpI9lwmF+DugpZbagmb3pSij/ZSzzub3PwNp4YaL2YSa1Vkswdm3LD
|
||||
jhOMSKyw7jIn2e9gQ3VI8vzh/38OFFFoKq7sAGvNGSLDbCHm6AKvOylksnTCUBF2
|
||||
6mbNWaaNpRjCQU+8N5/1UblJAs/voG+hGuWbGjS6z4v6mYvIr5731rQjxYbIpZRT
|
||||
B6+lu9sCbwHuYQKe8MBlsn0+Y/o7l25m+xOfeRK1UGViUNV+2G2SQKY2CnfBoPis
|
||||
lZSwKv1mfYifT1bsVyTsDWi0yr3BdbhVRI4pLziNrMFJ5tJhN2Y8HB2FGLlmzJtM
|
||||
YRyljlMtj3YrYnhX82dKIwlrLfoWYP90tiiGh3DlqUTVCj4Y/IBmFGF6VpKWYZ0F
|
||||
1VGwR8dDt0a0IonoBo3T4OtqUStlMkWgwGyNlauZnXt4jHoP5ECZ23TLpAtLCgUE
|
||||
BuTiSXYFHaz+ToomhzTqrqznhLf9PRV+TM96/66xYdSYMDwGCSqGSIb3DQEHATAd
|
||||
BglghkgBZQMEASoEEFSk9vw7RRWfjkB3sVedCgqAEPYXgbXvcA4rj2DCHA80Etg=
|
||||
-----END PKCS7-----
|
||||
Loading…
Reference in a new issue