use std::borrow::Cow;
use std::collections::HashSet;
use std::fmt::Write;
use bstr::ByteSlice;
use const_oid::db::{rfc5911, rfc5912, rfc6268};
use der_parser::asn1_rs::{Set, Tag, ToDer, UtcTime};
use digest::Digest;
use itertools::Itertools;
use md2::Md2;
use md5::Md5;
use protobuf::MessageField;
use sha1::Sha1;
use sha2::{Sha256, Sha384, Sha512};
use x509_parser::certificate::X509Certificate;
use x509_parser::der_parser::num_bigint::BigUint;
use x509_parser::x509::{AlgorithmIdentifier, X509Name};
#[cfg(feature = "logging")]
use log::error;
use crate::modules::protos;
use crate::modules::utils::asn1::{
oid, oid_to_object_identifier, oid_to_str, Attribute, Certificate,
ContentInfo, DigestInfo, SignedData, SignerInfo, SpcIndirectDataContent,
SpcSpOpusInfo, TstInfo,
};
use crate::modules::utils::crypto::PublicKey;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ParseError {
InvalidContentInfo,
InvalidContentType(String),
InvalidSignedData,
InvalidSignedDataVersion(i32),
InvalidNumDigestAlgorithms(usize),
InvalidEncapsulatedContentType(String),
InvalidSpcIndirectDataContent,
InvalidNumSignerInfo,
MissingContentTypeAuthenticatedAttribute,
MissingAuthenticodeDigest,
InvalidDigestAlgorithm,
}
pub trait AuthenticodeHasher {
fn hash(&self, digest: &mut dyn digest::Update) -> Option<()>;
}
pub struct AuthenticodeParser {}
impl AuthenticodeParser {
pub fn parse<'a>(
input: &'a [u8],
authenticode_hasher: &impl AuthenticodeHasher,
) -> Result<Vec<AuthenticodeSignature<'a>>, ParseError> {
let content_info = ContentInfo::from_ber(input)
.map_err(|_| ParseError::InvalidContentInfo)?;
Self::parse_content_info(content_info, authenticode_hasher)
}
fn parse_content_info<'a>(
content_info: ContentInfo<'a>,
authenticode_hasher: &impl AuthenticodeHasher,
) -> Result<Vec<AuthenticodeSignature<'a>>, ParseError> {
if content_info.content_type != rfc5911::ID_SIGNED_DATA {
return Err(ParseError::InvalidContentType(
content_info.content_type.to_string(),
));
}
let mut signed_data: SignedData = content_info
.content
.try_into()
.map_err(|_| ParseError::InvalidSignedData)?;
if signed_data.version != 1 {
return Err(ParseError::InvalidSignedDataVersion(
signed_data.version,
));
}
if signed_data.digest_algorithms.len() != 1 {
return Err(ParseError::InvalidNumDigestAlgorithms(
signed_data.digest_algorithms.len(),
));
}
if signed_data.content_info.content_type
!= oid::MS_SPC_INDIRECT_DATA_OBJID
{
return Err(ParseError::InvalidEncapsulatedContentType(
signed_data.content_info.content_type.to_string(),
));
}
let signer_info = match signed_data.signer_infos.pop() {
Some(si) => si,
None => return Err(ParseError::InvalidNumSignerInfo),
};
let digest_algorithm =
match oid_to_object_identifier(signer_info.digest_algorithm.oid())
{
Ok(oid) => oid,
Err(_) => return Err(ParseError::InvalidDigestAlgorithm),
};
if !signed_data.signer_infos.is_empty() {
return Err(ParseError::InvalidNumSignerInfo);
}
let signer_info_digest = match signer_info
.get_signed_attr(&rfc5911::ID_MESSAGE_DIGEST)
.map(|value| value.data.as_bytes())
{
Some(md) => md,
None => return Err(ParseError::MissingAuthenticodeDigest),
};
if signer_info.get_signed_attr(&rfc5911::ID_CONTENT_TYPE).is_none() {
return Err(ParseError::MissingContentTypeAuthenticatedAttribute);
}
let opus_info: Option<SpcSpOpusInfo> = signer_info
.get_signed_attr(&oid::MS_SPC_OPUS_INFO)
.and_then(|value| value.try_into().ok());
let signed_data_raw = signed_data.content_info.content.data;
let indirect_data: SpcIndirectDataContent =
match signed_data.content_info.content.try_into() {
Ok(idc) => idc,
Err(_) => {
return Err(ParseError::InvalidSpcIndirectDataContent)
}
};
let mut certificates: Vec<Certificate> = signed_data.certificates;
let mut nested_signatures = Vec::new();
let mut countersignatures = Vec::new();
for attr in signer_info.unsigned_attrs.iter() {
match attr.attr_type {
oid::MS_SPC_NESTED_SIGNATURE => {
for value in &attr.attr_values {
if let Ok(content_info) = value.try_into()
&& let Ok(nested) = Self::parse_content_info(
content_info,
authenticode_hasher,
) {
nested_signatures.extend(nested);
};
}
}
oid::MS_COUNTERSIGN => {
Self::parse_ms_countersignature_attr(
&signer_info,
attr,
&mut certificates,
&mut countersignatures,
)?;
}
rfc5911::ID_COUNTERSIGNATURE => {
Self::parse_pkcs9_countersignature_attr(
&signer_info,
attr,
&mut certificates,
&mut countersignatures,
)?;
}
_ => {}
}
}
let computed_authenticode_hash = match digest_algorithm {
rfc5912::ID_MD_2 | rfc5912::MD_2_WITH_RSA_ENCRYPTION => {
let mut md2 = Md2::default();
authenticode_hasher.hash(&mut md2);
md2.finalize().to_vec()
}
rfc5912::ID_MD_5 | rfc5912::MD_5_WITH_RSA_ENCRYPTION => {
let mut md5 = Md5::default();
authenticode_hasher.hash(&mut md5);
md5.finalize().to_vec()
}
rfc5912::ID_SHA_1
| rfc5912::SHA_1_WITH_RSA_ENCRYPTION
| oid::SHA1_WITH_RSA_ENCRYPTION_OBSOLETE => {
let mut sha1 = Sha1::default();
authenticode_hasher.hash(&mut sha1);
sha1.finalize().to_vec()
}
rfc5912::ID_SHA_256 | rfc5912::SHA_256_WITH_RSA_ENCRYPTION => {
let mut sha256 = Sha256::default();
authenticode_hasher.hash(&mut sha256);
sha256.finalize().to_vec()
}
rfc5912::ID_SHA_384 | rfc5912::SHA_384_WITH_RSA_ENCRYPTION => {
let mut sha384 = Sha384::default();
authenticode_hasher.hash(&mut sha384);
sha384.finalize().to_vec()
}
rfc5912::ID_SHA_512 | rfc5912::SHA_512_WITH_RSA_ENCRYPTION => {
let mut sha512 = Sha512::default();
authenticode_hasher.hash(&mut sha512);
sha512.finalize().to_vec()
}
_ => {
#[cfg(feature = "logging")]
error!("unknown digest algorithm: {:?}", digest_algorithm);
return Err(ParseError::InvalidDigestAlgorithm);
}
};
let authenticode_digest = indirect_data.message_digest;
let verified = authenticode_digest.digest
== computed_authenticode_hash.as_slice()
&& verify_message_digest(
&signer_info.digest_algorithm,
signed_data_raw,
signer_info_digest,
)
&& verify_signer_info(&signer_info, certificates.as_slice());
let mut signatures = Vec::with_capacity(nested_signatures.len() + 1);
let (program_name, more_info) = match opus_info {
Some(SpcSpOpusInfo { program_name, more_info }) => {
(program_name, more_info)
}
None => (None, None),
};
signatures.push(AuthenticodeSignature {
computed_authenticode_hash,
program_name,
more_info,
authenticode_digest,
signer_info,
signer_info_digest,
countersignatures,
certificates,
verified,
});
signatures.append(&mut nested_signatures);
Ok(signatures)
}
fn parse_ms_countersignature_attr<'a>(
si: &SignerInfo<'a>,
attr: &Attribute<'a>,
certificates: &mut Vec<Certificate<'a>>,
countersignatures: &mut Vec<AuthenticodeCountersign<'a>>,
) -> Result<(), ParseError> {
for value in &attr.attr_values {
let ci: ContentInfo = match value.try_into() {
Ok(ci) => ci,
Err(_) => continue,
};
let sd: SignedData = match ci.content.try_into() {
Ok(sd) => sd,
Err(_) => continue,
};
certificates.extend(sd.certificates);
let cs_si = match sd.signer_infos.first() {
Some(cs_si) => cs_si,
None => continue,
};
let mut countersignature = Self::pkcs9_countersignature(cs_si)?;
let tst =
match TstInfo::from_ber(sd.content_info.content.as_bytes()) {
Ok(tst_info) => tst_info,
Err(_) => continue,
};
countersignature.digest_alg = oid_to_str(tst.hash_algorithm.oid());
countersignature.digest = tst.hashed_message;
let cs_si_digest = match cs_si
.get_signed_attr(&rfc5911::ID_MESSAGE_DIGEST)
.map(|value| value.data.as_bytes())
{
Some(md) => md,
None => return Err(ParseError::MissingAuthenticodeDigest),
};
countersignature.verified =
verify_message_digest(
&tst.hash_algorithm,
si.signature,
tst.hashed_message,
) && verify_message_digest(
&cs_si.digest_algorithm,
sd.content_info.content.as_bytes(),
cs_si_digest,
) && verify_signer_info(cs_si, certificates.as_slice());
countersignatures.push(countersignature);
}
Ok(())
}
fn parse_pkcs9_countersignature_attr<'a>(
si: &SignerInfo<'a>,
attr: &Attribute<'a>,
certificates: &mut Vec<Certificate<'a>>,
countersignatures: &mut Vec<AuthenticodeCountersign<'a>>,
) -> Result<(), ParseError> {
for value in &attr.attr_values {
if let Ok(cs_si) = value.try_into() {
let mut countersignature =
Self::pkcs9_countersignature(&cs_si)?;
countersignature.verified =
verify_message_digest(
&cs_si.digest_algorithm,
si.signature,
countersignature.digest,
) && verify_signer_info(&cs_si, certificates.as_slice());
countersignatures.push(countersignature);
}
}
Ok(())
}
fn pkcs9_countersignature<'a>(
si: &SignerInfo<'a>,
) -> Result<AuthenticodeCountersign<'a>, ParseError> {
let mut digest = None;
let mut signing_time = None;
for attr in &si.signed_attrs {
match attr.attr_type {
rfc6268::ID_MESSAGE_DIGEST => {
digest = attr.attr_values.first().map(|v| v.data);
}
rfc6268::ID_SIGNING_TIME => {
signing_time = attr
.attr_values
.first()
.and_then(|v| v.try_into().ok())
.and_then(|t: UtcTime| t.utc_adjusted_datetime().ok())
.map(|t| t.unix_timestamp());
}
_ => {}
}
}
let digest = match digest {
Some(digest) => digest,
None => return Err(ParseError::MissingAuthenticodeDigest),
};
Ok(AuthenticodeCountersign {
signer: si.serial_number.clone(),
digest_alg: oid_to_str(si.digest_algorithm.oid()),
digest,
signing_time,
verified: false,
})
}
}
pub struct AuthenticodeCountersign<'a> {
signer: BigUint,
digest_alg: Cow<'static, str>,
digest: &'a [u8],
signing_time: Option<i64>,
verified: bool,
}
pub struct AuthenticodeSignature<'a> {
signer_info: SignerInfo<'a>,
signer_info_digest: &'a [u8],
authenticode_digest: DigestInfo<'a>,
certificates: Vec<Certificate<'a>>,
countersignatures: Vec<AuthenticodeCountersign<'a>>,
program_name: Option<String>,
more_info: Option<String>,
computed_authenticode_hash: Vec<u8>,
verified: bool,
}
impl<'a> AuthenticodeSignature<'a> {
#[inline]
pub fn stored_authenticode_hash(&self) -> &[u8] {
self.authenticode_digest.digest
}
#[inline]
pub fn computed_authenticode_hash(&self) -> &[u8] {
self.computed_authenticode_hash.as_slice()
}
pub fn authenticode_hash_algorithm(&self) -> Cow<'static, str> {
oid_to_str(self.authenticode_digest.algorithm.oid())
}
#[inline]
pub fn signer_info_digest_alg(&self) -> Cow<'static, str> {
oid_to_str(self.signer_info.digest_algorithm.oid())
}
#[inline]
pub fn signer_info_digest(&self) -> String {
hex::encode(self.signer_info_digest)
}
#[inline]
pub fn certificates(&self) -> &[Certificate<'a>] {
self.certificates.as_slice()
}
#[inline]
pub fn chain(&self) -> impl Iterator<Item = &Certificate<'a>> {
CertificateChain::new(self.certificates(), |cert| {
cert.tbs_certificate.issuer.eq(self.issuer())
})
}
#[inline]
pub fn countersignatures(
&self,
) -> impl Iterator<Item = &AuthenticodeCountersign<'a>> {
self.countersignatures.iter()
}
pub fn issuer(&self) -> &X509Name<'a> {
&self.signer_info.issuer
}
pub fn verified(&self) -> bool {
self.verified
}
}
impl From<&AuthenticodeSignature<'_>> for protos::pe::Signature {
fn from(value: &AuthenticodeSignature) -> Self {
let mut sig = protos::pe::Signature::new();
sig.set_digest(hex::encode(value.stored_authenticode_hash()));
sig.set_digest_alg(value.authenticode_hash_algorithm().into_owned());
sig.set_file_digest(hex::encode(value.computed_authenticode_hash()));
sig.set_verified(value.verified());
sig.certificates.extend(
value.certificates().iter().map(protos::pe::Certificate::from),
);
for cs in value.countersignatures() {
let mut pbcs = protos::pe::CounterSignature::from(cs);
pbcs.chain = CertificateChain::new(value.certificates(), |cert| {
cert.tbs_certificate.serial == cs.signer
})
.map(protos::pe::Certificate::from)
.collect();
sig.countersignatures.push(pbcs);
}
sig.set_number_of_certificates(
sig.certificates.len().try_into().unwrap(),
);
sig.set_number_of_countersignatures(
sig.countersignatures.len().try_into().unwrap(),
);
let mut signer_info = protos::pe::SignerInfo::new();
signer_info
.set_digest_alg(value.signer_info_digest_alg().into_owned());
signer_info.set_digest(value.signer_info_digest());
if let Some(program_name) = &value.program_name {
signer_info.set_program_name(program_name.clone())
}
if let Some(more_info) = &value.more_info {
signer_info.set_more_info(more_info.clone())
}
signer_info
.chain
.extend(value.chain().map(protos::pe::Certificate::from));
sig.signer_info = MessageField::from(Some(signer_info));
if let Some(signer_info) = sig.signer_info.as_ref()
&& let Some(cert) = signer_info.chain.first() {
sig.version = cert.version;
sig.thumbprint.clone_from(&cert.thumbprint);
sig.issuer.clone_from(&cert.issuer);
sig.subject.clone_from(&cert.subject);
sig.serial.clone_from(&cert.serial);
sig.not_after = cert.not_after;
sig.not_before = cert.not_before;
sig.algorithm.clone_from(&cert.algorithm);
sig.algorithm_oid.clone_from(&cert.algorithm_oid);
}
sig
}
}
impl From<&AuthenticodeCountersign<'_>> for protos::pe::CounterSignature {
fn from(value: &AuthenticodeCountersign<'_>) -> Self {
let mut cs = protos::pe::CounterSignature::new();
cs.set_digest(hex::encode(value.digest));
cs.set_digest_alg(value.digest_alg.to_string());
cs.set_verified(value.verified);
cs.sign_time = value.signing_time;
cs
}
}
impl From<&Certificate<'_>> for protos::pe::Certificate {
fn from(value: &Certificate) -> Self {
let mut cert = protos::pe::Certificate::new();
cert.set_version(value.x509.tbs_certificate.version.0 as i64 + 1);
cert.set_issuer(format_name(&value.x509.tbs_certificate.issuer));
cert.set_subject(format_name(&value.x509.tbs_certificate.subject));
cert.set_serial(value.x509.raw_serial_as_string());
cert.set_algorithm_oid(format!(
"{}",
value.x509.signature_algorithm.algorithm
));
cert.set_algorithm(
oid_to_str(&value.x509.signature_algorithm.algorithm).into_owned(),
);
cert.set_thumbprint(value.thumbprint.clone());
cert.set_not_before(
value.x509.tbs_certificate.validity.not_before.timestamp(),
);
cert.set_not_after(
value.x509.tbs_certificate.validity.not_after.timestamp(),
);
cert
}
}
fn format_name(name: &X509Name) -> String {
let mut n = String::new();
for rdn in name.iter_rdn() {
write!(n, "/").unwrap();
for atv in rdn.iter() {
let key = oid_to_str(atv.attr_type());
let attr_val = atv.attr_value();
let val = match attr_val.tag() {
Tag::PrintableString => {
attr_val.as_printablestring().ok().map(|s| s.string())
}
Tag::Utf8String => {
attr_val.as_utf8string().ok().map(|s| s.string())
}
Tag::Ia5String => {
attr_val.as_ia5string().ok().map(|s| s.string())
}
Tag::TeletexString => {
attr_val.as_teletexstring().ok().map(|s| s.string())
}
_ => None,
};
match (key, val) {
(key, Some(val)) => {
write!(n, "{key}=").unwrap();
for char in val.chars() {
n.write_char(char).unwrap();
}
}
(key, None) => {
write!(n, "{key}=#").unwrap();
for c in attr_val.data {
write!(n, "{c:02x}").unwrap();
}
}
}
}
}
n
}
fn verify_message_digest(
algorithm: &AlgorithmIdentifier,
message: &[u8],
digest: &[u8],
) -> bool {
let oid = match oid_to_object_identifier(algorithm.oid()) {
Ok(oid) => oid,
Err(_) => return false,
};
match oid {
rfc5912::ID_SHA_1
| rfc5912::SHA_1_WITH_RSA_ENCRYPTION
| oid::SHA1_WITH_RSA_ENCRYPTION_OBSOLETE => {
Sha1::digest(message).as_slice() == digest
}
rfc5912::ID_SHA_256 | rfc5912::SHA_256_WITH_RSA_ENCRYPTION => {
Sha256::digest(message).as_slice() == digest
}
rfc5912::ID_SHA_384 | rfc5912::SHA_384_WITH_RSA_ENCRYPTION => {
Sha384::digest(message).as_slice() == digest
}
rfc5912::ID_SHA_512 | rfc5912::SHA_512_WITH_RSA_ENCRYPTION => {
Sha512::digest(message).as_slice() == digest
}
rfc5912::ID_MD_2 | rfc5912::MD_2_WITH_RSA_ENCRYPTION => {
Md2::digest(message).as_slice() == digest
}
rfc5912::ID_MD_5 | rfc5912::MD_5_WITH_RSA_ENCRYPTION => {
Md5::digest(message).as_slice() == digest
}
_ => {
#[cfg(feature = "logging")]
error!("unknown digest algorithm: {:?}", algorithm.oid());
false
}
}
}
fn verify_signer_info(si: &SignerInfo, certs: &[Certificate<'_>]) -> bool {
let digest_algorithm =
match oid_to_object_identifier(si.digest_algorithm.oid()) {
Ok(oid) => oid,
Err(_) => return false,
};
let cert_chain = CertificateChain::new(certs, |cert| {
cert.tbs_certificate.serial.eq(&si.serial_number)
});
if !cert_chain.verify() {
return false;
}
let signing_cert = match certs
.iter()
.map(|cert| &cert.x509)
.find(|cert| cert.tbs_certificate.serial.eq(&si.serial_number))
{
Some(cert) => cert,
None => return false,
};
let spki = &signing_cert.tbs_certificate.subject_pki;
let key = match PublicKey::try_from(spki) {
Ok(key) => key,
Err(_) => return false,
};
let attrs_set = Set::new(Cow::Borrowed(si.raw_signed_attrs));
match digest_algorithm {
rfc5912::ID_MD_2 | rfc5912::MD_2_WITH_RSA_ENCRYPTION => {
let mut md2 = Md2::default();
attrs_set.write_der(&mut md2).unwrap();
key.verify_digest::<Md2>(md2.finalize(), si.signature)
}
rfc5912::ID_MD_5 | rfc5912::MD_5_WITH_RSA_ENCRYPTION => {
let mut md5 = Md5::default();
attrs_set.write_der(&mut md5).unwrap();
key.verify_digest::<Md5>(md5.finalize(), si.signature)
}
rfc5912::ID_SHA_1
| rfc5912::SHA_1_WITH_RSA_ENCRYPTION
| oid::SHA1_WITH_RSA_ENCRYPTION_OBSOLETE => {
let mut sha1 = Sha1::default();
attrs_set.write_der(&mut sha1).unwrap();
key.verify_digest::<Sha1>(sha1.finalize(), si.signature)
}
rfc5912::ID_SHA_256 | rfc5912::SHA_256_WITH_RSA_ENCRYPTION => {
let mut sha256 = Sha256::default();
attrs_set.write_der(&mut sha256).unwrap();
key.verify_digest::<Sha256>(sha256.finalize(), si.signature)
}
rfc5912::ID_SHA_384 | rfc5912::SHA_384_WITH_RSA_ENCRYPTION => {
let mut sha384 = Sha384::default();
attrs_set.write_der(&mut sha384).unwrap();
key.verify_digest::<Sha384>(sha384.finalize(), si.signature)
}
rfc5912::ID_SHA_512 | rfc5912::SHA_512_WITH_RSA_ENCRYPTION => {
let mut sha512 = Sha512::default();
attrs_set.write_der(&mut sha512).unwrap();
key.verify_digest::<Sha512>(sha512.finalize(), si.signature)
}
_ => {
#[cfg(feature = "logging")]
error!("unknown digest algorithm: {:?}", digest_algorithm);
false
}
}
}
struct CertificateChain<'a, 'b> {
certs: &'b [Certificate<'a>],
next: Option<&'b Certificate<'a>>,
seen: HashSet<&'a [u8]>,
}
impl<'a, 'b> CertificateChain<'a, 'b> {
pub fn new<P>(certs: &'b [Certificate<'a>], predicate: P) -> Self
where
P: Fn(&X509Certificate<'_>) -> bool,
{
let next = certs.iter().find(|cert| predicate(&cert.x509));
Self { certs, next, seen: HashSet::new() }
}
pub fn verify(self) -> bool {
for (signed, signer) in self.tuple_windows() {
let key = match PublicKey::try_from(
&signer.x509.tbs_certificate.subject_pki,
) {
Ok(key) => key,
Err(_) => return false,
};
if !key.verify(
&signed.x509.signature_algorithm,
signed.x509.tbs_certificate.as_ref(),
signed.x509.signature_value.as_ref(),
) {
return false;
}
}
true
}
}
impl<'a, 'b> Iterator for CertificateChain<'a, 'b> {
type Item = &'b Certificate<'a>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.next;
if let Some(next) = self.next {
if next.x509.tbs_certificate.subject
== next.x509.tbs_certificate.issuer
{
self.next = None
} else {
self.next = self
.certs
.iter()
.find(|c| {
c.x509.tbs_certificate.subject
== next.x509.tbs_certificate.issuer
})
.filter(|c| {
self.seen
.insert(c.x509.tbs_certificate.subject.as_raw())
});
}
}
next
}
}