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 ¤t, 106 SecretMaterial::from_slice(&[0x11; 32]).expect("material"), 107 ) 108 .expect("provision"); 109 provider 110 .rotate( 111 ¤t, 112 &next, 113 SecretMaterial::from_slice(&[0x22; 32]).expect("material"), 114 ) 115 .expect("rotate"); 116 117 assert!(!provider.contains(¤t).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 }