age_setup/keypair.rs
1//! Age key pair.
2//!
3//! This module defines the [`KeyPair`] type, which bundles a validated
4//! [`PublicKey`] with its corresponding [`SecretKey`]. Key pairs are created
5//! via the [`build_keypair`](crate::build_keypair) function, which handles
6//! generation and validation, ensuring that the two keys are mathematically
7//! related and conform to the age specification.
8
9use crate::public_key::PublicKey;
10use crate::secret_key::SecretKey;
11
12/// An age X25519 key pair consisting of a public key and a secret key.
13///
14/// `KeyPair` is the central type of this crate. It is produced by
15/// [`build_keypair`](crate::build_keypair) and provides access to both the
16/// public identity (safe to share) and the secret identity (must be kept
17/// confidential).
18///
19/// # Fields
20///
21/// * `public: PublicKey` – The public key. It is guaranteed to start with
22/// `"age1"` and can be safely displayed, cloned, and shared.
23/// * `secret: SecretKey` – The secret key. It is automatically zeroized when
24/// dropped, and its `Display` and `Debug` implementations redact the actual
25/// key material.
26///
27/// # Creation
28///
29/// `KeyPair` cannot be constructed directly from outside the crate because
30/// its constructor is `pub(crate)`. This ensures that all key pairs are
31/// produced by [`build_keypair`], which properly generates a fresh identity
32/// and validates both keys.
33///
34/// # Examples
35///
36/// ```rust
37/// use age_setup::build_keypair;
38///
39/// let kp = build_keypair()?;
40/// println!("Public key: {}", kp.public);
41/// // Secret key is redacted when printed:
42/// println!("Secret key: {}", kp.secret); // prints [REDACTED]
43/// # Ok::<(), age_setup::Error>(())
44/// ```
45#[derive(Debug)]
46pub struct KeyPair {
47 /// The public half of the key pair.
48 pub public: PublicKey,
49 /// The secret half of the key pair (zeroized on drop).
50 pub secret: SecretKey,
51}
52
53impl KeyPair {
54 /// Creates a new `KeyPair` from an already validated public and secret key.
55 ///
56 /// This constructor is `pub(crate)` – only accessible within the crate.
57 /// External users obtain a `KeyPair` exclusively through
58 /// [`build_keypair`](crate::build_keypair), which performs the necessary
59 /// generation and validation steps.
60 pub(crate) fn new(public: PublicKey, secret: SecretKey) -> Self {
61 Self { public, secret }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use crate::build_keypair;
68
69 /// Verifies that a freshly generated key pair has fields that are
70 /// accessible and conform to the expected format.
71 #[test]
72 fn generated_keypair_fields_are_valid() {
73 let kp = build_keypair().unwrap();
74 assert!(kp.public.expose().starts_with("age1"));
75 assert!(kp.secret.expose_secret().starts_with("AGE-SECRET-KEY-1"));
76 }
77
78 /// Ensures that the public and secret keys of a newly generated
79 /// `KeyPair` are non‑empty.
80 #[test]
81 fn generated_keypair_fields_are_non_empty() {
82 let kp = build_keypair().unwrap();
83 assert!(!kp.public.expose().is_empty());
84 assert!(!kp.secret.expose_secret().is_empty());
85 }
86
87 /// The `Debug` representation of a `KeyPair` must not expose the
88 /// secret key (it should be redacted by `SecretKey`'s `Debug` impl).
89 #[test]
90 fn debug_does_not_leak_secret() {
91 let kp = build_keypair().unwrap();
92 let debug_str = format!("{:?}", kp);
93 // The debug output should contain the public key (it is safe) ...
94 assert!(debug_str.contains(kp.public.expose()));
95 // ... but NOT the raw secret key material.
96 assert!(!debug_str.contains(kp.secret.expose_secret()));
97 // It should mention the redacted marker instead.
98 assert!(debug_str.contains("[REDACTED]"));
99 }
100}