Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Changelog

.. note:: This version is not yet released and is under active development.

* Added support for serialization of PKCS#12 Java truststores in
:func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_java_truststore`
* Support for Python 3.7 is deprecated and will be removed in the next
``cryptography`` release.
* Added support for PKCS7 decryption and encryption using AES-256 as the
Expand Down
4 changes: 4 additions & 0 deletions docs/development/test-vectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,10 @@ Custom PKCS12 Test Vectors
certs (``x509/cryptography.io.pem`` and ``x509/letsencryptx3.pem``)
with friendly names ``☹`` and ``ï``, respectively, encrypted via
AES 256 CBC with the password ``cryptography``.
* ``pkcs12/java-truststore.p12`` - A PKCS12 file containing two certs
(``x509/custom/dsa_selfsigned_ca.pem`` and ``x509/letsencryptx3.pem``) with
the first having a friendly name of `cert1`. Both have Java truststore
attributes with ANY_EXTENDED_KEY_USAGE.

Custom PKCS7 Test Vectors
~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
36 changes: 36 additions & 0 deletions docs/hazmat/primitives/asymmetric/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,42 @@ file suffix.
... b"friendlyname", key, cert, None, encryption
... )

.. function:: serialize_java_truststore(certs, encryption_algorithm)

.. versionadded:: 45.0.0

.. warning::

PKCS12 encryption is typically not secure and should not be used as a
security mechanism. Wrap a PKCS12 blob in a more secure envelope if you
need to store or send it safely.

Serialize a PKCS12 blob containing provided certificates. Java expects an
internal flag to denote truststore usage, which this function adds.

:param certs: A set of certificates to also include in the structure.
:type certs:

A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate`
instances.

