1use mundane::public::ed25519 as mundane_ed25519;
6use serde::{Deserialize, Serialize};
7use serde_json::json;
8use std::fs::File;
9use std::io::Write;
10use std::path::Path;
11use std::{fmt, io};
12use tuf::crypto::{Ed25519PrivateKey, KeyType, PrivateKey, PublicKey, SignatureScheme};
13
14const DEFAULT_KEYTYPE_GENERATION: &KeyType = &KeyType::Ed25519;
15
16#[derive(Debug, thiserror::Error)]
18pub enum ParseError {
19 #[error(transparent)]
21 Io(#[from] io::Error),
22
23 #[error(transparent)]
25 Tuf(#[from] tuf::Error),
26
27 #[error(transparent)]
29 Json(#[from] serde_json::Error),
30
31 #[error("private key's public key {expected:?} does not match public key {actual:?}")]
33 PublicKeyDoesNotMatchPrivateKey { expected: PublicKey, actual: PublicKey },
34
35 #[error("unsupported key type {keytype:?} and signature scheme {scheme:?}")]
37 UnsupportedKeyTypeAndScheme { keytype: KeyType, scheme: SignatureScheme },
38
39 #[error("unsupported generation for key type {keytype:?}")]
41 UnsupportedKeyTypeGeneration { keytype: KeyType },
42
43 #[error("The keys file is encrypted, which is not supported")]
45 EncryptedKeys,
46}
47
48pub struct RepoKeys {
50 root_keys: Vec<Box<dyn PrivateKey>>,
51 targets_keys: Vec<Box<dyn PrivateKey>>,
52 snapshot_keys: Vec<Box<dyn PrivateKey>>,
53 timestamp_keys: Vec<Box<dyn PrivateKey>>,
54}
55
56impl fmt::Debug for RepoKeys {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.debug_struct("RepoKeys")
59 .field("root_keys", &self.root_keys.iter().map(|key| key.public()).collect::<Vec<_>>())
60 .field(
61 "targets_keys",
62 &self.targets_keys.iter().map(|key| key.public()).collect::<Vec<_>>(),
63 )
64 .field(
65 "snapshot_keys",
66 &self.snapshot_keys.iter().map(|key| key.public()).collect::<Vec<_>>(),
67 )
68 .field(
69 "timestamp_keys",
70 &self.timestamp_keys.iter().map(|key| key.public()).collect::<Vec<_>>(),
71 )
72 .finish()
73 }
74}
75
76impl RepoKeys {
77 pub fn generate(dir: &Path) -> Result<Self, ParseError> {
85 fn generate_tuf_keyval(keytype: &KeyType) -> Result<KeyVal, ParseError> {
89 match keytype {
90 &KeyType::Ed25519 => {
91 let private_key = mundane_ed25519::Ed25519PrivKey::generate();
92 let private_key_bytes = *private_key.bytes();
93 let mut public_key_bytes = [0u8; 32];
94 public_key_bytes[..].copy_from_slice(&private_key_bytes[32..]);
95
96 Ok(KeyVal {
97 public: public_key_bytes.to_vec(),
98 private: private_key_bytes.to_vec(),
99 })
100 }
101 _ => Err(ParseError::UnsupportedKeyTypeGeneration { keytype: keytype.clone() }),
102 }
103 }
104
105 fn generate_rolekey(keytype: &KeyType) -> Result<RoleKey, ParseError> {
107 match keytype {
108 &KeyType::Ed25519 => Ok(RoleKey {
109 keytype: KeyType::Ed25519,
110 scheme: SignatureScheme::Ed25519,
111 keyid_hash_algorithms: None,
112 keyval: generate_tuf_keyval(keytype).unwrap(),
113 }),
114 _ => Err(ParseError::UnsupportedKeyTypeGeneration { keytype: keytype.clone() }),
115 }
116 }
117
118 fn generate_rolekey_collection(
121 keytype: &KeyType,
122 role_key: &RoleKey,
123 ) -> Result<Vec<Box<dyn PrivateKey>>, ParseError> {
124 let mut keys = Vec::new();
125 match keytype {
126 &KeyType::Ed25519 => {
127 keys.push(Box::new(Ed25519PrivateKey::from_ed25519(&role_key.keyval.private)?)
128 as Box<_>);
129 }
130 _ => {
131 return Err(ParseError::UnsupportedKeyTypeGeneration {
132 keytype: keytype.clone(),
133 });
134 }
135 }
136 Ok(keys)
137 }
138
139 fn write_rolekeys(
141 dir: &Path,
142 rolekeys_filename: &str,
143 rolekeys: RoleKeys,
144 ) -> Result<(), ParseError> {
145 let mut rolekeys_file = File::create(dir.join(rolekeys_filename))?;
146 let rolekeys_string = serde_json::to_string(&rolekeys).unwrap();
147 rolekeys_file.write_all(rolekeys_string.as_bytes())?;
148 rolekeys_file.sync_all()?;
149 Ok(())
150 }
151
152 let root_key = generate_rolekey(DEFAULT_KEYTYPE_GENERATION).unwrap();
153 let targets_key = generate_rolekey(DEFAULT_KEYTYPE_GENERATION).unwrap();
154 let snapshot_key = generate_rolekey(DEFAULT_KEYTYPE_GENERATION).unwrap();
155 let timestamp_key = generate_rolekey(DEFAULT_KEYTYPE_GENERATION).unwrap();
156
157 for (rolekeys_filename, rolekeys) in [
158 ("root.json", RoleKeys { encrypted: false, data: json! { [root_key] } }),
159 ("targets.json", RoleKeys { encrypted: false, data: json! {[targets_key] } }),
160 ("snapshot.json", RoleKeys { encrypted: false, data: json! {[snapshot_key] } }),
161 ("timestamp.json", RoleKeys { encrypted: false, data: json! {[timestamp_key] } }),
162 ] {
163 write_rolekeys(dir, rolekeys_filename, rolekeys)?;
164 }
165
166 Ok(Self {
167 root_keys: generate_rolekey_collection(DEFAULT_KEYTYPE_GENERATION, &root_key)?,
168 targets_keys: generate_rolekey_collection(DEFAULT_KEYTYPE_GENERATION, &targets_key)?,
169 snapshot_keys: generate_rolekey_collection(DEFAULT_KEYTYPE_GENERATION, &snapshot_key)?,
170 timestamp_keys: generate_rolekey_collection(
171 DEFAULT_KEYTYPE_GENERATION,
172 ×tamp_key,
173 )?,
174 })
175 }
176
177 pub fn builder() -> RepoKeysBuilder {
179 RepoKeysBuilder::new()
180 }
181
182 pub fn from_dir(path: &Path) -> Result<Self, ParseError> {
189 Ok(RepoKeys {
190 root_keys: parse_keys_if_exists(&path.join("root.json"))?,
191 targets_keys: parse_keys_if_exists(&path.join("targets.json"))?,
192 snapshot_keys: parse_keys_if_exists(&path.join("snapshot.json"))?,
193 timestamp_keys: parse_keys_if_exists(&path.join("timestamp.json"))?,
194 })
195 }
196
197 pub fn root_keys(&self) -> &[Box<dyn PrivateKey>] {
199 &self.root_keys
200 }
201
202 pub fn targets_keys(&self) -> &[Box<dyn PrivateKey>] {
204 &self.targets_keys
205 }
206
207 pub fn snapshot_keys(&self) -> &[Box<dyn PrivateKey>] {
209 &self.snapshot_keys
210 }
211
212 pub fn timestamp_keys(&self) -> &[Box<dyn PrivateKey>] {
214 &self.timestamp_keys
215 }
216}
217
218#[derive(Debug)]
220pub struct RepoKeysBuilder {
221 keys: RepoKeys,
222}
223
224impl RepoKeysBuilder {
225 #[allow(clippy::new_without_default)]
227 pub fn new() -> Self {
228 RepoKeysBuilder {
229 keys: RepoKeys {
230 root_keys: vec![],
231 targets_keys: vec![],
232 snapshot_keys: vec![],
233 timestamp_keys: vec![],
234 },
235 }
236 }
237
238 pub fn add_root_key(mut self, key: Box<dyn PrivateKey>) -> Self {
240 self.keys.root_keys.push(key);
241 self
242 }
243
244 pub fn add_targets_key(mut self, key: Box<dyn PrivateKey>) -> Self {
246 self.keys.targets_keys.push(key);
247 self
248 }
249
250 pub fn add_snapshot_key(mut self, key: Box<dyn PrivateKey>) -> Self {
252 self.keys.snapshot_keys.push(key);
253 self
254 }
255
256 pub fn add_timestamp_key(mut self, key: Box<dyn PrivateKey>) -> Self {
258 self.keys.timestamp_keys.push(key);
259 self
260 }
261
262 pub fn load_root_keys(mut self, path: &Path) -> Result<Self, ParseError> {
264 self.keys.root_keys.extend(parse_keys(File::open(path)?)?);
265 Ok(self)
266 }
267
268 pub fn load_targets_keys(mut self, path: &Path) -> Result<Self, ParseError> {
270 self.keys.targets_keys.extend(parse_keys(File::open(path)?)?);
271 Ok(self)
272 }
273
274 pub fn load_snapshot_keys(mut self, path: &Path) -> Result<Self, ParseError> {
276 self.keys.snapshot_keys.extend(parse_keys(File::open(path)?)?);
277 Ok(self)
278 }
279
280 pub fn load_timestamp_keys(mut self, path: &Path) -> Result<Self, ParseError> {
282 self.keys.timestamp_keys.extend(parse_keys(File::open(path)?)?);
283 Ok(self)
284 }
285
286 pub fn build(self) -> RepoKeys {
287 self.keys
288 }
289}
290
291fn parse_keys_if_exists(path: &Path) -> Result<Vec<Box<dyn PrivateKey>>, ParseError> {
293 match File::open(path) {
294 Ok(f) => parse_keys(f),
295 Err(err) => {
296 if err.kind() == io::ErrorKind::NotFound {
297 Ok(vec![])
298 } else {
299 Err(err.into())
300 }
301 }
302 }
303}
304
305fn parse_keys(f: File) -> Result<Vec<Box<dyn PrivateKey>>, ParseError> {
307 let role_keys: RoleKeys = serde_json::from_reader(f)?;
308
309 if role_keys.encrypted {
310 return Err(ParseError::EncryptedKeys);
311 }
312
313 let role_keys: Vec<RoleKey> = serde_json::from_value(role_keys.data)?;
314
315 let mut keys = Vec::with_capacity(role_keys.len());
316 for RoleKey { keytype, scheme, keyid_hash_algorithms, keyval } in role_keys {
317 match (keytype, scheme) {
318 (KeyType::Ed25519, SignatureScheme::Ed25519) => {
319 let (public, private) = if let Some(keyid_hash_algorithms) = keyid_hash_algorithms {
320 (
321 PublicKey::from_ed25519_with_keyid_hash_algorithms(
322 keyval.public,
323 Some(keyid_hash_algorithms.clone()),
324 )?,
325 Ed25519PrivateKey::from_ed25519_with_keyid_hash_algorithms(
326 &keyval.private,
327 Some(keyid_hash_algorithms),
328 )?,
329 )
330 } else {
331 (
332 PublicKey::from_ed25519(keyval.public)?,
333 Ed25519PrivateKey::from_ed25519(&keyval.private)?,
334 )
335 };
336
337 if public.as_bytes() != private.public().as_bytes() {
338 return Err(ParseError::PublicKeyDoesNotMatchPrivateKey {
339 expected: private.public().clone(),
340 actual: public,
341 });
342 }
343
344 keys.push(Box::new(private) as Box<_>);
345 }
346 (keytype, scheme) => {
347 return Err(ParseError::UnsupportedKeyTypeAndScheme { keytype, scheme });
348 }
349 };
350 }
351
352 Ok(keys)
353}
354
355#[derive(Serialize, Deserialize)]
356struct RoleKeys {
357 #[serde(default = "default_false", skip_serializing_if = "bool_is_false")]
358 encrypted: bool,
359 data: serde_json::Value,
360}
361
362fn default_false() -> bool {
363 false
364}
365
366fn bool_is_false(b: &bool) -> bool {
367 !(*b)
368}
369
370#[derive(Clone, Serialize, Deserialize)]
371struct RoleKey {
372 keytype: KeyType,
373 scheme: SignatureScheme,
374 #[serde(default)]
380 keyid_hash_algorithms: Option<Vec<String>>,
381 keyval: KeyVal,
382}
383
384#[derive(Clone, Serialize, Deserialize)]
385struct KeyVal {
386 #[serde(with = "hex::serde")]
387 public: Vec<u8>,
388 #[serde(with = "hex::serde")]
389 private: Vec<u8>,
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::test_utils;
396 use assert_matches::assert_matches;
397 use camino::Utf8Path;
398
399 macro_rules! assert_keys {
400 ($actual:expr, $expected:expr) => {
401 assert_eq!(
402 $actual.iter().map(|key| key.public()).collect::<Vec<&PublicKey>>(),
403 $expected.iter().collect::<Vec<&PublicKey>>(),
404 )
405 };
406 }
407
408 #[test]
409 fn test_parsing_empty_builder() {
410 let keys = RepoKeys::builder().build();
411
412 assert_keys!(keys.root_keys(), &[]);
413 assert_keys!(keys.targets_keys(), &[]);
414 assert_keys!(keys.snapshot_keys(), &[]);
415 assert_keys!(keys.timestamp_keys(), &[]);
416 }
417
418 #[test]
419 fn test_parsing_empty_file() {
420 let json_keys = json!({
421 "encrypted": false,
422 "data": [],
423 });
424 let (file, temp_path) = tempfile::NamedTempFile::new().unwrap().into_parts();
425 serde_json::to_writer(file, &json_keys).unwrap();
426
427 let keys = RepoKeys::builder().load_root_keys(&temp_path).unwrap().build();
428
429 assert_keys!(keys.root_keys(), &[]);
430 assert_keys!(keys.targets_keys(), &[]);
431 assert_keys!(keys.snapshot_keys(), &[]);
432 assert_keys!(keys.timestamp_keys(), &[]);
433 }
434
435 #[test]
436 fn test_parsing_keys() {
437 let json_keys = json!({
438 "encrypted": false,
439 "data": [
440 {
441 "keytype": "ed25519",
442 "scheme": "ed25519",
443 "keyid_hash_algorithms": [
444 "sha256"
445 ],
446 "keyval": {
447 "public":
448 "1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71",
449 "private":
450 "b841db733b03ee9f5061c3e5495545175e30cbfd167371bae0c5cf51f3065d8a\
451 1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71",
452 },
453 },
454 {
455 "keytype": "ed25519",
456 "scheme": "ed25519",
457 "keyval": {
458 "public":
459 "b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb",
460 "private":
461 "41a6dfefe5f29859014745a854bff4571b46c022714e3d1dfd6abdd67c10d750\
462 b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb",
463 },
464 },
465 ]
466 });
467
468 let (file, temp_path) = tempfile::NamedTempFile::new().unwrap().into_parts();
469 serde_json::to_writer(file, &json_keys).unwrap();
470
471 let keys = RepoKeys::builder().load_root_keys(&temp_path).unwrap().build();
472
473 assert_keys!(
474 keys.root_keys(),
475 &[
476 PublicKey::from_ed25519_with_keyid_hash_algorithms(
477 hex::decode(
478 b"1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71"
479 )
480 .unwrap(),
481 Some(vec!["sha256".into()]),
482 )
483 .unwrap(),
484 PublicKey::from_ed25519(
485 hex::decode(
486 b"b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb"
487 )
488 .unwrap(),
489 )
490 .unwrap(),
491 ]
492 );
493 assert_keys!(keys.targets_keys(), &[]);
494 assert_keys!(keys.snapshot_keys(), &[]);
495 assert_keys!(keys.timestamp_keys(), &[]);
496 }
497
498 #[test]
499 fn test_from_dir_all_keys() {
500 let tmp = tempfile::tempdir().unwrap();
501 let dir = Utf8Path::from_path(tmp.path()).unwrap();
502 test_utils::make_empty_pm_repo_dir(dir);
503
504 let keys = RepoKeys::from_dir(&dir.join("keys").into_std_path_buf()).unwrap();
505
506 assert_keys!(
507 keys.root_keys(),
508 &[PublicKey::from_ed25519_with_keyid_hash_algorithms(
509 hex::decode(b"1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71")
510 .unwrap(),
511 Some(vec!["sha256".into()]),
512 )
513 .unwrap()]
514 );
515 assert_keys!(
516 keys.targets_keys(),
517 &[PublicKey::from_ed25519_with_keyid_hash_algorithms(
518 hex::decode(b"d18d96532e15f1f8e2e2307d23bbbfc4df90782273abcf642740642d8871a640")
519 .unwrap(),
520 Some(vec!["sha256".into()]),
521 )
522 .unwrap()]
523 );
524 assert_keys!(
525 keys.snapshot_keys(),
526 &[PublicKey::from_ed25519_with_keyid_hash_algorithms(
527 hex::decode(b"b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb")
528 .unwrap(),
529 Some(vec!["sha256".into()]),
530 )
531 .unwrap()]
532 );
533 assert_keys!(
534 keys.timestamp_keys(),
535 &[PublicKey::from_ed25519_with_keyid_hash_algorithms(
536 hex::decode(b"10dd7f1b17b379cbce30f09ffcb582b3c2bf7923b2bb99399966569867c4eeaa")
537 .unwrap(),
538 Some(vec!["sha256".into()]),
539 )
540 .unwrap()]
541 );
542 }
543
544 #[test]
545 fn test_from_dir_some_keys() {
546 let tmp = tempfile::tempdir().unwrap();
547 let dir = Utf8Path::from_path(tmp.path()).unwrap();
548 test_utils::make_empty_pm_repo_dir(dir);
549
550 let keys_dir = dir.join("keys");
551 std::fs::remove_file(keys_dir.join("root.json")).unwrap();
552 let keys = RepoKeys::from_dir(&keys_dir.into_std_path_buf()).unwrap();
553
554 assert_keys!(keys.root_keys(), &[]);
555 assert_keys!(
556 keys.targets_keys(),
557 &[PublicKey::from_ed25519_with_keyid_hash_algorithms(
558 hex::decode(b"d18d96532e15f1f8e2e2307d23bbbfc4df90782273abcf642740642d8871a640")
559 .unwrap(),
560 Some(vec!["sha256".into()]),
561 )
562 .unwrap()]
563 );
564 assert_keys!(
565 keys.snapshot_keys(),
566 &[PublicKey::from_ed25519_with_keyid_hash_algorithms(
567 hex::decode(b"b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb")
568 .unwrap(),
569 Some(vec!["sha256".into()]),
570 )
571 .unwrap()]
572 );
573 assert_keys!(
574 keys.timestamp_keys(),
575 &[PublicKey::from_ed25519_with_keyid_hash_algorithms(
576 hex::decode(b"10dd7f1b17b379cbce30f09ffcb582b3c2bf7923b2bb99399966569867c4eeaa")
577 .unwrap(),
578 Some(vec!["sha256".into()]),
579 )
580 .unwrap()]
581 );
582 }
583
584 #[test]
585 fn test_from_dir_empty() {
586 let tmp = tempfile::tempdir().unwrap();
587 let keys = RepoKeys::from_dir(tmp.path()).unwrap();
588
589 assert_keys!(keys.root_keys(), &[]);
590 assert_keys!(keys.targets_keys(), &[]);
591 assert_keys!(keys.snapshot_keys(), &[]);
592 assert_keys!(keys.timestamp_keys(), &[]);
593 }
594
595 #[test]
596 fn test_from_dir_generated_keys() {
597 macro_rules! assert_repo_keys {
598 ($generated:expr, $parsed:expr) => {
599 let generated: Vec<&PublicKey> =
600 $generated.iter().map(|key| key.public()).collect::<_>();
601 let parsed: Vec<&PublicKey> = $parsed.iter().map(|key| key.public()).collect::<_>();
602 assert_eq!(generated, parsed);
603 assert_ne!(generated, Vec::<&PublicKey>::new());
604 };
605 }
606
607 let tmp = tempfile::tempdir().unwrap();
608
609 let generated_keys = RepoKeys::generate(tmp.path()).unwrap();
610 let parsed_keys = RepoKeys::from_dir(tmp.path()).unwrap();
611
612 assert_repo_keys!(generated_keys.root_keys(), parsed_keys.root_keys());
613 assert_repo_keys!(generated_keys.targets_keys(), parsed_keys.targets_keys());
614 assert_repo_keys!(generated_keys.snapshot_keys(), parsed_keys.snapshot_keys());
615 assert_repo_keys!(generated_keys.timestamp_keys(), parsed_keys.timestamp_keys());
616 }
617
618 #[test]
619 fn test_parsing_keys_missing_file() {
620 let tmp = tempfile::tempdir().unwrap();
621 assert_matches!(
622 RepoKeys::builder().load_root_keys(&tmp.path().join("does-not-exist")),
623 Err(ParseError::Io(_))
624 );
625 }
626
627 #[test]
628 fn test_parsing_keys_malformed_json() {
629 let (mut file, temp_path) = tempfile::NamedTempFile::new().unwrap().into_parts();
630 file.write_all(b"invalid json").unwrap();
631 drop(file);
632
633 assert_matches!(RepoKeys::builder().load_root_keys(&temp_path), Err(ParseError::Json(_)));
634 }
635
636 #[test]
637 fn test_parsing_keys_rejects_unknown_keytype() {
638 let json_keys = json!({
639 "encrypted": false,
640 "data": [
641 {
642 "keytype": "unknown",
643 "scheme": "ed25519",
644 "keyval": {
645 "public":
646 "b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb",
647 "private":
648 "b841db733b03ee9f5061c3e5495545175e30cbfd167371bae0c5cf51f3065d8a\
649 1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71",
650 },
651 },
652 ]
653 });
654
655 let (file, temp_path) = tempfile::NamedTempFile::new().unwrap().into_parts();
656 serde_json::to_writer(file, &json_keys).unwrap();
657
658 assert_matches!(
659 RepoKeys::builder().load_root_keys(&temp_path),
660 Err(ParseError::UnsupportedKeyTypeAndScheme { keytype, scheme })
661 if keytype == KeyType::Unknown("unknown".into()) && scheme == SignatureScheme::Ed25519
662 );
663 }
664
665 #[test]
666 fn test_parsing_keys_rejects_unknown_scheme() {
667 let json_keys = json!({
668 "encrypted": false,
669 "data": [
670 {
671 "keytype": "ed25519",
672 "scheme": "unknown",
673 "keyval": {
674 "public":
675 "b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb",
676 "private":
677 "b841db733b03ee9f5061c3e5495545175e30cbfd167371bae0c5cf51f3065d8a\
678 1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71",
679 },
680 },
681 ]
682 });
683
684 let (file, temp_path) = tempfile::NamedTempFile::new().unwrap().into_parts();
685 serde_json::to_writer(file, &json_keys).unwrap();
686
687 assert_matches!(
688 RepoKeys::builder().load_root_keys(&temp_path),
689 Err(ParseError::UnsupportedKeyTypeAndScheme { keytype, scheme })
690 if keytype == KeyType::Ed25519 && scheme == SignatureScheme::Unknown("unknown".into())
691 );
692 }
693
694 #[test]
695 fn test_parsing_keys_rejects_wrong_keys() {
696 let json_keys = json!({
697 "encrypted": false,
698 "data": [
699 {
700 "keytype": "ed25519",
701 "scheme": "ed25519",
702 "keyval": {
703 "public":
704 "b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb",
705 "private":
706 "b841db733b03ee9f5061c3e5495545175e30cbfd167371bae0c5cf51f3065d8a\
707 1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71",
708 },
709 },
710 ]
711 });
712
713 let (file, temp_path) = tempfile::NamedTempFile::new().unwrap().into_parts();
714 serde_json::to_writer(file, &json_keys).unwrap();
715
716 assert_matches!(
717 RepoKeys::builder().load_root_keys(&temp_path),
718 Err(ParseError::PublicKeyDoesNotMatchPrivateKey { expected, actual })
719 if expected == PublicKey::from_ed25519(
720 hex::decode(b"1d4c564cb8466c49f97f042f5f3f242365ffd4210a9a1a82759d7d58afd66d71")
721 .unwrap(),
722 ).unwrap() && actual ==
723 PublicKey::from_ed25519(
724 hex::decode(b"b3ef3423402006eba0775f51f1fa4b38b70297098a0f40d699e984d76a6b83fb")
725 .unwrap(),
726 ).unwrap()
727 );
728 }
729
730 #[test]
731 fn test_parsing_keys_rejects_encrypted_keys() {
732 let json_keys = json!({
733 "encrypted": true,
734 "data": {
735 "kdf": {
736 "name": "scrypt",
737 "params": {
738 "N": 32768,
739 "r": 8,
740 "p": 1
741 },
742 "salt": "abc",
743 },
744 "cipher": {
745 "name": "nacl/secretbox",
746 "nonce": "def",
747 },
748 "ciphertext": "efg",
749 }
750 });
751
752 let (file, temp_path) = tempfile::NamedTempFile::new().unwrap().into_parts();
753 serde_json::to_writer(file, &json_keys).unwrap();
754
755 assert_matches!(
756 RepoKeys::builder().load_root_keys(&temp_path),
757 Err(ParseError::EncryptedKeys)
758 );
759 }
760}