1use std::collections::BTreeMap;
10use std::fmt;
11
12use corium_crypt::{KeyId, Keyring, SecretKey};
13
14use crate::{RootStore, StoreError};
15
16pub const KEY_MANIFEST_FORMAT_VERSION: u32 = 1;
18
19const MANIFEST_HEADER: &str = "corium-keys-v";
20const ALGORITHM_AES_256: &str = "aes-256";
21
22pub const LOG_RECORDS_PER_EPOCH_LIMIT: u64 = 1 << 32;
34
35pub const LOG_RECORDS_PER_EPOCH_WARN: u64 = LOG_RECORDS_PER_EPOCH_LIMIT / 2;
40
41#[must_use]
43pub fn keys_root_name(db: &str) -> String {
44 format!("keys:{db}")
45}
46
47#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
55pub enum StorageAlgorithm {
56 #[default]
58 Aes256,
59}
60
61impl StorageAlgorithm {
62 fn as_str(self) -> &'static str {
63 match self {
64 Self::Aes256 => ALGORITHM_AES_256,
65 }
66 }
67
68 fn parse(text: &str) -> Option<Self> {
69 match text {
70 ALGORITHM_AES_256 => Some(Self::Aes256),
71 _ => None,
72 }
73 }
74}
75
76impl fmt::Display for StorageAlgorithm {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.write_str(self.as_str())
79 }
80}
81
82#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
89pub enum StorageKeyState {
90 #[default]
92 Active,
93 Retiring,
95 Retired,
97}
98
99impl StorageKeyState {
100 fn as_str(self) -> &'static str {
101 match self {
102 Self::Active => "active",
103 Self::Retiring => "retiring",
104 Self::Retired => "retired",
105 }
106 }
107
108 fn parse(text: &str) -> Option<Self> {
109 match text {
110 "active" => Some(Self::Active),
111 "retiring" => Some(Self::Retiring),
112 "retired" => Some(Self::Retired),
113 _ => None,
114 }
115 }
116}
117
118impl fmt::Display for StorageKeyState {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 f.write_str(self.as_str())
121 }
122}
123
124#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct StorageKey {
127 pub epoch: u32,
129 pub kek_epoch: u32,
132 pub algorithm: StorageAlgorithm,
134 pub wrapped_dek: Vec<u8>,
136 pub created_at_unix_ms: i64,
138 pub opened_at_t: u64,
149 pub state: StorageKeyState,
151 pub live_objects: u64,
156}
157
158#[derive(Clone, Debug, Eq, PartialEq)]
165pub struct ProtectionClassKey {
166 pub class: u64,
168 pub key_id: KeyId,
170 pub current_epoch: u32,
172}
173
174#[derive(Clone, Debug, Eq, PartialEq)]
176pub struct KeyManifest {
177 pub format_version: u32,
179 pub kek: KeyId,
183 pub storage_keys: Vec<StorageKey>,
185 pub classes: Vec<ProtectionClassKey>,
187}
188
189impl KeyManifest {
190 pub async fn create(
200 keyring: &dyn Keyring,
201 kek: KeyId,
202 created_at_unix_ms: i64,
203 ) -> Result<Self, StoreError> {
204 let mut manifest = Self {
205 format_version: KEY_MANIFEST_FORMAT_VERSION,
206 kek,
207 storage_keys: Vec::new(),
208 classes: Vec::new(),
209 };
210 manifest
211 .mint_storage_key(keyring, 1, created_at_unix_ms, 0)
212 .await?;
213 Ok(manifest)
214 }
215
216 #[must_use]
222 pub fn active_storage_epoch(&self) -> Option<u32> {
223 self.storage_keys
224 .iter()
225 .filter(|key| key.state == StorageKeyState::Active)
226 .map(|key| key.epoch)
227 .max()
228 }
229
230 #[must_use]
232 pub fn storage_key(&self, epoch: u32) -> Option<&StorageKey> {
233 self.storage_keys.iter().find(|key| key.epoch == epoch)
234 }
235
236 pub async fn unwrap_storage_keys(
251 &self,
252 keyring: &dyn Keyring,
253 ) -> Result<BTreeMap<u32, SecretKey>, StoreError> {
254 let mut keys = BTreeMap::new();
255 for key in &self.storage_keys {
256 let material = keyring
257 .unwrap(&self.kek, key.kek_epoch, &key.wrapped_dek)
258 .await
259 .map_err(StoreError::Keyring)?;
260 keys.insert(key.epoch, material);
261 }
262 Ok(keys)
263 }
264
265 #[must_use]
274 pub fn log_records_sealed(&self, epoch: u32, current_t: u64) -> Option<u64> {
275 let key = self.storage_key(epoch)?;
276 let end = self
277 .storage_keys
278 .iter()
279 .filter(|other| other.epoch > epoch)
280 .map(|other| other.opened_at_t)
281 .min()
282 .unwrap_or(current_t);
283 Some(end.saturating_sub(key.opened_at_t))
287 }
288
289 #[must_use]
292 pub fn storage_rotation_due(&self, current_t: u64) -> bool {
293 self.active_storage_epoch()
294 .and_then(|epoch| self.log_records_sealed(epoch, current_t))
295 .is_some_and(|sealed| sealed >= LOG_RECORDS_PER_EPOCH_WARN)
296 }
297
298 pub async fn rotate_storage_key(
314 &mut self,
315 keyring: &dyn Keyring,
316 created_at_unix_ms: i64,
317 current_t: u64,
318 ) -> Result<u32, StoreError> {
319 let epoch = self
320 .storage_keys
321 .iter()
322 .map(|key| key.epoch)
323 .max()
324 .unwrap_or(0)
325 .checked_add(1)
326 .ok_or(StoreError::StorageEpochExhausted)?;
327 if self
328 .storage_keys
329 .iter()
330 .any(|key| key.opened_at_t > current_t)
331 {
332 return Err(invalid("rotation basis precedes an existing epoch"));
333 }
334 for key in &mut self.storage_keys {
335 if key.state == StorageKeyState::Active {
336 key.state = StorageKeyState::Retiring;
337 }
338 }
339 self.mint_storage_key(keyring, epoch, created_at_unix_ms, current_t)
340 .await?;
341 Ok(epoch)
342 }
343
344 pub async fn rewrap(&mut self, keyring: &dyn Keyring, kek: KeyId) -> Result<(), StoreError> {
354 let kek_epoch = keyring
355 .current_epoch(&kek)
356 .await
357 .map_err(StoreError::Keyring)?;
358 let mut rewrapped = Vec::with_capacity(self.storage_keys.len());
359 for key in &self.storage_keys {
360 let material = keyring
361 .unwrap(&self.kek, key.kek_epoch, &key.wrapped_dek)
362 .await
363 .map_err(StoreError::Keyring)?;
364 rewrapped.push(StorageKey {
365 kek_epoch,
366 wrapped_dek: keyring
367 .wrap(&kek, kek_epoch, &material)
368 .await
369 .map_err(StoreError::Keyring)?,
370 ..key.clone()
371 });
372 }
373 self.kek = kek;
374 self.storage_keys = rewrapped;
375 Ok(())
376 }
377
378 async fn mint_storage_key(
379 &mut self,
380 keyring: &dyn Keyring,
381 epoch: u32,
382 created_at_unix_ms: i64,
383 opened_at_t: u64,
384 ) -> Result<(), StoreError> {
385 let kek_epoch = keyring
386 .current_epoch(&self.kek)
387 .await
388 .map_err(StoreError::Keyring)?;
389 let dek = SecretKey::generate()?;
390 let wrapped_dek = keyring
391 .wrap(&self.kek, kek_epoch, &dek)
392 .await
393 .map_err(StoreError::Keyring)?;
394 self.storage_keys.push(StorageKey {
395 epoch,
396 kek_epoch,
397 algorithm: StorageAlgorithm::default(),
398 wrapped_dek,
399 created_at_unix_ms,
400 opened_at_t,
401 state: StorageKeyState::Active,
402 live_objects: 0,
403 });
404 self.storage_keys.sort_by_key(|key| key.epoch);
405 Ok(())
406 }
407
408 #[must_use]
410 pub fn encode(&self) -> Vec<u8> {
411 use fmt::Write as _;
412 let mut out = format!("{MANIFEST_HEADER}{}\n{}\n", self.format_version, self.kek);
413 let _ = writeln!(out, "{}", self.storage_keys.len());
414 for key in &self.storage_keys {
415 let _ = writeln!(
419 out,
420 "{} {} {} {} {} {} {} {}",
421 key.epoch,
422 key.kek_epoch,
423 key.algorithm,
424 key.state,
425 key.created_at_unix_ms,
426 key.opened_at_t,
427 key.live_objects,
428 hex(&key.wrapped_dek),
429 );
430 }
431 let _ = writeln!(out, "{}", self.classes.len());
432 for class in &self.classes {
433 let _ = writeln!(
435 out,
436 "{} {} {}",
437 class.class, class.current_epoch, class.key_id,
438 );
439 }
440 out.into_bytes()
441 }
442
443 pub fn decode(bytes: &[u8]) -> Result<Self, StoreError> {
451 let text = std::str::from_utf8(bytes).map_err(|_| invalid("manifest is not UTF-8"))?;
452 let mut lines = text.lines();
453 let format_version: u32 = lines
454 .next()
455 .and_then(|line| line.strip_prefix(MANIFEST_HEADER))
456 .and_then(|version| version.parse().ok())
457 .ok_or_else(|| invalid("missing manifest header"))?;
458 if format_version > KEY_MANIFEST_FORMAT_VERSION {
459 return Err(StoreError::UnsupportedKeyManifest {
460 found: format_version,
461 supported: KEY_MANIFEST_FORMAT_VERSION,
462 });
463 }
464 let kek = lines
465 .next()
466 .ok_or_else(|| invalid("missing key-encryption key"))
467 .and_then(|line| KeyId::new(line).map_err(StoreError::Keyring))?;
468
469 let storage_count = parse_count(lines.next())?;
471 let mut storage_keys = Vec::new();
472 for _ in 0..storage_count {
473 let line = lines
474 .next()
475 .ok_or_else(|| invalid("truncated storage key"))?;
476 storage_keys.push(decode_storage_key(line)?);
477 }
478 let class_count = parse_count(lines.next())?;
479 let mut classes = Vec::new();
480 for _ in 0..class_count {
481 let line = lines.next().ok_or_else(|| invalid("truncated class key"))?;
482 classes.push(decode_class_key(line)?);
483 }
484 if lines.next().is_some() {
485 return Err(invalid("trailing content after the class entries"));
486 }
487 let manifest = Self {
488 format_version,
489 kek,
490 storage_keys,
491 classes,
492 };
493 manifest.validate()?;
494 Ok(manifest)
495 }
496
497 pub fn validate(&self) -> Result<(), StoreError> {
510 for pair in self.storage_keys.windows(2) {
514 if pair[0].epoch >= pair[1].epoch {
515 return Err(invalid("storage-key epochs are not strictly ascending"));
516 }
517 if pair[0].opened_at_t > pair[1].opened_at_t {
518 return Err(invalid("storage-key epochs are not ordered in t"));
519 }
520 }
521 for pair in self.classes.windows(2) {
522 if pair[0].class >= pair[1].class {
523 return Err(invalid("class entries are not strictly ascending"));
524 }
525 }
526 let active = self
527 .storage_keys
528 .iter()
529 .filter(|key| key.state == StorageKeyState::Active)
530 .count();
531 if !self.storage_keys.is_empty() && active != 1 {
535 return Err(invalid(if active == 0 {
536 "no active storage-key epoch"
537 } else {
538 "more than one active storage-key epoch"
539 }));
540 }
541 Ok(())
542 }
543}
544
545pub async fn load_key_manifest(
556 store: &dyn RootStore,
557 db: &str,
558) -> Result<Option<KeyManifest>, StoreError> {
559 match store.get_root(&keys_root_name(db)).await? {
560 Some(bytes) => KeyManifest::decode(&bytes).map(Some),
561 None => Ok(None),
562 }
563}
564
565pub async fn publish_key_manifest(
580 store: &dyn RootStore,
581 db: &str,
582 previous: Option<&KeyManifest>,
583 manifest: &KeyManifest,
584) -> Result<(), StoreError> {
585 manifest.validate()?;
586 let expected = previous.map(KeyManifest::encode);
587 store
588 .cas_root(&keys_root_name(db), expected.as_deref(), &manifest.encode())
589 .await
590}
591
592fn invalid(reason: &str) -> StoreError {
593 StoreError::InvalidKeyManifest(reason.to_owned())
594}
595
596fn parse_count(line: Option<&str>) -> Result<usize, StoreError> {
606 line.and_then(|line| line.parse().ok())
607 .ok_or_else(|| invalid("missing entry count"))
608}
609
610fn decode_storage_key(line: &str) -> Result<StorageKey, StoreError> {
611 let mut fields = line.split(' ');
612 let mut next = || {
613 fields
614 .next()
615 .ok_or_else(|| invalid("truncated storage key"))
616 };
617 let epoch = next()?
618 .parse()
619 .map_err(|_| invalid("invalid storage-key epoch"))?;
620 let kek_epoch = next()?.parse().map_err(|_| invalid("invalid KEK epoch"))?;
621 let algorithm = next().and_then(|text| {
622 StorageAlgorithm::parse(text)
623 .ok_or_else(|| StoreError::UnsupportedKeyAlgorithm(text.to_owned()))
624 })?;
625 let state = next().and_then(|text| {
626 StorageKeyState::parse(text).ok_or_else(|| invalid("invalid storage-key state"))
627 })?;
628 let created_at_unix_ms = next()?
629 .parse()
630 .map_err(|_| invalid("invalid storage-key timestamp"))?;
631 let opened_at_t = next()?
632 .parse()
633 .map_err(|_| invalid("invalid storage-key basis"))?;
634 let live_objects = next()?
635 .parse()
636 .map_err(|_| invalid("invalid live-object count"))?;
637 let wrapped_dek = next().and_then(unhex)?;
638 if fields.next().is_some() {
639 return Err(invalid("trailing storage-key field"));
640 }
641 Ok(StorageKey {
642 epoch,
643 kek_epoch,
644 algorithm,
645 wrapped_dek,
646 created_at_unix_ms,
647 opened_at_t,
648 state,
649 live_objects,
650 })
651}
652
653fn decode_class_key(line: &str) -> Result<ProtectionClassKey, StoreError> {
654 let mut fields = line.splitn(3, ' ');
655 let mut next = || fields.next().ok_or_else(|| invalid("truncated class key"));
656 let class = next()?.parse().map_err(|_| invalid("invalid class id"))?;
657 let current_epoch = next()?
658 .parse()
659 .map_err(|_| invalid("invalid class epoch"))?;
660 let key_id = next().and_then(|text| KeyId::new(text).map_err(StoreError::Keyring))?;
661 Ok(ProtectionClassKey {
662 class,
663 key_id,
664 current_epoch,
665 })
666}
667
668fn hex(bytes: &[u8]) -> String {
669 use fmt::Write as _;
670 bytes.iter().fold(String::new(), |mut out, byte| {
671 let _ = write!(out, "{byte:02x}");
672 out
673 })
674}
675
676fn unhex(text: &str) -> Result<Vec<u8>, StoreError> {
677 if !text.len().is_multiple_of(2) {
678 return Err(invalid("wrapped key is not hex"));
679 }
680 text.as_bytes()
681 .chunks(2)
682 .map(|pair| {
683 std::str::from_utf8(pair)
684 .ok()
685 .and_then(|pair| u8::from_str_radix(pair, 16).ok())
686 .ok_or_else(|| invalid("wrapped key is not hex"))
687 })
688 .collect()
689}
690
691#[cfg(test)]
692mod tests {
693 use super::*;
694 use corium_crypt::StaticKeyring;
695
696 fn keyring() -> StaticKeyring {
697 let mut keyring = StaticKeyring::default();
698 keyring.insert(
699 KeyId::new("file:/etc/corium/storage.key").expect("kek"),
700 1,
701 SecretKey::new([9; 32]),
702 true,
703 );
704 keyring
705 }
706
707 fn kek() -> KeyId {
708 KeyId::new("file:/etc/corium/storage.key").expect("kek")
709 }
710
711 #[tokio::test]
712 async fn a_created_manifest_round_trips_and_unwraps() {
713 let keyring = keyring();
714 let mut manifest = KeyManifest::create(&keyring, kek(), 1_700_000_000_000)
715 .await
716 .expect("create");
717 manifest.classes.push(ProtectionClassKey {
718 class: 74,
719 key_id: KeyId::new("awskms:arn:aws:kms:us-west-2:1:key/2f1c").expect("class key"),
720 current_epoch: 3,
721 });
722
723 let encoded = manifest.encode();
724 assert_eq!(KeyManifest::decode(&encoded).expect("decode"), manifest);
725 assert!(!encoded.windows(32).any(|window| window == [9_u8; 32]));
728
729 assert_eq!(manifest.active_storage_epoch(), Some(1));
730 let keys = manifest
731 .unwrap_storage_keys(&keyring)
732 .await
733 .expect("unwrap");
734 assert_eq!(keys.keys().copied().collect::<Vec<_>>(), vec![1]);
735 }
736
737 #[tokio::test]
738 async fn rotation_opens_an_epoch_and_keeps_the_old_one_readable() {
739 let keyring = keyring();
740 let mut manifest = KeyManifest::create(&keyring, kek(), 1)
741 .await
742 .expect("create");
743 let first = manifest
744 .unwrap_storage_keys(&keyring)
745 .await
746 .expect("unwrap")[&1]
747 .clone();
748
749 assert_eq!(
750 manifest
751 .rotate_storage_key(&keyring, 2, 5_000)
752 .await
753 .expect("rotate"),
754 2
755 );
756 assert_eq!(manifest.active_storage_epoch(), Some(2));
757 assert_eq!(
758 manifest.storage_key(1).expect("epoch 1").state,
759 StorageKeyState::Retiring
760 );
761
762 let keys = manifest
763 .unwrap_storage_keys(&keyring)
764 .await
765 .expect("unwrap");
766 assert_eq!(keys.keys().copied().collect::<Vec<_>>(), vec![1, 2]);
767 assert_eq!(keys[&1], first, "a rotation must not disturb old epochs");
768 assert_ne!(keys[&2], first);
769 }
770
771 #[tokio::test]
772 async fn an_epochs_nonce_budget_is_the_span_of_t_it_covers() {
773 let keyring = keyring();
774 let mut manifest = KeyManifest::create(&keyring, kek(), 1)
775 .await
776 .expect("create");
777 assert_eq!(manifest.log_records_sealed(1, 900), Some(900));
780 assert!(!manifest.storage_rotation_due(900));
781 assert!(manifest.storage_rotation_due(LOG_RECORDS_PER_EPOCH_WARN));
782
783 manifest
784 .rotate_storage_key(&keyring, 2, 1_000)
785 .await
786 .expect("rotate");
787 assert_eq!(manifest.log_records_sealed(1, 4_000), Some(1_000));
790 assert_eq!(manifest.log_records_sealed(2, 4_000), Some(3_000));
791 assert_eq!(manifest.log_records_sealed(3, 4_000), None);
792 assert!(!manifest.storage_rotation_due(4_000));
793 assert!(manifest.storage_rotation_due(1_000 + LOG_RECORDS_PER_EPOCH_WARN));
794
795 assert_eq!(manifest.log_records_sealed(2, 10), Some(0));
798 assert!(matches!(
801 manifest.rotate_storage_key(&keyring, 3, 10).await,
802 Err(StoreError::InvalidKeyManifest(_))
803 ));
804 }
805
806 #[tokio::test]
807 async fn structurally_impossible_manifests_are_rejected() {
808 let keyring = keyring();
809 let mut manifest = KeyManifest::create(&keyring, kek(), 1)
810 .await
811 .expect("create");
812 manifest
813 .rotate_storage_key(&keyring, 2, 1_000)
814 .await
815 .expect("rotate");
816 assert!(manifest.validate().is_ok());
817 assert!(KeyManifest::decode(&manifest.encode()).is_ok());
818
819 let mut two_active = manifest.clone();
821 two_active.storage_keys[0].state = StorageKeyState::Active;
822 assert!(two_active.validate().is_err());
823 assert!(KeyManifest::decode(&two_active.encode()).is_err());
824
825 let mut none_active = manifest.clone();
827 for key in &mut none_active.storage_keys {
828 key.state = StorageKeyState::Retired;
829 }
830 assert!(none_active.validate().is_err());
831
832 let mut duplicate = manifest.clone();
834 duplicate.storage_keys[1].epoch = 1;
835 assert!(duplicate.validate().is_err());
836
837 let mut unordered = manifest.clone();
839 unordered.storage_keys[0].opened_at_t = 9_999;
840 assert!(unordered.validate().is_err());
841
842 let mut classes = manifest;
845 classes.classes = vec![
846 ProtectionClassKey {
847 class: 74,
848 key_id: KeyId::new("file:a").expect("key"),
849 current_epoch: 1,
850 },
851 ProtectionClassKey {
852 class: 74,
853 key_id: KeyId::new("file:b").expect("key"),
854 current_epoch: 1,
855 },
856 ];
857 assert!(classes.validate().is_err());
858 assert!(KeyManifest::decode(&classes.encode()).is_err());
859 }
860
861 #[test]
862 fn trailing_content_after_the_entries_is_rejected() {
863 let mut encoded = b"corium-keys-v1\nfile:kek\n0\n0\n".to_vec();
864 assert!(KeyManifest::decode(&encoded).is_ok());
865 encoded.extend_from_slice(b"1 1 aes-256 active 0 0 0 00\n");
866 assert!(matches!(
867 KeyManifest::decode(&encoded),
868 Err(StoreError::InvalidKeyManifest(_))
869 ));
870 }
871
872 #[tokio::test]
873 async fn rewrapping_changes_the_kek_and_no_data_key() {
874 let mut keyring = keyring();
875 let replacement = KeyId::new("awskms:arn:aws:kms:us-west-2:1:key/9ab3").expect("kek");
876 keyring.insert(replacement.clone(), 7, SecretKey::new([4; 32]), true);
877
878 let mut manifest = KeyManifest::create(&keyring, kek(), 1)
879 .await
880 .expect("create");
881 manifest
882 .rotate_storage_key(&keyring, 2, 5_000)
883 .await
884 .expect("rotate");
885 let before = manifest
886 .unwrap_storage_keys(&keyring)
887 .await
888 .expect("unwrap");
889 let wrapped_before: Vec<_> = manifest
890 .storage_keys
891 .iter()
892 .map(|key| key.wrapped_dek.clone())
893 .collect();
894
895 manifest
896 .rewrap(&keyring, replacement.clone())
897 .await
898 .expect("rewrap");
899
900 assert_eq!(manifest.kek, replacement);
901 assert!(manifest.storage_keys.iter().all(|key| key.kek_epoch == 7));
902 assert!(
903 manifest
904 .storage_keys
905 .iter()
906 .zip(&wrapped_before)
907 .all(|(key, before)| key.wrapped_dek != *before)
908 );
909 assert_eq!(
910 manifest
911 .unwrap_storage_keys(&keyring)
912 .await
913 .expect("unwrap"),
914 before,
915 "re-wrapping must not change any data key"
916 );
917 }
918
919 #[tokio::test]
920 async fn an_unresolvable_epoch_fails_at_open_naming_the_key() {
921 let keyring = keyring();
922 let manifest = KeyManifest::create(&keyring, kek(), 1)
923 .await
924 .expect("create");
925 let error = manifest
926 .unwrap_storage_keys(&StaticKeyring::default())
927 .await
928 .expect_err("no KEK");
929 assert!(
930 error.to_string().contains("file:/etc/corium/storage.key"),
931 "{error}"
932 );
933 }
934
935 #[test]
936 fn a_newer_manifest_is_refused_rather_than_half_understood() {
937 let encoded = b"corium-keys-v2\nfile:kek\n0\n0\n".as_slice();
938 assert!(matches!(
939 KeyManifest::decode(encoded),
940 Err(StoreError::UnsupportedKeyManifest {
941 found: 2,
942 supported: 1
943 })
944 ));
945 }
946
947 #[test]
948 fn an_overstated_entry_count_is_bounded_by_the_record() {
949 for bytes in [
953 format!("corium-keys-v1\nfile:kek\n{}\n0\n", usize::MAX),
954 format!("corium-keys-v1\nfile:kek\n0\n{}\n", usize::MAX),
955 format!("corium-keys-v1\nfile:kek\n{}\n", u64::MAX),
956 ] {
957 assert!(matches!(
958 KeyManifest::decode(bytes.as_bytes()),
959 Err(StoreError::InvalidKeyManifest(_))
960 ));
961 }
962 }
963
964 #[test]
965 fn malformed_manifests_are_rejected() {
966 for bytes in [
967 b"".as_slice(),
968 b"corium-keys-v1\n".as_slice(),
969 b"corium-keys-v1\nfile:kek\n1\n".as_slice(),
970 b"corium-keys-v1\nfile:kek\n1\n1 1 aes-256 active 0 0 0 xyz\n0\n".as_slice(),
972 b"corium-keys-v1\nfile:kek\n1\n1 1 aes-256 sideways 0 0 0 00\n0\n".as_slice(),
974 b"corium-keys-v1\nfile:kek\n1\n1 1 aes-256 active 0 0 00\n0\n".as_slice(),
976 b"corium-keys-v1\n\n0\n0\n".as_slice(),
978 ] {
979 assert!(
980 KeyManifest::decode(bytes).is_err(),
981 "accepted {:?}",
982 String::from_utf8_lossy(bytes)
983 );
984 }
985 assert!(matches!(
986 KeyManifest::decode(b"corium-keys-v1\nfile:kek\n1\n1 1 rot13 active 0 0 0 00\n0\n"),
987 Err(StoreError::UnsupportedKeyAlgorithm(_))
988 ));
989 }
990}