Skip to main content

hyperstack_auth/
keys.rs

1use crate::error::AuthError;
2use ed25519_dalek::{
3    Signature, Signer, SigningKey as EdSigningKey, Verifier, VerifyingKey as EdVerifyingKey,
4};
5use std::fs;
6use std::path::Path;
7
8/// A signing key for token issuance
9#[derive(Debug, Clone)]
10pub struct SigningKey {
11    inner: EdSigningKey,
12}
13
14impl SigningKey {
15    /// Generate a new random signing key
16    pub fn generate() -> Self {
17        use rand::rngs::OsRng;
18        use rand::RngCore;
19        let mut bytes = [0u8; 32];
20        OsRng.fill_bytes(&mut bytes);
21        Self {
22            inner: EdSigningKey::from_bytes(&bytes),
23        }
24    }
25
26    /// Load from raw bytes (32-byte seed)
27    pub fn from_bytes(bytes: &[u8; 32]) -> Self {
28        Self {
29            inner: EdSigningKey::from_bytes(bytes),
30        }
31    }
32
33    /// Get the corresponding verifying key
34    pub fn verifying_key(&self) -> VerifyingKey {
35        VerifyingKey {
36            inner: self.inner.verifying_key(),
37        }
38    }
39
40    /// Get a stable key identifier derived from the public key.
41    pub fn key_id(&self) -> String {
42        self.verifying_key().key_id()
43    }
44
45    /// Sign a message
46    pub fn sign(&self, message: &[u8]) -> Signature {
47        self.inner.sign(message)
48    }
49
50    /// Export to bytes
51    pub fn to_bytes(&self) -> [u8; 32] {
52        self.inner.to_bytes()
53    }
54
55    /// Export to keypair bytes (64 bytes: 32 secret + 32 public)
56    pub fn to_keypair_bytes(&self) -> [u8; 64] {
57        self.inner.to_keypair_bytes()
58    }
59
60    /// Load from keypair bytes
61    pub fn from_keypair_bytes(bytes: &[u8; 64]) -> Result<Self, AuthError> {
62        let key = EdSigningKey::from_keypair_bytes(bytes)
63            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid keypair: {:?}", e)))?;
64        Ok(Self { inner: key })
65    }
66
67    /// Export to PKCS#8 DER format (for use with jsonwebtoken)
68    pub fn to_pkcs8_der(&self) -> Result<Vec<u8>, AuthError> {
69        use ed25519_dalek::pkcs8::EncodePrivateKey;
70        self.inner
71            .to_pkcs8_der()
72            .map(|der| der.as_bytes().to_vec())
73            .map_err(|e| AuthError::InvalidKeyFormat(format!("PKCS#8 encoding failed: {:?}", e)))
74    }
75}
76
77/// A verifying key for token verification
78#[derive(Debug, Clone)]
79pub struct VerifyingKey {
80    pub(crate) inner: EdVerifyingKey,
81}
82
83impl VerifyingKey {
84    /// Load from raw bytes (32-byte public key)
85    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, AuthError> {
86        let key = EdVerifyingKey::from_bytes(bytes)
87            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid public key: {:?}", e)))?;
88        Ok(Self { inner: key })
89    }
90
91    /// Verify a signature
92    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), AuthError> {
93        self.inner
94            .verify(message, signature)
95            .map_err(|e| AuthError::InvalidKeyFormat(format!("Verification failed: {:?}", e)))
96    }
97
98    /// Get raw bytes
99    pub fn to_bytes(&self) -> [u8; 32] {
100        self.inner.to_bytes()
101    }
102
103    /// Get a stable key identifier derived from the public key.
104    pub fn key_id(&self) -> String {
105        let hex = self
106            .to_bytes()
107            .into_iter()
108            .map(|byte| format!("{byte:02x}"))
109            .collect::<String>();
110        hex[..16].to_string()
111    }
112
113    /// Export to SubjectPublicKeyInfo (SPKI) DER format (for use with jsonwebtoken)
114    pub fn to_spki_der(&self) -> Result<Vec<u8>, AuthError> {
115        use ed25519_dalek::pkcs8::EncodePublicKey;
116        self.inner
117            .to_public_key_der()
118            .map(|der| der.as_bytes().to_vec())
119            .map_err(|e| AuthError::InvalidKeyFormat(format!("SPKI encoding failed: {:?}", e)))
120    }
121}
122
123/// Key loader for different sources
124pub struct KeyLoader;
125
126impl KeyLoader {
127    /// Load signing key from environment variable (base64-encoded bytes)
128    pub fn signing_key_from_env(var_name: &str) -> Result<SigningKey, AuthError> {
129        let b64 = std::env::var(var_name).map_err(|_| {
130            AuthError::KeyLoadingFailed(format!("Environment variable {} not set", var_name))
131        })?;
132        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
133            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
134        let key_bytes: [u8; 32] = bytes
135            .try_into()
136            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
137        Ok(SigningKey::from_bytes(&key_bytes))
138    }
139
140    /// Load verifying key from environment variable (base64-encoded bytes)
141    pub fn verifying_key_from_env(var_name: &str) -> Result<VerifyingKey, AuthError> {
142        let b64 = std::env::var(var_name).map_err(|_| {
143            AuthError::KeyLoadingFailed(format!("Environment variable {} not set", var_name))
144        })?;
145        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
146            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
147        let key_bytes: [u8; 32] = bytes
148            .try_into()
149            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
150        VerifyingKey::from_bytes(&key_bytes)
151    }
152
153    /// Generate and save a new key pair to files (base64-encoded)
154    pub fn generate_and_save_keys(
155        signing_key_path: impl AsRef<Path>,
156        verifying_key_path: impl AsRef<Path>,
157    ) -> Result<(SigningKey, VerifyingKey), AuthError> {
158        let signing_key = SigningKey::generate();
159        let verifying_key = signing_key.verifying_key();
160
161        // Save signing key (base64)
162        let signing_b64 = base64::Engine::encode(
163            &base64::engine::general_purpose::STANDARD,
164            signing_key.to_bytes(),
165        );
166        fs::write(signing_key_path, signing_b64)?;
167
168        // Save verifying key (base64)
169        let verifying_b64 = base64::Engine::encode(
170            &base64::engine::general_purpose::STANDARD,
171            verifying_key.to_bytes(),
172        );
173        fs::write(verifying_key_path, verifying_b64)?;
174
175        Ok((signing_key, verifying_key))
176    }
177
178    /// Load signing key from file (base64-encoded)
179    pub fn signing_key_from_file(path: impl AsRef<Path>) -> Result<SigningKey, AuthError> {
180        let b64 = fs::read_to_string(path)?;
181        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
182            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
183        let key_bytes: [u8; 32] = bytes
184            .try_into()
185            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
186        Ok(SigningKey::from_bytes(&key_bytes))
187    }
188
189    /// Load verifying key from file (base64-encoded)
190    pub fn verifying_key_from_file(path: impl AsRef<Path>) -> Result<VerifyingKey, AuthError> {
191        let b64 = fs::read_to_string(path)?;
192        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
193            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
194        let key_bytes: [u8; 32] = bytes
195            .try_into()
196            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
197        VerifyingKey::from_bytes(&key_bytes)
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_generate_and_sign() {
207        let signing_key = SigningKey::generate();
208        let message = b"test message";
209        let signature = signing_key.sign(message);
210
211        let verifying_key = signing_key.verifying_key();
212        assert!(verifying_key.verify(message, &signature).is_ok());
213    }
214
215    #[test]
216    fn test_bytes_roundtrip() {
217        let signing_key = SigningKey::generate();
218        let bytes = signing_key.to_bytes();
219
220        let loaded = SigningKey::from_bytes(&bytes);
221        assert_eq!(
222            signing_key.verifying_key().to_bytes(),
223            loaded.verifying_key().to_bytes()
224        );
225    }
226
227    #[test]
228    fn test_keypair_bytes_roundtrip() {
229        let signing_key = SigningKey::generate();
230        let keypair_bytes = signing_key.to_keypair_bytes();
231
232        let loaded = SigningKey::from_keypair_bytes(&keypair_bytes).unwrap();
233        assert_eq!(
234            signing_key.verifying_key().to_bytes(),
235            loaded.verifying_key().to_bytes()
236        );
237    }
238}