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#[derive(Debug, Clone)]
10pub struct SigningKey {
11 inner: EdSigningKey,
12}
13
14impl SigningKey {
15 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 pub fn from_bytes(bytes: &[u8; 32]) -> Self {
28 Self {
29 inner: EdSigningKey::from_bytes(bytes),
30 }
31 }
32
33 pub fn verifying_key(&self) -> VerifyingKey {
35 VerifyingKey {
36 inner: self.inner.verifying_key(),
37 }
38 }
39
40 pub fn key_id(&self) -> String {
42 self.verifying_key().key_id()
43 }
44
45 pub fn sign(&self, message: &[u8]) -> Signature {
47 self.inner.sign(message)
48 }
49
50 pub fn to_bytes(&self) -> [u8; 32] {
52 self.inner.to_bytes()
53 }
54
55 pub fn to_keypair_bytes(&self) -> [u8; 64] {
57 self.inner.to_keypair_bytes()
58 }
59
60 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 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#[derive(Debug, Clone)]
79pub struct VerifyingKey {
80 pub(crate) inner: EdVerifyingKey,
81}
82
83impl VerifyingKey {
84 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 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 pub fn to_bytes(&self) -> [u8; 32] {
104 self.inner.to_bytes()
105 }
106
107 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 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
127pub struct KeyLoader;
129
130impl KeyLoader {
131 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 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 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 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 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 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 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
208fn 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}