1use std::collections::BTreeMap;
2use std::fmt;
3use std::num::{NonZeroU32, NonZeroU64};
4use std::str::FromStr;
5
6use crate::keys::KeyError;
7use chacha20poly1305::aead::generic_array::GenericArray;
8use chacha20poly1305::aead::{Aead, Payload};
9use chacha20poly1305::{KeyInit, XChaCha20Poly1305};
10use hkdf::Hkdf;
11use rand::RngCore;
12use sha2::{Digest, Sha256};
13use thiserror::Error;
14use tracing::info;
15
16pub(crate) const NONCE_SIZE: usize = 24;
18
19pub const TAG_SIZE: usize = 16;
21
22pub const CHUNK_SIZE: usize = 65536;
24pub const INITIAL_KEY_GENERATION: u64 = 1;
25
26const KEY_TAG_MARKER: &[u8; 3] = b"CKF";
27
28const KEY_TAG_VERSION: u8 = 1;
31
32pub struct KeyTag;
53
54impl KeyTag {
55 pub const LEN: usize = KEY_TAG_MARKER.len() + 1 + 32;
56
57 pub fn write(fingerprint: &[u8; 32]) -> Vec<u8> {
58 Self::tagged(fingerprint, KEY_TAG_VERSION)
59 }
60
61 #[cfg(test)]
64 pub(crate) fn write_version_for_test(fingerprint: &[u8; 32], version: u8) -> Vec<u8> {
65 Self::tagged(fingerprint, version)
66 }
67
68 fn tagged(fingerprint: &[u8; 32], version: u8) -> Vec<u8> {
69 let mut tag = Vec::with_capacity(Self::LEN);
70 tag.extend_from_slice(KEY_TAG_MARKER);
71 tag.push(version);
72 tag.extend_from_slice(fingerprint);
73 tag
74 }
75
76 pub fn read(stored: &[u8]) -> Result<([u8; 32], &[u8]), KeyTagError> {
79 if stored.len() < Self::LEN {
80 return Err(KeyTagError::Truncated);
81 }
82 let (tag, body) = stored.split_at(Self::LEN);
83 let (marker, versioned) = tag.split_at(KEY_TAG_MARKER.len());
84 if marker != KEY_TAG_MARKER {
85 return Err(KeyTagError::Unmarked);
86 }
87 let (&version, fingerprint) = versioned
88 .split_first()
89 .expect("a key tag holds its version byte");
90 if version != KEY_TAG_VERSION {
91 return Err(KeyTagError::UnknownVersion(version));
92 }
93 Ok((
94 fingerprint
95 .try_into()
96 .expect("a key tag holds 32 fingerprint bytes"),
97 body,
98 ))
99 }
100}
101
102#[derive(Debug, Error)]
104pub enum KeyTagError {
105 #[error("sealed payload is too short to carry a key tag")]
106 Truncated,
107 #[error("sealed payload carries no key tag")]
108 Unmarked,
109 #[error("unsupported key tag version {0}")]
110 UnknownVersion(u8),
111}
112
113impl From<KeyTagError> for SealError {
117 fn from(error: KeyTagError) -> Self {
118 match error {
119 KeyTagError::UnknownVersion(version) => Self::UnknownVersion(version),
120 KeyTagError::Truncated | KeyTagError::Unmarked => Self::Crypto(error.into()),
121 }
122 }
123}
124
125#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub struct KeyFingerprint([u8; 32]);
129
130impl KeyFingerprint {
131 pub fn from_bytes(bytes: [u8; 32]) -> Self {
132 Self(bytes)
133 }
134
135 pub fn as_bytes(&self) -> &[u8; 32] {
136 &self.0
137 }
138}
139
140impl fmt::Debug for KeyFingerprint {
141 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142 fmt::Display::fmt(self, formatter)
143 }
144}
145
146impl fmt::Display for KeyFingerprint {
147 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148 formatter.write_str(&hex::encode(self.0))
149 }
150}
151
152impl FromStr for KeyFingerprint {
153 type Err = KeyFingerprintParseError;
154
155 fn from_str(value: &str) -> Result<Self, Self::Err> {
156 if value.len() != 64
157 || value
158 .bytes()
159 .any(|byte| !byte.is_ascii_digit() && !(b'a'..=b'f').contains(&byte))
160 {
161 return Err(KeyFingerprintParseError(value.to_string()));
162 }
163 let bytes: [u8; 32] = hex::decode(value)
164 .map_err(|_| KeyFingerprintParseError(value.to_string()))?
165 .try_into()
166 .map_err(|_| KeyFingerprintParseError(value.to_string()))?;
167 Ok(Self(bytes))
168 }
169}
170
171impl serde::Serialize for KeyFingerprint {
172 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: serde::Serializer,
175 {
176 serializer.serialize_str(&self.to_string())
177 }
178}
179
180impl<'de> serde::Deserialize<'de> for KeyFingerprint {
181 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182 where
183 D: serde::Deserializer<'de>,
184 {
185 <String as serde::Deserialize>::deserialize(deserializer)?
186 .parse()
187 .map_err(serde::de::Error::custom)
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
192#[error("key fingerprint must be exactly 64 lowercase hexadecimal characters: {0:?}")]
193pub struct KeyFingerprintParseError(String);
194
195pub fn generate_random_key() -> [u8; 32] {
197 let mut key = [0u8; 32];
198 rand::rng().fill_bytes(&mut key);
199 key
200}
201
202pub fn chunked_encrypted_len(plaintext_len: u64) -> u64 {
205 whole_object_header(plaintext_len).sealed_len()
206}
207
208fn whole_object_header(plaintext_len: u64) -> SealedBlobHeader {
215 SealedBlobHeader::new(
216 DEFAULT_BLOB_CHUNK_SIZE,
217 plaintext_len,
218 &NoncePolicy::RandomStored,
219 )
220}
221
222struct ChunkCipher {
227 cipher: XChaCha20Poly1305,
228 base_nonce: [u8; NONCE_SIZE],
229}
230
231impl ChunkCipher {
232 fn new(key: &[u8; 32], base_nonce: [u8; NONCE_SIZE]) -> Self {
233 Self {
234 cipher: XChaCha20Poly1305::new(GenericArray::from_slice(key)),
235 base_nonce,
236 }
237 }
238
239 fn seal(&self, index: u64, aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
240 self.cipher
241 .encrypt(
242 GenericArray::from_slice(&chunk_nonce(&self.base_nonce, index)),
243 Payload {
244 msg: plaintext,
245 aad,
246 },
247 )
248 .expect("encryption should not fail")
249 }
250
251 fn open(&self, index: u64, aad: &[u8], sealed: &[u8]) -> Option<Vec<u8>> {
255 self.cipher
256 .decrypt(
257 GenericArray::from_slice(&chunk_nonce(&self.base_nonce, index)),
258 Payload { msg: sealed, aad },
259 )
260 .ok()
261 }
262}
263
264pub(crate) const SEALED_BLOB_VERSION: u8 = 1;
268
269pub const DEFAULT_BLOB_CHUNK_SIZE: NonZeroU32 = NonZeroU32::new(64 * 1024).expect("64 KiB");
273
274pub const SEALED_BLOB_HEADER_LEN: usize = 1 + 1 + 4 + 8;
280
281const BLOB_AEAD_LABEL: &[u8] = b"coven-blob-aead-v1";
282const BLOB_NONCE_INFO: &[u8] = b"coven-blob-nonce-v1";
283
284const DERIVED_NONCE_TAG: u8 = 0;
285const RANDOM_NONCE_TAG: u8 = 1;
286
287#[derive(Clone, Debug, PartialEq, Eq)]
301pub enum NoncePolicy {
302 RandomStored,
306 DerivedFromContext { context: Vec<u8> },
324}
325
326impl NoncePolicy {
327 fn tag(&self) -> u8 {
328 match self {
329 Self::RandomStored => RANDOM_NONCE_TAG,
330 Self::DerivedFromContext { .. } => DERIVED_NONCE_TAG,
331 }
332 }
333}
334
335#[derive(Clone, Copy, Debug, PartialEq, Eq)]
339enum StoredNonceBase {
340 Derived,
341 Random([u8; NONCE_SIZE]),
342}
343
344#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353pub struct SealedBlobHeader {
354 base: StoredNonceBase,
355 chunk_size: NonZeroU32,
356 plaintext_len: u64,
357}
358
359#[derive(Clone, Debug, PartialEq, Eq, Error)]
361pub enum SealedBlobError {
362 #[error("sealed blob header names version {0}, which this build does not read")]
363 UnknownVersion(u8),
364 #[error("sealed blob header names nonce policy {0}, which this build does not read")]
365 UnknownNoncePolicy(u8),
366 #[error("sealed blob stores nonce policy {stored} but is being opened as {requested}")]
367 NoncePolicyMismatch { stored: u8, requested: u8 },
368 #[error("sealed blob header is {0} bytes, expected {SEALED_BLOB_HEADER_LEN}")]
369 ShortHeader(usize),
370 #[error("sealed blob header names chunk size 0")]
371 ZeroChunkSize,
372 #[error("sealed blob chunk {index} is {actual} bytes, expected {expected}")]
373 ChunkLength {
374 index: u64,
375 expected: usize,
376 actual: usize,
377 },
378 #[error("sealed blob chunk {index} failed authentication")]
379 ChunkAuthentication { index: u64 },
380 #[error("sealed blob range {start}..{end} lies outside its {plaintext_len}-byte plaintext")]
381 RangeOutOfBounds {
382 start: u64,
383 end: u64,
384 plaintext_len: u64,
385 },
386}
387
388impl SealedBlobHeader {
389 pub fn new(chunk_size: NonZeroU32, plaintext_len: u64, policy: &NoncePolicy) -> Self {
394 let base = match policy {
395 NoncePolicy::RandomStored => {
396 let mut nonce = [0u8; NONCE_SIZE];
397 rand::rng().fill_bytes(&mut nonce);
398 StoredNonceBase::Random(nonce)
399 }
400 NoncePolicy::DerivedFromContext { .. } => StoredNonceBase::Derived,
401 };
402 Self {
403 base,
404 chunk_size,
405 plaintext_len,
406 }
407 }
408
409 pub fn parse(bytes: &[u8]) -> Result<Self, SealedBlobError> {
410 if bytes.len() < SEALED_BLOB_HEADER_LEN {
411 return Err(SealedBlobError::ShortHeader(bytes.len()));
412 }
413 if bytes[0] != SEALED_BLOB_VERSION {
414 return Err(SealedBlobError::UnknownVersion(bytes[0]));
415 }
416 let base = match bytes[1] {
417 DERIVED_NONCE_TAG => StoredNonceBase::Derived,
418 RANDOM_NONCE_TAG => {
419 let end = SEALED_BLOB_HEADER_LEN + NONCE_SIZE;
420 if bytes.len() < end {
421 return Err(SealedBlobError::ShortHeader(bytes.len()));
422 }
423 StoredNonceBase::Random(
424 bytes[SEALED_BLOB_HEADER_LEN..end]
425 .try_into()
426 .expect("NONCE_SIZE base nonce bytes"),
427 )
428 }
429 other => return Err(SealedBlobError::UnknownNoncePolicy(other)),
430 };
431 let chunk_size = NonZeroU32::new(u32::from_le_bytes(
432 bytes[2..6].try_into().expect("four header bytes"),
433 ))
434 .ok_or(SealedBlobError::ZeroChunkSize)?;
435 let plaintext_len =
436 u64::from_le_bytes(bytes[6..14].try_into().expect("eight header bytes"));
437 Ok(Self {
438 base,
439 chunk_size,
440 plaintext_len,
441 })
442 }
443
444 pub fn to_bytes(self) -> Vec<u8> {
445 let mut bytes = Vec::with_capacity(self.prefix_len() as usize);
446 bytes.push(SEALED_BLOB_VERSION);
447 bytes.push(match self.base {
448 StoredNonceBase::Derived => DERIVED_NONCE_TAG,
449 StoredNonceBase::Random(_) => RANDOM_NONCE_TAG,
450 });
451 bytes.extend_from_slice(&self.chunk_size.get().to_le_bytes());
452 bytes.extend_from_slice(&self.plaintext_len.to_le_bytes());
453 if let StoredNonceBase::Random(nonce) = self.base {
454 bytes.extend_from_slice(&nonce);
455 }
456 bytes
457 }
458
459 pub fn prefix_len(self) -> u64 {
462 SEALED_BLOB_HEADER_LEN as u64
463 + match self.base {
464 StoredNonceBase::Derived => 0,
465 StoredNonceBase::Random(_) => NONCE_SIZE as u64,
466 }
467 }
468
469 fn base_nonce(
474 self,
475 key: &[u8; 32],
476 policy: &NoncePolicy,
477 ) -> Result<[u8; NONCE_SIZE], SealedBlobError> {
478 match (self.base, policy) {
479 (StoredNonceBase::Random(nonce), NoncePolicy::RandomStored) => Ok(nonce),
480 (StoredNonceBase::Derived, NoncePolicy::DerivedFromContext { context }) => {
481 Ok(derive_nonce_base(key, context))
482 }
483 (stored, policy) => Err(SealedBlobError::NoncePolicyMismatch {
484 stored: match stored {
485 StoredNonceBase::Derived => DERIVED_NONCE_TAG,
486 StoredNonceBase::Random(_) => RANDOM_NONCE_TAG,
487 },
488 requested: policy.tag(),
489 }),
490 }
491 }
492
493 pub fn chunk_size(self) -> NonZeroU32 {
494 self.chunk_size
495 }
496
497 pub fn plaintext_len(self) -> u64 {
498 self.plaintext_len
499 }
500
501 fn chunk_aad(self, aad_context: &[u8], index: u64) -> Vec<u8> {
507 let header = self.to_bytes();
508 let mut aad =
509 Vec::with_capacity(BLOB_AEAD_LABEL.len() + header.len() + 16 + aad_context.len());
510 aad.extend_from_slice(BLOB_AEAD_LABEL);
511 aad.extend_from_slice(&header);
512 aad.extend_from_slice(&(aad_context.len() as u64).to_le_bytes());
513 aad.extend_from_slice(aad_context);
514 aad.extend_from_slice(&index.to_le_bytes());
515 aad
516 }
517
518 pub fn chunk_count(self) -> u64 {
522 self.plaintext_len
523 .div_ceil(u64::from(self.chunk_size.get()))
524 .max(1)
525 }
526
527 pub(crate) fn chunk_plaintext_len(self, index: u64) -> u64 {
530 let start = index.saturating_mul(u64::from(self.chunk_size.get()));
531 self.plaintext_len
532 .saturating_sub(start)
533 .min(u64::from(self.chunk_size.get()))
534 }
535
536 pub fn sealed_chunk_len(self, index: u64) -> u64 {
538 self.chunk_plaintext_len(index) + TAG_SIZE as u64
539 }
540
541 pub fn sealed_len(self) -> u64 {
544 self.prefix_len() + self.plaintext_len + self.chunk_count() * TAG_SIZE as u64
545 }
546
547 pub fn covering_chunks(
550 self,
551 start: u64,
552 end: u64,
553 ) -> Result<std::ops::Range<u64>, SealedBlobError> {
554 if start > end || end > self.plaintext_len {
555 return Err(SealedBlobError::RangeOutOfBounds {
556 start,
557 end,
558 plaintext_len: self.plaintext_len,
559 });
560 }
561 if start == end {
562 return Ok(0..0);
563 }
564 let chunk_size = u64::from(self.chunk_size.get());
565 Ok((start / chunk_size)..((end - 1) / chunk_size + 1))
566 }
567
568 pub fn sealed_span(self, chunks: std::ops::Range<u64>) -> std::ops::Range<u64> {
572 let full = u64::from(self.chunk_size.get()) + TAG_SIZE as u64;
573 let start = self.prefix_len() + chunks.start * full;
574 let mut end = start;
575 for index in chunks {
576 end += self.sealed_chunk_len(index);
577 }
578 start..end
579 }
580
581 pub fn request_runs(
587 self,
588 chunks: std::ops::Range<u64>,
589 window: NonZeroU64,
590 ) -> Vec<std::ops::Range<u64>> {
591 let mut runs = Vec::new();
592 let mut start = chunks.start;
593 while start < chunks.end {
594 let mut end = start;
595 let mut span = 0u64;
596 while end < chunks.end {
597 let next = span.saturating_add(self.sealed_chunk_len(end));
598 if end > start && next > window.get() {
599 break;
600 }
601 span = next;
602 end += 1;
603 }
604 runs.push(start..end);
605 start = end;
606 }
607 runs
608 }
609
610 pub fn plaintext_span(self, chunks: std::ops::Range<u64>) -> std::ops::Range<u64> {
612 let chunk_size = u64::from(self.chunk_size.get());
613 let start = (chunks.start * chunk_size).min(self.plaintext_len);
614 let end = (chunks.end * chunk_size).min(self.plaintext_len);
615 start..end
616 }
617}
618
619fn derive_nonce_base(key: &[u8; 32], context: &[u8]) -> [u8; NONCE_SIZE] {
624 let mut info = Vec::with_capacity(BLOB_NONCE_INFO.len() + context.len());
625 info.extend_from_slice(BLOB_NONCE_INFO);
626 info.extend_from_slice(context);
627 let hk = Hkdf::<Sha256>::new(Some(b"coven-hkdf-salt-v1"), key);
628 let mut base = [0u8; NONCE_SIZE];
629 hk.expand(&info, &mut base)
630 .expect("24 bytes is a valid HKDF output length");
631 base
632}
633
634pub struct SealedBlobSealer {
638 cipher: ChunkCipher,
639 header: SealedBlobHeader,
640 aad_context: Vec<u8>,
641 next_index: u64,
642}
643
644impl SealedBlobSealer {
645 fn new(
646 key: &[u8; 32],
647 header: SealedBlobHeader,
648 policy: &NoncePolicy,
649 aad_context: &[u8],
650 ) -> Result<Self, SealedBlobError> {
651 Ok(Self {
652 cipher: ChunkCipher::new(key, header.base_nonce(key, policy)?),
653 header,
654 aad_context: aad_context.to_vec(),
655 next_index: 0,
656 })
657 }
658
659 pub fn header(&self) -> SealedBlobHeader {
660 self.header
661 }
662
663 pub fn seal_chunk(&mut self, plaintext: &[u8]) -> Vec<u8> {
667 let index = self.next_index;
668 debug_assert_eq!(
669 plaintext.len() as u64,
670 self.header.chunk_plaintext_len(index),
671 "a sealed chunk carries exactly the plaintext its header assigns it",
672 );
673 self.next_index += 1;
674 let aad = self.header.chunk_aad(&self.aad_context, index);
675 self.cipher.seal(index, &aad, plaintext)
676 }
677}
678
679pub struct SealedBlobOpener {
683 cipher: ChunkCipher,
684 header: SealedBlobHeader,
685 aad_context: Vec<u8>,
686}
687
688impl SealedBlobOpener {
689 fn new(
690 key: &[u8; 32],
691 header: SealedBlobHeader,
692 policy: &NoncePolicy,
693 aad_context: &[u8],
694 ) -> Result<Self, SealedBlobError> {
695 Ok(Self {
696 cipher: ChunkCipher::new(key, header.base_nonce(key, policy)?),
697 header,
698 aad_context: aad_context.to_vec(),
699 })
700 }
701
702 pub fn header(&self) -> SealedBlobHeader {
703 self.header
704 }
705
706 pub fn open_chunk(&self, index: u64, sealed: &[u8]) -> Result<Vec<u8>, SealedBlobError> {
710 let expected = self.header.sealed_chunk_len(index);
711 if sealed.len() as u64 != expected {
712 return Err(SealedBlobError::ChunkLength {
713 index,
714 expected: expected as usize,
715 actual: sealed.len(),
716 });
717 }
718 let aad = self.header.chunk_aad(&self.aad_context, index);
719 self.cipher
720 .open(index, &aad, sealed)
721 .ok_or(SealedBlobError::ChunkAuthentication { index })
722 }
723
724 pub fn open_chunks(
729 &self,
730 chunks: std::ops::Range<u64>,
731 sealed: &[u8],
732 ) -> Result<Vec<u8>, SealedBlobError> {
733 let window = self.header.plaintext_span(chunks.clone());
734 let mut plaintext = Vec::with_capacity((window.end - window.start) as usize);
735 let mut offset = 0usize;
736 for index in chunks {
737 let len = self.header.sealed_chunk_len(index) as usize;
738 let sealed_chunk = sealed.get(offset..offset + len).ok_or({
739 SealedBlobError::ChunkLength {
740 index,
741 expected: len,
742 actual: sealed.len().saturating_sub(offset),
743 }
744 })?;
745 offset += len;
746 plaintext.extend(self.open_chunk(index, sealed_chunk)?);
747 }
748 Ok(plaintext)
749 }
750}
751
752#[derive(Error, Debug)]
753pub enum EncryptionError {
754 #[error("sealed blob could not be opened: {0}")]
755 SealedBlob(#[from] SealedBlobError),
756 #[error("sealed payload key tag is invalid: {0}")]
757 KeyTag(#[from] KeyTagError),
758 #[error("serialize keyring: {0}")]
759 SerializeKeyring(#[source] serde_json::Error),
760 #[error("keyring JSON is malformed: {0}")]
761 ParseKeyring(#[source] serde_json::Error),
762 #[error("keyring payload is not UTF-8: {0}")]
763 KeyringUtf8(#[source] std::string::FromUtf8Error),
764 #[error("keyring key is not valid hexadecimal: {0}")]
765 KeyHex(#[source] hex::FromHexError),
766 #[error("keyring key has length {actual}, expected 32")]
767 KeyLength { actual: usize },
768 #[error("keyring has no keys")]
769 EmptyKeyring,
770 #[error("cannot merge keys into a plaintext cloud home")]
771 PlaintextKeyring,
772 #[error("key fingerprint {fingerprint} identifies conflicting entries at generations {existing_generation} and {new_generation}")]
773 FingerprintConflict {
774 fingerprint: KeyFingerprint,
775 existing_generation: u64,
776 new_generation: u64,
777 },
778 #[error("no key with fingerprint {0}")]
779 UnknownKeyFingerprint(KeyFingerprint),
780 #[error("new generation {new_generation} must be greater than current generation {current_generation}")]
781 NonIncreasingGeneration {
782 new_generation: u64,
783 current_generation: u64,
784 },
785}
786
787#[derive(Debug, Error)]
794pub enum SealError {
795 #[error("no master key is established for this store (locked, or never initialized)")]
800 Locked,
801 #[error("custody error: {0}")]
805 Custody(#[from] KeyError),
806 #[error("unsupported sealed app-data version {0}")]
808 UnknownVersion(u8),
809 #[error("sealed app-data names key {0}, which this keyring does not hold")]
812 UnknownKey(String),
813 #[error("app-data cryptography failed: {0}")]
816 Crypto(#[from] EncryptionError),
817}
818
819#[derive(serde::Serialize, serde::Deserialize)]
820struct StoredKeyring {
821 keys: Vec<StoredKeyringGeneration>,
822}
823
824#[derive(serde::Serialize, serde::Deserialize)]
825struct StoredKeyringGeneration {
826 generation: u64,
827 key_hex: String,
828}
829
830#[derive(Clone, PartialEq, Eq)]
834struct KeyEntry {
835 generation: u64,
836 key: [u8; 32],
837}
838
839fn seal_entry_of(keys: &BTreeMap<KeyFingerprint, KeyEntry>) -> (&KeyFingerprint, &KeyEntry) {
847 keys.iter()
848 .max_by(|(fingerprint_a, a), (fingerprint_b, b)| {
849 a.generation
850 .cmp(&b.generation)
851 .then_with(|| fingerprint_a.cmp(fingerprint_b))
852 })
853 .expect("a keyring always holds at least one key")
854}
855
856fn keyring_string(keys: &BTreeMap<KeyFingerprint, KeyEntry>) -> Result<String, EncryptionError> {
859 let payload = StoredKeyring {
860 keys: keys
861 .values()
862 .map(|entry| StoredKeyringGeneration {
863 generation: entry.generation,
864 key_hex: hex::encode(entry.key),
865 })
866 .collect(),
867 };
868 serde_json::to_string(&payload).map_err(EncryptionError::SerializeKeyring)
869}
870
871#[derive(Clone)]
876pub struct MasterKeyring {
877 keys: BTreeMap<KeyFingerprint, KeyEntry>,
878}
879
880impl std::fmt::Debug for MasterKeyring {
881 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
882 f.debug_struct("MasterKeyring")
883 .field("keys", &"<redacted>")
884 .finish()
885 }
886}
887
888impl MasterKeyring {
889 pub fn generate() -> Self {
891 Self::from(EncryptionService::from_key(generate_random_key()))
892 }
893
894 pub fn to_serialized(&self) -> String {
898 keyring_string(&self.keys).expect("a MasterKeyring always holds at least one generation")
899 }
900
901 pub fn from_serialized(s: &str) -> Result<Self, EncryptionError> {
903 EncryptionService::new(s).map(Self::from)
904 }
905
906 pub fn fingerprint(&self) -> String {
909 hex::encode(seal_entry_of(&self.keys).0.as_bytes())
910 }
911}
912
913impl From<EncryptionService> for MasterKeyring {
914 fn from(service: EncryptionService) -> Self {
915 Self { keys: service.keys }
916 }
917}
918
919impl From<MasterKeyring> for EncryptionService {
920 fn from(keyring: MasterKeyring) -> Self {
921 EncryptionService { keys: keyring.keys }
922 }
923}
924
925fn key_fingerprint(key: &[u8; 32]) -> KeyFingerprint {
928 KeyFingerprint::from_bytes(Sha256::digest(key).into())
929}
930
931fn insert_key_entry(
932 keys: &mut BTreeMap<KeyFingerprint, KeyEntry>,
933 fingerprint: KeyFingerprint,
934 entry: KeyEntry,
935) -> Result<(), EncryptionError> {
936 match keys.get(&fingerprint) {
937 None => {
938 keys.insert(fingerprint, entry);
939 Ok(())
940 }
941 Some(existing) if existing == &entry => Ok(()),
942 Some(existing) => Err(EncryptionError::FingerprintConflict {
943 fingerprint,
944 existing_generation: existing.generation,
945 new_generation: entry.generation,
946 }),
947 }
948}
949
950#[derive(Clone)]
956pub struct EncryptionService {
957 keys: BTreeMap<KeyFingerprint, KeyEntry>,
965}
966impl std::fmt::Debug for EncryptionService {
967 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
968 f.debug_struct("EncryptionService")
969 .field("keys", &"<redacted>")
970 .finish()
971 }
972}
973impl EncryptionService {
974 pub fn new(stored_key: &str) -> Result<Self, EncryptionError> {
976 info!("Loading master key...");
977 EncryptionService::from_keyring_json(stored_key)
978 }
979
980 pub fn from_key(key: [u8; 32]) -> Self {
982 Self::from_key_at_generation(INITIAL_KEY_GENERATION, key)
983 }
984
985 pub fn from_key_at_generation(generation: u64, key: [u8; 32]) -> Self {
986 let mut keys = BTreeMap::new();
987 keys.insert(key_fingerprint(&key), KeyEntry { generation, key });
988 EncryptionService { keys }
989 }
990
991 pub fn from_keyring(
992 keys: impl IntoIterator<Item = (u64, [u8; 32])>,
993 ) -> Result<Self, EncryptionError> {
994 let mut keyring = BTreeMap::new();
995 for (generation, key) in keys {
996 insert_key_entry(
997 &mut keyring,
998 key_fingerprint(&key),
999 KeyEntry { generation, key },
1000 )?;
1001 }
1002 if keyring.is_empty() {
1003 return Err(EncryptionError::EmptyKeyring);
1004 }
1005 Ok(EncryptionService { keys: keyring })
1006 }
1007
1008 fn seal_entry(&self) -> (&KeyFingerprint, &KeyEntry) {
1014 seal_entry_of(&self.keys)
1015 }
1016
1017 pub fn current_generation(&self) -> u64 {
1018 self.seal_entry().1.generation
1019 }
1020
1021 pub fn key_count(&self) -> usize {
1024 self.keys.len()
1025 }
1026
1027 pub fn keyring_entries(&self) -> Vec<(u64, [u8; 32])> {
1028 self.keys
1029 .values()
1030 .map(|entry| (entry.generation, entry.key))
1031 .collect()
1032 }
1033
1034 pub fn merged_with(
1038 &self,
1039 other: &EncryptionService,
1040 ) -> Result<EncryptionService, EncryptionError> {
1041 let mut keys = self.keys.clone();
1042 for (fingerprint, entry) in &other.keys {
1043 insert_key_entry(&mut keys, *fingerprint, entry.clone())?;
1044 }
1045 Ok(EncryptionService { keys })
1046 }
1047
1048 pub fn to_keyring_string(&self) -> Result<String, EncryptionError> {
1049 keyring_string(&self.keys)
1050 }
1051
1052 pub fn to_keyring_payload(&self) -> Result<Vec<u8>, EncryptionError> {
1053 self.to_keyring_string().map(String::into_bytes)
1054 }
1055
1056 pub fn from_keyring_payload(plaintext: Vec<u8>) -> Result<Self, EncryptionError> {
1057 let keyring = String::from_utf8(plaintext).map_err(EncryptionError::KeyringUtf8)?;
1058 EncryptionService::from_keyring_json(&keyring)
1059 }
1060
1061 fn from_keyring_json(keyring: &str) -> Result<Self, EncryptionError> {
1062 let payload: StoredKeyring =
1063 serde_json::from_str(keyring).map_err(EncryptionError::ParseKeyring)?;
1064 let mut keys = Vec::with_capacity(payload.keys.len());
1065 for entry in payload.keys {
1066 let key = hex::decode(&entry.key_hex).map_err(EncryptionError::KeyHex)?;
1067 let actual = key.len();
1068 let key: [u8; 32] = key
1069 .try_into()
1070 .map_err(|_| EncryptionError::KeyLength { actual })?;
1071 keys.push((entry.generation, key));
1072 }
1073 EncryptionService::from_keyring(keys)
1074 }
1075
1076 pub(crate) fn key_for_fingerprint(
1080 &self,
1081 fingerprint: &[u8; 32],
1082 ) -> Result<[u8; 32], EncryptionError> {
1083 let fingerprint = KeyFingerprint::from_bytes(*fingerprint);
1084 self.keys
1085 .get(&fingerprint)
1086 .map(|e| e.key)
1087 .ok_or(EncryptionError::UnknownKeyFingerprint(fingerprint))
1088 }
1089
1090 pub fn service_for_fingerprint(
1091 &self,
1092 fingerprint: &[u8; 32],
1093 ) -> Result<EncryptionService, EncryptionError> {
1094 let key = self.key_for_fingerprint(fingerprint)?;
1095 let generation = self
1099 .keys
1100 .get(&KeyFingerprint::from_bytes(*fingerprint))
1101 .expect("just resolved")
1102 .generation;
1103 Ok(EncryptionService::from_key_at_generation(generation, key))
1104 }
1105
1106 pub fn with_appended_generation(
1107 &self,
1108 generation: u64,
1109 key: [u8; 32],
1110 ) -> Result<EncryptionService, EncryptionError> {
1111 if generation <= self.current_generation() {
1112 return Err(EncryptionError::NonIncreasingGeneration {
1113 new_generation: generation,
1114 current_generation: self.current_generation(),
1115 });
1116 }
1117 let mut keys = self.keys.clone();
1118 insert_key_entry(
1119 &mut keys,
1120 key_fingerprint(&key),
1121 KeyEntry { generation, key },
1122 )?;
1123 Ok(EncryptionService { keys })
1124 }
1125
1126 pub fn fingerprint(&self) -> String {
1128 hex::encode(self.seal_fingerprint())
1129 }
1130
1131 pub fn seal_fingerprint(&self) -> [u8; 32] {
1134 *self.seal_entry().0.as_bytes()
1135 }
1136
1137 pub fn seal_key_fingerprint(&self) -> KeyFingerprint {
1138 KeyFingerprint::from_bytes(self.seal_fingerprint())
1139 }
1140
1141 pub fn key_bytes(&self) -> [u8; 32] {
1143 self.seal_entry().1.key
1144 }
1145
1146 pub fn encrypt(&self, plaintext: &[u8], aad_context: &[u8]) -> Vec<u8> {
1150 let header = whole_object_header(plaintext.len() as u64);
1151 let mut sealer = self
1152 .blob_sealer(header, &NoncePolicy::RandomStored, aad_context)
1153 .expect("a header built for RandomStored opens under it");
1154 let mut output = header.to_bytes();
1155 if plaintext.is_empty() {
1158 output.extend(sealer.seal_chunk(&[]));
1159 return output;
1160 }
1161 for chunk in plaintext.chunks(header.chunk_size().get() as usize) {
1162 output.extend(sealer.seal_chunk(chunk));
1163 }
1164 output
1165 }
1166
1167 pub fn decrypt(
1169 &self,
1170 encrypted_data: &[u8],
1171 aad_context: &[u8],
1172 ) -> Result<Vec<u8>, EncryptionError> {
1173 let header = SealedBlobHeader::parse(encrypted_data)?;
1174 let opener = self.blob_opener(header, &NoncePolicy::RandomStored, aad_context)?;
1175 let body = encrypted_data
1176 .get(header.prefix_len() as usize..)
1177 .expect("a parsed header fits the payload it was parsed from");
1178 Ok(opener.open_chunks(0..header.chunk_count(), body)?)
1179 }
1180
1181 pub fn blob_sealer(
1185 &self,
1186 header: SealedBlobHeader,
1187 policy: &NoncePolicy,
1188 aad_context: &[u8],
1189 ) -> Result<SealedBlobSealer, SealedBlobError> {
1190 SealedBlobSealer::new(&self.key_bytes(), header, policy, aad_context)
1191 }
1192
1193 pub fn blob_opener(
1196 &self,
1197 header: SealedBlobHeader,
1198 policy: &NoncePolicy,
1199 aad_context: &[u8],
1200 ) -> Result<SealedBlobOpener, SealedBlobError> {
1201 SealedBlobOpener::new(&self.key_bytes(), header, policy, aad_context)
1202 }
1203
1204 pub fn derive_scoped(&self, scope_id: &str) -> EncryptionService {
1209 let derived = derive_key_from(&self.key_bytes(), &format!("coven-scope-v1:{scope_id}"));
1210 EncryptionService::from_key_at_generation(self.current_generation(), derived)
1211 }
1212
1213 pub fn derive_scoped_for_fingerprint(
1214 &self,
1215 fingerprint: &[u8; 32],
1216 scope_id: &str,
1217 ) -> Result<EncryptionService, EncryptionError> {
1218 let key = self.key_for_fingerprint(fingerprint)?;
1219 let generation = self
1220 .keys
1221 .get(&KeyFingerprint::from_bytes(*fingerprint))
1222 .expect("just resolved")
1223 .generation;
1224 let derived = derive_key_from(&key, &format!("coven-scope-v1:{scope_id}"));
1225 Ok(EncryptionService::from_key_at_generation(
1226 generation, derived,
1227 ))
1228 }
1229
1230 pub fn seal_app_data(&self, plaintext: &[u8], aad: &[u8]) -> Vec<u8> {
1240 let mut sealed = KeyTag::write(&self.seal_fingerprint());
1241 sealed.reserve(chunked_encrypted_len(plaintext.len() as u64) as usize);
1242 sealed.extend(self.encrypt(plaintext, aad));
1243 sealed
1244 }
1245
1246 pub fn open_app_data(&self, sealed: &[u8], aad: &[u8]) -> Result<Vec<u8>, SealError> {
1252 let (fingerprint, ciphertext) = KeyTag::read(sealed)?;
1253 self.service_for_fingerprint(&fingerprint)
1254 .map_err(|_| SealError::UnknownKey(hex::encode(fingerprint)))?
1257 .decrypt(ciphertext, aad)
1258 .map_err(SealError::Crypto)
1259 }
1260}
1261
1262fn derive_key_from(key: &[u8; 32], info: &str) -> [u8; 32] {
1272 let hk = Hkdf::<Sha256>::new(Some(b"coven-hkdf-salt-v1"), key);
1273 let mut okm = [0u8; 32];
1274 hk.expand(info.as_bytes(), &mut okm)
1275 .expect("32 bytes is a valid HKDF output length");
1276 okm
1277}
1278
1279fn chunk_nonce(base_nonce: &[u8; NONCE_SIZE], chunk_index: u64) -> [u8; NONCE_SIZE] {
1281 let mut nonce = *base_nonce;
1282 let index_bytes = chunk_index.to_le_bytes();
1283 for i in 0..8 {
1284 nonce[i] ^= index_bytes[i];
1285 }
1286 nonce
1287}
1288
1289#[cfg(test)]
1290#[path = "encryption_tests.rs"]
1291mod tests;