use aes::cipher::{block_padding::Pkcs7, BlockModeDecrypt, KeyIvInit};
use aes::Aes128;
use aes::Aes256;
use cms::content_info::ContentInfo;
use cms::enveloped_data::{
EnvelopedData, KeyAgreeRecipientIdentifier, KeyAgreeRecipientInfo, OriginatorIdentifierOrKey,
RecipientInfo,
};
use const_oid::db::{
rfc5753,
rfc5911::{
ID_AES_128_CBC, ID_AES_128_GCM, ID_AES_128_WRAP, ID_AES_256_CBC, ID_AES_256_GCM,
ID_AES_256_WRAP,
},
rfc5912::{ID_RSAES_OAEP, RSA_ENCRYPTION},
};
use der::{asn1::OctetString, Decode, Encode};
use spki::AlgorithmIdentifierOwned;
use zeroize::Zeroizing;
use crate::error::SmimeError;
use crate::key::{
DecryptionKey, KariAlgorithm, KariKeyAgreement, KeyEncryptionAlgorithm, KeyWrapAlgorithm,
RecipientIdentifier,
};
const ID_ENVELOPED_DATA: der::asn1::ObjectIdentifier = const_oid::db::rfc5911::ID_ENVELOPED_DATA;
pub fn decrypt(enveloped_der: &[u8], key: &dyn DecryptionKey) -> Result<Vec<u8>, SmimeError> {
let ci = ContentInfo::from_der(enveloped_der)?;
if ci.content_type != ID_ENVELOPED_DATA {
return Err(SmimeError::WrongContentType(format!(
"expected EnvelopedData, got OID {}",
ci.content_type
)));
}
let content_der = ci.content.to_der()?;
let env_data = EnvelopedData::from_der(content_der.as_slice())?;
let cek = find_and_decrypt_cek(&env_data, key)?;
let content_enc_oid = env_data.encrypted_content.content_enc_alg.oid;
let ct = env_data
.encrypted_content
.encrypted_content
.as_ref()
.ok_or_else(|| SmimeError::MalformedInput("EnvelopedData has no encrypted content".into()))?
.as_bytes();
if content_enc_oid == ID_AES_128_CBC {
let iv = extract_cbc_iv(&env_data)?;
decrypt_aes128_cbc(&cek, &iv, ct)
} else if content_enc_oid == ID_AES_256_CBC {
let iv = extract_cbc_iv(&env_data)?;
decrypt_aes256_cbc(&cek, &iv, ct)
} else if content_enc_oid == ID_AES_128_GCM || content_enc_oid == ID_AES_256_GCM {
Err(SmimeError::UnsupportedAlgorithm(
"AES-GCM in EnvelopedData is not supported; use AuthEnvelopedData".into(),
))
} else {
Err(SmimeError::UnsupportedAlgorithm(format!(
"content encryption OID {}",
content_enc_oid
)))
}
}
fn find_and_decrypt_cek(
env_data: &EnvelopedData,
key: &dyn DecryptionKey,
) -> Result<Zeroizing<Vec<u8>>, SmimeError> {
for ri in env_data.recip_infos.0.iter() {
match ri {
RecipientInfo::Ktri(ktri) => {
let rid = cms_rid_to_owned(&ktri.rid)?;
if !key.matches_recipient(&rid) {
continue;
}
let alg = map_ktri_alg(ktri)?;
return key
.decrypt_cek(ktri.enc_key.as_bytes(), &alg)
.map(Zeroizing::new);
}
RecipientInfo::Kari(kari) => {
if let Some(cek) = try_decrypt_kari(kari, key)? {
return Ok(cek);
}
}
RecipientInfo::Kekri(_) | RecipientInfo::Pwri(_) | RecipientInfo::Ori(_) => continue,
}
}
Err(SmimeError::NoMatchingRecipient)
}
fn map_ktri_alg(
ktri: &cms::enveloped_data::KeyTransRecipientInfo,
) -> Result<KeyEncryptionAlgorithm, SmimeError> {
let oid = ktri.key_enc_alg.oid;
if oid == RSA_ENCRYPTION {
Ok(KeyEncryptionAlgorithm::RsaPkcs1v15)
} else if oid == ID_RSAES_OAEP {
Ok(KeyEncryptionAlgorithm::RsaOaep)
} else {
Err(SmimeError::UnsupportedAlgorithm(format!(
"key encryption OID {}",
oid
)))
}
}
fn extract_cbc_iv(env_data: &EnvelopedData) -> Result<Vec<u8>, SmimeError> {
let params = env_data
.encrypted_content
.content_enc_alg
.parameters
.as_ref()
.ok_or_else(|| SmimeError::MalformedInput("CBC algorithm parameters missing".into()))?;
let iv = params.decode_as::<OctetString>()?;
Ok(iv.as_bytes().to_vec())
}
fn decrypt_aes128_cbc(cek: &[u8], iv: &[u8], ct: &[u8]) -> Result<Vec<u8>, SmimeError> {
let key: &[u8; 16] = cek
.try_into()
.map_err(|_| SmimeError::MalformedInput("AES-128 CEK must be 16 bytes".into()))?;
let iv: &[u8; 16] = iv
.try_into()
.map_err(|_| SmimeError::MalformedInput("AES-128-CBC IV must be 16 bytes".into()))?;
cbc::Decryptor::<Aes128>::new(key.into(), iv.into())
.decrypt_padded_vec::<Pkcs7>(ct)
.map_err(|e| SmimeError::DecryptionFailed(format!("AES-128-CBC: {e}")))
}
fn cms_rid_to_owned(
rid: &cms::enveloped_data::RecipientIdentifier,
) -> Result<RecipientIdentifier, SmimeError> {
match rid {
cms::enveloped_data::RecipientIdentifier::IssuerAndSerialNumber(ias) => {
let issuer_der = ias.issuer.to_der()?;
let serial = ias.serial_number.as_bytes().to_vec();
Ok(RecipientIdentifier::IssuerAndSerialNumber { issuer_der, serial })
}
cms::enveloped_data::RecipientIdentifier::SubjectKeyIdentifier(ski) => Ok(
RecipientIdentifier::SubjectKeyIdentifier(ski.0.as_bytes().to_vec()),
),
}
}
fn try_decrypt_kari(
kari: &KeyAgreeRecipientInfo,
key: &dyn DecryptionKey,
) -> Result<Option<Zeroizing<Vec<u8>>>, SmimeError> {
let ephemeral_bytes = match &kari.originator {
OriginatorIdentifierOrKey::OriginatorKey(orig) => orig.public_key.raw_bytes().to_vec(),
_ => return Ok(None),
};
let ukm: Option<Vec<u8>> = kari.ukm.as_ref().map(|u| u.as_bytes().to_vec());
let matching_rek = kari.recipient_enc_keys.iter().find(|rek| {
kari_rid_to_owned(&rek.rid)
.map(|rid| key.matches_recipient(&rid))
.unwrap_or(false)
});
let rek = match matching_rek {
Some(r) => r,
None => return Ok(None), };
let kari_alg = map_kari_alg(kari)?;
let cek = key.agree_ecdh(
&ephemeral_bytes,
ukm.as_deref(),
rek.enc_key.as_bytes(),
&kari_alg,
)?;
Ok(Some(Zeroizing::new(cek)))
}
fn map_kari_alg(kari: &KeyAgreeRecipientInfo) -> Result<KariAlgorithm, SmimeError> {
let ecdh_oid = kari.key_enc_alg.oid;
let key_agreement = if ecdh_oid == rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME {
KariKeyAgreement::StdDhSha256Kdf
} else if ecdh_oid == rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME {
KariKeyAgreement::StdDhSha384Kdf
} else {
return Err(SmimeError::UnsupportedAlgorithm(format!(
"ECDH algorithm OID {ecdh_oid}"
)));
};
let params = kari.key_enc_alg.parameters.as_ref().ok_or_else(|| {
SmimeError::MalformedInput("KARI keyEncryptionAlgorithm has no parameters".into())
})?;
let wrap_alg = params.decode_as::<AlgorithmIdentifierOwned>()?;
let key_wrap = if wrap_alg.oid == ID_AES_128_WRAP {
KeyWrapAlgorithm::Aes128Kw
} else if wrap_alg.oid == ID_AES_256_WRAP {
KeyWrapAlgorithm::Aes256Kw
} else {
return Err(SmimeError::UnsupportedAlgorithm(format!(
"key wrap OID {}",
wrap_alg.oid
)));
};
Ok(KariAlgorithm {
key_agreement,
key_wrap,
})
}
fn kari_rid_to_owned(rid: &KeyAgreeRecipientIdentifier) -> Result<RecipientIdentifier, SmimeError> {
match rid {
KeyAgreeRecipientIdentifier::IssuerAndSerialNumber(ias) => {
let issuer_der = ias.issuer.to_der()?;
let serial = ias.serial_number.as_bytes().to_vec();
Ok(RecipientIdentifier::IssuerAndSerialNumber { issuer_der, serial })
}
KeyAgreeRecipientIdentifier::RKeyId(rki) => Ok(RecipientIdentifier::SubjectKeyIdentifier(
rki.subject_key_identifier.0.as_bytes().to_vec(),
)),
}
}
fn decrypt_aes256_cbc(cek: &[u8], iv: &[u8], ct: &[u8]) -> Result<Vec<u8>, SmimeError> {
let key: &[u8; 32] = cek
.try_into()
.map_err(|_| SmimeError::MalformedInput("AES-256 CEK must be 32 bytes".into()))?;
let iv: &[u8; 16] = iv
.try_into()
.map_err(|_| SmimeError::MalformedInput("AES-256-CBC IV must be 16 bytes".into()))?;
cbc::Decryptor::<Aes256>::new(key.into(), iv.into())
.decrypt_padded_vec::<Pkcs7>(ct)
.map_err(|e| SmimeError::DecryptionFailed(format!("AES-256-CBC: {e}")))
}