Skip to main content

age_setup/
generator.rs

1use crate::errors::Result;
2use crate::keypair::KeyPair;
3use crate::public_key::PublicKey;
4use crate::secret_key::SecretKey;
5use age::secrecy::ExposeSecret;
6use age::x25519::Identity;
7
8/// Generates a new age X25519 key pair.
9///
10/// Creates a fresh [`Identity`] using the age library, extracts the public
11/// and secret components, validates both, and returns them as a [`KeyPair`].
12///
13/// # Performance
14///
15/// This operation involves cryptographic key generation and is relatively
16/// expensive. The result is marked `#[must_use]` to discourage discarding
17/// generated keys accidentally. Consider caching the `KeyPair` when possible.
18///
19/// # Errors
20///
21/// Returns [`Error::Generation`](crate::Error::Generation) if the underlying
22/// age identity generation fails.
23///
24/// # Examples
25///
26/// ```no_run
27/// use age_setup::build_keypair;
28///
29/// let kp = build_keypair()?;
30/// println!("Public key: {}", kp.public);
31/// // Secret key is not displayed: the Debug impl redacts it.
32/// println!("KeyPair: {:?}", kp);
33/// # Ok::<(), age_setup::Error>(())
34/// ```
35///
36/// # See Also
37///
38/// * [`KeyPair`](crate::KeyPair) – Container for the generated keys.
39/// * [`Identity::generate`](age::x25519::Identity::generate) – Underlying generation.
40#[must_use = "generating a key pair is an expensive operation; consider reusing the result"]
41pub fn build_keypair() -> Result<KeyPair> {
42    let identity = Identity::generate();
43    let recipient = identity.to_public();
44    let public_raw = recipient.to_string();
45    let secret_raw = identity.to_string().expose_secret().to_string();
46    let public = PublicKey::new(public_raw)?;
47    let secret = SecretKey::new(secret_raw)?;
48    Ok(KeyPair::new(public, secret))
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn generated_keypair_has_valid_format() {
57        let kp = build_keypair().unwrap();
58        assert!(kp.public.expose().starts_with("age1"));
59        assert!(kp.secret.expose_secret().starts_with("AGE-SECRET-KEY-1"));
60    }
61
62    #[test]
63    fn generated_keypairs_are_random() {
64        let kp1 = build_keypair().unwrap();
65        let kp2 = build_keypair().unwrap();
66        assert_ne!(kp1.public.expose(), kp2.public.expose());
67        assert_ne!(kp1.secret.expose_secret(), kp2.secret.expose_secret());
68    }
69
70    #[test]
71    fn secret_is_not_leaked() {
72        let kp = build_keypair().unwrap();
73        let debug = format!("{:?}", kp);
74        assert!(!debug.contains(kp.secret.expose_secret()));
75    }
76
77    #[test]
78    fn keys_have_body_after_prefix() {
79        let kp = build_keypair().unwrap();
80        assert!(kp.public.expose().len() > "age1".len());
81        assert!(kp.secret.expose_secret().len() > "AGE-SECRET-KEY-1".len());
82    }
83}