certificate_manager 0.2.2

A library for managing and spoofing X.509 certificates
Documentation
#[cfg(test)]
mod tests {
    use certificate_manager::certificate::*;
    use rustls_pki_types::{pem::PemObject, CertificateDer};
    use std::fs::write;
    use x509_parser::parse_x509_certificate;

    #[test]
    fn test_load_from_pem_files_without_passphrase() {
        // Create a temporary directory
        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
        let cert_path = temp_dir.path().join("cert.pem");
        let key_path = temp_dir.path().join("key.pem");

        // Generate a self-signed certificate and private key
        let ca_created =
            CertificateAuthority::new(None).expect("Failed to create Certificate Authority");

        // Save the certificate to a file*
        write(&cert_path, &ca_created.cert.pem()).expect("Failed to write certificate to file");

        write(&key_path, &ca_created.key_pair.serialize_pem())
            .expect("Failed to write private key to file");

        // Load the certificate and private key from files
        let ca_loaded =
            CertificateAuthority::load_from_pem_files(cert_path.clone(), key_path.clone(), None)
                .expect("Failed to load Certificate Authority from PEM files");

        let (_, parsed_ca_created) = parse_x509_certificate(&ca_created.cert.der())
            .expect("Failed to parse PEM from certificate");

        let (_, parsed_ca_loaded) = parse_x509_certificate(&ca_loaded.cert.der())
            .expect("Failed to parse PEM from certificate");

        // Verify that the loaded certificate matches the created certificate
        assert_eq!(
            parsed_ca_created, parsed_ca_loaded,
            "Loaded certificate does not match the created certificate"
        );

        // Verify that the loaded private key matches the created private key
        assert_eq!(
            ca_loaded.key_pair.serialize_pem(),
            ca_created.key_pair.serialize_pem(),
            "Loaded private key does not match the loaded certificate"
        );

        // Clean up the temporary directory
        temp_dir
            .close()
            .expect("Failed to clean up temporary directory");
    }

    #[test]
    fn test_load_from_pem_files_with_passphrase() {
        // Create a temporary directory
        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
        let cert_path = temp_dir.path().join("cert.pem");
        let key_path = temp_dir.path().join("key.pem");
        let passphrase = Some("passphrase".to_string());

        // Generate a self-signed certificate and encrypted private key
        let ca_created =
            CertificateAuthority::new(None).expect("Failed to create Certificate Authority");

        // Encrypt the private key if a passphrase is provided
        let private_key = encrypt_private_key(&ca_created.key_pair, &passphrase).unwrap();

        // Save the certificate to a file
        write(&cert_path, &ca_created.cert.pem()).expect("Failed to write certificate to file");

        write(&key_path, &private_key).expect("Failed to write private key to file");

        // Load the certificate and private key from files
        let ca_loaded = CertificateAuthority::load_from_pem_files(
            cert_path.clone(),
            key_path.clone(),
            passphrase,
        )
        .expect("Failed to load Certificate Authority from PEM files");

        let (_, parsed_ca_created) = parse_x509_certificate(&ca_created.cert.der())
            .expect("Failed to parse PEM from certificate");

        let (_, parsed_ca_loaded) = parse_x509_certificate(&ca_loaded.cert.der())
            .expect("Failed to parse PEM from certificate");

        // Verify that the loaded certificate matches the created certificate
        assert_eq!(
            parsed_ca_created, parsed_ca_loaded,
            "Loaded certificate does not match the created certificate"
        );

        // Verify that the loaded private key matches the created private key
        assert_eq!(
            ca_created.key_pair.serialize_pem(),
            ca_loaded.key_pair.serialize_pem(),
            "Loaded private key does not match the loaded certificate"
        );

        // Clean up the temporary directory
        temp_dir
            .close()
            .expect("Failed to clean up temporary directory");
    }

