lib

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

commit b791b5859584d6172b9fe9ccd1cdee6d80395001
parent 8841ab428afe31a996f64ac41bd267f1f515eeb2
Author: triesap <tyson@radroots.org>
Date:   Fri, 10 Apr 2026 21:01:58 +0000

protected_store: cover entropy and error paths

Diffstat:
Mcrates/protected_store/src/error.rs | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/protected_store/src/lib.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/crates/protected_store/src/error.rs b/crates/protected_store/src/error.rs @@ -38,3 +38,64 @@ impl fmt::Display for RadrootsProtectedStoreError { #[cfg(feature = "std")] impl std::error::Error for RadrootsProtectedStoreError {} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::string::ToString; + + #[test] + fn display_covers_all_error_variants() { + let cases = [ + ( + RadrootsProtectedStoreError::EntropyUnavailable, + "protected-store entropy is unavailable", + ), + ( + RadrootsProtectedStoreError::UnsupportedEnvelopeVersion(7), + "protected-store envelope version 7 is unsupported", + ), + ( + RadrootsProtectedStoreError::InvalidStoreKeyLength(31), + "protected-store key must be 32 bytes, got 31", + ), + ( + RadrootsProtectedStoreError::EnvelopeEncodeFailed, + "protected-store envelope encoding failed", + ), + ( + RadrootsProtectedStoreError::EnvelopeDecodeFailed, + "protected-store envelope decoding failed", + ), + ( + RadrootsProtectedStoreError::KeyWrapFailed, + "protected-store key wrapping failed", + ), + ( + RadrootsProtectedStoreError::KeyUnwrapFailed, + "protected-store key unwrapping failed", + ), + ( + RadrootsProtectedStoreError::EncryptFailed, + "protected-store encryption failed", + ), + ( + RadrootsProtectedStoreError::DecryptFailed, + "protected-store decryption failed", + ), + ]; + + for (error, expected) in cases { + assert_eq!(error.to_string(), expected); + } + } + + #[cfg(feature = "std")] + #[test] + fn error_trait_is_available_with_std() { + let error = RadrootsProtectedStoreError::DecryptFailed; + let dyn_error: &dyn std::error::Error = &error; + + assert_eq!(dyn_error.to_string(), "protected-store decryption failed"); + } +} diff --git a/crates/protected_store/src/lib.rs b/crates/protected_store/src/lib.rs @@ -68,10 +68,23 @@ impl RadrootsProtectedStoreEnvelope { where V: RadrootsSecretKeyWrapping, { + Self::seal_with_wrapped_key_with_entropy(vault, key_slot, plaintext, fill_random_bytes) + } + + fn seal_with_wrapped_key_with_entropy<V, F>( + vault: &V, + key_slot: &str, + plaintext: &[u8], + mut fill_entropy: F, + ) -> Result<Self, RadrootsProtectedStoreError> + where + V: RadrootsSecretKeyWrapping, + F: FnMut(&mut [u8]) -> Result<(), RadrootsProtectedStoreError>, + { let mut store_key = [0_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH]; let mut nonce = [0_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH]; - getrandom(&mut store_key).map_err(|_| RadrootsProtectedStoreError::EntropyUnavailable)?; - getrandom(&mut nonce).map_err(|_| RadrootsProtectedStoreError::EntropyUnavailable)?; + fill_entropy(&mut store_key)?; + fill_entropy(&mut nonce)?; let result = Self::seal_with_wrapped_key_and_material(vault, key_slot, plaintext, store_key, nonce); store_key.zeroize(); @@ -194,6 +207,10 @@ fn envelope_aad( .map_err(|_| RadrootsProtectedStoreError::EnvelopeEncodeFailed) } +fn fill_random_bytes(bytes: &mut [u8]) -> Result<(), RadrootsProtectedStoreError> { + getrandom(bytes).map_err(|_| RadrootsProtectedStoreError::EntropyUnavailable) +} + #[cfg(test)] mod tests { use super::*; @@ -316,6 +333,47 @@ mod tests { } #[test] + fn seal_with_wrapped_key_uses_runtime_entropy_and_roundtrips() { + let vault = FakeVault::new(); + let envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key( + &vault, + "drafts/default", + b"runtime entropy body", + ) + .expect("seal succeeds"); + + assert_eq!(vault.wrap_calls.get(), 1); + assert_eq!(envelope.header.key_slot, "drafts/default"); + + let plaintext = envelope + .open_with_wrapped_key(&vault) + .expect("open succeeds"); + + assert_eq!(vault.unwrap_calls.get(), 1); + assert_eq!(plaintext, b"runtime entropy body"); + } + + #[test] + fn seal_with_wrapped_key_reports_entropy_failure() { + let vault = FakeVault::new(); + let mut attempts = 0usize; + let err = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_with_entropy( + &vault, + "drafts/default", + b"secret draft body", + |_bytes| { + attempts += 1; + Err(RadrootsProtectedStoreError::EntropyUnavailable) + }, + ) + .expect_err("entropy failure must surface"); + + assert_eq!(attempts, 1); + assert_eq!(err, RadrootsProtectedStoreError::EntropyUnavailable); + assert_eq!(vault.wrap_calls.get(), 0); + } + + #[test] fn tampered_wrapped_key_fails_authentication() { let vault = FakeVault::new(); let mut envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_and_material(