From 9a682b262d1116f08df3092d5b94b63fac7bdbab Mon Sep 17 00:00:00 2001 From: crbednarz Date: Sun, 2 Feb 2025 20:06:15 -0800 Subject: [PATCH 1/5] Added support for PKCS12 Java truststores via new "serialize_java_truststore" function --- .../hazmat/bindings/_rust/pkcs12.pyi | 4 + .../hazmat/primitives/serialization/pkcs12.py | 19 ++ src/rust/cryptography-x509/src/pkcs12.rs | 7 + src/rust/src/pkcs12.rs | 58 +++++- tests/hazmat/primitives/test_pkcs12.py | 188 ++++++++++++++++++ .../pkcs12/java-truststore.p12 | Bin 0 -> 2861 bytes 6 files changed, 269 insertions(+), 7 deletions(-) create mode 100644 vectors/cryptography_vectors/pkcs12/java-truststore.p12 diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi index 1ad1479e4472..b25becb6bdef 100644 --- a/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi +++ b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi @@ -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, diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py index bab4d1eabf14..58884ff61a79 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -27,6 +27,7 @@ "PKCS12PrivateKeyTypes", "load_key_and_certificates", "load_pkcs12", + "serialize_java_truststore", "serialize_key_and_certificates", ] @@ -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, diff --git a/src/rust/cryptography-x509/src/pkcs12.rs b/src/rust/cryptography-x509/src/pkcs12.rs index b4ce81c0a345..9935d3bd9830 100644 --- a/src/rust/cryptography-x509/src/pkcs12.rs +++ b/src/rust/cryptography-x509/src/pkcs12.rs @@ -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}; @@ -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> { @@ -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)] diff --git a/src/rust/src/pkcs12.rs b/src/rust/src/pkcs12.rs index 00e6a759e2a2..42601795b96c 100644 --- a/src/rust/src/pkcs12.rs +++ b/src/rust/src/pkcs12.rs @@ -6,6 +6,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use cryptography_x509::common::Utf8StoredBMPString; +use cryptography_x509::pkcs12::JDK_TRUSTSTORE_USAGE; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use pyo3::{IntoPyObject, PyTypeInfo}; @@ -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< @@ -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([JDK_TRUSTSTORE_USAGE]), + ), + }); + } if attrs.is_empty() { Ok(None) @@ -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> { Ok(cryptography_x509::pkcs12::SafeBag { _bag_id: asn1::DefinedByMarker::marker(), @@ -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)?, }) } @@ -388,7 +399,7 @@ enum CertificateOrPKCS12Certificate { PKCS12Certificate(pyo3::Py), } -fn serialize_bags<'p>( +fn serialize_safebags<'p>( py: pyo3::Python<'p>, safebags: &[cryptography_x509::pkcs12::SafeBag<'_>], encryption_details: &KeySerializationEncryption<'_>, @@ -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 { @@ -521,6 +535,33 @@ 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: pyo3::Bound<'_, pyo3::PyAny>, + encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>, +) -> CryptographyResult> { + let encryption_details = decode_encryption_algorithm(py, encryption_algorithm)?; + let mut safebags = vec![]; + let mut ca_certs = vec![]; + + for cert in certs.try_iter()? { + ca_certs.push(cert?.extract::>()?); + } + + for cert in &ca_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>( @@ -559,6 +600,7 @@ fn serialize_key_and_certificates<'p>( cert, name, key_id.as_ref().map(|v| v.as_bytes()), + false, )?); } @@ -570,12 +612,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); @@ -627,7 +670,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)?; @@ -637,14 +680,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( @@ -795,6 +838,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, }; } diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index 0490a543587e..96b4d59ebc55 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -32,6 +32,7 @@ PKCS12KeyAndCertificates, load_key_and_certificates, load_pkcs12, + serialize_java_truststore, serialize_key_and_certificates, ) @@ -737,6 +738,193 @@ def test_generate_localkeyid(self, backend, encryption_algorithm): assert p12.count(cert.fingerprint(hashes.SHA1())) == count +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) +class TestPKCS12TrustStoreCreation: + def test_generate_valid_truststore(self, backend): + # serialize_java_truststore adds a special attribute to each + # certificate's safebag. As we cannot read this back currently, + # comparison against a pre-verified file is necessary. + cert1 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert2 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + encryption = serialization.NoEncryption() + p12 = serialize_java_truststore( + [ + PKCS12Certificate(cert1, b"cert1"), + PKCS12Certificate(cert2, None), + ], + encryption, + ) + + # The golden file was verified with: + # keytool -list -keystore java-truststore.p12 + # Ensuring both entries are listed with "trustedCertEntry" + golden_bytes = load_vectors_from_file( + os.path.join("pkcs12", "java-truststore.p12"), + lambda data: data.read(), + mode="rb", + ) + + # The last 49 bytes are the MAC digest, and will vary each call, so we + # can ignore them. + mac_digest_size = 49 + assert p12[:-mac_digest_size] == golden_bytes[:-mac_digest_size] + + def test_generate_certs_friendly_names(self, backend): + cert1 = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + cert2 = _load_cert(backend, os.path.join("x509", "letsencryptx3.pem")) + encryption = serialization.NoEncryption() + p12 = serialize_java_truststore( + [ + PKCS12Certificate(cert1, b"cert1"), + PKCS12Certificate(cert2, None), + ], + encryption, + ) + + p12_cert = load_pkcs12(p12, None, backend) + cas = p12_cert.additional_certs + assert cas[0].certificate == cert1 + assert cas[0].friendly_name == b"cert1" + assert cas[1].certificate == cert2 + assert cas[1].friendly_name is None + + @pytest.mark.parametrize( + ("enc_alg", "enc_alg_der"), + [ + ( + PBES.PBESv2SHA256AndAES256CBC, + [ + b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x05\x0d", # PBESv2 + b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x01\x2a", # AES + ], + ), + ( + PBES.PBESv1SHA1And3KeyTripleDESCBC, + [b"\x06\x0a\x2a\x86\x48\x86\xf7\x0d\x01\x0c\x01\x03"], + ), + ( + None, + [], + ), + ], + ) + @pytest.mark.parametrize( + ("mac_alg", "mac_alg_der"), + [ + (hashes.SHA1(), b"\x06\x05\x2b\x0e\x03\x02\x1a"), + (hashes.SHA256(), b"\x06\t`\x86H\x01e\x03\x04\x02\x01"), + (None, None), + ], + ) + @pytest.mark.parametrize( + ("iters", "iter_der"), + [ + (420, b"\x02\x02\x01\xa4"), + (22222, b"\x02\x02\x56\xce"), + (None, None), + ], + ) + def test_key_serialization_encryption( + self, + backend, + enc_alg, + enc_alg_der, + mac_alg, + mac_alg_der, + iters, + iter_der, + ): + builder = serialization.PrivateFormat.PKCS12.encryption_builder() + if enc_alg is not None: + builder = builder.key_cert_algorithm(enc_alg) + if mac_alg is not None: + builder = builder.hmac_hash(mac_alg) + if iters is not None: + builder = builder.kdf_rounds(iters) + + encryption = builder.build(b"password") + cert = _load_cert( + backend, os.path.join("x509", "custom", "dsa_selfsigned_ca.pem") + ) + assert isinstance(cert, x509.Certificate) + p12 = serialize_java_truststore( + [PKCS12Certificate(cert, b"name")], encryption + ) + # We want to know if we've serialized something that has the parameters + # we expect, so we match on specific byte strings of OIDs & DER values. + for der in enc_alg_der: + assert der in p12 + if mac_alg_der is not None: + assert mac_alg_der in p12 + if iter_der is not None: + assert iter_der in p12 + _, _, parsed_more_certs = load_key_and_certificates( + p12, b"password", backend + ) + assert parsed_more_certs == [cert] + + def test_invalid_utf8_friendly_name(self, backend): + cert, _ = _load_ca(backend) + with pytest.raises(ValueError): + serialize_java_truststore( + [PKCS12Certificate(cert, b"\xc9")], + serialization.NoEncryption(), + ) + + def test_generate_empty_certs(self): + with pytest.raises(ValueError) as exc: + serialize_java_truststore([], serialization.NoEncryption()) + assert str(exc.value) == ("You must supply at least one cert") + + def test_generate_unsupported_encryption_type(self, backend): + cert, _ = _load_ca(backend) + with pytest.raises(ValueError) as exc: + serialize_java_truststore( + [PKCS12Certificate(cert, None)], + DummyKeySerializationEncryption(), + ) + assert str(exc.value) == "Unsupported key encryption type" + + def test_generate_wrong_types(self, backend): + cert, key = _load_ca(backend) + encryption = serialization.NoEncryption() + with pytest.raises(TypeError) as exc: + serialize_java_truststore(cert, encryption) + + with pytest.raises(TypeError) as exc: + serialize_java_truststore([cert], encryption) + assert "object cannot be converted to 'PKCS12Certificate'" in str( + exc.value + ) + + with pytest.raises(TypeError) as exc: + serialize_java_truststore( + [PKCS12Certificate(cert, None), key], + encryption, + ) + assert "object cannot be converted to 'PKCS12Certificate'" in str( + exc.value + ) + + with pytest.raises(TypeError) as exc: + serialize_java_truststore([PKCS12Certificate(cert, None)], cert) + assert str(exc.value) == ( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + with pytest.raises(TypeError) as exc: + serialize_java_truststore([key], encryption) + assert "object cannot be converted to 'PKCS12Certificate'" in str( + exc.value + ) + + @pytest.mark.skip_fips( reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." ) diff --git a/vectors/cryptography_vectors/pkcs12/java-truststore.p12 b/vectors/cryptography_vectors/pkcs12/java-truststore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..bb17f27791fb769bb80c969be0708b7a213a4afa GIT binary patch literal 2861 zcmcguX;c$g7EUFV5CTCV5D^4|EDA#Mim=OKSQG&_1Vs>)gh;}ckObK}C{cp8inySH zAZTQ9194+f5CIX8#a3o;6cn{#8bk%bR?*R^xE<{?=ghzUQRmh7y?fugb=ctUg{(WS0ux4 zLP-&U^p#YlqG#dRunfNrG*1>VoIyrx5yZv;*rBUK$Y*Ht9sYfR$Z*a; zhPD2d0tf{(tPFxM*Uk>XSq?5ezweAp%iNU}N^CI3mc!4iE;UC5I6IHubfunOW_#k` zqV)2#<;yjU6B7eVgmp7ZJ;)reZ7?W~*>0sF4NNPZj8ZAmf4ed7Y27GneZ+EbXmqj= zQ(JH*_jKGRZL-z#H?v=`8{dg}s}E^=HHMLxcOYp~EYEU!;77{?bNdxRWh=VkV#5dA zScN_9ONC5|#8uv2ma%^t`+4Yu$35z*IXuJsCR4vs%j3pA3rNxRTXdFYguzw?jfw^9 z4OgTNKP<+xU&o$w-;nXvtMuRa>tmint5@9ET1~c??zTq#mif#3MLS%#y4f$>S z;e+V`XPbXUSoE)(T;e$-O)AJuB#qiMu)^9bgxiUt``#bl_8V%3xcNYisU^KZaw7uc z?&aZ;EOVBQiBt7lBvfbai@P8^=F6Qf{_lvahAtgHTMqkB(s#@>&tEil|=Q-@vVCd|13@ z?9#TjlzAqhNB9?`TOR5?_plV$>e__wY%e!Hd|I(xwV>kM`mswdM=zW!r{=LA-=xj1 zSYFPnn2&SO9eQ#7&7+0;npbsHROR0s*j-ed6nB`~b)Rbxe(_3bdSjC7$zMj;k0LA9 zW#k(5Q7Xo#wP+B6Ir5t5s@>d#X@LxRjv>C}p8lx^op#3u+j}%~P0FKaSXDjO8PMka zH!OsjE`6H0!WtF-V!}wBYLJcH3vhE_%qHajSa4=kq2qVEDz>uK6LGA;|-)%hGo(e&T#?KhY;KzT_ zakv~MPyMXpa2_m!52E}&Aj746(edddh~E&Cp((hCzAKXHm|-t3DPoNMBSX3Uk{j^%^ug)+l>qN zF{-NlSoqT?kJ=G~M=LFxd6{a8rwP9gO_Z|ZYm?JA81?SWE1bBU6dB*j*T~u1{c$LF zc!P^iM_T)mL@m>TMfxGnYDNtmFthT{q?Y1Gx_qD2u?FoA*WCI~tjj^(BNI!HY<0OL zvUPKDUUfjYHpQJgEX%z0U`k`vpYk0O8SaPV73cc&UPhC1sM6+nMcnhbuzkXo=rg_( z`q_(<7{?DTv!t0>z%7!7+eqgg#4(3jTgI&R9IrHL0-KK~BNSI_`?pU&ykbVxI3YJG zns<{8BB;z$Q`;;q_oy}8lQ!+g;vfv9kU>&3Bgvl!s{sWqT$l(5=z@hXKm$#(!89cY zqjO#kHWP^R2oN5x0zt4C!~>ge7(jA!oHG+8+-HK$ z5yXP|-+i!h##f(kp-}v@8AF8DjF=H2mMC91;Ym!o(1G*$2LWw z>cL~b?1WFvIvX-}Ob3C=h-vT=;0K5VLS(u+goS`RK@ANXhGdV>?}GpU^lvd>A?oa} zF>ufjly_MtA@mroM9lqCYIK1p61XT&HG=H`6Cg#5B5V1)Z%(3^zr>B!d%&_~fu5{j zre}aZog<3*LD9;5qd2g%zjo+1r>YWf?#qd?(PKZBDB5CZi9(ouX0PT1C)lxHSLeNI zQ2nM=HA0_a{-Qalji$tX;~_@AS{4qbk`=Sqyz0&zVdUVO6IYkcUD0{ukMhidk$bfv z$Dq8vvHmE7sC#a!O^%ir!tA_Hz4-PjBliZ9&(#CxE_2xf*8>L?mS<_r!$Vj4TK~M% zWTQCTkytjA%vG6pYwky9YCw8dfI5Bs)<6D1V7^a`=~^&ibJS?xTGn+*OlTNsJ+os; z;Tgv Date: Mon, 3 Feb 2025 14:23:41 -0800 Subject: [PATCH 2/5] Added documentation for new serialize_java_truststore function --- CHANGELOG.rst | 2 ++ .../primitives/asymmetric/serialization.rst | 36 +++++++++++++++++++ docs/spelling_wordlist.txt | 2 ++ 3 files changed, 40 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 02a6405050fa..de1b4242ec5d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index b832dd032722..f414df569d15 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -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 diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 001f8f5f7c2a..80160dc57156 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -143,6 +143,8 @@ TLS toolchain totient Trixie +truststore +truststores tunable Ubuntu unencrypted From 168fb8eb6cc8b8d308af5433a3b67b1838e784c9 Mon Sep 17 00:00:00 2001 From: crbednarz Date: Tue, 4 Feb 2025 09:39:21 -0800 Subject: [PATCH 3/5] Corrected issue where truststores were encoded with the wrong usage attribute --- src/rust/cryptography-x509/src/pkcs12.rs | 1 + src/rust/src/pkcs12.rs | 4 ++-- .../pkcs12/java-truststore.p12 | Bin 2861 -> 2845 bytes 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rust/cryptography-x509/src/pkcs12.rs b/src/rust/cryptography-x509/src/pkcs12.rs index 9935d3bd9830..b7c27c86c357 100644 --- a/src/rust/cryptography-x509/src/pkcs12.rs +++ b/src/rust/cryptography-x509/src/pkcs12.rs @@ -16,6 +16,7 @@ pub const FRIENDLY_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 1135 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); +pub const ANY_EXTENDED_KEY_USAGE: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37, 0); #[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct Pfx<'a> { diff --git a/src/rust/src/pkcs12.rs b/src/rust/src/pkcs12.rs index 42601795b96c..2b08d81e7efe 100644 --- a/src/rust/src/pkcs12.rs +++ b/src/rust/src/pkcs12.rs @@ -6,7 +6,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use cryptography_x509::common::Utf8StoredBMPString; -use cryptography_x509::pkcs12::JDK_TRUSTSTORE_USAGE; +use cryptography_x509::pkcs12::ANY_EXTENDED_KEY_USAGE; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use pyo3::{IntoPyObject, PyTypeInfo}; @@ -276,7 +276,7 @@ fn pkcs12_attributes<'a>( attrs.push(cryptography_x509::pkcs12::Attribute { _attr_id: asn1::DefinedByMarker::marker(), attr_values: cryptography_x509::pkcs12::AttributeSet::JDKTruststoreUsage( - asn1::SetOfWriter::new([JDK_TRUSTSTORE_USAGE]), + asn1::SetOfWriter::new([ANY_EXTENDED_KEY_USAGE]), ), }); } diff --git a/vectors/cryptography_vectors/pkcs12/java-truststore.p12 b/vectors/cryptography_vectors/pkcs12/java-truststore.p12 index bb17f27791fb769bb80c969be0708b7a213a4afa..02d8e7220f2a3f78f3895af930915fd8d627e243 100644 GIT binary patch delta 206 zcmZ20HdoBipov?OiILf$iR(NYr&gOs+jm|@cE$xwTnAX1xb^^rcL4DgRH>yvsf9q{ zxdu(Fdp0_Duu2*mh_Ue`w0SVL{Y+bXs+y6}kd2KcR92N?@+nps<|dY#n>pDIu*68< zQfA<2U}(V0#tAYam6?T!k(Gf(;ePdv^ciJ$42rb9QyG7JHTvtn?&!B4%>@jCJ}tZE V9-YR*@v!LazO@m5jhL7?7yz|>LDT>M delta 245 zcmbO$wpPs1pov?PiILf$iR(Tar&gOs+jm|@cE$xwTo+iHxXu8DPXO@|RH>~%sf|G4 zwFXVBM>aZkusT>9NWztHiWu_9aWNz_q%ssSlrR_?$g%Mxw0SVL{Y+bXs+y6}kPnq_ z(8Tg^Gb7spmJ|hoN*xUh4S3l&LB^#rvoJBTGO#Gj`}ww3?snZ;n{zQMt(+n(=5Fzq gXY2U&VZyQPYu0FG3bJrKn$WcImB&jnCMFIB0B5OAnE(I) From 648785a220bbbf76696731e6e9ac59b54c29d82d Mon Sep 17 00:00:00 2001 From: crbednarz Date: Thu, 13 Feb 2025 16:50:25 -0800 Subject: [PATCH 4/5] Removed duplicate ANY_KEY_USAGE definition and simplified truststore certs arg --- src/rust/cryptography-x509/src/pkcs12.rs | 1 - src/rust/src/pkcs12.rs | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/rust/cryptography-x509/src/pkcs12.rs b/src/rust/cryptography-x509/src/pkcs12.rs index b7c27c86c357..9935d3bd9830 100644 --- a/src/rust/cryptography-x509/src/pkcs12.rs +++ b/src/rust/cryptography-x509/src/pkcs12.rs @@ -16,7 +16,6 @@ pub const FRIENDLY_NAME_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 1135 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); -pub const ANY_EXTENDED_KEY_USAGE: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 37, 0); #[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct Pfx<'a> { diff --git a/src/rust/src/pkcs12.rs b/src/rust/src/pkcs12.rs index 2b08d81e7efe..e2ba412a6899 100644 --- a/src/rust/src/pkcs12.rs +++ b/src/rust/src/pkcs12.rs @@ -6,7 +6,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use cryptography_x509::common::Utf8StoredBMPString; -use cryptography_x509::pkcs12::ANY_EXTENDED_KEY_USAGE; +use cryptography_x509::oid::EKU_ANY_KEY_USAGE_OID; use pyo3::types::{PyAnyMethods, PyBytesMethods, PyListMethods}; use pyo3::{IntoPyObject, PyTypeInfo}; @@ -276,7 +276,7 @@ fn pkcs12_attributes<'a>( attrs.push(cryptography_x509::pkcs12::Attribute { _attr_id: asn1::DefinedByMarker::marker(), attr_values: cryptography_x509::pkcs12::AttributeSet::JDKTruststoreUsage( - asn1::SetOfWriter::new([ANY_EXTENDED_KEY_USAGE]), + asn1::SetOfWriter::new([EKU_ANY_KEY_USAGE_OID]), ), }); } @@ -539,18 +539,13 @@ fn serialize_safebags<'p>( #[pyo3(signature = (certs, encryption_algorithm))] fn serialize_java_truststore<'p>( py: pyo3::Python<'p>, - certs: pyo3::Bound<'_, pyo3::PyAny>, + certs: Vec>, encryption_algorithm: pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { let encryption_details = decode_encryption_algorithm(py, encryption_algorithm)?; let mut safebags = vec![]; - let mut ca_certs = vec![]; - - for cert in certs.try_iter()? { - ca_certs.push(cert?.extract::>()?); - } - for cert in &ca_certs { + 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)), From 393617385def36ae93ee8b072aa68b8c4facff05 Mon Sep 17 00:00:00 2001 From: crbednarz Date: Thu, 13 Feb 2025 17:11:48 -0800 Subject: [PATCH 5/5] Added documentation for java-truststore.p12 --- docs/development/test-vectors.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 45f850fe17d8..41c6b5d111f8 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~