Skip to main content

aven_core/sync/encrypted_tail/attachments/
codec.rs

1use 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    /// The owned source is hashed in full before any saved nonce is used.
98    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}