ssh-key 0.5.1

Pure Rust implementation of SSH key file format decoders/encoders as described in RFC4251/RFC4253 and OpenSSH key formats, as well as "sshsig" signatures and certificates (including certificate validation and certificate authority support), with further support for the `authorized_keys` and `known_hosts` file formats.
Documentation
//! Certificate builder tests.

#![cfg(all(feature = "alloc", any(feature = "ed25519", feature = "p256")))]

use hex_literal::hex;
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
use ssh_key::{
    certificate::{self, CertType},
    Algorithm, PrivateKey,
};

#[cfg(feature = "p256")]
use ssh_key::EcdsaCurve;

#[cfg(feature = "rsa")]
use std::str::FromStr;

/// 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;

/// Example serial number.
const SERIAL: u64 = 42;

/// Example key ID.
const KEY_ID: &str = "example";

/// Example principal name.
const PRINCIPAL: &str = "nobody";

/// Critical extension 1.
const CRITICAL_EXTENSION_1: (&str, &str) = ("critical name 1", "critical data 2");

/// Critical extension 1.
const CRITICAL_EXTENSION_2: (&str, &str) = ("critical name 2", "critical data 2");

/// Non critical extension 1.
const EXTENSION_1: (&str, &str) = ("extension name 1", "extension data 1");

/// Non critical extension 2.
const EXTENSION_2: (&str, &str) = ("extension name 2", "extension data 2");

/// Example comment.
const COMMENT: &str = "user@example.com";

/// Seed to use for PRNG.
const PRNG_SEED: [u8; 32] = [42; 32];

#[cfg(feature = "ed25519")]
#[test]
fn ed25519_sign_and_verify() {
    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,
    );
    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(), 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).unwrap();
    let subject_key = PrivateKey::random(&mut rng, algorithm).unwrap();
    let mut cert_builder = certificate::Builder::new_with_random_nonce(
        &mut rng,
        subject_key.public_key(),
        ISSUED_AT,
        EXPIRES_AT,
    );
    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(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,
    );
    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());
}