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