1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//! Signed firmware updates for the pamoja SDK.
//!
//! A device in a clinic, on a pump, or under a solar panel is expensive to reach
//! and sometimes impossible. If it cannot be updated in place, every bug in it is
//! permanent and every fix is a journey. This crate is what makes updating one
//! safe enough to do remotely:
//!
//! - [`Manifest`] - what an update claims about itself: which device it is for,
//! where it goes, how big it is, what it hashes to, and where it sits in the
//! release order.
//! - [`Envelope`] - that manifest next to a signature over it, so a device can
//! tell an author's release from anyone else's bytes.
//! - [`ImageVerifier`] - hashes the image as it arrives, so a device with
//! kilobytes of memory can check a payload of megabytes.
//! - [`SlotStore`] and [`MemoryStore`] - where images live, with an in-memory
//! implementation so the whole flow runs in a test with no hardware.
//! - [`Delegation`] - the anchor's signed statement of which key may sign
//! releases, so that key can be rotated without visiting the devices.
//! - [`Updater`] - the rules: verify, then stage, then try, then confirm or fall
//! back. A transfer cut off by a dead link resumes where it stopped rather than
//! starting over, which is what makes a large image installable over a slow
//! radio at all.
//!
//! # Why this is safe over an untrusted link
//!
//! The signature covers the manifest, and the manifest commits to the image's
//! digest. Authenticity therefore reaches the image without the carrier being
//! trusted at all. An update can ride a LoRa mesh, a passing phone, or a USB stick
//! left in a village, and a device will still only run what its author released.
//! That matters more here than a secure channel would, because the deployments
//! this SDK targets frequently have no certificate authority, and often no
//! internet.
//!
//! # Relationship to the SUIT specifications
//!
//! The information model is the one RFC 9124 defines, and the architecture and
//! terminology are RFC 9019's. Both are published standards. The concrete CBOR
//! serialization, `draft-ietf-suit-manifest`, is not: at the time of writing it
//! remains an Internet-Draft awaiting publication.
//!
//! So this crate implements the settled part and serializes it itself, rather
//! than pinning the SDK to a wire format that can still change. The encoding is
//! deliberately kept separate from the model, so a SUIT reader can later produce
//! the same [`Manifest`] without any of the rules around it moving. This is the same kind of considered deviation as the hand-written
//! MAVLink dialect, and it is recorded here rather than left to be discovered.
//!
//! # What it defends against
//!
//! RFC 9124 enumerates the threats a firmware update mechanism has to answer.
//! Each one this crate answers is answered by a rule with a test naming it:
//!
//! | Threat | Answered by |
//! | --- | --- |
//! | `THREAT.IMG.NON_AUTH`, unauthorised firmware | the author's signature, checked before the manifest is parsed |
//! | `THREAT.IMG.EXPIRED`, a replayed older release | a sequence number that must beat every slot, failed ones included |
//! | `THREAT.IMG.EXPIRED.OFFLINE`, a stale release aimed at a device that has been out of contact | [`Manifest::expires`], which bounds how long a release stays usable |
//! | `THREAT.IMG.INCOMPATIBLE`, firmware for another device | authenticated vendor and class identifiers |
//! | `THREAT.IMG.FORMAT`, a misread payload type | the payload format sits inside the signed body |
//!
//! Two it does not answer. `THREAT.IMG.DISCLOSURE`, an attacker reading the
//! firmware to hunt for flaws, wants payload encryption. `THREAT.UPD.WRONG_PRECURSOR`
//! only arises for differential updates, which this crate does not do.
//!
//! # Known limits
//!
//! **A retired key stays trusted until the device hears otherwise.** Rotation
//! takes effect when a device adopts the new delegation, and a device that has
//! been out of contact since a key was compromised still honours that key until
//! it is reached. There is no way to revoke faster than you can deliver, which
//! RFC 9124 acknowledges by leaving revocation outside the manifest format.
//! Setting an expiry on a delegation bounds the exposure for devices that have a
//! clock.
//!
//! **Delegation is one level deep.** The anchor appoints a release key, and the
//! chain stops there. RFC 9124 allows longer chains for delegated authority
//! between several parties; that is not implemented, and a release key cannot
//! appoint a successor.
//!
//! **The sequence number is only as trustworthy as the slot records.** It is
//! derived from what [`SlotStore`] reports, so an implementation that loses or
//! exposes those records weakens rollback protection. Hardware that can keep a
//! monotonic counter should be used where it exists.
//!
//! **It does not fetch, and it does not write to flash.** There is no transport
//! and no driver: the image arrives however the caller arranges, and
//! [`SlotStore`] is the seam to real storage.
//!
//! Also absent: attestation and secure boot, delta updates, encrypted payloads,
//! multi-payload dependency manifests, and the optional RFC 9124 elements for
//! multi-component devices, payload URIs, and execute-in-place metadata.
//!
//! # Resuming an interrupted transfer
//!
//! A slow radio can spend half an hour on one image, so a link that drops near the
//! end must not mean starting again. Progress is recorded as it is made, and
//! [`Updater::resume_at`] continues from there when the slot already holds part of
//! exactly the same image. Anything else starts over, because two images spliced
//! together are neither.
//!
//! Resuming does not make the earlier bytes trusted. A hash cannot be carried
//! across a reset, so it is rebuilt by reading back what the slot holds, and the
//! whole image is still settled by the digest check at the end. A resumed transfer
//! that completes with the wrong bytes fails exactly as a fresh one would.
//!
//! How often progress is recorded is the caller's to choose through its chunk
//! size: larger chunks mean fewer writes and less flash wear, but more to redo
//! after a reset.
//!
//! # Who may sign
//!
//! A device anchors its trust in one key. That anchor can sign releases itself,
//! which is the simple arrangement, or it can sign a [`Delegation`] naming a
//! separate release key and then stay somewhere hard to reach.
//!
//! The second is worth the extra step. The key that signs releases has to be
//! available every time you cut one, and availability is what eventually gets a
//! key stolen; an anchor that only comes out to authorise a rotation can live in a
//! safe. Rotating means issuing a delegation with a higher epoch, which retires
//! the previous key rather than adding to it.
//!
//! # How it boots
//!
//! An image is run from whichever slot holds it, and slots are never swapped.
//! That is the model MCUboot calls direct-XIP, chosen because a swap can be
//! interrupted halfway and then has to be recovered; here there is nothing to
//! recover, because nothing moves.
//!
//! # Examples
//!
//! An update is released, carried to a device, tried, and confirmed:
//!
//! ```
//! use pamoja_security::DeviceIdentity;
//! use pamoja_update::{
//! Boot, Device, Manifest, MemoryStore, PayloadFormat, Updater, ENVELOPE_MAX,
//! STRUCTURE_VERSION,
//! };
//! use sha2::{Digest, Sha256};
//!
//! let author = DeviceIdentity::from_seed(&[1u8; 32]);
//! let image = b"version two of the firmware";
//!
//! let manifest = Manifest {
//! structure_version: STRUCTURE_VERSION,
//! sequence: 2,
//! vendor_id: [0xab; 16],
//! class_id: [0xcd; 16],
//! format: PayloadFormat::Raw,
//! storage: 1,
//! digest: Sha256::digest(image).into(),
//! size: image.len() as u32,
//! expires: 0,
//! };
//! let mut envelope = [0u8; ENVELOPE_MAX];
//! let written = manifest.sign(&author, &mut envelope).unwrap();
//!
//! // The device trusts one author and knows what it is.
//! let device = Device {
//! vendor_id: [0xab; 16],
//! class_id: [0xcd; 16],
//! anchor: author.public(),
//! };
//! let mut updater = Updater::new(device, MemoryStore::new(2, 4096));
//! updater.provision(0, 1).unwrap(); // the image it shipped with
//!
//! updater.stage(&envelope[..written], image).unwrap();
//! assert_eq!(updater.on_boot().unwrap(), Boot::Trying(1));
//! assert_eq!(updater.confirm().unwrap(), 1);
//!
//! // The next boot simply runs it.
//! assert_eq!(updater.on_boot().unwrap(), Boot::Confirmed(1));
//! ```
extern crate alloc;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;