use super::{algorithm, encoding, end, equal_oid, invalid, number, positive, sequence};
use crate::{Error, ErrorCode, Octets, Result, signer::required};
use structured_asn1::{Integer, Oid, Reader, Tag, universal};
use structured_pkix::AlgorithmIdentifier;
const FIELD: &str = "personal.protection";
pub const PBES2: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 5, 13]);
pub const PBMAC1: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 5, 14]);
pub const PBKDF2: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 5, 12]);
const SHA1: Oid<'static> = Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 2, 7]);
pub enum Salt<'a> {
Specified(Octets<'a>),
Source(AlgorithmIdentifier<'a>),
}
#[derive(Debug, Clone, Copy)]
pub enum Prf<'a> {
DefaultSha1,
Written(AlgorithmIdentifier<'a>),
}
pub struct Pbkdf2<'a> {
pub salt: Salt<'a>,
pub iterations: Integer<'a>,
pub key_length: Option<Integer<'a>>,
pub prf: Prf<'a>,
}
impl<'a> Pbkdf2<'a> {
pub fn read(identifier: AlgorithmIdentifier<'a>, parent: &Reader<'a>) -> Result<Self> {
session(identifier, parent)?;
if !equal_oid(parent, identifier.algorithm(), PBKDF2)? {
return Err(Error::schema(
ErrorCode::AlgorithmSyntax,
FIELD,
identifier.offset(),
));
}
let mut reader = parameters(identifier, parent)?;
let value = required(&mut reader, FIELD)?;
let mut inside = sequence(&reader, &value)?;
end(&reader)?;
let salt_element = required(&mut inside, FIELD)?;
let salt = if salt_element.tag() == Tag::constructed(universal::SEQUENCE) {
Salt::Source(
structured_pkix::algorithm::read(&inside, &salt_element, FIELD)
.map_err(Error::pkix)?,
)
} else {
Salt::Specified(Octets::read(&inside, &salt_element, FIELD)?)
};
let iterations = number(&mut inside)?;
positive(iterations, &value)?;
let mut next = inside.next_element().map_err(encoding)?;
let key_length =
if let Some(element) = next.filter(|v| v.tag() == Tag::primitive(universal::INTEGER)) {
let length = inside.integer(&element).map_err(encoding)?;
positive(length, &element)?;
next = inside.next_element().map_err(encoding)?;
Some(length)
} else {
None
};
let prf = if let Some(element) = next {
let written =
structured_pkix::algorithm::read(&inside, &element, FIELD).map_err(Error::pkix)?;
hmac_width(written, &inside)?;
if equal_oid(&inside, written.algorithm(), SHA1)? && written.has_null_parameter() {
if inside.encoding().is_der() {
return Err(invalid(ErrorCode::DefaultWritten, &element));
}
}
Prf::Written(written)
} else {
Prf::DefaultSha1
};
end(&inside)?;
Ok(Self {
salt,
iterations,
key_length,
prf,
})
}
}
pub struct Pair<'a> {
pub derivation: AlgorithmIdentifier<'a>,
pub scheme: AlgorithmIdentifier<'a>,
pub pbkdf2: Option<Pbkdf2<'a>>,
}
impl<'a> Pair<'a> {
pub fn read(identifier: AlgorithmIdentifier<'a>, parent: &Reader<'a>) -> Result<Self> {
session(identifier, parent)?;
if !equal_oid(parent, identifier.algorithm(), PBES2)?
&& !equal_oid(parent, identifier.algorithm(), PBMAC1)?
{
return Err(Error::schema(
ErrorCode::AlgorithmSyntax,
FIELD,
identifier.offset(),
));
}
let mut reader = parameters(identifier, parent)?;
let value = required(&mut reader, FIELD)?;
let mut inside = sequence(&reader, &value)?;
end(&reader)?;
let derivation = algorithm(&mut inside)?;
let scheme = algorithm(&mut inside)?;
end(&inside)?;
let pbkdf2 = if equal_oid(&inside, derivation.algorithm(), PBKDF2)? {
Some(Pbkdf2::read(derivation, &inside)?)
} else {
None
};
Ok(Self {
derivation,
scheme,
pbkdf2,
})
}
}
pub struct Legacy<'a> {
pub salt: Octets<'a>,
pub iterations: Integer<'a>,
}
#[expect(
clippy::large_enum_variant,
reason = "one protection descriptor is consumed inline; boxing PBES2 would add an allocation for every password operation"
)]
pub enum Protection<'a> {
Pbes2(Pair<'a>),
Pbes1(Legacy<'a>),
Pkcs12(Legacy<'a>),
Other(AlgorithmIdentifier<'a>),
}
impl<'a> Protection<'a> {
pub fn read(identifier: AlgorithmIdentifier<'a>, parent: &Reader<'a>) -> Result<Self> {
session(identifier, parent)?;
if equal_oid(parent, identifier.algorithm(), PBES2)? {
return Pair::read(identifier, parent).map(Self::Pbes2);
}
let mut pbes1 = false;
for last in [1, 3, 4, 6, 10, 11] {
pbes1 |= equal_oid(
parent,
identifier.algorithm(),
Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 5, last]),
)?;
}
let mut pkcs12 = false;
for last in 1..=6 {
pkcs12 |= equal_oid(
parent,
identifier.algorithm(),
Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 12, 1, last]),
)?;
}
if !pbes1 && !pkcs12 {
return Ok(Self::Other(identifier));
}
let mut reader = parameters(identifier, parent)?;
let value = required(&mut reader, FIELD)?;
let mut inside = sequence(&reader, &value)?;
end(&reader)?;
let salt_value = required(&mut inside, FIELD)?;
let salt = Octets::read(&inside, &salt_value, FIELD)?;
let iterations = number(&mut inside)?;
positive(iterations, &value)?;
end(&inside)?;
if pbes1 && salt.len() != 8 {
return Err(invalid(ErrorCode::MalformedValue, &salt_value));
}
let result = Legacy { salt, iterations };
Ok(if pbes1 {
Self::Pbes1(result)
} else {
Self::Pkcs12(result)
})
}
}
fn parameters<'a>(identifier: AlgorithmIdentifier<'a>, parent: &Reader<'a>) -> Result<Reader<'a>> {
identifier
.parameters_reader_in(parent, FIELD)
.map_err(Error::pkix)?
.ok_or_else(|| Error::schema(ErrorCode::MissingField, FIELD, identifier.offset()))
}
fn session(identifier: AlgorithmIdentifier<'_>, parent: &Reader<'_>) -> Result<()> {
if !identifier.same_session(parent) {
return Err(Error::pkix(structured_pkix::Error::new(
structured_pkix::ErrorCode::ForeignReader,
FIELD,
identifier.offset(),
)));
}
Ok(())
}
pub(super) fn hmac_width(
identifier: AlgorithmIdentifier<'_>,
parent: &Reader<'_>,
) -> Result<Option<usize>> {
session(identifier, parent)?;
let bytes = identifier.algorithm().as_bytes();
parent.charge(bytes.len() as u64 + 1).map_err(encoding)?;
let width = match bytes {
[42, 134, 72, 134, 247, 13, 2, 7] => 20,
[42, 134, 72, 134, 247, 13, 2, 8 | 12] => 28,
[42, 134, 72, 134, 247, 13, 2, 9 | 13] => 32,
[42, 134, 72, 134, 247, 13, 2, 10] => 48,
[42, 134, 72, 134, 247, 13, 2, 11] => 64,
_ => return Ok(None),
};
if identifier.parameters().is_some() && !identifier.has_null_parameter() {
return Err(Error::schema(
ErrorCode::AlgorithmSyntax,
FIELD,
identifier.offset(),
));
}
Ok(Some(width))
}