lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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:
Mcrates/runtime/src/secret_file.rs | 167++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
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:?}"), + } + } }