lib

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

memory_adapter.rs (5699B)


      1 #![cfg(feature = "memory")]
      2 
      3 use futures_executor::block_on;
      4 use radroots_secrets::context::{
      5     EnvelopeContext, EnvelopePurpose, EnvelopeSubject, PayloadSchemaId,
      6 };
      7 use radroots_secrets::id::{BackendKind, KeyVersion};
      8 use radroots_secrets::memory::MemoryProvider;
      9 use radroots_secrets::wrapping::{SecretMaterial, UnwrapRequest, WrapRequest};
     10 use radroots_secrets::{Error, KeyWrapping, SecretId, SecretProvider, SecretRef};
     11 
     12 fn reference(id: &str, version: u32) -> SecretRef {
     13     SecretRef::new(
     14         SecretId::parse(id).expect("valid id"),
     15         BackendKind::Memory,
     16         KeyVersion::new(version).expect("valid version"),
     17     )
     18 }
     19 
     20 fn context() -> EnvelopeContext {
     21     EnvelopeContext::new(
     22         EnvelopePurpose::parse("radroots.memory_test").expect("purpose"),
     23         EnvelopeSubject::parse("memory_test", "fixture").expect("subject"),
     24         PayloadSchemaId::parse("radroots.memory_test.v1").expect("schema"),
     25     )
     26 }
     27 
     28 fn different_context() -> EnvelopeContext {
     29     EnvelopeContext::new(
     30         EnvelopePurpose::parse("radroots.memory_test").expect("purpose"),
     31         EnvelopeSubject::parse("memory_test", "different").expect("subject"),
     32         PayloadSchemaId::parse("radroots.memory_test.v1").expect("schema"),
     33     )
     34 }
     35 
     36 #[test]
     37 fn memory_provider_has_explicit_lifecycle_and_round_trips() {
     38     let provider = MemoryProvider::new();
     39     let reference = reference("memory-key", 1);
     40     assert!(!provider.contains(&reference).expect("contains"));
     41 
     42     provider
     43         .provision(
     44             &reference,
     45             SecretMaterial::from_slice(&[0x41; 32]).expect("material"),
     46         )
     47         .expect("provision");
     48     assert!(provider.contains(&reference).expect("contains"));
     49 
     50     let plaintext = SecretMaterial::from_slice(&[0x41; 32]).expect("material");
     51     let context = context();
     52     let wrapped =
     53         block_on(provider.wrap(WrapRequest::new(&reference, &context, &plaintext))).expect("wrap");
     54     let opened = block_on(provider.unwrap(UnwrapRequest::new(&reference, &context, &wrapped)))
     55         .expect("unwrap");
     56     opened.expose_secret(|bytes| assert_eq!(bytes, &[0x41; 32]));
     57 
     58     assert!(provider.remove(&reference).expect("remove"));
     59     assert!(!provider.remove(&reference).expect("idempotent remove"));
     60     assert!(!provider.contains(&reference).expect("contains"));
     61 }
     62 
     63 #[test]
     64 fn missing_and_mismatched_material_fail_closed() {
     65     let provider = MemoryProvider::new();
     66     let reference = reference("missing-key", 1);
     67     let plaintext = SecretMaterial::from_slice(&[0x11; 32]).expect("material");
     68     assert!(matches!(
     69         block_on(provider.wrap(WrapRequest::new(&reference, &context(), &plaintext))),
     70         Err(Error::SecretNotFound {
     71             backend: BackendKind::Memory,
     72             key_version: 1,
     73         })
     74     ));
     75 
     76     provider
     77         .provision(
     78             &reference,
     79             SecretMaterial::from_slice(&[0x22; 32]).expect("material"),
     80         )
     81         .expect("provision");
     82     assert!(matches!(
     83         block_on(provider.wrap(WrapRequest::new(&reference, &context(), &plaintext))),
     84         Err(Error::BackendFailure { .. })
     85     ));
     86     assert!(matches!(
     87         provider.provision(
     88             &reference,
     89             SecretMaterial::from_slice(&[0x33; 32]).expect("material"),
     90         ),
     91         Err(Error::SecretAlreadyExists {
     92             backend: BackendKind::Memory,
     93             key_version: 1,
     94         })
     95     ));
     96 }
     97 
     98 #[test]
     99 fn rotation_is_monotonic_atomic_and_invalidates_the_old_version() {
    100     let provider = MemoryProvider::new();
    101     let current = reference("rotating-key", 1);
    102     let next = reference("rotating-key", 2);
    103     provider
    104         .provision(
    105             &current,
    106             SecretMaterial::from_slice(&[0x11; 32]).expect("material"),
    107         )
    108         .expect("provision");
    109     provider
    110         .rotate(
    111             &current,
    112             &next,
    113             SecretMaterial::from_slice(&[0x22; 32]).expect("material"),
    114         )
    115         .expect("rotate");
    116 
    117     assert!(!provider.contains(&current).expect("old contains"));
    118     assert!(provider.contains(&next).expect("new contains"));
    119     let new_material = SecretMaterial::from_slice(&[0x22; 32]).expect("material");
    120     assert!(block_on(provider.wrap(WrapRequest::new(&next, &context(), &new_material))).is_ok());
    121 
    122     let invalid = reference("different-key", 3);
    123     assert_eq!(
    124         provider.rotate(
    125             &next,
    126             &invalid,
    127             SecretMaterial::from_slice(&[0x33; 32]).expect("material"),
    128         ),
    129         Err(Error::InvalidRotation)
    130     );
    131     assert!(provider.contains(&next).expect("rotation stayed atomic"));
    132 }
    133 
    134 #[test]
    135 fn provider_capabilities_and_diagnostics_are_explicit() {
    136     let provider = MemoryProvider::new();
    137     assert_eq!(provider.backend_kind(), BackendKind::Memory);
    138     assert!(provider.capabilities().is_available());
    139     assert_eq!(format!("{provider:?}"), "MemoryProvider(<redacted>)");
    140 }
    141 
    142 #[test]
    143 fn wrapped_tokens_are_bound_to_the_exact_context() {
    144     let provider = MemoryProvider::new();
    145     let reference = reference("context-key", 1);
    146     let material = SecretMaterial::from_slice(&[0x41; 32]).expect("material");
    147     provider
    148         .provision(
    149             &reference,
    150             SecretMaterial::from_slice(&[0x41; 32]).expect("material"),
    151         )
    152         .expect("provision");
    153     let wrapped =
    154         block_on(provider.wrap(WrapRequest::new(&reference, &context(), &material))).expect("wrap");
    155     assert!(matches!(
    156         block_on(provider.unwrap(UnwrapRequest::new(
    157             &reference,
    158             &different_context(),
    159             &wrapped,
    160         ))),
    161         Err(Error::BackendFailure {
    162             backend: BackendKind::Memory,
    163             ..
    164         })
    165     ));
    166 }