lib.rs (21511B)
1 #![forbid(unsafe_code)] 2 #![no_std] 3 4 extern crate alloc; 5 #[cfg(any(feature = "std", test))] 6 extern crate std; 7 8 pub mod error; 9 #[cfg(feature = "std")] 10 pub mod file; 11 12 use alloc::string::String; 13 use alloc::vec::Vec; 14 use chacha20poly1305::aead::{Aead, KeyInit, Payload}; 15 use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; 16 use error::RadrootsProtectedStoreError; 17 use getrandom::getrandom; 18 use radroots_secret_vault::RadrootsSecretKeyWrapping; 19 use serde::{Deserialize, Serialize}; 20 use zeroize::Zeroize; 21 22 pub const RADROOTS_PROTECTED_STORE_ENVELOPE_VERSION: u8 = 1; 23 pub const RADROOTS_PROTECTED_STORE_KEY_LENGTH: usize = 32; 24 pub const RADROOTS_PROTECTED_STORE_NONCE_LENGTH: usize = 24; 25 26 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 27 #[serde(rename_all = "snake_case")] 28 pub enum RadrootsProtectedStoreCipher { 29 XChaCha20Poly1305, 30 } 31 32 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 33 #[serde(rename_all = "snake_case")] 34 pub enum RadrootsProtectedStoreKeySource { 35 SecretVaultWrapped, 36 } 37 38 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 39 pub struct RadrootsProtectedStoreHeader { 40 pub version: u8, 41 pub cipher: RadrootsProtectedStoreCipher, 42 pub key_source: RadrootsProtectedStoreKeySource, 43 pub key_slot: String, 44 pub nonce: [u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 45 } 46 47 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 48 pub struct RadrootsProtectedStoreEnvelope { 49 pub header: RadrootsProtectedStoreHeader, 50 pub wrapped_key: Vec<u8>, 51 pub ciphertext: Vec<u8>, 52 } 53 54 #[cfg(feature = "std")] 55 pub use file::{RadrootsProtectedFileKeySource, RadrootsProtectedFileSecretVault, sidecar_path}; 56 57 #[derive(Debug, Serialize)] 58 struct RadrootsProtectedStoreAad<'a> { 59 version: u8, 60 cipher: RadrootsProtectedStoreCipher, 61 key_source: RadrootsProtectedStoreKeySource, 62 key_slot: &'a str, 63 nonce: &'a [u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 64 wrapped_key: &'a [u8], 65 } 66 67 impl RadrootsProtectedStoreEnvelope { 68 #[inline(always)] 69 pub fn seal_with_wrapped_key<V: RadrootsSecretKeyWrapping>( 70 vault: &V, 71 key_slot: &str, 72 plaintext: &[u8], 73 ) -> Result<Self, RadrootsProtectedStoreError> { 74 Self::seal_with_wrapped_key_internal(vault, key_slot, plaintext) 75 } 76 77 #[inline(always)] 78 fn seal_with_wrapped_key_internal<V: RadrootsSecretKeyWrapping>( 79 vault: &V, 80 key_slot: &str, 81 plaintext: &[u8], 82 ) -> Result<Self, RadrootsProtectedStoreError> { 83 let mut store_key = [0_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH]; 84 let mut nonce = [0_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH]; 85 fill_random_bytes(&mut store_key)?; 86 fill_random_bytes(&mut nonce)?; 87 Self::seal_with_generated_material(vault, key_slot, plaintext, store_key, nonce) 88 } 89 90 fn seal_with_generated_material<V: RadrootsSecretKeyWrapping>( 91 vault: &V, 92 key_slot: &str, 93 plaintext: &[u8], 94 mut store_key: [u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 95 nonce: [u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 96 ) -> Result<Self, RadrootsProtectedStoreError> { 97 let result = 98 Self::seal_with_wrapped_key_and_material(vault, key_slot, plaintext, store_key, nonce); 99 store_key.zeroize(); 100 result 101 } 102 103 #[cfg(test)] 104 #[inline(always)] 105 fn seal_with_entropy_results<V: RadrootsSecretKeyWrapping>( 106 vault: &V, 107 key_slot: &str, 108 plaintext: &[u8], 109 store_key: Result<[u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], RadrootsProtectedStoreError>, 110 nonce: Result<[u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], RadrootsProtectedStoreError>, 111 ) -> Result<Self, RadrootsProtectedStoreError> { 112 Self::seal_with_generated_material(vault, key_slot, plaintext, store_key?, nonce?) 113 } 114 115 #[inline(always)] 116 pub fn seal_with_wrapped_key_and_material<V: RadrootsSecretKeyWrapping>( 117 vault: &V, 118 key_slot: &str, 119 plaintext: &[u8], 120 mut store_key: [u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 121 nonce: [u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 122 ) -> Result<Self, RadrootsProtectedStoreError> { 123 let wrapped_key = vault 124 .wrap_data_key(key_slot, &store_key) 125 .map_err(|_| RadrootsProtectedStoreError::KeyWrapFailed)?; 126 127 let header = RadrootsProtectedStoreHeader { 128 version: RADROOTS_PROTECTED_STORE_ENVELOPE_VERSION, 129 cipher: RadrootsProtectedStoreCipher::XChaCha20Poly1305, 130 key_source: RadrootsProtectedStoreKeySource::SecretVaultWrapped, 131 key_slot: String::from(key_slot), 132 nonce, 133 }; 134 135 let aad = envelope_aad(&header, &wrapped_key)?; 136 let cipher = XChaCha20Poly1305::new(Key::from_slice(&store_key)); 137 let ciphertext = cipher 138 .encrypt( 139 XNonce::from_slice(&header.nonce), 140 Payload { 141 msg: plaintext, 142 aad: &aad, 143 }, 144 ) 145 .map_err(|_| RadrootsProtectedStoreError::EncryptFailed)?; 146 store_key.zeroize(); 147 148 Ok(Self { 149 header, 150 wrapped_key, 151 ciphertext, 152 }) 153 } 154 155 #[inline(always)] 156 pub fn open_with_wrapped_key<V: RadrootsSecretKeyWrapping>( 157 &self, 158 vault: &V, 159 ) -> Result<Vec<u8>, RadrootsProtectedStoreError> { 160 self.validate_header()?; 161 let mut store_key = vault 162 .unwrap_data_key(&self.header.key_slot, &self.wrapped_key) 163 .map_err(|_| RadrootsProtectedStoreError::KeyUnwrapFailed)?; 164 165 if store_key.len() != RADROOTS_PROTECTED_STORE_KEY_LENGTH { 166 let length = store_key.len(); 167 store_key.zeroize(); 168 return Err(RadrootsProtectedStoreError::InvalidStoreKeyLength(length)); 169 } 170 171 let mut store_key_bytes = [0_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH]; 172 store_key_bytes.copy_from_slice(&store_key); 173 store_key.zeroize(); 174 175 let aad = envelope_aad(&self.header, &self.wrapped_key)?; 176 let cipher = XChaCha20Poly1305::new(Key::from_slice(&store_key_bytes)); 177 let decrypted = cipher 178 .decrypt( 179 XNonce::from_slice(&self.header.nonce), 180 Payload { 181 msg: &self.ciphertext, 182 aad: &aad, 183 }, 184 ) 185 .map_err(|_| RadrootsProtectedStoreError::DecryptFailed)?; 186 store_key_bytes.zeroize(); 187 Ok(decrypted) 188 } 189 190 pub fn encode_json(&self) -> Result<Vec<u8>, RadrootsProtectedStoreError> { 191 serde_json::to_vec(self).map_err(|_| RadrootsProtectedStoreError::EnvelopeEncodeFailed) 192 } 193 194 pub fn decode_json(json: &[u8]) -> Result<Self, RadrootsProtectedStoreError> { 195 let envelope: Self = serde_json::from_slice(json) 196 .map_err(|_| RadrootsProtectedStoreError::EnvelopeDecodeFailed)?; 197 envelope.validate_header()?; 198 Ok(envelope) 199 } 200 201 fn validate_header(&self) -> Result<(), RadrootsProtectedStoreError> { 202 if self.header.version != RADROOTS_PROTECTED_STORE_ENVELOPE_VERSION { 203 return Err(RadrootsProtectedStoreError::UnsupportedEnvelopeVersion( 204 self.header.version, 205 )); 206 } 207 208 Ok(()) 209 } 210 } 211 212 fn envelope_aad( 213 header: &RadrootsProtectedStoreHeader, 214 wrapped_key: &[u8], 215 ) -> Result<Vec<u8>, RadrootsProtectedStoreError> { 216 serde_json::to_vec(&RadrootsProtectedStoreAad { 217 version: header.version, 218 cipher: header.cipher, 219 key_source: header.key_source, 220 key_slot: &header.key_slot, 221 nonce: &header.nonce, 222 wrapped_key, 223 }) 224 .map_err(|_| RadrootsProtectedStoreError::EnvelopeEncodeFailed) 225 } 226 227 fn fill_random_bytes(bytes: &mut [u8]) -> Result<(), RadrootsProtectedStoreError> { 228 getrandom(bytes).map_err(|_| RadrootsProtectedStoreError::EntropyUnavailable) 229 } 230 231 #[cfg(test)] 232 mod tests { 233 use super::*; 234 use alloc::string::String; 235 use alloc::vec; 236 use core::cell::{Cell, RefCell}; 237 238 struct FakeVault { 239 wrap_calls: Cell<usize>, 240 unwrap_calls: Cell<usize>, 241 fail_wrap: bool, 242 fail_unwrap: bool, 243 unwrap_length: Option<usize>, 244 last_slot: RefCell<Option<String>>, 245 } 246 247 impl FakeVault { 248 fn new() -> Self { 249 Self { 250 wrap_calls: Cell::new(0), 251 unwrap_calls: Cell::new(0), 252 fail_wrap: false, 253 fail_unwrap: false, 254 unwrap_length: None, 255 last_slot: RefCell::new(None), 256 } 257 } 258 259 fn with_wrap_failure() -> Self { 260 Self { 261 fail_wrap: true, 262 ..Self::new() 263 } 264 } 265 266 fn with_unwrap_failure() -> Self { 267 Self { 268 fail_unwrap: true, 269 ..Self::new() 270 } 271 } 272 273 fn with_unwrap_length(length: usize) -> Self { 274 Self { 275 unwrap_length: Some(length), 276 ..Self::new() 277 } 278 } 279 } 280 281 impl RadrootsSecretKeyWrapping for FakeVault { 282 type Error = (); 283 284 fn wrap_data_key( 285 &self, 286 key_slot: &str, 287 plaintext_key: &[u8], 288 ) -> Result<Vec<u8>, Self::Error> { 289 if self.fail_wrap { 290 return Err(()); 291 } 292 self.wrap_calls.set(self.wrap_calls.get() + 1); 293 self.last_slot.replace(Some(String::from(key_slot))); 294 let mut wrapped = key_slot.as_bytes().to_vec(); 295 wrapped.push(0); 296 wrapped.extend(plaintext_key.iter().map(|byte| byte ^ 0x5a)); 297 Ok(wrapped) 298 } 299 300 fn unwrap_data_key( 301 &self, 302 key_slot: &str, 303 wrapped_key: &[u8], 304 ) -> Result<Vec<u8>, Self::Error> { 305 if self.fail_unwrap { 306 return Err(()); 307 } 308 self.unwrap_calls.set(self.unwrap_calls.get() + 1); 309 self.last_slot.replace(Some(String::from(key_slot))); 310 311 let separator = wrapped_key.iter().position(|byte| *byte == 0).ok_or(())?; 312 if &wrapped_key[..separator] != key_slot.as_bytes() { 313 return Err(()); 314 } 315 316 let mut unwrapped = wrapped_key[separator + 1..] 317 .iter() 318 .map(|byte| byte ^ 0x5a) 319 .collect::<Vec<_>>(); 320 if let Some(length) = self.unwrap_length { 321 unwrapped.truncate(length); 322 } 323 324 Ok(unwrapped) 325 } 326 } 327 328 #[test] 329 fn wrapped_key_roundtrip_uses_secret_vault_and_stable_envelope() { 330 let vault = FakeVault::new(); 331 let envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_and_material( 332 &vault, 333 "drafts/default", 334 b"secret draft body", 335 [7_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 336 [9_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 337 ) 338 .expect("seal succeeds"); 339 340 assert_eq!(vault.wrap_calls.get(), 1); 341 assert_eq!( 342 envelope.header.version, 343 RADROOTS_PROTECTED_STORE_ENVELOPE_VERSION 344 ); 345 assert_eq!( 346 envelope.header.cipher, 347 RadrootsProtectedStoreCipher::XChaCha20Poly1305 348 ); 349 assert_eq!( 350 envelope.header.key_source, 351 RadrootsProtectedStoreKeySource::SecretVaultWrapped 352 ); 353 assert_eq!(envelope.header.key_slot, "drafts/default"); 354 355 let encoded = envelope.encode_json().expect("encode succeeds"); 356 let decoded = 357 RadrootsProtectedStoreEnvelope::decode_json(&encoded).expect("decode succeeds"); 358 let plaintext = decoded 359 .open_with_wrapped_key(&vault) 360 .expect("open succeeds"); 361 362 assert_eq!(vault.unwrap_calls.get(), 1); 363 assert_eq!(plaintext, b"secret draft body"); 364 } 365 366 #[test] 367 fn seal_with_wrapped_key_uses_runtime_entropy_and_roundtrips() { 368 let vault = FakeVault::new(); 369 let envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key( 370 &vault, 371 "drafts/default", 372 b"runtime entropy body", 373 ) 374 .expect("seal succeeds"); 375 376 assert_eq!(vault.wrap_calls.get(), 1); 377 assert_eq!(envelope.header.key_slot, "drafts/default"); 378 379 let plaintext = envelope 380 .open_with_wrapped_key(&vault) 381 .expect("open succeeds"); 382 383 assert_eq!(vault.unwrap_calls.get(), 1); 384 assert_eq!(plaintext, b"runtime entropy body"); 385 } 386 387 #[test] 388 fn seal_with_wrapped_key_reports_entropy_failure() { 389 let vault = FakeVault::new(); 390 let err = RadrootsProtectedStoreEnvelope::seal_with_entropy_results( 391 &vault, 392 "drafts/default", 393 b"secret draft body", 394 Err(RadrootsProtectedStoreError::EntropyUnavailable), 395 Ok([9_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH]), 396 ) 397 .expect_err("entropy failure must surface"); 398 399 assert_eq!(err, RadrootsProtectedStoreError::EntropyUnavailable); 400 assert_eq!(vault.wrap_calls.get(), 0); 401 } 402 403 #[test] 404 fn seal_with_wrapped_key_reports_entropy_failure_for_nonce_generation() { 405 let vault = FakeVault::new(); 406 let err = RadrootsProtectedStoreEnvelope::seal_with_entropy_results( 407 &vault, 408 "drafts/default", 409 b"secret draft body", 410 Ok([7_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH]), 411 Err(RadrootsProtectedStoreError::EntropyUnavailable), 412 ) 413 .expect_err("second entropy failure must surface"); 414 415 assert_eq!(err, RadrootsProtectedStoreError::EntropyUnavailable); 416 assert_eq!(vault.wrap_calls.get(), 0); 417 } 418 419 #[test] 420 fn seal_with_entropy_results_succeeds_when_material_is_provided() { 421 let vault = FakeVault::new(); 422 let envelope = RadrootsProtectedStoreEnvelope::seal_with_entropy_results( 423 &vault, 424 "drafts/default", 425 b"entropy helper body", 426 Ok([7_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH]), 427 Ok([9_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH]), 428 ) 429 .expect("explicit material should succeed"); 430 431 let plaintext = envelope 432 .open_with_wrapped_key(&vault) 433 .expect("helper envelope opens"); 434 assert_eq!(plaintext, b"entropy helper body"); 435 } 436 437 #[test] 438 fn seal_with_wrapped_key_surfaces_wrap_failure_after_entropy() { 439 let vault = FakeVault::with_wrap_failure(); 440 let err = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key( 441 &vault, 442 "drafts/default", 443 b"secret draft body", 444 ) 445 .expect_err("wrap failure must surface through public seal"); 446 447 assert_eq!(err, RadrootsProtectedStoreError::KeyWrapFailed); 448 } 449 450 #[test] 451 fn tampered_wrapped_key_fails_authentication() { 452 let vault = FakeVault::new(); 453 let mut envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_and_material( 454 &vault, 455 "drafts/default", 456 b"secret draft body", 457 [3_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 458 [4_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 459 ) 460 .expect("seal succeeds"); 461 462 let last = envelope.wrapped_key.len() - 1; 463 envelope.wrapped_key[last] ^= 0x01; 464 465 let err = envelope 466 .open_with_wrapped_key(&vault) 467 .expect_err("tampered wrapped key must fail"); 468 assert_eq!(err, RadrootsProtectedStoreError::DecryptFailed); 469 } 470 471 #[test] 472 fn unsupported_version_is_rejected() { 473 let envelope = RadrootsProtectedStoreEnvelope { 474 header: RadrootsProtectedStoreHeader { 475 version: 2, 476 cipher: RadrootsProtectedStoreCipher::XChaCha20Poly1305, 477 key_source: RadrootsProtectedStoreKeySource::SecretVaultWrapped, 478 key_slot: String::from("drafts/default"), 479 nonce: [0_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 480 }, 481 wrapped_key: vec![1, 2, 3], 482 ciphertext: vec![4, 5, 6], 483 }; 484 485 let encoded = envelope.encode_json().expect("encode succeeds"); 486 let err = RadrootsProtectedStoreEnvelope::decode_json(&encoded) 487 .expect_err("unsupported version must fail"); 488 assert_eq!( 489 err, 490 RadrootsProtectedStoreError::UnsupportedEnvelopeVersion(2) 491 ); 492 } 493 494 #[test] 495 fn open_rejects_unsupported_version_before_unwrap() { 496 let vault = FakeVault::new(); 497 let envelope = RadrootsProtectedStoreEnvelope { 498 header: RadrootsProtectedStoreHeader { 499 version: 2, 500 cipher: RadrootsProtectedStoreCipher::XChaCha20Poly1305, 501 key_source: RadrootsProtectedStoreKeySource::SecretVaultWrapped, 502 key_slot: String::from("drafts/default"), 503 nonce: [0_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 504 }, 505 wrapped_key: vec![1, 2, 3], 506 ciphertext: vec![4, 5, 6], 507 }; 508 509 let err = envelope 510 .open_with_wrapped_key(&vault) 511 .expect_err("unsupported version must fail before unwrap"); 512 assert_eq!( 513 err, 514 RadrootsProtectedStoreError::UnsupportedEnvelopeVersion(2) 515 ); 516 assert_eq!(vault.unwrap_calls.get(), 0); 517 } 518 519 #[test] 520 fn decode_json_rejects_invalid_payloads() { 521 let err = RadrootsProtectedStoreEnvelope::decode_json(br#"{"header":"bad"}"#) 522 .expect_err("invalid payload must fail decode"); 523 assert_eq!(err, RadrootsProtectedStoreError::EnvelopeDecodeFailed); 524 } 525 526 #[test] 527 fn wrap_failures_are_delegated_to_secret_vault() { 528 let vault = FakeVault::with_wrap_failure(); 529 let err = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_and_material( 530 &vault, 531 "drafts/default", 532 b"secret draft body", 533 [7_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 534 [9_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 535 ) 536 .expect_err("wrap failure must surface"); 537 538 assert_eq!(err, RadrootsProtectedStoreError::KeyWrapFailed); 539 } 540 541 #[test] 542 fn unwrap_failures_are_delegated_to_secret_vault() { 543 let seal_vault = FakeVault::new(); 544 let open_vault = FakeVault::with_unwrap_failure(); 545 let envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_and_material( 546 &seal_vault, 547 "drafts/default", 548 b"secret draft body", 549 [7_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 550 [9_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 551 ) 552 .expect("seal succeeds"); 553 554 let err = envelope 555 .open_with_wrapped_key(&open_vault) 556 .expect_err("unwrap failure must surface"); 557 assert_eq!(err, RadrootsProtectedStoreError::KeyUnwrapFailed); 558 } 559 560 #[test] 561 fn invalid_store_key_length_is_rejected_after_unwrap() { 562 let seal_vault = FakeVault::new(); 563 let open_vault = FakeVault::with_unwrap_length(31); 564 let envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_and_material( 565 &seal_vault, 566 "drafts/default", 567 b"secret draft body", 568 [7_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 569 [9_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 570 ) 571 .expect("seal succeeds"); 572 573 let err = envelope 574 .open_with_wrapped_key(&open_vault) 575 .expect_err("short store key must fail"); 576 577 assert_eq!(err, RadrootsProtectedStoreError::InvalidStoreKeyLength(31)); 578 } 579 580 #[test] 581 fn wrapped_key_slot_mismatch_is_rejected_during_unwrap() { 582 let vault = FakeVault::new(); 583 let mut envelope = RadrootsProtectedStoreEnvelope::seal_with_wrapped_key_and_material( 584 &vault, 585 "drafts/default", 586 b"secret draft body", 587 [7_u8; RADROOTS_PROTECTED_STORE_KEY_LENGTH], 588 [9_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 589 ) 590 .expect("seal succeeds"); 591 envelope.header.key_slot = String::from("drafts/other"); 592 593 let err = envelope 594 .open_with_wrapped_key(&vault) 595 .expect_err("mismatched key slot must fail"); 596 597 assert_eq!(err, RadrootsProtectedStoreError::KeyUnwrapFailed); 598 } 599 600 #[test] 601 fn wrapped_key_without_separator_is_rejected_during_unwrap() { 602 let vault = FakeVault::new(); 603 let envelope = RadrootsProtectedStoreEnvelope { 604 header: RadrootsProtectedStoreHeader { 605 version: RADROOTS_PROTECTED_STORE_ENVELOPE_VERSION, 606 cipher: RadrootsProtectedStoreCipher::XChaCha20Poly1305, 607 key_source: RadrootsProtectedStoreKeySource::SecretVaultWrapped, 608 key_slot: String::from("drafts/default"), 609 nonce: [0_u8; RADROOTS_PROTECTED_STORE_NONCE_LENGTH], 610 }, 611 wrapped_key: vec![1, 2, 3, 4], 612 ciphertext: vec![5, 6, 7], 613 }; 614 615 let err = envelope 616 .open_with_wrapped_key(&vault) 617 .expect_err("missing separator must fail"); 618 assert_eq!(err, RadrootsProtectedStoreError::KeyUnwrapFailed); 619 } 620 }