lib

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

id.rs (6527B)


      1 //! Typed secret identifiers and references.
      2 
      3 use crate::error::{Error, SecretIdError};
      4 use alloc::string::{String, ToString};
      5 use core::fmt;
      6 use core::num::NonZeroU32;
      7 use core::str::FromStr;
      8 
      9 /// Maximum encoded length of a portable secret identifier.
     10 pub const SECRET_ID_MAX_BYTES: usize = 128;
     11 
     12 /// A validated, backend-independent secret identifier.
     13 ///
     14 /// Identifier contents are available only through [`Self::as_str`]. Ordinary
     15 /// display and debug formatting are intentionally redacted.
     16 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
     17 pub struct SecretId(String);
     18 
     19 impl SecretId {
     20     /// Parses an identifier from the portable ASCII alphabet.
     21     ///
     22     /// The first character must be alphanumeric. Remaining characters may
     23     /// additionally use `.`, `_`, `-`, and `:` separators.
     24     pub fn parse(value: impl AsRef<str>) -> Result<Self, Error> {
     25         let value = value.as_ref();
     26         if value.is_empty() {
     27             return Err(Error::InvalidSecretId(SecretIdError::Empty));
     28         }
     29         if value.len() > SECRET_ID_MAX_BYTES {
     30             return Err(Error::InvalidSecretId(SecretIdError::TooLong {
     31                 actual_bytes: value.len(),
     32                 max_bytes: SECRET_ID_MAX_BYTES,
     33             }));
     34         }
     35         for (byte_offset, character) in value.char_indices() {
     36             let valid = character.is_ascii_alphanumeric()
     37                 || (byte_offset > 0 && matches!(character, '.' | '_' | '-' | ':'));
     38             if !valid {
     39                 return Err(Error::InvalidSecretId(SecretIdError::InvalidCharacter {
     40                     byte_offset,
     41                 }));
     42             }
     43         }
     44         Ok(Self(value.to_string()))
     45     }
     46 
     47     /// Returns the validated identifier for explicit backend use.
     48     #[must_use]
     49     pub fn as_str(&self) -> &str {
     50         self.0.as_str()
     51     }
     52 }
     53 
     54 impl fmt::Debug for SecretId {
     55     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     56         formatter.write_str("SecretId(<redacted>)")
     57     }
     58 }
     59 
     60 impl fmt::Display for SecretId {
     61     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     62         formatter.write_str("<redacted secret id>")
     63     }
     64 }
     65 
     66 impl FromStr for SecretId {
     67     type Err = Error;
     68 
     69     fn from_str(value: &str) -> Result<Self, Self::Err> {
     70         Self::parse(value)
     71     }
     72 }
     73 
     74 #[cfg(feature = "serde")]
     75 impl serde::Serialize for SecretId {
     76     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     77     where
     78         S: serde::Serializer,
     79     {
     80         serializer.serialize_str(self.as_str())
     81     }
     82 }
     83 
     84 #[cfg(feature = "serde")]
     85 impl<'de> serde::Deserialize<'de> for SecretId {
     86     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     87     where
     88         D: serde::Deserializer<'de>,
     89     {
     90         let value = <String as serde::Deserialize>::deserialize(deserializer)?;
     91         Self::parse(value).map_err(serde::de::Error::custom)
     92     }
     93 }
     94 
     95 /// A provider-owned key revision.
     96 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
     97 pub struct KeyVersion(NonZeroU32);
     98 
     99 impl KeyVersion {
    100     /// Creates a non-zero key version.
    101     pub const fn new(value: u32) -> Result<Self, Error> {
    102         match NonZeroU32::new(value) {
    103             Some(value) => Ok(Self(value)),
    104             None => Err(Error::InvalidKeyVersion),
    105         }
    106     }
    107 
    108     /// Returns the numeric version.
    109     #[must_use]
    110     pub const fn get(self) -> u32 {
    111         self.0.get()
    112     }
    113 }
    114 
    115 /// The explicit adapter family that owns a secret reference.
    116 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    117 #[non_exhaustive]
    118 pub enum BackendKind {
    119     /// Deterministic in-process storage selected by the host.
    120     Memory,
    121     /// Explicit file-backed storage selected by the host.
    122     File,
    123     /// Operating-system keyring storage selected by the host.
    124     Keyring,
    125     /// A host-provided implementation outside the built-in adapters.
    126     External,
    127 }
    128 
    129 impl BackendKind {
    130     pub(crate) const fn code(self) -> u8 {
    131         match self {
    132             Self::Memory => 1,
    133             Self::File => 2,
    134             Self::Keyring => 3,
    135             Self::External => 4,
    136         }
    137     }
    138 
    139     pub(crate) const fn from_code(code: u8) -> Result<Self, Error> {
    140         match code {
    141             1 => Ok(Self::Memory),
    142             2 => Ok(Self::File),
    143             3 => Ok(Self::Keyring),
    144             4 => Ok(Self::External),
    145             backend => Err(Error::UnsupportedBackend { backend }),
    146         }
    147     }
    148 }
    149 
    150 /// A single-owner capability handle for a secret held by a provider.
    151 ///
    152 /// Cloning and ordinary serialization are intentionally unavailable. Debug
    153 /// output never reveals the identifier.
    154 ///
    155 /// ```compile_fail
    156 /// use radroots_secrets::{SecretId, SecretRef};
    157 /// use radroots_secrets::id::{BackendKind, KeyVersion};
    158 ///
    159 /// let reference = SecretRef::new(
    160 ///     SecretId::parse("account-signing-key")?,
    161 ///     BackendKind::Memory,
    162 ///     KeyVersion::new(1)?,
    163 /// );
    164 /// let _duplicate = reference.clone();
    165 /// # Ok::<(), radroots_secrets::Error>(())
    166 /// ```
    167 ///
    168 /// ```compile_fail
    169 /// use radroots_secrets::{SecretId, SecretRef};
    170 /// use radroots_secrets::id::{BackendKind, KeyVersion};
    171 ///
    172 /// let reference = SecretRef::new(
    173 ///     SecretId::parse("account-signing-key")?,
    174 ///     BackendKind::Memory,
    175 ///     KeyVersion::new(1)?,
    176 /// );
    177 /// let _json = serde_json::to_string(&reference)?;
    178 /// # Ok::<(), Box<dyn std::error::Error>>(())
    179 /// ```
    180 pub struct SecretRef {
    181     id: SecretId,
    182     backend: BackendKind,
    183     key_version: KeyVersion,
    184 }
    185 
    186 impl SecretRef {
    187     /// Creates a capability reference from validated metadata.
    188     #[must_use]
    189     pub const fn new(id: SecretId, backend: BackendKind, key_version: KeyVersion) -> Self {
    190         Self {
    191             id,
    192             backend,
    193             key_version,
    194         }
    195     }
    196 
    197     /// Returns the validated provider-local identifier.
    198     #[must_use]
    199     pub const fn id(&self) -> &SecretId {
    200         &self.id
    201     }
    202 
    203     /// Returns the adapter family that owns the secret.
    204     #[must_use]
    205     pub const fn backend(&self) -> BackendKind {
    206         self.backend
    207     }
    208 
    209     /// Returns the expected provider key version.
    210     #[must_use]
    211     pub const fn key_version(&self) -> KeyVersion {
    212         self.key_version
    213     }
    214 }
    215 
    216 impl fmt::Debug for SecretRef {
    217     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    218         formatter
    219             .debug_struct("SecretRef")
    220             .field("id", &"<redacted>")
    221             .field("backend", &self.backend)
    222             .field("key_version", &self.key_version)
    223             .finish()
    224     }
    225 }