isomdl 0.2.0

ISO mDL implementation in Rust
Documentation
use const_oid::{AssociatedOid, ObjectIdentifier};
use der::Decode;
use sha1::{Digest, Sha1};
use x509_cert::{
    ext::{pkix::SubjectKeyIdentifier, Extension},
    Certificate,
};

use super::Error;
use super::ExtensionValidator;

pub struct SubjectKeyIdentifierValidator {
    subject_public_key_bitstring_raw_bytes: Vec<u8>,
}

impl SubjectKeyIdentifierValidator {
    pub fn from_certificate(certificate: &Certificate) -> Self {
        Self {
            subject_public_key_bitstring_raw_bytes: certificate
                .tbs_certificate
                .subject_public_key_info
                .subject_public_key
                .raw_bytes()
                .to_owned(),
        }
    }

    fn check(&self, ski: SubjectKeyIdentifier) -> Option<Error> {
        let expected_digest = ski.0.as_bytes();
        let digest = Sha1::digest(&self.subject_public_key_bitstring_raw_bytes);

        if digest.as_slice() != expected_digest {
            Some("public key digest did not match the expected value".into())
        } else {
            None
        }
    }
}

impl ExtensionValidator for SubjectKeyIdentifierValidator {
    fn oid(&self) -> ObjectIdentifier {
        SubjectKeyIdentifier::OID
    }

    fn ext_name(&self) -> &'static str {
        "SubjectKeyIdentifier"
    }

    fn validate(&self, extension: &Extension) -> Vec<Error> {
        let bytes = extension.extn_value.as_bytes();
        let ski = SubjectKeyIdentifier::from_der(bytes);
        match ski {
            Ok(ski) => {
                if let Some(e) = self.check(ski) {
                    vec![e]
                } else {
                    vec![]
                }
            }
            Err(e) => {
                vec![format!("failed to decode: {e}")]
            }
        }
    }
}

#[cfg(test)]
#[rstest::rstest]
#[case::ok(
    include_str!("../../../../../test/issuance/256-cert.pem"),
    include_str!("../../../../../test/issuance/256-cert.pem"),
    true
)]
#[case::different_cert_ski_ext(
    include_str!("../../../../../test/issuance/256-cert.pem"),
    include_str!("../../../../../test/issuance/384-cert.pem"),
    false
)]
fn test(
    #[case] public_key_from_certificate_pem: &'static str,
    #[case] ski_ext_from_certificate_pem: &'static str,
    #[case] valid: bool,
) {
    use der::DecodePem;

    let certificate = Certificate::from_pem(public_key_from_certificate_pem).unwrap();
    let skiv = SubjectKeyIdentifierValidator::from_certificate(&certificate);

    let certificate = Certificate::from_pem(ski_ext_from_certificate_pem).unwrap();
    let outcome = skiv.validate(
        certificate
            .tbs_certificate
            .extensions
            .iter()
            .flatten()
            .find(|ext| ext.extn_id == skiv.oid())
            .unwrap(),
    );
    assert_eq!(outcome.is_empty(), valid)
}