commit 3f3081ed92c5139cd4889a45c3f1abca4c445198
parent b90b34cfbc4e989f76a6a39708af8e1bd77b6f67
Author: triesap <tyson@radroots.org>
Date: Fri, 10 Apr 2026 22:16:26 +0000
runtime: cover secret file failure paths
Diffstat:
1 file changed, 166 insertions(+), 1 deletion(-)
diff --git a/crates/runtime/src/secret_file.rs b/crates/runtime/src/secret_file.rs
@@ -232,7 +232,11 @@ fn set_secret_permissions(_path: &Path) -> Result<(), RadrootsSecretVaultAccessE
#[cfg(test)]
mod tests {
- use super::{local_wrapping_key_path, open_local_secret_file, seal_local_secret_file};
+ use super::{
+ LocalWrappedKeySource, RuntimeProtectedFileError, WRAPPED_KEY_VERSION,
+ local_wrapping_key_path, open_local_secret_file, seal_local_secret_file,
+ };
+ use radroots_secret_vault::RadrootsSecretKeyWrapping;
#[test]
fn secret_file_round_trips_with_sidecar_key() {
@@ -281,4 +285,165 @@ mod tests {
.contains("expected key slot unexpected_slot")
);
}
+
+ #[test]
+ fn local_wrapped_key_source_reuses_existing_key_file() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ let key_path = local_wrapping_key_path(&path);
+ let expected = [7_u8; super::RADROOTS_PROTECTED_STORE_KEY_LENGTH];
+ std::fs::write(&key_path, expected).expect("write sidecar key");
+
+ let source = LocalWrappedKeySource::new(&path);
+ let loaded = source
+ .load_or_create_wrapping_key()
+ .expect("existing key should be reused");
+
+ assert_eq!(loaded, expected);
+ }
+
+ #[test]
+ fn local_wrapped_key_source_rejects_invalid_key_length() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ let key_path = local_wrapping_key_path(&path);
+ std::fs::write(
+ &key_path,
+ [7_u8; super::RADROOTS_PROTECTED_STORE_KEY_LENGTH - 1],
+ )
+ .expect("write short sidecar key");
+
+ let source = LocalWrappedKeySource::new(&path);
+ let err = source
+ .load_wrapping_key()
+ .expect_err("short wrapping key must fail");
+
+ assert!(
+ err.to_string().contains("invalid length"),
+ "unexpected error: {err}"
+ );
+ }
+
+ #[test]
+ fn local_wrapped_key_source_rejects_truncated_invalid_and_tampered_wrapped_keys() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ let source = LocalWrappedKeySource::new(&path);
+
+ let wrapped = source
+ .wrap_data_key("runtime_test_identity", b"payload")
+ .expect("wrap succeeds");
+
+ let err = source
+ .unwrap_data_key(
+ "runtime_test_identity",
+ &wrapped[..=super::RADROOTS_PROTECTED_STORE_NONCE_LENGTH],
+ )
+ .expect_err("truncated wrapped key must fail");
+ assert!(err.to_string().contains("truncated"));
+
+ let mut invalid_version = wrapped.clone();
+ invalid_version[0] = WRAPPED_KEY_VERSION + 1;
+ let err = source
+ .unwrap_data_key("runtime_test_identity", &invalid_version)
+ .expect_err("invalid wrapped key version must fail");
+ assert!(
+ err.to_string()
+ .contains("unsupported wrapped protected secret data key version")
+ );
+
+ let mut tampered = wrapped;
+ let last = tampered.len() - 1;
+ tampered[last] ^= 0x01;
+ let err = source
+ .unwrap_data_key("runtime_test_identity", &tampered)
+ .expect_err("tampered ciphertext must fail");
+ assert!(
+ err.to_string()
+ .contains("failed to unwrap protected secret data key")
+ );
+ }
+
+ #[test]
+ fn seal_local_secret_file_reports_create_dir_failure() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let blocked_parent = temp.path().join("not-a-dir");
+ std::fs::write(&blocked_parent, b"blocker").expect("write blocker file");
+ let path = blocked_parent.join("identity.secret.json");
+
+ let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect_err("parent file must block directory creation");
+
+ match err {
+ RuntimeProtectedFileError::CreateDir { path: err_path, .. } => {
+ assert_eq!(err_path, blocked_parent);
+ }
+ other => panic!("unexpected error variant: {other:?}"),
+ }
+ }
+
+ #[test]
+ fn seal_local_secret_file_reports_seal_failure_for_invalid_existing_wrapping_key() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ std::fs::write(local_wrapping_key_path(&path), [1_u8; 3]).expect("write invalid sidecar");
+
+ let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect_err("invalid sidecar should fail sealing");
+
+ match err {
+ RuntimeProtectedFileError::Seal {
+ path: err_path,
+ message,
+ } => {
+ assert_eq!(err_path, path);
+ assert!(!message.is_empty());
+ }
+ other => panic!("unexpected error variant: {other:?}"),
+ }
+ }
+
+ #[test]
+ fn seal_local_secret_file_reports_io_error_when_target_is_directory() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ std::fs::create_dir(&path).expect("create directory target");
+
+ let err = seal_local_secret_file(&path, "runtime_test_identity", b"payload")
+ .expect_err("directory target must fail write");
+
+ match err {
+ RuntimeProtectedFileError::Io { path: err_path, .. } => assert_eq!(err_path, path),
+ other => panic!("unexpected error variant: {other:?}"),
+ }
+ }
+
+ #[test]
+ fn open_local_secret_file_reports_io_error_for_missing_payload_file() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("missing.secret.json");
+
+ let err = open_local_secret_file(&path, "runtime_test_identity")
+ .expect_err("missing file must fail");
+
+ match err {
+ RuntimeProtectedFileError::Io { path: err_path, .. } => assert_eq!(err_path, path),
+ other => panic!("unexpected error variant: {other:?}"),
+ }
+ }
+
+ #[test]
+ fn open_local_secret_file_reports_decode_error_for_invalid_payload() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.secret.json");
+ std::fs::write(&path, b"not-json").expect("write invalid payload");
+
+ let err = open_local_secret_file(&path, "runtime_test_identity")
+ .expect_err("invalid json payload must fail");
+
+ match err {
+ RuntimeProtectedFileError::Decode { path: err_path, .. } => assert_eq!(err_path, path),
+ other => panic!("unexpected error variant: {other:?}"),
+ }
+ }
}