Skip to main content

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}