lib

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

commit 98718659de8314eb59c77bcdb276a1d119609981
parent 800adb92c4bf3569ac225b77e0a5986b502ed3fb
Author: triesap <tyson@radroots.org>
Date:   Wed,  5 Aug 2026 18:21:58 +0000

feat(secrets): gate legacy envelope reseal

Diffstat:
MCargo.lock | 1+
Mcrates/secrets/Cargo.toml | 1+
Mcrates/secrets/README.md | 8++++++++
Mcrates/secrets/src/envelope.rs | 170++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcrates/secrets/src/error.rs | 18++++++++++++++++++
Mcrates/secrets/src/file.rs | 16+++++++++++++++-
Mcrates/secrets/src/keyring.rs | 16+++++++++++++++-
Mcrates/secrets/src/memory.rs | 29+++++++++++++++++++++++++----
Mcrates/secrets/src/wrapping.rs | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/secrets/tests/envelope_contract.rs | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mcrates/secrets/tests/package_boundary.rs | 1+
Mcrates/secrets/tests/security_contract.rs | 2++
Mdocs/api/radroots_secrets.txt | 39+++++++++++++++++++++++++++++++++++++++
13 files changed, 465 insertions(+), 9 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -2684,6 +2684,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "subtle", "tempfile", "zeroize", ] diff --git a/crates/secrets/Cargo.toml b/crates/secrets/Cargo.toml @@ -37,6 +37,7 @@ serde = { workspace = true, default-features = false, features = [ "derive", ], optional = true } sha2 = { workspace = true, default-features = false } +subtle = { workspace = true, default-features = false } tempfile = { workspace = true, optional = true } zeroize = { workspace = true } diff --git a/crates/secrets/README.md b/crates/secrets/README.md @@ -122,6 +122,14 @@ a persistence contract; Rust layout and debug output are not. Unknown versions, ciphers, key sources, malformed lengths, context mismatches, backend mismatches, and authentication failures fail closed. +Legacy v1 bytes remain decodeable for migration inventory, but normal open +always rejects them. An authorized host migration boundary must explicitly +construct `LegacyV1ResealAuthority`, supply the independently derived v2 +context and expected provider reference, validate the owning payload schema, +and provide a fresh data key and nonce. The migration primitive rejects key or +nonce reuse and returns only a new v2 envelope plus a plaintext commitment; +transient plaintext and key material remain single-owner zeroizing values. + Provider-native error strings are normalized before crossing the public boundary. Callers must still avoid logging plaintext, serialized identifiers, encoded envelopes, or provider configuration. diff --git a/crates/secrets/src/envelope.rs b/crates/secrets/src/envelope.rs @@ -7,13 +7,17 @@ use crate::context::{ }; use crate::error::Error; use crate::id::{BackendKind, KeyVersion}; -use crate::wrapping::{KeyWrapping, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret}; +use crate::wrapping::{ + KeyWrapping, LegacyV1UnwrapRequest, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret, +}; use crate::{SecretId, SecretRef}; use alloc::string::String; use alloc::vec::Vec; use chacha20poly1305::aead::{Aead, KeyInit, Payload}; use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; use core::fmt; +use sha2::{Digest, Sha256}; +use subtle::ConstantTimeEq; const MAGIC: [u8; 4] = *b"RRS1"; const DATA_KEY_BYTES: usize = 32; @@ -117,6 +121,62 @@ impl fmt::Debug for SealMaterial { } } +/// Explicit capability required to read and reseal a decoded v1 envelope. +/// +/// Hosts must construct this value only inside their authorized migration +/// boundary. It cannot be derived from envelope bytes and is intentionally not +/// cloneable or serializable. +pub struct LegacyV1ResealAuthority { + _private: (), +} + +#[allow(clippy::new_without_default)] +impl LegacyV1ResealAuthority { + /// Grants one explicitly scoped host migration boundary v1 access. + #[must_use] + pub const fn new() -> Self { + Self { _private: () } + } +} + +impl fmt::Debug for LegacyV1ResealAuthority { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("LegacyV1ResealAuthority(<redacted>)") + } +} + +/// Result of an authenticated v1 read and fresh-material v2 reseal. +pub struct LegacyV1ResealResult { + envelope: EncryptedEnvelope, + plaintext_commitment: [u8; 32], +} + +impl LegacyV1ResealResult { + /// Returns the new context-bound v2 envelope. + #[must_use] + pub const fn envelope(&self) -> &EncryptedEnvelope { + &self.envelope + } + + /// Consumes the result and returns the new v2 envelope. + #[must_use] + pub fn into_envelope(self) -> EncryptedEnvelope { + self.envelope + } + + /// Returns the SHA-256 commitment to the authenticated plaintext. + #[must_use] + pub const fn plaintext_commitment(&self) -> &[u8; 32] { + &self.plaintext_commitment + } +} + +impl fmt::Debug for LegacyV1ResealResult { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("LegacyV1ResealResult(<redacted>)") + } +} + /// Complete input for one context-bound envelope sealing operation. pub struct SealRequest<'a> { reference: SecretRef, @@ -258,6 +318,71 @@ impl EncryptedEnvelope { SecretMaterial::from_owned(plaintext) } + /// Authenticates and opens v1 only under explicit migration authority. + pub async fn open_legacy_v1( + &self, + wrapping: &dyn KeyWrapping, + authority: &LegacyV1ResealAuthority, + expected_reference: &SecretRef, + _destination_context: &EnvelopeContext, + ) -> Result<SecretMaterial, Error> { + let (plaintext, _) = self + .open_legacy_parts(wrapping, authority, expected_reference) + .await?; + Ok(plaintext) + } + + /// Authenticates v1, validates its payload, and reseals as v2 with fresh material. + #[allow(clippy::too_many_arguments)] + pub async fn reseal_legacy_v1<V>( + &self, + wrapping: &dyn KeyWrapping, + authority: &LegacyV1ResealAuthority, + expected_reference: &SecretRef, + new_reference: SecretRef, + new_context: EnvelopeContext, + validator: &V, + material: SealMaterial, + ) -> Result<LegacyV1ResealResult, Error> + where + V: Fn(&[u8]) -> bool + Send + Sync, + { + if material.nonce == self.nonce { + return Err(Error::LegacyEntropyReuse); + } + validate_data_key(&material.data_key)?; + let (plaintext, legacy_data_key) = self + .open_legacy_parts(wrapping, authority, expected_reference) + .await?; + let reused_key = legacy_data_key.expose_secret(|legacy| { + material + .data_key + .expose_secret(|fresh| bool::from(legacy.ct_eq(fresh))) + }); + if reused_key { + return Err(Error::LegacyEntropyReuse); + } + if !plaintext.expose_secret(validator) { + return Err(Error::LegacyPayloadValidationFailed); + } + let plaintext_commitment = + plaintext.expose_secret(|bytes| <[u8; 32]>::from(Sha256::digest(bytes))); + let envelope = Self::seal( + wrapping, + SealRequest::new(new_reference, new_context, &plaintext, material), + ) + .await?; + let resealed_commitment = + plaintext.expose_secret(|bytes| <[u8; 32]>::from(Sha256::digest(bytes))); + if plaintext_commitment.ct_eq(&resealed_commitment).unwrap_u8() != 1 { + return Err(Error::EncryptFailed); + } + Ok(LegacyV1ResealResult { + envelope, + plaintext_commitment, + }) + } + /// Returns the authenticated provider reference. #[must_use] pub const fn reference(&self) -> &SecretRef { @@ -384,6 +509,43 @@ impl EncryptedEnvelope { } Ok(()) } + + async fn open_legacy_parts( + &self, + wrapping: &dyn KeyWrapping, + authority: &LegacyV1ResealAuthority, + expected_reference: &SecretRef, + ) -> Result<(SecretMaterial, SecretMaterial), Error> { + self.validate()?; + if self.version != LEGACY_ENVELOPE_VERSION { + return Err(Error::LegacyEnvelopeDenied); + } + if !references_match(&self.reference, expected_reference) { + return Err(Error::ProviderReferenceMismatch); + } + let data_key = wrapping + .unwrap_legacy_v1(LegacyV1UnwrapRequest::new( + &self.reference, + &self.wrapped_key, + authority, + )) + .await?; + validate_data_key(&data_key)?; + let aad = self.encoded_header()?; + let plaintext = data_key.expose_secret(|data_key| { + let cipher = XChaCha20Poly1305::new(Key::from_slice(data_key)); + cipher + .decrypt( + XNonce::from_slice(self.nonce.as_bytes()), + Payload { + msg: self.ciphertext.as_slice(), + aad: aad.as_slice(), + }, + ) + .map_err(|_| Error::DecryptFailed) + })?; + Ok((SecretMaterial::from_owned(plaintext)?, data_key)) + } } impl fmt::Debug for EncryptedEnvelope { @@ -449,6 +611,12 @@ fn validate_data_key(data_key: &SecretMaterial) -> Result<(), Error> { Ok(()) } +fn references_match(left: &SecretRef, right: &SecretRef) -> bool { + left.backend() == right.backend() + && left.key_version() == right.key_version() + && left.id().as_str() == right.id().as_str() +} + #[allow(clippy::too_many_arguments)] fn encode_header( version: u16, diff --git a/crates/secrets/src/error.rs b/crates/secrets/src/error.rs @@ -188,6 +188,12 @@ pub enum Error { }, /// A v1 envelope was presented to the normal v2-only open API. LegacyEnvelopeDenied, + /// The expected legacy provider reference did not match authenticated v1 metadata. + ProviderReferenceMismatch, + /// A legacy payload failed its owning schema validator. + LegacyPayloadValidationFailed, + /// Legacy key or nonce material was reused for a v2 reseal. + LegacyEntropyReuse, /// The encoded cipher identifier is not supported. UnsupportedCipher { /// Observed cipher identifier. @@ -324,6 +330,15 @@ impl fmt::Display for Error { Self::LegacyEnvelopeDenied => { formatter.write_str("legacy encrypted envelope requires migration authority") } + Self::ProviderReferenceMismatch => { + formatter.write_str("encrypted envelope provider reference mismatch") + } + Self::LegacyPayloadValidationFailed => { + formatter.write_str("legacy encrypted payload failed schema validation") + } + Self::LegacyEntropyReuse => { + formatter.write_str("legacy envelope cryptographic material cannot be reused") + } Self::UnsupportedCipher { cipher } => { write!( formatter, @@ -439,6 +454,9 @@ mod tests { Error::UnsupportedEnvelopeVersion { version: 2 }, Error::UnsupportedContextVersion { version: 2 }, Error::LegacyEnvelopeDenied, + Error::ProviderReferenceMismatch, + Error::LegacyPayloadValidationFailed, + Error::LegacyEntropyReuse, Error::UnsupportedCipher { cipher: 9 }, Error::UnsupportedKeySource { key_source: 9 }, Error::UnsupportedBackend { backend: 9 }, diff --git a/crates/secrets/src/file.rs b/crates/secrets/src/file.rs @@ -6,7 +6,8 @@ use crate::error::{Error, Operation}; use crate::id::BackendKind; use crate::provider::{CapabilitySupport, ResidencySupport, SecretCapabilities, SecretProvider}; use crate::wrapping::{ - BoxFuture, KeyWrapping, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret, + BoxFuture, KeyWrapping, LegacyV1UnwrapRequest, SecretMaterial, UnwrapRequest, WrapRequest, + WrappedSecret, }; use alloc::string::String; use alloc::vec::Vec; @@ -311,6 +312,19 @@ impl KeyWrapping for FileProvider { self.read_entry(request.reference()) }) } + + fn unwrap_legacy_v1<'a>( + &'a self, + request: LegacyV1UnwrapRequest<'a>, + ) -> BoxFuture<'a, Result<SecretMaterial, Error>> { + Box::pin(async move { + validate_file_reference(request.reference())?; + if request.wrapped().as_bytes() != entry_token(request.reference()).as_slice() { + return Err(backend_failure(Operation::Unwrap)); + } + self.read_entry(request.reference()) + }) + } } impl SecretProvider for FileProvider { diff --git a/crates/secrets/src/keyring.rs b/crates/secrets/src/keyring.rs @@ -5,7 +5,8 @@ use crate::error::{Error, Operation}; use crate::id::BackendKind; use crate::provider::{CapabilitySupport, ResidencySupport, SecretCapabilities, SecretProvider}; use crate::wrapping::{ - BoxFuture, KeyWrapping, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret, + BoxFuture, KeyWrapping, LegacyV1UnwrapRequest, SecretMaterial, UnwrapRequest, WrapRequest, + WrappedSecret, }; use alloc::boxed::Box; use alloc::string::{String, ToString}; @@ -165,6 +166,19 @@ impl KeyWrapping for KeyringProvider { self.read_material(request.reference()) }) } + + fn unwrap_legacy_v1<'a>( + &'a self, + request: LegacyV1UnwrapRequest<'a>, + ) -> BoxFuture<'a, Result<SecretMaterial, Error>> { + Box::pin(async move { + validate_keyring_reference(request.reference())?; + if request.wrapped().as_bytes() != reference_token(request.reference()).as_slice() { + return Err(backend_failure(Operation::Unwrap)); + } + self.read_material(request.reference()) + }) + } } fn wrapping_token(reference: &SecretRef, context: &crate::context::EnvelopeContext) -> Vec<u8> { diff --git a/crates/secrets/src/memory.rs b/crates/secrets/src/memory.rs @@ -5,7 +5,8 @@ use crate::error::{Error, Operation}; use crate::id::BackendKind; use crate::provider::{CapabilitySupport, ResidencySupport, SecretCapabilities, SecretProvider}; use crate::wrapping::{ - BoxFuture, KeyWrapping, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret, + BoxFuture, KeyWrapping, LegacyV1UnwrapRequest, SecretMaterial, UnwrapRequest, WrapRequest, + WrappedSecret, }; use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; @@ -106,15 +107,20 @@ impl MemoryProvider { reference: &SecretRef, context: &crate::context::EnvelopeContext, ) -> Result<WrappedSecret, Error> { + let mut token = Self::legacy_wrapped_token(reference)?; + token.extend_from_slice(&context.authentication_digest()); + WrappedSecret::from_bytes(token) + } + + fn legacy_wrapped_token(reference: &SecretRef) -> Result<Vec<u8>, Error> { let id = reference.id().as_str().as_bytes(); - let mut token = Vec::with_capacity(TOKEN_MAGIC.len() + 4 + 2 + id.len() + 32); + let mut token = Vec::with_capacity(TOKEN_MAGIC.len() + 4 + 2 + id.len()); token.extend_from_slice(TOKEN_MAGIC); token.extend_from_slice(&reference.key_version().get().to_be_bytes()); let id_len = u16::try_from(id.len()).map_err(|_| backend_failure(Operation::Wrap))?; token.extend_from_slice(&id_len.to_be_bytes()); token.extend_from_slice(id); - token.extend_from_slice(&context.authentication_digest()); - WrappedSecret::from_bytes(token) + Ok(token) } fn clone_material( @@ -169,6 +175,21 @@ impl KeyWrapping for MemoryProvider { self.clone_material(request.reference(), Operation::Unwrap) }) } + + fn unwrap_legacy_v1<'a>( + &'a self, + request: LegacyV1UnwrapRequest<'a>, + ) -> BoxFuture<'a, Result<SecretMaterial, Error>> { + Box::pin(async move { + validate_memory_reference(request.reference())?; + if request.wrapped().as_bytes() + != Self::legacy_wrapped_token(request.reference())?.as_slice() + { + return Err(backend_failure(Operation::Unwrap)); + } + self.clone_material(request.reference(), Operation::Unwrap) + }) + } } impl SecretProvider for MemoryProvider { diff --git a/crates/secrets/src/wrapping.rs b/crates/secrets/src/wrapping.rs @@ -2,6 +2,7 @@ use crate::SecretRef; use crate::context::EnvelopeContext; +use crate::envelope::LegacyV1ResealAuthority; use crate::error::Error; use alloc::boxed::Box; use alloc::vec::Vec; @@ -162,6 +163,45 @@ pub struct UnwrapRequest<'a> { wrapped: &'a WrappedSecret, } +/// Capability-gated input for migration-only v1 key unwrapping. +pub struct LegacyV1UnwrapRequest<'a> { + reference: &'a SecretRef, + wrapped: &'a WrappedSecret, + _authority: &'a LegacyV1ResealAuthority, +} + +impl<'a> LegacyV1UnwrapRequest<'a> { + pub(crate) const fn new( + reference: &'a SecretRef, + wrapped: &'a WrappedSecret, + authority: &'a LegacyV1ResealAuthority, + ) -> Self { + Self { + reference, + wrapped, + _authority: authority, + } + } + + /// Returns the exact legacy provider capability reference. + #[must_use] + pub const fn reference(&self) -> &'a SecretRef { + self.reference + } + + /// Returns the exact provider-wrapped v1 value. + #[must_use] + pub const fn wrapped(&self) -> &'a WrappedSecret { + self.wrapped + } +} + +impl fmt::Debug for LegacyV1UnwrapRequest<'_> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("LegacyV1UnwrapRequest(<redacted>)") + } +} + impl<'a> UnwrapRequest<'a> { /// Creates an explicit unwrapping request. #[must_use] @@ -206,6 +246,14 @@ pub trait KeyWrapping: Send + Sync { &'a self, request: UnwrapRequest<'a>, ) -> BoxFuture<'a, Result<SecretMaterial, Error>>; + + /// Unwraps v1 material only when the envelope migration boundary grants authority. + fn unwrap_legacy_v1<'a>( + &'a self, + _request: LegacyV1UnwrapRequest<'a>, + ) -> BoxFuture<'a, Result<SecretMaterial, Error>> { + Box::pin(async { Err(Error::LegacyEnvelopeDenied) }) + } } #[cfg(test)] diff --git a/crates/secrets/tests/envelope_contract.rs b/crates/secrets/tests/envelope_contract.rs @@ -3,12 +3,12 @@ use radroots_secrets::context::{ EnvelopeContext, EnvelopePurpose, EnvelopeSubject, PayloadSchemaId, }; use radroots_secrets::envelope::{ - Cipher, ENVELOPE_VERSION, KeySource, Nonce, SealMaterial, SealRequest, + Cipher, ENVELOPE_VERSION, KeySource, LegacyV1ResealAuthority, Nonce, SealMaterial, SealRequest, }; use radroots_secrets::error::Operation; use radroots_secrets::id::{BackendKind, KeyVersion}; use radroots_secrets::wrapping::{ - BoxFuture, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret, + BoxFuture, LegacyV1UnwrapRequest, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret, }; use radroots_secrets::{EncryptedEnvelope, Error, KeyWrapping, SecretId, SecretRef}; @@ -50,6 +50,27 @@ impl KeyWrapping for VectorWrapping { SecretMaterial::from_slice(plaintext.as_slice()) }) } + + fn unwrap_legacy_v1<'a>( + &'a self, + request: LegacyV1UnwrapRequest<'a>, + ) -> BoxFuture<'a, Result<SecretMaterial, Error>> { + Box::pin(async move { + if request.reference().id().as_str() != "envelope-key" { + return Err(Error::BackendFailure { + backend: BackendKind::Memory, + operation: Operation::Unwrap, + }); + } + let plaintext = request + .wrapped() + .as_bytes() + .iter() + .map(|byte| byte ^ 0x5A) + .collect::<Vec<_>>(); + SecretMaterial::from_slice(plaintext.as_slice()) + }) + } } fn reference() -> SecretRef { @@ -169,6 +190,106 @@ fn normal_open_denies_the_frozen_v1_corpus() { } #[test] +fn explicit_legacy_open_and_fresh_reseal_preserve_plaintext() { + let legacy = hex::decode("52525331000101010100000007000c656e76656c6f70652d6b6579222222222222222222222222222222222222222222222222000000204b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b00000028f106837e33d690e7c5287abdd815ce9257b7b5b176ea9596abf3b7fe745aec5a8c2487a553d4659d").expect("legacy hex"); + let envelope = EncryptedEnvelope::decode(&legacy).expect("legacy decode"); + let authority = LegacyV1ResealAuthority::new(); + let expected_reference = reference(); + let opened = block_on(envelope.open_legacy_v1( + &VectorWrapping, + &authority, + &expected_reference, + &context(), + )) + .expect("legacy open"); + opened.expose_secret(|bytes| assert_eq!(bytes, b"radroots envelope vector")); + + let result = block_on(envelope.reseal_legacy_v1( + &VectorWrapping, + &authority, + &expected_reference, + reference(), + context(), + &|bytes: &[u8]| bytes == b"radroots envelope vector", + SealMaterial::new( + SecretMaterial::from_slice(&[0x33; 32]).expect("fresh key"), + Nonce::new([0x44; 24]), + ), + )) + .expect("legacy reseal"); + assert_eq!(result.envelope().version(), ENVELOPE_VERSION); + assert_ne!(result.plaintext_commitment(), &[0; 32]); + let resealed = result.into_envelope(); + let opened = block_on(resealed.open(&VectorWrapping, &context())).expect("open reseal"); + opened.expose_secret(|bytes| assert_eq!(bytes, b"radroots envelope vector")); +} + +#[test] +fn legacy_reseal_rejects_wrong_authority_inputs_validation_and_entropy_reuse() { + let legacy = hex::decode("52525331000101010100000007000c656e76656c6f70652d6b6579222222222222222222222222222222222222222222222222000000204b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b00000028f106837e33d690e7c5287abdd815ce9257b7b5b176ea9596abf3b7fe745aec5a8c2487a553d4659d").expect("legacy hex"); + let envelope = EncryptedEnvelope::decode(&legacy).expect("legacy decode"); + let authority = LegacyV1ResealAuthority::new(); + let wrong_reference = SecretRef::new( + SecretId::parse("wrong-key").expect("id"), + BackendKind::Memory, + KeyVersion::new(7).expect("version"), + ); + assert_eq!( + block_on(envelope.open_legacy_v1( + &VectorWrapping, + &authority, + &wrong_reference, + &context(), + )) + .err(), + Some(Error::ProviderReferenceMismatch) + ); + + let expected_reference = reference(); + let invalid = block_on(envelope.reseal_legacy_v1( + &VectorWrapping, + &authority, + &expected_reference, + reference(), + context(), + &|_: &[u8]| false, + SealMaterial::new( + SecretMaterial::from_slice(&[0x33; 32]).expect("fresh key"), + Nonce::new([0x44; 24]), + ), + )); + assert!(matches!(invalid, Err(Error::LegacyPayloadValidationFailed))); + + let reused_nonce = block_on(envelope.reseal_legacy_v1( + &VectorWrapping, + &authority, + &expected_reference, + reference(), + context(), + &|_: &[u8]| true, + SealMaterial::new( + SecretMaterial::from_slice(&[0x33; 32]).expect("fresh key"), + Nonce::new([0x22; 24]), + ), + )); + assert!(matches!(reused_nonce, Err(Error::LegacyEntropyReuse))); + + let reused_key = block_on(envelope.reseal_legacy_v1( + &VectorWrapping, + &authority, + &expected_reference, + reference(), + context(), + &|_: &[u8]| true, + SealMaterial::new( + SecretMaterial::from_slice(&[0x11; 32]).expect("legacy key"), + Nonce::new([0x44; 24]), + ), + )); + assert!(matches!(reused_key, Err(Error::LegacyEntropyReuse))); +} + +#[test] fn wrong_key_slot_and_invalid_version_fail_closed() { let encoded = seal(b"slot-bound plaintext").encode().expect("encode"); let id_offset = 4 + 2 + 1 + 1 + 1 + 4 + 2; diff --git a/crates/secrets/tests/package_boundary.rs b/crates/secrets/tests/package_boundary.rs @@ -43,6 +43,7 @@ fn manifest_has_final_identity_features_and_no_radroots_dependencies() { "keyring", "serde", "sha2", + "subtle", "tempfile", "zeroize" ]) diff --git a/crates/secrets/tests/security_contract.rs b/crates/secrets/tests/security_contract.rs @@ -36,6 +36,8 @@ fn reviewed_api_forbids_secret_bearing_clone_serialize_and_byte_access() { "radroots_secrets::id::SecretRef", "radroots_secrets::envelope::SealMaterial", "radroots_secrets::envelope::SealRequest", + "radroots_secrets::envelope::LegacyV1ResealAuthority", + "radroots_secrets::wrapping::LegacyV1UnwrapRequest", ] { for forbidden_trait in ["core::clone::Clone", "serde_core::ser::Serialize"] { let forbidden = format!("impl {forbidden_trait} for {secret_bearing_type}"); diff --git a/docs/api/radroots_secrets.txt b/docs/api/radroots_secrets.txt @@ -58,7 +58,9 @@ pub fn radroots_secrets::envelope::EncryptedEnvelope::decode(&[u8]) -> core::res pub fn radroots_secrets::envelope::EncryptedEnvelope::encode(&self) -> core::result::Result<alloc::vec::Vec<u8>, radroots_secrets::error::Error> pub const fn radroots_secrets::envelope::EncryptedEnvelope::key_source(&self) -> radroots_secrets::envelope::KeySource pub async fn radroots_secrets::envelope::EncryptedEnvelope::open(&self, &dyn radroots_secrets::wrapping::KeyWrapping, &radroots_secrets::context::EnvelopeContext) -> core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error> +pub async fn radroots_secrets::envelope::EncryptedEnvelope::open_legacy_v1(&self, &dyn radroots_secrets::wrapping::KeyWrapping, &radroots_secrets::envelope::LegacyV1ResealAuthority, &radroots_secrets::id::SecretRef, &radroots_secrets::context::EnvelopeContext) -> core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error> pub const fn radroots_secrets::envelope::EncryptedEnvelope::reference(&self) -> &radroots_secrets::id::SecretRef +pub async fn radroots_secrets::envelope::EncryptedEnvelope::reseal_legacy_v1<V>(&self, &dyn radroots_secrets::wrapping::KeyWrapping, &radroots_secrets::envelope::LegacyV1ResealAuthority, &radroots_secrets::id::SecretRef, radroots_secrets::id::SecretRef, radroots_secrets::context::EnvelopeContext, &V, radroots_secrets::envelope::SealMaterial) -> core::result::Result<radroots_secrets::envelope::LegacyV1ResealResult, radroots_secrets::error::Error> where V: core::ops::function::Fn(&[u8]) -> bool + core::marker::Send + core::marker::Sync pub async fn radroots_secrets::envelope::EncryptedEnvelope::seal(&dyn radroots_secrets::wrapping::KeyWrapping, radroots_secrets::envelope::SealRequest<'_>) -> core::result::Result<Self, radroots_secrets::error::Error> pub const fn radroots_secrets::envelope::EncryptedEnvelope::version(&self) -> u16 impl core::fmt::Debug for radroots_secrets::envelope::EncryptedEnvelope @@ -67,6 +69,18 @@ impl serde_core::ser::Serialize for radroots_secrets::envelope::EncryptedEnvelop pub fn radroots_secrets::envelope::EncryptedEnvelope::serialize<S>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error> where S: serde_core::ser::Serializer impl<'de> serde_core::de::Deserialize<'de> for radroots_secrets::envelope::EncryptedEnvelope pub fn radroots_secrets::envelope::EncryptedEnvelope::deserialize<D>(D) -> core::result::Result<Self, <D as serde_core::de::Deserializer>::Error> where D: serde_core::de::Deserializer<'de> +pub struct radroots_secrets::envelope::LegacyV1ResealAuthority +impl radroots_secrets::envelope::LegacyV1ResealAuthority +pub const fn radroots_secrets::envelope::LegacyV1ResealAuthority::new() -> Self +impl core::fmt::Debug for radroots_secrets::envelope::LegacyV1ResealAuthority +pub fn radroots_secrets::envelope::LegacyV1ResealAuthority::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub struct radroots_secrets::envelope::LegacyV1ResealResult +impl radroots_secrets::envelope::LegacyV1ResealResult +pub const fn radroots_secrets::envelope::LegacyV1ResealResult::envelope(&self) -> &radroots_secrets::envelope::EncryptedEnvelope +pub fn radroots_secrets::envelope::LegacyV1ResealResult::into_envelope(self) -> radroots_secrets::envelope::EncryptedEnvelope +pub const fn radroots_secrets::envelope::LegacyV1ResealResult::plaintext_commitment(&self) -> &[u8; 32] +impl core::fmt::Debug for radroots_secrets::envelope::LegacyV1ResealResult +pub fn radroots_secrets::envelope::LegacyV1ResealResult::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result pub struct radroots_secrets::envelope::Nonce(_) impl radroots_secrets::envelope::Nonce pub const fn radroots_secrets::envelope::Nonce::as_bytes(&self) -> &[u8; 24] @@ -132,10 +146,13 @@ pub radroots_secrets::error::Error::InvalidServiceName pub radroots_secrets::error::Error::InvalidWrappedLength pub radroots_secrets::error::Error::InvalidWrappedLength::actual_bytes: usize pub radroots_secrets::error::Error::InvalidWrappedLength::max_bytes: usize +pub radroots_secrets::error::Error::LegacyEntropyReuse pub radroots_secrets::error::Error::LegacyEnvelopeDenied +pub radroots_secrets::error::Error::LegacyPayloadValidationFailed pub radroots_secrets::error::Error::PolicyUnsupported pub radroots_secrets::error::Error::PolicyUnsupported::backend: radroots_secrets::id::BackendKind pub radroots_secrets::error::Error::PolicyUnsupported::requirement: radroots_secrets::error::PolicyRequirement +pub radroots_secrets::error::Error::ProviderReferenceMismatch pub radroots_secrets::error::Error::SecretAlreadyExists pub radroots_secrets::error::Error::SecretAlreadyExists::backend: radroots_secrets::id::BackendKind pub radroots_secrets::error::Error::SecretAlreadyExists::key_version: u32 @@ -196,6 +213,7 @@ pub fn radroots_secrets::file::FileProvider::backend_kind(&self) -> radroots_sec pub fn radroots_secrets::file::FileProvider::capabilities(&self) -> radroots_secrets::provider::SecretCapabilities impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::file::FileProvider pub fn radroots_secrets::file::FileProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::file::FileProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::file::FileProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> pub mod radroots_secrets::id #[non_exhaustive] pub enum radroots_secrets::id::BackendKind @@ -245,6 +263,7 @@ pub fn radroots_secrets::keyring::KeyringProvider::backend_kind(&self) -> radroo pub fn radroots_secrets::keyring::KeyringProvider::capabilities(&self) -> radroots_secrets::provider::SecretCapabilities impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::keyring::KeyringProvider pub fn radroots_secrets::keyring::KeyringProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::keyring::KeyringProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::keyring::KeyringProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> pub mod radroots_secrets::memory pub struct radroots_secrets::memory::MemoryProvider @@ -261,6 +280,7 @@ pub fn radroots_secrets::memory::MemoryProvider::backend_kind(&self) -> radroots pub fn radroots_secrets::memory::MemoryProvider::capabilities(&self) -> radroots_secrets::provider::SecretCapabilities impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::memory::MemoryProvider pub fn radroots_secrets::memory::MemoryProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::memory::MemoryProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::memory::MemoryProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> pub mod radroots_secrets::provider #[non_exhaustive] pub enum radroots_secrets::provider::CapabilitySupport @@ -309,6 +329,12 @@ impl radroots_secrets::provider::SecretProvider for radroots_secrets::memory::Me pub fn radroots_secrets::memory::MemoryProvider::backend_kind(&self) -> radroots_secrets::id::BackendKind pub fn radroots_secrets::memory::MemoryProvider::capabilities(&self) -> radroots_secrets::provider::SecretCapabilities pub mod radroots_secrets::wrapping +pub struct radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a> +impl<'a> radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a> +pub const fn radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>::reference(&self) -> &'a radroots_secrets::id::SecretRef +pub const fn radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>::wrapped(&self) -> &'a radroots_secrets::wrapping::WrappedSecret +impl core::fmt::Debug for radroots_secrets::wrapping::LegacyV1UnwrapRequest<'_> +pub fn radroots_secrets::wrapping::LegacyV1UnwrapRequest<'_>::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result pub struct radroots_secrets::wrapping::SecretMaterial(_) impl radroots_secrets::wrapping::SecretMaterial pub fn radroots_secrets::wrapping::SecretMaterial::expose_secret<T>(&self, impl core::ops::function::FnOnce(&[u8]) -> T) -> T @@ -339,15 +365,19 @@ pub const radroots_secrets::wrapping::SECRET_MATERIAL_MAX_BYTES: usize pub const radroots_secrets::wrapping::WRAPPED_SECRET_MAX_BYTES: usize pub trait radroots_secrets::wrapping::KeyWrapping: core::marker::Send + core::marker::Sync pub fn radroots_secrets::wrapping::KeyWrapping::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::wrapping::KeyWrapping::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::wrapping::KeyWrapping::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::file::FileProvider pub fn radroots_secrets::file::FileProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::file::FileProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::file::FileProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::keyring::KeyringProvider pub fn radroots_secrets::keyring::KeyringProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::keyring::KeyringProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::keyring::KeyringProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::memory::MemoryProvider pub fn radroots_secrets::memory::MemoryProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::memory::MemoryProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::memory::MemoryProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> pub type radroots_secrets::wrapping::BoxFuture<'a, T> = core::pin::Pin<alloc::boxed::Box<(dyn core::future::future::Future<Output = T> + core::marker::Send + 'a)>> #[non_exhaustive] pub enum radroots_secrets::Error @@ -382,10 +412,13 @@ pub radroots_secrets::Error::InvalidServiceName pub radroots_secrets::Error::InvalidWrappedLength pub radroots_secrets::Error::InvalidWrappedLength::actual_bytes: usize pub radroots_secrets::Error::InvalidWrappedLength::max_bytes: usize +pub radroots_secrets::Error::LegacyEntropyReuse pub radroots_secrets::Error::LegacyEnvelopeDenied +pub radroots_secrets::Error::LegacyPayloadValidationFailed pub radroots_secrets::Error::PolicyUnsupported pub radroots_secrets::Error::PolicyUnsupported::backend: radroots_secrets::id::BackendKind pub radroots_secrets::Error::PolicyUnsupported::requirement: radroots_secrets::error::PolicyRequirement +pub radroots_secrets::Error::ProviderReferenceMismatch pub radroots_secrets::Error::SecretAlreadyExists pub radroots_secrets::Error::SecretAlreadyExists::backend: radroots_secrets::id::BackendKind pub radroots_secrets::Error::SecretAlreadyExists::key_version: u32 @@ -414,7 +447,9 @@ pub fn radroots_secrets::envelope::EncryptedEnvelope::decode(&[u8]) -> core::res pub fn radroots_secrets::envelope::EncryptedEnvelope::encode(&self) -> core::result::Result<alloc::vec::Vec<u8>, radroots_secrets::error::Error> pub const fn radroots_secrets::envelope::EncryptedEnvelope::key_source(&self) -> radroots_secrets::envelope::KeySource pub async fn radroots_secrets::envelope::EncryptedEnvelope::open(&self, &dyn radroots_secrets::wrapping::KeyWrapping, &radroots_secrets::context::EnvelopeContext) -> core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error> +pub async fn radroots_secrets::envelope::EncryptedEnvelope::open_legacy_v1(&self, &dyn radroots_secrets::wrapping::KeyWrapping, &radroots_secrets::envelope::LegacyV1ResealAuthority, &radroots_secrets::id::SecretRef, &radroots_secrets::context::EnvelopeContext) -> core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error> pub const fn radroots_secrets::envelope::EncryptedEnvelope::reference(&self) -> &radroots_secrets::id::SecretRef +pub async fn radroots_secrets::envelope::EncryptedEnvelope::reseal_legacy_v1<V>(&self, &dyn radroots_secrets::wrapping::KeyWrapping, &radroots_secrets::envelope::LegacyV1ResealAuthority, &radroots_secrets::id::SecretRef, radroots_secrets::id::SecretRef, radroots_secrets::context::EnvelopeContext, &V, radroots_secrets::envelope::SealMaterial) -> core::result::Result<radroots_secrets::envelope::LegacyV1ResealResult, radroots_secrets::error::Error> where V: core::ops::function::Fn(&[u8]) -> bool + core::marker::Send + core::marker::Sync pub async fn radroots_secrets::envelope::EncryptedEnvelope::seal(&dyn radroots_secrets::wrapping::KeyWrapping, radroots_secrets::envelope::SealRequest<'_>) -> core::result::Result<Self, radroots_secrets::error::Error> pub const fn radroots_secrets::envelope::EncryptedEnvelope::version(&self) -> u16 impl core::fmt::Debug for radroots_secrets::envelope::EncryptedEnvelope @@ -448,15 +483,19 @@ impl core::fmt::Debug for radroots_secrets::id::SecretRef pub fn radroots_secrets::id::SecretRef::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result pub trait radroots_secrets::KeyWrapping: core::marker::Send + core::marker::Sync pub fn radroots_secrets::KeyWrapping::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::KeyWrapping::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::KeyWrapping::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::file::FileProvider pub fn radroots_secrets::file::FileProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::file::FileProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::file::FileProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::keyring::KeyringProvider pub fn radroots_secrets::keyring::KeyringProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::keyring::KeyringProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::keyring::KeyringProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> impl radroots_secrets::wrapping::KeyWrapping for radroots_secrets::memory::MemoryProvider pub fn radroots_secrets::memory::MemoryProvider::unwrap<'a>(&'a self, radroots_secrets::wrapping::UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> +pub fn radroots_secrets::memory::MemoryProvider::unwrap_legacy_v1<'a>(&'a self, radroots_secrets::wrapping::LegacyV1UnwrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::SecretMaterial, radroots_secrets::error::Error>> pub fn radroots_secrets::memory::MemoryProvider::wrap<'a>(&'a self, radroots_secrets::wrapping::WrapRequest<'a>) -> radroots_secrets::wrapping::BoxFuture<'a, core::result::Result<radroots_secrets::wrapping::WrappedSecret, radroots_secrets::error::Error>> pub trait radroots_secrets::SecretProvider: radroots_secrets::wrapping::KeyWrapping + core::marker::Send + core::marker::Sync pub fn radroots_secrets::SecretProvider::backend_kind(&self) -> radroots_secrets::id::BackendKind