pe-sign 0.1.10

pe-sign is a cross-platform tool developed in Rust, designed for parsing and verifying digital signatures in PE files. It provides a simple command-line interface that supports extracting certificates, verifying digital signatures, calculating Authenticode digests, and printing certificate information.
Documentation
use der::{
    asn1::{BmpString, Ia5StringRef, PrintableStringRef, TeletexStringRef, Utf8StringRef},
    oid::db::DB,
    Decode, Encode, Tag, Tagged,
};
use std::fmt::Display;

use crate::utils::VecInto;

pub type Name = RdnSequence;

#[derive(Clone, Debug, Default)]
pub struct RdnSequence(pub Vec<RelativeDistinguishedName>);

impl PartialEq for RdnSequence {
    fn eq(&self, other: &Self) -> bool {
        if self.0.len() != other.0.len() {
            return false;
        }

        for rdn in &self.0 {
            if !other.0.iter().any(|vv| vv == rdn) {
                return false;
            }
        }

        true
    }
}

impl Eq for RdnSequence {}

impl Display for RdnSequence {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let rdn = self
            .0
            .iter()
            .map(|v| v.to_string())
            .collect::<Vec<_>>()
            .join(", ");
        write!(f, "{}", rdn)
    }
}

impl From<x509_cert::name::RdnSequence> for RdnSequence {
    fn from(value: x509_cert::name::RdnSequence) -> Self {
        Self(value.0.vec_into())
    }
}

#[derive(Clone, Debug)]
pub struct RelativeDistinguishedName(pub Vec<String>);

impl PartialEq for RelativeDistinguishedName {
    fn eq(&self, other: &Self) -> bool {
        if self.0.len() != other.0.len() {
            return false;
        }

        for rdn in &self.0 {
            if !other.0.iter().any(|vv| vv == rdn) {
                return false;
            }
        }

        true
    }
}

impl Eq for RelativeDistinguishedName {}

impl Display for RelativeDistinguishedName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0.join(", "))
    }
}

impl From<x509_cert::name::RelativeDistinguishedName> for RelativeDistinguishedName {
    fn from(value: x509_cert::name::RelativeDistinguishedName) -> Self {
        Self(
            value
                .0
                .iter()
                .map(|tv| {
                    let val = match tv.value.tag() {
                        Tag::PrintableString => PrintableStringRef::try_from(&tv.value)
                            .ok()
                            .map(|s| s.to_string()),
                        Tag::Utf8String => Utf8StringRef::try_from(&tv.value)
                            .ok()
                            .map(|s| s.to_string()),
                        Tag::Ia5String => Ia5StringRef::try_from(&tv.value)
                            .ok()
                            .map(|s| s.to_string()),
                        Tag::TeletexString => TeletexStringRef::try_from(&tv.value)
                            .ok()
                            .map(|s| s.to_string()),
                        // UTF-16
                        Tag::BmpString => BmpString::from_der(&tv.value.to_der().unwrap())
                            .ok()
                            .map(|s| s.to_string()),
                        _ => None,
                    };

                    let key = {
                        let mut best_match: Option<&str> = None;

                        for m in DB.find_names_for_oid(tv.oid) {
                            if let Some(previous) = best_match {
                                if m.len() < previous.len() {
                                    best_match = Some(m);
                                }
                            } else {
                                best_match = Some(m);
                            }
                        }

                        best_match
                    };

                    let key_ss = match key {
                        Some(key) => key.to_ascii_uppercase(),
                        None => tv.oid.to_string(),
                    };

                    let val_ss = match val {
                        Some(val) => {
                            let mut val_ss = String::new();
                            let mut iter = val.char_indices().peekable();
                            while let Some((i, c)) = iter.next() {
                                match c {
                                    '#' if i == 0 => val_ss.push_str("\\#"),
                                    ' ' if i == 0 || iter.peek().is_none() => {
                                        val_ss.push_str("\\ ")
                                    }
                                    '"' | '+' | ',' | ';' | '<' | '>' | '\\' => {
                                        val_ss = val_ss + &format!("\\{}", c);
                                    }
                                    '\x00'..='\x1f' | '\x7f' => {
                                        val_ss = val_ss + &format!("\\{:02x}", c as u8)
                                    }
                                    _ => val_ss.push(c),
                                }
                            }

                            val_ss
                        }
                        None => {
                            let mut val_ss = "#".to_owned();
                            let value = tv.value.to_der().unwrap();

                            for c in value {
                                val_ss = val_ss + &format!("{:02x}", c);
                            }

                            val_ss
                        }
                    };

                    format!("{}={}", key_ss, val_ss)
                })
                .collect(),
        )
    }
}

#[cfg(test)]
mod tests {
    use der::Decode;

    use super::{RdnSequence, RelativeDistinguishedName};

    #[test]
    fn test_rdns_eq() {
        let a = RdnSequence(vec![RelativeDistinguishedName(vec![
            "O=test".to_owned(),
            "CN=test".to_owned(),
        ])]);
        let b = RdnSequence(vec![RelativeDistinguishedName(vec![
            "CN=test".to_owned(),
            "O=test".to_owned(),
        ])]);
        let c = RdnSequence(vec![RelativeDistinguishedName(vec!["CN=test".to_owned()])]);

        assert_eq!(a, b);
        assert_ne!(b, c);
    }

    #[test]
    fn test_utf16_rdn() {
        let der_rdn = x509_cert::name::RelativeDistinguishedName::from_der(
            b"\x31\x0d\x30\x0b\x06\x03\x55\x04\x03\x1e\x04\x66\x53\x7f\xbd",
        )
        .unwrap();

        let rdn = RelativeDistinguishedName::from(der_rdn);

        assert_eq!(rdn.to_string(), "CN=晓羽");
    }

    #[test]
    fn test_unkown_rdn_oid() {
        let der_rdn = x509_cert::name::RelativeDistinguishedName::from_der(
            b"\x31\x13\x30\x11\x06\x0b\x2b\x06\x01\x04\x01\x82\x37\x3c\x02\x01\x03\x13\x02\x43\x4e",
        )
        .unwrap();

        let rdn = RelativeDistinguishedName::from(der_rdn);

        assert_eq!(rdn.to_string(), "1.3.6.1.4.1.311.60.2.1.3=CN");
    }
}