1use super::*;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct Generation {
6 pub id: Hash,
7 pub commitment: Hash,
8 pub starts_after: u64,
9}
10const ROTATION_PLAINTEXT_BYTES: usize = 176;
11const ROTATION_KEY_OFFSET: usize = 6 + 3 * 32 + 2 + 32;
12
13pub struct RotationMaterial {
15 generation: Hash,
16 key: LocalSharedStatePackageKey,
17 entropy: Secret,
18}
19impl RotationMaterial {
20 pub fn generate() -> Result<Self> {
21 Ok(Self {
22 generation: *Secret::generate()?.expose(),
23 key: LocalSharedStatePackageKey::new(*Secret::generate()?.expose()),
24 entropy: Secret::generate()?,
25 })
26 }
27 pub fn protected_storage_bytes(&self) -> Zeroizing<Vec<u8>> {
28 let mut bytes = Zeroizing::new(b"AVRM\0\x01".to_vec());
29 bytes.extend(self.generation);
30 bytes.extend(self.key.protected_storage_bytes());
31 bytes.extend(self.entropy.expose());
32 bytes
33 }
34 pub fn validate_generation(&self, membership: &Membership) -> Result<()> {
35 let generation = membership.current_generation();
36 check(
37 generation.id == self.generation
38 && generation.commitment
39 == generation_commitment(
40 LocalSharedStatePackageContext {
41 vault_id: membership.genesis.context().vault_id,
42 generation_id: self.generation,
43 },
44 self.key.protected_storage_bytes(),
45 ),
46 )
47 }
48 pub fn from_protected_storage(bytes: &[u8]) -> Result<Self> {
49 check(bytes.len() == 102)?;
50 let mut r = Reader(bytes);
51 check(r.take(6)? == b"AVRM\0\x01")?;
52 Ok(Self {
53 generation: r.array()?,
54 key: LocalSharedStatePackageKey::new(r.array()?),
55 entropy: Secret::new(r.array()?),
56 })
57 }
58}
59fn revoke(m: &Membership, targets: &[Hash]) -> Result<Membership> {
60 check(targets.len() < m.members.len() && targets.windows(2).all(|w| w[0] < w[1]))?;
61 let mut next = m.clone();
62 for target in targets {
63 let member = m.member(target)?;
64 next.retired.push(member.clone());
65 }
66 next.members
67 .retain(|member| !targets.contains(&member.device));
68 next.pending = true;
69 Ok(next)
70}
71fn rotate(m: &Membership, generation: Generation) -> Result<Membership> {
72 check(
73 m.pending
74 && m.generations.len() < MAX_GENERATIONS
75 && !m.generations.iter().any(|g| g.id == generation.id)
76 && generation.starts_after >= m.publication.binding().prefix_count
77 && generation.starts_after >= m.current_generation().starts_after
78 && generation.starts_after <= MAX_CUTOFF,
79 )?;
80 let mut next = m.clone();
81 next.generations.push(generation);
82 next.pending = false;
83 Ok(next)
84}
85pub(super) fn validate(m: &Membership, raw: &[u8]) -> Result<Membership> {
86 let (core, state, attachments, signature) = encoding::components(raw)?;
87 let mut r = Reader(core);
88 check(r.take(1)? == [1] && r.blob(32)? == m.genesis.context().vault_id)?;
89 check(u64::from_be_bytes(r.array()?) == m.sequence() + 1 && r.blob(32)? == m.head())?;
90 check(r.take(1)? == [1])?;
91 let signer: Hash = r.blob(32)?.try_into()?;
92 let member = m.member(&signer)?;
93 let action = r.take(1)?[0];
94 let body = encoding::blob(&mut r, 8 + MAX_DEVICES * 32)?;
95 r.blob(32)?;
96 r.end()?;
97 verify(
98 &member.sign,
99 MEMBERSHIP_SIGN,
100 &[core, attachments],
101 signature,
102 )?;
103 let mut b = Reader(body);
104 let next = match action {
105 4 => {
106 check(b.take(6)? == b"AVRV\0\x01")?;
107 let count = u16::from_be_bytes(b.array()?) as usize;
108 check(count <= MAX_DEVICES)?;
109 let targets = (0..count)
110 .map(|_| b.array())
111 .collect::<Result<Vec<Hash>>>()?;
112 encoding::read_packages(attachments, 0, 0, 0)?;
113 revoke(m, &targets)?
114 }
115 5 => {
116 check(
117 b.take(6)? == b"AVRT\0\x01"
118 && b.array::<32>()? == m.publication.binding().stream_id,
119 )?;
120 let next = rotate(
121 m,
122 Generation {
123 id: b.array()?,
124 commitment: b.array()?,
125 starts_after: u64::from_be_bytes(b.array()?),
126 },
127 )?;
128 let packages = encoding::read_packages(
129 attachments,
130 0,
131 next.members.len(),
132 ROTATION_PLAINTEXT_BYTES,
133 )?;
134 for (p, member) in packages.iter().zip(&next.members) {
135 check(p.device == member.device && p.public == member.hpke)?;
136 }
137 next
138 }
139 _ => anyhow::bail!("error membership-action"),
140 };
141 b.end()?;
142 check(
143 state == encoding::state(&next) && core == encoding::core(m, signer, action, body, state),
144 )?;
145 Ok(next)
146}
147pub(super) fn info(m: &Membership, member: &Member) -> Vec<u8> {
148 cce(
149 "aven-e2ee/v1/membership/rotation-key",
150 &[
151 &m.genesis.context().vault_id,
152 &[1],
153 &member.device,
154 &member.hpke,
155 ],
156 )
157}
158pub(super) fn plaintext(
159 m: &Membership,
160 member: &Member,
161 g: &Generation,
162 key: &LocalSharedStatePackageKey,
163) -> Zeroizing<Vec<u8>> {
164 let mut out = Zeroizing::new(b"AVRK\0\x01".to_vec());
165 out.extend(m.genesis.context().vault_id);
166 out.extend(m.genesis.commitment());
167 out.extend(member.device);
168 out.extend(1_u16.to_be_bytes());
169 out.extend(g.id);
170 out.extend(key.protected_storage_bytes());
171 out.extend(g.starts_after.to_be_bytes());
172 out
173}
174impl Device<'_> {
175 pub fn prepare_revoke(&self, m: &Membership, targets: &[Hash]) -> Result<Vec<u8>> {
178 self.active(m)?;
179 let next = revoke(m, targets)?;
180 let mut body = b"AVRV\0\x01".to_vec();
181 body.extend((targets.len() as u16).to_be_bytes());
182 for target in targets {
183 body.extend(target);
184 }
185 let state = encoding::state(&next);
186 let core = encoding::core(m, self.device, 4, &body, &state);
187 let raw = admission::signed(self.signing, &core, &state, &encoding::packages(0));
188 m.append(&[], &[], &raw)?;
189 Ok(raw)
190 }
191 #[cfg(any(test, feature = "test-support"))]
194 pub fn prepare_rotation(
195 &self,
196 m: &Membership,
197 keys: &VerifiedKeys,
198 cutoff: u64,
199 ) -> Result<Vec<u8>> {
200 self.prepare_rotation_with(m, keys, cutoff, &RotationMaterial::generate()?)
201 }
202 pub fn prepare_rotation_with(
207 &self,
208 m: &Membership,
209 keys: &VerifiedKeys,
210 cutoff: u64,
211 material: &RotationMaterial,
212 ) -> Result<Vec<u8>> {
213 let genesis = m.genesis.commitment();
214 let predecessor = m.head();
215 let sequence = m.sequence().to_be_bytes();
216 let stream = m.publication.binding().stream_id;
217 let cutoff_bytes = cutoff.to_be_bytes();
218 let commitment = generation_commitment(
219 LocalSharedStatePackageContext {
220 vault_id: m.genesis.context().vault_id,
221 generation_id: material.generation,
222 },
223 material.key.protected_storage_bytes(),
224 );
225 let context = cce(
226 "aven-e2ee/v1/membership/rotation-rng/context",
227 &[
228 &genesis,
229 &predecessor,
230 &sequence,
231 &stream,
232 &self.device,
233 &cutoff_bytes,
234 &material.generation,
235 &commitment,
236 ],
237 );
238 let kdf = hkdf::Hkdf::<sha2::Sha256>::new(
239 Some(b"aven-e2ee/v1/membership/rotation-rng"),
240 material.entropy.expose(),
241 );
242 let mut seed = Zeroizing::new([0; 32]);
243 kdf.expand(&context, seed.as_mut())
244 .expect("fixed HKDF length");
245 self.rotation_with(
246 m,
247 keys,
248 material.generation,
249 &material.key,
250 cutoff,
251 &mut ChaCha20Rng::from_seed(*seed),
252 )
253 }
254 pub(super) fn rotation_with(
255 &self,
256 m: &Membership,
257 keys: &VerifiedKeys,
258 id: Hash,
259 key: &LocalSharedStatePackageKey,
260 cutoff: u64,
261 rng: &mut impl rand_core::CryptoRng,
262 ) -> Result<Vec<u8>> {
263 self.active(m)?;
264 keys.validate(m)?;
265 let g = Generation {
266 id,
267 commitment: generation_commitment(
268 LocalSharedStatePackageContext {
269 vault_id: m.genesis.context().vault_id,
270 generation_id: id,
271 },
272 key.protected_storage_bytes(),
273 ),
274 starts_after: cutoff,
275 };
276 let next = rotate(m, g.clone())?;
277 let mut body = b"AVRT\0\x01".to_vec();
278 body.extend(m.publication.binding().stream_id);
279 body.extend(g.id);
280 body.extend(g.commitment);
281 body.extend(cutoff.to_be_bytes());
282 let state = encoding::state(&next);
283 let core = encoding::core(m, self.device, 5, &body, &state);
284 let mut attachments = encoding::packages(next.members.len());
285 for member in &next.members {
286 let public = <Kem as hpke::Kem>::PublicKey::from_bytes(&member.hpke)
287 .map_err(|_| anyhow::anyhow!("error membership-recipient"))?;
288 let (enc, cipher) = hpke::single_shot_seal_with_rng::<Aead, Kdf, Kem>(
289 &OpModeS::Base,
290 &public,
291 &info(m, member),
292 &plaintext(m, member, &g, key),
293 &core,
294 rng,
295 )
296 .map_err(|_| anyhow::anyhow!("error membership-package-seal"))?;
297 encoding::package(
298 &mut attachments,
299 0,
300 &member.device,
301 &member.hpke,
302 &enc.to_bytes(),
303 &cipher,
304 );
305 }
306 let raw = admission::signed(self.signing, &core, &state, &attachments);
307 self.receive_rotation(m, &raw, keys)?;
308 Ok(raw)
309 }
310 pub fn receive_rotation(
313 &self,
314 before: &Membership,
315 raw: &[u8],
316 keys: &VerifiedKeys,
317 ) -> Result<VerifiedKeys> {
318 self.active(before)?;
319 keys.validate(before)?;
320 let next = before.append(&[], &[], raw)?;
321 check(next.generations.len() == before.generations.len() + 1)?;
322 let member = self.active(&next)?;
323 let (core, _, attachments, _) = encoding::components(raw)?;
324 let packages =
325 encoding::read_packages(attachments, 0, next.members.len(), ROTATION_PLAINTEXT_BYTES)?;
326 let p = packages
327 .iter()
328 .find(|p| p.device == self.device)
329 .context("error membership-package-missing")?;
330 let private = HpkePrivate::from_bytes(self.recipient.expose())
331 .map_err(|_| anyhow::anyhow!("error membership-recipient"))?;
332 let enc =
333 Enc::from_bytes(p.enc).map_err(|_| anyhow::anyhow!("error membership-package-enc"))?;
334 let plain = Zeroizing::new(
335 hpke::single_shot_open::<Aead, Kdf, Kem>(
336 &OpModeR::Base,
337 &private,
338 &enc,
339 &info(before, member),
340 p.cipher,
341 core,
342 )
343 .map_err(|_| anyhow::anyhow!("error membership-package-open"))?,
344 );
345 check(plain.len() == ROTATION_PLAINTEXT_BYTES)?;
346 let key = LocalSharedStatePackageKey::new(
347 plain[ROTATION_KEY_OFFSET..ROTATION_KEY_OFFSET + 32].try_into()?,
348 );
349 check(bool::from(plain.as_slice().ct_eq(&plaintext(
350 before,
351 member,
352 next.current_generation(),
353 &key,
354 ))))?;
355 keys.extended(&next, key)
356 }
357}