isomdl 0.2.0

ISO mDL implementation in Rust
Documentation
pub mod trust_anchor;
mod util;
pub mod validation;
pub mod x5chain;

pub use x5chain::{Builder, X5Chain};

#[cfg(test)]
mod test {
    use std::time::Duration;

    use const_oid::ObjectIdentifier;
    use der::asn1::OctetString;
    use p256::NistP256;
    use rand::random;
    use sec1::pkcs8::EncodePublicKey;
    use sha1::{Digest, Sha1};
    use signature::{Keypair, KeypairRef, Signer};
    use x509_cert::{
        builder::{Builder, CertificateBuilder},
        ext::pkix::{
            crl::dp::DistributionPoint,
            name::{DistributionPointName, GeneralName},
            AuthorityKeyIdentifier, BasicConstraints, CrlDistributionPoints, ExtendedKeyUsage,
            IssuerAltName, KeyUsage, KeyUsages, SubjectKeyIdentifier,
        },
        name::Name,
        spki::{
            DynSignatureAlgorithmIdentifier, SignatureBitStringEncoding, SubjectPublicKeyInfoOwned,
        },
        time::Validity,
        Certificate,
    };

    use super::validation;

    fn prepare_root_certificate<S>(root_key: &S, issuer: Name) -> CertificateBuilder<'_, S>
    where
        S: KeypairRef + DynSignatureAlgorithmIdentifier,
        S::VerifyingKey: EncodePublicKey,
    {
        let spki = SubjectPublicKeyInfoOwned::from_key(root_key.verifying_key()).unwrap();
        let ski_digest = Sha1::digest(spki.subject_public_key.raw_bytes());
        let ski_digest_octet = OctetString::new(ski_digest.to_vec()).unwrap();

        let mut builder = CertificateBuilder::new(
            x509_cert::builder::Profile::Manual { issuer: None },
            random::<u64>().into(),
            Validity::from_now(Duration::from_secs(600)).unwrap(),
            issuer,
            spki,
            root_key,
        )
        .unwrap();

        builder
            .add_extension(&SubjectKeyIdentifier(ski_digest_octet))
            .unwrap();

        builder
            .add_extension(&KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign))
            .unwrap();

        builder
            .add_extension(&BasicConstraints {
                ca: true,
                path_len_constraint: Some(0),
            })
            .unwrap();

        builder
            .add_extension(&IssuerAltName(vec![GeneralName::Rfc822Name(
                "test@example.com".to_string().try_into().unwrap(),
            )]))
            .unwrap();

        builder
            .add_extension(&CrlDistributionPoints(vec![DistributionPoint {
                distribution_point: Some(DistributionPointName::FullName(vec![
                    GeneralName::UniformResourceIdentifier(
                        "http://example.com".to_string().try_into().unwrap(),
                    ),
                ])),
                reasons: None,
                crl_issuer: None,
            }]))
            .unwrap();

        builder
    }

    fn prepare_signer_certificate<'s, S>(
        signer_key: &'s S,
        root_key: &'s S,
        issuer: Name,
    ) -> CertificateBuilder<'s, S>
    where
        S: KeypairRef + DynSignatureAlgorithmIdentifier,
        S::VerifyingKey: EncodePublicKey,
    {
        let spki = SubjectPublicKeyInfoOwned::from_key(signer_key.verifying_key()).unwrap();
        let ski_digest = Sha1::digest(spki.subject_public_key.raw_bytes());
        let ski_digest_octet = OctetString::new(ski_digest.to_vec()).unwrap();

        let apki = SubjectPublicKeyInfoOwned::from_key(root_key.verifying_key()).unwrap();
        let aki_digest = Sha1::digest(apki.subject_public_key.raw_bytes());
        let aki_digest_octet = OctetString::new(aki_digest.to_vec()).unwrap();

        let mut builder = CertificateBuilder::new(
            x509_cert::builder::Profile::Manual {
                issuer: Some(issuer),
            },
            random::<u64>().into(),
            Validity::from_now(Duration::from_secs(600)).unwrap(),
            "CN=subject,C=US".parse().unwrap(),
            spki,
            root_key,
        )
        .unwrap();

        builder
            .add_extension(&SubjectKeyIdentifier(ski_digest_octet))
            .unwrap();

        builder
            .add_extension(&AuthorityKeyIdentifier {
                key_identifier: Some(aki_digest_octet),
                ..Default::default()
            })
            .unwrap();

        builder
            .add_extension(&KeyUsage(KeyUsages::DigitalSignature.into()))
            .unwrap();

        builder
            .add_extension(&IssuerAltName(vec![GeneralName::Rfc822Name(
                "test@example.com".to_string().try_into().unwrap(),
            )]))
            .unwrap();

        builder
            .add_extension(&CrlDistributionPoints(vec![DistributionPoint {
                distribution_point: Some(DistributionPointName::FullName(vec![
                    GeneralName::UniformResourceIdentifier(
                        "http://example.com".to_string().try_into().unwrap(),
                    ),
                ])),
                reasons: None,
                crl_issuer: None,
            }]))
            .unwrap();

        builder
            .add_extension(&ExtendedKeyUsage(vec![ObjectIdentifier::new_unwrap(
                "1.0.18013.5.1.2",
            )]))
            .unwrap();

        builder
    }

    fn setup() -> (Certificate, Certificate) {
        let root_key = p256::ecdsa::SigningKey::random(&mut rand::thread_rng());
        let signer_key = p256::ecdsa::SigningKey::random(&mut rand::thread_rng());

        let issuer: Name = "CN=issuer,C=US".parse().unwrap();

        let mut prepared_root_certificate = prepare_root_certificate(&root_key, issuer.clone());
        let signature: ecdsa::Signature<NistP256> =
            root_key.sign(&prepared_root_certificate.finalize().unwrap());
        let root_certificate: Certificate = prepared_root_certificate
            .assemble(signature.to_der().to_bitstring().unwrap())
            .unwrap();

        let mut prepared_signer_certificate =
            prepare_signer_certificate(&signer_key, &root_key, issuer.clone());
        let signature: ecdsa::Signature<NistP256> =
            root_key.sign(&prepared_signer_certificate.finalize().unwrap());
        let signer_certificate: Certificate = prepared_signer_certificate
            .assemble(signature.to_der().to_bitstring().unwrap())
            .unwrap();

        assert!(validation::signature::issuer_signed_subject(
            &signer_certificate,
            &root_certificate
        ));

        (root_certificate, signer_certificate)
    }

    mod iaca {
        use der::EncodePem;

        use crate::definitions::x509::{
            trust_anchor::{TrustAnchor, TrustAnchorRegistry, TrustPurpose},
            validation::ValidationRuleset,
            X5Chain,
        };

        #[test_log::test]
        fn valid_mdoc_issuer_certificate_chain_is_validated() {
            let (root, signer) = super::setup();

            tracing::debug!(
                "issuer certificate:\n{}",
                root.to_pem(Default::default()).unwrap()
            );
            tracing::debug!(
                "signer certificate:\n{}",
                signer.to_pem(Default::default()).unwrap()
            );

            let trust_anchor_registry = TrustAnchorRegistry {
                anchors: vec![TrustAnchor {
                    certificate: root,
                    purpose: TrustPurpose::Iaca,
                }],
            };
            let x5chain = X5Chain::builder()
                .with_certificate(signer)
                .unwrap()
                .build()
                .unwrap();
            let outcome = ValidationRuleset::Mdl.validate(&x5chain, &trust_anchor_registry);
            assert!(outcome.success(), "{outcome:?}");
        }
    }
}