1use std::fs::File;
4use std::io::{self, Read};
5use std::path::Path;
6
7use aws_lc_rs::aead::{AES_256_GCM, Aad, LessSafeKey, Nonce, UnboundKey};
8use aws_lc_rs::digest::{SHA256, digest};
9use aws_lc_rs::hkdf::{HKDF_SHA256, KeyType, Salt};
10use uuid::Uuid;
11use zeroize::Zeroizing;
12
13use crate::ids::{KeyId, TenantId, VersionId};
14
15mod locked;
16
17use locked::LockedPage;
18
19const KEY_LEN: usize = 32;
21
22const FORMAT: u8 = 1;
25const NONCE_LEN: usize = 12;
26const TAG_LEN: usize = 16;
27
28const LABEL: &[u8] = b"vcon-server/v1";
30
31const CATALOG_LABEL: &[u8] = b"vcon-server/v1 catalog";
33
34pub struct Key {
38 page: LockedPage,
39}
40
41impl Key {
42 pub fn generate() -> Result<Key, KeyError> {
44 let mut page = LockedPage::new().map_err(KeyError::Memory)?;
45 aws_lc_rs::rand::fill(&mut page.bytes_mut()[..KEY_LEN])
48 .expect("AWS-LC aborts instead of failing");
49 Ok(Key { page })
50 }
51
52 pub fn load_master(path: &Path) -> Result<Key, KeyError> {
55 let mut page = LockedPage::new().map_err(KeyError::Memory)?;
56 let mut file = File::open(path).map_err(KeyError::Read)?;
57 let buf = page.bytes_mut();
58 file.read_exact(&mut buf[..KEY_LEN])
59 .map_err(|e| match e.kind() {
60 io::ErrorKind::UnexpectedEof => KeyError::WrongSize,
61 _ => KeyError::Read(e),
62 })?;
63 if file
64 .read(&mut buf[KEY_LEN..=KEY_LEN])
65 .map_err(KeyError::Read)?
66 != 0
67 {
68 return Err(KeyError::WrongSize);
69 }
70 Ok(Key { page })
71 }
72
73 pub fn id(&self) -> KeyId {
75 let hash = digest(&SHA256, self.bytes());
76 KeyId(hash.as_ref()[..8].try_into().expect("SHA-256 is 32 bytes"))
77 }
78
79 fn bytes(&self) -> &[u8] {
80 &self.page.bytes()[..KEY_LEN]
81 }
82
83 pub(crate) fn sqlcipher_raw(&self) -> Zeroizing<String> {
87 const HEX: &[u8; 16] = b"0123456789abcdef";
88 let mut literal = Zeroizing::new(String::with_capacity(3 + 2 * KEY_LEN));
89 literal.push_str("x'");
90 for byte in self.bytes() {
91 literal.push(char::from(HEX[usize::from(byte >> 4)]));
92 literal.push(char::from(HEX[usize::from(byte & 0x0f)]));
93 }
94 literal.push('\'');
95 literal
96 }
97}
98
99#[derive(Debug)]
101pub enum KeyError {
102 Memory(io::Error),
104 Read(io::Error),
106 WrongSize,
108 Damaged,
111}
112
113pub fn lock_all_memory() -> io::Result<()> {
119 if unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) } != 0 {
121 return Err(io::Error::last_os_error());
122 }
123 Ok(())
124}
125
126pub fn disable_core_dumps() -> io::Result<()> {
133 let none = libc::rlimit {
134 rlim_cur: 0,
135 rlim_max: 0,
136 };
137 if unsafe { libc::setrlimit(libc::RLIMIT_CORE, &none) } != 0 {
139 return Err(io::Error::last_os_error());
140 }
141 if unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0) } != 0 {
143 return Err(io::Error::last_os_error());
144 }
145 Ok(())
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum Context {
153 Tenant { tenant: TenantId },
155 Version {
158 tenant: TenantId,
159 uuid: Uuid,
160 version: VersionId,
161 },
162}
163
164impl Context {
165 fn associated_data(&self, format: u8) -> Vec<u8> {
169 let mut ad = LABEL.to_vec();
170 ad.push(format);
171 match self {
172 Context::Tenant { tenant } => {
173 ad.push(1);
174 ad.extend(tenant.0);
175 }
176 Context::Version {
177 tenant,
178 uuid,
179 version,
180 } => {
181 ad.push(2);
182 ad.extend(tenant.0);
183 ad.extend(uuid.as_bytes());
184 ad.extend(version.0);
185 }
186 }
187 ad
188 }
189}
190
191#[derive(Debug, PartialEq, Eq)]
194pub struct OpenError;
195
196pub fn seal(data_key: &Key, ctx: &Context, plaintext: &[u8]) -> Vec<u8> {
200 let nonce: [u8; NONCE_LEN] = aws_lc_rs::rand::generate(&aws_lc_rs::rand::SystemRandom::new())
203 .expect("AWS-LC aborts instead of failing")
204 .expose();
205 seal_with_nonce(data_key.bytes(), nonce, ctx, plaintext)
206}
207
208pub fn open(data_key: &Key, ctx: &Context, blob: &[u8]) -> Result<Vec<u8>, OpenError> {
212 let mut text = vec![0; blob.len().saturating_sub(1 + NONCE_LEN)];
213 let len = open_into(data_key.bytes(), ctx, blob, &mut text)?;
214 text.truncate(len);
215 Ok(text)
216}
217
218pub fn wrap(wrapping: &Key, ctx: &Context, key: &Key) -> Vec<u8> {
221 seal(wrapping, ctx, key.bytes())
222}
223
224pub fn unwrap(wrapping: &Key, ctx: &Context, wrapped: &[u8]) -> Result<Key, KeyError> {
229 let mut page = LockedPage::new().map_err(KeyError::Memory)?;
230 match open_into(wrapping.bytes(), ctx, wrapped, page.bytes_mut()) {
231 Ok(KEY_LEN) => Ok(Key { page }),
232 _ => Err(KeyError::Damaged),
233 }
234}
235
236pub fn rewrap(old: &Key, new: &Key, ctx: &Context, wrapped: &[u8]) -> Result<Vec<u8>, KeyError> {
240 let key = unwrap(old, ctx, wrapped)?;
241 Ok(wrap(new, ctx, &key))
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
246pub enum WrapLimit {
247 Allow,
249 Warn,
251 Refuse,
253}
254
255pub fn wrap_limit(wraps: u64) -> WrapLimit {
260 const REFUSE_AT: u64 = 1 << 32;
261 const WARN_AT: u64 = 1 << 31;
262 if wraps >= REFUSE_AT {
263 WrapLimit::Refuse
264 } else if wraps >= WARN_AT {
265 WrapLimit::Warn
266 } else {
267 WrapLimit::Allow
268 }
269}
270
271fn open_into(key: &[u8], ctx: &Context, blob: &[u8], out: &mut [u8]) -> Result<usize, OpenError> {
274 let (&format, rest) = blob.split_first().ok_or(OpenError)?;
275 let (nonce, sealed) = rest.split_first_chunk::<NONCE_LEN>().ok_or(OpenError)?;
276 let text = out.get_mut(..sealed.len()).ok_or(OpenError)?;
277 text.copy_from_slice(sealed);
278 gcm_open(key, *nonce, &ctx.associated_data(format), text)
279}
280
281pub fn catalog_key(parent: &Key) -> Result<Key, KeyError> {
286 let mut page = LockedPage::new().map_err(KeyError::Memory)?;
287 hkdf_sha256(
288 &[],
289 parent.bytes(),
290 CATALOG_LABEL,
291 &mut page.bytes_mut()[..KEY_LEN],
292 );
293 Ok(Key { page })
294}
295
296fn hkdf_sha256(salt: &[u8], ikm: &[u8], info: &[u8], out: &mut [u8]) {
298 struct Len(usize);
299 impl KeyType for Len {
300 fn len(&self) -> usize {
301 self.0
302 }
303 }
304 Salt::new(HKDF_SHA256, salt)
307 .extract(ikm)
308 .expand(&[info], Len(out.len()))
309 .and_then(|okm| okm.fill(out))
310 .expect("within HKDF's output limit");
311}
312
313fn seal_with_nonce(key: &[u8], nonce: [u8; NONCE_LEN], ctx: &Context, plaintext: &[u8]) -> Vec<u8> {
314 let mut blob = Vec::with_capacity(1 + NONCE_LEN + plaintext.len() + TAG_LEN);
315 blob.push(FORMAT);
316 blob.extend(nonce);
317 blob.extend(plaintext);
318 let ad = ctx.associated_data(FORMAT);
319 let tag = gcm_seal(key, nonce, &ad, &mut blob[1 + NONCE_LEN..]);
320 blob.extend(tag);
321 blob
322}
323
324fn aead_key(key: &[u8]) -> LessSafeKey {
325 LessSafeKey::new(UnboundKey::new(&AES_256_GCM, key).expect("every key is 32 bytes"))
326}
327
328fn gcm_seal(key: &[u8], nonce: [u8; NONCE_LEN], ad: &[u8], in_out: &mut [u8]) -> [u8; TAG_LEN] {
330 let tag = aead_key(key)
334 .seal_in_place_separate_tag(Nonce::assume_unique_for_key(nonce), Aad::from(ad), in_out)
335 .expect("within GCM's limits");
336 tag.as_ref().try_into().expect("a 16-byte tag")
337}
338
339fn gcm_open(
342 key: &[u8],
343 nonce: [u8; NONCE_LEN],
344 ad: &[u8],
345 in_out: &mut [u8],
346) -> Result<usize, OpenError> {
347 aead_key(key)
348 .open_in_place(Nonce::assume_unique_for_key(nonce), Aad::from(ad), in_out)
349 .map(|plaintext| plaintext.len())
350 .map_err(|_| OpenError)
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn every_byte_of_a_key_is_random() {
359 let keys: Vec<Key> = (0..8).map(|_| Key::generate().expect("key")).collect();
360 for i in 0..KEY_LEN {
361 let first = keys[0].bytes()[i];
362 assert!(
363 keys.iter().any(|k| k.bytes()[i] != first),
364 "byte {i} is {first} in all eight keys"
365 );
366 }
367 }
368
369 fn hex(text: &str) -> Vec<u8> {
370 (0..text.len())
371 .step_by(2)
372 .map(|i| u8::from_str_radix(&text[i..i + 2], 16).expect("hex"))
373 .collect()
374 }
375
376 #[test]
380 fn aes_256_gcm_matches_the_published_vector() {
381 let key = hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
382 let nonce: [u8; 12] = hex("cafebabefacedbaddecaf888")
383 .try_into()
384 .expect("12 bytes");
385 let ad = hex("feedfacedeadbeeffeedfacedeadbeefabaddad2");
386 let mut text = hex(concat!(
387 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72",
388 "1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"
389 ));
390 let tag = gcm_seal(&key, nonce, &ad, &mut text);
391 assert_eq!(
392 text,
393 hex(concat!(
394 "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa",
395 "8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662"
396 ))
397 );
398 assert_eq!(tag.to_vec(), hex("76fc6ece0f4e1768cddf8853bb2d551b"));
399 }
400
401 const TENANT: TenantId = TenantId([0x11; 16]);
402 const VERSION: VersionId = VersionId([0x22; 16]);
403 const UUID: Uuid = Uuid::from_bytes([0x33; 16]);
404
405 fn version_ctx() -> Context {
406 Context::Version {
407 tenant: TENANT,
408 uuid: UUID,
409 version: VERSION,
410 }
411 }
412
413 #[test]
417 fn a_blob_is_the_format_byte_nonce_ciphertext_and_tag() {
418 let key = [0x44; 32];
419 let nonce = [0x55; 12];
420 let mut ad = b"vcon-server/v1".to_vec();
421 ad.extend([1, 2]);
422 ad.extend([0x11; 16]);
423 ad.extend([0x33; 16]);
424 ad.extend([0x22; 16]);
425 let mut ciphertext = b"body".to_vec();
426 let tag = gcm_seal(&key, nonce, &ad, &mut ciphertext);
427 let mut expected = vec![1];
428 expected.extend(nonce);
429 expected.extend(ciphertext);
430 expected.extend(tag);
431 assert_eq!(
432 seal_with_nonce(&key, nonce, &version_ctx(), b"body"),
433 expected
434 );
435
436 let mut tenant_ad = b"vcon-server/v1".to_vec();
437 tenant_ad.extend([1, 1]);
438 tenant_ad.extend([0x11; 16]);
439 let mut ciphertext = b"key".to_vec();
440 let tag = gcm_seal(&key, nonce, &tenant_ad, &mut ciphertext);
441 let mut expected = vec![1];
442 expected.extend(nonce);
443 expected.extend(ciphertext);
444 expected.extend(tag);
445 let tenant = Context::Tenant { tenant: TENANT };
446 assert_eq!(seal_with_nonce(&key, nonce, &tenant, b"key"), expected);
447 }
448
449 #[test]
450 fn a_sealed_body_opens_with_the_same_key_and_context() {
451 let key = Key::generate().expect("key");
452 for body in [&b""[..], b"x", &[0xa5; 100_000]] {
453 let blob = seal(&key, &version_ctx(), body);
454 assert_eq!(blob.len(), 1 + 12 + body.len() + 16);
455 assert_eq!(open(&key, &version_ctx(), &blob).as_deref(), Ok(body));
456 }
457 }
458
459 #[test]
460 fn each_seal_uses_a_new_nonce() {
461 let key = Key::generate().expect("key");
462 let a = seal(&key, &version_ctx(), b"same");
463 let b = seal(&key, &version_ctx(), b"same");
464 assert_ne!(a[1..13], b[1..13]);
465 }
466
467 #[test]
471 fn every_byte_of_a_nonce_is_random() {
472 let key = Key::generate().expect("key");
473 let nonces: Vec<[u8; 12]> = (0..1000)
474 .map(|_| {
475 let blob = seal(&key, &version_ctx(), b"same");
476 blob[1..13].try_into().expect("12 bytes")
477 })
478 .collect();
479 let distinct: std::collections::HashSet<_> = nonces.iter().collect();
480 assert_eq!(distinct.len(), nonces.len(), "a nonce repeated");
481 for i in 0..12 {
482 assert!(
483 nonces.iter().any(|n| n[i] != nonces[0][i]),
484 "byte {i} of the nonce never changes"
485 );
486 }
487 }
488
489 #[test]
490 fn changing_any_byte_of_a_blob_fails() {
491 let key = Key::generate().expect("key");
492 let blob = seal(&key, &version_ctx(), b"a body");
493 assert_eq!(
494 open(&key, &version_ctx(), &blob).as_deref(),
495 Ok(&b"a body"[..]),
496 "unchanged"
497 );
498 for i in 0..blob.len() {
499 let mut bad = blob.clone();
500 bad[i] ^= 0x01;
501 assert_eq!(open(&key, &version_ctx(), &bad), Err(OpenError), "byte {i}");
502 }
503 for len in 0..blob.len() {
504 assert_eq!(
505 open(&key, &version_ctx(), &blob[..len]),
506 Err(OpenError),
507 "first {len} bytes"
508 );
509 }
510 let mut longer = blob.clone();
511 longer.push(0);
512 assert_eq!(
513 open(&key, &version_ctx(), &longer),
514 Err(OpenError),
515 "one byte more"
516 );
517 }
518
519 #[test]
520 fn another_context_or_key_fails() {
521 let key = Key::generate().expect("key");
522 let blob = seal(&key, &version_ctx(), b"a body");
523 assert_eq!(
524 open(&key, &version_ctx(), &blob).as_deref(),
525 Ok(&b"a body"[..]),
526 "unchanged"
527 );
528 let others = [
529 Context::Version {
530 tenant: TenantId([0x12; 16]),
531 uuid: UUID,
532 version: VERSION,
533 },
534 Context::Version {
535 tenant: TENANT,
536 uuid: Uuid::from_bytes([0x34; 16]),
537 version: VERSION,
538 },
539 Context::Version {
540 tenant: TENANT,
541 uuid: UUID,
542 version: VersionId([0x23; 16]),
543 },
544 Context::Tenant { tenant: TENANT },
545 ];
546 for other in others {
547 assert_eq!(open(&key, &other, &blob), Err(OpenError), "{other:?}");
548 }
549 let other_key = Key::generate().expect("key");
550 assert_eq!(open(&other_key, &version_ctx(), &blob), Err(OpenError));
551 }
552
553 const TENANT_CTX: Context = Context::Tenant { tenant: TENANT };
554
555 #[test]
556 fn a_wrapped_key_unwraps_to_the_same_key() {
557 let wrapping = Key::generate().expect("key");
558 let key = Key::generate().expect("key");
559 for ctx in [TENANT_CTX, version_ctx()] {
560 let wrapped = wrap(&wrapping, &ctx, &key);
561 assert_eq!(wrapped.len(), 1 + 12 + KEY_LEN + 16);
562 let unwrapped = unwrap(&wrapping, &ctx, &wrapped).expect("unwraps");
563 assert_eq!(unwrapped.bytes(), key.bytes());
564 }
565 }
566
567 #[test]
568 fn changing_any_byte_of_a_wrapped_key_fails() {
569 let wrapping = Key::generate().expect("key");
570 let key = Key::generate().expect("key");
571 let wrapped = wrap(&wrapping, &TENANT_CTX, &key);
572 assert!(
573 unwrap(&wrapping, &TENANT_CTX, &wrapped).is_ok(),
574 "unchanged"
575 );
576 for i in 0..wrapped.len() {
577 let mut bad = wrapped.clone();
578 bad[i] ^= 0x01;
579 assert!(unwrap(&wrapping, &TENANT_CTX, &bad).is_err(), "byte {i}");
580 }
581 for len in 0..wrapped.len() {
582 assert!(
583 unwrap(&wrapping, &TENANT_CTX, &wrapped[..len]).is_err(),
584 "first {len} bytes"
585 );
586 }
587 }
588
589 #[test]
590 fn another_context_or_wrapping_key_fails() {
591 let wrapping = Key::generate().expect("key");
592 let key = Key::generate().expect("key");
593 let wrapped = wrap(&wrapping, &TENANT_CTX, &key);
594 assert!(
595 unwrap(&wrapping, &TENANT_CTX, &wrapped).is_ok(),
596 "unchanged"
597 );
598 let other_tenant = Context::Tenant {
599 tenant: TenantId([0x12; 16]),
600 };
601 assert!(
602 unwrap(&wrapping, &other_tenant, &wrapped).is_err(),
603 "other tenant"
604 );
605 assert!(
606 unwrap(&wrapping, &version_ctx(), &wrapped).is_err(),
607 "other kind"
608 );
609 let other = Key::generate().expect("key");
610 assert!(
611 unwrap(&other, &TENANT_CTX, &wrapped).is_err(),
612 "other wrapping key"
613 );
614 }
615
616 #[test]
617 fn only_a_32_byte_key_unwraps() {
618 let wrapping = Key::generate().expect("key");
619 for len in [0, 31, 33, 64] {
620 let sealed = seal(&wrapping, &TENANT_CTX, &vec![7; len]);
621 assert!(
622 unwrap(&wrapping, &TENANT_CTX, &sealed).is_err(),
623 "{len} bytes"
624 );
625 }
626 let sealed = seal(&wrapping, &TENANT_CTX, &[7; KEY_LEN]);
627 let key = unwrap(&wrapping, &TENANT_CTX, &sealed).expect("32 bytes unwrap");
628 assert_eq!(key.bytes(), [7; KEY_LEN]);
629 }
630
631 fn key_of(bytes: [u8; KEY_LEN]) -> Key {
633 let mut page = LockedPage::new().expect("page");
634 page.bytes_mut()[..KEY_LEN].copy_from_slice(&bytes);
635 Key { page }
636 }
637
638 #[test]
640 fn hkdf_sha256_matches_rfc_5869() {
641 let cases = [
642 (
643 "000102030405060708090a0b0c",
644 "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
645 "f0f1f2f3f4f5f6f7f8f9",
646 concat!(
647 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf",
648 "34007208d5b887185865"
649 ),
650 ),
651 (
652 concat!(
653 "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f",
654 "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
655 "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
656 ),
657 concat!(
658 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
659 "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
660 "404142434445464748494a4b4c4d4e4f"
661 ),
662 concat!(
663 "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
664 "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef",
665 "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
666 ),
667 concat!(
668 "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c",
669 "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71",
670 "cc30c58179ec3e87c14c01d5c1f3434f1d87"
671 ),
672 ),
673 (
674 "",
675 "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
676 "",
677 concat!(
678 "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d",
679 "9d201395faa4b61a96c8"
680 ),
681 ),
682 ];
683 for (n, (salt, ikm, info, okm)) in cases.into_iter().enumerate() {
684 let expected = hex(okm);
685 let mut out = vec![0; expected.len()];
686 hkdf_sha256(&hex(salt), &hex(ikm), &hex(info), &mut out);
687 assert_eq!(out, expected, "test case {}", n + 1);
688 }
689 }
690
691 #[test]
695 fn a_catalog_key_is_hkdf_of_its_parent_with_the_catalog_label() {
696 let catalog = catalog_key(&key_of([0x44; KEY_LEN])).expect("derives");
697 assert_eq!(
698 catalog.bytes(),
699 hex("c0df4ea4f63e64687ca93c494fff4ca00b3ae13112766d8ea24cca35c6b1031b")
700 );
701 }
702
703 #[test]
704 fn distinct_parents_or_labels_give_distinct_keys() {
705 let parent = Key::generate().expect("key");
706 let again = catalog_key(&parent).expect("derives");
707 assert_eq!(
708 catalog_key(&parent).expect("derives").bytes(),
709 again.bytes()
710 );
711 let other = catalog_key(&Key::generate().expect("key")).expect("derives");
712 assert_ne!(other.bytes(), again.bytes());
713 let mut a = [0; KEY_LEN];
714 let mut b = [0; KEY_LEN];
715 hkdf_sha256(&[], parent.bytes(), b"label a", &mut a);
716 hkdf_sha256(&[], parent.bytes(), b"label b", &mut b);
717 assert_ne!(a, b);
718 }
719
720 #[test]
721 fn a_rewrapped_key_opens_under_the_new_key_only() {
722 let old = Key::generate().expect("key");
723 let new = Key::generate().expect("key");
724 let key = Key::generate().expect("key");
725 let wrapped = wrap(&old, &version_ctx(), &key);
726 let rewrapped = rewrap(&old, &new, &version_ctx(), &wrapped).expect("rewraps");
727 let opened = unwrap(&new, &version_ctx(), &rewrapped).expect("opens under new");
728 assert_eq!(opened.bytes(), key.bytes());
729 assert!(unwrap(&old, &version_ctx(), &rewrapped).is_err(), "old key");
730 assert!(
731 unwrap(&new, &TENANT_CTX, &rewrapped).is_err(),
732 "context kept"
733 );
734 }
735
736 #[test]
737 fn rewrapping_needs_the_right_old_key_and_context() {
738 let old = Key::generate().expect("key");
739 let new = Key::generate().expect("key");
740 let wrapped = wrap(&old, &TENANT_CTX, &Key::generate().expect("key"));
741 assert!(matches!(
742 rewrap(&new, &new, &TENANT_CTX, &wrapped),
743 Err(KeyError::Damaged)
744 ));
745 assert!(matches!(
746 rewrap(&old, &new, &version_ctx(), &wrapped),
747 Err(KeyError::Damaged)
748 ));
749 }
750
751 #[test]
752 fn wrapping_warns_from_2_to_the_31_and_stops_at_2_to_the_32() {
753 let cases = [
754 (0, WrapLimit::Allow),
755 ((1 << 31) - 1, WrapLimit::Allow),
756 (1 << 31, WrapLimit::Warn),
757 ((1 << 32) - 1, WrapLimit::Warn),
758 (1 << 32, WrapLimit::Refuse),
759 (u64::MAX, WrapLimit::Refuse),
760 ];
761 for (wraps, expected) in cases {
762 assert_eq!(wrap_limit(wraps), expected, "{wraps} wraps");
763 }
764 }
765
766 #[test]
767 fn the_sqlcipher_literal_spells_the_key_in_hex() {
768 let mut bytes = [0; KEY_LEN];
769 bytes[0] = 0x0a;
770 bytes[31] = 0xff;
771 let literal = key_of(bytes).sqlcipher_raw();
772 let expected = format!("x'0a{}ff'", "00".repeat(30));
773 assert_eq!(*literal, expected);
774 assert_eq!(literal.capacity(), literal.len(), "built at its final size");
775 }
776
777 #[test]
778 fn keys_can_be_shared_between_threads() {
779 fn shared<T: Send + Sync>() {}
780 shared::<Key>();
781 }
782}