Skip to content

Commit

Permalink
tuftool: Allow providing multiple keys to root add-key
Browse files Browse the repository at this point in the history
This command is often used to add multiple keys to a role. That
currently means calling the command multiple times, once for each key.

Since this is a common scenario, this changes the subcommand to allow
providing multiple keys are part of one invocation.

Signed-off-by: Sean McGinnis <[email protected]>
  • Loading branch information
stmcginnis committed Aug 7, 2023
1 parent 532596d commit facccaf
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 39 deletions.
38 changes: 23 additions & 15 deletions tuftool/src/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ pub(crate) enum Command {
/// Version number
version: NonZeroU64,
},
/// Add a key (public or private) to a role
/// Add one or more keys (public or private) to a role
AddKey {
/// Path to root.json
path: PathBuf,
/// The new key
#[clap(parse(try_from_str = parse_key_source))]
key_source: Box<dyn KeySource>,
/// The new key to be added
#[clap(short = 'k', long = "key", parse(try_from_str = parse_key_source))]
key_source: Vec<Box<dyn KeySource>>,
/// The role to add the key to
#[clap(short = 'r', long = "role")]
roles: Vec<RoleType>,
Expand Down Expand Up @@ -101,13 +101,13 @@ pub(crate) enum Command {
Sign {
/// Path to root.json
path: PathBuf,
///Key source(s) to sign the file with
#[clap(short = 'k', long = "key",parse(try_from_str = parse_key_source))]
/// Key source(s) to sign the file with
#[clap(short = 'k', long = "key", parse(try_from_str = parse_key_source))]
key_sources: Vec<Box<dyn KeySource>>,
///Optional - Path of older root.json that contains the key-id
/// Optional - Path of older root.json that contains the key-id
#[clap(short = 'c', long = "cross-sign")]
cross_sign: Option<PathBuf>,
///Ignore the threshold when signing with fewer keys
/// Ignore the threshold when signing with fewer keys
#[clap(short = 'i', long = "ignore-threshold")]
ignore_threshold: bool,
},
Expand Down Expand Up @@ -225,15 +225,23 @@ impl Command {
}

#[allow(clippy::borrowed_box)]
fn add_key(path: &Path, roles: &[RoleType], key_source: &Box<dyn KeySource>) -> Result<()> {
fn add_key(
path: &Path,
roles: &[RoleType],
key_source: &Vec<Box<dyn KeySource>>,
) -> Result<()> {
let mut root: Signed<Root> = load_file(path)?;
let key_pair = key_source
.as_sign()
.context(error::KeyPairFromKeySourceSnafu)?
.tuf_key();
let key_id = hex::encode(add_key(&mut root.signed, roles, key_pair)?);
clear_sigs(&mut root);
println!("{key_id}");

for ks in key_source {
let key_pair = ks
.as_sign()
.context(error::KeyPairFromKeySourceSnafu)?
.tuf_key();
let key_id = hex::encode(add_key(&mut root.signed, roles, key_pair)?);
println!("{key_id}");
}

write_file(path, &root)
}

Expand Down
91 changes: 67 additions & 24 deletions tuftool/tests/root_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,56 @@ fn initialize_root_json(root_json: &str) {
.success();
}

fn add_key_root(key: &str, root_json: &str) {
Command::cargo_bin("tuftool")
.unwrap()
.args(["root", "add-key", root_json, key, "--role", "root"])
.assert()
.success();
fn add_key_root(keys: &Vec<&str>, root_json: &str) {
let mut cmd = Command::cargo_bin("tuftool").unwrap();

cmd.args(["root", "add-key", root_json, "--role", "root"]);

for key in keys {
cmd.args(["-k", key]);
}

cmd.assert().success();
}

fn add_key_timestamp(key: &str, root_json: &str) {
Command::cargo_bin("tuftool")
.unwrap()
.args(["root", "add-key", root_json, key, "--role", "timestamp"])
.args([
"root",
"add-key",
root_json,
"-k",
key,
"--role",
"timestamp",
])
.assert()
.success();
}

fn add_key_snapshot(key: &str, root_json: &str) {
Command::cargo_bin("tuftool")
.unwrap()
.args(["root", "add-key", root_json, key, "--role", "snapshot"])
.args([
"root", "add-key", root_json, "-k", key, "--role", "snapshot",
])
.assert()
.success();
}
fn add_key_targets(key: &str, root_json: &str) {
Command::cargo_bin("tuftool")
.unwrap()
.args(["root", "add-key", root_json, key, "--role", "targets"])
.args(["root", "add-key", root_json, "-k", key, "--role", "targets"])
.assert()
.success();
}

fn add_key_all_roles(key: &str, root_json: &str) {
add_key_root(key, root_json);
fn add_keys_all_roles(keys: Vec<&str>, root_json: &str) {
add_key_root(&keys, root_json);

// Only add the first key for the rest until we have tests that want it for all keys
let key = keys.get(0).unwrap();
add_key_timestamp(key, root_json);
add_key_snapshot(key, root_json);
add_key_targets(key, root_json);
Expand Down Expand Up @@ -163,9 +180,9 @@ fn create_root() {
// Create and initialise root.json
initialize_root_json(root_json.to_str().unwrap());
// Add keys for all roles
add_key_all_roles(key_1.to_str().unwrap(), root_json.to_str().unwrap());
add_keys_all_roles(vec![key_1.to_str().unwrap()], root_json.to_str().unwrap());
// Add second key for root role
add_key_root(key_2.to_str().unwrap(), root_json.to_str().unwrap());
add_key_root(&vec![key_2.to_str().unwrap()], root_json.to_str().unwrap());
// Sign root.json with 1 key
sign_root_json_two_keys(
key_1.to_str().unwrap(),
Expand Down Expand Up @@ -197,7 +214,7 @@ fn create_unstable_root() {
.assert()
.success();
// Add keys for all roles
add_key_all_roles(key.to_str().unwrap(), root_json.to_str().unwrap());
add_keys_all_roles(vec![key.to_str().unwrap()], root_json.to_str().unwrap());
// Sign root.json (error because targets can never be validated root has 1 key but targets requires 2 signatures)
Command::cargo_bin("tuftool")
.unwrap()
Expand All @@ -222,7 +239,7 @@ fn create_invalid_root() {
// Create and initialise root.json
initialize_root_json(root_json.to_str().unwrap());
// Add keys for all roles
add_key_all_roles(key.to_str().unwrap(), root_json.to_str().unwrap());
add_keys_all_roles(vec![key.to_str().unwrap()], root_json.to_str().unwrap());
// Sign root.json (error because key is not valid)
Command::cargo_bin("tuftool")
.unwrap()
Expand Down Expand Up @@ -258,8 +275,8 @@ fn cross_sign_root() {
// Create and initialise root.json
initialize_root_json(new_root_json.to_str().unwrap());
// Add keys for all roles
add_key_all_roles(
new_root_key.to_str().unwrap(),
add_keys_all_roles(
vec![new_root_key.to_str().unwrap()],
new_root_json.to_str().unwrap(),
);
//Sign 2.root.json with key from 1.root.json
Expand All @@ -274,7 +291,7 @@ fn cross_sign_root() {
));
}

//cross-signing new_root.json with invalid key ( key not present in old_root.json )
// cross-signing new_root.json with invalid key ( key not present in old_root.json )
#[test]
fn cross_sign_root_invalid_key() {
let out_dir = TempDir::new().unwrap();
Expand All @@ -287,8 +304,11 @@ fn cross_sign_root_invalid_key() {
// Create and initialise root.json
initialize_root_json(new_root_json.to_str().unwrap());
// Add keys for all roles
add_key_all_roles(root_key.to_str().unwrap(), new_root_json.to_str().unwrap());
//Sign 2.root.json with key not in 1.root.json
add_keys_all_roles(
vec![root_key.to_str().unwrap()],
new_root_json.to_str().unwrap(),
);
// Sign 2.root.json with key not in 1.root.json
Command::cargo_bin("tuftool")
.unwrap()
.args([
Expand All @@ -314,9 +334,32 @@ fn append_signature_root() {
// 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_keys_all_roles(vec![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());
add_key_root(&vec![key_2.to_str().unwrap()], root_json.to_str().unwrap());
// Sign root.json with key_1
sign_root_json(key_1.to_str().unwrap(), root_json.to_str().unwrap());
// Sign root.json with key_2
sign_root_json(key_2.to_str().unwrap(), root_json.to_str().unwrap());

//validate number of signatures
assert_eq!(get_sign_len(root_json.to_str().unwrap()), 2);
}

#[test]
fn add_multiple_keys_root() {
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 and key_2 for all roles
add_keys_all_roles(
vec![key_1.to_str().unwrap(), key_2.to_str().unwrap()],
root_json.to_str().unwrap(),
);
// Sign root.json with key_1
sign_root_json(key_1.to_str().unwrap(), root_json.to_str().unwrap());
// Sign root.json with key_2
Expand All @@ -335,9 +378,9 @@ fn below_threshold_failure() {
// 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_keys_all_roles(vec![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());
add_key_root(&vec![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());
}
Expand Down

0 comments on commit facccaf

Please sign in to comment.