aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2024-04-28 12:44:04 -0400
committerGitHub <noreply@github.com>2024-04-28 09:44:04 -0700
commit66cf834fadae8d1353c5322014b4d3a64361b36b (patch)
tree631374955515ac7078da44a336286f17f013164c /src
parent56fcdb3ac4a574aadf61a7338d010751333d00eb (diff)
downloadpyca-cryptography-66cf834fadae8d1353c5322014b4d3a64361b36b.zip
pyca-cryptography-66cf834fadae8d1353c5322014b4d3a64361b36b.tar.gz
pyca-cryptography-66cf834fadae8d1353c5322014b4d3a64361b36b.tar.bz2
Begin migrating PKCS#12 serialization to Rust (#10616)
For now, only handle unencrypted cert-only PKCS#12.
Diffstat (limited to 'src')
-rw-r--r--src/cryptography/hazmat/bindings/_rust/pkcs12.pyi5
-rw-r--r--src/cryptography/hazmat/primitives/serialization/pkcs12.py5
-rw-r--r--src/rust/cryptography-x509/src/common.rs19
-rw-r--r--src/rust/cryptography-x509/src/pkcs12.rs37
-rw-r--r--src/rust/cryptography-x509/src/pkcs7.rs2
-rw-r--r--src/rust/src/pkcs12.rs169
-rw-r--r--src/rust/src/types.rs2
7 files changed, 216 insertions, 23 deletions
diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi
index 109ae4f..76dd019 100644
--- a/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi
+++ b/src/cryptography/hazmat/bindings/_rust/pkcs12.pyi
@@ -33,3 +33,8 @@ def load_pkcs12(
password: bytes | None,
backend: typing.Any = None,
) -> PKCS12KeyAndCertificates: ...
+def serialize_key_and_certificates(
+ name: bytes | None,
+ cert: x509.Certificate | None,
+ cas: typing.Iterable[x509.Certificate | PKCS12Certificate] | None,
+) -> bytes: ...
diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py
index 8ed5f1e..0d37145 100644
--- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py
+++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py
@@ -167,6 +167,11 @@ def serialize_key_and_certificates(
if key is None and cert is None and not cas:
raise ValueError("You must supply at least one of key, cert, or cas")
+ if key is None and isinstance(
+ encryption_algorithm, serialization.NoEncryption
+ ):
+ return rust_pkcs12.serialize_key_and_certificates(name, cert, cas)
+
from cryptography.hazmat.backends.openssl.backend import backend
return backend.serialize_key_and_certificates_to_pkcs12(
diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs
index 77cebc3..9eea5ff 100644
--- a/src/rust/cryptography-x509/src/common.rs
+++ b/src/rust/cryptography-x509/src/common.rs
@@ -414,6 +414,25 @@ impl<'a> asn1::SimpleAsn1Writable for UnvalidatedVisibleString<'a> {
}
}
+/// A BMPString ASN.1 element, where it is stored as a UTF-8 string in memory.
+pub struct Utf8StoredBMPString<'a>(pub &'a str);
+
+impl<'a> Utf8StoredBMPString<'a> {
+ pub fn new(s: &'a str) -> Self {
+ Utf8StoredBMPString(s)
+ }
+}
+
+impl<'a> asn1::SimpleAsn1Writable for Utf8StoredBMPString<'a> {
+ const TAG: asn1::Tag = asn1::BMPString::TAG;
+ fn write_data(&self, writer: &mut asn1::WriteBuf) -> asn1::WriteResult {
+ for ch in self.0.encode_utf16() {
+ writer.push_slice(&ch.to_be_bytes())?;
+ }
+ Ok(())
+ }
+}
+
#[derive(Clone)]
pub struct WithTlv<'a, T> {
tlv: asn1::Tlv<'a>,
diff --git a/src/rust/cryptography-x509/src/pkcs12.rs b/src/rust/cryptography-x509/src/pkcs12.rs
index 328961f..4fea621 100644
--- a/src/rust/cryptography-x509/src/pkcs12.rs
+++ b/src/rust/cryptography-x509/src/pkcs12.rs
@@ -2,6 +2,7 @@
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.
+use crate::common::Utf8StoredBMPString;
use crate::pkcs7;
pub const CERT_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 12, 10, 1, 3);
@@ -9,60 +10,60 @@ pub const KEY_BAG_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1,
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);
-// #[derive(asn1::Asn1Write)]
+#[derive(asn1::Asn1Write)]
pub struct Pfx<'a> {
pub version: u8,
pub auth_safe: pkcs7::ContentInfo<'a>,
pub mac_data: Option<MacData<'a>>,
}
-// #[derive(asn1::Asn1Write)]
+#[derive(asn1::Asn1Write)]
pub struct MacData<'a> {
pub mac: pkcs7::DigestInfo<'a>,
pub salt: &'a [u8],
- // #[default(1u64)]
+ #[default(1u64)]
pub iterations: u64,
}
-// #[derive(asn1::Asn1Write)]
+#[derive(asn1::Asn1Write)]
pub struct SafeBag<'a> {
pub _bag_id: asn1::DefinedByMarker<asn1::ObjectIdentifier>,
- // #[defined_by(_bag_id)]
+ #[defined_by(_bag_id)]
pub bag_value: asn1::Explicit<BagValue<'a>, 0>,
- // pub attributes: Option<asn1::SetOfWriter<'a, Attribute<'a>>>,
+ pub attributes: Option<asn1::SetOfWriter<'a, Attribute<'a>, Vec<Attribute<'a>>>>,
}
-// #[derive(asn1::Asn1Write)]
+#[derive(asn1::Asn1Write)]
pub struct Attribute<'a> {
pub _attr_id: asn1::DefinedByMarker<asn1::ObjectIdentifier>,
- // #[defined_by(_attr_id)]
+ #[defined_by(_attr_id)]
pub attr_values: AttributeSet<'a>,
}
-// #[derive(asn1::Asn1DefinedByWrite)]
+#[derive(asn1::Asn1DefinedByWrite)]
pub enum AttributeSet<'a> {
- // #[defined_by(FRIENDLY_NAME_OID)]
- FriendlyName(asn1::SetOfWriter<'a, asn1::BMPString<'a>>),
+ #[defined_by(FRIENDLY_NAME_OID)]
+ FriendlyName(asn1::SetOfWriter<'a, Utf8StoredBMPString<'a>, [Utf8StoredBMPString<'a>; 1]>),
}
-// #[derive(asn1::Asn1DefinedByWrite)]
+#[derive(asn1::Asn1DefinedByWrite)]
pub enum BagValue<'a> {
- // #[defined_by(CERT_BAG_OID)]
+ #[defined_by(CERT_BAG_OID)]
CertBag(CertBag<'a>),
- // #[defined_by(KEY_BAG_OID)]
+ #[defined_by(KEY_BAG_OID)]
KeyBag(asn1::Tlv<'a>),
}
-// #[derive(asn1::Asn1Write)]
+#[derive(asn1::Asn1Write)]
pub struct CertBag<'a> {
pub _cert_id: asn1::DefinedByMarker<asn1::ObjectIdentifier>,
- // #[defined_by(_cert_id)]
+ #[defined_by(_cert_id)]
pub cert_value: asn1::Explicit<CertType<'a>, 0>,
}
-// #[derive(asn1::Asn1DefinedByWrite)]
+#[derive(asn1::Asn1DefinedByWrite)]
pub enum CertType<'a> {
- // #[defined_by(X509_CERTIFICATE_OID)]
+ #[defined_by(X509_CERTIFICATE_OID)]
X509(asn1::OctetStringEncoded<crate::certificate::Certificate<'a>>),
}
diff --git a/src/rust/cryptography-x509/src/pkcs7.rs b/src/rust/cryptography-x509/src/pkcs7.rs
index e1581a0..9df3236 100644
--- a/src/rust/cryptography-x509/src/pkcs7.rs
+++ b/src/rust/cryptography-x509/src/pkcs7.rs
@@ -59,7 +59,7 @@ pub struct IssuerAndSerialNumber<'a> {
pub serial_number: asn1::BigInt<'a>,
}
-// #[derive(asn1::Asn1Write)]
+#[derive(asn1::Asn1Write)]
pub struct DigestInfo<'a> {
pub algorithm: common::AlgorithmIdentifier<'a>,
pub digest: &'a [u8],
diff --git a/src/rust/src/pkcs12.rs b/src/rust/src/pkcs12.rs
index 51116c5..1b1b6ce 100644
--- a/src/rust/src/pkcs12.rs
+++ b/src/rust/src/pkcs12.rs
@@ -2,12 +2,13 @@
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.
-use crate::backend::keys;
+use crate::backend::{hashes, hmac, keys};
use crate::buf::CffiBuf;
use crate::error::CryptographyResult;
use crate::x509::certificate::Certificate;
use crate::{types, x509};
-use pyo3::prelude::{PyAnyMethods, PyListMethods, PyModuleMethods};
+use cryptography_x509::common::Utf8StoredBMPString;
+use pyo3::prelude::{PyAnyMethods, PyBytesMethods, PyListMethods, PyModuleMethods};
use pyo3::IntoPy;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
@@ -80,9 +81,8 @@ impl PKCS12Certificate {
const KDF_ENCRYPTION_KEY_ID: u8 = 1;
#[allow(dead_code)]
const KDF_IV_ID: u8 = 2;
-#[allow(dead_code)]
const KDF_MAC_KEY_ID: u8 = 3;
-#[allow(dead_code)]
+
fn pkcs12_kdf(
pass: &[u8],
salt: &[u8],
@@ -183,6 +183,163 @@ fn pkcs12_kdf(
Ok(result)
}
+fn friendly_name_attributes(
+ friendly_name: Option<&[u8]>,
+) -> CryptographyResult<
+ Option<
+ asn1::SetOfWriter<
+ '_,
+ cryptography_x509::pkcs12::Attribute<'_>,
+ Vec<cryptography_x509::pkcs12::Attribute<'_>>,
+ >,
+ >,
+> {
+ if let Some(name) = friendly_name {
+ let name_str = std::str::from_utf8(name).map_err(|_| {
+ pyo3::exceptions::PyValueError::new_err("friendly_name must be valid UTF-8")
+ })?;
+
+ Ok(Some(asn1::SetOfWriter::new(vec![
+ cryptography_x509::pkcs12::Attribute {
+ _attr_id: asn1::DefinedByMarker::marker(),
+ attr_values: cryptography_x509::pkcs12::AttributeSet::FriendlyName(
+ asn1::SetOfWriter::new([Utf8StoredBMPString::new(name_str)]),
+ ),
+ },
+ ])))
+ } else {
+ Ok(None)
+ }
+}
+
+fn cert_to_bag<'a>(
+ cert: &'a Certificate,
+ friendly_name: Option<&'a [u8]>,
+) -> CryptographyResult<cryptography_x509::pkcs12::SafeBag<'a>> {
+ Ok(cryptography_x509::pkcs12::SafeBag {
+ _bag_id: asn1::DefinedByMarker::marker(),
+ bag_value: asn1::Explicit::new(cryptography_x509::pkcs12::BagValue::CertBag(
+ cryptography_x509::pkcs12::CertBag {
+ _cert_id: asn1::DefinedByMarker::marker(),
+ cert_value: asn1::Explicit::new(cryptography_x509::pkcs12::CertType::X509(
+ asn1::OctetStringEncoded::new(cert.raw.borrow_dependent().clone()),
+ )),
+ },
+ )),
+ attributes: friendly_name_attributes(friendly_name)?,
+ })
+}
+
+fn decode_encryption_algorithm(
+ py: pyo3::Python<'_>,
+) -> CryptographyResult<(&[u8], pyo3::Bound<'_, pyo3::PyAny>, u64)> {
+ let default_hmac_alg = types::SHA256.get(py)?.call0()?;
+ let default_hmac_kdf_iter = 2048;
+
+ Ok((b"", default_hmac_alg, default_hmac_kdf_iter))
+}
+
+#[derive(pyo3::FromPyObject)]
+enum CertificateOrPKCS12Certificate {
+ Certificate(pyo3::Py<Certificate>),
+ PKCS12Certificate(pyo3::Py<PKCS12Certificate>),
+}
+
+#[pyo3::prelude::pyfunction]
+#[pyo3(signature = (name, cert, cas))]
+fn serialize_key_and_certificates<'p>(
+ py: pyo3::Python<'p>,
+ name: Option<&[u8]>,
+ cert: Option<&Certificate>,
+ cas: Option<pyo3::Bound<'_, pyo3::PyAny>>,
+) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
+ let (password, mac_algorithm, mac_kdf_iter) = decode_encryption_algorithm(py)?;
+
+ let mut auth_safe_contents = vec![];
+ let cert_bag_contents;
+ let mut ca_certs = vec![];
+ assert!(cert.is_some() || cas.is_some());
+ {
+ let mut cert_bags = vec![];
+
+ if let Some(cert) = cert {
+ cert_bags.push(cert_to_bag(cert, name)?);
+ }
+
+ if let Some(cas) = cas {
+ for cert in cas.iter()? {
+ ca_certs.push(cert?.extract::<CertificateOrPKCS12Certificate>()?);
+ }
+
+ for cert in &ca_certs {
+ let bag = match cert {
+ CertificateOrPKCS12Certificate::Certificate(c) => cert_to_bag(c.get(), None)?,
+ CertificateOrPKCS12Certificate::PKCS12Certificate(c) => cert_to_bag(
+ c.get().certificate.get(),
+ c.get().friendly_name.as_ref().map(|v| v.as_bytes(py)),
+ )?,
+ };
+ cert_bags.push(bag);
+ }
+ }
+
+ cert_bag_contents = asn1::write_single(&asn1::SequenceOfWriter::new(cert_bags))?;
+ auth_safe_contents.push(cryptography_x509::pkcs7::ContentInfo {
+ _content_type: asn1::DefinedByMarker::marker(),
+ content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new(
+ &cert_bag_contents,
+ ))),
+ });
+ }
+ let auth_safe_content = asn1::write_single(&asn1::SequenceOfWriter::new(auth_safe_contents))?;
+
+ let salt = types::OS_URANDOM
+ .get(py)?
+ .call1((8,))?
+ .extract::<pyo3::pybacked::PyBackedBytes>()?;
+ let mac_algorithm_md = hashes::message_digest_from_algorithm(py, &mac_algorithm)?;
+ let mac_key = pkcs12_kdf(
+ password,
+ &salt,
+ KDF_MAC_KEY_ID,
+ mac_kdf_iter,
+ mac_algorithm_md.size(),
+ mac_algorithm_md,
+ )?;
+ let mac_digest = {
+ let mut h = hmac::Hmac::new_bytes(py, &mac_key, &mac_algorithm)?;
+ h.update_bytes(&auth_safe_content)?;
+ h.finalize(py)?
+ };
+ let mac_algorithm_identifier = crate::x509::ocsp::HASH_NAME_TO_ALGORITHM_IDENTIFIERS
+ [&*mac_algorithm
+ .getattr(pyo3::intern!(py, "name"))?
+ .extract::<pyo3::pybacked::PyBackedStr>()?]
+ .clone();
+
+ let p12 = cryptography_x509::pkcs12::Pfx {
+ version: 3,
+ auth_safe: cryptography_x509::pkcs7::ContentInfo {
+ _content_type: asn1::DefinedByMarker::marker(),
+ content: cryptography_x509::pkcs7::Content::Data(Some(asn1::Explicit::new(
+ &auth_safe_content,
+ ))),
+ },
+ mac_data: Some(cryptography_x509::pkcs12::MacData {
+ mac: cryptography_x509::pkcs7::DigestInfo {
+ algorithm: mac_algorithm_identifier,
+ digest: mac_digest.as_bytes(),
+ },
+ salt: &salt,
+ iterations: mac_kdf_iter,
+ }),
+ };
+ Ok(pyo3::types::PyBytes::new_bound(
+ py,
+ &asn1::write_single(&p12)?,
+ ))
+}
+
fn decode_p12(
data: CffiBuf<'_>,
password: Option<CffiBuf<'_>>,
@@ -323,6 +480,10 @@ pub(crate) fn create_submodule(
&submod
)?)?;
submod.add_function(pyo3::wrap_pyfunction_bound!(load_pkcs12, &submod)?)?;
+ submod.add_function(pyo3::wrap_pyfunction_bound!(
+ serialize_key_and_certificates,
+ &submod
+ )?)?;
submod.add_class::<PKCS12Certificate>()?;
diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs
index d60c50e..3b21ec1 100644
--- a/src/rust/src/types.rs
+++ b/src/rust/src/types.rs
@@ -345,6 +345,8 @@ pub static EXTENDABLE_OUTPUT_FUNCTION: LazyPyImport = LazyPyImport::new(
);
pub static SHA1: LazyPyImport =
LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA1"]);
+pub static SHA256: LazyPyImport =
+ LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA256"]);
pub static PREHASHED: LazyPyImport = LazyPyImport::new(
"cryptography.hazmat.primitives.asymmetric.utils",