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::{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}