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 }