Merge pull request #363 from erickt/improve-errors

Improve errors
diff --git a/tuf/src/client.rs b/tuf/src/client.rs
index 0a9c7d3..8e01e01 100644
--- a/tuf/src/client.rs
+++ b/tuf/src/client.rs
@@ -55,7 +55,7 @@
 
 use crate::crypto::{self, HashAlgorithm, HashValue, PublicKey};
 use crate::database::Database;
-use crate::error::Error;
+use crate::error::{Error, Result};
 use crate::interchange::DataInterchange;
 use crate::metadata::{
     Metadata, MetadataPath, MetadataVersion, RawSignedMetadata, RootMetadata, SnapshotMetadata,
@@ -63,7 +63,6 @@
 };
 use crate::repository::{Repository, RepositoryProvider, RepositoryStorage};
 use crate::verify::Verified;
-use crate::Result;
 
 /// A client that interacts with TUF repositories.
 #[derive(Debug)]
@@ -402,7 +401,7 @@
         .await;
 
         match res {
-            Ok(()) | Err(Error::NotFound) => {}
+            Ok(()) | Err(Error::MetadataNotFound { .. }) => {}
             Err(err) => {
                 warn!("error loading local metadata: : {}", err);
             }
@@ -537,7 +536,7 @@
 
             let raw_signed_root = match res {
                 Ok(raw_signed_root) => raw_signed_root,
-                Err(Error::NotFound) => {
+                Err(Error::MetadataNotFound { .. }) => {
                     break;
                 }
                 Err(err) => {
@@ -691,7 +690,10 @@
     {
         let snapshot_description = match tuf.trusted_timestamp() {
             Some(ts) => Ok(ts.snapshot()),
-            None => Err(Error::MissingMetadata(MetadataPath::timestamp())),
+            None => Err(Error::MetadataNotFound {
+                path: MetadataPath::timestamp(),
+                version: MetadataVersion::None,
+            }),
         }?
         .clone();
 
@@ -772,13 +774,15 @@
         let targets_description = match tuf.trusted_snapshot() {
             Some(sn) => match sn.meta().get(&MetadataPath::targets()) {
                 Some(d) => Ok(d),
-                None => Err(Error::VerificationFailure(
-                    "Snapshot metadata did not contain a description of the \
-                     current targets metadata."
-                        .into(),
-                )),
+                None => Err(Error::MissingMetadataDescription {
+                    parent_role: MetadataPath::snapshot(),
+                    child_role: MetadataPath::targets(),
+                }),
             },
-            None => Err(Error::MissingMetadata(MetadataPath::snapshot())),
+            None => Err(Error::MetadataNotFound {
+                path: MetadataPath::snapshot(),
+                version: MetadataVersion::None,
+            }),
         }?
         .clone();
 
@@ -928,7 +932,10 @@
         let snapshot = self
             .tuf
             .trusted_snapshot()
-            .ok_or_else(|| Error::MissingMetadata(MetadataPath::snapshot()))?
+            .ok_or_else(|| Error::MetadataNotFound {
+                path: MetadataPath::snapshot(),
+                version: MetadataVersion::None,
+            })?
             .clone();
 
         /////////////////////////////////////////
@@ -960,7 +967,10 @@
                 "Walking the delegation graph would have exceeded the configured max depth: {}",
                 self.config.max_delegation_depth
             );
-            return (default_terminate, Err(Error::NotFound));
+            return (
+                default_terminate,
+                Err(Error::TargetNotFound(target.clone())),
+            );
         }
 
         // these clones are dumb, but we need immutable values and not references for update
@@ -972,7 +982,10 @@
                 None => {
                     return (
                         default_terminate,
-                        Err(Error::MissingMetadata(MetadataPath::targets())),
+                        Err(Error::MetadataNotFound {
+                            path: MetadataPath::targets(),
+                            version: MetadataVersion::None,
+                        }),
                     );
                 }
             },
@@ -984,13 +997,18 @@
 
         let delegations = match targets.delegations() {
             Some(d) => d,
-            None => return (default_terminate, Err(Error::NotFound)),
+            None => {
+                return (
+                    default_terminate,
+                    Err(Error::TargetNotFound(target.clone())),
+                )
+            }
         };
 
         for delegation in delegations.roles().iter() {
             if !delegation.paths().iter().any(|p| target.is_child(p)) {
                 if delegation.terminating() {
-                    return (true, Err(Error::NotFound));
+                    return (true, Err(Error::TargetNotFound(target.clone())));
                 } else {
                     continue;
                 }
@@ -999,7 +1017,7 @@
             let role_meta = match snapshot.meta().get(delegation.role()) {
                 Some(m) => m,
                 None if delegation.terminating() => {
-                    return (true, Err(Error::NotFound));
+                    return (true, Err(Error::TargetNotFound(target.clone())));
                 }
                 None => {
                     continue;
@@ -1105,7 +1123,10 @@
             };
         }
 
-        (default_terminate, Err(Error::NotFound))
+        (
+            default_terminate,
+            Err(Error::TargetNotFound(target.clone())),
+        )
     }
 }
 
@@ -1156,7 +1177,7 @@
         .await
     {
         Ok(raw_meta) => Ok((false, raw_meta)),
-        Err(Error::NotFound) => {
+        Err(Error::MetadataNotFound { .. }) => {
             let raw_meta = remote
                 .fetch_metadata(path, version, max_length, hashes)
                 .await?;
@@ -1338,7 +1359,8 @@
 
             assert_matches!(
                 Client::with_trusted_local(Config::default(), &mut local, &remote).await,
-                Err(Error::NotFound)
+                Err(Error::MetadataNotFound { path, version })
+                if path == MetadataPath::root() && version == MetadataVersion::Number(1)
             );
 
             assert_matches!(
@@ -1351,7 +1373,8 @@
                     &remote,
                 )
                 .await,
-                Err(Error::NotFound)
+                Err(Error::MetadataNotFound { path, version })
+                if path == MetadataPath::root() && version == MetadataVersion::Number(1)
             );
         })
     }
@@ -1383,7 +1406,8 @@
                     &remote,
                 )
                 .await,
-                Err(Error::VerificationFailure(_))
+                Err(Error::MetadataMissingSignatures { role, number_of_valid_signatures: 0, threshold: 1 })
+                if role == MetadataPath::root()
             );
         })
     }
diff --git a/tuf/src/crypto.rs b/tuf/src/crypto.rs
index 5d653e8..03e0506 100644
--- a/tuf/src/crypto.rs
+++ b/tuf/src/crypto.rs
@@ -39,9 +39,9 @@
     },
 };
 
-use crate::error::Error;
+use crate::error::{derp_error_to_error, Error, Result};
 use crate::interchange::cjson::shims;
-use crate::Result;
+use crate::metadata::MetadataPath;
 
 const HASH_ALG_PREFS: &[HashAlgorithm] = &[HashAlgorithm::Sha512, HashAlgorithm::Sha256];
 
@@ -205,7 +205,7 @@
         #[cfg(feature = "unstable_rsa")]
         (KeyType::Rsa, SignatureScheme::RsaSsaPssSha256)
         | (KeyType::Rsa, SignatureScheme::RsaSsaPssSha512) => {
-            let bytes = write_spki(public_key, key_type)?;
+            let bytes = write_spki(public_key, key_type).map_err(derp_error_to_error)?;
             BASE64URL.encode(&bytes)
         }
         (_, _) => {
@@ -371,13 +371,6 @@
         SignatureValue(bytes)
     }
 
-    /// Create a new `SignatureValue` from the given hex string.
-    ///
-    /// Note: It is unlikely that you ever want to do this manually.
-    pub fn from_hex(string: &str) -> Result<Self> {
-        Ok(SignatureValue(HEXLOWER.decode(string.as_bytes())?))
-    }
-
     /// Return the signature as bytes.
     pub fn as_bytes(&self) -> &[u8] {
         &self.0
@@ -665,7 +658,7 @@
             )));
         }
 
