Skip to main content

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}