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::OpenOptions;
22use std::io::stdin;
23use std::io::{self, Read, Write};
24use std::path::Path;
25use zeroize::Zeroize;
26
27pub const COMMENTHDR: &str = "untrusted comment: ";
29pub const MAX_COMMENT_LEN: usize = 1024;
31
32#[repr(C)]
36#[derive(Debug)]
37pub struct EncKey {
38 pub pkalg: [u8; 2],
40 pub kdfalg: [u8; 2],
42 pub kdfrounds: u32,
44 pub salt: [u8; SALT_LEN],
46 pub checksum: [u8; CHECKSUM_LEN],
48 pub keynum: [u8; KEYNUMLEN],
50 pub seckey: [u8; 64],
52}
53
54impl Zeroize for EncKey {
55 fn zeroize(&mut self) {
56 self.pkalg.zeroize();
57 self.kdfalg.zeroize();
58 self.kdfrounds.zeroize();
59 self.salt.zeroize();
60 self.checksum.zeroize();
61 self.keynum.zeroize();
62 self.seckey.zeroize();
63 }
64}
65
66impl Drop for EncKey {
67 fn drop(&mut self) {
68 self.zeroize();
69 }
70}
71
72#[repr(C)]
74#[derive(Debug, Clone, Copy)]
75pub struct PubKey {
76 pub pkalg: [u8; 2],
78 pub keynum: [u8; KEYNUMLEN],
80 pub pubkey: [u8; 32],
82}
83
84#[derive(Debug, Clone, Copy)]
86pub struct Sig {
87 pub pkalg: [u8; 2],
89 pub keynum: [u8; KEYNUMLEN],
91 pub sig: [u8; 64],
93}
94
95impl EncKey {
96 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
104 let pkalg = bytes
105 .get(0..2)
106 .ok_or(Error::InvalidKeyLength)?
107 .try_into()
108 .map_err(|_e| Error::InvalidKeyLength)?;
109 let kdfalg = bytes
110 .get(2..4)
111 .ok_or(Error::InvalidKeyLength)?
112 .try_into()
113 .map_err(|_e| Error::InvalidKeyLength)?;
114 let kdfrounds_bytes = bytes.get(4..8).ok_or(Error::InvalidKeyLength)?;
115 let kdfrounds = u32::from_be_bytes(
116 kdfrounds_bytes
117 .try_into()
118 .map_err(|_e| Error::InvalidKeyLength)?,
119 );
120 let salt = bytes
121 .get(8..24)
122 .ok_or(Error::InvalidKeyLength)?
123 .try_into()
124 .map_err(|_e| Error::InvalidKeyLength)?;
125 let checksum = bytes
126 .get(24..32)
127 .ok_or(Error::InvalidKeyLength)?
128 .try_into()
129 .map_err(|_e| Error::InvalidKeyLength)?;
130 let keynum = bytes
131 .get(32..40)
132 .ok_or(Error::InvalidKeyLength)?
133 .try_into()
134 .map_err(|_e| Error::InvalidKeyLength)?;
135 let seckey = bytes
136 .get(40..104)
137 .ok_or(Error::InvalidKeyLength)?
138 .try_into()
139 .map_err(|_e| Error::InvalidKeyLength)?;
140
141 if bytes.len() != 104 {
143 return Err(Error::InvalidKeyLength);
144 }
145
146 if pkalg != PKALG {
147 return Err(Error::UnsupportedPkAlgo);
148 }
149 if kdfalg != KDFALG {
150 return Err(Error::UnsupportedKdfAlgo);
151 }
152
153 Ok(Self {
154 pkalg,
155 kdfalg,
156 kdfrounds,
157 salt,
158 checksum,
159 keynum,
160 seckey,
161 })
162 }
163
164 #[must_use]
166 pub fn to_bytes(&self) -> Vec<u8> {
167 let mut out = Vec::with_capacity(104);
168 out.extend_from_slice(&self.pkalg);
169 out.extend_from_slice(&self.kdfalg);
170 out.extend_from_slice(&self.kdfrounds.to_be_bytes());
171 out.extend_from_slice(&self.salt);
172 out.extend_from_slice(&self.checksum);
173 out.extend_from_slice(&self.keynum);
174 out.extend_from_slice(&self.seckey);
175 out
176 }
177}
178
179impl PubKey {
180 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
187 let pkalg = bytes
188 .get(0..2)
189 .ok_or(Error::InvalidKeyLength)?
190 .try_into()
191 .map_err(|_e| Error::InvalidKeyLength)?;
192 let keynum = bytes
193 .get(2..10)
194 .ok_or(Error::InvalidKeyLength)?
195 .try_into()
196 .map_err(|_e| Error::InvalidKeyLength)?;
197 let pubkey = bytes
198 .get(10..42)
199 .ok_or(Error::InvalidKeyLength)?
200 .try_into()
201 .map_err(|_e| Error::InvalidKeyLength)?;
202
203 if bytes.len() != 42 {
204 return Err(Error::InvalidKeyLength);
205 }
206
207 if pkalg != PKALG {
208 return Err(Error::UnsupportedPkAlgo);
209 }
210
211 Ok(Self {
212 pkalg,
213 keynum,
214 pubkey,
215 })
216 }
217
218 #[must_use]
220 pub fn to_bytes(&self) -> Vec<u8> {
221 let mut out = Vec::with_capacity(42);
222 out.extend_from_slice(&self.pkalg);
223 out.extend_from_slice(&self.keynum);
224 out.extend_from_slice(&self.pubkey);
225 out
226 }
227}
228
229impl Sig {
230 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
237 let pkalg = bytes
238 .get(0..2)
239 .ok_or(Error::InvalidKeyLength)?
240 .try_into()
241 .map_err(|_e| Error::InvalidKeyLength)?;
242 let keynum = bytes
243 .get(2..10)
244 .ok_or(Error::InvalidKeyLength)?
245 .try_into()
246 .map_err(|_e| Error::InvalidKeyLength)?;
247 let sig = bytes
248 .get(10..74)
249 .ok_or(Error::InvalidKeyLength)?
250 .try_into()
251 .map_err(|_e| Error::InvalidKeyLength)?;
252
253 if bytes.len() != 74 {
254 return Err(Error::InvalidKeyLength);
255 }
256
257 if pkalg != PKALG {
258 return Err(Error::UnsupportedPkAlgo);
259 }
260
261 Ok(Self { pkalg, keynum, sig })
262 }
263
264 #[must_use]
266 pub fn to_bytes(&self) -> Vec<u8> {
267 let mut out = Vec::with_capacity(74);
268 out.extend_from_slice(&self.pkalg);
269 out.extend_from_slice(&self.keynum);
270 out.extend_from_slice(&self.sig);
271 out
272 }
273}
274
275pub fn parse<T, F>(path: &Path, parse_fn: F) -> Result<(T, Vec<u8>)>
288where
289 F: Fn(&[u8]) -> Result<T>,
290{
291 const HEADER_LIMIT: usize = 4096;
296 let mut header_buf = vec![0_u8; HEADER_LIMIT];
297
298 let mut reader: Box<dyn Read> = if path.to_str() == Some("-") {
299 Box::new(stdin())
300 } else {
301 let file = OpenOptions::new()
302 .read(true)
303 .open(path)
304 .map_err(Error::Io)?;
305 Box::new(file)
306 };
307
308 let mut total_read = 0;
310 while total_read < HEADER_LIMIT {
311 let n = reader
312 .read(&mut header_buf[total_read..])
313 .map_err(Error::Io)?;
314 if n == 0 {
315 break;
316 }
317 total_read = total_read.checked_add(n).ok_or(Error::Overflow)?;
318 }
319 header_buf.truncate(total_read);
320
321 let n1 = memchr(b'\n', &header_buf).ok_or(Error::InvalidCommentHeader)?;
323
324 let header_bytes = &header_buf[..n1];
325
326 let prefix = COMMENTHDR.as_bytes();
328 if !header_bytes.starts_with(prefix) {
329 return Err(Error::InvalidCommentHeader);
330 }
331 let comment = header_bytes[prefix.len()..].to_vec();
332
333 let n2_start = n1.checked_add(1).ok_or(Error::Overflow)?;
334 let n2 = memchr(b'\n', &header_buf[n2_start..])
335 .unwrap_or_else(|| header_buf.len().saturating_sub(n2_start));
336
337 let b64_start = n2_start;
338 let b64_end = b64_start.checked_add(n2).ok_or(Error::Overflow)?;
339
340 if b64_end > header_buf.len() {
341 return Err(Error::InvalidCommentHeader);
342 }
343
344 let b64_bytes = &header_buf[b64_start..b64_end];
345
346 let b64_str = str::from_utf8(b64_bytes).map_err(|_e| Error::InvalidSignatureUtf8)?;
348 let decoded = Base64::decode_vec(b64_str.trim()).map_err(Error::Base64Decode)?;
349
350 let obj = parse_fn(&decoded)?;
351 Ok((obj, comment))
352}
353
354pub fn write(path: &Path, comment: &[u8], data: &[u8]) -> Result<()> {
362 let encoded = Base64::encode_string(data);
363
364 let mut content = Vec::new();
365 content.extend_from_slice(COMMENTHDR.as_bytes());
366 content.extend_from_slice(comment);
367 content.push(b'\n');
368 content.extend_from_slice(encoded.as_bytes());
369 content.push(b'\n');
370
371 let mut writer: Box<dyn Write> = if path.to_str() == Some("-") {
372 Box::new(io::stdout())
373 } else {
374 let mut options = OpenOptions::new();
375 options.write(true).create_new(true);
376 let file = options.open(path).map_err(Error::Io)?;
377 Box::new(file)
378 };
379
380 writer.write_all(&content).map_err(Error::Io)?;
381 Ok(())
382}
383
384assert_eq_size!(EncKey, [u8; 104]);
385assert_eq_size!(PubKey, [u8; 42]);
386assert_eq_size!(Sig, [u8; 74]);
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn test_enckey_serialization() -> crate::error::Result<()> {
394 let enc = EncKey {
395 pkalg: PKALG,
396 kdfalg: KDFALG,
397 kdfrounds: 42,
398 salt: [1u8; SALT_LEN],
399 checksum: [2u8; CHECKSUM_LEN],
400 keynum: [3u8; KEYNUMLEN],
401 seckey: [4u8; 64],
402 };
403 let bytes = enc.to_bytes();
404 let enc2 = EncKey::from_bytes(&bytes)?;
405 assert_eq!(enc.kdfrounds, enc2.kdfrounds);
406 assert_eq!(enc.salt, enc2.salt);
407
408 assert!(matches!(
410 EncKey::from_bytes(&bytes[..103]),
411 Err(Error::InvalidKeyLength)
412 ));
413
414 let mut long = bytes.clone();
415 long.push(0);
416 assert!(matches!(
417 EncKey::from_bytes(&long),
418 Err(Error::InvalidKeyLength)
419 ));
420
421 let mut bad_alg = bytes.clone();
423 bad_alg[0] = b'X';
424 assert!(matches!(
425 EncKey::from_bytes(&bad_alg),
426 Err(Error::UnsupportedPkAlgo)
427 ));
428
429 let mut bad_kdf = bytes.clone();
431 bad_kdf[2] = b'X';
432 assert!(matches!(
433 EncKey::from_bytes(&bad_kdf),
434 Err(Error::UnsupportedKdfAlgo)
435 ));
436
437 Ok(())
438 }
439
440 #[test]
441 fn test_pubkey_serialization() -> crate::error::Result<()> {
442 let pubk = PubKey {
443 pkalg: PKALG,
444 keynum: [1u8; KEYNUMLEN],
445 pubkey: [2u8; 32],
446 };
447 let bytes = pubk.to_bytes();
448 let pubk2 = PubKey::from_bytes(&bytes)?;
449 assert_eq!(pubk.keynum, pubk2.keynum);
450
451 assert!(matches!(
453 PubKey::from_bytes(&bytes[..41]),
454 Err(Error::InvalidKeyLength)
455 ));
456 let mut long = bytes.clone();
457 long.push(0);
458 assert!(matches!(
459 PubKey::from_bytes(&long),
460 Err(Error::InvalidKeyLength)
461 ));
462
463 let mut bad_alg = bytes.clone();
465 bad_alg[0] = b'X';
466 assert!(matches!(
467 PubKey::from_bytes(&bad_alg),
468 Err(Error::UnsupportedPkAlgo)
469 ));
470
471 Ok(())
472 }
473
474 #[test]
475 fn test_sig_serialization() -> crate::error::Result<()> {
476 let sig = Sig {
477 pkalg: PKALG,
478 keynum: [1u8; KEYNUMLEN],
479 sig: [0u8; 64],
480 };
481 let bytes = sig.to_bytes();
482 let sig2 = Sig::from_bytes(&bytes)?;
483 assert_eq!(sig.keynum, sig2.keynum);
484
485 assert!(matches!(
487 Sig::from_bytes(&bytes[..73]),
488 Err(Error::InvalidKeyLength)
489 ));
490
491 let mut long = bytes.clone();
492 long.push(0);
493 assert!(matches!(
494 Sig::from_bytes(&long),
495 Err(Error::InvalidKeyLength)
496 ));
497
498 let mut bad_alg = bytes.clone();
500 bad_alg[0] = b'X';
501 assert!(matches!(
502 Sig::from_bytes(&bad_alg),
503 Err(Error::UnsupportedPkAlgo)
504 ));
505
506 Ok(())
507 }
508
509 #[test]
510 fn test_file_io() -> std::result::Result<(), Box<dyn std::error::Error>> {
511 let dir = tempfile::tempdir()?;
512 let path = dir.path().join("secret.key");
513 let data = b"secret data";
514
515 write(&path, b"mycomment", data)?;
516
517 let (read_data, comment) = parse::<Vec<u8>, _>(&path, |b| Ok(b.to_vec()))?;
518 assert_eq!(read_data, data);
519 assert_eq!(comment, b"mycomment");
520
521 let missing = dir.path().join("missing");
523 assert!(matches!(
524 parse::<Vec<u8>, _>(&missing, |_| Ok(vec![])),
525 Err(Error::Io(_))
526 ));
527
528 let bad_prefix = dir.path().join("bad_prefix");
530 let mut f = OpenOptions::new()
531 .write(true)
532 .create_new(true)
533 .open(&bad_prefix)?;
534 f.write_all(b"invalid header\n")?;
535 assert!(matches!(
536 parse::<Vec<u8>, _>(&bad_prefix, |_| Ok(vec![])),
537 Err(Error::InvalidCommentHeader)
538 ));
539
540 let no_newline = dir.path().join("no_newline");
542 let mut f = OpenOptions::new()
543 .write(true)
544 .create_new(true)
545 .open(&no_newline)?;
546 f.write_all(b"untrusted comment: foo")?;
547 assert!(matches!(
548 parse::<Vec<u8>, _>(&no_newline, |_| Ok(vec![])),
549 Err(Error::InvalidCommentHeader)
550 ));
551
552 let bad_utf8 = dir.path().join("bad_utf8");
556 write(&bad_utf8, b"comment", b"")?;
557 let mut f = OpenOptions::new().write(true).open(&bad_utf8)?;
558 f.write_all(b"untrusted comment: comment\n\xFF\xFF\n")?;
559 assert!(matches!(
560 parse::<Vec<u8>, _>(&bad_utf8, |_| Ok(vec![])),
561 Err(Error::InvalidSignatureUtf8)
562 ));
563
564 Ok(())
565 }
566}