-        let pub_key = extract_rsa_pub_from_pkcs8(der_key)?;
+        let pub_key = extract_rsa_pub_from_pkcs8(der_key).map_err(derp_error_to_error)?;
 
         let public = PublicKey::new(
             KeyType::Rsa,
@@ -755,22 +748,24 @@
     ) -> Result<Self> {
         let input = Input::from(der_bytes);
 
-        let (typ, value) = input.read_all(derp::Error::Read, |input| {
-            derp::nested(input, Tag::Sequence, |input| {
-                let typ = derp::nested(input, Tag::Sequence, |input| {
-                    let typ = derp::expect_tag_and_get_value(input, Tag::Oid)?;
+        let (typ, value) = input
+            .read_all(derp::Error::Read, |input| {
+                derp::nested(input, Tag::Sequence, |input| {
+                    let typ = derp::nested(input, Tag::Sequence, |input| {
+                        let typ = derp::expect_tag_and_get_value(input, Tag::Oid)?;
 
-                    let typ = KeyType::from_oid(typ.as_slice_less_safe())
-                        .map_err(|_| derp::Error::WrongValue)?;
+                        let typ = KeyType::from_oid(typ.as_slice_less_safe())
+                            .map_err(|_| derp::Error::WrongValue)?;
 
-                    // for RSA / ed25519 this is null, so don't both parsing it
-                    derp::read_null(input)?;
-                    Ok(typ)
-                })?;
-                let value = derp::bit_string_with_no_unused_bits(input)?;
-                Ok((typ, value.as_slice_less_safe().to_vec()))
+                        // for RSA / ed25519 this is null, so don't both parsing it
+                        derp::read_null(input)?;
+                        Ok(typ)
+                    })?;
+                    let value = derp::bit_string_with_no_unused_bits(input)?;
+                    Ok((typ, value.as_slice_less_safe().to_vec()))
+                })
             })
-        })?;
+            .map_err(derp_error_to_error)?;
 
         Self::new(typ, scheme, keyid_hash_algorithms, value)
     }
@@ -804,7 +799,7 @@
     ///
     /// See the documentation on `KeyValue` for more information on SPKI.
     pub fn as_spki(&self) -> Result<Vec<u8>> {
-        Ok(write_spki(&self.value.0, &self.typ)?)
+        write_spki(&self.value.0, &self.typ).map_err(derp_error_to_error)
     }
 
     /// An immutable reference to the key's type.
@@ -828,7 +823,7 @@
     }
 
     /// Use this key to verify a message with a signature.
-    pub fn verify(&self, msg: &[u8], sig: &Signature) -> Result<()> {
+    pub fn verify(&self, role: &MetadataPath, msg: &[u8], sig: &Signature) -> Result<()> {
         let alg: &dyn ring::signature::VerificationAlgorithm = match self.scheme {
             SignatureScheme::Ed25519 => &ED25519,
             #[cfg(feature = "unstable_rsa")]
@@ -842,7 +837,7 @@
 
         let key = ring::signature::UnparsedPublicKey::new(alg, &self.value.0);
         key.verify(msg, &sig.value.0)
-            .map_err(|_| Error::BadSignature)
+            .map_err(|_| Error::BadSignature(role.clone()))
     }
 }
 
@@ -1231,12 +1226,12 @@
         let key =
             RsaPrivateKey::from_pkcs8(rsa::PK8_2048, SignatureScheme::RsaSsaPssSha256).unwrap();
         let sig = key.sign(msg).unwrap();
-        key.public.verify(msg, &sig).unwrap();
+        key.public.verify(&MetadataPath::root(), msg, &sig).unwrap();
 
         let key =
             RsaPrivateKey::from_pkcs8(rsa::PK8_2048, SignatureScheme::RsaSsaPssSha512).unwrap();
         let sig = key.sign(msg).unwrap();
-        key.public.verify(msg, &sig).unwrap();
+        key.public.verify(&MetadataPath::root(), msg, &sig).unwrap();
     }
 
     #[cfg(feature = "unstable_rsa")]
@@ -1247,12 +1242,12 @@
         let key =
             RsaPrivateKey::from_pkcs8(rsa::PK8_4096, SignatureScheme::RsaSsaPssSha256).unwrap();
         let sig = key.sign(msg).unwrap();
-        key.public.verify(msg, &sig).unwrap();
+        key.public.verify(&MetadataPath::root(), msg, &sig).unwrap();
 
         let key =
             RsaPrivateKey::from_pkcs8(rsa::PK8_4096, SignatureScheme::RsaSsaPssSha512).unwrap();
         let sig = key.sign(msg).unwrap();
-        key.public.verify(msg, &sig).unwrap();
+        key.public.verify(&MetadataPath::root(), msg, &sig).unwrap();
     }
 
     #[cfg(feature = "unstable_rsa")]
@@ -1279,7 +1274,8 @@
         let pub_key =
             PublicKey::from_spki(&key.public.as_spki().unwrap(), SignatureScheme::Ed25519).unwrap();
 
-        assert_matches!(pub_key.verify(msg, &sig), Ok(()));
+        let role = MetadataPath::root();
+        assert_matches!(pub_key.verify(&role, msg, &sig), Ok(()));
 
         // Make sure we match what ring expects.
         let ring_key = ring::signature::Ed25519KeyPair::from_pkcs8(ed25519::PK8_1).unwrap();
@@ -1292,7 +1288,11 @@
             .public()
             .clone();
 
-        assert_matches!(bad_pub_key.verify(msg, &sig), Err(Error::BadSignature));
+        assert_matches!(
+            bad_pub_key.verify(&role, msg, &sig),
+            Err(Error::BadSignature(r))
+            if r == role
+        );
     }
 
     #[test]
@@ -1301,9 +1301,10 @@
         let pub_key = PublicKey::from_ed25519(ed25519::PUBLIC_KEY).unwrap();
         assert_eq!(key.public(), &pub_key);
 
+        let role = MetadataPath::root();
         let msg = b"test";
         let sig = key.sign(msg).unwrap();
-        assert_matches!(pub_key.verify(msg, &sig), Ok(()));
+        assert_matches!(pub_key.verify(&role, msg, &sig), Ok(()));
 
         // Make sure we match what ring expects.
         let ring_key = ring::signature::Ed25519KeyPair::from_pkcs8(ed25519::PK8_1).unwrap();
@@ -1316,7 +1317,11 @@
             .public()
             .clone();
 
-        assert_matches!(bad_pub_key.verify(msg, &sig), Err(Error::BadSignature));
+        assert_matches!(
+            bad_pub_key.verify(&role, msg, &sig),
+            Err(Error::BadSignature(r))
+            if r == role
+        );
     }
 
     #[test]
@@ -1333,9 +1338,10 @@
         .unwrap();
         assert_eq!(key.public(), &pub_key);
 
+        let role = MetadataPath::root();
         let msg = b"test";
         let sig = key.sign(msg).unwrap();
-        assert_matches!(pub_key.verify(msg, &sig), Ok(()));
+        assert_matches!(pub_key.verify(&role, msg, &sig), Ok(()));
 
         // Make sure we match what ring expects.
         let ring_key = ring::signature::Ed25519KeyPair::from_pkcs8(ed25519::PK8_1).unwrap();
@@ -1348,7 +1354,11 @@
             .public()
             .clone();
 
-        assert_matches!(bad_pub_key.verify(msg, &sig), Err(Error::BadSignature));
+        assert_matches!(
+            bad_pub_key.verify(&role, msg, &sig),
+            Err(Error::BadSignature(r))
+            if r == role
+        );
     }
 
     #[test]
@@ -1360,6 +1370,7 @@
             b"unknown-key".to_vec(),
         )
         .unwrap();
+        let role = MetadataPath::root();
         let msg = b"test";
         let sig = Signature {
             key_id: KeyId("key-id".into()),
@@ -1367,7 +1378,7 @@
         };
 
         assert_matches!(
-            pub_key.verify(msg, &sig),
+            pub_key.verify(&role, msg, &sig),
             Err(Error::UnknownSignatureScheme(s))
             if s == "unknown-scheme"
         );
@@ -1416,7 +1427,10 @@
         let s = "4750eaf6878740780d6f97b12dbad079fb012bec88c78de2c380add56d3f51db";
         let jsn = json!(s);
         let parsed: SignatureValue = serde_json::from_str(&format!("\"{}\"", s)).unwrap();
-        assert_eq!(parsed, SignatureValue::from_hex(s).unwrap());
+        assert_eq!(
+            parsed,
+            SignatureValue(HEXLOWER.decode(s.as_bytes()).unwrap())
+        );
         let encoded = serde_json::to_value(&parsed).unwrap();
         assert_eq!(encoded, jsn);
     }
diff --git a/tuf/src/database.rs b/tuf/src/database.rs
index 0dbde75..74e7c7c 100644
--- a/tuf/src/database.rs
+++ b/tuf/src/database.rs
@@ -9,8 +9,9 @@
 use crate::error::Error;
 use crate::interchange::DataInterchange;
 use crate::metadata::{
-    Delegations, Metadata, MetadataPath, RawSignedMetadata, RawSignedMetadataSet, RootMetadata,
-    SnapshotMetadata, TargetDescription, TargetPath, TargetsMetadata, TimestampMetadata,
+    Delegations, Metadata, MetadataPath, MetadataVersion, RawSignedMetadata, RawSignedMetadataSet,
+    RootMetadata, SnapshotMetadata, TargetDescription, TargetPath, TargetsMetadata,
+    TimestampMetadata,
 };
 use crate::verify::{self, Verified};
 use crate::Result;
