Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions crates/enclaveapp-app-storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ workspace = true

[features]
default = []
# Mock backend. Opt-in at compile time ONLY — release binaries must
# not include it (a runtime flag in a production build would let an
# attacker swap the hardware-backed store for an AES-in-RAM mock).
# Consumers enable this via a `[dev-dependencies]` entry so the mock
# is unified in during `cargo test` but never during `cargo build`.
mock = ["dep:aes-gcm", "dep:rand", "dep:sha2"]

[dependencies]
enclaveapp-core = { workspace = true }
thiserror = { workspace = true }
tracing = "0.1"

# Mock backend — always compiled so `ENCLAVEAPP_MOCK_STORAGE=1` can
# swap it in at runtime without any feature flag gymnastics.
aes-gcm = { workspace = true }
rand = { workspace = true }
sha2 = { workspace = true }
# Test-only crypto for MockEncryptionStorage — feature-gated so
# release builds do not pull them in via this crate.
aes-gcm = { workspace = true, optional = true }
rand = { workspace = true, optional = true }
sha2 = { workspace = true, optional = true }

[dev-dependencies]
enclaveapp-test-support = { workspace = true }
Expand Down
50 changes: 32 additions & 18 deletions crates/enclaveapp-app-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@

pub mod encryption;
pub mod error;
#[cfg(feature = "mock")]
pub mod mock;
pub mod platform;
pub mod signing;

// Re-export primary types for consumers.
pub use encryption::{AppEncryptionStorage, EncryptionStorage};
pub use error::{Result, StorageError};
#[cfg(feature = "mock")]
pub use mock::MockEncryptionStorage;
pub use platform::BackendKind;
pub use signing::AppSigningBackend;
Expand Down Expand Up @@ -88,32 +90,44 @@ pub struct StorageConfig {
pub force_keyring: bool,
}

/// Environment variable that, when set to a non-empty value, forces
/// Environment variable that, when the `mock` cargo feature is
/// compiled in **and** this var is set to a non-empty value, forces
/// [`create_encryption_storage`] to return a [`MockEncryptionStorage`]
/// instead of the real platform backend.
///
/// This is the sanctioned escape hatch for CI environments that
/// cannot satisfy a real hardware-backed backend — typically GitHub
/// Actions macOS runners, which would otherwise block indefinitely
/// on a login-keychain ACL confirmation prompt. It is **not** a
/// production-safe mode: the mock uses a random in-memory AES key
/// and provides no hardware backing.
/// **Security:** the env-var check below is feature-gated to `mock`.
/// Release binaries built without the feature ignore the variable
/// entirely — no runtime path leads to the mock backend, so setting
/// the variable in production does nothing. Only `cargo test` builds
/// (where downstream `[dev-dependencies]` turn the feature on) read
/// this variable.
///
/// This exists for CI environments that cannot satisfy a real
/// hardware-backed backend — typically GitHub Actions macOS runners,
/// which would otherwise block on a login-keychain ACL confirmation
/// prompt.
#[cfg(feature = "mock")]
pub const MOCK_STORAGE_ENV: &str = "ENCLAVEAPP_MOCK_STORAGE";

/// Create encryption storage with automatic platform detection.
///
/// Honours [`MOCK_STORAGE_ENV`]: when that env var is set to a
/// non-empty value the returned box wraps a fresh
/// [`MockEncryptionStorage`]. Callers get a uniform
/// [`EncryptionStorage`] trait object either way.
/// When built with the `mock` feature (test builds only), honours
/// [`MOCK_STORAGE_ENV`]: a non-empty value routes through
/// [`MockEncryptionStorage`]. Release builds have the feature off,
/// so this function unconditionally returns the real backend —
/// there is no runtime switch that could downgrade production
/// security.
pub fn create_encryption_storage(config: StorageConfig) -> Result<Box<dyn EncryptionStorage>> {
if let Ok(val) = std::env::var(MOCK_STORAGE_ENV) {
if !val.is_empty() {
tracing::warn!(
app = %config.app_name,
"{MOCK_STORAGE_ENV} is set — returning MockEncryptionStorage (no hardware backing)"
);
return Ok(Box::new(MockEncryptionStorage::for_app(&config.app_name)));
#[cfg(feature = "mock")]
{
if let Ok(val) = std::env::var(MOCK_STORAGE_ENV) {
if !val.is_empty() {
tracing::warn!(
app = %config.app_name,
"{MOCK_STORAGE_ENV} is set — returning MockEncryptionStorage (no hardware backing)"
);
return Ok(Box::new(MockEncryptionStorage::for_app(&config.app_name)));
}
}
}
let storage = AppEncryptionStorage::init(config)?;
Expand Down