Skip to content

Commit

Permalink
Merge pull request #412 from arnaldo2792/add-force-flag
Browse files Browse the repository at this point in the history
Add ignore threshold flag
  • Loading branch information
tjkirch authored Sep 14, 2021
2 parents 9c060dd + bf8383b commit d15f687
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 15 deletions.
54 changes: 41 additions & 13 deletions tuftool/src/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::error::{self, Result};
use crate::source::parse_key_source;
use crate::{load_file, write_file};
use chrono::{DateTime, Timelike, Utc};
use log::warn;
use maplit::hashmap;
use ring::rand::SystemRandom;
use snafu::{ensure, OptionExt, ResultExt};
Expand Down Expand Up @@ -106,6 +107,9 @@ pub(crate) enum Command {
///Optional - Path of older root.json that contains the key-id
#[structopt(short = "c", long = "cross-sign")]
cross_sign: Option<PathBuf>,
///Ignore the threshold when signing with fewer keys
#[structopt(short = "i", long = "ignore-threshold")]
ignore_threshold: bool,
},
}

Expand Down Expand Up @@ -153,7 +157,8 @@ impl Command {
path,
key_sources,
cross_sign,
} => Command::sign(&path, &key_sources, cross_sign),
ignore_threshold,
} => Command::sign(&path, &key_sources, cross_sign, ignore_threshold),
}
}

Expand Down Expand Up @@ -302,14 +307,15 @@ impl Command {
path: &Path,
key_source: &[Box<dyn KeySource>],
cross_sign: Option<PathBuf>,
ignore_threshold: bool,
) -> Result<()> {
let root: Signed<Root> = load_file(path)?;
// get the root based on cross-sign
let loaded_root = match cross_sign {
None => root.clone(),
Some(cross_sign_root) => load_file(&cross_sign_root)?,
};
//sign the root
// sign the root
let mut signed_root = SignedRole::new(
root.signed.clone(),
&KeyHolder::Root(loaded_root.signed),
Expand All @@ -325,14 +331,27 @@ impl Command {
.context(error::SignRoot { path })?;
}

// Quick check that root is signed by enough key IDs
// Quick check that root is signed by enough key IDs, in all its roles.
for (roletype, rolekeys) in &signed_root.signed().signed.roles {
if rolekeys.threshold.get() > rolekeys.keyids.len() as u64 {
return Err(error::Error::UnstableRoot {
role: *roletype,
threshold: rolekeys.threshold.get(),
actual: rolekeys.keyids.len(),
});
let threshold = rolekeys.threshold.get();
let keyids = rolekeys.keyids.len();
if threshold > keyids as u64 {
// Return an error when the referenced root.json isn't compliant with the
// threshold. The referenced file could be a root.json used for cross signing,
// which wasn't signed with enough keys.
if !ignore_threshold {
return Err(error::Error::UnstableRoot {
role: *roletype,
threshold,
actual: keyids,
});
}
// Print out a warning to let the user know that the referenced root.json
// file isn't compliant with the threshold specified for the role type.
warn!(
"Loaded unstable root, role '{}' contains '{}' keys, expected '{}'",
*roletype, threshold, keyids
);
}
}

Expand All @@ -352,10 +371,19 @@ impl Command {
.get();
let signature_count = signed_root.signed().signatures.len();
if threshold > signature_count as u64 {
return Err(error::Error::SignatureRoot {
threshold,
signature_count,
});
// Return an error when the "ignore-threshold" flag wasn't set
if !ignore_threshold {
return Err(error::Error::SignatureRoot {
threshold,
signature_count,
});
}
// Print out a warning letting the user know that the target file isn't compliant with
// the threshold used for the root role.
warn!(
"The root.json file requires at least {} signatures, the target file contains {}",
threshold, signature_count
);
}

// Use `tempfile::NamedTempFile::persist` to perform an atomic file write.
Expand Down
31 changes: 29 additions & 2 deletions tuftool/tests/root_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn initialize_root_json(root_json: &str) {
.success();
Command::cargo_bin("tuftool")
.unwrap()
.args(&["root", "set-threshold", root_json, "root", "1"])
.args(&["root", "set-threshold", root_json, "root", "2"])
.assert()
.success();
Command::cargo_bin("tuftool")
Expand Down Expand Up @@ -84,11 +84,21 @@ fn add_key_all_roles(key: &str, root_json: &str) {
fn sign_root_json(key: &str, root_json: &str) {
Command::cargo_bin("tuftool")
.unwrap()
.args(&["root", "sign", root_json, "-k", key])
// We don't have enough signatures to meet the threshold, so we have to pass `-i`
.args(&["root", "sign", root_json, "-i", "-k", key])
.assert()
.success();
}

fn sign_root_json_failure(key: &str, root_json: &str) {
Command::cargo_bin("tuftool")
.unwrap()
// We don't have enough signatures to meet the threshold, so we should fail
.args(&["root", "sign", root_json, "-k", key])
.assert()
.failure();
}

fn sign_root_json_two_keys(key_1: &str, key_2: &str, root_json: &str) {
Command::cargo_bin("tuftool")
.unwrap()
Expand All @@ -104,6 +114,7 @@ fn cross_sign(old_root: &str, new_root: &str, key: &str) {
"root",
"sign",
new_root,
"-i",
"-k",
key,
"--cross-sign",
Expand Down Expand Up @@ -310,6 +321,22 @@ fn append_signature_root() {
assert_eq!(get_sign_len(root_json.to_str().unwrap()), 2);
}

#[test]
fn below_threshold_failure() {
let out_dir = TempDir::new().unwrap();
let root_json = out_dir.path().join("root.json");
let key_1 = test_utils::test_data().join("snakeoil.pem");
let key_2 = test_utils::test_data().join("snakeoil_2.pem");
// Create and initialise root.json
initialize_root_json(root_json.to_str().unwrap());
// Add key_1 for all roles
add_key_all_roles(key_1.to_str().unwrap(), root_json.to_str().unwrap());
// Add key_2 to root
add_key_root(key_2.to_str().unwrap(), root_json.to_str().unwrap());
// Sign root.json with key_1 fails, when no `--ignore-threshold` is passed
sign_root_json_failure(key_1.to_str().unwrap(), root_json.to_str().unwrap());
}

#[test]
fn set_version_root() {
let out_dir = TempDir::new().unwrap();
Expand Down

0 comments on commit d15f687

Please sign in to comment.