@@ -41,10 +42,16 @@
     {
         let verified_root = {
             // Make sure the keys signed the root.
-            let new_root = verify::verify_signatures(raw_root, root_threshold, root_keys)?;
+            let new_root = verify::verify_signatures(
+                &MetadataPath::root(),
+                raw_root,
+                root_threshold,
+                root_keys,
+            )?;
 
             // Make sure the root signed itself.
             verify::verify_signatures(
+                &MetadataPath::root(),
                 raw_root,
                 new_root.root().threshold(),
                 new_root.keys().iter().filter_map(|(k, v)| {
@@ -82,6 +89,7 @@
 
             // Make sure the root signed itself.
             verify::verify_signatures(
+                &MetadataPath::root(),
                 raw_root,
                 unverified_root.root().threshold(),
                 unverified_root.root_keys(),
@@ -132,7 +140,10 @@
         let mut db = if let Some(root) = metadata_set.root() {
             Database::from_root_with_trusted_keys(root, root_threshold, root_keys)?
         } else {
-            return Err(Error::MissingMetadata(MetadataPath::root()));
+            return Err(Error::MetadataNotFound {
+                path: MetadataPath::root(),
+                version: MetadataVersion::None,
+            });
         };
 
         db.update_metadata_after_root(start_time, metadata_set)?;
@@ -165,7 +176,10 @@
         let mut db = if let Some(root) = metadata_set.root() {
             Database::from_trusted_root(root)?
         } else {
-            return Err(Error::MissingMetadata(MetadataPath::root()));
+            return Err(Error::MetadataNotFound {
+                path: MetadataPath::root(),
+                version: MetadataVersion::None,
+            });
         };
 
         db.update_metadata_after_root(start_time, metadata_set)?;
@@ -266,6 +280,7 @@
             //     cycle, begin at step 0 and version N of the root metadata file.  Verify the
             //     trusted root signed the new root.
             let new_root = verify::verify_signatures(
+                &MetadataPath::root(),
                 raw_root,
                 trusted_root.root().threshold(),
                 trusted_root.root_keys(),
@@ -273,6 +288,7 @@
 
             // Verify the new root signed itself.
             let new_root = verify::verify_signatures(
+                &MetadataPath::root(),
                 raw_root,
                 new_root.root().threshold(),
                 new_root.root_keys(),
@@ -290,15 +306,15 @@
             //     update cycle, begin at step 0 and version N of the root metadata file.
 
             let next_root_version = trusted_root.version().checked_add(1).ok_or_else(|| {
-                Error::VerificationFailure("root version should be less than max u32".into())
+                Error::MetadataVersionMustBeSmallerThanMaxU32(MetadataPath::root())
             })?;
 
             if new_root.version() != next_root_version {
-                return Err(Error::VerificationFailure(format!(
-                    "Attempted to roll back root metadata at version {} to {}.",
-                    trusted_root.version(),
-                    new_root.version()
-                )));
+                return Err(Error::AttemptedMetadataRollBack {
+                    role: MetadataPath::root(),
+                    trusted_version: trusted_root.version(),
+                    new_version: new_root.version(),
+                });
             }
 
             /////////////////////////////////////////
@@ -367,6 +383,7 @@
             //     cycle, and report the signature failure.
 
             let new_timestamp = verify::verify_signatures(
+                &MetadataPath::timestamp(),
                 raw_timestamp,
                 trusted_root.timestamp().threshold(),
                 trusted_root.timestamp_keys(),
@@ -386,11 +403,11 @@
             if let Some(trusted_timestamp) = &self.trusted_timestamp {
                 match new_timestamp.version().cmp(&trusted_timestamp.version()) {
                     Ordering::Less => {
-                        return Err(Error::VerificationFailure(format!(
-                            "Attempted to roll back timestamp metadata at version {} to {}.",
-                            trusted_timestamp.version(),
-                            new_timestamp.version()
-                        )));
+                        return Err(Error::AttemptedMetadataRollBack {
+                            role: MetadataPath::timestamp(),
+                            trusted_version: trusted_timestamp.version(),
+                            new_version: new_timestamp.version(),
+                        });
                     }
                     Ordering::Equal => {
                         return Ok(None);
@@ -459,11 +476,11 @@
                     .cmp(&trusted_snapshot.version())
                 {
                     Ordering::Less => {
-                        return Err(Error::VerificationFailure(format!(
-                            "Attempted to roll back snapshot metadata at version {} to {}.",
-                            trusted_snapshot.version(),
-                            trusted_timestamp.snapshot().version()
-                        )));
+                        return Err(Error::AttemptedMetadataRollBack {
+                            role: MetadataPath::snapshot(),
+                            trusted_version: trusted_snapshot.version(),
+                            new_version: trusted_timestamp.snapshot().version(),
+                        });
                     }
                     Ordering::Equal => {
                         return Ok(false);
@@ -498,6 +515,7 @@
             //     signature failure.
 
             let new_snapshot = verify::verify_signatures(
+                &MetadataPath::snapshot(),
                 raw_snapshot,
                 trusted_root.snapshot().threshold(),
                 trusted_root.snapshot_keys(),
@@ -508,12 +526,12 @@
             // the version.
 
             if new_snapshot.version() != trusted_timestamp.snapshot().version() {
-                return Err(Error::VerificationFailure(format!(
-                    "The timestamp metadata reported that the snapshot metadata should be at \
-                     version {} but version {} was found instead.",
-                    trusted_timestamp.snapshot().version(),
-                    new_snapshot.version(),
-                )));
+                return Err(Error::WrongMetadataVersion {
+                    parent_role: MetadataPath::timestamp(),
+                    child_role: MetadataPath::snapshot(),
+                    expected_version: trusted_timestamp.snapshot().version(),
+                    new_version: new_snapshot.version(),
+                });
             }
 
             /////////////////////////////////////////
@@ -529,11 +547,11 @@
 
             if let Some(trusted_snapshot) = &self.trusted_snapshot {
                 if new_snapshot.version() < trusted_snapshot.version() {
-                    return Err(Error::VerificationFailure(format!(
-                        "Attempted to roll back snapshot metadata at version {} to {}",
-                        trusted_snapshot.version(),
-                        new_snapshot.version(),
-                    )));
+                    return Err(Error::AttemptedMetadataRollBack {
+                        role: MetadataPath::snapshot(),
+                        trusted_version: trusted_snapshot.version(),
+                        new_version: new_snapshot.version(),
+                    });
                 }
             }
 
@@ -663,18 +681,17 @@
             let trusted_targets = self.trusted_targets_unexpired(start_time)?;
 
             if trusted_targets.delegations().is_none() {
-                return Err(Error::VerificationFailure(
-                    "Delegations not authorized".into(),
-                ));
+                return Err(Error::UnauthorizedDelegation {
+                    parent_role: parent_role.clone(),
+                    child_role: role.clone(),
+                });
             };
 
             let (threshold, keys) = self
                 .find_delegation_threshold_and_keys(parent_role, role)?
-                .ok_or_else(|| {
-                    Error::VerificationFailure(format!(
-                        "The delegated targets role {:?} is not known to the delegating targets role {}",
-                        role, parent_role,
-                    ))
+                .ok_or_else(|| Error::UnauthorizedDelegation {
+                    parent_role: parent_role.clone(),
+                    child_role: role.clone(),
                 })?;
 
             let trusted_delegated_targets_version =
@@ -711,12 +728,14 @@
         // this metadata expired isn't part of the spec. Do we actually want to do this?
         let trusted_snapshot = self.trusted_snapshot_unexpired(start_time)?;
 
-        let trusted_targets_description = trusted_snapshot.meta().get(role).ok_or_else(|| {
-            Error::VerificationFailure(format!(
-                "Snapshot metadata had no description of the {} metadata",
-                role
-            ))
-        })?;
+        let trusted_targets_description =
+            trusted_snapshot
+                .meta()
+                .get(role)
+                .ok_or_else(|| Error::MissingMetadataDescription {
+                    parent_role: MetadataPath::snapshot(),
+                    child_role: role.clone(),
+                })?;
 
         /////////////////////////////////////////
         // TUF-1.0.5 §5.4.1:
@@ -745,6 +764,7 @@
         //     the update cycle, and report the failure.
 
         let new_targets = verify::verify_signatures(
+            role,
             raw_targets,
             trusted_targets_threshold,
             trusted_targets_keys,
@@ -757,24 +777,22 @@
         // FIXME(#295): TUF-1.0.5 §5.3.3.2 says this check should be done when updating the
         // snapshot, not here.
         if new_targets.version() != trusted_targets_description.version() {
-            return Err(Error::VerificationFailure(format!(
-                "The snapshot metadata reported that the {} metadata should be at \
-                     version {} but version {} was found instead.",
-                role,
-                trusted_targets_description.version(),
-                new_targets.version()
-            )));
+            return Err(Error::WrongMetadataVersion {
+                parent_role: MetadataPath::snapshot(),
+                child_role: role.clone(),
+                expected_version: trusted_targets_description.version(),
+                new_version: new_targets.version(),
+            });
         }
 
         if let Some(trusted_targets_version) = trusted_targets_version {
             match new_targets.version().cmp(&trusted_targets_version) {
                 Ordering::Less => {
-                    return Err(Error::VerificationFailure(format!(
-                        "Attempted to roll back {} metadata at version {} to {}.",
-                        role,
-                        trusted_targets_version,
-                        trusted_targets_description.version()
-                    )));
+                    return Err(Error::AttemptedMetadataRollBack {
+                        role: role.clone(),
+                        trusted_version: trusted_targets_version,
+                        new_version: new_targets.version(),
+                    });
                 }
                 Ordering::Equal => {
                     return Ok(None);
@@ -811,12 +829,18 @@
             if let Some(trusted_targets) = self.trusted_targets() {
                 trusted_targets
             } else {
-                return Err(Error::MissingMetadata(parent_role.clone()));
+                return Err(Error::MetadataNotFound {
+                    path: parent_role.clone(),
+                    version: MetadataVersion::None,
+                });
             }
         } else if let Some(trusted_parent) = self.trusted_delegations.get(parent_role) {
             trusted_parent
         } else {
-            return Err(Error::MissingMetadata(parent_role.clone()));
+            return Err(Error::MetadataNotFound {
+                path: parent_role.clone(),
+                version: MetadataVersion::None,
+            });
         };
 
         // Only consider targets metadata that define delegations.
@@ -947,9 +971,9 @@
                     &mut visited,
                 )
                 .1
-                .ok_or(Error::TargetUnavailable)
+                .ok_or_else(|| Error::TargetNotFound(target_path.clone()))
             }
-            None => Err(Error::TargetUnavailable),
+            None => Err(Error::TargetNotFound(target_path.clone())),
         }
     }
 
@@ -979,7 +1003,10 @@
                 }
                 Ok(trusted_timestamp)
             }
-            None => Err(Error::MissingMetadata(MetadataPath::timestamp())),
+            None => Err(Error::MetadataNotFound {
+                path: MetadataPath::timestamp(),
+                version: MetadataVersion::None,
+            }),
         }
     }
 
@@ -991,7 +1018,10 @@
                 }
                 Ok(trusted_snapshot)
             }
-            None => Err(Error::MissingMetadata(MetadataPath::snapshot())),
+            None => Err(Error::MetadataNotFound {
+                path: MetadataPath::snapshot(),
+                version: MetadataVersion::None,
+            }),
         }
     }
 
@@ -1003,7 +1033,10 @@
                 }
                 Ok(trusted_targets)
             }
-            None => Err(Error::MissingMetadata(MetadataPath::targets())),
+            None => Err(Error::MetadataNotFound {
+                path: MetadataPath::targets(),
+                version: MetadataVersion::None,
+            }),
         }
     }
 }
@@ -1067,7 +1100,12 @@
 
         assert_matches!(
             Database::from_root_with_trusted_keys(&raw_root, 1, once(KEYS[1].public())),
-            Err(Error::VerificationFailure(s)) if s == "Signature threshold not met: 0/1"
+            Err(Error::MetadataMissingSignatures {
+                role,
+                number_of_valid_signatures: 0,
+                threshold: 1,
+            })
+            if role == MetadataPath::root()
         );
     }
 
@@ -1104,7 +1142,12 @@
 
         assert_matches!(
             Database::from_trusted_metadata(&metadata),
-            Err(Error::VerificationFailure(s)) if s == "Signature threshold not met: 0/1"
+            Err(Error::MetadataMissingSignatures {
+                role,
+                number_of_valid_signatures: 0,
+                threshold: 1,
+            })
+            if role == MetadataPath::root()
         );
     }
 
@@ -1144,7 +1187,12 @@
 
         assert_matches!(
             Database::from_metadata_with_trusted_keys(&metadata, 1, once(KEYS[1].public())),
-            Err(Error::VerificationFailure(s)) if s == "Signature threshold not met: 0/1"
+            Err(Error::MetadataMissingSignatures {
+                role,
+                number_of_valid_signatures: 0,
+                threshold: 1,
+            })
+            if role == MetadataPath::root()
         );
     }
 
