ap_noise/psk.rs
1//! PSK (Pre-Shared Key) module.
2//!
3//! Pre-Shared Keys (PSKs) provide authentication and protection against man-in-the-middle attacks
4//! in Noise protocol handshakes. When both parties share a secret PSK established through a secure
5//! out-of-band channel (e.g., QR code, NFC, secure messaging), the handshake will fail if an
6//! attacker attempts to intercept or modify messages, and no fingerprint verification is needed.
7
8use rand::RngCore;
9use std::fmt::Debug;
10
11/// PSK length in bytes (32 bytes for Noise protocol)
12pub const PSK_LENGTH: usize = 32;
13
14#[derive(Clone, PartialEq, zeroize::ZeroizeOnDrop)]
15pub struct Psk([u8; PSK_LENGTH]);
16
17impl Debug for Psk {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 use sha2::{Digest, Sha256 as Sha256Hash};
20 let hash = Sha256Hash::digest(self.0);
21 let preview = format!(
22 "{:02x}{:02x}{:02x}{:02x}...",
23 hash[0], hash[1], hash[2], hash[3]
24 );
25 write!(f, "Psk({preview})")
26 }
27}
28
29impl Psk {
30 /// A null PSK (all zeroes).
31 ///
32 /// This should only be used when PSK authentication is not required and only encryption
33 /// is desired. Using a null PSK provides confidentiality but not authentication.
34 pub fn null() -> Self {
35 Psk([0u8; PSK_LENGTH])
36 }
37
38 /// Generate a random PSK using a cryptographically secure random number generator.
39 ///
40 /// This is the recommended way to create a PSK for secure authentication. The PSK
41 /// must then be shared with the other party through a secure out-of-band channel
42 /// (QR code, NFC, secure messaging, etc.).
43 ///
44 /// # Example
45 ///
46 /// ```rust
47 /// use ap_noise::Psk;
48 ///
49 /// let psk = Psk::generate();
50 /// ```
51 pub fn generate() -> Self {
52 let mut rng = rand::thread_rng();
53 let mut bytes = [0u8; PSK_LENGTH];
54 rng.fill_bytes(&mut bytes);
55 Psk(bytes)
56 }
57
58 /// Construct a PSK from a 32-byte array.
59 pub fn from_bytes(bytes: [u8; 32]) -> Self {
60 Psk(bytes)
61 }
62
63 /// Export the PSK as a 32-byte array.
64 pub fn to_bytes(&self) -> [u8; 32] {
65 self.0
66 }
67
68 /// Encode the PSK as a hexadecimal string.
69 ///
70 /// This is the recommended format for storing or transmitting PSKs (e.g., in QR codes,
71 /// configuration files, or secure storage). The resulting string is 64 characters long.
72 ///
73 /// # Example
74 ///
75 /// ```rust
76 /// use ap_noise::Psk;
77 /// use rand::thread_rng;
78 ///
79 /// let psk = Psk::generate();
80 /// let encoded = psk.to_hex();
81 /// assert_eq!(encoded.len(), 64);
82 /// ```
83 pub fn to_hex(&self) -> String {
84 hex::encode(self.0)
85 }
86
87 /// Decode a PSK from a hexadecimal string.
88 ///
89 /// Returns an error if the string is not valid hex or does not decode to exactly
90 /// 32 bytes.
91 ///
92 /// # Example
93 ///
94 /// ```rust
95 /// use ap_noise::Psk;
96 /// use rand::thread_rng;
97 ///
98 /// let psk = Psk::generate();
99 /// let encoded = psk.to_hex();
100 /// let decoded = Psk::from_hex(&encoded).unwrap();
101 /// assert_eq!(psk.to_bytes(), decoded.to_bytes());
102 /// ```
103 pub fn from_hex(s: &str) -> Result<Self, crate::error::NoiseProtocolError> {
104 let bytes =
105 hex::decode(s).map_err(|_| crate::error::NoiseProtocolError::InvalidPskEncoding)?;
106
107 if bytes.len() != PSK_LENGTH {
108 return Err(crate::error::NoiseProtocolError::InvalidPskLength);
109 }
110
111 let mut arr = [0u8; PSK_LENGTH];
112 arr.copy_from_slice(&bytes);
113 Ok(Psk(arr))
114 }
115
116 pub fn as_slice(&self) -> &[u8] {
117 &self.0
118 }
119
120 /// Compute a stable, non-secret identifier for this PSK.
121 ///
122 /// Returns `hex(SHA256(psk)[0..8])` — a 16-character hex string derived from
123 /// the first 8 bytes of the PSK's SHA-256 hash. This serves as a lookup key
124 /// for matching incoming handshakes to pending pairings without revealing the
125 /// PSK itself.
126 pub fn id(&self) -> String {
127 use sha2::{Digest, Sha256 as Sha256Hash};
128 let hash = Sha256Hash::digest(self.0);
129 hex::encode(&hash[..8])
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_null_psk() {
139 let psk = Psk::null();
140 assert_eq!(psk.to_bytes(), [0u8; PSK_LENGTH]);
141 }
142
143 #[test]
144 fn test_generate_psk() {
145 let psk1 = Psk::generate();
146 let psk2 = Psk::generate();
147
148 // PSKs should be different
149 assert_ne!(psk1.to_bytes(), psk2.to_bytes());
150 }
151
152 #[test]
153 fn test_bytes_roundtrip() {
154 let psk = Psk::generate();
155 let bytes = psk.to_bytes();
156 let psk_from_bytes = Psk::from_bytes(bytes);
157 assert_eq!(psk.to_bytes(), psk_from_bytes.to_bytes());
158 }
159
160 #[test]
161 fn test_hex_roundtrip() {
162 let psk = Psk::generate();
163 let encoded = psk.to_hex();
164 assert_eq!(encoded.len(), 64);
165 let decoded = Psk::from_hex(&encoded).expect("Failed to decode PSK from hex");
166 assert_eq!(psk.to_bytes(), decoded.to_bytes());
167 }
168}