    #[test]
    fn test_create_signed_certificate_for_domain() {
        // Generate a Certificate Authority
        let ca = CertificateAuthority::new(None).expect("Failed to create Certificate Authority");

        // Test case 1: Create a certificate for a single domain without additional SANs
        let domain = "example.com";
        let cert = ca
            .create_signed_certificate_for_domain(domain, None)
            .expect("Failed to create signed certificate");

        // Parse the certificate into PEM format
        let (_, parsed_cert) =
            parse_x509_certificate(&cert.der()).expect("Failed to parse PEM from certificate");

        // Parse the certificate into PEM format
        let (_, parsed_ca_cert) =
            parse_x509_certificate(&ca.cert.der()).expect("Failed to parse PEM from certificate");

        // Verify the certificate's common name (CN)
        let subject_cn = parsed_cert
            .subject()
            .iter_common_name()
            .next()
            .expect("No common name in subject")
            .as_str()
            .expect("Failed to parse subject common name");
        assert_eq!(
            subject_cn, domain,
            "The subject CN does not match the domain"
        );

        // Verify that the issuer matches the CA's certificate
        let issuer_name = parsed_cert.issuer().to_string();
        let ca_subject_name = parsed_ca_cert.issuer().to_string();
        assert_eq!(
            issuer_name, ca_subject_name,
            "The issuer does not match the CA's subject name"
        );

        // Verify the certificate with the CA's key
        assert!(
            parsed_cert
                .verify_signature(Some(&parsed_ca_cert.subject_pki))
                .is_ok(),
            "Certificate failed verification with CA key"
        );

        // Verify the default SAN (should match the domain)
        let san_extension = parsed_cert
            .subject_alternative_name()
            .expect("No SAN extension found")
            .unwrap();
        assert!(
            san_extension
            .value
            .general_names
                .iter()
                .any(|name| matches!(name, x509_parser::extensions::GeneralName::DNSName(dns) if *dns == domain)),
            "Default SAN does not match the domain"
        );

        // Test case 2: Create a certificate with multiple SANs
        let san_list = vec!["www.example.com".to_string(), "api.example.com".to_string()];
        let cert_with_sans = ca
            .create_signed_certificate_for_domain(domain, Some(san_list.clone()))
            .expect("Failed to create signed certificate with SANs");

        let (_, parsed_cert_with_sans) = parse_x509_certificate(&cert_with_sans.der())
            .expect("Failed to parse PEM from certificate");

        let subject_cn_with_sans = parsed_cert_with_sans
            .subject()
            .iter_common_name()
            .next()
            .expect("No common name in subject")
            .as_str()
            .expect("Failed to parse subject common name");
        assert_eq!(
            subject_cn_with_sans.to_string(),
            domain,
            "The subject CN does not match the domain"
        );

        // Verify that the issuer matches the CA's certificate
        let issuer_name = parsed_cert_with_sans.issuer().to_string();
        let ca_subject_name = parsed_ca_cert.issuer().to_string();
        assert_eq!(
            issuer_name, ca_subject_name,
            "The issuer does not match the CA's subject name"
        );

        // Verify the certificate with the CA's key
        assert!(
            parsed_cert_with_sans
                .verify_signature(Some(&parsed_ca_cert.subject_pki))
                .is_ok(),
            "Certificate failed verification with CA key"
        );

        // Verify the SANs
        let san_extension_with_sans = parsed_cert_with_sans
            .subject_alternative_name()
            .expect("No SAN extension found in certificate with SANs")
            .unwrap();
        for san in san_list {
            assert!(
                    san_extension_with_sans
                        .value
                        .general_names
                        .iter()
                        .any(|name| matches!(name, x509_parser::extensions::GeneralName::DNSName(dns) if *dns == san.as_str())),
                    "SAN {} not found in certificate",
                    san
                );
        }
    }

    #[test]
    fn test_spoof_certificate() {
        // Create a temporary directory
        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
        let cert_path = temp_dir.path().join("cert.pem");
        let key_path = temp_dir.path().join("key.pem");

        // Generate a self-signed certificate and private key
        let ca_created = CertificateAuthority::new(Some("CA Spoofed".to_string()))
            .expect("Failed to create Certificate Authority");

        // Save the certificate to a file
        write(&cert_path, &ca_created.cert.pem()).expect("Failed to write certificate to file");

        write(&key_path, &ca_created.key_pair.serialize_pem())
            .expect("Failed to write private key to file");

        // Load the certificate and private key
        let ca = CertificateAuthority::load_from_pem_files(cert_path, key_path, None)
            .expect("Failed to create Certificate Authority");

        // Generate a self-signed certificate to spoof
        let ca_to_spoof =
            CertificateAuthority::new(None).expect("Failed to create Certificate Authority");

        let (_, parsed_ca_cert) =
            parse_x509_certificate(&ca.cert.der()).expect("Failed to parse PEM from certificate");

        let ca_to_spoof_der = CertificateDer::from_pem_slice(ca_to_spoof.cert.pem().as_bytes())
            .expect("Failed to parse PEM from certificate");
        // Spoof the certificate
        let spoofed_cert = ca
            .spoof_certificate(ca_to_spoof_der)
            .expect("Failed to spoof certificate");

        let (_, parsed_spoofed_cert) =
            parse_x509_certificate(&spoofed_cert).expect("Failed to parse PEM from certificate");

        // Verify that the spoofed certificate was signed by the CA
        assert!(
            parsed_spoofed_cert
                .verify_signature(Some(&parsed_ca_cert.subject_pki))
                .is_ok(),
            "Certificate failed verification with CA key"
        );
    }
}