README.md (8865B)
1 # radroots_secrets 2 3 `radroots_secrets` defines the bounded secret-provider, key-wrapping, and 4 authenticated-envelope contracts for Radroots. Hosts choose and configure a 5 provider explicitly; this crate does not own application authorization, 6 credential UI, account policy, runtime execution, or global provider state. 7 8 The crate is pre-release and publication remains disabled. Its Cargo version 9 is frozen at `0.1.0-alpha` until the coordinated release contract explicitly 10 changes it. 11 12 ## Canonical surface 13 14 | Module | Responsibility | 15 | --- | --- | 16 | `id` | Validated, redacted identifiers, backend kinds, key versions, and single-owner references. | 17 | `provider` | Provider capabilities, exact selection policy, and the executor-neutral provider SPI. | 18 | `wrapping` | Bounded zeroizing plaintext, wrapped values, requests, and the key-wrapping SPI. | 19 | `envelope` | Versioned XChaCha20-Poly1305 envelope encoding with authenticated provider metadata. | 20 | `memory` | Opt-in, process-local adapter for development and deterministic tests. | 21 | `file` | Opt-in encrypted file adapter with secure path, permission, and durable-write rules. | 22 | `keyring` | Opt-in operating-system credential-store adapter with lazy native access. | 23 | `error` | Normalized errors that do not expose provider-native messages or secret values. | 24 25 The curated root exports only `EncryptedEnvelope`, `Error`, `SecretId`, 26 `SecretRef`, `SecretProvider`, and `KeyWrapping`. Supporting request, policy, 27 adapter, and value types remain in their owning modules so security boundaries 28 stay explicit. The reviewed Rust surface is recorded in the 29 [public API baseline](../../contracts/api_baselines/radroots_secrets.txt). 30 31 ## Explicit provider and envelope flow 32 33 The memory adapter is empty when constructed. This example provisions an 34 explicit key, supplies a separate explicit data key and nonce, seals one value, 35 serializes the envelope, and opens it again: 36 37 ```rust 38 # #[cfg(feature = "memory")] 39 # fn main() -> Result<(), Box<dyn std::error::Error>> { 40 use futures_executor::block_on; 41 use radroots_secrets::EncryptedEnvelope; 42 use radroots_secrets::context::{ 43 EnvelopeContext, EnvelopePurpose, EnvelopeSubject, PayloadSchemaId, 44 }; 45 use radroots_secrets::envelope::{Nonce, SealMaterial, SealRequest}; 46 use radroots_secrets::id::{BackendKind, KeyVersion}; 47 use radroots_secrets::memory::MemoryProvider; 48 use radroots_secrets::wrapping::SecretMaterial; 49 use radroots_secrets::{SecretId, SecretRef}; 50 51 let reference = SecretRef::new( 52 SecretId::parse("example-profile-key")?, 53 BackendKind::Memory, 54 KeyVersion::new(1)?, 55 ); 56 let provider = MemoryProvider::new(); 57 provider.provision( 58 &reference, 59 SecretMaterial::from_slice(&[0x41; 32])?, 60 )?; 61 62 let plaintext = SecretMaterial::from_slice(b"private profile value")?; 63 let data_key = SecretMaterial::from_slice(&[0x41; 32])?; 64 let context = EnvelopeContext::new( 65 EnvelopePurpose::parse("radroots.private_profile")?, 66 EnvelopeSubject::parse("profile", "example-profile")?, 67 PayloadSchemaId::parse("radroots.private_profile.v1")?, 68 ); 69 let request = SealRequest::new( 70 reference, 71 context.clone(), 72 &plaintext, 73 SealMaterial::new(data_key, Nonce::new([0x24; 24])), 74 ); 75 let encoded = block_on(EncryptedEnvelope::seal(&provider, request))?.encode()?; 76 let decoded = EncryptedEnvelope::decode(&encoded)?; 77 let opened = block_on(decoded.open(&provider, &context))?; 78 79 opened.expose_secret(|bytes| assert_eq!(bytes, b"private profile value")); 80 # Ok(()) 81 # } 82 # #[cfg(not(feature = "memory"))] 83 # fn main() {} 84 ``` 85 86 A runnable version is available at 87 [`examples/explicit_memory_provider.rs`](examples/explicit_memory_provider.rs). 88 Its fixed key and nonce exist only to make the example deterministic. Production 89 hosts must supply cryptographically strong key material and a unique nonce for 90 every encryption under the same data key. 91 92 ## Features and supported targets 93 94 | Feature | Default | Effect | 95 | --- | --- | --- | 96 | `std` | yes | Enables standard-library integration; it performs no I/O by itself. | 97 | `serde` | yes | Serializes validated identifiers and encoded envelopes, never plaintext material or capability references. | 98 | `memory` | no | Enables the explicit process-local adapter; requires `std`. | 99 | `file` | no | Enables encrypted file persistence; requires `std`. | 100 | `keyring` | no | Enables lazy operating-system credential-store access; requires `std`. | 101 102 The core contracts compile without the standard library. `file` and `keyring` 103 are native host adapters; `memory` is available on standard-library targets. 104 No feature installs a global provider, starts a runtime, generates key 105 material, selects a fallback, opens storage, or performs a credential prompt. 106 107 ## Security and serialization contract 108 109 `SecretMaterial` is bounded, single-owner, zeroizing plaintext. It implements 110 neither `Clone` nor serialization and exposes bytes only inside an explicit 111 closure. `SecretRef`, seal requests, and seal material are likewise not 112 cloneable or serializable. Secret identifiers are serialized only where a 113 documented wire or storage format requires them; their ordinary diagnostics 114 remain redacted. 115 116 `EncryptedEnvelope` v2 authenticates its format version, cipher, key source, 117 backend, key version, secret identifier, purpose, typed subject, payload 118 schema, nonce, wrapped data key, and ciphertext length. Normal open requires an 119 independently derived expected context and rejects legacy v1. Decode validates 120 all lengths and enum values before provider access. Envelope serialization is 121 a persistence contract; Rust layout and debug output are not. Unknown versions, 122 ciphers, key sources, malformed lengths, context mismatches, backend mismatches, 123 and authentication failures fail closed. 124 125 Legacy v1 bytes remain decodeable for migration inventory, but normal open 126 always rejects them. An authorized host migration boundary must explicitly 127 construct `LegacyV1ResealAuthority`, supply the independently derived v2 128 context and expected provider reference, validate the owning payload schema, 129 and provide a fresh data key and nonce. The migration primitive rejects key or 130 nonce reuse and returns only a new v2 envelope plus a plaintext commitment; 131 transient plaintext and key material remain single-owner zeroizing values. 132 133 Provider-native error strings are normalized before crossing the public 134 boundary. Callers must still avoid logging plaintext, serialized identifiers, 135 encoded envelopes, or provider configuration. 136 137 ## Side effects, cancellation, and commit points 138 139 Constructing `MemoryProvider` and `KeyringProvider`, querying capabilities, 140 validating identifiers, selecting a provider, and encoding or decoding an 141 already-built envelope do not access secret storage. `KeyringProvider` creates 142 native credential entries lazily when an explicit operation begins. 143 144 Memory provisioning commits when the in-process map accepts the value. File 145 provisioning commits when the no-clobber entry is durably renamed and its 146 directory is synchronized. File rotation commits the new version before 147 removing the old version and can resume that boundary. Keyring provisioning 148 commits when the native store accepts the credential. Removal is idempotent; 149 rotation requires a higher version of the same identifier. 150 151 The SPIs return executor-neutral futures and do not create cancellation tokens 152 or background work. Dropping a future before its provider commit point cancels 153 only work the provider has not committed. After a durable or native commit, 154 the host must inspect or retry the exact operation; it must not assume that 155 dropping the future rolled the operation back. The built-in adapters perform no 156 implicit retry or fallback. 157 158 ## Intended consumers 159 160 Direct consumers are storage adapters, SDK signing hosts, Myc custody hosts, 161 and other first-party runtimes that own provider selection and authorization. 162 Applications should normally consume these contracts through `radroots_sdk`. 163 External provider implementations may implement `SecretProvider` and 164 `KeyWrapping` without depending on a built-in adapter. 165 166 This package must not acquire actor authorization, signing policy, account 167 management, application database ownership, UI prompts, executor ownership, 168 global sessions, network transport, or provider fallback. Those responsibilities 169 remain with their dedicated packages and hosts. 170 171 ## Package charter 172 173 The authoritative responsibility, dependency, feature, module, root-export, 174 and forbidden-scope contract is the 175 [Radroots crates Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). 176 The reviewed surface is the 177 [`radroots_secrets` API baseline](../../contracts/api_baselines/radroots_secrets.txt). 178 179 ## Copyright 180 181 Except as otherwise noted, all files in the `radroots_secrets` distribution 182 are copyright (c) 2025 Tyson Lupul. See `LICENSE` for usage, redistribution, 183 and warranty terms.