aven_core/sync/encrypted_tail/attachments/
codec.rs1use super::super::{Authority, hash, valid};
2use crate::sync::bootstrap_format::{catalog::Artifact, codec::Reader};
3use crate::sync::shared_state::package::{self as crypto, LocalSharedStatePackageContext};
4use anyhow::{Result, ensure};
5use chacha20poly1305::{
6 KeyInit, XChaCha20Poly1305, XNonce,
7 aead::{Aead, Payload},
8};
9use zeroize::Zeroizing;
10
11pub const DESCRIPTOR_LIMIT: usize = 1984;
12pub const IMAGE_BYTES: usize = 25 * 1048576;
13pub const CHUNK_BYTES: usize = 1048576 + 222;
14pub const TRANSFER_BYTES: usize = IMAGE_BYTES + 25 * 222;
15pub const HTTP_LIMIT: usize = crate::sync::base64_bytes::encoded_len(CHUNK_BYTES) + 16384;
16
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub(crate) struct Descriptor {
19 pub vault: [u8; 32],
20 pub stream: [u8; 32],
21 pub generation: [u8; 32],
22 pub object: [u8; 32],
23 pub artifact: Artifact,
24}
25impl Descriptor {
26 pub fn decode(bytes: &[u8]) -> Result<Self> {
27 valid(bytes.len() <= DESCRIPTOR_LIMIT)?;
28 let mut r = Reader(bytes);
29 valid(r.take(8)? == b"AVEN\x03\x00\x01\x01")?;
30 let value = Self {
31 vault: r.array()?,
32 stream: r.array()?,
33 generation: r.array()?,
34 object: r.array()?,
35 artifact: Artifact::read(&mut r, IMAGE_BYTES as u64, true)?,
36 };
37 r.end()?;
38 Ok(value)
39 }
40 pub fn encode(&self) -> Result<Vec<u8>> {
41 self.artifact.shape(IMAGE_BYTES as u64, true)?;
42 let mut bytes = b"AVEN\x03\x00\x01\x01".to_vec();
43 for id in [&self.vault, &self.stream, &self.generation, &self.object] {
44 bytes.extend(id);
45 }
46 self.artifact.write(&mut bytes)?;
47 Ok(bytes)
48 }
49 pub fn context(&self) -> LocalSharedStatePackageContext {
50 LocalSharedStatePackageContext {
51 vault_id: self.vault,
52 generation_id: self.generation,
53 }
54 }
55 pub fn byte_size(&self) -> i64 {
56 self.artifact.chunks.iter().map(|c| c.length as i64).sum()
57 }
58 pub fn verify_chunk(&self, index: usize, bytes: &[u8]) -> Result<()> {
59 self.artifact
60 .verify_chunk(bytes, index, self.context(), self.stream, self.object, 1, 0)?;
61 Ok(())
62 }
63 pub fn verify(&self, records: &[Vec<u8>]) -> Result<()> {
64 self.artifact
65 .verify(records, self.context(), self.stream, self.object, 1, 0)?;
66 Ok(())
67 }
68 pub(super) fn authority(&self, a: &Authority) -> Result<()> {
69 a.validate()?;
70 a.key(self.generation)?;
71 valid(self.vault == a.context.vault && self.stream == a.context.stream)
72 }
73 pub fn seal(a: &Authority, bytes: &[u8]) -> Result<(Self, Vec<Vec<u8>>)> {
74 a.validate()?;
75 ensure!(!a.rotation_pending(), "error membership-rotation-pending");
76 valid(!bytes.is_empty() && bytes.len() <= IMAGE_BYTES)?;
77 let mut object = [0; 32];
78 getrandom::fill(&mut object)
79 .map_err(|_| anyhow::anyhow!("error encrypted-image-entropy"))?;
80 let context = LocalSharedStatePackageContext {
81 vault_id: a.context.vault,
82 generation_id: a.generation(),
83 };
84 let key = crypto::derive_image_key(a.key(a.generation())?, context, object)?;
85 let encrypted =
86 crypto::encrypt_artifact(bytes, context, a.context.stream, object, 1, 0, &key)?;
87 let descriptor = Self {
88 vault: context.vault_id,
89 stream: a.context.stream,
90 generation: context.generation_id,
91 object,
92 artifact: Artifact::from_encrypted(&encrypted)?,
93 };
94 let records = encrypted.into_records();
95 Ok((descriptor, records))
96 }
97 pub fn reconstruct(
99 &self,
100 a: &Authority,
101 source: &[u8],
102 expected_hash: &str,
103 ) -> Result<Vec<Vec<u8>>> {
104 self.authority(a)?;
105 ensure!(
106 source.len() as u64 == self.artifact.total
107 && hex::encode(hash(source)) == expected_hash,
108 "error encrypted-image-source-changed"
109 );
110 let key = crypto::derive_image_key(a.key(self.generation)?, self.context(), self.object)?;
111 let cipher = XChaCha20Poly1305::new_from_slice(key.as_ref()).expect("fixed key");
112 let mut records = Vec::new();
113 for (index, chunk) in self.artifact.chunks.iter().enumerate() {
114 let start = index * 1048576;
115 let end = source.len().min(start + 1048576);
116 let header = crypto::chunk_header(
117 self.context(),
118 self.stream,
119 self.object,
120 1,
121 0,
122 index as u32,
123 self.artifact.chunks.len() as u32,
124 self.artifact.total,
125 chunk.nonce,
126 )?;
127 let body = cipher
128 .encrypt(
129 &XNonce::from(chunk.nonce),
130 Payload {
131 msg: &source[start..end],
132 aad: &header,
133 },
134 )
135 .map_err(|_| anyhow::anyhow!("error encrypted-image-encryption"))?;
136 let mut record = Vec::new();
137 for part in [&header, &body] {
138 record.extend((part.len() as u32).to_be_bytes());
139 record.extend(part);
140 }
141 records.push(record);
142 }
143 self.verify(&records)?;
144 Ok(records)
145 }
146 pub fn open(&self, a: &Authority, records: &[Vec<u8>]) -> Result<Zeroizing<Vec<u8>>> {
147 self.authority(a)?;
148 self.verify(records)?;
149 let key = crypto::derive_image_key(a.key(self.generation)?, self.context(), self.object)?;
150 Ok(Zeroizing::new(crypto::decrypt_artifact(
151 &self.artifact.encrypted(records),
152 self.context(),
153 self.stream,
154 self.object,
155 1,
156 0,
157 &key,
158 IMAGE_BYTES,
159 )?))
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 #[test]
167 fn exact_recipe_reconstruction_and_frozen_commitments() {
168 let a = crate::sync::encrypted_tail::tests::authority();
169 let source = vec![42; 1048579];
170 let sha = hex::encode(hash(&source));
171 let (d, records) = Descriptor::seal(&a, &source).unwrap();
172 assert_eq!(Descriptor::decode(&d.encode().unwrap()).unwrap(), d);
173 assert_eq!(d.reconstruct(&a, &source, &sha).unwrap(), records);
174 assert_eq!(d.open(&a, &records).unwrap().as_slice(), source);
175 let mut changed = source.clone();
176 changed[0] ^= 1;
177 assert!(d.reconstruct(&a, &changed, &sha).is_err());
178 let mut changed = d.clone();
179 changed.artifact.chunks[0].nonce[0] ^= 1;
180 assert!(changed.reconstruct(&a, &source, &sha).is_err());
181 let encoded = d.encode().unwrap();
182 for n in 0..encoded.len() {
183 assert!(Descriptor::decode(&encoded[..n]).is_err());
184 }
185 }
186}