use synta::traits::Encode;
use crate::pkcs5_types::{Pkcs5AlgorithmIdentifier, Pkcs5Pbes2Params, Pkcs5Pbkdf2Params};
#[derive(Debug, Default)]
pub struct Pbkdf2ParamsBuilder {
salt: Option<Vec<u8>>,
iteration_count: Option<u64>,
key_length: Option<u64>,
prf_oid: Option<Vec<u32>>,
error: Option<String>,
}
impl Pbkdf2ParamsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn salt(mut self, salt: &[u8]) -> Self {
if self.error.is_some() {
return self;
}
self.salt = Some(salt.to_vec());
self
}
pub fn iteration_count(mut self, count: u64) -> Self {
if self.error.is_some() {
return self;
}
if count == 0 {
self.error = Some("iteration_count must be at least 1".to_string());
return self;
}
if count > i64::MAX as u64 {
self.error = Some(format!(
"iteration_count {} exceeds maximum representable DER INTEGER value",
count
));
return self;
}
self.iteration_count = Some(count);
self
}
pub fn key_length(mut self, len: u64) -> Self {
if self.error.is_some() {
return self;
}
if len > i64::MAX as u64 {
self.error = Some(format!(
"key_length {} exceeds maximum representable DER INTEGER value",
len
));
return self;
}
self.key_length = Some(len);
self
}
pub fn prf(mut self, prf_oid: &[u32]) -> Self {
if self.error.is_some() {
return self;
}
self.prf_oid = Some(prf_oid.to_vec());
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
self.build_inner()
}
pub fn build_as_algorithm_identifier(self, alg_oid: &[u32]) -> Result<Vec<u8>, String> {
let params_bytes = self.build_inner()?;
encode_alg_id_with_params(alg_oid, ¶ms_bytes)
}
fn build_inner(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
let salt_bytes = self.salt.ok_or_else(|| "salt is required".to_string())?;
let iter_count = self
.iteration_count
.ok_or_else(|| "iteration_count is required".to_string())?;
let salt_ref = synta::OctetStringRef::new(&salt_bytes);
let iteration_count = synta::Integer::from(iter_count as i64);
let key_length = self.key_length.map(|n| synta::Integer::from(n as i64));
let prf_bytes: Option<Vec<u8>> = if let Some(ref oid_comps) = self.prf_oid {
let oid = synta::ObjectIdentifier::new(oid_comps)
.map_err(|e| format!("invalid PRF OID: {e}"))?;
let prf_alg = Pkcs5AlgorithmIdentifier {
algorithm: oid,
parameters: None,
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
prf_alg
.encode(&mut enc)
.map_err(|e| format!("PRF AlgorithmIdentifier encode failed: {e}"))?;
Some(
enc.finish()
.map_err(|e| format!("PRF AlgorithmIdentifier finish failed: {e}"))?,
)
} else {
None
};
let prf: Option<Pkcs5AlgorithmIdentifier<'_>> = if let Some(ref bytes) = prf_bytes {
Some(
synta::Decoder::new(bytes, synta::Encoding::Der)
.decode::<Pkcs5AlgorithmIdentifier<'_>>()
.map_err(|e| format!("PRF AlgorithmIdentifier re-decode failed: {e}"))?,
)
} else {
None
};
let params = Pkcs5Pbkdf2Params {
salt: salt_ref,
iteration_count,
key_length,
prf,
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
params
.encode(&mut enc)
.map_err(|e| format!("Pkcs5Pbkdf2Params DER encoding failed: {e}"))?;
enc.finish().map_err(|e| format!("DER finish failed: {e}"))
}
}
#[derive(Debug, Default)]
pub struct Pbes2ParamsBuilder {
kdf_der: Option<Vec<u8>>,
enc_scheme_der: Option<Vec<u8>>,
error: Option<String>,
}
impl Pbes2ParamsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn key_derivation_func(mut self, alg_id_der: &[u8]) -> Self {
if self.error.is_some() {
return self;
}
self.kdf_der = Some(alg_id_der.to_vec());
self
}
pub fn encryption_scheme(mut self, alg_id_der: &[u8]) -> Self {
if self.error.is_some() {
return self;
}
self.enc_scheme_der = Some(alg_id_der.to_vec());
self
}
pub fn encryption_scheme_from_oid(mut self, enc_oid: &[u32], iv: &[u8]) -> Self {
if self.error.is_some() {
return self;
}
if iv.len() != 16 {
self.error = Some(format!(
"IV must be exactly 16 bytes for AES-CBC-PAD; got {} bytes",
iv.len()
));
return self;
}
match encode_alg_id_with_octet_string_param(enc_oid, iv) {
Ok(bytes) => self.enc_scheme_der = Some(bytes),
Err(e) => self.error = Some(e),
}
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
let kdf_bytes = self
.kdf_der
.ok_or_else(|| "key_derivation_func is required".to_string())?;
let enc_bytes = self
.enc_scheme_der
.ok_or_else(|| "encryption_scheme is required".to_string())?;
let params = Pkcs5Pbes2Params {
key_derivation_func: synta::RawDer(&kdf_bytes),
encryption_scheme: synta::RawDer(&enc_bytes),
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
params
.encode(&mut enc)
.map_err(|e| format!("Pkcs5Pbes2Params DER encoding failed: {e}"))?;
enc.finish().map_err(|e| format!("DER finish failed: {e}"))
}
}
fn encode_alg_id_with_params(oid_comps: &[u32], params_der: &[u8]) -> Result<Vec<u8>, String> {
let oid = synta::ObjectIdentifier::new(oid_comps)
.map_err(|e| format!("invalid algorithm OID: {e}"))?;
let alg = Pkcs5AlgorithmIdentifier {
algorithm: oid,
parameters: Some(synta::RawDer(params_der)),
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
alg.encode(&mut enc)
.map_err(|e| format!("AlgorithmIdentifier encode failed: {e}"))?;
enc.finish()
.map_err(|e| format!("AlgorithmIdentifier finish failed: {e}"))
}
fn encode_alg_id_with_octet_string_param(oid_comps: &[u32], iv: &[u8]) -> Result<Vec<u8>, String> {
let os = synta::OctetString::new(iv.to_vec());
let mut iv_enc = synta::Encoder::new(synta::Encoding::Der);
os.encode(&mut iv_enc)
.map_err(|e| format!("IV OCTET STRING encode failed: {e}"))?;
let iv_der = iv_enc
.finish()
.map_err(|e| format!("IV OCTET STRING finish failed: {e}"))?;
encode_alg_id_with_params(oid_comps, &iv_der)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pkcs5_types;
const TEST_SALT: &[u8] = &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
const TEST_IV: &[u8] = &[0u8; 16];
#[test]
fn pbkdf2_params_minimal() {
let der = Pbkdf2ParamsBuilder::new()
.salt(TEST_SALT)
.iteration_count(1000)
.build()
.expect("build should succeed");
let decoded =
pkcs5_types::Pkcs5Pbkdf2Params::from_der(&der).expect("round-trip decode failed");
assert_eq!(decoded.iteration_count.as_i64().ok(), Some(1000));
assert!(decoded.key_length.is_none());
assert!(decoded.prf.is_none());
}
#[test]
fn pbkdf2_params_with_sha256_prf() {
let der = Pbkdf2ParamsBuilder::new()
.salt(TEST_SALT)
.iteration_count(10_000)
.key_length(32)
.prf(pkcs5_types::ID_HMAC_WITH_SHA256_P5)
.build()
.expect("build with SHA-256 PRF should succeed");
let decoded =
pkcs5_types::Pkcs5Pbkdf2Params::from_der(&der).expect("round-trip decode failed");
assert_eq!(decoded.iteration_count.as_i64().ok(), Some(10_000));
assert_eq!(
decoded.key_length.as_ref().and_then(|n| n.as_i64().ok()),
Some(32)
);
assert!(decoded.prf.is_some());
}
#[test]
fn pbkdf2_params_missing_salt_returns_error() {
let err = Pbkdf2ParamsBuilder::new().iteration_count(1000).build();
assert!(err.is_err());
assert!(err.unwrap_err().contains("salt"));
}
#[test]
fn pbkdf2_params_zero_count_returns_error() {
let err = Pbkdf2ParamsBuilder::new()
.salt(TEST_SALT)
.iteration_count(0)
.build();
assert!(err.is_err());
}
#[test]
fn pbes2_params_round_trip() {
let kdf_der = Pbkdf2ParamsBuilder::new()
.salt(TEST_SALT)
.iteration_count(10_000)
.prf(pkcs5_types::ID_HMAC_WITH_SHA256_P5)
.build_as_algorithm_identifier(pkcs5_types::ID_PBKDF2)
.expect("PBKDF2 AlgorithmIdentifier build should succeed");
let pbes2_der = Pbes2ParamsBuilder::new()
.key_derivation_func(&kdf_der)
.encryption_scheme_from_oid(pkcs5_types::AES256_CBC_PAD, TEST_IV)
.build()
.expect("PBES2 params build should succeed");
let _decoded =
pkcs5_types::Pkcs5Pbes2Params::from_der(&pbes2_der).expect("round-trip decode failed");
}
#[test]
fn pbes2_params_missing_kdf_returns_error() {
let err = Pbes2ParamsBuilder::new()
.encryption_scheme_from_oid(pkcs5_types::AES256_CBC_PAD, TEST_IV)
.build();
assert!(err.is_err());
assert!(err.unwrap_err().contains("key_derivation_func"));
}
}