Skip to main content

fuchsia_repo/
repo_keys.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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/// Errors returned by parsing keys.
17#[derive(Debug, thiserror::Error)]
18pub enum ParseError {
19    /// IO error occurred.
20    #[error(transparent)]
21    Io(#[from] io::Error),
22
23    /// TUF experienced an error parsing keys.
24    #[error(transparent)]
25    Tuf(#[from] tuf::Error),
26
27    /// JSON parsing error.
28    #[error(transparent)]
29    Json(#[from] serde_json::Error),
30
31    /// The private key's public key does not match the public key.
32    #[error("private key's public key {expected:?} does not match public key {actual:?}")]
33    PublicKeyDoesNotMatchPrivateKey { expected: PublicKey, actual: PublicKey },
34
35    /// The key type and signature scheme is unsupported.
36    #[error("unsupported key type {keytype:?} and signature scheme {scheme:?}")]
37    UnsupportedKeyTypeAndScheme { keytype: KeyType, scheme: SignatureScheme },
38
39    /// The key type and signature scheme is unsupported.
40    #[error("unsupported generation for key type {keytype:?}")]
41    UnsupportedKeyTypeGeneration { keytype: KeyType },
42
43    /// The keys file is encrypted, which is not supported.
44    #[error("The keys file is encrypted, which is not supported")]
45    EncryptedKeys,
46}
47
48/// Hold all the private keys for a repository.
49pub 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    /// Generates a set of [RoleKey]s, writing the following files to the passed `dir`:
78    /// * root.json
79    /// * targets.json
80    /// * snapshot.json
81    /// * timestamp.json
82    ///
83    /// Returns the generated [RepoKeys] struct.
84    pub fn generate(dir: &Path) -> Result<Self, ParseError> {
85        /// Generates a [KeyVal] in format specified by `keytype`.
86        /// * For the Ed25519 format, a tuf private key is generated using the `mundane`
87        ///   crate.
88        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        /// Generates a [RoleKey] in format specified by `keytype`.
106        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        /// Takes the input [RoleKey], and generates a [Vec<Box<dyn PrivateKey>>]
119        /// struct.
120        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        /// Writes the `keyname` file to the specified directory.
140        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                &timestamp_key,
173            )?,
174        })
175    }
176
177    /// Return a [RepoKeysBuilder].
178    pub fn builder() -> RepoKeysBuilder {
179        RepoKeysBuilder::new()
180    }
181
182    /// Create a [RepoKeys] from a pm-style keys directory, which can optionally contain the
183    /// following files:
184    /// * root.json - all the root metadata private keys.
185    /// * targets.json - all the targets metadata private keys.
186    /// * snapshot.json - all the snapshot metadata private keys.
187    /// * timestamp.json - all the timestamp metadata private keys.
188    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    /// Return all the loaded [PrivateKey]s for the root metadata.
198    pub fn root_keys(&self) -> &[Box<dyn PrivateKey>] {
199        &self.root_keys
200    }
201
202    /// Return all the loaded [PrivateKey]s for the targets metadata.
203    pub fn targets_keys(&self) -> &[Box<dyn PrivateKey>] {
204        &self.targets_keys
205    }
206
207    /// Return all the loaded [PrivateKey]s for the snapshot metadata.
208    pub fn snapshot_keys(&self) -> &[Box<dyn PrivateKey>] {
209        &self.snapshot_keys
210    }
211
212    /// Return all the loaded [PrivateKey]s for the timestamp metadata.
213    pub fn timestamp_keys(&self) -> &[Box<dyn PrivateKey>] {
214        &self.timestamp_keys
215    }
216}
217
218/// Helper to construct [RepoKeys].
219#[derive(Debug)]
220pub struct RepoKeysBuilder {
221    keys: RepoKeys,
222}
223
224impl RepoKeysBuilder {
225    /// Construct a new [RepoKeysBuilder].
226    #[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    /// Add a [PrivateKey] that will be used as a root key.
239    pub fn add_root_key(mut self, key: Box<dyn PrivateKey>) -> Self {
240        self.keys.root_keys.push(key);
241        self
242    }
243
244    /// Add a [PrivateKey] that will be used as a targets key.
245    pub fn add_targets_key(mut self, key: Box<dyn PrivateKey>) -> Self {
246        self.keys.targets_keys.push(key);
247        self
248    }
249
250    /// Add a [PrivateKey] that will be used as a snapshot key.
251    pub fn add_snapshot_key(mut self, key: Box<dyn PrivateKey>) -> Self {
252        self.keys.snapshot_keys.push(key);
253        self
254    }
255
256    /// Add a [PrivateKey] that will be used as a timestamp key.
257    pub fn add_timestamp_key(mut self, key: Box<dyn PrivateKey>) -> Self {
258        self.keys.timestamp_keys.push(key);
259        self
260    }
261
262    /// Load root metadata [PrivateKey]s from a pm-style json file.
263    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    /// Load targets metadata [PrivateKey]s from a pm-style json file.
269    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    /// Load snapshot metadata [PrivateKey]s from a pm-style json file.
275    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    /// Load timestamp metadata [PrivateKey]s from a pm-style json file.
281    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
291/// Try to open the key file. Return an empty vector if the file doesn't exist.
292fn 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
305/// Open the key file.
306fn 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    /// The `keyid_hash_algorithms` is a deprecated field we added for support in order to test
375    /// against python-tuf, which accidentally incorporated this field into the keyid computation.
376    /// If the field is present, the value will be incorporated into the computation of the keyid,
377    /// even if the field is empty. That's why it needs the option wrapper so we can distinguish
378    /// between the value not being specified from it being empty.
379    #[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}