Skip to main content

astrid_crypto/
keypair.rs

1//! Ed25519 key pairs with secure memory handling.
2//!
3//! Provides key generation, signing, and verification for:
4//! - Runtime identity (signs audit entries, capability tokens)
5//! - User identity verification (optional user signing keys)
6
7use ed25519_dalek::{Signer, SigningKey, VerifyingKey};
8use rand::{TryRng, rngs::SysRng};
9use serde::{Deserialize, Serialize};
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12use crate::error::{CryptoError, CryptoResult};
13use crate::signature::Signature;
14
15/// An Ed25519 key pair with secure memory handling.
16///
17/// The secret key is zeroized on drop to prevent leaking sensitive material.
18#[derive(ZeroizeOnDrop)]
19pub struct KeyPair {
20    #[zeroize(skip)] // VerifyingKey doesn't implement Zeroize
21    verifying_key: VerifyingKey,
22    signing_key: SigningKey,
23}
24
25impl KeyPair {
26    /// Generate a new random key pair.
27    ///
28    /// # Panics
29    ///
30    /// Panics if the OS CSPRNG is unavailable.
31    #[must_use]
32    pub fn generate() -> Self {
33        let mut secret = [0u8; 32];
34        SysRng
35            .try_fill_bytes(&mut secret)
36            .expect("OS CSPRNG unavailable while generating key pair");
37        let signing_key = SigningKey::from_bytes(&secret);
38        let verifying_key = signing_key.verifying_key();
39        secret.zeroize();
40        Self {
41            verifying_key,
42            signing_key,
43        }
44    }
45
46    /// Create from a secret key (32 bytes).
47    ///
48    /// # Errors
49    ///
50    /// Returns [`CryptoError::InvalidKeyLength`] if the slice is not exactly 32 bytes.
51    pub fn from_secret_key(bytes: &[u8]) -> CryptoResult<Self> {
52        if bytes.len() != 32 {
53            return Err(CryptoError::InvalidKeyLength {
54                expected: 32,
55                actual: bytes.len(),
56            });
57        }
58
59        let mut secret = [0u8; 32];
60        secret.copy_from_slice(bytes);
61
62        let signing_key = SigningKey::from_bytes(&secret);
63        let verifying_key = signing_key.verifying_key();
64
65        // Zeroize the temporary buffer
66        secret.zeroize();
67
68        Ok(Self {
69            verifying_key,
70            signing_key,
71        })
72    }
73
74    /// Get the public key bytes (32 bytes).
75    #[must_use]
76    pub fn public_key_bytes(&self) -> &[u8; 32] {
77        self.verifying_key.as_bytes()
78    }
79
80    /// Get a short key ID (first 8 bytes of public key).
81    ///
82    /// Useful for identifying keys in logs without exposing the full key.
83    #[must_use]
84    pub fn key_id(&self) -> [u8; 8] {
85        let mut id = [0u8; 8];
86        id.copy_from_slice(&self.public_key_bytes()[..8]);
87        id
88    }
89
90    /// Get the key ID as a hex string.
91    #[must_use]
92    pub fn key_id_hex(&self) -> String {
93        hex::encode(self.key_id())
94    }
95
96    /// Sign a message.
97    #[must_use]
98    pub fn sign(&self, message: &[u8]) -> Signature {
99        let sig = self.signing_key.sign(message);
100        Signature::from(sig)
101    }
102
103    /// Verify a signature (convenience method using our public key).
104    ///
105    /// # Errors
106    ///
107    /// Returns [`CryptoError::SignatureVerificationFailed`] if verification fails.
108    pub fn verify(&self, message: &[u8], signature: &Signature) -> CryptoResult<()> {
109        signature.verify(message, self.public_key_bytes())
110    }
111
112    /// Export the public key for serialization.
113    #[must_use]
114    pub fn export_public_key(&self) -> PublicKey {
115        PublicKey::from_bytes(*self.public_key_bytes())
116    }
117
118    /// Export the secret key bytes (careful - sensitive!).
119    ///
120    /// This should only be used for secure storage.
121    #[must_use]
122    pub fn secret_key_bytes(&self) -> [u8; 32] {
123        self.signing_key.to_bytes()
124    }
125}
126
127impl std::fmt::Debug for KeyPair {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        f.debug_struct("KeyPair")
130            .field("key_id", &self.key_id_hex())
131            .finish_non_exhaustive()
132    }
133}
134
135/// A public key (safe to share, serialize, etc.).
136#[derive(Clone, Copy, PartialEq, Eq, Hash)]
137pub struct PublicKey([u8; 32]);
138
139impl PublicKey {
140    /// Create from raw bytes.
141    #[must_use]
142    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
143        Self(bytes)
144    }
145
146    /// Try to create from a slice.
147    ///
148    /// # Errors
149    ///
150    /// Returns [`CryptoError::InvalidKeyLength`] if the slice is not exactly 32 bytes.
151    pub fn try_from_slice(slice: &[u8]) -> CryptoResult<Self> {
152        if slice.len() != 32 {
153            return Err(CryptoError::InvalidKeyLength {
154                expected: 32,
155                actual: slice.len(),
156            });
157        }
158        let mut bytes = [0u8; 32];
159        bytes.copy_from_slice(slice);
160        Ok(Self(bytes))
161    }
162
163    /// Get the raw bytes.
164    #[must_use]
165    pub const fn as_bytes(&self) -> &[u8; 32] {
166        &self.0
167    }
168
169    /// Get a short key ID (first 8 bytes).
170    #[must_use]
171    pub fn key_id(&self) -> [u8; 8] {
172        let mut id = [0u8; 8];
173        id.copy_from_slice(&self.0[..8]);
174        id
175    }
176
177    /// Get the key ID as a hex string.
178    #[must_use]
179    pub fn key_id_hex(&self) -> String {
180        hex::encode(self.key_id())
181    }
182
183    /// Encode as hex string.
184    #[must_use]
185    pub fn to_hex(&self) -> String {
186        hex::encode(self.0)
187    }
188
189    /// Decode from hex string.
190    ///
191    /// # Errors
192    ///
193    /// Returns an error if the string is not valid hex or not 32 bytes.
194    pub fn from_hex(s: &str) -> CryptoResult<Self> {
195        let bytes = hex::decode(s).map_err(|_| CryptoError::InvalidHexEncoding)?;
196        Self::try_from_slice(&bytes)
197    }
198
199    /// Encode as base64 string.
200    #[must_use]
201    pub fn to_base64(&self) -> String {
202        use base64::Engine;
203        base64::engine::general_purpose::STANDARD.encode(self.0)
204    }
205
206    /// Decode from base64 string.
207    ///
208    /// # Errors
209    ///
210    /// Returns an error if the string is not valid base64 or not 32 bytes.
211    pub fn from_base64(s: &str) -> CryptoResult<Self> {
212        use base64::Engine;
213        let bytes = base64::engine::general_purpose::STANDARD
214            .decode(s)
215            .map_err(|_| CryptoError::InvalidBase64Encoding)?;
216        Self::try_from_slice(&bytes)
217    }
218
219    /// Verify a signature against this public key.
220    ///
221    /// # Errors
222    ///
223    /// Returns [`CryptoError::SignatureVerificationFailed`] if verification fails.
224    pub fn verify(&self, message: &[u8], signature: &Signature) -> CryptoResult<()> {
225        signature.verify(message, &self.0)
226    }
227}
228
229impl std::fmt::Debug for PublicKey {
230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231        write!(f, "PublicKey({})", self.key_id_hex())
232    }
233}
234
235impl std::fmt::Display for PublicKey {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        write!(f, "{}", self.to_hex())
238    }
239}
240
241impl Serialize for PublicKey {
242    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
243    where
244        S: serde::Serializer,
245    {
246        serializer.serialize_str(&self.to_base64())
247    }
248}
249
250impl<'de> Deserialize<'de> for PublicKey {
251    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252    where
253        D: serde::Deserializer<'de>,
254    {
255        let s = String::deserialize(deserializer)?;
256        Self::from_base64(&s).map_err(serde::de::Error::custom)
257    }
258}
259
260impl From<[u8; 32]> for PublicKey {
261    fn from(bytes: [u8; 32]) -> Self {
262        Self(bytes)
263    }
264}
265
266impl From<PublicKey> for [u8; 32] {
267    fn from(pk: PublicKey) -> Self {
268        pk.0
269    }
270}
271
272impl AsRef<[u8]> for PublicKey {
273    fn as_ref(&self) -> &[u8] {
274        &self.0
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_keypair_generation() {
284        let kp1 = KeyPair::generate();
285        let kp2 = KeyPair::generate();
286
287        // Different keypairs have different public keys
288        assert_ne!(kp1.public_key_bytes(), kp2.public_key_bytes());
289    }
290
291    #[test]
292    fn test_keypair_from_secret() {
293        let original = KeyPair::generate();
294        let secret = original.secret_key_bytes();
295
296        let restored = KeyPair::from_secret_key(&secret).unwrap();
297
298        assert_eq!(original.public_key_bytes(), restored.public_key_bytes());
299    }
300
301    #[test]
302    fn test_sign_verify() {
303        let keypair = KeyPair::generate();
304        let message = b"hello world";
305
306        let signature = keypair.sign(message);
307        assert!(keypair.verify(message, &signature).is_ok());
308
309        // Wrong message fails
310        assert!(keypair.verify(b"wrong", &signature).is_err());
311    }
312
313    #[test]
314    fn test_key_id() {
315        let keypair = KeyPair::generate();
316        let key_id = keypair.key_id();
317
318        // Key ID is first 8 bytes of public key
319        assert_eq!(&key_id[..], &keypair.public_key_bytes()[..8]);
320
321        // Hex encoding works
322        let hex_id = keypair.key_id_hex();
323        assert_eq!(hex_id.len(), 16); // 8 bytes = 16 hex chars
324    }
325
326    #[test]
327    fn test_public_key_encoding() {
328        let keypair = KeyPair::generate();
329        let pk = keypair.export_public_key();
330
331        // Hex roundtrip
332        let hex = pk.to_hex();
333        let decoded = PublicKey::from_hex(&hex).unwrap();
334        assert_eq!(pk, decoded);
335
336        // Base64 roundtrip
337        let b64 = pk.to_base64();
338        let decoded = PublicKey::from_base64(&b64).unwrap();
339        assert_eq!(pk, decoded);
340    }
341
342    #[test]
343    fn test_public_key_verify() {
344        let keypair = KeyPair::generate();
345        let pk = keypair.export_public_key();
346        let message = b"test";
347
348        let sig = keypair.sign(message);
349        assert!(pk.verify(message, &sig).is_ok());
350    }
351
352    #[test]
353    fn test_invalid_key_length() {
354        let result = KeyPair::from_secret_key(&[0u8; 31]);
355        assert!(matches!(result, Err(CryptoError::InvalidKeyLength { .. })));
356    }
357}