pamoja_update/manifest.rs
1//! The manifest: what an update claims about itself, and who vouches for it.
2//!
3//! The fields are the information elements RFC 9124 marks REQUIRED for a
4//! single-payload update, named the way the RFC names them. Nothing here is
5//! decorative: each one exists because leaving it out enables a specific attack,
6//! and each is checked before an image is written.
7//!
8//! An [`Envelope`] carries the encoded manifest as an opaque byte string next to
9//! the signature over exactly those bytes. Keeping the signed body intact rather
10//! than re-encoding it after parsing means the bytes that were verified are the
11//! bytes that are read, so no encoding difference can open a gap between them.
12
13use pamoja_security::{DeviceIdentity, PublicIdentity, Signature};
14
15use crate::cbor::{Reader, Writer};
16use crate::error::{Refusal, Result};
17
18/// The manifest structure version this crate writes and understands.
19pub const STRUCTURE_VERSION: u8 = 1;
20
21/// The length of a vendor or device class identifier, in bytes.
22pub const ID_LEN: usize = 16;
23
24/// The length of a payload digest, in bytes.
25pub const DIGEST_LEN: usize = 32;
26
27/// A buffer of this size always holds an encoded manifest body.
28pub const MANIFEST_MAX: usize = 128;
29
30/// A buffer of this size always holds an encoded envelope.
31pub const ENVELOPE_MAX: usize = 224;
32
33/// The length of a signature, in bytes.
34const SIGNATURE_LEN: usize = 64;
35
36// Map keys, ascending, so the encoding satisfies the deterministic ordering rule.
37const KEY_STRUCTURE_VERSION: u64 = 1;
38const KEY_SEQUENCE: u64 = 2;
39const KEY_VENDOR: u64 = 3;
40const KEY_CLASS: u64 = 4;
41const KEY_FORMAT: u64 = 5;
42const KEY_STORAGE: u64 = 6;
43const KEY_DIGEST: u64 = 7;
44const KEY_SIZE: u64 = 8;
45const KEY_EXPIRES: u64 = 9;
46
47/// Envelope keys.
48const KEY_BODY: u64 = 1;
49const KEY_SIGNATURE: u64 = 2;
50
51/// How the payload is encoded.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum PayloadFormat {
54 /// The payload is the firmware image itself, byte for byte.
55 Raw = 1,
56}
57
58impl PayloadFormat {
59 /// Reads a payload format from its encoded value.
60 ///
61 /// # Arguments
62 ///
63 /// * `value` - the encoded discriminant.
64 ///
65 /// # Returns
66 ///
67 /// The format.
68 ///
69 /// # Errors
70 ///
71 /// Returns [`Refusal::UnsupportedVersion`] for a format this build cannot
72 /// apply, so an unknown encoding is refused rather than guessed at.
73 fn from_value(value: u64) -> Result<Self> {
74 match value {
75 1 => Ok(Self::Raw),
76 _ => Err(Refusal::UnsupportedVersion),
77 }
78 }
79}
80
81/// What an update claims about itself.
82///
83/// # Examples
84///
85/// ```
86/// use pamoja_security::DeviceIdentity;
87/// use pamoja_update::{image_digest, Envelope, Manifest, PayloadFormat, ENVELOPE_MAX};
88///
89/// const VENDOR: [u8; 16] = [10; 16];
90/// const WEATHER_STATION: [u8; 16] = [13; 16];
91///
92/// let author = DeviceIdentity::from_seed(&[1u8; 32]);
93/// let image = b"release seven of the station firmware";
94/// let manifest = Manifest {
95/// structure_version: pamoja_update::STRUCTURE_VERSION,
96/// sequence: 7,
97/// vendor_id: VENDOR,
98/// class_id: WEATHER_STATION,
99/// format: PayloadFormat::Raw,
100/// storage: 0,
101/// digest: image_digest(image),
102/// size: image.len() as u32,
103/// expires: 0,
104/// };
105///
106/// let mut buf = [0u8; ENVELOPE_MAX];
107/// let written = manifest.sign(&author, &mut buf).unwrap();
108///
109/// let envelope = Envelope::decode(&buf[..written]).unwrap();
110/// let checked = envelope.verify(&author.public()).unwrap();
111/// assert_eq!(checked.sequence, 7);
112/// ```
113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub struct Manifest {
115 /// Which iteration of the manifest format this is.
116 pub structure_version: u8,
117 /// Rises with every release. A device refuses anything not above what it runs,
118 /// which is what stops a captured older image being replayed at it.
119 pub sequence: u64,
120 /// Who built the image.
121 pub vendor_id: [u8; ID_LEN],
122 /// Which kind of device it is for.
123 pub class_id: [u8; ID_LEN],
124 /// How the payload is encoded.
125 pub format: PayloadFormat,
126 /// Which slot the payload belongs in.
127 pub storage: u8,
128 /// The SHA-256 of the payload. Every other guarantee rests on this one.
129 pub digest: [u8; DIGEST_LEN],
130 /// The payload's length in bytes, known before a single byte is accepted.
131 pub size: u32,
132 /// When this release stops being offered, in seconds since the Unix epoch, or
133 /// `0` to never expire.
134 ///
135 /// A sequence number alone cannot protect a device that has been offline for a
136 /// long time: an attacker can hand it a release that is genuinely newer than
137 /// the one it runs, but old enough to have a known flaw, and the device has no
138 /// way to know a better one exists. An expiry bounds how long such a release
139 /// stays usable. Setting one requires the device to have a clock.
140 pub expires: u64,
141}
142
143impl Manifest {
144 /// Encodes the manifest body, which is the part a signature covers.
145 ///
146 /// # Arguments
147 ///
148 /// * `buf` - the destination, at least [`MANIFEST_MAX`] bytes.
149 ///
150 /// # Returns
151 ///
152 /// How many bytes were written.
153 ///
154 /// # Errors
155 ///
156 /// Returns [`Refusal::Malformed`] if `buf` is too small.
157 pub fn encode(&self, buf: &mut [u8]) -> Result<usize> {
158 let mut writer = Writer::new(buf);
159 writer.map(9)?;
160
161 writer.uint(KEY_STRUCTURE_VERSION)?;
162 writer.uint(u64::from(self.structure_version))?;
163 writer.uint(KEY_SEQUENCE)?;
164 writer.uint(self.sequence)?;
165 writer.uint(KEY_VENDOR)?;
166 writer.bytes(&self.vendor_id)?;
167 writer.uint(KEY_CLASS)?;
168 writer.bytes(&self.class_id)?;
169 writer.uint(KEY_FORMAT)?;
170 writer.uint(self.format as u64)?;
171 writer.uint(KEY_STORAGE)?;
172 writer.uint(u64::from(self.storage))?;
173 writer.uint(KEY_DIGEST)?;
174 writer.bytes(&self.digest)?;
175 writer.uint(KEY_SIZE)?;
176 writer.uint(u64::from(self.size))?;
177 writer.uint(KEY_EXPIRES)?;
178 writer.uint(self.expires)?;
179
180 Ok(writer.finish())
181 }
182
183 /// Decodes a manifest body.
184 ///
185 /// # Arguments
186 ///
187 /// * `bytes` - an encoded manifest body.
188 ///
189 /// # Returns
190 ///
191 /// The manifest.
192 ///
193 /// # Errors
194 ///
195 /// Returns [`Refusal::Malformed`] if the encoding is not a well-formed
196 /// manifest with its keys in order, or [`Refusal::UnsupportedVersion`] if it
197 /// announces a structure version or payload format this build cannot apply.
198 pub fn decode(bytes: &[u8]) -> Result<Self> {
199 let mut reader = Reader::new(bytes);
200 if reader.map()? != 9 {
201 return Err(Refusal::Malformed);
202 }
203
204 let structure_version = read_key(&mut reader, KEY_STRUCTURE_VERSION)?;
205 let structure_version = u8::try_from(structure_version).map_err(|_| Refusal::Malformed)?;
206 // Refuse a newer structure before reading further: the fields after this
207 // point only mean what this build thinks they mean at a version it knows.
208 if structure_version != STRUCTURE_VERSION {
209 return Err(Refusal::UnsupportedVersion);
210 }
211
212 let sequence = read_key(&mut reader, KEY_SEQUENCE)?;
213
214 expect_key(&mut reader, KEY_VENDOR)?;
215 let vendor_id = read_id(&mut reader)?;
216 expect_key(&mut reader, KEY_CLASS)?;
217 let class_id = read_id(&mut reader)?;
218
219 let format = PayloadFormat::from_value(read_key(&mut reader, KEY_FORMAT)?)?;
220 let storage = read_key(&mut reader, KEY_STORAGE)?;
221 let storage = u8::try_from(storage).map_err(|_| Refusal::Malformed)?;
222
223 expect_key(&mut reader, KEY_DIGEST)?;
224 let digest_bytes = reader.bytes()?;
225 let digest = <[u8; DIGEST_LEN]>::try_from(digest_bytes).map_err(|_| Refusal::Malformed)?;
226
227 let size = read_key(&mut reader, KEY_SIZE)?;
228 let size = u32::try_from(size).map_err(|_| Refusal::Malformed)?;
229
230 let expires = read_key(&mut reader, KEY_EXPIRES)?;
231
232 // Trailing bytes would mean the signed body carries something this parser
233 // never looked at, so they are refused rather than ignored.
234 if reader.position() != bytes.len() {
235 return Err(Refusal::Malformed);
236 }
237
238 Ok(Self {
239 structure_version,
240 sequence,
241 vendor_id,
242 class_id,
243 format,
244 storage,
245 digest,
246 size,
247 expires,
248 })
249 }
250
251 /// Encodes the manifest and signs it, producing an envelope.
252 ///
253 /// # Arguments
254 ///
255 /// * `author` - the identity releasing the update.
256 /// * `buf` - the destination, at least [`ENVELOPE_MAX`] bytes.
257 ///
258 /// # Returns
259 ///
260 /// How many bytes of `buf` the envelope occupies.
261 ///
262 /// # Errors
263 ///
264 /// Returns [`Refusal::Malformed`] if `buf` is too small.
265 pub fn sign(&self, author: &DeviceIdentity, buf: &mut [u8]) -> Result<usize> {
266 let mut body = [0u8; MANIFEST_MAX];
267 let body_len = self.encode(&mut body)?;
268 seal(&body[..body_len], author, buf)
269 }
270}
271
272/// A manifest body next to the signature over exactly those bytes.
273#[derive(Clone, Copy, Debug)]
274pub struct Envelope<'a> {
275 body: &'a [u8],
276 signature: [u8; SIGNATURE_LEN],
277}
278
279impl<'a> Envelope<'a> {
280 /// Decodes an envelope, borrowing the signed body from the input.
281 ///
282 /// # Arguments
283 ///
284 /// * `bytes` - the encoded envelope.
285 ///
286 /// # Returns
287 ///
288 /// The envelope, whose body is not yet trusted.
289 ///
290 /// # Errors
291 ///
292 /// Returns [`Refusal::Malformed`] if the encoding is not a well-formed
293 /// envelope.
294 pub fn decode(bytes: &'a [u8]) -> Result<Self> {
295 let mut reader = Reader::new(bytes);
296 if reader.map()? != 2 {
297 return Err(Refusal::Malformed);
298 }
299
300 expect_key(&mut reader, KEY_BODY)?;
301 let body = reader.bytes()?;
302 expect_key(&mut reader, KEY_SIGNATURE)?;
303 let signature =
304 <[u8; SIGNATURE_LEN]>::try_from(reader.bytes()?).map_err(|_| Refusal::Malformed)?;
305
306 if reader.position() != bytes.len() {
307 return Err(Refusal::Malformed);
308 }
309
310 Ok(Self { body, signature })
311 }
312
313 /// Checks the signature and returns the manifest it vouches for.
314 ///
315 /// The signature is checked before the body is interpreted, so nothing an
316 /// unknown author wrote reaches the parser.
317 ///
318 /// # Arguments
319 ///
320 /// * `author` - the public key the device trusts to release updates.
321 ///
322 /// # Returns
323 ///
324 /// The manifest, now known to be from `author` and unaltered.
325 ///
326 /// # Errors
327 ///
328 /// Returns [`Refusal::Signature`] if the signature is not this author's over
329 /// this body, or a decoding refusal if the body is not a valid manifest.
330 pub fn verify(&self, author: &PublicIdentity) -> Result<Manifest> {
331 Manifest::decode(self.verified_body(author)?)
332 }
333
334 /// Checks the signature and returns the bytes it covers.
335 ///
336 /// An envelope carries whatever its author signed, which is a manifest in the
337 /// usual case and a delegation when authority is being handed on. Checking the
338 /// signature separately from reading the body lets both share one envelope
339 /// shape without either having to know about the other.
340 ///
341 /// # Arguments
342 ///
343 /// * `signer` - the public key the body must be signed by.
344 ///
345 /// # Returns
346 ///
347 /// The signed bytes, now known to be from `signer` and unaltered.
348 ///
349 /// # Errors
350 ///
351 /// Returns [`Refusal::Signature`] if the signature is not this signer's over
352 /// this body.
353 pub fn verified_body(&self, signer: &PublicIdentity) -> Result<&'a [u8]> {
354 let signature = Signature::from_bytes(&self.signature);
355 signer
356 .verify(self.body, &signature)
357 .map_err(|_| Refusal::Signature)?;
358 Ok(self.body)
359 }
360
361 /// Returns the signed body, which is not yet known to be authentic.
362 ///
363 /// # Returns
364 ///
365 /// The encoded manifest the signature covers.
366 pub fn body(&self) -> &'a [u8] {
367 self.body
368 }
369}
370
371/// Wraps a signed body and its signature into an envelope.
372///
373/// # Arguments
374///
375/// * `body` - the bytes to sign.
376/// * `signer` - the identity vouching for them.
377/// * `buf` - the destination.
378///
379/// # Returns
380///
381/// How many bytes of `buf` the envelope occupies.
382///
383/// # Errors
384///
385/// Returns [`Refusal::Malformed`] if `buf` is too small.
386pub(crate) fn seal(body: &[u8], signer: &DeviceIdentity, buf: &mut [u8]) -> Result<usize> {
387 let signature = signer.sign(body);
388 let mut writer = Writer::new(buf);
389 writer.map(2)?;
390 writer.uint(KEY_BODY)?;
391 writer.bytes(body)?;
392 writer.uint(KEY_SIGNATURE)?;
393 writer.bytes(&signature.to_bytes())?;
394 Ok(writer.finish())
395}
396
397/// Reads an expected key and refuses anything else, holding the map to its order.
398pub(crate) fn expect_key(reader: &mut Reader<'_>, key: u64) -> Result<()> {
399 if reader.uint()? != key {
400 return Err(Refusal::Malformed);
401 }
402 Ok(())
403}
404
405/// Reads an expected key and the unsigned integer that follows it.
406pub(crate) fn read_key(reader: &mut Reader<'_>, key: u64) -> Result<u64> {
407 expect_key(reader, key)?;
408 reader.uint()
409}
410
411/// Reads a vendor or class identifier.
412fn read_id(reader: &mut Reader<'_>) -> Result<[u8; ID_LEN]> {
413 <[u8; ID_LEN]>::try_from(reader.bytes()?).map_err(|_| Refusal::Malformed)
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 /// A manifest the tests can vary one field of at a time.
421 fn sample() -> Manifest {
422 Manifest {
423 structure_version: STRUCTURE_VERSION,
424 sequence: 42,
425 vendor_id: [0xab; ID_LEN],
426 class_id: [0xcd; ID_LEN],
427 format: PayloadFormat::Raw,
428 storage: 1,
429 digest: [0x5a; DIGEST_LEN],
430 size: 65_536,
431 expires: 0,
432 }
433 }
434
435 #[test]
436 fn a_manifest_round_trips() {
437 let manifest = sample();
438 let mut buf = [0u8; MANIFEST_MAX];
439 let written = manifest.encode(&mut buf).expect("encode");
440 assert_eq!(Manifest::decode(&buf[..written]).expect("decode"), manifest);
441 }
442
443 #[test]
444 fn the_encoding_fits_the_documented_buffer_sizes() {
445 let mut body = [0u8; MANIFEST_MAX];
446 assert!(sample().encode(&mut body).expect("encode") <= MANIFEST_MAX);
447
448 let author = DeviceIdentity::from_seed(&[3u8; 32]);
449 let mut envelope = [0u8; ENVELOPE_MAX];
450 assert!(sample().sign(&author, &mut envelope).expect("sign") <= ENVELOPE_MAX);
451 }
452
453 #[test]
454 fn a_signed_envelope_verifies_against_its_author() {
455 let author = DeviceIdentity::from_seed(&[3u8; 32]);
456 let mut buf = [0u8; ENVELOPE_MAX];
457 let written = sample().sign(&author, &mut buf).expect("sign");
458
459 let envelope = Envelope::decode(&buf[..written]).expect("decode");
460 assert_eq!(envelope.verify(&author.public()).expect("verify"), sample());
461 }
462
463 #[test]
464 fn a_different_author_is_refused() {
465 let author = DeviceIdentity::from_seed(&[3u8; 32]);
466 let impostor = DeviceIdentity::from_seed(&[4u8; 32]);
467 let mut buf = [0u8; ENVELOPE_MAX];
468 let written = sample().sign(&author, &mut buf).expect("sign");
469
470 let envelope = Envelope::decode(&buf[..written]).expect("decode");
471 assert_eq!(
472 envelope.verify(&impostor.public()),
473 Err(Refusal::Signature),
474 "an envelope signed by someone else is not this device's update"
475 );
476 }
477
478 #[test]
479 fn altering_the_body_breaks_the_signature() {
480 let author = DeviceIdentity::from_seed(&[3u8; 32]);
481 let mut buf = [0u8; ENVELOPE_MAX];
482 let written = sample().sign(&author, &mut buf).expect("sign");
483
484 // Flip a bit inside the signed body, which begins after the envelope map
485 // header, the body key, and the byte-string header.
486 buf[6] ^= 0x01;
487
488 let envelope = Envelope::decode(&buf[..written]).expect("decode");
489 assert_eq!(envelope.verify(&author.public()), Err(Refusal::Signature));
490 }
491
492 #[test]
493 fn a_newer_structure_version_is_refused() {
494 let mut manifest = sample();
495 manifest.structure_version = STRUCTURE_VERSION + 1;
496 let mut buf = [0u8; MANIFEST_MAX];
497 let written = manifest.encode(&mut buf).expect("encode");
498 assert_eq!(
499 Manifest::decode(&buf[..written]),
500 Err(Refusal::UnsupportedVersion)
501 );
502 }
503
504 #[test]
505 fn an_unknown_payload_format_is_refused() {
506 let mut buf = [0u8; MANIFEST_MAX];
507 let written = sample().encode(&mut buf).expect("encode");
508 // The format value sits immediately after its key; rewrite it to one this
509 // build has no way to apply.
510 let at = buf[..written]
511 .windows(2)
512 .position(|pair| pair == [KEY_FORMAT as u8, PayloadFormat::Raw as u8])
513 .expect("the format pair");
514 buf[at + 1] = 9;
515 assert_eq!(
516 Manifest::decode(&buf[..written]),
517 Err(Refusal::UnsupportedVersion)
518 );
519 }
520
521 #[test]
522 fn a_reordered_map_is_refused() {
523 // Swap the first two keys, which breaks the ascending order the
524 // deterministic encoding requires.
525 let mut buf = [0u8; MANIFEST_MAX];
526 let written = sample().encode(&mut buf).expect("encode");
527 buf[1] = KEY_SEQUENCE as u8;
528 assert_eq!(Manifest::decode(&buf[..written]), Err(Refusal::Malformed));
529 }
530
531 #[test]
532 fn trailing_bytes_are_refused() {
533 let mut buf = [0u8; MANIFEST_MAX];
534 let written = sample().encode(&mut buf).expect("encode");
535 // A byte the parser never reads must not ride along inside a signed body.
536 assert_eq!(
537 Manifest::decode(&buf[..written + 1]),
538 Err(Refusal::Malformed)
539 );
540 }
541
542 #[test]
543 fn a_truncated_manifest_is_refused() {
544 let mut buf = [0u8; MANIFEST_MAX];
545 let written = sample().encode(&mut buf).expect("encode");
546 assert!(Manifest::decode(&buf[..written - 1]).is_err());
547 }
548
549 #[test]
550 fn a_buffer_too_small_to_hold_the_manifest_is_refused() {
551 let mut buf = [0u8; 8];
552 assert_eq!(sample().encode(&mut buf), Err(Refusal::Malformed));
553 }
554}