use super::*;
use crate::hash::Sha256 as Sha256Hash;
use crate::rng::HmacDrbg;
fn rng(seed: &[u8]) -> HmacDrbg<Sha256Hash> {
HmacDrbg::<Sha256Hash>::new(seed, b"pkcs12-test", &[])
}
const KEY_PK8: &[u8] = include_bytes!("../../testdata/pkcs12_test_key.pk8.der");
const CERT_DER: &[u8] = include_bytes!("../../testdata/pkcs12_test_cert.der");
const P12_DEFAULT: &[u8] = include_bytes!("../../testdata/pkcs12_openssl3_default.p12");
const P12_LEGACY: &[u8] = include_bytes!("../../testdata/pkcs12_openssl_legacy_3des.p12");
const PASSWORD: &str = "hunter2";
#[test]
fn openssl3_default_interop() {
let parsed = Pfx::parse(P12_DEFAULT, PASSWORD).expect("parse OpenSSL3 default p12");
assert_eq!(parsed.certs.len(), 1, "one cert");
assert_eq!(parsed.certs[0], CERT_DER, "cert DER round-trips OpenSSL");
assert_eq!(parsed.keys.len(), 1, "one key");
assert_eq!(parsed.keys[0], KEY_PK8, "key PKCS#8 round-trips OpenSSL");
assert!(
parsed.friendly_names.iter().any(|n| n == "purecrypto test"),
"friendlyName recovered, got {:?}",
parsed.friendly_names
);
}
#[test]
fn openssl_legacy_3des_interop() {
let parsed = Pfx::parse(P12_LEGACY, PASSWORD).expect("parse OpenSSL legacy 3DES p12");
assert_eq!(parsed.certs.len(), 1);
assert_eq!(parsed.certs[0], CERT_DER);
assert_eq!(parsed.keys.len(), 1);
assert_eq!(parsed.keys[0], KEY_PK8);
}
#[test]
fn wrong_password_is_mac_mismatch() {
assert_eq!(
Pfx::parse(P12_DEFAULT, "wrong").unwrap_err(),
Error::MacMismatch
);
assert_eq!(
Pfx::parse(P12_LEGACY, "nope").unwrap_err(),
Error::MacMismatch
);
}
#[test]
fn tampered_content_is_rejected() {
let mut bad = P12_DEFAULT.to_vec();
bad[100] ^= 0x01;
assert_eq!(Pfx::parse(&bad, PASSWORD).unwrap_err(), Error::MacMismatch);
}
#[test]
fn build_then_parse_roundtrip() {
let mut r = rng(b"build-roundtrip");
let p12 = Pfx::build(KEY_PK8, &[CERT_DER], "s3cret", Some("my identity"), &mut r);
let parsed = Pfx::parse(&p12, "s3cret").expect("parse our own build");
assert_eq!(parsed.keys.len(), 1);
assert_eq!(parsed.keys[0], KEY_PK8, "key survives build->parse");
assert_eq!(parsed.certs.len(), 1);
assert_eq!(parsed.certs[0], CERT_DER, "cert survives build->parse");
assert!(parsed.friendly_names.iter().any(|n| n == "my identity"));
assert_eq!(Pfx::parse(&p12, "wrong").unwrap_err(), Error::MacMismatch);
}
#[test]
fn build_multi_cert_chain() {
let mut r = rng(b"build-chain");
let p12 = Pfx::build(KEY_PK8, &[CERT_DER, CERT_DER], "pw", None, &mut r);
let parsed = Pfx::parse(&p12, "pw").unwrap();
assert_eq!(parsed.certs.len(), 2);
assert_eq!(parsed.keys.len(), 1);
}
#[test]
fn missing_mac_rejected() {
let inner = encode_sequence(&[]); let ci = encode_data_content_info(&inner);
let version = encode_integer(&[0x03]);
let pfx = encode_sequence(&[version, ci].concat());
assert_eq!(Pfx::parse(&pfx, "x").unwrap_err(), Error::MissingMac);
}
#[test]
fn recovered_material_is_usable() {
let parsed = Pfx::parse(P12_DEFAULT, PASSWORD).unwrap();
let cert =
crate::x509::Certificate::from_der(parsed.certs[0].clone()).expect("recovered cert parses");
assert!(cert.subject().is_ok());
let key = crate::x509::AnyPrivateKey::from_pkcs8_der(
&parsed.keys[0],
crate::x509::Pkcs8ReadOptions::new(),
)
.expect("recovered key parses");
assert!(matches!(key, crate::x509::AnyPrivateKey::Ecdsa(_)));
}
#[test]
fn sha_based_mac_matches_openssl_tag() {
let mut reader = Reader::new(P12_DEFAULT);
let mut pfx = reader.read_sequence().unwrap();
let _version = pfx.read_integer_bytes().unwrap();
let auth_safe = read_content_info_data(&mut pfx).unwrap();
let mac = pfx.read_element().unwrap();
let mut mr = Reader::new(mac);
let mut md = mr.read_sequence().unwrap();
let mut di = md.read_sequence().unwrap();
let _alg = di.read_sequence().unwrap();
let stored = di.read_octet_string().unwrap().to_vec();
let salt = md.read_octet_string().unwrap().to_vec();
let iters = read_iterations(&mut md).unwrap();
let pw = password_to_bmp(PASSWORD);
let computed = sha_based_hmac(PkcsHash::Sha256, &pw, &salt, iters, auth_safe);
assert_eq!(computed, stored, "RFC 7292 §B SHA-256 MAC matches OpenSSL");
}
#[test]
#[ignore = "writes /tmp/purecrypto_built.p12 for manual openssl interop check"]
fn dump_built_for_openssl() {
let mut r = rng(b"openssl-interop-dump");
let p12 = Pfx::build(KEY_PK8, &[CERT_DER], "hunter2", Some("pc built"), &mut r);
std::fs::write("/tmp/purecrypto_built.p12", &p12).unwrap();
}