1mod admission;
7mod encoding;
8mod evidence;
9mod pairing;
10pub use evidence::{Evidence, EvidenceRecord, MAX_EVIDENCE_JSON_BYTES, Mailbox};
11mod keys;
12mod rotation;
13pub use keys::{MAX_COVERAGE_BYTES, VerifiedKeys};
14pub use rotation::{Generation, RotationMaterial};
15#[cfg(any(test, feature = "test-support"))]
16pub mod test_support;
17#[cfg(test)]
18pub(crate) mod tests;
19
20pub use super::peer::Invitation;
21use super::peer::{Recipient, verify};
22use super::*;
23use anyhow::Result;
24pub use pairing::{Declaration, Device, Joiner, ProvisionalGrant, VerifiedEnrollment};
25
26pub const MAX_DEVICES: usize = 32;
27pub use crate::wire::enrollment::MAX_RECORD_BYTES;
28pub const MAX_TRANSITIONS: usize = 256;
31pub const MAX_GENERATIONS: usize = 64;
33pub const MAX_CUTOFF: u64 = (i64::MAX - 1) as u64;
35pub const MAX_KEY_PLAINTEXT_BYTES: usize =
37 GRANT_PREFIX_BYTES + 2 + GENERATION_BYTES * MAX_GENERATIONS;
38pub const DECLARATION_BYTES: usize = 280;
39pub const REQUEST_BYTES: usize = 314;
40const CORE_BYTES: usize = 461;
41const GRANT_PREFIX_BYTES: usize = 1 + 12 * 32 + 8;
42const MEMBER_BYTES: usize = 164;
44const GENERATION_BYTES: usize = 72;
45const MAX_ADMISSION_BYTES: usize =
47 1257 + MEMBER_BYTES * MAX_DEVICES + 2 * GENERATION_BYTES * MAX_GENERATIONS;
48const MAX_ROTATION_BYTES: usize =
50 540 + (MEMBER_BYTES + 306) * MAX_DEVICES + GENERATION_BYTES * MAX_GENERATIONS;
51const MAX_REVOKE_BYTES: usize =
53 438 + MEMBER_BYTES * MAX_DEVICES + GENERATION_BYTES * MAX_GENERATIONS;
54pub const MAX_CHAIN_BYTES: usize = GENESIS_BYTES
57 + PUBLICATION_BYTES
58 + crate::artifact::codec::MAX_DESCRIPTOR_BYTES
59 + (MAX_GENERATIONS - 1) * MAX_ROTATION_BYTES
60 + (MAX_TRANSITIONS + 1 - MAX_GENERATIONS)
61 * (DECLARATION_BYTES + REQUEST_BYTES + MAX_ADMISSION_BYTES);
62const _: () = assert!(
63 MAX_ADMISSION_BYTES <= MAX_RECORD_BYTES
64 && MAX_ROTATION_BYTES <= MAX_RECORD_BYTES
65 && MAX_REVOKE_BYTES <= DECLARATION_BYTES + REQUEST_BYTES + MAX_ADMISSION_BYTES
66);
67const DOMAIN_VERSION: u32 = 1;
68
69type Hash = [u8; 32];
70fn check(condition: bool) -> Result<()> {
71 ensure!(condition, "error membership-invalid");
72 Ok(())
73}
74
75#[derive(Clone, PartialEq, Eq)]
76struct Member {
77 device: Hash,
78 sign: Hash,
79 hpke: Hash,
80 verifier: Hash,
81 admission: Hash,
82 admitted_at: u64,
83}
84impl Member {
85 fn write(&self, out: &mut Vec<u8>) {
86 for field in [self.device, self.sign, self.hpke, self.verifier] {
87 out.extend(field);
88 }
89 out.extend(1_u32.to_be_bytes());
90 out.extend(self.admission);
91 }
92}
93
94#[derive(Clone)]
97pub struct Membership {
98 genesis: Genesis,
99 publication: Publication,
100 members: Vec<Member>,
101 heads: Vec<Hash>,
102 handles: Vec<Hash>,
103 retired: Vec<Member>,
104 generations: Vec<Generation>,
105 pending: bool,
106}
107impl Membership {
108 pub fn from_publication(
109 genesis: &Genesis,
110 descriptor: &[u8],
111 publication: &[u8],
112 ) -> Result<Self> {
113 let publication = Publication::from_record(genesis, descriptor, publication)?;
114 Ok(Self {
115 genesis: genesis.clone(),
116 heads: vec![publication.commitment()],
117 publication,
118 members: vec![Member {
119 device: genesis.device_id(),
120 sign: genesis.signing_public(),
121 hpke: genesis.hpke_public(),
122 verifier: genesis.verifier(),
123 admission: genesis.claim_id(),
124 admitted_at: 0,
125 }],
126 handles: vec![],
127 retired: vec![],
128 generations: vec![Generation {
129 id: genesis.context().generation_id,
130 commitment: genesis.generation_commitment(),
131 starts_after: 0,
132 }],
133 pending: false,
134 })
135 }
136 pub fn head(&self) -> Hash {
137 self.heads[self.heads.len() - 1]
138 }
139 pub fn sequence(&self) -> u64 {
140 self.heads.len() as u64
141 }
142 pub fn has_device(&self, device: Hash) -> bool {
143 self.members.iter().any(|member| member.device == device)
144 }
145 pub fn devices(&self) -> impl Iterator<Item = Hash> + '_ {
146 self.members.iter().map(|member| member.device)
147 }
148 pub fn admissions(&self) -> impl Iterator<Item = (Hash, u64)> + '_ {
151 self.members
152 .iter()
153 .map(|member| (member.device, member.admitted_at))
154 }
155 pub fn has_admission(&self, handle: Hash) -> bool {
157 self.members.iter().any(|member| member.admission == handle)
158 }
159 pub fn record_action(record: &[u8]) -> Result<(Hash, u8)> {
161 let (core, _, _, _) = encoding::components(record)?;
162 encoding::signer_action(core)
163 }
164 pub fn device_count(&self) -> usize {
165 self.members.len()
166 }
167 pub fn genesis(&self) -> &Genesis {
168 &self.genesis
169 }
170 pub fn publication(&self) -> &Publication {
171 &self.publication
172 }
173 pub fn extends(&self, previous: &Self) -> bool {
175 self.genesis == previous.genesis && self.heads.starts_with(&previous.heads)
176 }
177 pub fn authenticate(
179 &self,
180 auth: &super::peer::Authentication<'_>,
181 ancestor: bool,
182 ) -> Result<()> {
183 ensure!(
184 auth.vault == self.genesis.context().vault_id
185 && auth.genesis == self.genesis.commitment(),
186 Unauthorized
187 );
188 let member = self.member(&auth.device).map_err(|_| Unauthorized)?;
189 ensure!(
190 bool::from(member.verifier.ct_eq(&credential_verifier(
191 auth.vault,
192 auth.device,
193 auth.bearer
194 ))),
195 Unauthorized
196 );
197 ensure!(
198 self.heads.contains(&auth.head),
199 "error membership-context-unknown"
200 );
201 if !ancestor && auth.head != self.head() {
202 anyhow::bail!(StaleContext);
203 }
204 Ok(())
205 }
206 pub fn head_at(&self, sequence: u64) -> Option<Hash> {
207 sequence
208 .checked_sub(1)
209 .and_then(|n| self.heads.get(n as usize))
210 .copied()
211 }
212 pub fn validate_key(&self, key: &LocalSharedStatePackageKey) -> Result<()> {
213 check(self.generations.len() == 1)?;
214 check(
215 generation_commitment(self.genesis.context(), key.protected_storage_bytes())
216 == self.genesis.generation_commitment(),
217 )
218 }
219 pub fn contains_head(&self, head: &Hash) -> bool {
220 self.heads.contains(head)
221 }
222 fn member(&self, device: &Hash) -> Result<&Member> {
223 self.members
224 .iter()
225 .find(|m| &m.device == device)
226 .context("error membership-signer")
227 }
228 fn unique(&self, recipient: &Recipient, handle: &Hash) -> Result<()> {
229 check(self.members.len() < MAX_DEVICES && !self.handles.contains(handle))?;
230 check(self.members.iter().chain(&self.retired).all(|m| {
231 m.device != recipient.device && m.sign != recipient.sign && m.hpke != recipient.hpke
232 }))
233 }
234 pub fn append(&self, declaration: &[u8], request: &[u8], record: &[u8]) -> Result<Self> {
237 let (core, _, _, _) = encoding::components(record)?;
238 let (_, action) = encoding::signer_action(core)?;
239 match action {
240 3 => self.admit(
241 &Declaration::from_record(self, declaration)?,
242 request,
243 record,
244 ),
245 4 | 5 => {
246 check(declaration.is_empty() && request.is_empty())?;
247 self.finish(rotation::validate(self, record)?, record)
248 }
249 _ => anyhow::bail!("error membership-action"),
250 }
251 }
252 pub(super) fn admit(
255 &self,
256 declaration: &Declaration,
257 request: &[u8],
258 record: &[u8],
259 ) -> Result<Self> {
260 let recipient = admission::validate(self, declaration, request, record)?;
261 let mut next = self.clone();
262 next.members.push(Member {
263 device: recipient.device,
264 sign: recipient.sign,
265 hpke: recipient.hpke,
266 verifier: recipient.verifier,
267 admission: declaration.handle,
268 admitted_at: self.sequence() + 1,
269 });
270 next.members.sort_by_key(|m| m.device);
271 next.handles.push(declaration.handle);
272 self.finish(next, record)
273 }
274 fn finish(&self, mut next: Self, record: &[u8]) -> Result<Self> {
275 next.heads.push(hash(record));
276 next.capacity()?;
277 Ok(next)
278 }
279 fn capacity(&self) -> Result<()> {
282 let reserve = usize::from(self.pending);
283 ensure!(
284 self.heads.len() - 1 + reserve <= MAX_TRANSITIONS
285 && self.generations.len() + reserve <= MAX_GENERATIONS,
286 "error membership-change-limit"
287 );
288 Ok(())
289 }
290 pub fn ensure_change_capacity(&self) -> Result<()> {
293 ensure!(
294 self.heads.len() - 1 + 2 <= MAX_TRANSITIONS && self.generations.len() < MAX_GENERATIONS,
295 "error membership-change-limit"
296 );
297 Ok(())
298 }
299 pub fn rotation_pending(&self) -> bool {
300 self.pending
301 }
302 pub fn generations(&self) -> &[Generation] {
303 &self.generations
304 }
305 pub fn current_generation(&self) -> &Generation {
306 self.generations.last().expect("genesis generation")
307 }
308 pub fn generation_allows(&self, generation: Hash, sequence: u64) -> bool {
310 self.generations
311 .iter()
312 .position(|g| g.id == generation)
313 .is_some_and(|i| {
314 sequence > self.generations[i].starts_after
315 && self
316 .generations
317 .get(i + 1)
318 .is_none_or(|g| sequence <= g.starts_after)
319 })
320 }
321}
322
323#[derive(Debug)]
325pub struct StaleContext;
326impl fmt::Display for StaleContext {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.write_str("error enrollment-context-stale")
329 }
330}
331impl std::error::Error for StaleContext {}
332
333#[derive(Debug)]
335pub struct Unauthorized;
336impl fmt::Display for Unauthorized {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 f.write_str("error enrollment-unauthorized")
339 }
340}
341impl std::error::Error for Unauthorized {}
342
343#[derive(serde::Serialize, serde::Deserialize)]
344#[serde(deny_unknown_fields)]
345pub struct ManagementPreparation {
346 pub evidence: Evidence,
347 pub high_water: u64,
348}