use zeroize::Zeroizing;
use crate::derive::{KeyDeriver, KeyPurpose};
use crate::format;
use crate::identity::{identity_hash, identity_pubkey};
use crate::signer::RootSecret;
#[derive(Debug, Clone)]
pub struct AllPublicKeys {
pub identity_hash: String,
pub signing_pubkey: [u8; 32],
pub signing_pubkey_hex: String,
pub ssh_host_pubkey: String,
pub ssh_host_fingerprint: String,
pub wireguard_pubkey: String,
pub age_recipient: String,
}
impl AllPublicKeys {
pub fn from_root(root: &RootSecret) -> Self {
let identity_hash = identity_hash(root);
let signing_pubkey = identity_pubkey(root);
let signing_pubkey_hex = hex::encode(signing_pubkey);
let deriver = KeyDeriver::new(root.as_bytes());
let (ssh_host_pubkey, ssh_host_fingerprint) = {
let seed = Zeroizing::new(deriver.derive(KeyPurpose::SshHost));
(format::ssh_pubkey(&seed, "styrene"), format::ssh_pubkey_fingerprint(&seed))
};
let wireguard_pubkey = {
let secret = Zeroizing::new(deriver.derive(KeyPurpose::WireGuard));
format::wireguard_pubkey(&secret)
};
let age_recipient = {
#[cfg(feature = "age-format")]
{
let secret = Zeroizing::new(deriver.derive(KeyPurpose::Age));
format::age_recipient(&secret)
}
#[cfg(not(feature = "age-format"))]
{
String::new()
}
};
Self {
identity_hash,
signing_pubkey,
signing_pubkey_hex,
ssh_host_pubkey,
ssh_host_fingerprint,
wireguard_pubkey,
age_recipient,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_root(fill: u8) -> RootSecret {
RootSecret::new([fill; 32])
}
#[test]
fn from_root_produces_all_fields() {
let keys = AllPublicKeys::from_root(&test_root(0x42));
assert_eq!(keys.identity_hash.len(), 32);
assert!(keys.identity_hash.chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(keys.signing_pubkey_hex.len(), 64);
assert!(keys.ssh_host_pubkey.starts_with("ssh-ed25519 "));
assert!(keys.ssh_host_fingerprint.starts_with("SHA256:"));
assert_eq!(keys.wireguard_pubkey.len(), 44); }
#[test]
fn from_root_deterministic() {
let a = AllPublicKeys::from_root(&test_root(0x42));
let b = AllPublicKeys::from_root(&test_root(0x42));
assert_eq!(a.identity_hash, b.identity_hash);
assert_eq!(a.signing_pubkey, b.signing_pubkey);
assert_eq!(a.ssh_host_pubkey, b.ssh_host_pubkey);
assert_eq!(a.wireguard_pubkey, b.wireguard_pubkey);
}
#[test]
fn different_roots_different_keys() {
let a = AllPublicKeys::from_root(&test_root(0x01));
let b = AllPublicKeys::from_root(&test_root(0x02));
assert_ne!(a.identity_hash, b.identity_hash);
assert_ne!(a.signing_pubkey, b.signing_pubkey);
assert_ne!(a.wireguard_pubkey, b.wireguard_pubkey);
}
#[test]
fn identity_hash_matches_standalone() {
let root = test_root(0x42);
let keys = AllPublicKeys::from_root(&root);
assert_eq!(keys.identity_hash, identity_hash(&root));
}
}