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