#![cfg(feature = "pkcs12")]
mod kdf;
mod pbes2_p12;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use crate::der::{
Reader, encode_context, encode_integer, encode_octet_string, encode_sequence, oid_tlv,
parse_oid, tag,
};
use crate::hash::{Hmac, Sha1, Sha256};
use crate::kdf::pbes2;
use crate::rng::RngCore;
use kdf::{ID_IV, ID_KEY, ID_MAC, PkcsHash, derive, password_to_bmp};
const OID_DATA: &[u64] = &[1, 2, 840, 113549, 1, 7, 1];
const OID_ENCRYPTED_DATA: &[u64] = &[1, 2, 840, 113549, 1, 7, 6];
const OID_KEY_BAG: &[u64] = &[1, 2, 840, 113549, 1, 12, 10, 1, 1];
const OID_PKCS8_SHROUDED_KEY_BAG: &[u64] = &[1, 2, 840, 113549, 1, 12, 10, 1, 2];
const OID_CERT_BAG: &[u64] = &[1, 2, 840, 113549, 1, 12, 10, 1, 3];
const OID_CERT_TYPE_X509: &[u64] = &[1, 2, 840, 113549, 1, 9, 22, 1];
const OID_FRIENDLY_NAME: &[u64] = &[1, 2, 840, 113549, 1, 9, 20];
const OID_LOCAL_KEY_ID: &[u64] = &[1, 2, 840, 113549, 1, 9, 21];
const OID_PBE_SHA1_3DES: &[u64] = &[1, 2, 840, 113549, 1, 12, 1, 3];
const OID_PBES2: &[u64] = &[1, 2, 840, 113549, 1, 5, 13];
const OID_HMAC_SHA1: &[u64] = &[1, 2, 840, 113549, 2, 7];
const OID_SHA1: &[u64] = &[1, 3, 14, 3, 2, 26];
const OID_SHA256: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 1];
const OID_PBMAC1: &[u64] = &[1, 2, 840, 113549, 1, 5, 14];
const OID_PBKDF2: &[u64] = &[1, 2, 840, 113549, 1, 5, 12];
const OID_HMAC_SHA256: &[u64] = &[1, 2, 840, 113549, 2, 9];
const MIN_ITERATIONS: u32 = 1;
const MAX_ITERATIONS: u32 = 10_000_000;
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Malformed,
MacMismatch,
MissingMac,
UnsupportedAlgorithm,
Decryption,
BadParameters,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::Malformed => f.write_str("malformed PKCS#12 archive"),
Error::MacMismatch => f.write_str("PKCS#12 MAC mismatch (wrong password?)"),
Error::MissingMac => f.write_str("PKCS#12 archive has no integrity MAC"),
Error::UnsupportedAlgorithm => f.write_str("unsupported PKCS#12 algorithm"),
Error::Decryption => f.write_str("PKCS#12 content decryption failed"),
Error::BadParameters => f.write_str("PKCS#12 KDF parameters out of range"),
}
}
}
impl core::error::Error for Error {}
impl From<crate::der::Error> for Error {
fn from(_: crate::der::Error) -> Self {
Error::Malformed
}
}
pub struct Parsed {
pub keys: Vec<Vec<u8>>,
pub certs: Vec<Vec<u8>>,
pub friendly_names: Vec<String>,
}
impl Drop for Parsed {
fn drop(&mut self) {
for k in self.keys.iter_mut() {
for b in k.iter_mut() {
*b = 0;
}
let _ = core::hint::black_box(&k);
}
}
}
impl core::fmt::Debug for Parsed {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Parsed")
.field(
"keys",
&format_args!("{} key(s) <redacted>", self.keys.len()),
)
.field("certs", &self.certs.len())
.field("friendly_names", &self.friendly_names)
.finish()
}
}
#[derive(Debug)]
pub struct Pfx;
impl Pfx {
pub fn parse(der: &[u8], password: &str) -> Result<Parsed, Error> {
let mut pw_bmp = password_to_bmp(password);
let result = Self::parse_inner(der, password, &pw_bmp);
for b in pw_bmp.iter_mut() {
*b = 0;
}
let _ = core::hint::black_box(&pw_bmp);
result
}
fn parse_inner(der: &[u8], password: &str, pw_bmp: &[u8]) -> Result<Parsed, Error> {
let mut reader = Reader::new(der);
let mut pfx = reader.read_sequence()?;
reader.finish()?;
let version = pfx.read_integer_bytes()?;
if version != [0x03] {
return Err(Error::Malformed);
}
let auth_safe_data = read_content_info_data(&mut pfx)?;
let mac_data = if pfx.peek_tag().is_some() {
Some(pfx.read_element()?)
} else {
None
};
pfx.finish()?;
let mac = mac_data.ok_or(Error::MissingMac)?;
verify_mac(mac, auth_safe_data, password, pw_bmp)?;
let mut out = Parsed {
keys: Vec::new(),
certs: Vec::new(),
friendly_names: Vec::new(),
};
let mut safe_reader = Reader::new(auth_safe_data);
let mut safe_seq = safe_reader.read_sequence()?;
safe_reader.finish()?;
while !safe_seq.is_empty() {
let mut ci = safe_seq.read_sequence()?;
let ci_oid = parse_oid(ci.read_oid()?)?;
let oid = ci_oid.as_slice();
if oid == OID_DATA {
let inner = ci.read_element()?;
let mut ctx = Reader::new(inner);
let body = ctx.read_tlv(tag::context(0))?;
let mut octet = Reader::new(body);
let safe_contents = octet.read_octet_string()?;
parse_safe_contents(safe_contents, pw_bmp, password, &mut out)?;
} else if oid == OID_ENCRYPTED_DATA {
let inner = ci.read_element()?;
let mut ctx = Reader::new(inner);
let body = ctx.read_tlv(tag::context(0))?;
let plain = decrypt_encrypted_data(body, pw_bmp, password)?;
parse_safe_contents(&plain, pw_bmp, password, &mut out)?;
} else {
return Err(Error::UnsupportedAlgorithm);
}
}
Ok(out)
}
pub fn build(
pkcs8_key_der: &[u8],
cert_chain_der: &[&[u8]],
password: &str,
friendly_name: Option<&str>,
rng: &mut impl RngCore,
) -> Vec<u8> {
let pw_bmp = password_to_bmp(password);
let iterations = 2048u32;
let mut local_key_id = [0u8; 20];
rng.fill_bytes(&mut local_key_id);
let shrouded = pbes2::encrypt(
pkcs8_key_der,
password.as_bytes(),
&pbes2::Pbes2Params {
kdf: pbes2::KdfChoice::Pbkdf2HmacSha256 {
iterations: 100_000,
},
cipher: pbes2::CipherChoice::Aes256Cbc,
salt_len: 16,
},
rng,
);
let key_bag = encode_safe_bag(
OID_PKCS8_SHROUDED_KEY_BAG,
&shrouded,
friendly_name,
Some(&local_key_id),
);
let key_safe_contents = encode_sequence(&key_bag);
let key_content_info = encode_data_content_info(&key_safe_contents);
let mut cert_bags = Vec::new();
for (idx, cert) in cert_chain_der.iter().enumerate() {
let cert_value = encode_context(0, &encode_octet_string(cert));
let cert_bag_body =
encode_sequence(&[oid_tlv(OID_CERT_TYPE_X509), cert_value].concat());
let (fname, lkid) = if idx == 0 {
(friendly_name, Some(&local_key_id[..]))
} else {
(None, None)
};
cert_bags.extend_from_slice(&encode_safe_bag(
OID_CERT_BAG,
&cert_bag_body,
fname,
lkid,
));
}
let cert_safe_contents = encode_sequence(&cert_bags);
let cert_content_info = encode_data_content_info(&cert_safe_contents);
let auth_safe = encode_sequence(&[key_content_info, cert_content_info].concat());
let auth_safe_ci = encode_data_content_info(&auth_safe);
let mut mac_salt = [0u8; 8];
rng.fill_bytes(&mut mac_salt);
let mac_data = build_mac_data(&auth_safe, &pw_bmp, &mac_salt, iterations);
let version = encode_integer(&[0x03]);
let pfx = encode_sequence(&[version, auth_safe_ci, mac_data].concat());
let mut pw_bmp = pw_bmp;
for b in pw_bmp.iter_mut() {
*b = 0;
}
let _ = core::hint::black_box(&pw_bmp);
pfx
}
}
fn read_content_info_data<'a>(r: &mut Reader<'a>) -> Result<&'a [u8], Error> {
let mut ci = r.read_sequence()?;
let oid = parse_oid(ci.read_oid()?)?;
if oid.as_slice() != OID_DATA {
return Err(Error::Malformed);
}
let ctx_body = ci.read_tlv(tag::context(0))?;
let mut inner = Reader::new(ctx_body);
let octets = inner.read_octet_string()?;
inner.finish()?;
Ok(octets)
}
fn encode_data_content_info(inner: &[u8]) -> Vec<u8> {
let octet = encode_octet_string(inner);
let ctx = encode_context(0, &octet);
encode_sequence(&[oid_tlv(OID_DATA), ctx].concat())
}
fn parse_safe_contents(
der: &[u8],
pw_bmp: &[u8],
password: &str,
out: &mut Parsed,
) -> Result<(), Error> {
let mut reader = Reader::new(der);
let mut seq = reader.read_sequence()?;
reader.finish()?;
while !seq.is_empty() {
let mut bag = seq.read_sequence()?;
let bag_oid = parse_oid(bag.read_oid()?)?;
let oid = bag_oid.as_slice();
let bag_value = bag.read_tlv(tag::context(0))?;
if let Some(t) = bag.peek_tag()
&& t == tag::SET
{
let attrs = bag.read_tlv(tag::SET)?;
if let Some(name) = extract_friendly_name(attrs) {
out.friendly_names.push(name);
}
}
bag.finish().ok();
if oid == OID_KEY_BAG {
out.keys.push(bag_value.to_vec());
} else if oid == OID_PKCS8_SHROUDED_KEY_BAG {
let plain = decrypt_shrouded_key(bag_value, pw_bmp, password)?;
out.keys.push(plain);
} else if oid == OID_CERT_BAG {
let mut cb = Reader::new(bag_value);
let mut cbs = cb.read_sequence()?;
let cert_id = parse_oid(cbs.read_oid()?)?;
if cert_id.as_slice() == OID_CERT_TYPE_X509 {
let ctx = cbs.read_tlv(tag::context(0))?;
let mut inner = Reader::new(ctx);
let cert = inner.read_octet_string()?;
out.certs.push(cert.to_vec());
}
}
}
Ok(())
}
fn extract_friendly_name(attrs: &[u8]) -> Option<String> {
let mut r = Reader::new(attrs);
while !r.is_empty() {
let mut attr = r.read_sequence().ok()?;
let oid = parse_oid(attr.read_oid().ok()?).ok()?;
let values = attr.read_tlv(tag::SET).ok()?;
if oid.as_slice() == OID_FRIENDLY_NAME {
let mut vr = Reader::new(values);
let (vtag, body) = vr.read_any().ok()?;
if vtag == 0x1e {
let mut units = Vec::with_capacity(body.len() / 2);
for pair in body.chunks_exact(2) {
units.push(u16::from_be_bytes([pair[0], pair[1]]));
}
return String::from_utf16(&units).ok();
}
}
}
None
}
fn decrypt_encrypted_data(der: &[u8], pw_bmp: &[u8], password: &str) -> Result<Vec<u8>, Error> {
let mut reader = Reader::new(der);
let mut ed = reader.read_sequence()?;
let _version = ed.read_integer_bytes()?;
let mut eci = ed.read_sequence()?;
let _content_type = parse_oid(eci.read_oid()?)?;
let alg = eci.read_element()?;
let enc_content = eci.read_tlv(0x80)?;
let mut alg_r = Reader::new(alg);
let mut alg_seq = alg_r.read_sequence()?;
let alg_oid = parse_oid(alg_seq.read_oid()?)?;
if alg_oid.as_slice() == OID_PBES2 {
return pbes2_p12::decrypt(alg, enc_content, password.as_bytes());
}
decrypt_pbe_blob(alg, enc_content, pw_bmp)
}
fn decrypt_shrouded_key(der: &[u8], pw_bmp: &[u8], password: &str) -> Result<Vec<u8>, Error> {
let mut reader = Reader::new(der);
let mut epki = reader.read_sequence()?;
let alg = epki.read_element()?;
let mut alg_r = Reader::new(alg);
let mut alg_seq = alg_r.read_sequence()?;
let alg_oid = parse_oid(alg_seq.read_oid()?)?;
if alg_oid.as_slice() == OID_PBES2 {
let enc = epki.read_octet_string()?;
pbes2_p12::decrypt(alg, enc, password.as_bytes())
} else {
let enc = epki.read_octet_string()?;
decrypt_pbe_blob(alg, enc, pw_bmp)
}
}
fn decrypt_pbe_blob(alg: &[u8], ciphertext: &[u8], pw_bmp: &[u8]) -> Result<Vec<u8>, Error> {
let mut alg_r = Reader::new(alg);
let mut alg_seq = alg_r.read_sequence()?;
let alg_oid = parse_oid(alg_seq.read_oid()?)?;
let oid = alg_oid.as_slice();
if oid == OID_PBE_SHA1_3DES {
let mut params = alg_seq.read_sequence()?;
let salt = params.read_octet_string()?.to_vec();
let iterations = read_iterations(&mut params)?;
if !(MIN_ITERATIONS..=MAX_ITERATIONS).contains(&iterations) {
return Err(Error::BadParameters);
}
return pbe_sha1_3des_decrypt(pw_bmp, &salt, iterations, ciphertext);
}
Err(Error::UnsupportedAlgorithm)
}
fn pbe_sha1_3des_decrypt(
pw_bmp: &[u8],
salt: &[u8],
iterations: u32,
ciphertext: &[u8],
) -> Result<Vec<u8>, Error> {
use crate::cipher::{Cbc64, TdesEde3};
if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(8) {
return Err(Error::Decryption);
}
let mut key = [0u8; 24];
let mut iv = [0u8; 8];
derive(PkcsHash::Sha1, pw_bmp, salt, iterations, ID_KEY, &mut key);
derive(PkcsHash::Sha1, pw_bmp, salt, iterations, ID_IV, &mut iv);
let mut buf = ciphertext.to_vec();
let mut cbc = Cbc64::new(TdesEde3::new(&key), &iv);
for b in key.iter_mut() {
*b = 0;
}
let _ = core::hint::black_box(&key);
cbc.decrypt(&mut buf).map_err(|_| Error::Decryption)?;
strip_pkcs7(buf, 8)
}
fn strip_pkcs7(mut buf: Vec<u8>, block: usize) -> Result<Vec<u8>, Error> {
let n = buf.len();
if n == 0 || block == 0 || block > 255 || !n.is_multiple_of(block) {
return Err(Error::Decryption);
}
let last = buf[n - 1];
let block_u8 = block as u8;
let range_ok = ct_nonzero_u8(last) & ct_le_u8(last, block_u8);
let mut bytes_ok: u8 = 0xFF;
let start = n - block;
for (i, &b) in buf[start..].iter().enumerate() {
let pos_from_end = block_u8 - i as u8; let is_pad = ct_le_u8(pos_from_end, last); let byte_bad = is_pad & ct_nonzero_u8(b ^ last);
bytes_ok &= !byte_bad;
}
if (range_ok & bytes_ok) != 0xFF {
for x in buf.iter_mut() {
*x = 0;
}
let _ = core::hint::black_box(&buf);
return Err(Error::Decryption);
}
buf.truncate(n - last as usize);
Ok(buf)
}
#[inline]
fn ct_le_u8(x: u8, y: u8) -> u8 {
let diff = y as i16 - x as i16;
let sign = ((diff as u16) >> 15) as u8; sign.wrapping_sub(1) }
#[inline]
fn ct_nonzero_u8(x: u8) -> u8 {
let mut v = x;
v |= v >> 4;
v |= v >> 2;
v |= v >> 1;
0u8.wrapping_sub(v & 1)
}
fn verify_mac(mac: &[u8], content: &[u8], password: &str, pw_bmp: &[u8]) -> Result<(), Error> {
let mut reader = Reader::new(mac);
let mut md = reader.read_sequence()?;
let mut di = md.read_sequence()?;
let mut alg = di.read_sequence()?;
let alg_oid = parse_oid(alg.read_oid()?)?;
let expected = di.read_octet_string()?.to_vec();
di.finish().ok();
let salt = md.read_octet_string()?.to_vec();
let iterations = if md.peek_tag().is_some() {
read_iterations(&mut md)?
} else {
1
};
md.finish().ok();
if iterations > MAX_ITERATIONS {
return Err(Error::BadParameters);
}
let oid = alg_oid.as_slice();
let computed = if oid == OID_SHA1 || oid == OID_HMAC_SHA1 {
sha_based_hmac(PkcsHash::Sha1, pw_bmp, &salt, iterations, content)
} else if oid == OID_SHA256 || oid == OID_HMAC_SHA256 {
sha_based_hmac(PkcsHash::Sha256, pw_bmp, &salt, iterations, content)
} else if oid == OID_PBMAC1 {
pbmac1_compute(&mut alg, password, content)?
} else {
return Err(Error::UnsupportedAlgorithm);
};
if ct_eq(&computed, &expected) {
Ok(())
} else {
Err(Error::MacMismatch)
}
}
fn sha_based_hmac(
hash: PkcsHash,
pw_bmp: &[u8],
salt: &[u8],
iterations: u32,
content: &[u8],
) -> Vec<u8> {
match hash {
PkcsHash::Sha1 => {
let mut key = [0u8; 20];
derive(hash, pw_bmp, salt, iterations, ID_MAC, &mut key);
let tag = Hmac::<Sha1>::mac(&key, content);
for b in key.iter_mut() {
*b = 0;
}
tag.as_ref().to_vec()
}
PkcsHash::Sha256 => {
let mut key = [0u8; 32];
derive(hash, pw_bmp, salt, iterations, ID_MAC, &mut key);
let tag = Hmac::<Sha256>::mac(&key, content);
for b in key.iter_mut() {
*b = 0;
}
tag.as_ref().to_vec()
}
}
}
fn pbmac1_compute(alg: &mut Reader<'_>, password: &str, content: &[u8]) -> Result<Vec<u8>, Error> {
let mut params = alg.read_sequence()?;
let mut kdf = params.read_sequence()?;
let kdf_oid = parse_oid(kdf.read_oid()?)?;
if kdf_oid.as_slice() != OID_PBKDF2 {
return Err(Error::UnsupportedAlgorithm);
}
let mut p = kdf.read_sequence()?;
let salt = p.read_octet_string()?.to_vec();
let iterations = read_iterations(&mut p)?;
let mut key_len = 32usize;
if let Some(t) = p.peek_tag()
&& t == tag::INTEGER
{
key_len = read_iterations(&mut p)? as usize;
}
if let Some(t) = p.peek_tag()
&& t == tag::SEQUENCE
{
let mut prf = p.read_sequence()?;
let prf_oid = parse_oid(prf.read_oid()?)?;
if prf_oid.as_slice() != OID_HMAC_SHA256 {
return Err(Error::UnsupportedAlgorithm);
}
}
let mut mas = params.read_sequence()?;
let mas_oid = parse_oid(mas.read_oid()?)?;
if mas_oid.as_slice() != OID_HMAC_SHA256 {
return Err(Error::UnsupportedAlgorithm);
}
if !(MIN_ITERATIONS..=MAX_ITERATIONS).contains(&iterations) {
return Err(Error::BadParameters);
}
let mut key = vec![0u8; key_len];
crate::kdf::pbkdf2::<Sha256>(password.as_bytes(), &salt, iterations, &mut key);
let tag = Hmac::<Sha256>::mac(&key, content);
for b in key.iter_mut() {
*b = 0;
}
Ok(tag.as_ref().to_vec())
}
fn build_mac_data(content: &[u8], pw_bmp: &[u8], salt: &[u8], iterations: u32) -> Vec<u8> {
let tag = sha_based_hmac(PkcsHash::Sha256, pw_bmp, salt, iterations, content);
let alg = encode_sequence(&[oid_tlv(OID_SHA256), crate::der::encode_null()].concat());
let digest_info = encode_sequence(&[alg, encode_octet_string(&tag)].concat());
let salt_os = encode_octet_string(salt);
let iter = encode_integer(&iterations.to_be_bytes());
encode_sequence(&[digest_info, salt_os, iter].concat())
}
fn encode_safe_bag(
bag_id: &[u64],
bag_value_der: &[u8],
friendly_name: Option<&str>,
local_key_id: Option<&[u8]>,
) -> Vec<u8> {
let bag_value = encode_context(0, bag_value_der);
let mut body = Vec::new();
body.extend_from_slice(&oid_tlv(bag_id));
body.extend_from_slice(&bag_value);
let mut attrs = Vec::new();
if let Some(name) = friendly_name {
attrs.extend_from_slice(&encode_attribute(
OID_FRIENDLY_NAME,
&encode_bmp_string(name),
));
}
if let Some(id) = local_key_id {
attrs.extend_from_slice(&encode_attribute(
OID_LOCAL_KEY_ID,
&encode_octet_string(id),
));
}
if !attrs.is_empty() {
body.extend_from_slice(&encode_tlv_set(&attrs));
}
encode_sequence(&body)
}
fn encode_attribute(oid: &[u64], value_der: &[u8]) -> Vec<u8> {
let values = encode_tlv_set(value_der);
encode_sequence(&[oid_tlv(oid), values].concat())
}
fn encode_tlv_set(content: &[u8]) -> Vec<u8> {
crate::der::encode_tlv(tag::SET, content)
}
fn encode_bmp_string(s: &str) -> Vec<u8> {
let mut bytes = Vec::new();
for unit in s.encode_utf16() {
bytes.extend_from_slice(&unit.to_be_bytes());
}
crate::der::encode_tlv(0x1e, &bytes)
}
fn read_iterations(r: &mut Reader<'_>) -> Result<u32, Error> {
let bytes = r.read_integer_bytes()?;
if bytes.is_empty() || bytes[0] & 0x80 != 0 {
return Err(Error::Malformed);
}
let trimmed = if bytes.len() > 1 && bytes[0] == 0 {
&bytes[1..]
} else {
bytes
};
if trimmed.len() > 4 {
return Err(Error::BadParameters);
}
let mut acc: u32 = 0;
for &b in trimmed {
acc = (acc << 8) | b as u32;
}
Ok(acc)
}
fn ct_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}
#[cfg(test)]
mod tests;