1use std::fmt::Display;
2
3use crate::{
4 public_key::{PublicKey, RawPk},
5 util::{raw_scrypt_params, validate_comment},
6 ErrorKind, PublicKeyBox, Result, SError, ALG_SIZE, CHK_ALG, CHK_SIZE, COMPONENT_SIZE, KDF_ALG,
7 KDF_ALG_NONE, KDF_LIMIT_SIZE, KDF_SALT_SIZE, KEYNUM_SK_SIZE, KEY_SIG_ALG, KID_SIZE, MEMLIMIT,
8 N_LOG2_MAX, OPSLIMIT,
9};
10use base64::Engine;
11use blake2::{Blake2b, Digest};
12use ed25519_dalek::{
13 ed25519::{self, ComponentBytes},
14 Signer,
15};
16use getrandom::rand_core::TryRng;
17use zeroize::{Zeroize, ZeroizeOnDrop};
18
19#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
23pub struct SecretKeyBox<'s> {
24 #[zeroize(skip)]
25 pub(crate) untrusted_comment: Option<&'s str>,
26 secret_key: SecretKey,
27}
28impl Display for SecretKeyBox<'_> {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 let mut s = String::new();
31 s.push_str("untrusted comment: ");
32 if let Some(c) = self.untrusted_comment {
33 s.push_str(c);
34 }
35 s.push('\n');
36 let encoder = base64::engine::general_purpose::STANDARD;
37 let mut sk_format = vec![];
38 sk_format.extend_from_slice(&self.secret_key.sig_alg);
39 sk_format.extend_from_slice(&self.secret_key.kdf_alg);
40 sk_format.extend_from_slice(&self.secret_key.cksum_alg);
41 sk_format.extend_from_slice(&self.secret_key.kdf_salt);
42 sk_format.extend_from_slice(&self.secret_key.kdf_opslimit.to_le_bytes());
43 sk_format.extend_from_slice(&self.secret_key.kdf_memlimit.to_le_bytes());
44 sk_format.extend_from_slice(&self.secret_key.keynum_sk);
45 let mut sk = encoder.encode(&sk_format);
46 sk_format.zeroize();
47 s.push_str(&sk);
48 sk.zeroize();
49 s.push('\n');
50
51 write!(f, "{}", s)
52 }
53}
54type Blake2b256 = Blake2b<blake2::digest::consts::U32>;
55impl<'s> SecretKeyBox<'s> {
56 fn new(untrusted_comment: Option<&'s str>, secret_key: SecretKey) -> Result<Self> {
57 validate_comment(untrusted_comment, ErrorKind::SecretKey)?;
58 Ok(Self {
59 untrusted_comment,
60 secret_key,
61 })
62 }
63 pub(crate) fn sig_alg(&self) -> [u8; ALG_SIZE] {
64 self.secret_key.sig_alg
65 }
66 pub fn from_signing_key(
67 signing_key: ed25519_dalek::SigningKey,
68 kid: &[u8; KID_SIZE],
69 password: Option<&[u8]>,
70 untrusted_comment: Option<&'s str>,
71 ) -> Result<Self> {
72 let sk = signing_key.to_bytes();
73 let pk = signing_key.verifying_key().to_bytes();
74 let (kdf_alg, kdf_salt, kdf_opslimit, kdf_memlimit, mut kdf_buf, checksum) =
75 if let Some(password) = password {
76 let mut kdf_salt = [0u8; KDF_SALT_SIZE];
77 getrandom::SysRng.try_fill_bytes(&mut kdf_salt)?;
78 let mut hash = Blake2b256::new();
79 hash.update(KEY_SIG_ALG);
80 hash.update(kid);
81 hash.update(sk);
82 hash.update(pk);
83 (
84 KDF_ALG,
85 kdf_salt,
86 OPSLIMIT,
87 MEMLIMIT,
88 kdf(Some(password), &kdf_salt, OPSLIMIT, MEMLIMIT)?,
89 hash.finalize().to_vec().try_into().unwrap(),
90 )
91 } else {
92 (
93 KDF_ALG_NONE,
94 [0u8; KDF_SALT_SIZE],
95 0,
96 0,
97 [0u8; KEYNUM_SK_SIZE],
98 [0u8; CHK_SIZE],
99 )
100 };
101 let keynum_sk = KeynumSK {
102 key_id: *kid,
103 sec_key: RawSk(sk),
104 pub_key: pk,
105 checksum,
106 };
107 kdf_buf = keynum_sk.to_bytes(kdf_buf);
108 let secret_key = SecretKey {
109 sig_alg: KEY_SIG_ALG,
110 kdf_alg,
111 cksum_alg: CHK_ALG,
112 kdf_salt,
113 kdf_opslimit,
114 kdf_memlimit,
115 keynum_sk: kdf_buf,
116 };
117 kdf_buf.zeroize();
118 Self::new(untrusted_comment, secret_key)
119 }
120 pub(crate) fn sign(
121 &self,
122 message: &[u8],
123 password: Option<&[u8]>,
124 ) -> Result<ed25519::Signature> {
125 self.secret_key.sign(message, password)
126 }
127 pub(crate) fn xor_keynum_sk(&self, password: Option<&[u8]>) -> Result<KeynumSK> {
128 self.secret_key.xor_keynum_sk(password)
129 }
130 pub fn from_raw_str(s: &'s str) -> Result<Self> {
133 let secret_key = s.trim();
134 let decoder = base64::engine::general_purpose::STANDARD;
135 let mut sk_format = decoder
136 .decode(secret_key.as_bytes())
137 .map_err(|e| SError::new(crate::ErrorKind::SecretKey, e))?;
138 if sk_format.len()
139 != ALG_SIZE
140 + ALG_SIZE
141 + ALG_SIZE
142 + KDF_SALT_SIZE
143 + KDF_LIMIT_SIZE
144 + KDF_LIMIT_SIZE
145 + KEYNUM_SK_SIZE
146 {
147 return Err(SError::new(
148 crate::ErrorKind::SecretKey,
149 "invalid secret key length",
150 ));
151 }
152 let sig_alg = &sk_format[..ALG_SIZE];
153 let kdf_alg = &sk_format[ALG_SIZE..ALG_SIZE + ALG_SIZE];
154 let cksum_alg = &sk_format[ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE];
155 let kdf_salt = &sk_format
156 [ALG_SIZE + ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE];
157 let kdf_opslimit = u64::from_le_bytes(
158 sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE
159 ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE]
160 .try_into()
161 .unwrap(),
162 );
163 let kdf_memlimit = u64::from_le_bytes(
164 sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE
165 ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE + KDF_LIMIT_SIZE]
166 .try_into()
167 .unwrap(),
168 );
169
170 let secret_key = SecretKey {
171 sig_alg: sig_alg.try_into().unwrap(),
172 kdf_alg: kdf_alg.try_into().unwrap(),
173 cksum_alg: cksum_alg.try_into().unwrap(),
174 kdf_salt: kdf_salt.try_into().unwrap(),
175 kdf_opslimit,
176 kdf_memlimit,
177 keynum_sk: sk_format[ALG_SIZE
178 + ALG_SIZE
179 + ALG_SIZE
180 + KDF_SALT_SIZE
181 + KDF_LIMIT_SIZE
182 + KDF_LIMIT_SIZE..]
183 .try_into()
184 .unwrap(),
185 };
186 sk_format.zeroize();
187 SecretKeyBox::new(None, secret_key)
188 }
189 #[allow(clippy::should_implement_trait)]
193 pub fn from_str(s: &'s str) -> Result<Self> {
194 parse_secret_key(s)
195 }
196 pub fn untrusted_comment(&self) -> Option<&'s str> {
198 self.untrusted_comment
199 }
200 pub fn public_key(&self, password: Option<&[u8]>) -> Result<PublicKeyBox<'s>> {
202 pub_key_from_sec_key(self, password)
203 }
204}
205fn pub_key_from_sec_key<'s>(
206 sec_key: &SecretKeyBox<'s>,
207 password: Option<&[u8]>,
208) -> Result<PublicKeyBox<'s>> {
209 let keynum_sk = sec_key.xor_keynum_sk(password)?;
210 let pk_box = PublicKeyBox::new(
211 None,
212 PublicKey::new(
213 sec_key.secret_key.sig_alg,
214 keynum_sk.key_id,
215 RawPk(keynum_sk.pub_key),
216 ),
217 );
218 pk_box
219}
220
221fn parse_raw_secret_key(secret_key: &str) -> Result<SecretKey> {
222 let decoder = base64::engine::general_purpose::STANDARD;
223 let mut sk_format = decoder
224 .decode(secret_key.as_bytes())
225 .map_err(|e| SError::new(crate::ErrorKind::SecretKey, e))?;
226 if sk_format.len()
227 != ALG_SIZE
228 + ALG_SIZE
229 + ALG_SIZE
230 + KDF_SALT_SIZE
231 + KDF_LIMIT_SIZE
232 + KDF_LIMIT_SIZE
233 + KEYNUM_SK_SIZE
234 {
235 return Err(SError::new(
236 crate::ErrorKind::SecretKey,
237 "invalid secret key length",
238 ));
239 }
240 let sig_alg = &sk_format[..ALG_SIZE];
241 let kdf_alg = &sk_format[ALG_SIZE..ALG_SIZE + ALG_SIZE];
242 let cksum_alg = &sk_format[ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE];
243 let kdf_salt =
244 &sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE];
245 let kdf_opslimit = u64::from_le_bytes(
246 sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE
247 ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE]
248 .try_into()
249 .unwrap(),
250 );
251 let kdf_memlimit = u64::from_le_bytes(
252 sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE
253 ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE + KDF_LIMIT_SIZE]
254 .try_into()
255 .unwrap(),
256 );
257
258 let secret_key = SecretKey {
259 sig_alg: sig_alg.try_into().unwrap(),
260 kdf_alg: kdf_alg.try_into().unwrap(),
261 cksum_alg: cksum_alg.try_into().unwrap(),
262 kdf_salt: kdf_salt.try_into().unwrap(),
263 kdf_opslimit,
264 kdf_memlimit,
265 keynum_sk: sk_format
266 [ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE + KDF_LIMIT_SIZE..]
267 .try_into()
268 .unwrap(),
269 };
270 sk_format.zeroize();
271 Ok(secret_key)
272}
273fn parse_secret_key(s: &str) -> Result<SecretKeyBox<'_>> {
274 let mut lines = s.lines();
275 let untrusted_comment = lines
276 .next()
277 .ok_or_else(|| SError::new(crate::ErrorKind::SecretKey, "missing untrusted comment"))?
278 .strip_prefix("untrusted comment: ")
279 .ok_or_else(|| SError::new(crate::ErrorKind::SecretKey, "missing untrusted comment"))?;
280 validate_comment(Some(untrusted_comment), ErrorKind::SecretKey)?;
281 let secret_key = lines
282 .next()
283 .ok_or_else(|| SError::new(crate::ErrorKind::SecretKey, "missing secret key"))?;
284 if lines.next().is_some() {
285 return Err(SError::new(
286 crate::ErrorKind::SecretKey,
287 "unexpected extra data",
288 ));
289 }
290 SecretKeyBox::new(Some(untrusted_comment), parse_raw_secret_key(secret_key)?)
291}
292
293#[cfg(test)]
294#[test]
295fn test_parse_secret_key() {
296 use crate::KeyPairBox;
297 let password = b"password";
298 let k = KeyPairBox::generate(Some(password), None, None).unwrap();
299 let file = k.secret_key_box.to_string();
300 let sk = parse_secret_key(&file).unwrap();
301 assert_eq!(file, sk.to_string());
302}
303#[cfg(test)]
304#[test]
305fn test_parse_secret_key_rejects_extra_lines() {
306 use crate::KeyPairBox;
307
308 let keypair = KeyPairBox::generate(Some(b"password"), None, None).unwrap();
309 let file = format!("{}extra\n", keypair.secret_key_box);
310
311 assert!(parse_secret_key(&file).is_err());
312}
313#[cfg(test)]
314#[test]
315fn test_parse_secret_key_requires_comment_prefix() {
316 use crate::KeyPairBox;
317
318 let keypair = KeyPairBox::generate(Some(b"password"), None, None).unwrap();
319 let secret_key = keypair
320 .secret_key_box
321 .to_string()
322 .lines()
323 .nth(1)
324 .unwrap()
325 .to_owned();
326 let malformed = format!("bad comment\n{secret_key}\n");
327
328 assert!(parse_secret_key(&malformed).is_err());
329}
330#[cfg(test)]
331#[test]
332fn test_generate_rejects_comment_control_characters() {
333 use crate::KeyPairBox;
334
335 assert!(KeyPairBox::generate(Some(b"password"), Some("bad\ncomment"), None).is_err());
336 assert!(KeyPairBox::generate(Some(b"password"), None, Some("bad\0comment")).is_err());
337}
338#[derive(Clone, Debug, ZeroizeOnDrop, PartialEq, Eq)]
340pub(crate) struct SecretKey {
341 pub(crate) sig_alg: [u8; ALG_SIZE],
342 kdf_alg: [u8; ALG_SIZE],
343 cksum_alg: [u8; ALG_SIZE],
344 kdf_salt: [u8; KDF_SALT_SIZE],
345 kdf_opslimit: u64,
346 kdf_memlimit: u64,
347 keynum_sk: [u8; KEYNUM_SK_SIZE],
348}
349#[derive(Clone, Debug, ZeroizeOnDrop)]
350pub(crate) struct KeynumSK {
351 pub(crate) key_id: [u8; KID_SIZE],
352 sec_key: RawSk,
353 pub(crate) pub_key: ComponentBytes,
354 checksum: [u8; CHK_SIZE],
355}
356impl KeynumSK {
357 fn to_bytes(&self, mut kdf_buf: [u8; KEYNUM_SK_SIZE]) -> [u8; KEYNUM_SK_SIZE] {
358 for (i, item) in kdf_buf.iter_mut().enumerate().take(KID_SIZE) {
359 *item ^= self.key_id[i];
360 }
361 for i in 0..COMPONENT_SIZE {
362 kdf_buf[KID_SIZE + i] ^= self.sec_key.0[i];
363 }
364 for i in 0..COMPONENT_SIZE {
365 kdf_buf[KID_SIZE + COMPONENT_SIZE + i] ^= self.pub_key[i];
366 }
367 for i in 0..CHK_SIZE {
368 kdf_buf[KID_SIZE + 2 * COMPONENT_SIZE + i] ^= self.checksum[i];
369 }
370 kdf_buf
371 }
372 fn from_bytes(keynum_sk: &[u8; KEYNUM_SK_SIZE], mut kdf_buf: [u8; KEYNUM_SK_SIZE]) -> Self {
373 for i in 0..KEYNUM_SK_SIZE {
374 kdf_buf[i] ^= keynum_sk[i];
375 }
376 Self {
377 key_id: kdf_buf[0..KID_SIZE].try_into().unwrap(),
378 sec_key: RawSk(
379 kdf_buf[KID_SIZE..KID_SIZE + COMPONENT_SIZE]
380 .try_into()
381 .unwrap(),
382 ),
383 pub_key: kdf_buf[KID_SIZE + COMPONENT_SIZE..KID_SIZE + 2 * COMPONENT_SIZE]
384 .try_into()
385 .unwrap(),
386 checksum: kdf_buf[KID_SIZE + 2 * COMPONENT_SIZE..KEYNUM_SK_SIZE]
387 .try_into()
388 .unwrap(),
389 }
390 }
391}
392#[derive(Debug, Clone, ZeroizeOnDrop, Zeroize)]
393struct RawSk(ComponentBytes);
394impl Signer<ed25519::Signature> for RawSk {
395 fn try_sign(&self, msg: &[u8]) -> std::result::Result<ed25519::Signature, ed25519::Error> {
396 let sk = ed25519_dalek::SigningKey::from_bytes(&self.0);
397 Ok(sk.sign(msg))
398 }
399}
400fn kdf(
401 password: Option<&[u8]>,
402 salt: &[u8; KDF_SALT_SIZE],
403 opslimit: u64,
404 memlimit: u64,
405) -> Result<[u8; KEYNUM_SK_SIZE]> {
406 let params = raw_scrypt_params(memlimit as usize, opslimit, N_LOG2_MAX)?;
407 let mut stream = [0u8; KEYNUM_SK_SIZE];
408 scrypt::scrypt(password.unwrap_or(&[]), salt, ¶ms, &mut stream)?;
409 Ok(stream)
410}
411impl SecretKey {
412 pub fn sign(&self, message: &[u8], password: Option<&[u8]>) -> Result<ed25519::Signature> {
413 let keynum_sk = self.xor_keynum_sk(password);
414 Ok(keynum_sk?.sec_key.sign(message))
415 }
416 pub(crate) fn xor_keynum_sk(&self, password: Option<&[u8]>) -> Result<KeynumSK> {
417 if self.sig_alg != KEY_SIG_ALG {
418 return Err(SError::new(
419 crate::ErrorKind::SecretKey,
420 "invalid secret key signature algorithm",
421 ));
422 }
423 if self.cksum_alg != CHK_ALG {
424 return Err(SError::new(
425 crate::ErrorKind::SecretKey,
426 "invalid secret key checksum algorithm",
427 ));
428 }
429 let stream = if self.kdf_alg == KDF_ALG {
430 kdf(
431 password,
432 &self.kdf_salt,
433 self.kdf_opslimit,
434 self.kdf_memlimit,
435 )?
436 } else if self.kdf_alg == KDF_ALG_NONE {
437 if password.is_some_and(|password| !password.is_empty()) {
438 return Err(SError::new(
439 crate::ErrorKind::SecretKey,
440 "secret key is not encrypted",
441 ));
442 }
443 [0u8; KEYNUM_SK_SIZE]
444 } else {
445 return Err(SError::new(
446 crate::ErrorKind::SecretKey,
447 "invalid secret key kdf algorithm",
448 ));
449 };
450
451 let keynum_sk = KeynumSK::from_bytes(&self.keynum_sk, stream);
452
453 if self.kdf_alg == KDF_ALG_NONE {
454 if keynum_sk.checksum != [0u8; CHK_SIZE] {
455 return Err(SError::new(
456 crate::ErrorKind::SecretKey,
457 "invalid unencrypted secret key checksum",
458 ));
459 }
460 let public_key = ed25519_dalek::SigningKey::from_bytes(&keynum_sk.sec_key.0)
461 .verifying_key()
462 .to_bytes();
463 if public_key != keynum_sk.pub_key {
464 return Err(SError::new(
465 crate::ErrorKind::SecretKey,
466 "secret key public key mismatch",
467 ));
468 }
469 return Ok(keynum_sk);
470 }
471
472 let mut hash = Blake2b256::new();
473 hash.update(self.sig_alg);
474 hash.update(&keynum_sk.key_id);
475 hash.update(&keynum_sk.sec_key.0);
476 hash.update(&keynum_sk.pub_key);
477 if hash.finalize().to_vec() != keynum_sk.checksum {
478 return Err(SError::new(
479 crate::ErrorKind::SecretKey,
480 "checksum mismatch, invalid password",
481 ));
482 }
483 Ok(keynum_sk)
484 }
485}
486#[cfg(test)]
487#[test]
488fn test_sign() {
489 use crate::{pub_key_from_sec_key, KeyPairBox};
490 let password = b"password";
491 let k = KeyPairBox::generate(Some(password), None, None).unwrap();
492 let s = k.secret_key_box.to_string();
493 let sk = parse_secret_key(&s).unwrap();
494 let msg = b"hello world";
495 let sig = sk.sign(msg, Some(password)).unwrap();
496 let pk = pub_key_from_sec_key(&sk, Some(password)).unwrap();
497 let v = pk.public_key.key.verify(msg, &sig);
498 assert!(v.unwrap());
499}