@@ -1180,7 +1228,8 @@
         // second update with the same metadata should fail.
         assert_matches!(
             tuf.update_root(&raw_root),
-            Err(Error::VerificationFailure(_))
+            Err(Error::AttemptedMetadataRollBack { role, trusted_version: 2, new_version: 2 })
+            if role == MetadataPath::root()
         );
     }
 
@@ -1696,7 +1745,12 @@
 
         assert_matches!(
             tuf.update_metadata(&metadata2),
-            Err(Error::VerificationFailure(s)) if s == "Signature threshold not met: 0/1"
+            Err(Error::MetadataMissingSignatures {
+                role,
+                number_of_valid_signatures: 0,
+                threshold: 1,
+            })
+            if role == MetadataPath::root()
         );
     }
 }
diff --git a/tuf/src/error.rs b/tuf/src/error.rs
index d253e49..b556b00 100644
--- a/tuf/src/error.rs
+++ b/tuf/src/error.rs
@@ -1,19 +1,20 @@
 //! Error types and converters.
 
-use data_encoding::DecodeError;
 use std::io;
-use std::path::Path;
 use thiserror::Error;
 
-use crate::metadata::MetadataPath;
+use crate::metadata::{MetadataPath, MetadataVersion, TargetPath};
+
+/// Alias for `Result<T, Error>`.
+pub type Result<T> = std::result::Result<T, Error>;
 
 /// Error type for all TUF related errors.
 #[non_exhaustive]
 #[derive(Error, Debug)]
 pub enum Error {
     /// The metadata had a bad signature.
-    #[error("bad signature")]
-    BadSignature,
+    #[error("metadata {0} has a bad signature")]
+    BadSignature(MetadataPath),
 
     /// There was a problem encoding or decoding.
     #[error("encoding: {0}")]
@@ -28,50 +29,80 @@
     IllegalArgument(String),
 
     /// Generic error for HTTP connections.
-    #[error("http: {0}")]
-    Http(#[from] http::Error),
-
-    /// Unexpected HTTP response status.
-    #[error("error getting {uri}: request failed with status code {code}")]
-    BadHttpStatus {
-        /// HTTP status code.
-        code: http::StatusCode,
-
+    #[error("http error for {uri}")]
+    Http {
         /// URI Resource that resulted in the error.
         uri: String,
+
+        /// The error.
+        #[source]
+        err: http::Error,
     },
 
     /// Errors that can occur parsing HTTP streams.
     #[cfg(feature = "hyper")]
-    #[error("hyper: {0}")]
-    Hyper(#[from] hyper::Error),
+    #[error("hyper error for {uri}")]
+    Hyper {
+        /// URI Resource that resulted in the error.
+        uri: String,
 
-    /// The metadata was missing, so an operation could not be completed.
-    #[error("missing {0} metadata")]
-    MissingMetadata(MetadataPath),
+        /// The error.
+        #[source]
+        err: hyper::Error,
+    },
+
+    /// Unexpected HTTP response status.
+    #[error("error getting {uri}: request failed with status code {code}")]
+    BadHttpStatus {
+        /// URI Resource that resulted in the error.
+        uri: String,
+
+        /// HTTP status code.
+        code: http::StatusCode,
+    },
+
+    /// An IO error occurred.
+    #[error(transparent)]
+    Io(#[from] io::Error),
+
+    /// An IO error occurred for a path.
+    #[error("IO error on path {path}")]
+    IoPath {
+        /// Path where the error occurred.
+        path: std::path::PathBuf,
+
+        /// The IO error.
+        #[source]
+        err: io::Error,
+    },
+
+    /// A json serialization error occurred.
+    #[error(transparent)]
+    Json(#[from] serde_json::error::Error),
 
     /// There were no available hash algorithms.
     #[error("no supported hash algorithm")]
     NoSupportedHashAlgorithm,
 
-    /// The metadata or target was not found.
-    #[error("not found")]
-    NotFound,
+    /// The metadata was not found.
+    #[error("metadata {path} at version {version} not found")]
+    MetadataNotFound {
+        /// The metadata path.
+        path: MetadataPath,
+
+        /// The metadata version.
+        version: MetadataVersion,
+    },
+
+    /// The target was not found.
+    #[error("target {0} not found")]
+    TargetNotFound(TargetPath),
 
     /// Opaque error type, to be interpreted similar to HTTP 500. Something went wrong, and you may
     /// or may not be able to do anything about it.
     #[error("opaque: {0}")]
     Opaque(String),
 
-    /// There was a library internal error. These errors are *ALWAYS* bugs and should be reported.
-    #[error("programming: {0}")]
-    Programming(String),
-
-    /// The target is unavailable. This may mean it is either not in the metadata or the metadata
-    /// chain to the target cannot be fully verified.
-    #[error("target unavailable")]
-    TargetUnavailable,
-
     /// There is no known or available key type.
     #[error("unknown key type: {0}")]
     UnknownKeyType(String),
@@ -80,68 +111,69 @@
     #[error("unknown signature scheme: {0}")]
     UnknownSignatureScheme(String),
 
-    /// The metadata or target failed to verify.
-    #[error("verification failure: {0}")]
-    VerificationFailure(String),
+    /// The metadata threshold cannot equal 0.
+    #[error("metadata {0} threshold must be greater than zero")]
+    MetadataThresholdMustBeGreaterThanZero(MetadataPath),
+
+    /// The metadata's version must be less than `u32::MAX`.
+    #[error("metadata {0} version should be less than max u32")]
+    MetadataVersionMustBeSmallerThanMaxU32(MetadataPath),
+
+    /// The metadata was not signed with enough valid signatures.
+    #[error("metadata {0} signature threshold not met: {number_of_valid_signatures}/{threshold}")]
+    MetadataMissingSignatures {
+        /// The signed metadata.
+        role: MetadataPath,
+        /// The number of signatures which are valid.
+        number_of_valid_signatures: u32,
+        /// The minimum number of valid signatures.
+        threshold: u32,
+    },
+
+    /// Attempted to update metadata with an older version.
+    #[error("attempted to roll back metadata {0} from version {trusted_version} to {new_version}")]
+    AttemptedMetadataRollBack {
+        /// The metadata.
+        role: MetadataPath,
+        /// The trusted metadata's version.
+        trusted_version: u32,
+        /// The new metadata's version.
+        new_version: u32,
+    },
+
+    /// The parent metadata expected the child metadata to be at one version, but was found to be at
+    /// another version.
+    #[error("metadata {parent_role} expected metadata {child_role} version {expected_version}, but found {new_version}")]
+    WrongMetadataVersion {
+        /// The parent metadata that contains the child metadata's version.
+        parent_role: MetadataPath,
+        /// The child metadata that has an unexpected version.
+        child_role: MetadataPath,
+        /// The expected version of the child metadata.
+        expected_version: u32,
+        /// The actual version of the child metadata.
+        new_version: u32,
+    },
+
+    /// The parent metadata does not contain a description of the child metadata.
+    #[error("metadata {parent_role} missing description of {child_role}")]
+    MissingMetadataDescription {
+        /// The parent metadata that contains the child metadata's description.
+        parent_role: MetadataPath,
+        /// The child metadata that should have been contained in the parent.
+        child_role: MetadataPath,
+    },
+
+    /// The parent metadata did not delegate to the child role.
+    #[error("{parent_role} delegation to {child_role} is not authorized")]
+    UnauthorizedDelegation {
+        /// The parent metadata that did not delegate to the child.
+        parent_role: MetadataPath,
+        /// That child metadata that was not delegated to by the parent.
+        child_role: MetadataPath,
+    },
 }
 
-impl From<serde_json::error::Error> for Error {
-    fn from(err: serde_json::error::Error) -> Error {
-        Error::Encoding(format!("JSON: {:?}", err))
-    }
-}
-
-impl Error {
-    /// Helper to include the path that causd the error for FS I/O errors.
-    pub fn from_io(err: &io::Error, path: &Path) -> Error {
-        Error::Opaque(format!("Path {:?} : {:?}", path, err))
-    }
-}
-
-impl From<io::Error> for Error {
-    fn from(err: io::Error) -> Error {
-        match err.kind() {
-            std::io::ErrorKind::NotFound => Error::NotFound,
-            _ => Error::Opaque(format!("IO: {:?}", err)),
-        }
-    }
-}
-
-impl From<DecodeError> for Error {
-    fn from(err: DecodeError) -> Error {
-        Error::Encoding(format!("{:?}", err))
-    }
-}
-
-impl From<derp::Error> for Error {
-    fn from(err: derp::Error) -> Error {
-        Error::Encoding(format!("DER: {:?}", err))
-    }
-}
-
-impl From<tempfile::PersistError> for Error {
-    fn from(err: tempfile::PersistError) -> Error {
-        Error::Opaque(format!("Error persisting temp file: {:?}", err))
-    }
-}
-
-impl From<tempfile::PathPersistError> for Error {
-    fn from(err: tempfile::PathPersistError) -> Error {
-        Error::Opaque(format!("Error persisting temp file: {:?}", err))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn verify_io_error_display_string() {
-        let err = Error::from(io::Error::from(std::io::ErrorKind::NotFound));
-        assert_eq!(err.to_string(), "not found");
-        assert_eq!(Error::NotFound.to_string(), "not found");
-
-        let err = Error::from(io::Error::from(std::io::ErrorKind::PermissionDenied));
-        assert_eq!(err.to_string(), "opaque: IO: Kind(PermissionDenied)");
-    }
+pub(crate) fn derp_error_to_error(err: derp::Error) -> Error {
+    Error::Encoding(format!("DER: {:?}", err))
 }
diff --git a/tuf/src/lib.rs b/tuf/src/lib.rs
index 241f087..89624f1 100644
--- a/tuf/src/lib.rs
+++ b/tuf/src/lib.rs
@@ -126,6 +126,3 @@
 
 pub use crate::database::*;
 pub use crate::error::*;
-
-/// Alias for `Result<T, Error>`.
-pub type Result<T> = std::result::Result<T, Error>;
diff --git a/tuf/src/metadata.rs b/tuf/src/metadata.rs
index acca77f..e2135d6 100644
--- a/tuf/src/metadata.rs
+++ b/tuf/src/metadata.rs
@@ -216,6 +216,15 @@
     Number(u32),
 }
 
+impl Display for MetadataVersion {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            MetadataVersion::None => f.write_str("none"),
+            MetadataVersion::Number(version) => write!(f, "{}", version),
+        }
+    }
+}
+
 impl MetadataVersion {
     /// Converts this struct into the string used for addressing metadata.
     pub fn prefix(&self) -> String {
@@ -1592,9 +1601,9 @@
     }
 }
 
-impl ToString for TargetPath {
-    fn to_string(&self) -> String {
-        self.0.clone()
+impl Display for TargetPath {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(&self.0)
     }
 }
 
@@ -2507,7 +2516,12 @@
         let raw_root = signed.to_raw().unwrap();
 
         assert_matches!(
-            verify_signatures(&raw_root, 1, &[root_key.public().clone()]),
+            verify_signatures(
+                &MetadataPath::root(),
+                &raw_root,
+                1,
+                &[root_key.public().clone()]
+            ),
             Ok(_)
         );
     }
@@ -2527,7 +2541,12 @@
         let raw_root = decoded.to_raw().unwrap();
 
         assert_matches!(
-            verify_signatures(&raw_root, 1, &[root_key.public().clone()]),
+            verify_signatures(
+                &MetadataPath::root(),
+                &raw_root,
+                1,
+                &[root_key.public().clone()]
+            ),
             Ok(_)
         );
     }
@@ -2550,11 +2569,21 @@
             serde_json::from_value(jsn).unwrap();
         let raw_root = decoded.to_raw().unwrap();
         assert_matches!(
-            verify_signatures(&raw_root, 2, &[root_key.public().clone()]),
-            Err(Error::VerificationFailure(_))
+            verify_signatures(&MetadataPath::root(), &raw_root, 2, &[root_key.public().clone()]),
+            Err(Error::MetadataMissingSignatures {
+                role,
+                number_of_valid_signatures: 1,
+                threshold: 2,
+            })
+            if role == MetadataPath::root()
         );
         assert_matches!(
-            verify_signatures(&raw_root, 1, &[root_key.public().clone()]),
+            verify_signatures(
+                &MetadataPath::root(),
+                &raw_root,
+                1,
+                &[root_key.public().clone()]
+            ),
             Ok(_)
         );
     }
@@ -2595,11 +2624,21 @@
 
         // Ensure the signatures are valid as-is.
         assert_matches!(
-            verify_signatures(&standard.to_raw().unwrap(), 1, &public_keys),
+            verify_signatures(
+                &M::ROLE.into(),
+                &standard.to_raw().unwrap(),
+                1,
+                &public_keys
+            ),
             Ok(_)
         );
         assert_matches!(
-            verify_signatures(&custom.to_raw().unwrap(), 1, std::iter::once(key.public())),
+            verify_signatures(
+                &M::ROLE.into(),
+                &custom.to_raw().unwrap(),
+                1,
+                std::iter::once(key.public())
+            ),
             Ok(_)
         );
 
@@ -2608,15 +2647,23 @@
         std::mem::swap(&mut standard.metadata, &mut custom.metadata);
         assert_matches!(
             verify_signatures(
+                &M::ROLE.into(),
                 &standard.to_raw().unwrap(),
                 1,
                 std::iter::once(key.public())
             ),
-            Err(Error::VerificationFailure(_))
+            Err(Error::MetadataMissingSignatures { role, number_of_valid_signatures: 0, threshold: 1 })
+            if role == M::ROLE.into()
         );
         assert_matches!(
-            verify_signatures(&custom.to_raw().unwrap(), 1, std::iter::once(key.public())),
-            Err(Error::VerificationFailure(_))
+            verify_signatures(
+                &M::ROLE.into(),
+                &custom.to_raw().unwrap(),
+                1,
+                std::iter::once(key.public())
+            ),
+            Err(Error::MetadataMissingSignatures { role, number_of_valid_signatures: 0, threshold: 1 })
+            if role == M::ROLE.into()
         );
     }
 
diff --git a/tuf/src/repo_builder.rs b/tuf/src/repo_builder.rs
index 0079145..acabab1 100644
--- a/tuf/src/repo_builder.rs
+++ b/tuf/src/repo_builder.rs
@@ -4,6 +4,7 @@
     crate::{
         crypto::{self, HashAlgorithm, PrivateKey},
         database::Database,
+        error::{Error, Result},
         interchange::DataInterchange,
         metadata::{
             Metadata, MetadataDescription, MetadataPath, MetadataVersion, RawSignedMetadata,
@@ -13,7 +14,6 @@
             TimestampMetadataBuilder,
         },
         repository::RepositoryStorage,
-        Error, Result,
     },
     chrono::{Duration, Utc},
     futures_io::{AsyncRead, AsyncSeek},
@@ -525,7 +525,7 @@
     {
         let next_version = if let Some(db) = self.ctx.db {
             db.trusted_root().version().checked_add(1).ok_or_else(|| {
-                Error::VerificationFailure("root version should be less than max u32".into())
+                Error::MetadataVersionMustBeSmallerThanMaxU32(MetadataPath::root())
             })?
         } else {
             1
@@ -707,7 +707,10 @@
         } else if let Some(db) = self.ctx.db {
             db.trusted_root().consistent_snapshot()
         } else {
-            return Err(Error::MissingMetadata(MetadataPath::root()));
+            return Err(Error::MetadataNotFound {
+                path: MetadataPath::root(),
+                version: MetadataVersion::None,
+            });
         };
 
         let target_description = TargetDescription::from_reader_with_custom(
@@ -762,7 +765,7 @@
         let next_version = if let Some(db) = self.ctx.db {
             if let Some(trusted_targets) = db.trusted_targets() {
                 trusted_targets.version().checked_add(1).ok_or_else(|| {
-                    Error::VerificationFailure("targets version should be less than max u32".into())
+                    Error::MetadataVersionMustBeSmallerThanMaxU32(MetadataPath::targets())
                 })?
             } else {
                 1
@@ -900,9 +903,7 @@
         let next_version = if let Some(db) = self.ctx.db {
             if let Some(trusted_snapshot) = db.trusted_snapshot() {
                 trusted_snapshot.version().checked_add(1).ok_or_else(|| {
-                    Error::VerificationFailure(
-                        "snapshot version should be less than max u32".into(),
-                    )
+                    Error::MetadataVersionMustBeSmallerThanMaxU32(MetadataPath::snapshot())
                 })?
             } else {
                 1
@@ -1062,9 +1063,7 @@
         let next_version = if let Some(db) = self.ctx.db {
             if let Some(trusted_timestamp) = db.trusted_timestamp() {
                 trusted_timestamp.version().checked_add(1).ok_or_else(|| {
-                    Error::VerificationFailure(
-                        "timestamp version should be less than max u32".into(),
-                    )
+                    Error::MetadataVersionMustBeSmallerThanMaxU32(MetadataPath::timestamp())
                 })?
             } else {
                 1
@@ -1080,7 +1079,10 @@
                 .db
                 .and_then(|db| db.trusted_timestamp())
                 .map(|timestamp| timestamp.snapshot().clone())
-                .ok_or_else(|| Error::MissingMetadata(MetadataPath::timestamp()))?
+                .ok_or_else(|| Error::MetadataNotFound {
+                    path: MetadataPath::timestamp(),
+                    version: MetadataVersion::None,
+                })?
         };
 
         let timestamp_builder = TimestampMetadataBuilder::from_metadata_description(description)
@@ -1211,7 +1213,10 @@
         } else if let Some(ref root) = self.state.staged_root {
             Database::from_trusted_root(&root.raw)?
         } else {
-            return Err(Error::MissingMetadata(MetadataPath::root()));
+            return Err(Error::MetadataNotFound {
+                path: MetadataPath::root(),
+                version: MetadataVersion::None,
+            });
         };
 
         let now = Utc::now();
@@ -1255,7 +1260,10 @@
         } else if let Some(db) = self.ctx.db {
             db.trusted_root().consistent_snapshot()
         } else {
-            return Err(Error::MissingMetadata(MetadataPath::root()));
+            return Err(Error::MetadataNotFound {
+                path: MetadataPath::root(),
+                version: MetadataVersion::None,
+            });
         };
 
         if let Some(ref targets) = self.state.staged_targets {
@@ -1954,9 +1962,14 @@
                     .trusted_timestamp_keys(&[&KEYS[0]])
                     .stage_root_with_builder(|builder| builder.version(3))
                     .unwrap()
-                    .commit().await,
-                Err(Error::VerificationFailure(s))
-                if &s == "Attempted to roll back root metadata at version 1 to 3."
+                    .commit()
+                    .await,
+                Err(Error::AttemptedMetadataRollBack {
+                    role,
+                    trusted_version: 1,
+                    new_version: 3,
+                })
+                if role == MetadataPath::root()
             );
         })
     }
diff --git a/tuf/src/repository.rs b/tuf/src/repository.rs
index 1fea3e3..a23f1bc 100644
--- a/tuf/src/repository.rs
+++ b/tuf/src/repository.rs
@@ -460,11 +460,11 @@
                     let target_path = target_path.with_hash_prefix(hash)?;
                     match self.repository.fetch_target(&target_path).await {
                         Ok(target) => break target,
-                        Err(Error::NotFound) => {}
+                        Err(Error::TargetNotFound(_)) => {}
                         Err(err) => return Err(err),
                     }
                 } else {
-                    return Err(Error::NotFound);
+                    return Err(Error::TargetNotFound(target_path.clone()));
                 }
             }
         } else {
@@ -532,7 +532,8 @@
                     vec![],
                 )
                 .await,
