tough/
error.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Contains the error type for this library.
5
6#![allow(clippy::default_trait_access)]
7
8use crate::schema::RoleType;
9use crate::{schema, TargetName, TransportError};
10use chrono::{DateTime, Utc};
11use snafu::{Backtrace, Snafu};
12use std::io;
13use std::path::PathBuf;
14use url::Url;
15
16/// Alias for `Result<T, Error>`.
17pub type Result<T> = std::result::Result<T, Error>;
18
19/// The error type for this library.
20#[derive(Debug, Snafu)]
21#[snafu(visibility(pub(crate)))]
22#[non_exhaustive]
23#[allow(missing_docs)]
24pub enum Error {
25    #[snafu(display("Unable to canonicalize path '{}': {}", path.display(), source))]
26    AbsolutePath {
27        path: PathBuf,
28        source: std::io::Error,
29        backtrace: Backtrace,
30    },
31
32    #[snafu(display(
33        "Failed to create temp directory for the repository datastore: {}",
34        source
35    ))]
36    DatastoreInit {
37        source: std::io::Error,
38        backtrace: Backtrace,
39    },
40
41    /// The library failed to create a file in the datastore.
42    #[snafu(display("Failed to create file at datastore path {}: {}", path.display(), source))]
43    DatastoreCreate {
44        path: PathBuf,
45        source: std::io::Error,
46        backtrace: Backtrace,
47    },
48
49    /// The library failed to open a file in the datastore.
50    #[snafu(display("Failed to open file from datastore path {}: {}", path.display(), source))]
51    DatastoreOpen {
52        path: PathBuf,
53        source: std::io::Error,
54        backtrace: Backtrace,
55    },
56
57    /// The library failed to remove a file in the datastore.
58    #[snafu(display("Failed to remove file at datastore path {}: {}", path.display(), source))]
59    DatastoreRemove {
60        path: PathBuf,
61        source: std::io::Error,
62        backtrace: Backtrace,
63    },
64
65    /// The library failed to serialize an object to JSON to the datastore.
66    #[snafu(display("Failed to serialize {} to JSON at datastore path {}: {}", what, path.display(), source))]
67    DatastoreSerialize {
68        what: String,
69        path: PathBuf,
70        source: serde_json::Error,
71        backtrace: Backtrace,
72    },
73
74    #[snafu(display("Failed to create directory '{}': {}", path.display(), source))]
75    DirCreate {
76        path: PathBuf,
77        source: std::io::Error,
78        backtrace: Backtrace,
79    },
80
81    /// A metadata file has expired.
82    #[snafu(display("{} metadata is expired", role))]
83    ExpiredMetadata {
84        role: RoleType,
85        backtrace: Backtrace,
86    },
87
88    #[snafu(display("Failed to stat '{}': {}", path.display(), source))]
89    FileMetadata {
90        path: PathBuf,
91        source: std::io::Error,
92        backtrace: Backtrace,
93    },
94
95    #[snafu(display("Failed to open {}: {}", path.display(), source))]
96    FileOpen {
97        path: PathBuf,
98        source: std::io::Error,
99        backtrace: Backtrace,
100    },
101
102    #[snafu(display("Failed to read {}: {}", path.display(), source))]
103    FileRead {
104        path: PathBuf,
105        source: std::io::Error,
106        backtrace: Backtrace,
107    },
108
109    #[snafu(display("Failed to parse {}: {}", path.display(), source))]
110    FileParseJson {
111        path: PathBuf,
112        source: serde_json::Error,
113        backtrace: Backtrace,
114    },
115
116    #[snafu(display("Can't build URL from relative path '{}'", path.display()))]
117    FileUrl { path: PathBuf, backtrace: Backtrace },
118
119    #[snafu(display("Failed to write to {}: {}", path.display(), source))]
120    FileWrite {
121        path: PathBuf,
122        source: std::io::Error,
123        backtrace: Backtrace,
124    },
125
126    /// A downloaded target's checksum does not match the checksum listed in the repository
127    /// metadata.
128    #[snafu(display(
129        "Hash mismatch for {}: calculated {}, expected {}",
130        context,
131        calculated,
132        expected,
133    ))]
134    HashMismatch {
135        context: String,
136        calculated: String,
137        expected: String,
138        backtrace: Backtrace,
139    },
140
141    #[snafu(display("Source path for target must be file or symlink - '{}'", path.display()))]
142    InvalidFileType { path: PathBuf, backtrace: Backtrace },
143
144    #[snafu(display("Encountered an invalid target name: {}", inner))]
145    InvalidTargetName { inner: String, backtrace: Backtrace },
146
147    /// The library failed to create a URL from a base URL and a path.
148    #[snafu(display("Failed to join \"{}\" to URL \"{}\": {}", path, url, source))]
149    JoinUrl {
150        path: String,
151        url: url::Url,
152        source: url::ParseError,
153        backtrace: Backtrace,
154    },
155
156    #[snafu(display(
157        "After encoding the name '{}' to '{}', failed to join '{}' to URL '{}': {}",
158        original,
159        encoded,
160        filename,
161        url,
162        source
163    ))]
164    JoinUrlEncoded {
165        original: String,
166        encoded: String,
167        filename: String,
168        url: url::Url,
169        source: url::ParseError,
170        backtrace: Backtrace,
171    },
172
173    #[snafu(display("Unable to parse keypair: {}", source))]
174    KeyPairFromKeySource {
175        source: Box<dyn std::error::Error + Send + Sync + 'static>,
176        backtrace: Backtrace,
177    },
178
179    #[snafu(display("Private key rejected: {}", source))]
180    KeyRejected {
181        source: aws_lc_rs::error::KeyRejected,
182        backtrace: Backtrace,
183    },
184
185    #[snafu(display("Unable to match any of the provided keys with root.json"))]
186    KeysNotFoundInRoot { backtrace: Backtrace },
187
188    #[snafu(display("Unrecognized private key format"))]
189    KeyUnrecognized { backtrace: Backtrace },
190
191    #[snafu(display("Failed to create symlink at '{}': {}", path.display(), source))]
192    LinkCreate {
193        path: PathBuf,
194        source: io::Error,
195        backtrace: Backtrace,
196    },
197
198    /// A file's maximum size exceeded a limit set by the consumer of this library or the metadata.
199    #[snafu(display("Maximum size {} (specified by {}) exceeded", max_size, specifier))]
200    MaxSizeExceeded {
201        max_size: u64,
202        specifier: &'static str,
203        backtrace: Backtrace,
204    },
205
206    /// The maximum root updates setting was exceeded.
207    #[snafu(display("Maximum root updates {} exceeded", max_root_updates))]
208    MaxUpdatesExceeded {
209        max_root_updates: u64,
210        backtrace: Backtrace,
211    },
212
213    /// A required reference to a metadata file is missing from a metadata file.
214    #[snafu(display("Meta for {:?} missing from {} metadata", file, role))]
215    MetaMissing {
216        file: &'static str,
217        role: RoleType,
218        backtrace: Backtrace,
219    },
220
221    #[snafu(display("Missing '{}' when building repo from RepositoryEditor", field))]
222    Missing { field: String, backtrace: Backtrace },
223
224    #[snafu(display("Unable to create NamedTempFile in directory '{}': {}", path.display(), source))]
225    NamedTempFileCreate {
226        path: PathBuf,
227        source: std::io::Error,
228        backtrace: Backtrace,
229    },
230
231    #[snafu(display("Unable to persist NamedTempFile to '{}': {}", path.display(), source))]
232    NamedTempFilePersist {
233        path: PathBuf,
234        source: tempfile::PersistError,
235        backtrace: Backtrace,
236    },
237
238    /// Unable to determine file name (path ends in '..' or is '/')
239    #[snafu(display("Unable to determine file name from path: '{}'", path.display()))]
240    NoFileName { path: PathBuf, backtrace: Backtrace },
241
242    #[snafu(display("Key for role '{}' doesn't exist in root.json", role))]
243    NoRoleKeysinRoot { role: String },
244
245    /// A downloaded metadata file has an older version than a previously downloaded metadata file.
246    #[snafu(display(
247        "Found version {} of {} metadata when we had previously fetched version {}",
248        new_version,
249        role,
250        current_version
251    ))]
252    OlderMetadata {
253        role: RoleType,
254        current_version: u64,
255        new_version: u64,
256        backtrace: Backtrace,
257    },
258
259    /// A timestamp metadata file must contain exactly one entry
260    #[snafu(display(
261        "Timestamp version {} meta length {} is not exactly one",
262        version,
263        meta_length
264    ))]
265    TimestampMetaLength { version: u64, meta_length: usize },
266
267    /// A timestamp metadata file must contain a meta entry for snapshot.json
268    #[snafu(display("No snapshot meta in timestamp.json version {}", version))]
269    MissingSnapshotMeta { version: u64 },
270
271    /// The snapshot version in a newer timestamp metadata file must be greater than
272    /// or equal to the version in an older timestamp.
273    #[snafu(display(
274        "Snapshot version {} in timestamp {} is less than {} in timestamp {}",
275        snapshot_new,
276        timestamp_new,
277        snapshot_old,
278        timestamp_old
279    ))]
280    OlderSnapshotInTimestamp {
281        snapshot_new: u64,
282        timestamp_new: u64,
283        snapshot_old: u64,
284        timestamp_old: u64,
285    },
286
287    /// The snapshot meta must contain targets.json
288    #[snafu(display("Snapshot version {} does not contain targets.json", version))]
289    SnapshotTargetsMetaMissing { version: u64 },
290
291    /// Any role in the trusted snapshot meta must also appear in the new snapshot meta
292    #[snafu(display(
293        "Role {} appears in snapshot version {} but not version {}",
294        role,
295        old_version,
296        new_version
297    ))]
298    SnapshotRoleMissing {
299        role: String,
300        old_version: u64,
301        new_version: u64,
302    },
303
304    /// Role version in trusted snapshot must be less than or equal to version in new snapshot
305    #[snafu(display(
306        "Role {} version {} in snapshot {} is greater than version {} in snapshot {}",
307        role,
308        old_role_version,
309        old_snapshot_version,
310        new_role_version,
311        new_snapshot_version
312    ))]
313    SnapshotRoleRollback {
314        role: String,
315        old_role_version: u64,
316        old_snapshot_version: u64,
317        new_role_version: u64,
318        new_snapshot_version: u64,
319    },
320
321    /// The library failed to parse a metadata file, either because it was not valid JSON or it did
322    /// not conform to the expected schema.
323    ///
324    /// Invalid JSON errors read like:
325    /// * EOF while parsing a string at line 1 column 14
326    ///
327    /// Schema non-conformance errors read like:
328    /// * invalid type: integer `2`, expected a string at line 1 column 11
329    /// * missing field `sig` at line 1 column 16
330    #[snafu(display("Failed to parse {} metadata: {}", role, source))]
331    ParseMetadata {
332        role: RoleType,
333        source: serde_json::Error,
334        backtrace: Backtrace,
335    },
336
337    /// The library failed to parse the trusted root metadata file, either because it was not valid
338    /// JSON or it did not conform to the expected schema. The *trusted* root metadata file is the
339    /// file is either the `root` argument passed to `Repository::load`, or the most recently
340    /// cached and validated root metadata file.
341    #[snafu(display("Failed to parse trusted root metadata: {}", source))]
342    ParseTrustedMetadata {
343        source: serde_json::Error,
344        backtrace: Backtrace,
345    },
346
347    /// Failed to parse a URL provided to [`Repository::load`][crate::Repository::load].
348    #[snafu(display("Failed to parse URL {:?}: {}", url, source))]
349    ParseUrl {
350        url: String,
351        source: url::ParseError,
352        backtrace: Backtrace,
353    },
354
355    #[snafu(display("Target path exists, caller requested we fail - '{}'", path.display()))]
356    PathExistsFail { path: PathBuf, backtrace: Backtrace },
357
358    #[snafu(display("Requested copy/link of '{}' which is not a file", path.display()))]
359    PathIsNotFile { path: PathBuf, backtrace: Backtrace },
360
361    #[snafu(display("Requested copy/link of '{}' which is not a repo target", path.display()))]
362    PathIsNotTarget { path: PathBuf, backtrace: Backtrace },
363
364    /// Path isn't a valid UTF8 string
365    #[snafu(display("Path {} is not valid UTF-8", path.display()))]
366    PathUtf8 { path: PathBuf, backtrace: Backtrace },
367
368    #[snafu(display("Path {} is not valid UTF-8", path.display()))]
369    UnixPathUtf8 {
370        path: typed_path::UnixPathBuf,
371        backtrace: Backtrace,
372    },
373
374    #[snafu(display("Failed to remove existing target path '{}': {}", path.display(), source))]
375    RemoveTarget {
376        path: PathBuf,
377        source: std::io::Error,
378        backtrace: Backtrace,
379    },
380
381    #[snafu(display("Unable to get info about the outdir '{}': {}", path.display(), source))]
382    SaveTargetDirInfo {
383        path: PathBuf,
384        source: std::io::Error,
385        backtrace: Backtrace,
386    },
387
388    #[snafu(display("The outdir '{}' either does not exist or is not a directory", path.display()))]
389    SaveTargetOutdir { path: PathBuf, backtrace: Backtrace },
390
391    #[snafu(display("Unable to canonicalize the outdir '{}': {}", path.display(), source))]
392    SaveTargetOutdirCanonicalize {
393        path: PathBuf,
394        source: std::io::Error,
395        backtrace: Backtrace,
396    },
397
398    #[snafu(display(
399        "The path '{}' to which we would save target '{}' has no parent",
400        path.display(),
401        name.raw(),
402    ))]
403    SaveTargetNoParent {
404        path: PathBuf,
405        name: TargetName,
406        backtrace: Backtrace,
407    },
408
409    #[snafu(display("The target '{}' was not found", name.raw()))]
410    SaveTargetNotFound {
411        name: TargetName,
412        backtrace: Backtrace,
413    },
414
415    #[snafu(display(
416        "The target '{}' had an unsafe name. Not writing to '{}' because it is not in the outdir '{}'",
417        name.raw(),
418        filepath.display(),
419        outdir.display()
420    ))]
421    SaveTargetUnsafePath {
422        name: TargetName,
423        outdir: PathBuf,
424        filepath: PathBuf,
425    },
426
427    #[snafu(display("Failed to serialize role '{}' for signing: {}", role, source))]
428    SerializeRole {
429        role: String,
430        source: serde_json::Error,
431        backtrace: Backtrace,
432    },
433
434    #[snafu(display("Failed to serialize signed role '{}': {}", role, source))]
435    SerializeSignedRole {
436        role: String,
437        source: serde_json::Error,
438        backtrace: Backtrace,
439    },
440
441    #[snafu(display("Failed to sign message"))]
442    Sign {
443        source: aws_lc_rs::error::Unspecified,
444        backtrace: Backtrace,
445    },
446
447    #[snafu(display("Failed to sign message: {}", source))]
448    SignMessage {
449        source: Box<dyn std::error::Error + Send + Sync + 'static>,
450        backtrace: Backtrace,
451    },
452
453    #[snafu(display("Unable to find signing keys for role '{}'", role))]
454    SigningKeysNotFound { role: String },
455
456    #[snafu(display(
457        "Tried to use role metadata with spec version '{}', version '{}' is supported",
458        given,
459        supported
460    ))]
461    SpecVersion {
462        given: String,
463        supported: String,
464        backtrace: Backtrace,
465    },
466
467    /// System time is behaving irrationally, went back in time
468    #[snafu(display(
469        "System time stepped backward: system time '{}', last known time '{}'",
470        sys_time,
471        latest_known_time,
472    ))]
473    SystemTimeSteppedBackward {
474        sys_time: DateTime<Utc>,
475        latest_known_time: DateTime<Utc>,
476    },
477
478    #[snafu(display("Refusing to replace {} with requested {} for target {}", found, expected, path.display()))]
479    TargetFileTypeMismatch {
480        expected: String,
481        found: String,
482        path: PathBuf,
483        backtrace: Backtrace,
484    },
485
486    #[snafu(display("Unable to create Target from path '{}': {}", path.display(), source))]
487    TargetFromPath {
488        path: PathBuf,
489        source: crate::schema::Error,
490        backtrace: Backtrace,
491    },
492
493    #[snafu(display("Unable to resolve the target name '{}': {}", name, source))]
494    TargetNameResolve {
495        name: String,
496        source: std::io::Error,
497    },
498
499    #[snafu(display(
500        "Unable to resolve target name '{}', a path with no components was produced",
501        name
502    ))]
503    TargetNameComponentsEmpty { name: String },
504
505    #[snafu(display("Unable to resolve target name '{}', expected a rooted path", name))]
506    TargetNameRootMissing { name: String },
507
508    /// A transport error occurred while fetching a URL.
509    #[snafu(display("Failed to fetch {}: {}", url, source))]
510    Transport {
511        url: url::Url,
512        source: TransportError,
513        backtrace: Backtrace,
514    },
515
516    #[snafu(display(
517        "The target name '..' is unsafe. Interpreting it as a path could escape from the intended \
518        directory",
519    ))]
520    UnsafeTargetNameDotDot {},
521
522    #[snafu(display(
523        "The target name '{}' is unsafe. Interpreting it as a path would lead to an empty filename",
524        name
525    ))]
526    UnsafeTargetNameEmpty { name: String },
527
528    #[snafu(display(
529        "The target name '{}' is unsafe. Interpreting it as a path would lead to a filename of '/'",
530        name
531    ))]
532    UnsafeTargetNameSlash { name: String },
533
534    /// A metadata file could not be verified.
535    #[snafu(display("Failed to verify {} metadata: {}", role, source))]
536    VerifyMetadata {
537        role: RoleType,
538        source: crate::schema::Error,
539        backtrace: Backtrace,
540    },
541
542    #[snafu(display("Failed to verify {} metadata: {}", role, source))]
543    VerifyRoleMetadata {
544        role: String,
545        source: crate::schema::Error,
546        backtrace: Backtrace,
547    },
548
549    /// The trusted root metadata file could not be verified.
550    #[snafu(display("Failed to verify trusted root metadata: {}", source))]
551    VerifyTrustedMetadata {
552        source: crate::schema::Error,
553        backtrace: Backtrace,
554    },
555
556    /// A fetched metadata file did not have the version we expected it to have.
557    #[snafu(display(
558        "{} metadata version mismatch: fetched {}, expected {}",
559        role,
560        fetched,
561        expected
562    ))]
563    VersionMismatch {
564        role: RoleType,
565        fetched: u64,
566        expected: u64,
567        backtrace: Backtrace,
568    },
569
570    #[snafu(display("Error reading data from '{}': {}", url, source))]
571    CacheFileRead {
572        url: Url,
573        source: std::io::Error,
574        backtrace: Backtrace,
575    },
576
577    #[snafu(display("Error writing data to '{}': {}", path.display(), source))]
578    CacheFileWrite {
579        path: PathBuf,
580        source: std::io::Error,
581        backtrace: Backtrace,
582    },
583
584    #[snafu(display("Error creating the directory '{}': {}", path.display(), source))]
585    CacheDirectoryCreate {
586        path: PathBuf,
587        source: std::io::Error,
588        backtrace: Backtrace,
589    },
590
591    #[snafu(display("Error writing target file to '{}': {}", path.display(), source))]
592    CacheTargetWrite {
593        path: PathBuf,
594        source: std::io::Error,
595        backtrace: Backtrace,
596    },
597
598    #[snafu(display("The target '{}' was not found", target_name.raw()))]
599    CacheTargetMissing {
600        target_name: TargetName,
601        source: crate::schema::Error,
602        backtrace: Backtrace,
603    },
604
605    #[snafu(display("Failed to walk directory tree '{}': {}", directory.display(), source))]
606    WalkDir {
607        directory: PathBuf,
608        source: walkdir::Error,
609        backtrace: Backtrace,
610    },
611
612    #[snafu(display("Delegated role not found: {}", name))]
613    DelegateNotFound { name: String },
614
615    #[snafu(display("Targets role '{}' not found: {}", name, source))]
616    TargetsNotFound {
617        name: String,
618        source: crate::schema::Error,
619    },
620
621    #[snafu(display("Delegated role not found: {}", name))]
622    DelegateMissing {
623        name: String,
624        source: crate::schema::Error,
625    },
626
627    #[snafu(display("Delegation doesn't contain targets field"))]
628    NoTargets,
629
630    #[snafu(display("Targets doesn't contain delegations field"))]
631    NoDelegations,
632
633    #[snafu(display("Delegated roles are not consistent for {}", name))]
634    DelegatedRolesNotConsistent { name: String },
635
636    /// Target doesn't have proper permissions from parent delegations
637    #[snafu(display("Invalid file permissions"))]
638    InvalidPath { source: crate::schema::Error },
639
640    #[snafu(display("Role missing from snapshot meta: {} ({})", name, parent))]
641    RoleNotInMeta { name: String, parent: String },
642
643    #[snafu(display("The key for {} was not included", role))]
644    KeyNotFound {
645        role: String,
646        source: schema::Error,
647        backtrace: Backtrace,
648    },
649
650    #[snafu(display("No keys were found for role '{}'", role))]
651    NoKeys { role: String },
652
653    #[snafu(display("Invalid number"))]
654    InvalidInto {
655        source: std::num::TryFromIntError,
656        backtrace: Backtrace,
657    },
658
659    #[snafu(display("Invalid threshold number"))]
660    InvalidThreshold { backtrace: Backtrace },
661
662    /// The library failed to serialize an object to JSON.
663    #[snafu(display("Failed to serialize to JSON: {}", source))]
664    JsonSerialization {
665        source: schema::Error,
666        backtrace: Backtrace,
667    },
668
669    /// Invalid path permissions
670    #[snafu(display("Invalid path permission of {} : {:?}", name, paths))]
671    InvalidPathPermission {
672        name: String,
673        paths: Vec<String>,
674        source: schema::Error,
675    },
676
677    /// Duplicate keyids (4.2.1)
678    #[snafu(display("Duplicate keyid {} in signatures", keyid))]
679    DuplicateKeyid { keyid: String },
680
681    /// `SignedDelegatedTargets` has more than 1 signed targets
682    #[snafu(display("Exactly 1 role was required, but {} were created", count))]
683    InvalidRoleCount { count: usize },
684
685    /// Could not create a targets map
686    #[snafu(display("Could not create a targets map: {}", source))]
687    TargetsMap { source: schema::Error },
688
689    /// A `key_holder` wasn't set
690    #[snafu(display("A key holder must be set"))]
691    NoKeyHolder,
692
693    #[snafu(display("No limits in editor"))]
694    MissingLimits,
695
696    #[snafu(display("The transport is not in editor"))]
697    MissingTransport,
698
699    /// Root creates an unloadable repo
700    #[snafu(display(
701        "Unstable root; found {} keys for role {}, threshold is {}",
702        role,
703        actual,
704        threshold
705    ))]
706    UnstableRoot {
707        role: RoleType,
708        actual: usize,
709        threshold: u64,
710    },
711
712    #[snafu(display("The targets editor was not cleared"))]
713    TargetsEditorSome,
714}