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
);
})
}