-                Err(Error::NotFound)
+                Err(Error::MetadataNotFound { path, version })
+                if path == MetadataPath::root() && version == MetadataVersion::None
             );
         });
     }
diff --git a/tuf/src/repository/ephemeral.rs b/tuf/src/repository/ephemeral.rs
index 938cbb8..32a8a77 100644
--- a/tuf/src/repository/ephemeral.rs
+++ b/tuf/src/repository/ephemeral.rs
@@ -59,7 +59,10 @@
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
         let bytes = match self.metadata.get(&(meta_path.clone(), version)) {
             Some(bytes) => Ok(bytes),
-            None => Err(Error::NotFound),
+            None => Err(Error::MetadataNotFound {
+                path: meta_path.clone(),
+                version,
+            }),
         };
         bytes_to_reader(bytes).boxed()
     }
@@ -70,7 +73,7 @@
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
         let bytes = match self.targets.get(target_path) {
             Some(bytes) => Ok(bytes),
-            None => Err(Error::NotFound),
+            None => Err(Error::TargetNotFound(target_path.clone())),
         };
         bytes_to_reader(bytes).boxed()
     }
@@ -157,7 +160,13 @@
         let bytes = if let Some(bytes) = self.staging_repo.metadata.get(&key) {
             Ok(bytes)
         } else {
-            self.parent_repo.metadata.get(&key).ok_or(Error::NotFound)
+            self.parent_repo
+                .metadata
+                .get(&key)
+                .ok_or_else(|| Error::MetadataNotFound {
+                    path: meta_path.clone(),
+                    version,
+                })
         };
         bytes_to_reader(bytes).boxed()
     }
