//! Certificate builder tests. #![cfg(all( feature = "alloc", feature = "rand_core", any(feature = "ed25519", feature = "p256") ))] use hex_literal::hex; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; use ssh_key::{certificate, Algorithm, PrivateKey}; #[cfg(feature = "p256")] use ssh_key::EcdsaCurve; #[cfg(all(feature = "ed25519", feature = "rsa"))] use std::str::FromStr; #[cfg(all(feature = "ed25519", feature = "std"))] use std::time::{Duration, SystemTime}; /// Example Unix timestamp when a certificate was issued (2020-09-13 12:26:40 UTC). const ISSUED_AT: u64 = 1600000000; /// Example Unix timestamp when a certificate is valid (2022-04-15 05:20:00 UTC). const VALID_AT: u64 = 1650000000; /// Example Unix timestamp when a certificate expires (2023-11-14 22:13:20 UTC). const EXPIRES_AT: u64 = 1700000000; /// Seed to use for PRNG. const PRNG_SEED: [u8; 32] = [42; 32]; #[cfg(feature = "ed25519")] #[test] fn ed25519_sign_and_verify() { const SERIAL: u64 = 42; const KEY_ID: &str = "example"; const PRINCIPAL: &str = "nobody"; const CRITICAL_EXTENSION_1: (&str, &str) = ("critical name 1", "critical data 2"); const CRITICAL_EXTENSION_2: (&str, &str) = ("critical name 2", "critical data 2"); const EXTENSION_1: (&str, &str) = ("extension name 1", "extension data 1"); const EXTENSION_2: (&str, &str) = ("extension name 2", "extension data 2"); const COMMENT: &str = "user@example.com"; let mut rng = ChaCha8Rng::from_seed(PRNG_SEED); let ca_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap(); let subject_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap(); let mut cert_builder = certificate::Builder::new_with_random_nonce( &mut rng, subject_key.public_key(), ISSUED_AT, EXPIRES_AT, ) .unwrap(); cert_builder.serial(SERIAL).unwrap(); cert_builder.key_id(KEY_ID).unwrap(); cert_builder.valid_principal(PRINCIPAL).unwrap(); cert_builder .critical_option(CRITICAL_EXTENSION_1.0, CRITICAL_EXTENSION_1.1) .unwrap(); cert_builder .critical_option(CRITICAL_EXTENSION_2.0, CRITICAL_EXTENSION_2.1) .unwrap(); cert_builder .extension(EXTENSION_1.0, EXTENSION_1.1) .unwrap(); cert_builder .extension(EXTENSION_2.0, EXTENSION_2.1) .unwrap(); cert_builder.comment(COMMENT).unwrap(); let cert = cert_builder.sign(&ca_key).unwrap(); assert_eq!(cert.algorithm(), Algorithm::Ed25519); assert_eq!(cert.nonce(), &hex!("321fdf7e0a2afe803308f394f54c6abe")); assert_eq!(cert.public_key(), subject_key.public_key().key_data()); assert_eq!(cert.serial(), SERIAL); assert_eq!(cert.cert_type(), certificate::CertType::User); assert_eq!(cert.key_id(), KEY_ID); assert_eq!(cert.valid_principals().len(), 1); assert_eq!(cert.valid_principals()[0], PRINCIPAL); assert_eq!(cert.valid_after(), ISSUED_AT); assert_eq!(cert.valid_before(), EXPIRES_AT); assert_eq!(cert.critical_options().len(), 2); assert_eq!( cert.critical_options().get(CRITICAL_EXTENSION_1.0).unwrap(), CRITICAL_EXTENSION_1.1 ); assert_eq!(cert.extensions().get(EXTENSION_2.0).unwrap(), EXTENSION_2.1); assert_eq!(cert.extensions().len(), 2); assert_eq!(cert.extensions().get(EXTENSION_1.0).unwrap(), EXTENSION_1.1); assert_eq!(cert.extensions().get(EXTENSION_2.0).unwrap(), EXTENSION_2.1); assert_eq!(cert.signature_key(), ca_key.public_key().key_data()); assert_eq!(cert.comment(), COMMENT); let ca_fingerprint = ca_key.fingerprint(Default::default()); assert!(cert.validate_at(VALID_AT, &[ca_fingerprint]).is_ok()); } #[cfg(feature = "p256")] #[test] fn ecdsa_nistp256_sign_and_verify() { let mut rng = ChaCha8Rng::from_seed(PRNG_SEED); let algorithm = Algorithm::Ecdsa { curve: EcdsaCurve::NistP256, }; let ca_key = PrivateKey::random(&mut rng, algorithm.clone()).unwrap(); let subject_key = PrivateKey::random(&mut rng, algorithm.clone()).unwrap(); let mut cert_builder = certificate::Builder::new_with_random_nonce( &mut rng, subject_key.public_key(), ISSUED_AT, EXPIRES_AT, ) .unwrap(); cert_builder.all_principals_valid().unwrap(); let cert = cert_builder.sign(&ca_key).unwrap(); assert_eq!(cert.algorithm(), algorithm); assert_eq!(cert.nonce(), &hex!("321fdf7e0a2afe803308f394f54c6abe")); assert_eq!(cert.public_key(), subject_key.public_key().key_data()); assert_eq!(cert.signature_key(), ca_key.public_key().key_data()); let ca_fingerprint = ca_key.fingerprint(Default::default()); assert!(cert.validate_at(VALID_AT, &[ca_fingerprint]).is_ok()); } #[cfg(all(feature = "ed25519", feature = "rsa"))] #[test] fn rsa_sign_and_verify() { let ca_key = PrivateKey::from_str( r#"-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn NhAAAAAwEAAQAAAQEAyng6J3IE5++Ji7EfVNTANDnhYH46LnZW+bwW45etzKswQkc/AvSA 9ih2VAhE8FFUR0Z6pyl4hEn/878x50pGt1FHplbbe4wZ5aornT1hcGGYy313Glt+zyn96M BTAjO0yULa1RrhBBmeY3yXIEAApUIVdvxcLOvJgltSFmFURtbY5cZkweuspwnHBE/JUPBX 9/Njb+z2R4BTnf0UrudxRKA/TJx9mL3Pb2JjkXfQ07pZqp+oEiUoGMvdfN9vYW4J5LTbXo n20kRt5UKSxKggBBa0rzGabF+P/BTd39ZrI27WRYhDAzeYJoLq/xfO6qCgAM3TKxe0tDeT gV4akFJ9CwAAA7hN/dPaTf3T2gAAAAdzc2gtcnNhAAABAQDKeDoncgTn74mLsR9U1MA0Oe Fgfjoudlb5vBbjl63MqzBCRz8C9ID2KHZUCETwUVRHRnqnKXiESf/zvzHnSka3UUemVtt7 jBnlqiudPWFwYZjLfXcaW37PKf3owFMCM7TJQtrVGuEEGZ5jfJcgQAClQhV2/Fws68mCW1 IWYVRG1tjlxmTB66ynCccET8lQ8Ff382Nv7PZHgFOd/RSu53FEoD9MnH2Yvc9vYmORd9DT ulmqn6gSJSgYy918329hbgnktNteifbSRG3lQpLEqCAEFrSvMZpsX4/8FN3f1msjbtZFiE MDN5gmgur/F87qoKAAzdMrF7S0N5OBXhqQUn0LAAAAAwEAAQAAAQAxxSgWdjK6iOl4y0t2 YO32aJv8SksnDLQIo7HEtI5ml1Y/lJ/qrAvfdsbPlVDM+lELTEnuOYWEj2Q5mLA9uMZ1Xa eNPiCp2CCtkg0yk9oV9AfJTcgvVHpxllLyGgTNr8QrDSIZ7IePqHSE5CWKKfF+riX0n8hQ yo04XBZrpfU/jDQV8ENKiNQd3Aiy6ppSbnDhyTzZEYIxtvnh1FmvU0Ct1jQRd8p42gurEn sq6nAPE9pnn0otKmjRdfGCnM9X/ZbUcaUcU/X8pPYG1pW0GZR7eTO+1f9s8TS5LIqz2Eru L4gBQweASh9mhatsMqJX/ZRrdHvdIuH8N1VDSahf1ZTxAAAAgF1+qA6ZVBEaoCj+fAJZyU EYf7NMI/nPqEVxiIjg4WKmRYKC9Pb9cuGehOs/XTi3KMEHzYJIKT1K+uO0OG025XVH06qk 9qyWcBwtRbCPVFJPSkKyGBPaUIxMI07x1+434vig6z7iwVROxy3vyhslgiJNpIkaWVUhQN EGEHX0oWLfAAAAgQDLd25QLAb1kngTsuwQ+xo3S6UcQvOTiDnVRvxWPaW4yn/3qO55+esd dzxUujFXhUO/POeUJiHv0B1QlDm/sHYL6YVI5+XRaWAst/z0T93mM4ts63Z1OoJbAtE5qH yGlKVPQ5ZG8SUVElbX+SZE2CcnsPx53trW8qQu/R2bPdDN7QAAAIEA/r7nlgz6D93vMVkn wq38d49h+PTfyBQ1bum8AhxCEfTaK94YrH9BeizO6Ma5MIjY6WHWbq7Co93J3fl8f4eTCo CpHJYWfbBqrf/5PUoOIjdMdfFHK6GpUCQNxhbSpnL4l75sxrhkEXtBHVKRXCNR5T4JnOcx R6qbyo6hPuCiV9cAAAAAAQID -----END OPENSSH PRIVATE KEY-----"#, ) .unwrap(); let mut rng = ChaCha8Rng::from_seed(PRNG_SEED); let subject_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap(); let mut cert_builder = certificate::Builder::new_with_random_nonce( &mut rng, subject_key.public_key(), ISSUED_AT, EXPIRES_AT, ) .unwrap(); cert_builder.all_principals_valid().unwrap(); let cert = cert_builder.sign(&ca_key).unwrap(); assert_eq!( cert.signature_key().algorithm(), Algorithm::Rsa { hash: None } ); assert_eq!(cert.nonce(), &hex!("55742ecb25ee56057b9e35eae54c40a9")); assert_eq!(cert.public_key(), subject_key.public_key().key_data()); assert_eq!(cert.signature_key(), ca_key.public_key().key_data()); let ca_fingerprint = ca_key.fingerprint(Default::default()); assert!(cert.validate_at(VALID_AT, &[ca_fingerprint]).is_ok()); } #[cfg(all(feature = "ed25519", feature = "std"))] #[test] fn new_with_validity_times() { let mut rng = ChaCha8Rng::from_seed(PRNG_SEED); let subject_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap(); // NOTE: use a random nonce, not an all-zero one! let nonce = [0u8; certificate::Builder::RECOMMENDED_NONCE_SIZE]; let issued_at = SystemTime::now(); let expires_at = issued_at + Duration::from_secs(3600); assert!(certificate::Builder::new_with_validity_times( nonce, subject_key.public_key(), issued_at, expires_at ) .is_ok()); }