use crate::{ContentInfo, Error, ErrorCode, Octets, Result, SignedData};
use crate::{attribute::Attribute, enveloped::EncodedValue, signer::required};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Element, Integer, Oid, Reader, Tag, universal};
use structured_pkix::AlgorithmIdentifier;
mod process;
pub mod protection;
pub use process::{
Backend, Cause, Decision, Failure, Fragments, Origin, Plaintext, Source, legacy_password,
process,
};
const FIELD: &str = "personal";
const DATA: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 1]);
const SIGNED: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 2]);
const ENCRYPTED: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 7, 6]);
#[expect(
clippy::large_enum_variant,
reason = "one PFX mode lives inline; boxing would add allocation and indirection to every inspected container"
)]
pub enum Integrity<'a> {
Password {
safe: Octets<'a>,
mac: Option<MacData<'a>>,
},
Signed(SignedData<'a>),
}
pub struct Pfx<'a> {
integrity: Integrity<'a>,
original: EncodedValue<'a>,
}
impl<'a> Pfx<'a> {
pub fn read(parent: &mut Reader<'a>) -> Result<Self> {
let element = required(parent, FIELD)?;
let value = Self::read_element(parent, &element)?;
end(parent)?;
Ok(value)
}
pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
let mut inside = sequence(parent, element)?;
let version = number(&mut inside)?;
if version.as_u64() != Some(3) {
return Err(invalid(ErrorCode::UnsupportedVersion, element));
}
let envelope = required(&mut inside, FIELD)?;
let (info, reader) = ContentInfo::read_element(&inside, &envelope)?;
let mac = inside
.next_element()
.map_err(encoding)?
.map(|value| MacData::read(&inside, &value))
.transpose()?;
end(&inside)?;
let integrity = if equal_oid(&inside, info.kind(), DATA)? {
Integrity::Password {
safe: Octets::read(&reader, info.content(), FIELD)?,
mac,
}
} else if equal_oid(&inside, info.kind(), SIGNED)? {
if mac.is_some() {
return Err(invalid(ErrorCode::FieldOrder, element));
}
let signed = SignedData::read_element(&reader, info.content())?;
if !equal_oid(&reader, signed.content().kind(), DATA)? {
return Err(invalid(ErrorCode::UnexpectedContentType, info.content()));
}
if signed.content().content().is_none() {
return Err(invalid(ErrorCode::DetachedContent, info.content()));
}
Integrity::Signed(signed)
} else {
return Err(invalid(ErrorCode::UnexpectedContentType, &envelope));
};
Ok(Self {
integrity,
original: EncodedValue::new(parent, *element),
})
}
#[must_use]
pub fn original(&self) -> &EncodedValue<'a> {
&self.original
}
#[must_use]
pub fn integrity(&self) -> &Integrity<'a> {
&self.integrity
}
pub(crate) fn safe(&self) -> &Octets<'a> {
match &self.integrity {
Integrity::Password { safe, .. } => safe,
Integrity::Signed(signed) => signed.content().content().expect("checked attached PFX"),
}
}
}
pub struct MacData<'a> {
pub algorithm: AlgorithmIdentifier<'a>,
pub digest: Octets<'a>,
pub salt: Octets<'a>,
pub iterations: Option<Integer<'a>>,
pub pbmac1: Option<protection::Pair<'a>>,
}
impl<'a> MacData<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
let mut inside = sequence(parent, element)?;
let digest_info = required(&mut inside, FIELD)?;
let mut digest_info = sequence(&inside, &digest_info)?;
let algorithm = algorithm(&mut digest_info)?;
let value = required(&mut digest_info, FIELD)?;
let digest = Octets::read(&digest_info, &value, FIELD)?;
end(&digest_info)?;
let value = required(&mut inside, FIELD)?;
let salt = Octets::read(&inside, &value, FIELD)?;
let iterations = if inside.is_empty() {
None
} else {
Some(number(&mut inside)?)
};
end(&inside)?;
if let Some(rounds) = iterations {
default_one(parent, rounds, element)?;
}
let pbmac1 = if equal_oid(parent, algorithm.algorithm(), protection::PBMAC1)? {
let pair = protection::Pair::read(algorithm, parent)?;
if let Some(width) = protection::hmac_width(pair.scheme, parent)?
&& digest.len() != width
{
return Err(invalid(ErrorCode::DigestWidth, element));
}
if let Some(kdf) = pair.pbkdf2.as_ref()
&& kdf.key_length.is_none()
{
return Err(invalid(ErrorCode::MissingField, element));
}
Some(pair)
} else {
match structured_pkix::algorithm::digest::algorithm(&algorithm, &parent.execution())
.map_err(Error::pkix)?
{
structured_asn1::digest::Resolution::Supported(hash)
if digest.len() != hash.width() =>
{
return Err(invalid(ErrorCode::DigestWidth, element));
}
structured_asn1::digest::Resolution::Malformed => {
return Err(invalid(ErrorCode::AlgorithmSyntax, element));
}
_ => {}
}
if let Some(rounds) = iterations {
positive(rounds, element)?;
}
None
};
Ok(Self {
algorithm,
digest,
salt,
iterations,
pbmac1,
})
}
}
pub enum Safe<'a> {
Data(Octets<'a>),
Encrypted(EncryptedData<'a>),
Enveloped(crate::enveloped::EnvelopedData<'a>),
Other {
kind: Oid<'a>,
value: EncodedValue<'a>,
},
}
impl<'a> Safe<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
let (info, reader) = ContentInfo::read_element(parent, element)?;
if equal_oid(parent, info.kind(), DATA)? {
Ok(Self::Data(Octets::read(&reader, info.content(), FIELD)?))
} else if equal_oid(parent, info.kind(), ENCRYPTED)? {
Ok(Self::Encrypted(EncryptedData::read(
&reader,
info.content(),
)?))
} else if equal_oid(parent, info.kind(), crate::enveloped::IDENTIFIER)? {
Ok(Self::Enveloped(
crate::enveloped::EnvelopedData::read_element(&reader, info.content())?,
))
} else {
Ok(Self::Other {
kind: info.kind(),
value: EncodedValue::new(&reader, *info.content()),
})
}
}
}
pub struct EncryptedData<'a> {
pub content: crate::enveloped::EncryptedContent<'a>,
pub attributes: Option<OwnedVec<Attribute<'a>>>,
pub protection: protection::Protection<'a>,
}
impl<'a> EncryptedData<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
let mut inside = sequence(parent, element)?;
let version = number(&mut inside)?;
let info = required(&mut inside, FIELD)?;
let content = crate::enveloped::encrypted_content(&inside, &info)?;
let attributes = inside
.next_element()
.map_err(encoding)?
.map(|set| attributes(&inside, &set, Tag::context(true, 1)))
.transpose()?;
end(&inside)?;
if version.as_u64() != Some(if attributes.is_some() { 2 } else { 0 }) {
return Err(invalid(ErrorCode::VersionMismatch, element));
}
if attributes.as_ref().is_some_and(|a| a.is_empty()) {
return Err(invalid(ErrorCode::EmptyAttributes, element));
}
let protection = protection::Protection::read(content.algorithm, parent)?;
Ok(Self {
content,
attributes,
protection,
})
}
}
pub struct PrivateKey<'a> {
pub version: u64,
pub algorithm: AlgorithmIdentifier<'a>,
pub secret: Octets<'a>,
pub attributes: Option<OwnedVec<Attribute<'a>>>,
pub public_key: Option<EncodedValue<'a>>,
}
impl<'a> PrivateKey<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
let mut inside = sequence(parent, element)?;
let version = number(&mut inside)?
.as_u64()
.ok_or_else(|| invalid(ErrorCode::ValueTooLarge, element))?;
let algorithm = algorithm(&mut inside)?;
let value = required(&mut inside, FIELD)?;
let secret = Octets::read(&inside, &value, FIELD)?;
let mut next = inside.next_element().map_err(encoding)?;
let attributes = if let Some(set) = next.filter(|v| v.tag() == Tag::context(true, 0)) {
let attributes = attributes(&inside, &set, Tag::context(true, 0))?;
next = inside.next_element().map_err(encoding)?;
Some(attributes)
} else {
None
};
let public_key = if let Some(bits) = next {
if bits.tag() != Tag::context(false, 1) && bits.tag() != Tag::context(true, 1) {
return Err(invalid(ErrorCode::UnexpectedTag, &bits));
}
structured_pkix::der::implicit_bit_string(&inside, &bits, FIELD)
.map_err(Error::pkix)?;
Some(EncodedValue::new(&inside, bits))
} else {
None
};
end(&inside)?;
if version != u64::from(public_key.is_some()) {
return Err(invalid(ErrorCode::VersionMismatch, element));
}
Ok(Self {
version,
algorithm,
secret,
attributes,
public_key,
})
}
}
pub struct ShroudedKey<'a> {
pub algorithm: AlgorithmIdentifier<'a>,
pub encrypted: Octets<'a>,
pub protection: protection::Protection<'a>,
}
impl<'a> ShroudedKey<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
let mut inside = sequence(parent, element)?;
let algorithm = algorithm(&mut inside)?;
let value = required(&mut inside, FIELD)?;
let encrypted = Octets::read(&inside, &value, FIELD)?;
end(&inside)?;
let protection = protection::Protection::read(algorithm, parent)?;
Ok(Self {
algorithm,
encrypted,
protection,
})
}
}
#[expect(
clippy::large_enum_variant,
reason = "one bag is decoded/visited at a time; inline protection facts avoid an extra heap owner per shrouded key"
)]
pub enum BagValue<'a> {
Key(PrivateKey<'a>),
Shrouded(ShroudedKey<'a>),
Certificate(Octets<'a>),
Sdsi(Octets<'a>),
Crl(Octets<'a>),
Secret {
kind: Oid<'a>,
value: EncodedValue<'a>,
},
Other {
kind: Oid<'a>,
value: EncodedValue<'a>,
},
Contents(EncodedValue<'a>),
}
pub struct Bag<'a> {
pub kind: Oid<'a>,
pub original: EncodedValue<'a>,
pub value: BagValue<'a>,
pub attributes: Option<OwnedVec<Attribute<'a>>>,
}
impl<'a> Bag<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
let mut inside = sequence(parent, element)?;
let kind = inside.read_oid().map_err(encoding)?;
let wrapper = inside.expect(Tag::context(true, 0)).map_err(encoding)?;
let mut explicit = inside.enter(&wrapper).map_err(encoding)?;
let value = required(&mut explicit, FIELD)?;
end(&explicit)?;
let attributes = inside
.next_element()
.map_err(encoding)?
.map(|set| attributes(&inside, &set, Tag::constructed(universal::SET)))
.transpose()?;
end(&inside)?;
let value = if bag_id(parent, kind, 1)? {
BagValue::Key(PrivateKey::read(&explicit, &value)?)
} else if bag_id(parent, kind, 2)? {
BagValue::Shrouded(ShroudedKey::read(&explicit, &value)?)
} else if bag_id(parent, kind, 6)? {
tag(&value, Tag::constructed(universal::SEQUENCE))?;
BagValue::Contents(EncodedValue::new(&explicit, value))
} else if bag_id(parent, kind, 3)? || bag_id(parent, kind, 4)? || bag_id(parent, kind, 5)? {
let mut typed = sequence(&explicit, &value)?;
let inner_kind = typed.read_oid().map_err(encoding)?;
let wrapper = typed.expect(Tag::context(true, 0)).map_err(encoding)?;
let mut inner = typed.enter(&wrapper).map_err(encoding)?;
let content = required(&mut inner, FIELD)?;
end(&inner)?;
end(&typed)?;
let certificate = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 22, 1]);
let sdsi = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 22, 2]);
let crl = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 23, 1]);
if bag_id(parent, kind, 5)? {
structured_pkix::der::conforms(&inner, &content, FIELD).map_err(Error::pkix)?;
BagValue::Secret {
kind: inner_kind,
value: EncodedValue::new(&inner, content),
}
} else if bag_id(parent, kind, 3)? && equal_oid(parent, inner_kind, certificate)? {
BagValue::Certificate(Octets::read(&inner, &content, FIELD)?)
} else if bag_id(parent, kind, 3)? && equal_oid(parent, inner_kind, sdsi)? {
BagValue::Sdsi(Octets::read_ia5(&inner, &content, FIELD)?)
} else if bag_id(parent, kind, 4)? && equal_oid(parent, inner_kind, crl)? {
BagValue::Crl(Octets::read(&inner, &content, FIELD)?)
} else {
structured_pkix::der::conforms(&inner, &content, FIELD).map_err(Error::pkix)?;
BagValue::Other {
kind: inner_kind,
value: EncodedValue::new(&inner, content),
}
}
} else {
structured_pkix::der::conforms(&explicit, &value, FIELD).map_err(Error::pkix)?;
BagValue::Other {
kind,
value: EncodedValue::new(&explicit, value),
}
};
Ok(Self {
kind,
original: EncodedValue::new(parent, *element),
value,
attributes,
})
}
}
fn bag_id(parent: &Reader<'_>, oid: Oid<'_>, last: u8) -> Result<bool> {
equal_oid(
parent,
oid,
Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 12, 10, 1, last]),
)
}
pub(super) fn equal_oid(parent: &Reader<'_>, left: Oid<'_>, right: Oid<'_>) -> Result<bool> {
parent.charge(1).map_err(encoding)?;
if left.as_bytes().len() != right.as_bytes().len() {
return Ok(false);
}
parent
.charge(left.as_bytes().len() as u64)
.map_err(encoding)?;
Ok(left == right)
}
fn attributes<'a>(
parent: &Reader<'a>,
set: &Element<'a>,
expected: Tag,
) -> Result<OwnedVec<Attribute<'a>>> {
tag(set, expected)?;
let mut inside = parent.enter(set).map_err(encoding)?;
let execution = parent.execution();
let mut out = execution.vector();
let mut previous = None;
while let Some(value) = inside.next_element().map_err(encoding)? {
if inside.encoding().is_der() {
if let Some(bytes) = previous
&& !structured_pkix::der::set_ordered(&inside, bytes, &value, FIELD)
.map_err(Error::pkix)?
{
return Err(invalid(ErrorCode::SetOrder, &value));
}
previous = Some(value.as_bytes());
}
let attribute = Attribute::read(&inside, &value, FIELD)?;
if !inside.encoding().is_der() {
for (ordinal, item) in attribute.values().iter().enumerate() {
let reader = attribute
.value_reader_in(ordinal, &inside, FIELD)?
.expect("ordinal comes from this attribute's original values");
structured_pkix::der::conforms(&reader, item, FIELD).map_err(Error::pkix)?;
}
}
out.try_push(attribute)
.map_err(|e| Error::resource(e, FIELD, value.span().start))?;
}
Ok(out.into_owned())
}
pub(super) fn sequence<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<Reader<'a>> {
tag(element, Tag::constructed(universal::SEQUENCE))?;
parent.enter(element).map_err(encoding)
}
pub(super) fn tag(element: &Element<'_>, expected: Tag) -> Result<()> {
if element.tag() != expected {
return Err(invalid(ErrorCode::UnexpectedTag, element));
}
Ok(())
}
pub(super) fn end(parent: &Reader<'_>) -> Result<()> {
if !parent.is_empty() {
return Err(Error::schema(
ErrorCode::TrailingData,
FIELD,
parent.offset(),
));
}
Ok(())
}
pub(super) fn number<'a>(parent: &mut Reader<'a>) -> Result<Integer<'a>> {
parent.read_integer().map_err(encoding)
}
pub(super) fn positive(value: Integer<'_>, element: &Element<'_>) -> Result<()> {
if value.is_negative() || value.as_u64() == Some(0) {
return Err(invalid(ErrorCode::MalformedValue, element));
}
Ok(())
}
fn default_one(parent: &Reader<'_>, value: Integer<'_>, element: &Element<'_>) -> Result<()> {
if parent.encoding().is_der() && value.as_u64() == Some(1) {
return Err(invalid(ErrorCode::DefaultWritten, element));
}
Ok(())
}
pub(super) fn algorithm<'a>(parent: &mut Reader<'a>) -> Result<AlgorithmIdentifier<'a>> {
let element = required(parent, FIELD)?;
structured_pkix::algorithm::read(parent, &element, FIELD).map_err(Error::pkix)
}
pub(super) fn invalid(code: ErrorCode, element: &Element<'_>) -> Error {
Error::schema(code, FIELD, element.span().start)
}
pub(super) fn encoding(error: structured_asn1::Error) -> Error {
Error::encoding(error, FIELD)
}