@@ -172,7 +181,7 @@
             self.parent_repo
                 .targets
                 .get(target_path)
-                .ok_or(Error::NotFound)
+                .ok_or_else(|| Error::TargetNotFound(target_path.clone()))
         };
         bytes_to_reader(bytes).boxed()
     }
@@ -225,7 +234,7 @@
 
             let path = TargetPath::new("batty").unwrap();
             if let Err(err) = repo.fetch_target(&path).await {
-                assert_matches!(err, Error::NotFound);
+                assert_matches!(err, Error::TargetNotFound(p) if p == path);
             } else {
                 panic!("expected fetch_target to fail");
             }
diff --git a/tuf/src/repository/file_system.rs b/tuf/src/repository/file_system.rs
index 80c7651..6324200 100644
--- a/tuf/src/repository/file_system.rs
+++ b/tuf/src/repository/file_system.rs
@@ -6,14 +6,15 @@
 use log::debug;
 use std::collections::HashMap;
 use std::fs::{DirBuilder, File};
+use std::io;
 use std::marker::PhantomData;
 use std::path::{Path, PathBuf};
 use tempfile::{NamedTempFile, TempPath};
 
+use crate::error::{Error, Result};
 use crate::interchange::DataInterchange;
 use crate::metadata::{MetadataPath, MetadataVersion, TargetPath};
 use crate::repository::{RepositoryProvider, RepositoryStorage};
-use crate::Result;
 
 /// A builder to create a repository contained on the local file system.
 pub struct FileSystemRepositoryBuilder<D> {
@@ -131,11 +132,49 @@
         path
     }
 
-    fn fetch_path(
+    fn fetch_metadata_from_path(
         &self,
+        meta_path: &MetadataPath,
+        version: MetadataVersion,
         path: &Path,
     ) -> BoxFuture<'_, Result<Box<dyn AsyncRead + Send + Unpin + '_>>> {
-        let reader = File::open(&path);
+        let reader = File::open(&path).map_err(|err| {
+            if err.kind() == io::ErrorKind::NotFound {
+                Error::MetadataNotFound {
+                    path: meta_path.clone(),
+                    version,
+                }
+            } else {
+                Error::IoPath {
+                    path: path.to_path_buf(),
+                    err,
+                }
+            }
+        });
+
+        async move {
+            let reader = reader?;
+            let reader: Box<dyn AsyncRead + Send + Unpin> = Box::new(AllowStdIo::new(reader));
+            Ok(reader)
+        }
+        .boxed()
+    }
+
+    fn fetch_target_from_path(
+        &self,
+        target_path: &TargetPath,
+        path: &Path,
+    ) -> BoxFuture<'_, Result<Box<dyn AsyncRead + Send + Unpin + '_>>> {
+        let reader = File::open(&path).map_err(|err| {
+            if err.kind() == io::ErrorKind::NotFound {
+                Error::TargetNotFound(target_path.clone())
+            } else {
+                Error::IoPath {
+                    path: path.to_path_buf(),
+                    err,
+                }
+            }
+        });
 
         async move {
             let reader = reader?;
@@ -156,7 +195,7 @@
         version: MetadataVersion,
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
         let path = self.metadata_path(meta_path, version);
-        self.fetch_path(&path)
+        self.fetch_metadata_from_path(meta_path, version, &path)
     }
 
     fn fetch_target<'a>(
@@ -164,7 +203,7 @@
         target_path: &TargetPath,
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
         let path = self.target_path(target_path);
-        self.fetch_path(&path)
+        self.fetch_target_from_path(target_path, &path)
     }
 }
 
