lib

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

memory.rs (8246B)


      1 //! Deterministic in-process secret adapters.
      2 
      3 use crate::SecretRef;
      4 use crate::error::{Error, Operation};
      5 use crate::id::BackendKind;
      6 use crate::provider::{CapabilitySupport, ResidencySupport, SecretCapabilities, SecretProvider};
      7 use crate::wrapping::{
      8     BoxFuture, KeyWrapping, LegacyV1UnwrapRequest, SecretMaterial, UnwrapRequest, WrapRequest,
      9     WrappedSecret,
     10 };
     11 use alloc::collections::BTreeMap;
     12 use alloc::string::{String, ToString};
     13 use alloc::vec::Vec;
     14 use core::fmt;
     15 use std::sync::RwLock;
     16 
     17 const TOKEN_MAGIC: &[u8] = b"radroots-memory-key-v1\0";
     18 
     19 /// Explicit-lifecycle, in-process provider for development and tests.
     20 #[derive(Default)]
     21 pub struct MemoryProvider {
     22     entries: RwLock<BTreeMap<MemoryKey, SecretMaterial>>,
     23 }
     24 
     25 impl MemoryProvider {
     26     /// Creates an empty provider without generating or installing keys.
     27     #[must_use]
     28     pub fn new() -> Self {
     29         Self::default()
     30     }
     31 
     32     /// Installs caller-owned material at one exact reference.
     33     pub fn provision(&self, reference: &SecretRef, material: SecretMaterial) -> Result<(), Error> {
     34         validate_memory_reference(reference)?;
     35         let key = MemoryKey::from_reference(reference);
     36         let mut entries = self
     37             .entries
     38             .write()
     39             .map_err(|_| backend_failure(Operation::Provision))?;
     40         if entries.contains_key(&key) {
     41             return Err(Error::SecretAlreadyExists {
     42                 backend: BackendKind::Memory,
     43                 key_version: reference.key_version().get(),
     44             });
     45         }
     46         entries.insert(key, material);
     47         Ok(())
     48     }
     49 
     50     /// Atomically replaces one version with a higher version of the same ID.
     51     pub fn rotate(
     52         &self,
     53         current: &SecretRef,
     54         next: &SecretRef,
     55         material: SecretMaterial,
     56     ) -> Result<(), Error> {
     57         validate_memory_reference(current)?;
     58         validate_memory_reference(next)?;
     59         if current.id().as_str() != next.id().as_str()
     60             || next.key_version() <= current.key_version()
     61         {
     62             return Err(Error::InvalidRotation);
     63         }
     64         let current_key = MemoryKey::from_reference(current);
     65         let next_key = MemoryKey::from_reference(next);
     66         let mut entries = self
     67             .entries
     68             .write()
     69             .map_err(|_| backend_failure(Operation::Rotate))?;
     70         if !entries.contains_key(&current_key) {
     71             return Err(not_found(current));
     72         }
     73         if entries.contains_key(&next_key) {
     74             return Err(Error::SecretAlreadyExists {
     75                 backend: BackendKind::Memory,
     76                 key_version: next.key_version().get(),
     77             });
     78         }
     79         entries.remove(&current_key);
     80         entries.insert(next_key, material);
     81         Ok(())
     82     }
     83 
     84     /// Removes and zeroizes provider-owned material when present.
     85     pub fn remove(&self, reference: &SecretRef) -> Result<bool, Error> {
     86         validate_memory_reference(reference)?;
     87         self.entries
     88             .write()
     89             .map(|mut entries| {
     90                 entries
     91                     .remove(&MemoryKey::from_reference(reference))
     92                     .is_some()
     93             })
     94             .map_err(|_| backend_failure(Operation::Remove))
     95     }
     96 
     97     /// Reports whether one exact reference is provisioned.
     98     pub fn contains(&self, reference: &SecretRef) -> Result<bool, Error> {
     99         validate_memory_reference(reference)?;
    100         self.entries
    101             .read()
    102             .map(|entries| entries.contains_key(&MemoryKey::from_reference(reference)))
    103             .map_err(|_| backend_failure(Operation::Unwrap))
    104     }
    105 
    106     fn wrapped_token(
    107         reference: &SecretRef,
    108         context: &crate::context::EnvelopeContext,
    109     ) -> Result<WrappedSecret, Error> {
    110         let mut token = Self::legacy_wrapped_token(reference)?;
    111         token.extend_from_slice(&context.authentication_digest());
    112         WrappedSecret::from_bytes(token)
    113     }
    114 
    115     fn legacy_wrapped_token(reference: &SecretRef) -> Result<Vec<u8>, Error> {
    116         let id = reference.id().as_str().as_bytes();
    117         let mut token = Vec::with_capacity(TOKEN_MAGIC.len() + 4 + 2 + id.len());
    118         token.extend_from_slice(TOKEN_MAGIC);
    119         token.extend_from_slice(&reference.key_version().get().to_be_bytes());
    120         let id_len = u16::try_from(id.len()).map_err(|_| backend_failure(Operation::Wrap))?;
    121         token.extend_from_slice(&id_len.to_be_bytes());
    122         token.extend_from_slice(id);
    123         Ok(token)
    124     }
    125 
    126     fn clone_material(
    127         &self,
    128         reference: &SecretRef,
    129         operation: Operation,
    130     ) -> Result<SecretMaterial, Error> {
    131         let entries = self
    132             .entries
    133             .read()
    134             .map_err(|_| backend_failure(operation))?;
    135         let material = entries
    136             .get(&MemoryKey::from_reference(reference))
    137             .ok_or_else(|| not_found(reference))?;
    138         material.expose_secret(SecretMaterial::from_slice)
    139     }
    140 }
    141 
    142 impl fmt::Debug for MemoryProvider {
    143     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    144         formatter.write_str("MemoryProvider(<redacted>)")
    145     }
    146 }
    147 
    148 impl KeyWrapping for MemoryProvider {
    149     fn wrap<'a>(&'a self, request: WrapRequest<'a>) -> BoxFuture<'a, Result<WrappedSecret, Error>> {
    150         Box::pin(async move {
    151             validate_memory_reference(request.reference())?;
    152             let provisioned = self.clone_material(request.reference(), Operation::Wrap)?;
    153             let matches = provisioned.expose_secret(|expected| {
    154                 request
    155                     .plaintext()
    156                     .expose_secret(|actual| expected == actual)
    157             });
    158             if !matches {
    159                 return Err(backend_failure(Operation::Wrap));
    160             }
    161             Self::wrapped_token(request.reference(), request.context())
    162         })
    163     }
    164 
    165     fn unwrap<'a>(
    166         &'a self,
    167         request: UnwrapRequest<'a>,
    168     ) -> BoxFuture<'a, Result<SecretMaterial, Error>> {
    169         Box::pin(async move {
    170             validate_memory_reference(request.reference())?;
    171             let expected = Self::wrapped_token(request.reference(), request.context())?;
    172             if expected.as_bytes() != request.wrapped().as_bytes() {
    173                 return Err(backend_failure(Operation::Unwrap));
    174             }
    175             self.clone_material(request.reference(), Operation::Unwrap)
    176         })
    177     }
    178 
    179     fn unwrap_legacy_v1<'a>(
    180         &'a self,
    181         request: LegacyV1UnwrapRequest<'a>,
    182     ) -> BoxFuture<'a, Result<SecretMaterial, Error>> {
    183         Box::pin(async move {
    184             validate_memory_reference(request.reference())?;
    185             if request.wrapped().as_bytes()
    186                 != Self::legacy_wrapped_token(request.reference())?.as_slice()
    187             {
    188                 return Err(backend_failure(Operation::Unwrap));
    189             }
    190             self.clone_material(request.reference(), Operation::Unwrap)
    191         })
    192     }
    193 }
    194 
    195 impl SecretProvider for MemoryProvider {
    196     fn backend_kind(&self) -> BackendKind {
    197         BackendKind::Memory
    198     }
    199 
    200     fn capabilities(&self) -> SecretCapabilities {
    201         SecretCapabilities::available(
    202             ResidencySupport::Volatile,
    203             CapabilitySupport::Unavailable,
    204             CapabilitySupport::Unavailable,
    205         )
    206     }
    207 }
    208 
    209 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    210 struct MemoryKey {
    211     id: String,
    212     version: u32,
    213 }
    214 
    215 impl MemoryKey {
    216     fn from_reference(reference: &SecretRef) -> Self {
    217         Self {
    218             id: reference.id().as_str().to_string(),
    219             version: reference.key_version().get(),
    220         }
    221     }
    222 }
    223 
    224 fn validate_memory_reference(reference: &SecretRef) -> Result<(), Error> {
    225     if reference.backend() != BackendKind::Memory {
    226         return Err(Error::BackendMismatch {
    227             provider: BackendKind::Memory,
    228             reference: reference.backend(),
    229         });
    230     }
    231     Ok(())
    232 }
    233 
    234 const fn backend_failure(operation: Operation) -> Error {
    235     Error::BackendFailure {
    236         backend: BackendKind::Memory,
    237         operation,
    238     }
    239 }
    240 
    241 fn not_found(reference: &SecretRef) -> Error {
    242     Error::SecretNotFound {
    243         backend: BackendKind::Memory,
    244         key_version: reference.key_version().get(),
    245     }
    246 }