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:
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(