@@ -186,8 +225,16 @@
             }
 
             let mut temp_file = AllowStdIo::new(create_temp_file(&path)?);
-            copy(metadata, &mut temp_file).await?;
-            temp_file.into_inner().persist(&path)?;
+            if let Err(err) = copy(metadata, &mut temp_file).await {
+                return Err(Error::IoPath { path, err });
+            }
+            temp_file
+                .into_inner()
+                .persist(&path)
+                .map_err(|err| Error::IoPath {
+                    path,
+                    err: err.error,
+                })?;
 
             Ok(())
         }
@@ -207,8 +254,16 @@
             }
 
             let mut temp_file = AllowStdIo::new(create_temp_file(&path)?);
-            copy(read, &mut temp_file).await?;
-            temp_file.into_inner().persist(&path)?;
+            if let Err(err) = copy(read, &mut temp_file).await {
+                return Err(Error::IoPath { path, err });
+            }
+            temp_file
+                .into_inner()
+                .persist(&path)
+                .map_err(|err| Error::IoPath {
+                    path,
+                    err: err.error,
+                })?;
 
             Ok(())
         }
@@ -242,14 +297,17 @@
             if path.exists() {
                 debug!("Target path exists. Overwriting: {:?}", path);
             }
-            tmp_path.persist(path)?;
+            tmp_path.persist(&path).map_err(|err| Error::IoPath {
+                path,
+                err: err.error,
+            })?;
         }
 
         for (path, tmp_path) in self.metadata {
             if path.exists() {
                 debug!("Metadata path exists. Overwriting: {:?}", path);
             }
-            tmp_path.persist(path)?;
+            tmp_path.persist(path).map_err(|err| err.error)?;
         }
 
         Ok(())
@@ -267,9 +325,11 @@
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
         let path = self.repo.metadata_path(meta_path, version);
         if let Some(temp_path) = self.metadata.get(&path) {
-            self.repo.fetch_path(temp_path)
+            self.repo
+                .fetch_metadata_from_path(meta_path, version, temp_path)
         } else {
-            self.repo.fetch_path(&path)
+            self.repo
+                .fetch_metadata_from_path(meta_path, version, &path)
         }
     }
 
@@ -279,9 +339,9 @@
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
         let path = self.repo.target_path(target_path);
         if let Some(temp_path) = self.targets.get(&path) {
-            self.repo.fetch_path(temp_path)
+            self.repo.fetch_target_from_path(target_path, temp_path)
         } else {
-            self.repo.fetch_path(&path)
+            self.repo.fetch_target_from_path(target_path, &path)
         }
     }
 }
@@ -301,7 +361,9 @@
 
         async move {
             let mut temp_file = AllowStdIo::new(create_temp_file(&path)?);
-            copy(read, &mut temp_file).await?;
+            if let Err(err) = copy(read, &mut temp_file).await {
+                return Err(Error::IoPath { path, err });
+            }
             metadata.insert(path, temp_file.into_inner().into_temp_path());
 
             Ok(())
@@ -319,7 +381,9 @@
 
         async move {
             let mut temp_file = AllowStdIo::new(create_temp_file(&path)?);
-            copy(read, &mut temp_file).await?;
+            if let Err(err) = copy(read, &mut temp_file).await {
+                return Err(Error::IoPath { path, err });
+            }
             targets.insert(path, temp_file.into_inner().into_temp_path());
 
             Ok(())
@@ -335,10 +399,22 @@
     // non-atomically copying the file to another mountpoint.
 
     if let Some(parent) = path.parent() {
-        DirBuilder::new().recursive(true).create(parent)?;
-        Ok(NamedTempFile::new_in(parent)?)
+        DirBuilder::new()
+            .recursive(true)
+            .create(parent)
+            .map_err(|err| Error::IoPath {
+                path: parent.to_path_buf(),
+                err,
+            })?;
+        Ok(NamedTempFile::new_in(parent).map_err(|err| Error::IoPath {
+            path: parent.to_path_buf(),
+            err,
+        })?)
     } else {
-        Ok(NamedTempFile::new_in(".")?)
+        Ok(NamedTempFile::new_in(".").map_err(|err| Error::IoPath {
+            path: path.to_path_buf(),
+            err,
+        })?)
     }
 }
 
@@ -374,7 +450,11 @@
                         vec![],
                     )
                     .await,
-                Err(Error::NotFound)
+                Err(Error::MetadataNotFound {
+                    path,
+                    version,
+                })
+                if path == MetadataPath::root() && version == MetadataVersion::None
             );
         })
     }
diff --git a/tuf/src/repository/http.rs b/tuf/src/repository/http.rs
index cefb139..d4a53ec 100644
--- a/tuf/src/repository/http.rs
+++ b/tuf/src/repository/http.rs
@@ -147,7 +147,8 @@
 const URLENCODE_PATH: &percent_encoding::AsciiSet =
     &URLENCODE_FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
 
-fn extend_uri(uri: Uri, prefix: &Option<Vec<String>>, components: &[String]) -> Result<Uri> {
+fn extend_uri(uri: &Uri, prefix: &Option<Vec<String>>, components: &[String]) -> Result<Uri> {
+    let uri = uri.clone();
     let mut uri_parts = uri.into_parts();
 
     let (path, query) = match &uri_parts.path_and_query {
@@ -207,31 +208,23 @@
     C: Connect + Clone + Send + Sync + 'static,
     D: DataInterchange,
 {
-    async fn get<'a>(
-        &'a self,
-        prefix: &'a Option<Vec<String>>,
-        components: &'a [String],
-    ) -> Result<Response<Body>> {
-        let base_uri = self.uri.clone();
-        let uri = extend_uri(base_uri, prefix, components)?;
-
-        let req = Request::builder()
-            .uri(&uri)
+    async fn get<'a>(&'a self, uri: &Uri) -> Result<Response<Body>> {
+        match Request::builder()
+            .uri(uri)
             .header("User-Agent", &*self.user_agent)
-            .body(Body::default())?;
-
-        let resp = self.client.request(req).await?;
-        let status = resp.status();
-
-        if status == StatusCode::OK {
-            Ok(resp)
-        } else if status == StatusCode::NOT_FOUND {
-            Err(Error::NotFound)
-        } else {
-            Err(Error::BadHttpStatus {
-                code: status,
+            .body(Body::default())
+        {
+            Ok(req) => match self.client.request(req).await {
+                Ok(resp) => Ok(resp),
+                Err(err) => Err(Error::Hyper {
+                    uri: uri.to_string(),
+                    err,
+                }),
+            },
+            Err(err) => Err(Error::Http {
                 uri: uri.to_string(),
-            })
+                err,
+            }),
         }
     }
 }
@@ -246,20 +239,37 @@
         meta_path: &MetadataPath,
         version: MetadataVersion,
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
+        let meta_path = meta_path.clone();
         let components = meta_path.components::<D>(version);
-        async move {
-            let resp = self.get(&self.metadata_prefix, &components).await?;
+        let uri = extend_uri(&self.uri, &self.metadata_prefix, &components);
 
+        async move {
             // TODO(#278) check content length if known and fail early if the payload is too large.
 
-            let reader = resp
-                .into_body()
-                .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
-                .into_async_read()
-                .enforce_minimum_bitrate(self.min_bytes_per_second);
+            let uri = uri?;
+            let resp = self.get(&uri).await?;
 
-            let reader: Box<dyn AsyncRead + Send + Unpin> = Box::new(reader);
-            Ok(reader)
+            let status = resp.status();
+            if status == StatusCode::OK {
+                let reader = resp
+                    .into_body()
+                    .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
+                    .into_async_read()
+                    .enforce_minimum_bitrate(self.min_bytes_per_second);
+
+                let reader: Box<dyn AsyncRead + Send + Unpin> = Box::new(reader);
+                Ok(reader)
+            } else if status == StatusCode::NOT_FOUND {
+                Err(Error::MetadataNotFound {
+                    path: meta_path,
+                    version,
+                })
+            } else {
+                Err(Error::BadHttpStatus {
+                    uri: uri.to_string(),
+                    code: status,
+                })
+            }
         }
         .boxed()
     }
@@ -268,19 +278,34 @@
         &'a self,
         target_path: &TargetPath,
     ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
+        let target_path = target_path.clone();
         let components = target_path.components();
+        let uri = extend_uri(&self.uri, &self.targets_prefix, &components);
 
         async move {
             // TODO(#278) check content length if known and fail early if the payload is too large.
-            let resp = self.get(&self.targets_prefix, &components).await?;
 
-            let reader = resp
-                .into_body()
-                .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
-                .into_async_read()
-                .enforce_minimum_bitrate(self.min_bytes_per_second);
+            let uri = uri?;
+            let resp = self.get(&uri).await?;
 
-            Ok(Box::new(reader) as Box<dyn AsyncRead + Send + Unpin>)
+            let status = resp.status();
+            if status == StatusCode::OK {
+                let reader = resp
+                    .into_body()
+                    .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
+                    .into_async_read()
+                    .enforce_minimum_bitrate(self.min_bytes_per_second);
+
+                let reader: Box<dyn AsyncRead + Send + Unpin> = Box::new(reader);
+                Ok(reader)
+            } else if status == StatusCode::NOT_FOUND {
+                Err(Error::TargetNotFound(target_path))
+            } else {
+                Err(Error::BadHttpStatus {
+                    uri: uri.to_string(),
+                    code: status,
+                })
+            }
         }
         .boxed()
     }
@@ -319,7 +344,7 @@
         ];
 
         let uri = base_uri.parse::<Uri>().unwrap();
-        let extended_uri = extend_uri(uri, &prefix, &components).unwrap();
+        let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
 
         let url =
             http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
@@ -338,7 +363,7 @@
         let prefix = Some(vec![String::from("prefix")]);
         let components = [String::from("chars to encode#?")];
         let uri = base_uri.parse::<Uri>().unwrap();
-        let extended_uri = extend_uri(uri, &prefix, &components)
+        let extended_uri = extend_uri(&uri, &prefix, &components)
             .expect("correctly generated a URI with a zone id");
 
         let url =
@@ -359,7 +384,7 @@
         let components = [];
 
         let uri = base_uri.parse::<Uri>().unwrap();
-        let extended_uri = extend_uri(uri, &prefix, &components).unwrap();
+        let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
 
         let url =
             http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
@@ -379,7 +404,7 @@
         ];
 
         let uri = base_uri.parse::<Uri>().unwrap();
