diff --git a/Cargo.lock b/Cargo.lock index 784912972..af1bee46a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2616,6 +2616,7 @@ dependencies = [ "log", "maplit", "olpc-cjson", + "openssl", "pem", "percent-encoding", "reqwest", diff --git a/tough-ssm/src/lib.rs b/tough-ssm/src/lib.rs index 81356a6a4..c46c67606 100644 --- a/tough-ssm/src/lib.rs +++ b/tough-ssm/src/lib.rs @@ -48,7 +48,7 @@ impl KeySource for SsmKeySource { })? .as_bytes() .to_vec(); - let sign = Box::new(parse_keypair(&data).context(error::KeyPairParseSnafu)?); + let sign = Box::new(parse_keypair(&data,None).context(error::KeyPairParseSnafu)?); Ok(sign) } diff --git a/tough/Cargo.toml b/tough/Cargo.toml index 8fe4dc51a..a2aebbef9 100644 --- a/tough/Cargo.toml +++ b/tough/Cargo.toml @@ -35,6 +35,7 @@ typed-path = "0.9" untrusted = "0.9" url = "2" walkdir = "2" +openssl = "0.10" [dev-dependencies] failure-server = { path = "../integ/failure-server" } diff --git a/tough/src/editor/test.rs b/tough/src/editor/test.rs index 9d76e5bbe..9aa7417a7 100644 --- a/tough/src/editor/test.rs +++ b/tough/src/editor/test.rs @@ -54,7 +54,7 @@ mod tests { #[tokio::test] async fn empty_repository() { let root_key = key_path(); - let key_source = LocalKeySource { path: root_key }; + let key_source = LocalKeySource { path: root_key,password: None }; let root_path = root_path(); let editor = RepositoryEditor::new(root_path).await.unwrap(); @@ -112,7 +112,7 @@ mod tests { async fn complete_repository() { let root = root_path(); let root_key = key_path(); - let key_source = LocalKeySource { path: root_key }; + let key_source = LocalKeySource { path: root_key, password: None }; let timestamp_expiration = Utc::now().checked_add_signed(days(3)).unwrap(); let timestamp_version = NonZeroU64::new(1234).unwrap(); let snapshot_expiration = Utc::now().checked_add_signed(days(21)).unwrap(); diff --git a/tough/src/key_source.rs b/tough/src/key_source.rs index b556f5fb7..3f8d28b97 100644 --- a/tough/src/key_source.rs +++ b/tough/src/key_source.rs @@ -33,6 +33,8 @@ pub trait KeySource: Debug + Send + Sync { pub struct LocalKeySource { /// The path to a local key file in PEM pkcs8 or RSA format. pub path: PathBuf, + /// Optional password for the key file. + pub password: Option, } /// Implements the `KeySource` trait for a `LocalKeySource` (file) @@ -44,7 +46,8 @@ impl KeySource for LocalKeySource { let data = tokio::fs::read(&self.path) .await .context(error::FileReadSnafu { path: &self.path })?; - Ok(Box::new(parse_keypair(&data)?)) + let password: Option<&str> = self.password.as_deref(); + Ok(Box::new(parse_keypair(&data,password)?)) } async fn write( diff --git a/tough/src/sign.rs b/tough/src/sign.rs index a5bdf521b..0bb965a6d 100644 --- a/tough/src/sign.rs +++ b/tough/src/sign.rs @@ -14,7 +14,9 @@ use ring::signature::{EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair}; use snafu::ResultExt; use std::collections::HashMap; use std::error::Error; - +use openssl::rsa::Rsa; +use openssl::pkey::PKey; +use std::str; /// This trait must be implemented for each type of key with which you will /// sign things. #[async_trait] @@ -161,19 +163,39 @@ impl Sign for SignKeyPair { } } +/// Decrypts an RSA private key in PEM format using the given password. +/// Returns the decrypted key in PKCS8 format. +pub fn decrypt_key(encrypted_key: &[u8], password: &str) -> std::result::Result, Box> { + + let pem_str = str::from_utf8(encrypted_key)?; + let rsa = Rsa::private_key_from_pem_passphrase(pem_str.as_bytes(), password.as_bytes())?; + + let pkey = PKey::from_rsa(rsa)?; + let pkcs8 = pkey.private_key_to_pem_pkcs8()?; + Ok(pkcs8) +} + /// Parses a supplied keypair and if it is recognized, returns an object that /// implements the Sign trait /// Accepted Keys: ED25519 pkcs8, Ecdsa pkcs8, RSA -pub fn parse_keypair(key: &[u8]) -> Result { - if let Ok(ed25519_key_pair) = Ed25519KeyPair::from_pkcs8(key) { +pub fn parse_keypair(key: &[u8], password: Option<&str>) -> Result { + + let decrypted_key = if let Some(pw) = password { + decrypt_key(key, pw).unwrap_or_else(|_| key.to_vec()) + } else { + key.to_vec() + }; + let decrypted_key_slice: &[u8] = &decrypted_key; + + if let Ok(ed25519_key_pair) = Ed25519KeyPair::from_pkcs8(decrypted_key_slice) { Ok(SignKeyPair::ED25519(ed25519_key_pair)) } else if let Ok(ecdsa_key_pair) = EcdsaKeyPair::from_pkcs8( &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING, - key, + decrypted_key_slice, &rand::SystemRandom::new(), ) { Ok(SignKeyPair::ECDSA(ecdsa_key_pair)) - } else if let Ok(pem) = pem::parse(key) { + } else if let Ok(pem) = pem::parse(decrypted_key_slice) { match pem.tag() { "PRIVATE KEY" => { if let Ok(rsa_key_pair) = RsaKeyPair::from_pkcs8(pem.contents()) { diff --git a/tough/tests/repo_editor.rs b/tough/tests/repo_editor.rs index ba0972a69..baf7297b0 100644 --- a/tough/tests/repo_editor.rs +++ b/tough/tests/repo_editor.rs @@ -153,14 +153,16 @@ async fn create_sign_write_reload_repo() { .unwrap(); let targets_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = - &[Box::new(LocalKeySource { path: key_path() })]; + &[Box::new(LocalKeySource { path: key_path(), password: None })]; let role1_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = &[Box::new(LocalKeySource { path: targets_key_path(), + password: None, })]; let role2_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = &[Box::new(LocalKeySource { path: targets_key_path1(), + password: None, })]; // add role1 to targets @@ -257,14 +259,16 @@ async fn create_role_flow() { let editor = test_repo_editor().await; let targets_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = - &[Box::new(LocalKeySource { path: key_path() })]; + &[Box::new(LocalKeySource { path: key_path(),password: None })]; let role1_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = &[Box::new(LocalKeySource { path: targets_key_path(), + password: None, })]; let role2_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = &[Box::new(LocalKeySource { path: targets_key_path1(), + password: None, })]; // write the repo to temp location @@ -320,7 +324,7 @@ async fn create_role_flow() { //sign everything since targets key is the same as snapshot and timestamp let root_key = key_path(); - let key_source = LocalKeySource { path: root_key }; + let key_source = LocalKeySource { path: root_key, password: None }; let timestamp_expiration = Utc::now().checked_add_signed(days(3)).unwrap(); let timestamp_version = NonZeroU64::new(1234).unwrap(); let snapshot_expiration = Utc::now().checked_add_signed(days(21)).unwrap(); @@ -430,7 +434,7 @@ async fn create_role_flow() { let metadata_base_url_out = dir_url(&metadata_destination_out); // add outdir to repo let root_key = key_path(); - let key_source = LocalKeySource { path: root_key }; + let key_source = LocalKeySource { path: root_key, password: None }; let mut editor = RepositoryEditor::from_repo(root_path(), new_repo) .await @@ -481,14 +485,16 @@ async fn update_targets_flow() { let editor = test_repo_editor().await; let targets_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = - &[Box::new(LocalKeySource { path: key_path() })]; + &[Box::new(LocalKeySource { path: key_path(), password: None })]; let role1_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = &[Box::new(LocalKeySource { path: targets_key_path(), + password: None, })]; let role2_key: &[std::boxed::Box<(dyn tough::key_source::KeySource + 'static)>] = &[Box::new(LocalKeySource { path: targets_key_path1(), + password: None, })]; // write the repo to temp location @@ -544,7 +550,7 @@ async fn update_targets_flow() { //sign everything since targets key is the same as snapshot and timestamp let root_key = key_path(); - let key_source = LocalKeySource { path: root_key }; + let key_source = LocalKeySource { path: root_key, password: None }; let timestamp_expiration = Utc::now().checked_add_signed(days(3)).unwrap(); let timestamp_version = NonZeroU64::new(1234).unwrap(); let snapshot_expiration = Utc::now().checked_add_signed(days(21)).unwrap(); @@ -654,7 +660,7 @@ async fn update_targets_flow() { let metadata_base_url_out = dir_url(&metadata_destination_out); // add outdir to repo let root_key = key_path(); - let key_source = LocalKeySource { path: root_key }; + let key_source = LocalKeySource { path: root_key, password: None}; let mut editor = RepositoryEditor::from_repo(root_path(), new_repo) .await diff --git a/tough/tests/target_path_safety.rs b/tough/tests/target_path_safety.rs index a5346204e..2c0b71707 100644 --- a/tough/tests/target_path_safety.rs +++ b/tough/tests/target_path_safety.rs @@ -26,6 +26,7 @@ fn later() -> DateTime { async fn create_root(root_path: &Path, consistent_snapshot: bool) -> Vec> { let keys: Vec> = vec![Box::new(LocalKeySource { path: test_data().join("snakeoil.pem"), + password: None, })]; let key_pair = keys.first().unwrap().as_sign().await.unwrap().tuf_key(); diff --git a/tuftool/src/add_key_role.rs b/tuftool/src/add_key_role.rs index 35d6e2954..59c574d0c 100644 --- a/tuftool/src/add_key_role.rs +++ b/tuftool/src/add_key_role.rs @@ -29,10 +29,18 @@ pub(crate) struct AddKeyArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files to sign with + #[arg(short, long = "password")] + passwords: Option>, + /// New keys to be used for role #[arg(long = "new-key", required = true)] new_keys: Vec, + /// [Optional] passwords/passphrases of the new keys + #[arg(long = "new-password")] + new_passwords: Option>, + /// TUF repository metadata base URL #[arg(short, long = "metadata-url")] metadata_base_url: Url, @@ -66,8 +74,18 @@ impl AddKeyArgs { async fn add_key(&self, role: &str, mut editor: TargetsEditor) -> Result<()> { // create the keypairs to add let mut key_pairs = HashMap::new(); - for source in &self.new_keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let new_passwords = match &self.new_passwords { + Some(pws) => pws, + None => &vec![], + }; + + if new_passwords.len() > self.new_keys.len() { + panic!("More new passwords provided than new key sources"); + } + for (i, source) in self.new_keys.iter().enumerate() { + let password = new_passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source,Some(password.to_string()))?; let key_pair = key_source .as_sign() .await @@ -83,8 +101,17 @@ impl AddKeyArgs { } let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + + for (i,source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source,Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/add_role.rs b/tuftool/src/add_role.rs index a097fe732..d61154b2d 100644 --- a/tuftool/src/add_role.rs +++ b/tuftool/src/add_role.rs @@ -33,6 +33,10 @@ pub(crate) struct AddRoleArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(long = "password")] + passwords: Option>, + /// TUF repository metadata base URL #[arg(short, long = "metadata-url")] metadata_base_url: Url, @@ -120,8 +124,17 @@ impl AddRoleArgs { }; let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } @@ -155,8 +168,17 @@ impl AddRoleArgs { /// Adds a role to metadata using repo Editor async fn with_repo_editor(&self, role: &str, mut editor: RepositoryEditor) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/create.rs b/tuftool/src/create.rs index 586f29e51..8d0655ad7 100644 --- a/tuftool/src/create.rs +++ b/tuftool/src/create.rs @@ -31,6 +31,10 @@ pub(crate) struct CreateArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, + /// The directory where the repository will be written #[arg(short, long)] outdir: PathBuf, @@ -80,8 +84,17 @@ pub(crate) struct CreateArgs { impl CreateArgs { pub(crate) async fn run(&self) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/create_role.rs b/tuftool/src/create_role.rs index 9b7f32ca5..b7cae2cd8 100644 --- a/tuftool/src/create_role.rs +++ b/tuftool/src/create_role.rs @@ -27,6 +27,10 @@ pub(crate) struct CreateRoleArgs { #[arg(short, long, required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, + /// The directory where the repository will be written #[arg(short, long)] outdir: PathBuf, @@ -39,8 +43,18 @@ pub(crate) struct CreateRoleArgs { impl CreateRoleArgs { pub(crate) async fn run(&self, role: &str) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/remove_key_role.rs b/tuftool/src/remove_key_role.rs index abd3fb6d9..7a159f3d6 100644 --- a/tuftool/src/remove_key_role.rs +++ b/tuftool/src/remove_key_role.rs @@ -29,6 +29,10 @@ pub(crate) struct RemoveKeyArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, + /// Key to be removed will look similar to `8ec3a843a0f9328c863cac4046ab1cacbbc67888476ac7acf73d9bcd9a223ada` #[arg(long = "keyid", required = true)] remove: Decoded, @@ -64,8 +68,18 @@ impl RemoveKeyArgs { /// Removes keys from a delegated role using targets Editor async fn remove_key(&self, role: &str, mut editor: TargetsEditor) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/remove_role.rs b/tuftool/src/remove_role.rs index 1856a0fe0..ece8c7b6d 100644 --- a/tuftool/src/remove_role.rs +++ b/tuftool/src/remove_role.rs @@ -28,6 +28,10 @@ pub(crate) struct RemoveRoleArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, + /// TUF repository metadata base URL #[arg(short, long = "metadata-url")] metadata_base_url: Url, @@ -63,8 +67,17 @@ impl RemoveRoleArgs { /// Removes a delegated role from a `Targets` role using `TargetsEditor` async fn remove_delegated_role(&self, role: &str, mut editor: TargetsEditor) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/root.rs b/tuftool/src/root.rs index 47e43d7ec..598f48435 100644 --- a/tuftool/src/root.rs +++ b/tuftool/src/root.rs @@ -31,6 +31,9 @@ pub(crate) enum Command { /// The new key to be added #[arg(short, long = "key")] key_source: Vec, + /// [Optional] password of the new key to be added + #[arg(short, long = "password")] + password: Option>, /// The role to add the key to #[arg(short, long = "role")] roles: Vec, @@ -65,6 +68,9 @@ pub(crate) enum Command { /// The role to add the key to #[arg(short, long = "role")] roles: Vec, + /// [Optional] password/passphrase of the new key + #[arg(short, long = "password")] + password: Option, }, /// Create a new root.json metadata file Init { @@ -107,6 +113,9 @@ pub(crate) enum Command { /// Key source(s) to sign the file with #[arg(short, long = "key")] key_sources: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, /// Optional - Path of older root.json that contains the key-id #[arg(short, long)] cross_sign: Option, @@ -147,7 +156,8 @@ impl Command { path, roles, key_source, - } => Command::add_key(&path, &roles, &key_source).await, + password, + } => Command::add_key(&path, &roles, &key_source, &password).await, Command::RemoveKey { path, key_id, role } => { Command::remove_key(&path, &key_id, role).await } @@ -157,16 +167,27 @@ impl Command { key_source, bits, exponent, - } => Command::gen_rsa_key(&path, &roles, &key_source, bits, exponent).await, + password, + } => Command::gen_rsa_key(&path, &roles, &key_source, bits, exponent, password).await, Command::Sign { path, key_sources, + passwords, cross_sign, ignore_threshold, } => { let mut keys = Vec::new(); - for source in &key_sources { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match passwords { + Some(pws) => pws, + None => vec![], + }; + if passwords.len() > key_sources.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in key_sources.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } Command::sign(&path, &keys, cross_sign, ignore_threshold).await @@ -239,10 +260,19 @@ impl Command { } #[allow(clippy::borrowed_box)] - async fn add_key(path: &Path, roles: &[RoleType], key_source: &Vec) -> Result<()> { + async fn add_key(path: &Path, roles: &[RoleType], key_source: &Vec, password: &Option>) -> Result<()> { let mut keys = Vec::new(); - for source in key_source { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match password { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > key_source.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in key_source.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } let mut root: Signed = load_file(path).await?; @@ -292,9 +322,9 @@ impl Command { key_source: &str, bits: u16, exponent: u32, + password: Option, ) -> Result<()> { let mut root: Signed = load_file(path).await?; - // ring doesn't support RSA key generation yet // https://github.com/briansmith/ring/issues/219 let mut command = std::process::Command::new("openssl"); @@ -302,7 +332,10 @@ impl Command { command.arg(format!("rsa_keygen_bits:{bits}")); command.arg("-pkeyopt"); command.arg(format!("rsa_keygen_pubexp:{exponent}")); - + if let Some(password_str) = password.as_deref() { + command.args(["-aes256","-pass"]); + command.arg(format!("pass:{}",password_str)); + } let command_str = format!("{command:?}"); let output = command.output().context(error::CommandExecSnafu { command_str: &command_str, @@ -317,9 +350,9 @@ impl Command { let stdout = String::from_utf8(output.stdout).context(error::CommandUtf8Snafu { command_str })?; - let key_pair = parse_keypair(stdout.as_bytes()).context(error::KeyPairParseSnafu)?; + let key_pair = parse_keypair(stdout.as_bytes(),password.as_deref()).context(error::KeyPairParseSnafu)?; let key_id = hex::encode(add_key(&mut root.signed, roles, key_pair.tuf_key())?); - let key = parse_key_source(key_source)?; + let key = parse_key_source(key_source,None)?; key.write(&stdout, &key_id) .await .context(error::WriteKeySourceSnafu)?; diff --git a/tuftool/src/source.rs b/tuftool/src/source.rs index 7a44bde43..0159b218a 100644 --- a/tuftool/src/source.rs +++ b/tuftool/src/source.rs @@ -49,10 +49,10 @@ use url::Url; /// Users are welcome to add their own sources of keys by implementing /// the `KeySource` trait in the `tough` library. A user can then add /// to this parser to support them in `tuftool`. -pub(crate) fn parse_key_source(input: &str) -> Result> { +pub(crate) fn parse_key_source(input: &str, password: Option) -> Result> { let path_or_url = parse_path_or_url(input)?; match path_or_url { - PathOrUrl::Path(path) => Ok(Box::new(LocalKeySource { path })), + PathOrUrl::Path(path) => Ok(Box::new(LocalKeySource { path, password })), PathOrUrl::Url(url) => { match url.scheme() { #[cfg(any(feature = "aws-sdk-rust-native-tls", feature = "aws-sdk-rust-rustls"))] diff --git a/tuftool/src/transfer_metadata.rs b/tuftool/src/transfer_metadata.rs index 3590c812f..e5a42732e 100644 --- a/tuftool/src/transfer_metadata.rs +++ b/tuftool/src/transfer_metadata.rs @@ -23,6 +23,10 @@ pub(crate) struct TransferMetadataArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, + /// TUF repository metadata base URL #[arg(short, long = "metadata-url")] metadata_base_url: Url, @@ -82,8 +86,17 @@ WARNING: `--allow-expired-repo` was passed; this is unsafe and will not establis impl TransferMetadataArgs { pub(crate) async fn run(&self) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/update.rs b/tuftool/src/update.rs index c3c7b76a4..e9b92f60b 100644 --- a/tuftool/src/update.rs +++ b/tuftool/src/update.rs @@ -42,6 +42,10 @@ pub(crate) struct UpdateArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, + /// TUF repository metadata base URL #[arg(short, long = "metadata-url")] metadata_base_url: Url, @@ -135,8 +139,17 @@ impl UpdateArgs { async fn update_metadata(&self, mut editor: RepositoryEditor) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/src/update_targets.rs b/tuftool/src/update_targets.rs index 3221a6190..de8da9dbc 100644 --- a/tuftool/src/update_targets.rs +++ b/tuftool/src/update_targets.rs @@ -39,6 +39,10 @@ pub(crate) struct UpdateTargetsArgs { #[arg(short, long = "key", required = true)] keys: Vec, + /// [Optional] passwords/passphrases of the Key files + #[arg(short, long = "password")] + passwords: Option>, + /// TUF repository metadata base URL #[arg(short, long = "metadata-url")] metadata_base_url: Url, @@ -78,8 +82,17 @@ impl UpdateTargetsArgs { async fn update_targets(&self, mut editor: TargetsEditor) -> Result<()> { let mut keys = Vec::new(); - for source in &self.keys { - let key_source = parse_key_source(source)?; + let default_password = String::new(); + let passwords = match &self.passwords { + Some(pws) => pws, + None => &vec![], + }; + if passwords.len() > self.keys.len() { + panic!("More passwords provided than key sources"); + } + for (i, source) in self.keys.iter().enumerate() { + let password = passwords.get(i).unwrap_or(&default_password); + let key_source = parse_key_source(source, Some(password.to_string()))?; keys.push(key_source); } diff --git a/tuftool/tests/root_command.rs b/tuftool/tests/root_command.rs index 288a08c51..4aae8c3d4 100644 --- a/tuftool/tests/root_command.rs +++ b/tuftool/tests/root_command.rs @@ -296,6 +296,7 @@ async fn cross_sign_root() { let new_root_key = test_utils::test_data().join("snakeoil_2.pem"); let old_key_source = LocalKeySource { path: old_root_key.clone(), + password: None, }; let old_key_id = old_key_source .as_sign()