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