-        let extended_uri = extend_uri(uri, &prefix, &components).unwrap();
+        let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
 
         let url =
             http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
@@ -402,7 +427,7 @@
         ];
 
         let uri = base_uri.parse::<Uri>().unwrap();
-        let extended_uri = extend_uri(uri, &prefix, &components).unwrap();
+        let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
 
         let url =
             http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
@@ -424,7 +449,7 @@
             String::from("components_two"),
         ];
         let uri = base_uri.parse::<Uri>().unwrap();
-        let extended_uri = extend_uri(uri, &prefix, &components)
+        let extended_uri = extend_uri(&uri, &prefix, &components)
             .expect("correctly generated a URI with a zone id");
         assert_eq!(
             extended_uri.to_string(),
diff --git a/tuf/src/verify.rs b/tuf/src/verify.rs
index 99e3457..09c3653 100644
--- a/tuf/src/verify.rs
+++ b/tuf/src/verify.rs
@@ -7,8 +7,7 @@
 use crate::crypto::{KeyId, PublicKey, Signature};
 use crate::error::Error;
 use crate::interchange::DataInterchange;
-use crate::metadata::{Metadata, RawSignedMetadata};
-use crate::Result;
+use crate::metadata::{Metadata, MetadataPath, RawSignedMetadata};
 
 /// `Verified` is a wrapper type that signifies the inner type has had it's signature verified.
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -38,7 +37,7 @@
 /// # use chrono::prelude::*;
 /// # use tuf::crypto::{Ed25519PrivateKey, PrivateKey, SignatureScheme, HashAlgorithm};
 /// # use tuf::interchange::Json;
-/// # use tuf::metadata::{SnapshotMetadataBuilder, SignedMetadata};
+/// # use tuf::metadata::{MetadataPath, SnapshotMetadataBuilder, SignedMetadata};
 /// # use tuf::verify::verify_signatures;
 ///
 /// let key_1: &[u8] = include_bytes!("../tests/ed25519/ed25519-1.pk8.der");
@@ -53,30 +52,49 @@
 ///     .to_raw()
 ///     .unwrap();
 ///
-/// assert!(verify_signatures(&raw_snapshot, 1, vec![key_1.public()]).is_ok());
+/// assert!(verify_signatures(
+///     &MetadataPath::snapshot(),
+///     &raw_snapshot,
+///     1,
+///     vec![key_1.public()],
+/// ).is_ok());
 ///
 /// // fail with increased threshold
-/// assert!(verify_signatures(&raw_snapshot, 2, vec![key_1.public()]).is_err());
+/// assert!(verify_signatures(
+///     &MetadataPath::snapshot(),
+///     &raw_snapshot,
+///     2,
+///     vec![key_1.public()],
+/// ).is_err());
 ///
 /// // fail when the keys aren't authorized
-/// assert!(verify_signatures(&raw_snapshot, 1, vec![key_2.public()]).is_err());
+/// assert!(verify_signatures(
+///     &MetadataPath::snapshot(),
+///     &raw_snapshot,
+///     1,
+///     vec![key_2.public()],
+/// ).is_err());
 ///
 /// // fail when the keys don't exist
-/// assert!(verify_signatures(&raw_snapshot, 1, &[]).is_err());
+/// assert!(verify_signatures(
+///     &MetadataPath::snapshot(),
+///     &raw_snapshot,
+///     1,
+///     &[],
+/// ).is_err());
 pub fn verify_signatures<'a, D, M, I>(
+    role: &MetadataPath,
     raw_metadata: &RawSignedMetadata<D, M>,
     threshold: u32,
     authorized_keys: I,
-) -> Result<Verified<M>>
+) -> Result<Verified<M>, Error>
 where
     D: DataInterchange,
     M: Metadata,
     I: IntoIterator<Item = &'a PublicKey>,
 {
     if threshold < 1 {
-        return Err(Error::VerificationFailure(
-            "Threshold must be strictly greater than zero".into(),
-        ));
+        return Err(Error::MetadataThresholdMustBeGreaterThanZero(role.clone()));
     }
 
     let authorized_keys = authorized_keys
@@ -94,12 +112,6 @@
 
         let unverified: SignedMetadata<D> = D::from_slice(raw_metadata.as_bytes())?;
 
-        if unverified.signatures.is_empty() {
-            return Err(Error::VerificationFailure(
-                "The metadata was not signed with any authorized keys.".into(),
-            ));
-        }
-
         let canonical_bytes = D::canonicalize(&unverified.signed)?;
         (unverified.signatures, canonical_bytes)
     };
@@ -114,7 +126,7 @@
 
     for (key_id, sig) in signatures {
         match authorized_keys.get(key_id) {
-            Some(pub_key) => match pub_key.verify(&canonical_bytes, sig) {
+            Some(pub_key) => match pub_key.verify(role, &canonical_bytes, sig) {
                 Ok(()) => {
                     debug!("Good signature from key ID {:?}", pub_key.key_id());
                     signatures_needed -= 1;
@@ -136,11 +148,11 @@
     }
 
     if signatures_needed > 0 {
-        return Err(Error::VerificationFailure(format!(
-            "Signature threshold not met: {}/{}",
-            threshold - signatures_needed,
-            threshold
-        )));
+        return Err(Error::MetadataMissingSignatures {
+            role: role.clone(),
+            number_of_valid_signatures: threshold - signatures_needed,
+            threshold,
+        });
     }
 
     // Everything looks good so deserialize the metadata.
diff --git a/tuf/tests/integration.rs b/tuf/tests/integration.rs
index c633c7d..019a7ec 100644
--- a/tuf/tests/integration.rs
+++ b/tuf/tests/integration.rs
@@ -295,12 +295,18 @@
                 &MetadataPath::new("delegation").unwrap(),
                 &raw_delegation
             ),
-            Err(Error::VerificationFailure(_))
+            Err(Error::MetadataMissingSignatures {
+                role,
+                number_of_valid_signatures: 0,
+                threshold: 1,
+            })
+            if role == MetadataPath::new("delegation").unwrap()
         );
 
+        let target_path = TargetPath::new("foo").unwrap();
         assert_matches!(
-            tuf.target_description(&TargetPath::new("foo").unwrap()),
-            Err(Error::TargetUnavailable)
+            tuf.target_description(&target_path),
+            Err(Error::TargetNotFound(p)) if p == target_path
         );
     })
 }
@@ -511,7 +517,12 @@
                 &MetadataPath::new("delegation-c").unwrap(),
                 &raw_delegation_c
             ),
-            Err(Error::VerificationFailure(_))
+            Err(Error::MetadataMissingSignatures {
+                role,
+                number_of_valid_signatures: 0,
+                threshold: 1,
+            })
+            if role == MetadataPath::new("delegation-c").unwrap()
         );
 
         tuf.update_delegated_targets(
@@ -526,9 +537,10 @@
             .target_description(&TargetPath::new("foo").unwrap())
             .is_ok());
 
+        let target_path = TargetPath::new("bar").unwrap();
         assert_matches!(
-            tuf.target_description(&TargetPath::new("bar").unwrap()),
-            Err(Error::TargetUnavailable)
+            tuf.target_description(&target_path),
+            Err(Error::TargetNotFound(p)) if p == target_path
         );
     })
 }