use aes::cipher::{block_padding::Pkcs7, BlockModeDecrypt, KeyIvInit};
use aes::Aes128;
use aes::Aes256;
use aes_gcm::aead::{Aead, KeyInit as GcmKeyInit};
use cms::authenveloped_data::AuthEnvelopedData;
use cms::content_info::ContentInfo;
use cms::enveloped_data::{
EnvelopedData, KeyAgreeRecipientIdentifier, KeyAgreeRecipientInfo, OriginatorIdentifierOrKey,
RecipientInfo, RecipientInfos,
};
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, ID_CT_AUTH_ENVELOPED_DATA, ID_ENVELOPED_DATA,
},
rfc5912::{ID_EC_PUBLIC_KEY, ID_RSAES_OAEP, RSA_ENCRYPTION, SECP_256_R_1, SECP_384_R_1},
};
use der::{asn1::OctetString, Decode, Encode, Sequence};
use spki::AlgorithmIdentifierOwned;
use zeroize::Zeroizing;
use crate::error::SmimeError;
use crate::key::{
DecryptionKey, KariAlgorithm, KariKeyAgreement, KeyEncryptionAlgorithm, KeyWrapAlgorithm,
RecipientIdentifier,
};
const MAX_ENCRYPTED_CONTENT_SIZE: usize = 64 * 1024 * 1024;
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
struct GcmParameters {
aes_nonce: OctetString,
#[asn1(default = "default_icv_len")]
aes_icv_len: u8,
}
fn default_icv_len() -> u8 {
12
}
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_CT_AUTH_ENVELOPED_DATA {
return decrypt_auth_enveloped(&ci, key);
}
if ci.content_type != ID_ENVELOPED_DATA {
return Err(SmimeError::WrongContentType(format!(
"expected EnvelopedData or AuthEnvelopedData, 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.recip_infos, 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 ct.len() > MAX_ENCRYPTED_CONTENT_SIZE {
return Err(SmimeError::MalformedInput(format!(
"encrypted content too large: {} bytes exceeds {} byte limit",
ct.len(),
MAX_ENCRYPTED_CONTENT_SIZE
)));
}
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 decrypt_auth_enveloped(
ci: &ContentInfo,
key: &dyn DecryptionKey,
) -> Result<Vec<u8>, SmimeError> {
let content_der = ci.content.to_der()?;
let auth_data = AuthEnvelopedData::from_der(content_der.as_slice())?;
let cek = find_and_decrypt_cek(&auth_data.recip_infos, key)?;
let content_enc_oid = auth_data.auth_encrypted_content_info.content_enc_alg.oid;
let params = auth_data
.auth_encrypted_content_info
.content_enc_alg
.parameters
.as_ref()
.ok_or_else(|| SmimeError::MalformedInput("GCM algorithm parameters missing".into()))?;
let gcm_params = params.decode_as::<GcmParameters>()?;
let nonce = gcm_params.aes_nonce.as_bytes();
if nonce.len() != 12 {
return Err(SmimeError::MalformedInput(format!(
"GCM nonce must be 12 bytes, got {}",
nonce.len()
)));
}
let icv_len = gcm_params.aes_icv_len;
if icv_len != 16 {
return Err(SmimeError::UnsupportedAlgorithm(format!(
"GCM tag length must be 16 bytes, got {icv_len}"
)));
}
let ct = auth_data
.auth_encrypted_content_info
.encrypted_content
.as_ref()
.ok_or_else(|| {
SmimeError::MalformedInput("AuthEnvelopedData has no encrypted content".into())
})?
.as_bytes();
if ct.len() > MAX_ENCRYPTED_CONTENT_SIZE {
return Err(SmimeError::MalformedInput(format!(
"encrypted content too large: {} bytes exceeds {} byte limit",
ct.len(),
MAX_ENCRYPTED_CONTENT_SIZE
)));
}
let tag = auth_data.mac.as_bytes();
if tag.len() != 16 {
return Err(SmimeError::MalformedInput(format!(
"GCM auth tag must be 16 bytes, got {}",
tag.len()
)));
}
let mut ct_with_tag = Vec::with_capacity(ct.len() + 16);
ct_with_tag.extend_from_slice(ct);
ct_with_tag.extend_from_slice(tag);
let nonce_arr = aes_gcm::Nonce::<aes_gcm::aead::consts::U12>::try_from(nonce)
.map_err(|_| SmimeError::MalformedInput("GCM nonce length mismatch".into()))?;
if content_enc_oid == ID_AES_128_GCM {
let key_arr: &[u8; 16] = cek
.as_slice()
.try_into()
.map_err(|_| SmimeError::MalformedInput("AES-128-GCM CEK must be 16 bytes".into()))?;
let cipher = aes_gcm::Aes128Gcm::new(key_arr.into());
cipher
.decrypt(&nonce_arr, ct_with_tag.as_ref())
.map_err(|_| SmimeError::DecryptionFailed("content decryption failed".into()))
} else if content_enc_oid == ID_AES_256_GCM {
let key_arr: &[u8; 32] = cek
.as_slice()
.try_into()
.map_err(|_| SmimeError::MalformedInput("AES-256-GCM CEK must be 32 bytes".into()))?;
let cipher = aes_gcm::Aes256Gcm::new(key_arr.into());
cipher
.decrypt(&nonce_arr, ct_with_tag.as_ref())
.map_err(|_| SmimeError::DecryptionFailed("content decryption failed".into()))
} else {
Err(SmimeError::UnsupportedAlgorithm(format!(
"AuthEnvelopedData content encryption OID {}",
content_enc_oid
)))
}
}
fn find_and_decrypt_cek(
recip_infos: &RecipientInfos,
key: &dyn DecryptionKey,
) -> Result<Zeroizing<Vec<u8>>, SmimeError> {
let mut unsupported: Option<SmimeError> = None;
for ri in 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) => {
match try_decrypt_kari(kari, key) {
Ok(Some(cek)) => return Ok(cek),
Ok(None) => {}
Err(e @ SmimeError::UnsupportedAlgorithm(_)) => unsupported = Some(e),
Err(e) => return Err(e),
}
}
RecipientInfo::Kekri(_) | RecipientInfo::Pwri(_) | RecipientInfo::Ori(_) => continue,
}
}
Err(unsupported.unwrap_or(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(|_| SmimeError::DecryptionFailed("content decryption failed".into()))
}
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 ukm: Option<&[u8]> = kari.ukm.as_ref().map(|u| u.as_bytes());
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 {
None => return Ok(None), Some(r) => r,
};
let ephemeral_bytes = match &kari.originator {
OriginatorIdentifierOrKey::OriginatorKey(orig) => {
validate_originator_curve(&orig.algorithm, kari)?;
orig.public_key.raw_bytes().to_vec()
}
_ => {
return Err(SmimeError::UnsupportedAlgorithm(
"KARI with static originator is not supported".into(),
))
}
};
let kari_alg = map_kari_alg(kari)?;
let cek = key.agree_ecdh(&ephemeral_bytes, ukm, rek.enc_key.as_bytes(), &kari_alg)?;
Ok(Some(Zeroizing::new(cek)))
}
fn validate_originator_curve(
orig_alg: &AlgorithmIdentifierOwned,
kari: &KeyAgreeRecipientInfo,
) -> Result<(), SmimeError> {
if orig_alg.oid != ID_EC_PUBLIC_KEY {
return Err(SmimeError::MalformedInput(format!(
"originator key algorithm is {}, expected id-ecPublicKey",
orig_alg.oid
)));
}
let curve_oid = orig_alg
.parameters
.as_ref()
.and_then(|p| p.decode_as::<der::asn1::ObjectIdentifier>().ok())
.ok_or_else(|| {
SmimeError::MalformedInput("originator key missing curve OID parameter".into())
})?;
let ecdh_oid = kari.key_enc_alg.oid;
let expected_curve = if ecdh_oid == rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME {
SECP_256_R_1
} else if ecdh_oid == rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME {
SECP_384_R_1
} else {
return Ok(());
};
if curve_oid != expected_curve {
return Err(SmimeError::MalformedInput(format!(
"originator key curve {} does not match KDF scheme (expected {})",
curve_oid, expected_curve
)));
}
Ok(())
}
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(|_| SmimeError::DecryptionFailed("content decryption failed".into()))
}