:param encryption_algorithm: The encryption algorithm that should be used
for the key and certificate. An instance of an object conforming to the
:class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption`
interface. PKCS12 encryption is typically **very weak** and should not
be used as a security boundary.

:return bytes: Serialized PKCS12.

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, pkcs12
>>> cert = x509.load_pem_x509_certificate(ca_cert)
>>> p12 = pkcs12.serialize_java_truststore(
... [pkcs12.PKCS12Certificate(cert, b"friendlyname")], BestAvailableEncryption(b"password")
... )

.. class:: PKCS12Certificate

.. versionadded:: 36.0.0
Expand Down
2 changes: 2 additions & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ TLS
toolchain
totient
Trixie
truststore
truststores
tunable
Ubuntu
unencrypted
Expand Down
4 changes: 4 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/pkcs12.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def load_pkcs12(
password: bytes | None,
backend: typing.Any = None,
) -> PKCS12KeyAndCertificates: ...
def serialize_java_truststore(
certs: Iterable[PKCS12Certificate],
encryption_algorithm: KeySerializationEncryption,
) -> bytes: ...
def serialize_key_and_certificates(
name: bytes | None,
key: PKCS12PrivateKeyTypes | None,
Expand Down
19 changes: 19 additions & 0 deletions src/cryptography/hazmat/primitives/serialization/pkcs12.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"PKCS12PrivateKeyTypes",
"load_key_and_certificates",
"load_pkcs12",
"serialize_java_truststore",
"serialize_key_and_certificates",
]

Expand Down Expand Up @@ -119,6 +120,24 @@ def __repr__(self) -> str:
]


def serialize_java_truststore(
certs: Iterable[PKCS12Certificate],
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
if not certs:
raise ValueError("You must supply at least one cert")

if not isinstance(
encryption_algorithm, serialization.KeySerializationEncryption
):
raise TypeError(
"Key encryption algorithm must be a "
"KeySerializationEncryption instance"
)

return rust_pkcs12.serialize_java_truststore(certs, encryption_algorithm)


def serialize_key_and_certificates(
name: bytes | None,
key: PKCS12PrivateKeyTypes | None,
Expand Down
7 changes: 7 additions & 0 deletions src/rust/cryptography-x509/src/pkcs12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.

use asn1::ObjectIdentifier;

use crate::common::Utf8StoredBMPString;
use crate::{pkcs7, pkcs8};

Expand All @@ -12,6 +14,8 @@ pub const SHROUDED_KEY_BAG_OID: asn1::ObjectIdentifier =
pub const X509_CERTIFICATE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 22, 1);
pub const FRIENDLY_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 20);
pub const LOCAL_KEY_ID_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 21);
pub const JDK_TRUSTSTORE_USAGE: asn1::ObjectIdentifier =
asn1::oid!(2, 16, 840, 1, 113894, 746875, 1, 1);

#[derive(asn1::Asn1Write, asn1::Asn1Read)]
pub struct Pfx<'a> {
Expand Down Expand Up @@ -50,6 +54,9 @@ pub enum AttributeSet<'a> {

#[defined_by(LOCAL_KEY_ID_OID)]
LocalKeyId(asn1::SetOfWriter<'a, &'a [u8], [&'a [u8]; 1]>),

#[defined_by(JDK_TRUSTSTORE_USAGE)]
JDKTruststoreUsage(asn1::SetOfWriter<'a, ObjectIdentifier, [ObjectIdentifier; 1]>),
}

#[derive(asn1::Asn1DefinedByWrite)]
Expand Down
53 changes: 46 additions & 7 deletions src/rust/src/pkcs12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

use cryptography_x509::common::Utf8StoredBMPString;
use cryptography_x509::oid::EKU_ANY_KEY_USAGE_OID;
use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods};
use pyo3::{IntoPyObject, PyTypeInfo};

Expand Down Expand Up @@ -240,6 +241,7 @@ impl EncryptionAlgorithm {
fn pkcs12_attributes<'a>(
friendly_name: Option<&'a [u8]>,
local_key_id: Option<&'a [u8]>,
is_java_trusted_cert: bool,
) -> CryptographyResult<
Option<
asn1::SetOfWriter<
Expand Down Expand Up @@ -270,6 +272,14 @@ fn pkcs12_attributes<'a>(
),
});
}
if is_java_trusted_cert {
attrs.push(cryptography_x509::pkcs12::Attribute {
_attr_id: asn1::DefinedByMarker::marker(),
attr_values: cryptography_x509::pkcs12::AttributeSet::JDKTruststoreUsage(
asn1::SetOfWriter::new([EKU_ANY_KEY_USAGE_OID]),
),
});
}

if attrs.is_empty() {
Ok(None)
Expand All @@ -282,6 +292,7 @@ fn cert_to_bag<'a>(
cert: &'a Certificate,
friendly_name: Option<&'a [u8]>,
local_key_id: Option<&'a [u8]>,
is_java_trusted_cert: bool,
) -> CryptographyResult<cryptography_x509::pkcs12::SafeBag<'a>> {
Ok(cryptography_x509::pkcs12::SafeBag {
_bag_id: asn1::DefinedByMarker::marker(),
Expand All @@ -293,7 +304,7 @@ fn cert_to_bag<'a>(
)),
},
))),
attributes: pkcs12_attributes(friendly_name, local_key_id)?,
attributes: pkcs12_attributes(friendly_name, local_key_id, is_java_trusted_cert)?,
})
}

Expand Down Expand Up @@ -388,7 +399,7 @@ enum CertificateOrPKCS12Certificate {
PKCS12Certificate(pyo3::Py<PKCS12Certificate>),
}

fn serialize_bags<'p>(
fn serialize_safebags<'p>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this slipped through the cracks for the refactor PR. I'm inclined to leave it in, but it really doesn't matter either way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't matter to me

py: pyo3::Python<'p>,
safebags: &[cryptography_x509::pkcs12::SafeBag<'_>],
encryption_details: &KeySerializationEncryption<'_>,
Expand All @@ -405,6 +416,9 @@ fn serialize_bags<'p>(
);

if let Some(e) = &encryption_details.encryption_algorithm {
// When encryption is applied, safebags that have already been encrypted (ShroudedKeyBag)
// should not be encrypted again, so they are placed in their own ContentInfo.
// See RFC 7292 4.1
let mut shrouded_safebags = vec![];
let mut plain_safebags = vec![];
for safebag in safebags {
Expand Down Expand Up @@ -521,6 +535,28 @@ fn serialize_bags<'p>(
Ok(pyo3::types::PyBytes::new(py, &asn1::write_single(&p12)?))
}

#[pyo3::pyfunction]
#[pyo3(signature = (certs, encryption_algorithm))]
fn serialize_java_truststore<'p>(
py: pyo3::Python<'p>,
certs: Vec<pyo3::Py<PKCS12Certificate>>,
encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>,
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
let encryption_details = decode_encryption_algorithm(py, encryption_algorithm)?;
let mut safebags = vec![];

for cert in &certs {
safebags.push(cert_to_bag(
cert.get().certificate.get(),
cert.get().friendly_name.as_ref().map(|v| v.as_bytes(py)),
None,
true,
)?);
}

serialize_safebags(py, &safebags, &encryption_details)
}

#[pyo3::pyfunction]
#[pyo3(signature = (name, key, cert, cas, encryption_algorithm))]
fn serialize_key_and_certificates<'p>(
Expand Down Expand Up @@ -559,6 +595,7 @@ fn serialize_key_and_certificates<'p>(
cert,
name,
key_id.as_ref().map(|v| v.as_bytes()),
false,
)?);
}

Expand All @@ -570,12 +607,13 @@ fn serialize_key_and_certificates<'p>(
for cert in &ca_certs {
let bag = match cert {
CertificateOrPKCS12Certificate::Certificate(c) => {
cert_to_bag(c.get(), None, None)?
cert_to_bag(c.get(), None, None, false)?
}
CertificateOrPKCS12Certificate::PKCS12Certificate(c) => cert_to_bag(
c.get().certificate.get(),
c.get().friendly_name.as_ref().map(|v| v.as_bytes(py)),
None,
false,
)?,
};
safebags.push(bag);
Expand Down Expand Up @@ -627,7 +665,7 @@ fn serialize_key_and_certificates<'p>(
},
),
),
attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()))?,
attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()), false)?,
}
} else {
let pkcs8_tlv = asn1::parse_single(&pkcs8_bytes)?;
Expand All @@ -637,14 +675,14 @@ fn serialize_key_and_certificates<'p>(
bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::KeyBag(
pkcs8_tlv,
)),
attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()))?,
attributes: pkcs12_attributes(name, key_id.as_ref().map(|v| v.as_bytes()), false)?,
}
};

safebags.push(key_bag);
}

serialize_bags(py, &safebags, &encryption_details)
serialize_safebags(py, &safebags, &encryption_details)
}

fn decode_p12(
Expand Down Expand Up @@ -795,6 +833,7 @@ fn load_pkcs12<'p>(
pub(crate) mod pkcs12 {
#[pymodule_export]
use super::{
load_key_and_certificates, load_pkcs12, serialize_key_and_certificates, PKCS12Certificate,
load_key_and_certificates, load_pkcs12, serialize_java_truststore,
serialize_key_and_certificates, PKCS12Certificate,
};
}
Loading