1use crate::crypto::{CHECKSUM_LEN, KDFALG, KEYNUMLEN, PKALG, SALT_LEN};
16use crate::error::{Error, Result};
17use base64ct::{Base64, Encoding as _};
18use core::str;
19use memchr::memchr;
20use static_assertions::assert_eq_size;
21use std::fs::File;
22use std::fs::OpenOptions;
23use std::io::stdin;
24use std::io::stdout;
25use std::io::{Read, Write};
26use std::path::Path;
27use zeroize::Zeroize;
28use zeroize::Zeroizing;
29
30pub const COMMENTHDR: &str = "untrusted comment: ";
32pub const MAX_COMMENT_LEN: usize = 1024;
34
35#[repr(C)]
39#[derive(Debug)]
40pub struct EncKey {
41 pub pkalg: [u8; 2],
43 pub kdfalg: [u8; 2],
45 pub kdfrounds: u32,
47 pub salt: [u8; SALT_LEN],
49 pub checksum: [u8; CHECKSUM_LEN],
51 pub keynum: [u8; KEYNUMLEN],
53 pub seckey: Zeroizing<[u8; 64]>,
55}
56
57impl Zeroize for EncKey {
58 fn zeroize(&mut self) {
59 self.pkalg.zeroize();
60 self.kdfalg.zeroize();
61 self.kdfrounds.zeroize();
62 self.salt.zeroize();
63 self.checksum.zeroize();
64 self.keynum.zeroize();
65 self.seckey.zeroize();
66 }
67}
68
69impl Drop for EncKey {
70 fn drop(&mut self) {
71 self.zeroize();
72 }
73}
74
75#[repr(C)]
77#[derive(Debug, Clone, Copy)]
78pub struct PubKey {
79 pub pkalg: [u8; 2],
81 pub keynum: [u8; KEYNUMLEN],
83 pub pubkey: [u8; 32],
85}
86
87#[derive(Debug, Clone, Copy)]
89pub struct Sig {
90 pub pkalg: [u8; 2],
92 pub keynum: [u8; KEYNUMLEN],
94 pub sig: [u8; 64],
96}
97
98impl EncKey {
99 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
107 let pkalg = bytes
108 .get(0..2)
109 .ok_or(Error::InvalidKeyLength)?
110 .try_into()
111 .map_err(|_e| Error::InvalidKeyLength)?;
112 let kdfalg = bytes
113 .get(2..4)
114 .ok_or(Error::InvalidKeyLength)?
115 .try_into()
116 .map_err(|_e| Error::InvalidKeyLength)?;
117 let kdfrounds_bytes = bytes.get(4..8).ok_or(Error::InvalidKeyLength)?;
118 let kdfrounds = u32::from_be_bytes(
119 kdfrounds_bytes
120 .try_into()
121 .map_err(|_e| Error::InvalidKeyLength)?,
122 );
123 let salt = bytes
124 .get(8..24)
125 .ok_or(Error::InvalidKeyLength)?
126 .try_into()
127 .map_err(|_e| Error::InvalidKeyLength)?;
128 let checksum = bytes
129 .get(24..32)
130 .ok_or(Error::InvalidKeyLength)?
131 .try_into()
132 .map_err(|_e| Error::InvalidKeyLength)?;
133 let keynum = bytes
134 .get(32..40)
135 .ok_or(Error::InvalidKeyLength)?
136 .try_into()
137 .map_err(|_e| Error::InvalidKeyLength)?;
138 let seckey = Zeroizing::new(
139 bytes
140 .get(40..104)
141 .ok_or(Error::InvalidKeyLength)?
142 .try_into()
143 .map_err(|_e| Error::InvalidKeyLength)?,
144 );
145
146 if bytes.len() != 104 {
148 return Err(Error::InvalidKeyLength);
149 }
150
151 if pkalg != PKALG {
152 return Err(Error::UnsupportedPkAlgo);
153 }
154 if kdfalg != KDFALG {
155 return Err(Error::UnsupportedKdfAlgo);
156 }
157
158 Ok(Self {
159 pkalg,
160 kdfalg,
161 kdfrounds,
162 salt,
163 checksum,
164 keynum,
165 seckey,
166 })
167 }
168
169 #[must_use]
171 pub fn to_bytes(&self) -> Zeroizing<Vec<u8>> {
172 let mut out = Zeroizing::new(Vec::with_capacity(104));
173 out.extend_from_slice(&self.pkalg);
174 out.extend_from_slice(&self.kdfalg);
175 out.extend_from_slice(&self.kdfrounds.to_be_bytes());
176 out.extend_from_slice(&self.salt);
177 out.extend_from_slice(&self.checksum);
178 out.extend_from_slice(&self.keynum);
179 out.extend_from_slice(self.seckey.as_ref());
180 out
181 }
182}
183
184impl PubKey {
185 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
192 let pkalg = bytes
193 .get(0..2)
194 .ok_or(Error::InvalidKeyLength)?
195 .try_into()
196 .map_err(|_e| Error::InvalidKeyLength)?;
197 let keynum = bytes
198 .get(2..10)
199 .ok_or(Error::InvalidKeyLength)?
200 .try_into()
201 .map_err(|_e| Error::InvalidKeyLength)?;
202 let pubkey = bytes
203 .get(10..42)
204 .ok_or(Error::InvalidKeyLength)?
205 .try_into()
206 .map_err(|_e| Error::InvalidKeyLength)?;
207
208 if bytes.len() != 42 {
209 return Err(Error::InvalidKeyLength);
210 }
211
212 if pkalg != PKALG {
213 return Err(Error::UnsupportedPkAlgo);
214 }
215
216 Ok(Self {
217 pkalg,
218 keynum,
219 pubkey,
220 })
221 }
222
223 #[must_use]
225 pub fn to_bytes(&self) -> Vec<u8> {
226 let mut out = Vec::with_capacity(42);
227 out.extend_from_slice(&self.pkalg);
228 out.extend_from_slice(&self.keynum);
229 out.extend_from_slice(&self.pubkey);
230 out
231 }
232}
233
234impl Sig {
235 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
242 let pkalg = bytes
243 .get(0..2)
244 .ok_or(Error::InvalidKeyLength)?
245 .try_into()
246 .map_err(|_e| Error::InvalidKeyLength)?;
247 let keynum = bytes
248 .get(2..10)
249 .ok_or(Error::InvalidKeyLength)?
250 .try_into()
251 .map_err(|_e| Error::InvalidKeyLength)?;
252 let sig = bytes
253 .get(10..74)
254 .ok_or(Error::InvalidKeyLength)?
255 .try_into()
256 .map_err(|_e| Error::InvalidKeyLength)?;
257
258 if bytes.len() != 74 {
259 return Err(Error::InvalidKeyLength);
260 }
261
262 if pkalg != PKALG {
263 return Err(Error::UnsupportedPkAlgo);
264 }
265
266 Ok(Self { pkalg, keynum, sig })
267 }
268
269 #[must_use]
271 pub fn to_bytes(&self) -> Vec<u8> {
272 let mut out = Vec::with_capacity(74);
273 out.extend_from_slice(&self.pkalg);
274 out.extend_from_slice(&self.keynum);
275 out.extend_from_slice(&self.sig);
276 out
277 }
278}
279
280pub fn parse_stream<F, R, T>(mut reader: R, parse_fn: F) -> Result<(T, Vec<u8>)>
293where
294 R: Read,
295 F: Fn(&[u8]) -> Result<T>,
296{
297 const HEADER_LIMIT: usize = 4096;
302 let mut header_buf = vec![0_u8; HEADER_LIMIT];
303
304 let mut total_read = 0;
306 while total_read < HEADER_LIMIT {
307 let n = reader
308 .read(&mut header_buf[total_read..])
309 .map_err(Error::Io)?;
310 if n == 0 {
311 break;
312 }
313 total_read = total_read.checked_add(n).ok_or(Error::Overflow)?;
314 }
315 header_buf.truncate(total_read);
316
317 let n1 = memchr(b'\n', &header_buf).ok_or(Error::InvalidCommentHeader)?;
319 let header_bytes = &header_buf[..n1];
320
321 let prefix = COMMENTHDR.as_bytes();
323 if !header_bytes.starts_with(prefix) {
324 return Err(Error::InvalidCommentHeader);
325 }
326 let comment = header_bytes[prefix.len()..].to_vec();
327
328 let n2_start = n1.checked_add(1).ok_or(Error::Overflow)?;
329 let n2 = memchr(b'\n', &header_buf[n2_start..])
330 .unwrap_or_else(|| header_buf.len().saturating_sub(n2_start));
331
332 let b64_start = n2_start;
333 let b64_end = b64_start.checked_add(n2).ok_or(Error::Overflow)?;
334
335 if b64_end > header_buf.len() {
336 return Err(Error::InvalidCommentHeader);
337 }
338
339 let b64_bytes = &header_buf[b64_start..b64_end];
340
341 let b64_str = str::from_utf8(b64_bytes).map_err(|_e| Error::InvalidSignatureUtf8)?;
343 let decoded = Base64::decode_vec(b64_str.trim()).map_err(Error::Base64Decode)?;
344
345 let obj = parse_fn(&decoded)?;
346 Ok((obj, comment))
347}
348
349pub fn parse<T, F>(path: &Path, parse_fn: F) -> Result<(T, Vec<u8>)>
362where
363 F: Fn(&[u8]) -> Result<T>,
364{
365 let reader: Box<dyn Read> = if path.to_str() == Some("-") {
366 Box::new(stdin())
367 } else {
368 Box::new(open(path, false)?)
369 };
370
371 parse_stream(reader, parse_fn)
372}
373
374pub fn write_stream(mut writer: impl Write, comment: &[u8], data: &[u8]) -> Result<()> {
382 let encoded = Base64::encode_string(data);
383
384 let mut content = Vec::new();
385 content.extend_from_slice(COMMENTHDR.as_bytes());
386 content.extend_from_slice(comment);
387 content.push(b'\n');
388 content.extend_from_slice(encoded.as_bytes());
389 content.push(b'\n');
390
391 writer.write_all(&content).map_err(Error::Io)?;
392 Ok(())
393}
394
395pub fn write(path: &Path, comment: &[u8], data: &[u8]) -> Result<()> {
403 let writer: Box<dyn Write> = if path.to_str() == Some("-") {
404 Box::new(stdout())
405 } else {
406 Box::new(open(path, true)?)
407 };
408
409 write_stream(writer, comment, data)
410}
411
412pub fn open(path: &Path, write: bool) -> Result<File> {
416 let mut opts = OpenOptions::new();
417 if write {
418 opts.write(true).create_new(true);
419 #[cfg(unix)]
420 {
421 use std::os::unix::fs::OpenOptionsExt;
422 opts.mode(0o600);
423 }
424 } else {
425 opts.read(true);
426 }
427
428 opts.open(path).map_err(Error::Io)
429}
430
431assert_eq_size!(EncKey, [u8; 104]);
432assert_eq_size!(PubKey, [u8; 42]);
433assert_eq_size!(Sig, [u8; 74]);
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn test_enckey_serialization() -> crate::error::Result<()> {
441 let enc = EncKey {
442 pkalg: PKALG,
443 kdfalg: KDFALG,
444 kdfrounds: 42,
445 salt: [1u8; SALT_LEN],
446 checksum: [2u8; CHECKSUM_LEN],
447 keynum: [3u8; KEYNUMLEN],
448 seckey: Zeroizing::new([4u8; 64]),
449 };
450 let bytes = enc.to_bytes();
451 let enc2 = EncKey::from_bytes(&bytes)?;
452 assert_eq!(enc.kdfrounds, enc2.kdfrounds);
453 assert_eq!(enc.salt, enc2.salt);
454
455 assert!(matches!(
457 EncKey::from_bytes(&bytes[..103]),
458 Err(Error::InvalidKeyLength)
459 ));
460
461 let mut long = bytes.clone();
462 long.push(0);
463 assert!(matches!(
464 EncKey::from_bytes(&long),
465 Err(Error::InvalidKeyLength)
466 ));
467
468 let mut bad_alg = bytes.clone();
470 bad_alg[0] = b'X';
471 assert!(matches!(
472 EncKey::from_bytes(&bad_alg),
473 Err(Error::UnsupportedPkAlgo)
474 ));
475
476 let mut bad_kdf = bytes.clone();
478 bad_kdf[2] = b'X';
479 assert!(matches!(
480 EncKey::from_bytes(&bad_kdf),
481 Err(Error::UnsupportedKdfAlgo)
482 ));
483
484 Ok(())
485 }
486
487 #[test]
488 fn test_pubkey_serialization() -> crate::error::Result<()> {
489 let pubk = PubKey {
490 pkalg: PKALG,
491 keynum: [1u8; KEYNUMLEN],
492 pubkey: [2u8; 32],
493 };
494 let bytes = pubk.to_bytes();
495 let pubk2 = PubKey::from_bytes(&bytes)?;
496 assert_eq!(pubk.keynum, pubk2.keynum);
497
498 assert!(matches!(
500 PubKey::from_bytes(&bytes[..41]),
501 Err(Error::InvalidKeyLength)
502 ));
503 let mut long = bytes.clone();
504 long.push(0);
505 assert!(matches!(
506 PubKey::from_bytes(&long),
507 Err(Error::InvalidKeyLength)
508 ));
509
510 let mut bad_alg = bytes.clone();
512 bad_alg[0] = b'X';
513 assert!(matches!(
514 PubKey::from_bytes(&bad_alg),
515 Err(Error::UnsupportedPkAlgo)
516 ));
517
518 Ok(())
519 }
520
521 #[test]
522 fn test_sig_serialization() -> crate::error::Result<()> {
523 let sig = Sig {
524 pkalg: PKALG,
525 keynum: [1u8; KEYNUMLEN],
526 sig: [0u8; 64],
527 };
528 let bytes = sig.to_bytes();
529 let sig2 = Sig::from_bytes(&bytes)?;
530 assert_eq!(sig.keynum, sig2.keynum);
531
532 assert!(matches!(
534 Sig::from_bytes(&bytes[..73]),
535 Err(Error::InvalidKeyLength)
536 ));
537
538 let mut long = bytes.clone();
539 long.push(0);
540 assert!(matches!(
541 Sig::from_bytes(&long),
542 Err(Error::InvalidKeyLength)
543 ));
544
545 let mut bad_alg = bytes.clone();
547 bad_alg[0] = b'X';
548 assert!(matches!(
549 Sig::from_bytes(&bad_alg),
550 Err(Error::UnsupportedPkAlgo)
551 ));
552
553 Ok(())
554 }
555
556 #[test]
557 #[cfg_attr(any(target_arch = "wasm32", target_arch = "wasm64"), ignore)]
558 fn test_file_io() -> std::result::Result<(), Box<dyn std::error::Error>> {
559 let dir = tempfile::tempdir()?;
560 let path = dir.path().join("secret.key");
561 let data = b"secret data";
562
563 write(&path, b"mycomment", data)?;
564
565 let (read_data, comment) = parse::<Vec<u8>, _>(&path, |b| Ok(b.to_vec()))?;
566 assert_eq!(read_data, data);
567 assert_eq!(comment, b"mycomment");
568
569 let missing = dir.path().join("missing");
571 assert!(matches!(
572 parse::<Vec<u8>, _>(&missing, |_| Ok(vec![])),
573 Err(Error::Io(_))
574 ));
575
576 let bad_prefix = dir.path().join("bad_prefix");
578 let mut f = OpenOptions::new()
579 .write(true)
580 .create_new(true)
581 .open(&bad_prefix)?;
582 f.write_all(b"invalid header\n")?;
583 assert!(matches!(
584 parse::<Vec<u8>, _>(&bad_prefix, |_| Ok(vec![])),
585 Err(Error::InvalidCommentHeader)
586 ));
587
588 let no_newline = dir.path().join("no_newline");
590 let mut f = OpenOptions::new()
591 .write(true)
592 .create_new(true)
593 .open(&no_newline)?;
594 f.write_all(b"untrusted comment: foo")?;
595 assert!(matches!(
596 parse::<Vec<u8>, _>(&no_newline, |_| Ok(vec![])),
597 Err(Error::InvalidCommentHeader)
598 ));
599
600 let bad_utf8 = dir.path().join("bad_utf8");
604 write(&bad_utf8, b"comment", b"")?;
605 let mut f = OpenOptions::new().write(true).open(&bad_utf8)?;
606 f.write_all(b"untrusted comment: comment\n\xFF\xFF\n")?;
607 assert!(matches!(
608 parse::<Vec<u8>, _>(&bad_utf8, |_| Ok(vec![])),
609 Err(Error::InvalidSignatureUtf8)
610 ));
611
612 Ok(())
613 }
614}