Skip to main content

arete_auth/
keys.rs

1use crate::error::AuthError;
2use ed25519_dalek::{
3    Signature, Signer, SigningKey as EdSigningKey, 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    ///
93    /// Uses strict verification, which rejects small-order public keys and
94    /// signature points: with such a key a signature can be forged without
95    /// the private key.
96    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), AuthError> {
97        self.inner
98            .verify_strict(message, signature)
99            .map_err(|e| AuthError::InvalidKeyFormat(format!("Verification failed: {:?}", e)))
100    }
101
102    /// Get raw bytes
103    pub fn to_bytes(&self) -> [u8; 32] {
104        self.inner.to_bytes()
105    }
106
107    /// Get a stable key identifier derived from the public key.
108    pub fn key_id(&self) -> String {
109        let hex = self
110            .to_bytes()
111            .into_iter()
112            .map(|byte| format!("{byte:02x}"))
113            .collect::<String>();
114        hex[..16].to_string()
115    }
116
117    /// Export to SubjectPublicKeyInfo (SPKI) DER format (for use with jsonwebtoken)
118    pub fn to_spki_der(&self) -> Result<Vec<u8>, AuthError> {
119        use ed25519_dalek::pkcs8::EncodePublicKey;
120        self.inner
121            .to_public_key_der()
122            .map(|der| der.as_bytes().to_vec())
123            .map_err(|e| AuthError::InvalidKeyFormat(format!("SPKI encoding failed: {:?}", e)))
124    }
125}
126
127/// Key loader for different sources
128pub struct KeyLoader;
129
130impl KeyLoader {
131    /// Load signing key from environment variable (base64-encoded bytes)
132    pub fn signing_key_from_env(var_name: &str) -> Result<SigningKey, AuthError> {
133        let b64 = std::env::var(var_name).map_err(|_| {
134            AuthError::KeyLoadingFailed(format!("Environment variable {} not set", var_name))
135        })?;
136        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
137            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
138        let key_bytes: [u8; 32] = bytes
139            .try_into()
140            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
141        Ok(SigningKey::from_bytes(&key_bytes))
142    }
143
144    /// Load verifying key from environment variable (base64-encoded bytes)
145    pub fn verifying_key_from_env(var_name: &str) -> Result<VerifyingKey, AuthError> {
146        let b64 = std::env::var(var_name).map_err(|_| {
147            AuthError::KeyLoadingFailed(format!("Environment variable {} not set", var_name))
148        })?;
149        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
150            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
151        let key_bytes: [u8; 32] = bytes
152            .try_into()
153            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
154        VerifyingKey::from_bytes(&key_bytes)
155    }
156
157    /// Generate and save a new key pair to files (base64-encoded).
158    ///
159    /// On Unix the signing key file is owner-only (`0600`), including when it
160    /// already existed with looser permissions.
161    pub fn generate_and_save_keys(
162        signing_key_path: impl AsRef<Path>,
163        verifying_key_path: impl AsRef<Path>,
164    ) -> Result<(SigningKey, VerifyingKey), AuthError> {
165        let signing_key = SigningKey::generate();
166        let verifying_key = signing_key.verifying_key();
167
168        // Save signing key (base64)
169        let signing_b64 = base64::Engine::encode(
170            &base64::engine::general_purpose::STANDARD,
171            signing_key.to_bytes(),
172        );
173        write_owner_only(signing_key_path.as_ref(), signing_b64.as_bytes())?;
174
175        // Save verifying key (base64)
176        let verifying_b64 = base64::Engine::encode(
177            &base64::engine::general_purpose::STANDARD,
178            verifying_key.to_bytes(),
179        );
180        fs::write(verifying_key_path, verifying_b64)?;
181
182        Ok((signing_key, verifying_key))
183    }
184
185    /// Load signing key from file (base64-encoded)
186    pub fn signing_key_from_file(path: impl AsRef<Path>) -> Result<SigningKey, AuthError> {
187        let b64 = fs::read_to_string(path)?;
188        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
189            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
190        let key_bytes: [u8; 32] = bytes
191            .try_into()
192            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
193        Ok(SigningKey::from_bytes(&key_bytes))
194    }
195
196    /// Load verifying key from file (base64-encoded)
197    pub fn verifying_key_from_file(path: impl AsRef<Path>) -> Result<VerifyingKey, AuthError> {
198        let b64 = fs::read_to_string(path)?;
199        let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &b64)
200            .map_err(|e| AuthError::InvalidKeyFormat(format!("Invalid base64: {}", e)))?;
201        let key_bytes: [u8; 32] = bytes
202            .try_into()
203            .map_err(|_| AuthError::InvalidKeyFormat("Invalid key length".to_string()))?;
204        VerifyingKey::from_bytes(&key_bytes)
205    }
206}
207
208/// Write `contents` to `path`, readable and writable by the owner only.
209///
210/// Permissions are set on the open handle before any secret byte is
211/// written, so a pre-existing file with looser permissions is tightened too.
212fn write_owner_only(path: &Path, contents: &[u8]) -> std::io::Result<()> {
213    use std::io::Write;
214
215    let mut options = fs::OpenOptions::new();
216    options.write(true).create(true).truncate(true);
217    #[cfg(unix)]
218    {
219        use std::os::unix::fs::OpenOptionsExt;
220        options.mode(0o600);
221    }
222    let mut file = options.open(path)?;
223    #[cfg(unix)]
224    {
225        use std::os::unix::fs::PermissionsExt;
226        file.set_permissions(fs::Permissions::from_mode(0o600))?;
227    }
228    file.write_all(contents)?;
229    file.sync_all()
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[cfg(unix)]
237    fn mode(path: &Path) -> u32 {
238        use std::os::unix::fs::PermissionsExt;
239        fs::metadata(path).unwrap().permissions().mode() & 0o777
240    }
241
242    #[cfg(unix)]
243    #[test]
244    fn generated_signing_key_is_owner_only() {
245        let dir = tempfile::tempdir().unwrap();
246        let signing = dir.path().join("signing.key");
247        let verifying = dir.path().join("verifying.key");
248
249        let (signing_key, _) = KeyLoader::generate_and_save_keys(&signing, &verifying).unwrap();
250
251        assert_eq!(mode(&signing), 0o600);
252        let loaded = KeyLoader::signing_key_from_file(&signing).unwrap();
253        assert_eq!(loaded.to_bytes(), signing_key.to_bytes());
254    }
255
256    #[cfg(unix)]
257    #[test]
258    fn regenerating_over_a_world_readable_signing_key_tightens_it() {
259        use std::os::unix::fs::PermissionsExt;
260
261        let dir = tempfile::tempdir().unwrap();
262        let signing = dir.path().join("signing.key");
263        let verifying = dir.path().join("verifying.key");
264        fs::write(&signing, "stale").unwrap();
265        fs::set_permissions(&signing, fs::Permissions::from_mode(0o644)).unwrap();
266
267        KeyLoader::generate_and_save_keys(&signing, &verifying).unwrap();
268
269        assert_eq!(mode(&signing), 0o600);
270    }
271
272    #[test]
273    fn test_generate_and_sign() {
274        let signing_key = SigningKey::generate();
275        let message = b"test message";
276        let signature = signing_key.sign(message);
277
278        let verifying_key = signing_key.verifying_key();
279        assert!(verifying_key.verify(message, &signature).is_ok());
280    }
281
282    #[test]
283    fn test_bytes_roundtrip() {
284        let signing_key = SigningKey::generate();
285        let bytes = signing_key.to_bytes();
286
287        let loaded = SigningKey::from_bytes(&bytes);
288        assert_eq!(
289            signing_key.verifying_key().to_bytes(),
290            loaded.verifying_key().to_bytes()
291        );
292    }
293
294    #[test]
295    fn test_keypair_bytes_roundtrip() {
296        let signing_key = SigningKey::generate();
297        let keypair_bytes = signing_key.to_keypair_bytes();
298
299        let loaded = SigningKey::from_keypair_bytes(&keypair_bytes).unwrap();
300        assert_eq!(
301            signing_key.verifying_key().to_bytes(),
302            loaded.verifying_key().to_bytes()
303        );
304    }
305}