lib

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

commit 5f094b213b982a2e1693618d202c9e2058a2c65f
parent 5edb693348d8656e844f4fad5a89be32dd0aa5e6
Author: triesap <tyson@radroots.org>
Date:   Wed,  5 Aug 2026 17:28:44 +0000

feat(secrets): add authenticated envelope contexts

Diffstat:
Acrates/secrets/src/context.rs | 341+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/secrets/src/error.rs | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/secrets/src/lib.rs | 1+
Acrates/secrets/tests/context_contract.rs | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/secrets/tests/package_boundary.rs | 3++-
Mcrates/secrets/tests/security_contract.rs | 1+
Mdocs/api/radroots_secrets.txt | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 563 insertions(+), 1 deletion(-)

diff --git a/crates/secrets/src/context.rs b/crates/secrets/src/context.rs @@ -0,0 +1,341 @@ +//! Validated semantic authority for encrypted envelopes. + +use crate::error::{ContextField, ContextValueError, Error}; +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt; + +/// Version of the canonical authenticated context encoding. +pub const ENVELOPE_CONTEXT_VERSION: u16 = 1; +/// Domain separator included in every canonical context encoding. +pub const ENVELOPE_CONTEXT_DOMAIN: &[u8] = b"radroots.envelope_context.v1"; +/// Maximum UTF-8 length of a purpose identifier. +pub const ENVELOPE_PURPOSE_MAX_BYTES: usize = 128; +/// Maximum UTF-8 length of a subject type discriminator. +pub const ENVELOPE_SUBJECT_TYPE_MAX_BYTES: usize = 64; +/// Maximum length of a subject's canonical bytes. +pub const ENVELOPE_SUBJECT_VALUE_MAX_BYTES: usize = 128; +/// Maximum UTF-8 length of a payload schema identifier. +pub const PAYLOAD_SCHEMA_MAX_BYTES: usize = 128; + +/// Validated, namespaced use-case identifier for protected plaintext. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct EnvelopePurpose(String); + +impl EnvelopePurpose { + /// Parses a canonical lower-case namespaced purpose. + pub fn parse(value: impl Into<String>) -> Result<Self, Error> { + let value = value.into(); + validate_namespaced( + value.as_str(), + ENVELOPE_PURPOSE_MAX_BYTES, + ContextField::Purpose, + )?; + Ok(Self(value)) + } + + /// Returns the validated identifier. + #[must_use] + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl fmt::Debug for EnvelopePurpose { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("EnvelopePurpose(<validated>)") + } +} + +impl fmt::Display for EnvelopePurpose { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("<validated envelope purpose>") + } +} + +/// Validated, typed identity of the protected object. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct EnvelopeSubject { + subject_type: String, + value: String, +} + +impl EnvelopeSubject { + /// Parses a subject type and its canonical, non-secret identity bytes. + pub fn parse(subject_type: impl Into<String>, value: impl Into<String>) -> Result<Self, Error> { + let subject_type = subject_type.into(); + let value = value.into(); + validate_label( + subject_type.as_str(), + ENVELOPE_SUBJECT_TYPE_MAX_BYTES, + ContextField::SubjectType, + )?; + validate_subject_value(value.as_str())?; + Ok(Self { + subject_type, + value, + }) + } + + /// Returns the validated type discriminator. + #[must_use] + pub fn subject_type(&self) -> &str { + self.subject_type.as_str() + } + + /// Returns the validated canonical subject value. + #[must_use] + pub fn value(&self) -> &str { + self.value.as_str() + } +} + +impl fmt::Debug for EnvelopeSubject { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("EnvelopeSubject") + .field("subject_type", &self.subject_type) + .field("value", &"<redacted>") + .finish() + } +} + +impl fmt::Display for EnvelopeSubject { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "{}:<redacted>", self.subject_type) + } +} + +/// Validated, version-bearing schema identifier for protected plaintext. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct PayloadSchemaId(String); + +impl PayloadSchemaId { + /// Parses a canonical lower-case namespaced payload schema identifier. + pub fn parse(value: impl Into<String>) -> Result<Self, Error> { + let value = value.into(); + validate_namespaced( + value.as_str(), + PAYLOAD_SCHEMA_MAX_BYTES, + ContextField::PayloadSchema, + )?; + Ok(Self(value)) + } + + /// Returns the validated identifier. + #[must_use] + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl fmt::Debug for PayloadSchemaId { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("PayloadSchemaId(<validated>)") + } +} + +impl fmt::Display for PayloadSchemaId { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("<validated payload schema>") + } +} + +/// Independently validated semantic authority authenticated by an envelope. +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct EnvelopeContext { + purpose: EnvelopePurpose, + subject: EnvelopeSubject, + payload_schema: PayloadSchemaId, +} + +impl EnvelopeContext { + /// Constructs a context only from independently validated parts. + #[must_use] + pub const fn new( + purpose: EnvelopePurpose, + subject: EnvelopeSubject, + payload_schema: PayloadSchemaId, + ) -> Self { + Self { + purpose, + subject, + payload_schema, + } + } + + /// Returns the authenticated use-case identifier. + #[must_use] + pub const fn purpose(&self) -> &EnvelopePurpose { + &self.purpose + } + + /// Returns the authenticated typed subject. + #[must_use] + pub const fn subject(&self) -> &EnvelopeSubject { + &self.subject + } + + /// Returns the authenticated payload schema identifier. + #[must_use] + pub const fn payload_schema(&self) -> &PayloadSchemaId { + &self.payload_schema + } + + /// Encodes the deterministic context wire representation used by envelope v2. + #[must_use] + pub fn to_canonical_bytes(&self) -> Vec<u8> { + let purpose = self.purpose.as_str().as_bytes(); + let subject_type = self.subject.subject_type().as_bytes(); + let subject_value = self.subject.value().as_bytes(); + let payload_schema = self.payload_schema.as_str().as_bytes(); + let mut encoded = Vec::with_capacity( + 2 + ENVELOPE_CONTEXT_DOMAIN.len() + + 2 + + purpose.len() + + 2 + + subject_type.len() + + 2 + + subject_value.len() + + 2 + + payload_schema.len(), + ); + encoded.extend_from_slice(&ENVELOPE_CONTEXT_VERSION.to_be_bytes()); + encoded.extend_from_slice(ENVELOPE_CONTEXT_DOMAIN); + push_bounded(&mut encoded, purpose); + push_bounded(&mut encoded, subject_type); + push_bounded(&mut encoded, subject_value); + push_bounded(&mut encoded, payload_schema); + encoded + } +} + +impl fmt::Debug for EnvelopeContext { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("EnvelopeContext") + .field("purpose", &self.purpose) + .field("subject", &self.subject) + .field("payload_schema", &self.payload_schema) + .finish() + } +} + +fn push_bounded(encoded: &mut Vec<u8>, value: &[u8]) { + let length = u16::try_from(value.len()).unwrap_or_else(|_| { + unreachable!("validated envelope context fields are bounded below u16::MAX") + }); + encoded.extend_from_slice(&length.to_be_bytes()); + encoded.extend_from_slice(value); +} + +fn validate_namespaced(value: &str, max: usize, field: ContextField) -> Result<(), Error> { + validate_length(value, max, field)?; + if !value.contains('.') || !value.split('.').all(valid_segment) { + return Err(invalid(field, ContextValueError::NonCanonical)); + } + Ok(()) +} + +fn validate_label(value: &str, max: usize, field: ContextField) -> Result<(), Error> { + validate_length(value, max, field)?; + if !valid_segment(value) { + return Err(invalid(field, ContextValueError::NonCanonical)); + } + Ok(()) +} + +fn validate_subject_value(value: &str) -> Result<(), Error> { + validate_length( + value, + ENVELOPE_SUBJECT_VALUE_MAX_BYTES, + ContextField::SubjectValue, + )?; + if !value.bytes().all(|byte| { + byte.is_ascii_lowercase() + || byte.is_ascii_digit() + || matches!(byte, b'_' | b'-' | b'.' | b':' | b'/') + }) { + return Err(invalid( + ContextField::SubjectValue, + ContextValueError::NonCanonical, + )); + } + Ok(()) +} + +fn validate_length(value: &str, max: usize, field: ContextField) -> Result<(), Error> { + if value.is_empty() { + return Err(invalid(field, ContextValueError::Empty)); + } + if value.len() > max { + return Err(invalid( + field, + ContextValueError::TooLong { + actual_bytes: value.len(), + max_bytes: max, + }, + )); + } + if !value.is_ascii() || value != value.trim() || value.chars().any(char::is_control) { + return Err(invalid(field, ContextValueError::NonCanonical)); + } + Ok(()) +} + +fn valid_segment(segment: &str) -> bool { + let mut bytes = segment.bytes(); + matches!(bytes.next(), Some(first) if first.is_ascii_lowercase()) + && bytes.all(|byte| { + byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'_' | b'-') + }) +} + +const fn invalid(field: ContextField, reason: ContextValueError) -> Error { + Error::InvalidContextValue { field, reason } +} + +#[cfg(feature = "serde")] +mod serde_impl { + use super::{EnvelopeContext, EnvelopePurpose, EnvelopeSubject, PayloadSchemaId}; + use alloc::string::String; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(deny_unknown_fields)] + struct WireContext { + purpose: String, + subject_type: String, + subject: String, + payload_schema: String, + } + + impl serde::Serialize for EnvelopeContext { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + WireContext { + purpose: String::from(self.purpose().as_str()), + subject_type: String::from(self.subject().subject_type()), + subject: String::from(self.subject().value()), + payload_schema: String::from(self.payload_schema().as_str()), + } + .serialize(serializer) + } + } + + impl<'de> serde::Deserialize<'de> for EnvelopeContext { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let wire = WireContext::deserialize(deserializer)?; + Ok(Self::new( + EnvelopePurpose::parse(wire.purpose).map_err(serde::de::Error::custom)?, + EnvelopeSubject::parse(wire.subject_type, wire.subject) + .map_err(serde::de::Error::custom)?, + PayloadSchemaId::parse(wire.payload_schema).map_err(serde::de::Error::custom)?, + )) + } + } +} diff --git a/crates/secrets/src/error.rs b/crates/secrets/src/error.rs @@ -23,6 +23,37 @@ pub enum SecretIdError { }, } +/// Authenticated context field rejected by validation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum ContextField { + /// Envelope use-case identifier. + Purpose, + /// Subject type discriminator. + SubjectType, + /// Canonical subject identity. + SubjectValue, + /// Plaintext schema identifier. + PayloadSchema, +} + +/// Secret-safe reason an authenticated context field was rejected. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum ContextValueError { + /// The field was empty. + Empty, + /// The field exceeded its explicit bound. + TooLong { + /// Observed UTF-8 byte length. + actual_bytes: usize, + /// Maximum accepted UTF-8 byte length. + max_bytes: usize, + }, + /// The field did not use its canonical portable representation. + NonCanonical, +} + /// A security property requested by a host but unsupported by a provider. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] @@ -63,6 +94,13 @@ pub enum Operation { pub enum Error { /// A secret identifier failed validation. InvalidSecretId(SecretIdError), + /// An authenticated envelope context field failed validation. + InvalidContextValue { + /// Rejected semantic field without its value. + field: ContextField, + /// Secret-safe validation class. + reason: ContextValueError, + }, /// Key versions start at one; zero is never a valid version. InvalidKeyVersion, /// Secret material was empty or exceeded the bounded input limit. @@ -190,6 +228,9 @@ impl fmt::Display for Error { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidSecretId(reason) => reason.fmt(formatter), + Self::InvalidContextValue { field, reason } => { + write!(formatter, "envelope context {field:?} is {reason}") + } Self::InvalidKeyVersion => formatter.write_str("secret key version must be non-zero"), Self::InvalidSecretLength { actual_bytes, @@ -290,6 +331,22 @@ impl fmt::Display for Error { } } +impl fmt::Display for ContextValueError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Empty => formatter.write_str("empty"), + Self::TooLong { + actual_bytes, + max_bytes, + } => write!( + formatter, + "too long ({actual_bytes} bytes; maximum is {max_bytes})" + ), + Self::NonCanonical => formatter.write_str("not canonical"), + } + } +} + #[cfg(feature = "std")] impl std::error::Error for Error {} @@ -312,6 +369,10 @@ mod tests { } let errors = [ Error::InvalidSecretId(SecretIdError::Empty), + Error::InvalidContextValue { + field: ContextField::SubjectValue, + reason: ContextValueError::NonCanonical, + }, Error::InvalidKeyVersion, Error::InvalidSecretLength { actual_bytes: 0, diff --git a/crates/secrets/src/lib.rs b/crates/secrets/src/lib.rs @@ -6,6 +6,7 @@ extern crate alloc; +pub mod context; pub mod envelope; pub mod error; #[cfg(feature = "file")] diff --git a/crates/secrets/tests/context_contract.rs b/crates/secrets/tests/context_contract.rs @@ -0,0 +1,93 @@ +use radroots_secrets::Error; +use radroots_secrets::context::{ + ENVELOPE_CONTEXT_DOMAIN, ENVELOPE_CONTEXT_VERSION, ENVELOPE_PURPOSE_MAX_BYTES, + ENVELOPE_SUBJECT_TYPE_MAX_BYTES, ENVELOPE_SUBJECT_VALUE_MAX_BYTES, EnvelopeContext, + EnvelopePurpose, EnvelopeSubject, PAYLOAD_SCHEMA_MAX_BYTES, PayloadSchemaId, +}; +use radroots_secrets::error::{ContextField, ContextValueError}; + +fn context() -> EnvelopeContext { + EnvelopeContext::new( + EnvelopePurpose::parse("radroots.private_artifact").expect("purpose"), + EnvelopeSubject::parse("private_artifact", "01010101010101010101010101010101") + .expect("subject"), + PayloadSchemaId::parse("trade.private_terms.v1").expect("schema"), + ) +} + +#[test] +fn context_encoding_is_canonical_and_domain_separated() { + let encoded = context().to_canonical_bytes(); + assert_eq!(&encoded[..2], &ENVELOPE_CONTEXT_VERSION.to_be_bytes()); + assert_eq!( + &encoded[2..2 + ENVELOPE_CONTEXT_DOMAIN.len()], + ENVELOPE_CONTEXT_DOMAIN + ); + assert_eq!(encoded, context().to_canonical_bytes()); +} + +#[test] +fn context_parts_accept_exact_boundaries() { + let purpose = format!("a.{}", "b".repeat(ENVELOPE_PURPOSE_MAX_BYTES - 2)); + let subject_type = format!("a{}", "b".repeat(ENVELOPE_SUBJECT_TYPE_MAX_BYTES - 1)); + let subject_value = "a".repeat(ENVELOPE_SUBJECT_VALUE_MAX_BYTES); + let schema = format!("a.{}", "b".repeat(PAYLOAD_SCHEMA_MAX_BYTES - 2)); + assert!(EnvelopePurpose::parse(purpose).is_ok()); + assert!(EnvelopeSubject::parse(subject_type, subject_value).is_ok()); + assert!(PayloadSchemaId::parse(schema).is_ok()); +} + +#[test] +fn invalid_context_is_rejected_without_echoing_values() { + let cases = [ + ( + EnvelopePurpose::parse("").err(), + ContextField::Purpose, + ContextValueError::Empty, + ), + ( + EnvelopePurpose::parse("Not.namespaced").err(), + ContextField::Purpose, + ContextValueError::NonCanonical, + ), + ( + EnvelopeSubject::parse("private artifact", "subject").err(), + ContextField::SubjectType, + ContextValueError::NonCanonical, + ), + ( + EnvelopeSubject::parse("private_artifact", "SUBJECT-SECRET").err(), + ContextField::SubjectValue, + ContextValueError::NonCanonical, + ), + ( + PayloadSchemaId::parse("schema\n.v1").err(), + ContextField::PayloadSchema, + ContextValueError::NonCanonical, + ), + ]; + for (error, field, reason) in cases { + let error = error.expect("invalid context"); + assert_eq!(error, Error::InvalidContextValue { field, reason }); + assert!(!error.to_string().contains("SUBJECT-SECRET")); + } +} + +#[test] +fn diagnostics_redact_semantic_values() { + let context = context(); + let debug = format!("{context:?}"); + assert!(debug.contains("private_artifact")); + assert!(!debug.contains("01010101010101010101010101010101")); + assert!(!debug.contains("trade.private_terms.v1")); +} + +#[cfg(feature = "serde")] +#[test] +fn serde_revalidates_context_parts() { + let encoded = serde_json::to_vec(&context()).expect("serialize"); + let decoded: EnvelopeContext = serde_json::from_slice(&encoded).expect("deserialize"); + assert_eq!(decoded, context()); + let invalid = br#"{"purpose":"radroots.private_artifact","subject_type":"private_artifact","subject":"INVALID","payload_schema":"trade.private_terms.v1"}"#; + assert!(serde_json::from_slice::<EnvelopeContext>(invalid).is_err()); +} diff --git a/crates/secrets/tests/package_boundary.rs b/crates/secrets/tests/package_boundary.rs @@ -88,7 +88,8 @@ fn crate_root_contains_only_the_approved_module_skeleton() { assert_eq!( declarations(ROOT, "pub mod "), BTreeSet::from([ - "envelope", "error", "file", "id", "keyring", "memory", "provider", "wrapping", + "context", "envelope", "error", "file", "id", "keyring", "memory", "provider", + "wrapping", ]) ); assert_eq!( diff --git a/crates/secrets/tests/security_contract.rs b/crates/secrets/tests/security_contract.rs @@ -11,6 +11,7 @@ const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_secrets.txt"); #[test] fn reviewed_api_forbids_secret_bearing_clone_serialize_and_byte_access() { for required in [ + "pub mod radroots_secrets::context", "pub mod radroots_secrets::envelope", "pub mod radroots_secrets::error", "pub mod radroots_secrets::id", diff --git a/docs/api/radroots_secrets.txt b/docs/api/radroots_secrets.txt @@ -1,4 +1,49 @@ pub mod radroots_secrets +pub mod radroots_secrets::context +pub struct radroots_secrets::context::EnvelopeContext +impl radroots_secrets::context::EnvelopeContext +pub const fn radroots_secrets::context::EnvelopeContext::new(radroots_secrets::context::EnvelopePurpose, radroots_secrets::context::EnvelopeSubject, radroots_secrets::context::PayloadSchemaId) -> Self +pub const fn radroots_secrets::context::EnvelopeContext::payload_schema(&self) -> &radroots_secrets::context::PayloadSchemaId +pub const fn radroots_secrets::context::EnvelopeContext::purpose(&self) -> &radroots_secrets::context::EnvelopePurpose +pub const fn radroots_secrets::context::EnvelopeContext::subject(&self) -> &radroots_secrets::context::EnvelopeSubject +pub fn radroots_secrets::context::EnvelopeContext::to_canonical_bytes(&self) -> alloc::vec::Vec<u8> +impl core::fmt::Debug for radroots_secrets::context::EnvelopeContext +pub fn radroots_secrets::context::EnvelopeContext::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl serde_core::ser::Serialize for radroots_secrets::context::EnvelopeContext +pub fn radroots_secrets::context::EnvelopeContext::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::context::EnvelopeContext +pub fn radroots_secrets::context::EnvelopeContext::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::context::EnvelopePurpose(_) +impl radroots_secrets::context::EnvelopePurpose +pub fn radroots_secrets::context::EnvelopePurpose::as_str(&self) -> &str +pub fn radroots_secrets::context::EnvelopePurpose::parse(impl core::convert::Into<alloc::string::String>) -> core::result::Result<Self, radroots_secrets::error::Error> +impl core::fmt::Debug for radroots_secrets::context::EnvelopePurpose +pub fn radroots_secrets::context::EnvelopePurpose::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for radroots_secrets::context::EnvelopePurpose +pub fn radroots_secrets::context::EnvelopePurpose::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub struct radroots_secrets::context::EnvelopeSubject +impl radroots_secrets::context::EnvelopeSubject +pub fn radroots_secrets::context::EnvelopeSubject::parse(impl core::convert::Into<alloc::string::String>, impl core::convert::Into<alloc::string::String>) -> core::result::Result<Self, radroots_secrets::error::Error> +pub fn radroots_secrets::context::EnvelopeSubject::subject_type(&self) -> &str +pub fn radroots_secrets::context::EnvelopeSubject::value(&self) -> &str +impl core::fmt::Debug for radroots_secrets::context::EnvelopeSubject +pub fn radroots_secrets::context::EnvelopeSubject::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for radroots_secrets::context::EnvelopeSubject +pub fn radroots_secrets::context::EnvelopeSubject::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub struct radroots_secrets::context::PayloadSchemaId(_) +impl radroots_secrets::context::PayloadSchemaId +pub fn radroots_secrets::context::PayloadSchemaId::as_str(&self) -> &str +pub fn radroots_secrets::context::PayloadSchemaId::parse(impl core::convert::Into<alloc::string::String>) -> core::result::Result<Self, radroots_secrets::error::Error> +impl core::fmt::Debug for radroots_secrets::context::PayloadSchemaId +pub fn radroots_secrets::context::PayloadSchemaId::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for radroots_secrets::context::PayloadSchemaId +pub fn radroots_secrets::context::PayloadSchemaId::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub const radroots_secrets::context::ENVELOPE_CONTEXT_DOMAIN: &[u8] +pub const radroots_secrets::context::ENVELOPE_CONTEXT_VERSION: u16 +pub const radroots_secrets::context::ENVELOPE_PURPOSE_MAX_BYTES: usize +pub const radroots_secrets::context::ENVELOPE_SUBJECT_TYPE_MAX_BYTES: usize +pub const radroots_secrets::context::ENVELOPE_SUBJECT_VALUE_MAX_BYTES: usize +pub const radroots_secrets::context::PAYLOAD_SCHEMA_MAX_BYTES: usize pub mod radroots_secrets::envelope #[non_exhaustive] pub enum radroots_secrets::envelope::Cipher pub radroots_secrets::envelope::Cipher::XChaCha20Poly1305 @@ -39,6 +84,19 @@ pub fn radroots_secrets::envelope::SealRequest<'_>::fmt(&self, &mut core::fmt::F pub const radroots_secrets::envelope::ENVELOPE_MAX_BYTES: usize pub const radroots_secrets::envelope::ENVELOPE_VERSION: u16 pub mod radroots_secrets::error +#[non_exhaustive] pub enum radroots_secrets::error::ContextField +pub radroots_secrets::error::ContextField::PayloadSchema +pub radroots_secrets::error::ContextField::Purpose +pub radroots_secrets::error::ContextField::SubjectType +pub radroots_secrets::error::ContextField::SubjectValue +#[non_exhaustive] pub enum radroots_secrets::error::ContextValueError +pub radroots_secrets::error::ContextValueError::Empty +pub radroots_secrets::error::ContextValueError::NonCanonical +pub radroots_secrets::error::ContextValueError::TooLong +pub radroots_secrets::error::ContextValueError::TooLong::actual_bytes: usize +pub radroots_secrets::error::ContextValueError::TooLong::max_bytes: usize +impl core::fmt::Display for radroots_secrets::error::ContextValueError +pub fn radroots_secrets::error::ContextValueError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result #[non_exhaustive] pub enum radroots_secrets::error::Error pub radroots_secrets::error::Error::BackendFailure pub radroots_secrets::error::Error::BackendFailure::backend: radroots_secrets::id::BackendKind @@ -55,6 +113,9 @@ pub radroots_secrets::error::Error::EnvelopeTooLarge pub radroots_secrets::error::Error::EnvelopeTooLarge::actual_bytes: usize pub radroots_secrets::error::Error::EnvelopeTooLarge::max_bytes: usize pub radroots_secrets::error::Error::InsecurePermissions +pub radroots_secrets::error::Error::InvalidContextValue +pub radroots_secrets::error::Error::InvalidContextValue::field: radroots_secrets::error::ContextField +pub radroots_secrets::error::Error::InvalidContextValue::reason: radroots_secrets::error::ContextValueError pub radroots_secrets::error::Error::InvalidDataKeyLength pub radroots_secrets::error::Error::InvalidDataKeyLength::actual_bytes: usize pub radroots_secrets::error::Error::InvalidKeyVersion @@ -296,6 +357,9 @@ pub radroots_secrets::Error::EnvelopeTooLarge pub radroots_secrets::Error::EnvelopeTooLarge::actual_bytes: usize pub radroots_secrets::Error::EnvelopeTooLarge::max_bytes: usize pub radroots_secrets::Error::InsecurePermissions +pub radroots_secrets::Error::InvalidContextValue +pub radroots_secrets::Error::InvalidContextValue::field: radroots_secrets::error::ContextField +pub radroots_secrets::Error::InvalidContextValue::reason: radroots_secrets::error::ContextValueError pub radroots_secrets::Error::InvalidDataKeyLength pub radroots_secrets::Error::InvalidDataKeyLength::actual_bytes: usize pub radroots_secrets::Error::InvalidKeyVersion