pamoja_update/update.rs
1//! The rules an update passes before a device will run it.
2//!
3//! The order matters and is deliberate. The signature is checked before the
4//! manifest is interpreted, the manifest is checked before a byte of image is
5//! accepted, and the image is checked before the slot is marked bootable. At no
6//! point is something unverified recorded as usable, so a device interrupted at
7//! any moment comes back up on the last image it confirmed.
8
9use pamoja_security::PublicIdentity;
10
11use crate::error::{Refusal, Result};
12use crate::manifest::{Envelope, Manifest, ID_LEN};
13use crate::slots::{SlotRecord, SlotState, SlotStore};
14use crate::trust::Delegation;
15use crate::verify::ImageVerifier;
16
17/// What the bootloader should do with this boot.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum Boot {
20 /// Nothing new to try; run the confirmed image in this slot.
21 Confirmed(u8),
22 /// A staged image is being tried for the first time. It is now pending, so if
23 /// it does not confirm itself, the next boot will revert.
24 Trying(u8),
25 /// A pending image never confirmed, so it has been failed and will not be
26 /// tried again. Run the fallback.
27 Reverted {
28 /// The slot whose image did not confirm.
29 failed: u8,
30 /// The confirmed slot to run instead.
31 fallback: u8,
32 },
33}
34
35impl Boot {
36 /// Returns the name of the decision, without the slots it names.
37 ///
38 /// The bindings carry a boot decision as an action and its slots, so this is the same
39 /// word in every language, which is what lets one boot be logged the same way
40 /// wherever a device's code is written.
41 ///
42 /// # Returns
43 ///
44 /// One of `"Confirmed"`, `"Trying"`, or `"Reverted"`.
45 pub fn action(self) -> &'static str {
46 match self {
47 Boot::Confirmed(_) => "Confirmed",
48 Boot::Trying(_) => "Trying",
49 Boot::Reverted { .. } => "Reverted",
50 }
51 }
52}
53
54/// Who this device is, and who it trusts to update it.
55#[derive(Clone, Copy, Debug)]
56pub struct Device {
57 /// Who built this device's firmware.
58 pub vendor_id: [u8; ID_LEN],
59 /// What kind of device this is.
60 pub class_id: [u8; ID_LEN],
61 /// The key this device anchors its trust in.
62 ///
63 /// It is the root of every decision about who may update the device, so it is
64 /// used almost never: either to sign releases directly, or to sign a
65 /// [`Delegation`] naming a release key that does. The second arrangement is
66 /// the one to prefer, because it lets the anchor stay somewhere it is hard to
67 /// steal.
68 pub anchor: PublicIdentity,
69}
70
71/// Applies the update rules against a device's slots.
72pub struct Updater<S> {
73 device: Device,
74 store: S,
75 delegation: Option<Delegation>,
76}
77
78impl<S: SlotStore> Updater<S> {
79 /// Creates an updater over a device's slots.
80 ///
81 /// # Arguments
82 ///
83 /// * `device` - the device's identity and trusted author.
84 /// * `store` - where its images live.
85 ///
86 /// # Returns
87 ///
88 /// The updater.
89 pub fn new(device: Device, store: S) -> Self {
90 Self {
91 device,
92 store,
93 delegation: None,
94 }
95 }
96
97 /// Adopts a delegation the device already held, after a restart.
98 ///
99 /// A delegation is small and its envelope is self-authenticating, so the
100 /// simplest place to keep one is wherever the caller already keeps device
101 /// settings. Hand it back here on the way up.
102 ///
103 /// # Arguments
104 ///
105 /// * `envelope` - the stored delegation envelope.
106 /// * `now` - seconds since the Unix epoch, or `None` on a device with no clock.
107 ///
108 /// # Returns
109 ///
110 /// The updater, now accepting releases signed by the delegated key.
111 ///
112 /// # Errors
113 ///
114 /// Returns whatever [`adopt`](Self::adopt) refuses.
115 pub fn with_delegation(mut self, envelope: &[u8], now: Option<u64>) -> Result<Self> {
116 self.adopt(envelope, now)?;
117 Ok(self)
118 }
119
120 /// Takes on a delegation, moving which key may sign this device's updates.
121 ///
122 /// The caller should persist the envelope it just passed, so the device comes
123 /// back up trusting the same key.
124 ///
125 /// # Arguments
126 ///
127 /// * `envelope` - a delegation signed by the device's trust anchor.
128 /// * `now` - seconds since the Unix epoch, or `None` on a device with no clock.
129 ///
130 /// # Returns
131 ///
132 /// The delegation now in force.
133 ///
134 /// # Errors
135 ///
136 /// Returns [`Refusal::Signature`] if it was not signed by the trust anchor,
137 /// [`Refusal::Rollback`] if its epoch does not rise above the one already
138 /// held, so a retired key cannot be reinstated by replay, and
139 /// [`Refusal::Expired`] or [`Refusal::NoClock`] on the same terms as a
140 /// manifest.
141 pub fn adopt(&mut self, envelope: &[u8], now: Option<u64>) -> Result<Delegation> {
142 let delegation = Delegation::open(envelope, &self.device.anchor)?;
143
144 if let Some(held) = self.delegation {
145 if delegation.epoch <= held.epoch {
146 return Err(Refusal::Rollback);
147 }
148 }
149
150 if delegation.expires != 0 {
151 match now {
152 Some(now) if now < delegation.expires => {}
153 Some(_) => return Err(Refusal::Expired),
154 None => return Err(Refusal::NoClock),
155 }
156 }
157
158 // Refuse a delegation naming something that is not a usable key, rather
159 // than adopting it and discovering at the next release that nothing can
160 // sign for this device any more.
161 delegation.signer()?;
162
163 self.delegation = Some(delegation);
164 Ok(delegation)
165 }
166
167 /// Returns the delegation in force, if the device holds one.
168 ///
169 /// # Returns
170 ///
171 /// The delegation, or `None` when releases are signed by the anchor itself.
172 pub fn delegation(&self) -> Option<Delegation> {
173 self.delegation
174 }
175
176 /// Returns the key a manifest must be signed by right now.
177 ///
178 /// # Returns
179 ///
180 /// The delegated release key when one is in force, and the trust anchor
181 /// otherwise.
182 ///
183 /// # Errors
184 ///
185 /// Returns [`Refusal::Signature`] if a held delegation names an unusable key.
186 fn signing_key(&self) -> Result<PublicIdentity> {
187 match self.delegation {
188 Some(delegation) => delegation.signer(),
189 None => Ok(self.device.anchor),
190 }
191 }
192
193 /// Borrows the underlying slot store.
194 ///
195 /// # Returns
196 ///
197 /// The store, for inspecting slot records.
198 pub fn store(&self) -> &S {
199 &self.store
200 }
201
202 /// Returns the highest sequence number any slot holds.
203 ///
204 /// A new manifest must beat this, not merely the running image, so an older
205 /// release cannot be slipped in alongside a newer one that is already staged.
206 /// Failed slots count too: re-releasing a sequence that already failed would
207 /// let a captured image be replayed.
208 ///
209 /// # Returns
210 ///
211 /// The highest sequence number present, or `0` if every slot is empty.
212 ///
213 /// # Errors
214 ///
215 /// Returns a refusal if a slot record cannot be read.
216 pub fn installed_sequence(&self) -> Result<u64> {
217 let mut highest = 0;
218 for slot in 0..self.store.slot_count() {
219 let record = self.store.record(slot)?;
220 if !matches!(record.state, SlotState::Empty | SlotState::Receiving) {
221 highest = highest.max(record.sequence);
222 }
223 }
224 Ok(highest)
225 }
226
227 /// Checks a manifest and opens the slot it names for writing.
228 ///
229 /// # Arguments
230 ///
231 /// * `envelope` - the signed manifest offered to this device.
232 ///
233 /// # Returns
234 ///
235 /// A [`Staging`] ready to take the image, once every check that can be made
236 /// without the image has passed.
237 ///
238 /// # Errors
239 ///
240 /// Returns whatever [`begin_at`](Self::begin_at) refuses. A manifest that
241 /// carries an expiry is refused, because a device with no clock cannot honour
242 /// one; call [`begin_at`](Self::begin_at) with the time if it has one.
243 pub fn begin(&mut self, envelope: &[u8]) -> Result<Staging<'_, S>> {
244 self.begin_at(envelope, None)
245 }
246
247 /// Checks a manifest against the current time and opens the slot it names.
248 ///
249 /// # Arguments
250 ///
251 /// * `envelope` - the signed manifest offered to this device.
252 /// * `now` - seconds since the Unix epoch, or `None` on a device with no
253 /// clock.
254 ///
255 /// # Returns
256 ///
257 /// A [`Staging`] ready to take the image, once every check that can be made
258 /// without the image has passed.
259 ///
260 /// # Errors
261 ///
262 /// Returns [`Refusal::Signature`] if the envelope is not from the trusted
263 /// author, [`Refusal::WrongDevice`] if it is for a different vendor or class,
264 /// [`Refusal::Expired`] if its expiry has passed, [`Refusal::NoClock`] if it
265 /// expires and `now` is `None`, [`Refusal::Rollback`] if it would not move the
266 /// device forward, [`Refusal::SlotTooSmall`] if the image cannot fit, or
267 /// [`Refusal::WrongState`] if it names the slot the device would fall back to.
268 pub fn begin_at(&mut self, envelope: &[u8], now: Option<u64>) -> Result<Staging<'_, S>> {
269 let manifest = Envelope::decode(envelope)?.verify(&self.signing_key()?)?;
270 self.check(&manifest, now)?;
271 self.open(manifest)
272 }
273
274 /// Runs every check that can be made before a byte of image arrives.
275 fn check(&self, manifest: &Manifest, now: Option<u64>) -> Result<()> {
276 if manifest.vendor_id != self.device.vendor_id || manifest.class_id != self.device.class_id
277 {
278 return Err(Refusal::WrongDevice);
279 }
280
281 // A sequence number cannot protect a device that has been offline a long
282 // time: an attacker can offer it a release genuinely newer than the one it
283 // runs, but old enough to have a known flaw. An expiry bounds that window.
284 if manifest.expires != 0 {
285 match now {
286 Some(now) if now < manifest.expires => {}
287 Some(_) => return Err(Refusal::Expired),
288 None => return Err(Refusal::NoClock),
289 }
290 }
291
292 if manifest.sequence <= self.installed_sequence()? {
293 return Err(Refusal::Rollback);
294 }
295
296 let slot = manifest.storage;
297 if manifest.size > self.store.capacity(slot)? {
298 return Err(Refusal::SlotTooSmall);
299 }
300
301 // Writing over the confirmed image would leave nothing to fall back to, so
302 // an update that names that slot is refused rather than obeyed.
303 if self.store.record(slot)?.state == SlotState::Confirmed {
304 return Err(Refusal::WrongState);
305 }
306
307 Ok(())
308 }
309
310 /// Clears the target slot and opens it for a transfer starting at zero.
311 fn open(&mut self, manifest: Manifest) -> Result<Staging<'_, S>> {
312 let slot = manifest.storage;
313 self.store.erase(slot)?;
314 // Recording the target before any bytes arrive is what makes the transfer
315 // resumable: after a reset the device can tell what it was receiving.
316 self.store.set_record(
317 slot,
318 SlotRecord {
319 state: SlotState::Receiving,
320 sequence: manifest.sequence,
321 size: manifest.size,
322 digest: manifest.digest,
323 written: 0,
324 },
325 )?;
326 Ok(Staging {
327 store: &mut self.store,
328 slot,
329 verifier: ImageVerifier::new(&manifest),
330 manifest,
331 offset: 0,
332 })
333 }
334
335 /// Opens a slot for an image, continuing a transfer that was cut off.
336 ///
337 /// A slow radio can spend half an hour on a single image, so a link that drops
338 /// near the end must not mean starting again. If the slot already holds part of
339 /// exactly this image, the transfer picks up where it stopped; anything else
340 /// starts over, because mixing two images produces neither. An image whose last
341 /// byte arrived but which was never settled counts as picking up where it
342 /// stopped, so a reset in that gap costs nothing.
343 ///
344 /// # Arguments
345 ///
346 /// * `envelope` - the signed manifest offered to this device.
347 /// * `now` - seconds since the Unix epoch, or `None` on a device with no clock.
348 ///
349 /// # Returns
350 ///
351 /// A [`Staging`] positioned after whatever already arrived, which
352 /// [`progress`](Staging::progress) reports.
353 ///
354 /// # Errors
355 ///
356 /// Returns whatever [`begin_at`](Self::begin_at) refuses.
357 pub fn resume_at(&mut self, envelope: &[u8], now: Option<u64>) -> Result<Staging<'_, S>> {
358 let manifest = Envelope::decode(envelope)?.verify(&self.signing_key()?)?;
359 self.check(&manifest, now)?;
360
361 let slot = manifest.storage;
362 let record = self.store.record(slot)?;
363 let resumable = record.state == SlotState::Receiving
364 && record.digest == manifest.digest
365 && record.size == manifest.size
366 && record.written <= manifest.size;
367
368 if !resumable {
369 return self.open(manifest);
370 }
371
372 // The hash cannot be carried across a reset, so it is rebuilt by reading
373 // back what the slot already holds. Those bytes are still unproven; the
374 // digest check at the end settles them, exactly as for a fresh transfer.
375 let mut verifier = ImageVerifier::new(&manifest);
376 let mut buf = [0u8; 256];
377 let mut at = 0u32;
378 while at < record.written {
379 let want = buf.len().min((record.written - at) as usize);
380 let read = self.store.read(slot, at, &mut buf[..want])?;
381 if read == 0 {
382 return Err(Refusal::Malformed);
383 }
384 verifier.update(&buf[..read])?;
385 at += read as u32;
386 }
387
388 Ok(Staging {
389 store: &mut self.store,
390 slot,
391 verifier,
392 manifest,
393 offset: record.written,
394 })
395 }
396
397 /// Checks a manifest and stages an image already held whole.
398 ///
399 /// # Arguments
400 ///
401 /// * `envelope` - the signed manifest.
402 /// * `image` - the whole image.
403 ///
404 /// # Returns
405 ///
406 /// The slot the image was staged into.
407 ///
408 /// # Errors
409 ///
410 /// Returns whatever [`begin`](Self::begin) or [`Staging::finish`] refuses.
411 pub fn stage(&mut self, envelope: &[u8], image: &[u8]) -> Result<u8> {
412 self.stage_at(envelope, image, None)
413 }
414
415 /// Checks a manifest against the current time and stages an image held whole.
416 ///
417 /// # Arguments
418 ///
419 /// * `envelope` - the signed manifest.
420 /// * `image` - the whole image.
421 /// * `now` - seconds since the Unix epoch, or `None` on a device with no clock.
422 ///
423 /// # Returns
424 ///
425 /// The slot the image was staged into.
426 ///
427 /// # Errors
428 ///
429 /// Returns whatever [`begin_at`](Self::begin_at) or [`Staging::finish`]
430 /// refuses.
431 pub fn stage_at(&mut self, envelope: &[u8], image: &[u8], now: Option<u64>) -> Result<u8> {
432 let mut staging = self.begin_at(envelope, now)?;
433 staging.write(image)?;
434 staging.finish()
435 }
436
437 /// Decides what to run, and records that decision before returning it.
438 ///
439 /// Call this once per boot, before jumping to an image. A staged image
440 /// becomes pending here, so if the device resets before confirming, the next
441 /// call sees a pending slot and reverts.
442 ///
443 /// # Returns
444 ///
445 /// What the bootloader should run.
446 ///
447 /// # Errors
448 ///
449 /// Returns [`Refusal::NothingToRevert`] if there is no image to fall back to.
450 pub fn on_boot(&mut self) -> Result<Boot> {
451 if let Some(pending) = self.find(SlotState::Pending)? {
452 // It was booted last time and never said it was healthy.
453 self.set_state(pending, SlotState::Failed)?;
454 let fallback = self
455 .find(SlotState::Confirmed)?
456 .ok_or(Refusal::NothingToRevert)?;
457 return Ok(Boot::Reverted {
458 failed: pending,
459 fallback,
460 });
461 }
462
463 if let Some(staged) = self.find(SlotState::Staged)? {
464 self.set_state(staged, SlotState::Pending)?;
465 return Ok(Boot::Trying(staged));
466 }
467
468 self.find(SlotState::Confirmed)?
469 .map(Boot::Confirmed)
470 .ok_or(Refusal::NothingToRevert)
471 }
472
473 /// Reports the running image healthy, making it the one to fall back to.
474 ///
475 /// The slot the device previously fell back to is erased, which is what frees
476 /// it to receive the next update.
477 ///
478 /// # Returns
479 ///
480 /// The slot that is now confirmed.
481 ///
482 /// # Errors
483 ///
484 /// Returns [`Refusal::WrongState`] if no image is pending, so a confirmation
485 /// that arrives twice, or from an image nobody is trying, does nothing.
486 pub fn confirm(&mut self) -> Result<u8> {
487 let pending = self.find(SlotState::Pending)?.ok_or(Refusal::WrongState)?;
488
489 if let Some(previous) = self.find(SlotState::Confirmed)? {
490 self.store.erase(previous)?;
491 }
492 self.set_state(pending, SlotState::Confirmed)?;
493 Ok(pending)
494 }
495
496 /// Gives up on the pending image and goes back to the confirmed one.
497 ///
498 /// # Returns
499 ///
500 /// The slot the device falls back to.
501 ///
502 /// # Errors
503 ///
504 /// Returns [`Refusal::WrongState`] if no image is pending, or
505 /// [`Refusal::NothingToRevert`] if there is nothing to fall back to.
506 pub fn revert(&mut self) -> Result<u8> {
507 let pending = self.find(SlotState::Pending)?.ok_or(Refusal::WrongState)?;
508 let fallback = self
509 .find(SlotState::Confirmed)?
510 .ok_or(Refusal::NothingToRevert)?;
511 self.set_state(pending, SlotState::Failed)?;
512 Ok(fallback)
513 }
514
515 /// Marks a slot confirmed at first provisioning, when nothing was staged.
516 ///
517 /// A device leaves the factory already running an image that no update
518 /// installed. Without this there is no fallback for the first update to
519 /// return to.
520 ///
521 /// # Arguments
522 ///
523 /// * `slot` - the slot the factory image occupies.
524 /// * `sequence` - the sequence number of that image.
525 ///
526 /// # Returns
527 ///
528 /// `Ok(())` once the slot is confirmed.
529 ///
530 /// # Errors
531 ///
532 /// Returns [`Refusal::WrongState`] if any slot is already confirmed.
533 pub fn provision(&mut self, slot: u8, sequence: u64) -> Result<()> {
534 if self.find(SlotState::Confirmed)?.is_some() {
535 return Err(Refusal::WrongState);
536 }
537 let mut record = self.store.record(slot)?;
538 record.state = SlotState::Confirmed;
539 record.sequence = sequence;
540 self.store.set_record(slot, record)
541 }
542
543 /// Returns the first slot in the given state.
544 fn find(&self, state: SlotState) -> Result<Option<u8>> {
545 for slot in 0..self.store.slot_count() {
546 if self.store.record(slot)?.state == state {
547 return Ok(Some(slot));
548 }
549 }
550 Ok(None)
551 }
552
553 /// Changes a slot's state, leaving the rest of its record alone.
554 fn set_state(&mut self, slot: u8, state: SlotState) -> Result<()> {
555 let mut record = self.store.record(slot)?;
556 record.state = state;
557 self.store.set_record(slot, record)
558 }
559}
560
561/// A slot open for an image, with the manifest's promises still to be met.
562///
563/// The slot's record is only written once the image has been verified whole, so
564/// an interrupted transfer leaves a slot that is never bootable rather than one
565/// holding half an image.
566pub struct Staging<'a, S: SlotStore> {
567 store: &'a mut S,
568 slot: u8,
569 manifest: Manifest,
570 verifier: ImageVerifier,
571 offset: u32,
572}
573
574impl<S: SlotStore> Staging<'_, S> {
575 /// Takes the next piece of the image.
576 ///
577 /// # Arguments
578 ///
579 /// * `chunk` - the next bytes of the image, in order.
580 ///
581 /// # Returns
582 ///
583 /// `Ok(())` once the chunk is hashed and stored.
584 ///
585 /// # Errors
586 ///
587 /// Returns [`Refusal::Size`] if more bytes arrive than the manifest declared,
588 /// or [`Refusal::SlotTooSmall`] if the slot cannot take them.
589 pub fn write(&mut self, chunk: &[u8]) -> Result<()> {
590 self.verifier.update(chunk)?;
591 self.store.write(self.slot, self.offset, chunk)?;
592 self.offset += chunk.len() as u32;
593
594 // Progress is recorded as it is made, so a reset costs at most the chunk in
595 // flight. How much that costs is the caller's to choose: a larger chunk
596 // means fewer record writes and so less flash wear, but more to redo.
597 let mut record = self.store.record(self.slot)?;
598 record.written = self.offset;
599 self.store.set_record(self.slot, record)
600 }
601
602 /// Reports how much of the image has arrived.
603 ///
604 /// # Returns
605 ///
606 /// The bytes stored so far and the total the manifest declares.
607 pub fn progress(&self) -> (u32, u32) {
608 (self.offset, self.manifest.size)
609 }
610
611 /// Finishes the image and marks the slot bootable if it matched.
612 ///
613 /// # Returns
614 ///
615 /// The slot now holding a staged image.
616 ///
617 /// # Errors
618 ///
619 /// Returns [`Refusal::Size`] or [`Refusal::Digest`] if the image is not the
620 /// one the manifest described, leaving the slot unbootable.
621 pub fn finish(self) -> Result<u8> {
622 let verified = self.verifier.finish()?;
623 self.store.set_record(
624 self.slot,
625 SlotRecord {
626 state: SlotState::Staged,
627 sequence: self.manifest.sequence,
628 size: verified.size(),
629 digest: verified.digest(),
630 written: verified.size(),
631 },
632 )?;
633 Ok(self.slot)
634 }
635
636 /// Returns the manifest this staging is fulfilling.
637 ///
638 /// # Returns
639 ///
640 /// The verified manifest.
641 pub fn manifest(&self) -> &Manifest {
642 &self.manifest
643 }
644}