Conversation
Reviewer's GuideThis PR extends key handling to support optional passwords for encrypted key files throughout the CLI and library, adding decryption logic, new error paths, CLI flags, and corresponding test updates. Sequence diagram for key parsing with optional passwordsequenceDiagram
participant CLI
participant LocalKeySource
participant parse_key_source()
participant parse_keypair()
participant decrypt_key()
CLI->>parse_key_source(): Provide key path and optional password
parse_key_source->>LocalKeySource: Create LocalKeySource(path, password)
LocalKeySource->>parse_keypair: Call with key bytes, password
alt password provided and not empty
parse_keypair->>decrypt_key: Decrypt key with password
decrypt_key-->>parse_keypair: Return decrypted key
else password not provided or empty
parse_keypair: Use key bytes as-is
end
parse_keypair-->>LocalKeySource: Return SignKeyPair
LocalKeySource-->>CLI: Return SignKeyPair
Class diagram for updated key handling with password supportclassDiagram
class LocalKeySource {
+PathBuf path
+Option<String> password
}
class KeySource {
<<trait>>
+async as_sign()
+async write()
}
LocalKeySource --|> KeySource
class Sign {
<<trait>>
}
class SignKeyPair {
+ED25519KeyPair
+ECDSAKeyPair
+RsaKeyPair
}
SignKeyPair --|> Sign
class Error {
+MorePasswords
+MoreNewPasswords
+EncryptedKeyNoPassword
+InvalidPassword
+KeyDecryption
}
class parse_keypair {
+parse_keypair(key: &[u8], password: Option<&str>)
}
class decrypt_key {
+decrypt_key(encrypted_key: &[u8], password: &str)
}
parse_keypair --> decrypt_key : uses for decryption
LocalKeySource --> parse_keypair : uses for key parsing
KeySource <|.. LocalKeySource : implemented by
Error <.. parse_keypair : error paths
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
/review |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||
There was a problem hiding this comment.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `tuftool/src/rhtas.rs:1161` </location>
<code_context>
+ path: path_buf.clone(),
+ })?;
+
+ let is_encrypted = key_content.contains("ENCRYPTED");
+
+ if is_encrypted {
</code_context>
<issue_to_address>
**issue:** Key encryption detection via substring may be unreliable.
Parsing the PEM header or using a library function would provide a more reliable way to detect encrypted keys than substring matching.
</issue_to_address>
### Comment 2
<location> `tuftool/tests/root_command.rs:476-485` </location>
<code_context>
+// Ensure we can create and sign a root file with a password encrypted key
</code_context>
<issue_to_address>
**suggestion (testing):** Missing test for multiple encrypted keys with different passwords.
Please add a test that covers signing with multiple encrypted keys, each using a distinct password, to verify correct password-key matching.
Suggested implementation:
```rust
#[test]
// Ensure we can create and sign a root file with a password encrypted key
fn create_root_encrypted_key() {
let out_dir = TempDir::new().unwrap();
let root_json = out_dir.path().join("root.json");
let key = test_utils::test_data().join("snakeoil_3.pem");
// Password used to decrypt key
let password = "test_password";
// Create and initialise root.json
initialize_root_json(root_json.to_str().unwrap());
// Add key for all roles
}
// Test signing with multiple encrypted keys, each with a distinct password
#[test]
fn sign_root_with_multiple_encrypted_keys() {
let out_dir = TempDir::new().unwrap();
let root_json = out_dir.path().join("root.json");
let key1 = test_utils::test_data().join("snakeoil_3.pem");
let key2 = test_utils::test_data().join("snakeoil_4.pem");
let password1 = "test_password_1";
let password2 = "test_password_2";
// Initialize root.json
initialize_root_json(root_json.to_str().unwrap());
// Add both keys for all roles
add_key_to_root_json(root_json.to_str().unwrap(), key1.to_str().unwrap(), Some(password1));
add_key_to_root_json(root_json.to_str().unwrap(), key2.to_str().unwrap(), Some(password2));
// Try signing with correct passwords for both keys
let sign_result = sign_root_json_with_multiple_keys(
root_json.to_str().unwrap(),
&[
(key1.to_str().unwrap(), password1),
(key2.to_str().unwrap(), password2),
],
);
assert!(sign_result.is_ok(), "Signing with correct passwords should succeed");
// Try signing with wrong password for key1
let sign_result_wrong1 = sign_root_json_with_multiple_keys(
root_json.to_str().unwrap(),
&[
(key1.to_str().unwrap(), "wrong_password"),
(key2.to_str().unwrap(), password2),
],
);
assert!(sign_result_wrong1.is_err(), "Signing with wrong password for key1 should fail");
// Try signing with wrong password for key2
let sign_result_wrong2 = sign_root_json_with_multiple_keys(
root_json.to_str().unwrap(),
&[
(key1.to_str().unwrap(), password1),
(key2.to_str().unwrap(), "wrong_password"),
],
);
assert!(sign_result_wrong2.is_err(), "Signing with wrong password for key2 should fail");
// Try signing with wrong passwords for both keys
let sign_result_wrong_both = sign_root_json_with_multiple_keys(
root_json.to_str().unwrap(),
&[
(key1.to_str().unwrap(), "wrong_password"),
(key2.to_str().unwrap(), "wrong_password"),
],
);
assert!(sign_result_wrong_both.is_err(), "Signing with wrong passwords for both keys should fail");
}
```
You will need to implement or adapt the following helper functions if they do not already exist:
- `add_key_to_root_json(root_path: &str, key_path: &str, password: Option<&str>)` to add a key to the root.json, using the password if provided.
- `sign_root_json_with_multiple_keys(root_path: &str, keys: &[(&str, &str)]) -> Result<(), Error>` to sign the root.json with multiple keys, each with its password.
If your test utilities use different conventions for adding keys or signing, adjust the function calls accordingly.
</issue_to_address>
### Comment 3
<location> `tuftool/tests/root_command.rs:517-522` </location>
<code_context>
+ let root_json = out_dir.path().join("root.json");
+ let key = test_utils::test_data().join("snakeoil_3.pem");
+
+ // Invalid password
+ let password = "invalid_password";
+ // Create and initialise root.json
+ initialize_root_json(root_json.to_str().unwrap());
+ // Add key to root role
+ let mut cmd = Command::cargo_bin("tuftool").unwrap();
+ cmd.args([
+ "root",
+ "add-key",
+ root_json.to_str().unwrap(),
+ "--role",
+ "root",
+ ])
+ .arg("-k")
+ .arg(key.to_str().unwrap());
+ if !password.is_empty() {
+ cmd.args(["--password", password]);
+ }
+ cmd.assert().failure();
+}
</code_context>
<issue_to_address>
**suggestion (testing):** Missing test for missing password with encrypted key.
Please add a test case where an encrypted key is used without supplying a password, and verify that the expected error is raised.
```suggestion
#[test]
// Add encrypted key with an invalid password
fn create_root_encrypted_key_invalid_password() {
let out_dir = TempDir::new().unwrap();
let root_json = out_dir.path().join("root.json");
let key = test_utils::test_data().join("snakeoil_3.pem");
// Invalid password
let password = "invalid_password";
// Create and initialise root.json
initialize_root_json(root_json.to_str().unwrap());
// Add key to root role
let mut cmd = Command::cargo_bin("tuftool").unwrap();
cmd.args([
"root",
"add-key",
root_json.to_str().unwrap(),
"--role",
"root",
])
.arg("-k")
.arg(key.to_str().unwrap());
if !password.is_empty() {
cmd.args(["--password", password]);
}
cmd.assert().failure();
}
// Test: Add encrypted key without supplying a password
#[test]
fn create_root_encrypted_key_missing_password() {
let out_dir = TempDir::new().unwrap();
let root_json = out_dir.path().join("root.json");
let key = test_utils::test_data().join("snakeoil_3.pem");
// Create and initialise root.json
initialize_root_json(root_json.to_str().unwrap());
// Add key to root role without password
let mut cmd = Command::cargo_bin("tuftool").unwrap();
cmd.args([
"root",
"add-key",
root_json.to_str().unwrap(),
"--role",
"root",
])
.arg("-k")
.arg(key.to_str().unwrap());
// Do NOT supply --password
let assert = cmd.assert().failure();
// Optionally check for expected error message
assert.stderr(predicates::str::contains("password required").or(predicates::str::contains("failed to decrypt")));
}
```
</issue_to_address>
### Comment 4
<location> `tuftool/tests/root_command.rs:482-491` </location>
<code_context>
+ // Password used to decrypt key
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a test for empty password with encrypted key.
Testing this scenario will verify that the code properly handles and rejects empty passwords when decrypting keys.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
/ask can we accept encrypted passwords instead of plain texts ? |
Ask❓can we accept encrypted passwords instead of plain texts ? Answer:Short answer: not with the current implementation. The PR accepts passwords as plain strings from CLI flags and threads them through to parsing/decryption, but it does not support receiving already-encrypted/encoded secrets nor any KMS/OS keyring integration. Details observed:
What would be required to accept “encrypted passwords”:
Minimal, incremental path:
Conclusion: As-is, only plaintext passphrases are supported. To accept “encrypted passwords,” we need to introduce secret retrieval/decryption mechanisms and new CLI options, then decode/decrypt to plaintext before invoking parse_keypair/decrypt_key. 📄 References
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||
|
/implement re-implement: allow using password-protected private keys |
|
/implement make password a mandatory parameter |
|
hello! |
/implement make password a mandatory parameter |
| timestamp_version: Option<NonZeroU64>, | ||
|
|
||
| /// [Optional] passwords/passphrases of the Key files to sign with | ||
| #[arg(short, long = "password")] |
There was a problem hiding this comment.
/implement make password a mandatory parameter
There was a problem hiding this comment.
Code Implementation 🛠️
Implementation: Make the --password (-p) CLI parameter mandatory by marking it as required in the RhtasArgs struct definition.
| #[arg(short, long = "password")] | |
| #[arg(short, long = "password", required = true)] | |
| passwords: Option<Vec<String>>, |
📄 References
- No matching references available
See review comment here
| /// 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<String>, |
There was a problem hiding this comment.
/implement make password mandatory.
There was a problem hiding this comment.
Code Implementation 🛠️
Implementation: Make the password field in LocalKeySource mandatory by changing its type from Option to String. This enforces that a password must always be provided when constructing LocalKeySource instances.
| pub password: Option<String>, | |
| pub password: String, |
📄 References
- No matching references available
See review comment here
|
/implement make all password parameters mandatory. |
PR Type
Enhancement
Description
Add password support for encrypted private keys
Update key parsing to handle password-protected keys
Add password validation and error handling
Update CLI commands to accept password arguments
Diagram Walkthrough
File Walkthrough
15 files
Update SSM key parsing with password parameterAdd password field to LocalKeySource structImplement key decryption and password parsingAdd password arguments for key operationsAdd password support for role creationAdd password arguments to create commandAdd password support for role creationAdd password arguments for key removalAdd password support for role removalAdd password validation and encryption checksAdd password support for root operationsUpdate key source parsing with passwordAdd password arguments for metadata transferAdd password support for update operationsAdd password arguments for target updates5 files
Add password field to LocalKeySource in testsUpdate LocalKeySource usage with password fieldAdd password field to LocalKeySource creationAdd encrypted key tests and password handlingAdd encrypted test key file2 files
Add KeyDecryption error variantAdd password-related error variants1 files
Add pkcs8 and base64 dependencies1 files
Update documentation with password examples