smart-id-rust-client 0.3.3

Smart ID Rust Client
Documentation
use crate::error::Result;
use crate::error::SmartIdClientError;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use openssl::stack::Stack;
use openssl::x509::store::X509StoreBuilder;
use openssl::x509::{X509StoreContext, X509};

/// Verify the certificate chain. This does not check the key usage.
///
/// Note: It is not possible to use the webpki or tls crates to verify the certificate chain (Which would be much simpler).
/// The reason is that they strictly expect the correct EKU for client/server certificates and we are validating signing/auth certificates.
/// For this reason we use openssl x509 to verify the certificate chain.
///
/// # Arguments
/// * `cert_value` - The certificate der base64 encoded to verify
/// * `intermediate_certificates` - The der base64 encoded intermediate certificates to use for verification
/// * `root_certificates` - The der base64 encoded root certificates to use for verification
///
/// # Returns
/// * `Result<Vec<String>>` - The valid certificate chain in der base64 encoded format
pub(crate) fn verify_certificate(
    cert_value: &str,
    intermediate_certificates: Vec<String>,
    root_certificates: Vec<String>,
) -> Result<Vec<String>> {
    // Decode the certificate we are validating
    let cert_der = BASE64_STANDARD.decode(cert_value).map_err(|e| {
        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
            "Could not decode base64 certificate: {:?}",
            e
        ))
    })?;
    let cert = X509::from_der(&cert_der).map_err(|e| {
        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
            "End-entity certificate validation failed: {:?}",
            e
        ))
    })?;

    // Decode and load intermediate certificates
    let mut intermediate_stack = Stack::new().map_err(|e| {
        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
            "Failed to create OpenSSL stack for intermediates: {:?}",
            e
        ))
    })?;

    for cert_str in intermediate_certificates {
        let intermediate_cert = parse_certificate(&cert_str)?;
        intermediate_stack.push(intermediate_cert).unwrap();
    }

    // Create a certificate store and add all root certificates
    let mut store_builder = X509StoreBuilder::new().map_err(|e| {
        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
            "Failed to create OpenSSL X509StoreBuilder: {:?}",
            e
        ))
    })?;

    for root_cert_str in root_certificates {
        let root_cert = parse_certificate(&root_cert_str)?;
        store_builder.add_cert(root_cert).unwrap();
    }

    let store = store_builder.build();

    let mut context = X509StoreContext::new().map_err(|e| {
        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
            "Failed to create OpenSSL X509StoreContext: {:?}",
            e
        ))
    })?;

    let chain = context
        .init(&store, &cert, &intermediate_stack, |ctx| {
            // Verify the certificate chain
            ctx.verify_cert()?;

            // Get the verified certificate chain
            let chain = ctx.chain().unwrap();

            let mut der_chain = Vec::new();

            for cert in chain.iter() {
                let base_64_encoded_der = BASE64_STANDARD.encode(cert.to_der().unwrap());
                der_chain.push(base_64_encoded_der);
            }

            Ok(der_chain)
        })
        .map_err(|e| {
            SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
                "Certificate chain validation failed: {:?}",
                e
            ))
        })?;

    Ok(chain)
}

/// Strips the certificate of the header and footer and decodes it
fn parse_certificate(cert: &str) -> Result<X509> {
    let stripped_cert = cert
        .lines()
        .filter(|line| {
            !line.starts_with("-----BEGIN CERTIFICATE-----")
                && !line.starts_with("-----END CERTIFICATE-----")
        })
        .collect::<Vec<&str>>()
        .join("");

    let cert_der = BASE64_STANDARD.decode(stripped_cert).map_err(|e| {
        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
            "Could not decode base64 certificate: {:?}",
            e
        ))
    })?;

    X509::from_der(&cert_der).map_err(|e| {
        SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
            "Certificate parsing failed: {:?}",
            e
        ))
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::utils::demo_certificates::{demo_intermediate_certificates, demo_root_certificates};

    const VALID_CERTIFICATE: &str = "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";
    const INVALID_CERTIFICATE: &str = "MIICwzCCAasCAQAwOzELMAkGA1UEBhMCQkUxGTAXBgNVBAgMEEJydXNzZWxzIENhcGl0YWwxETAPBgNVBAcMCEJydXNzZWxzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlP5UNGPL3YTEdF0NDnEJKJ/TTMP6K0zSfsTfQOQc6g6cTPbyPAGdgWy1+/P/kJjBcNwMCncFOgSPrtYBUXOY05zcnwBP0pR29FtEJQ6Y0PrmsDfodFE6PQ+k8B2NO15vHp3aUXWHVwXUBIRbPBO3Vo+u5fMOHKXiPotAtO6BHQ8nqg0JmnJXZRwA82SHDlScAm7NFUab2BHVaSe4d5MQOVe0ga6K2JLpudy3TLjGcQpDVCCmTv52h8KQzZUJAd/8jP44wiHm75iEG7azWlujz7IHsJOksGlGHZYafI5igrB5dpH2IhRg4vuGhvSwqiZhXn0oWccr0egHeCmXkcnPwIDAQABoEMwQQYJKoZIhvcNAQkOMTQwMjAOBgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBCNcQggWjxgencTRJL0FDV5u2+EAp2CQEiJd/ycPehbYIkE3kK9g4Ofk3rJSiy1YclROq++1n8AqIlGNz4fcCPDIJDDpnWa/uvg4aOtYOrFJn3lPJWvsI076YOODQh+6YsRWlraN1NrVAPq/kpwANmKSz2IF6s+6ES2z5LNDuDM7SxhAP0QnTI3YVws2/LJHruV3/TLczVf7+y/E4CyPGJSgtbxX6o75RwGQPWQG23ryt/lCgi+k8LgHWiOaMfcPGpgJpvlFo8fCJKqrXaZxvJMLRpskl0Rpzrk9WmQRQLk54fi8U7yf/Z/1X+Rm/YFDyasSfZTSAX7K5ie6+PppA8";
    #[test]
    fn test_verify_certificate() {
        let root_certificates = demo_root_certificates();
        let intermediate_certificates = demo_intermediate_certificates();

        let result = verify_certificate(
            VALID_CERTIFICATE,
            intermediate_certificates,
            root_certificates,
        );

        println!("Result: {:?}", result);

        assert!(result.is_ok());
    }

    #[test]
    fn test_verify_certificate_chain_correct() {
        let root_certificates = demo_root_certificates();
        let intermediate_certificates = demo_intermediate_certificates();

        let result = verify_certificate(
            VALID_CERTIFICATE,
            intermediate_certificates,
            root_certificates,
        );

        println!("Result: {:?}", result);

        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), 3);
    }

    #[test]
    fn test_verify_invalid_certificate() {
        let root_certificates = demo_root_certificates();
        let intermediate_certificates = demo_intermediate_certificates();

        let result = verify_certificate(
            INVALID_CERTIFICATE,
            intermediate_certificates,
            root_certificates,
        );

        println!("Result: {:?}", result);

        assert!(result.is_err());
    }
}