Skip to main content

pamoja_update/
trust.rs

1//! Who a device will take an update from, and how that can change.
2//!
3//! Trusting one key forever is a trap. The key has to be reachable to sign each
4//! release, which is exactly what makes it likely to leak eventually, and a device
5//! that trusts only that key is then permanently takeable. Lose it instead of
6//! leaking it and the fleet becomes permanently unreachable, which is no better.
7//!
8//! So a device anchors its trust in a key that is used almost never and can live
9//! in a safe, and that anchor signs a [`Delegation`] naming the key that actually
10//! signs releases. Rotating the release key means issuing a new delegation, not
11//! visiting the devices. This is the arrangement RFC 9124 calls a delegation
12//! chain, at the depth that covers rotation: anchor, then release key.
13
14use pamoja_security::{DeviceIdentity, PublicIdentity};
15
16use crate::cbor::{Reader, Writer};
17use crate::error::{Refusal, Result};
18use crate::manifest::{expect_key, read_key, seal, Envelope};
19
20/// A buffer of this size always holds an encoded delegation envelope.
21pub const DELEGATION_MAX: usize = 192;
22
23/// The length of a public key, in bytes.
24const KEY_LEN: usize = 32;
25
26// Map keys, ascending, as the deterministic encoding requires.
27const KEY_EPOCH: u64 = 1;
28const KEY_RELEASE: u64 = 2;
29const KEY_EXPIRES: u64 = 3;
30
31/// A statement, signed by a device's trust anchor, naming the key that may sign
32/// its updates.
33///
34/// # Examples
35///
36/// ```
37/// use pamoja_security::DeviceIdentity;
38/// use pamoja_update::{Delegation, DELEGATION_MAX};
39///
40/// let anchor = DeviceIdentity::from_seed(&[1u8; 32]);
41/// let release = DeviceIdentity::from_seed(&[2u8; 32]);
42///
43/// let delegation = Delegation {
44///     epoch: 1,
45///     release_key: release.public().to_bytes(),
46///     expires: 0,
47/// };
48///
49/// let mut buf = [0u8; DELEGATION_MAX];
50/// let written = delegation.sign(&anchor, &mut buf).unwrap();
51///
52/// let adopted = Delegation::open(&buf[..written], &anchor.public()).unwrap();
53/// assert_eq!(adopted, delegation);
54/// ```
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct Delegation {
57    /// Rises with every rotation. A device refuses a delegation not above the one
58    /// it holds, so a retired key cannot be reinstated by replaying the statement
59    /// that once authorised it.
60    pub epoch: u64,
61    /// The public key that may sign manifests while this delegation stands.
62    pub release_key: [u8; KEY_LEN],
63    /// When this delegation stops being honoured, in seconds since the Unix epoch,
64    /// or `0` to never expire. Setting one requires the device to have a clock.
65    pub expires: u64,
66}
67
68impl Delegation {
69    /// Encodes the delegation body, which is the part a signature covers.
70    ///
71    /// # Arguments
72    ///
73    /// * `buf` - the destination.
74    ///
75    /// # Returns
76    ///
77    /// How many bytes were written.
78    ///
79    /// # Errors
80    ///
81    /// Returns [`Refusal::Malformed`] if `buf` is too small.
82    pub fn encode(&self, buf: &mut [u8]) -> Result<usize> {
83        let mut writer = Writer::new(buf);
84        writer.map(3)?;
85        writer.uint(KEY_EPOCH)?;
86        writer.uint(self.epoch)?;
87        writer.uint(KEY_RELEASE)?;
88        writer.bytes(&self.release_key)?;
89        writer.uint(KEY_EXPIRES)?;
90        writer.uint(self.expires)?;
91        Ok(writer.finish())
92    }
93
94    /// Decodes a delegation body.
95    ///
96    /// # Arguments
97    ///
98    /// * `bytes` - an encoded delegation body.
99    ///
100    /// # Returns
101    ///
102    /// The delegation.
103    ///
104    /// # Errors
105    ///
106    /// Returns [`Refusal::Malformed`] if the encoding is not a well-formed
107    /// delegation with its keys in order.
108    pub fn decode(bytes: &[u8]) -> Result<Self> {
109        let mut reader = Reader::new(bytes);
110        if reader.map()? != 3 {
111            return Err(Refusal::Malformed);
112        }
113
114        let epoch = read_key(&mut reader, KEY_EPOCH)?;
115
116        expect_key(&mut reader, KEY_RELEASE)?;
117        let release_key =
118            <[u8; KEY_LEN]>::try_from(reader.bytes()?).map_err(|_| Refusal::Malformed)?;
119
120        let expires = read_key(&mut reader, KEY_EXPIRES)?;
121
122        if reader.position() != bytes.len() {
123            return Err(Refusal::Malformed);
124        }
125
126        Ok(Self {
127            epoch,
128            release_key,
129            expires,
130        })
131    }
132
133    /// Encodes the delegation and signs it with the trust anchor.
134    ///
135    /// # Arguments
136    ///
137    /// * `anchor` - the trust anchor's identity, which alone may delegate.
138    /// * `buf` - the destination, at least [`DELEGATION_MAX`] bytes.
139    ///
140    /// # Returns
141    ///
142    /// How many bytes of `buf` the envelope occupies.
143    ///
144    /// # Errors
145    ///
146    /// Returns [`Refusal::Malformed`] if `buf` is too small.
147    pub fn sign(&self, anchor: &DeviceIdentity, buf: &mut [u8]) -> Result<usize> {
148        let mut body = [0u8; DELEGATION_MAX];
149        let body_len = self.encode(&mut body)?;
150        seal(&body[..body_len], anchor, buf)
151    }
152
153    /// Checks a delegation envelope against a trust anchor and reads it.
154    ///
155    /// # Arguments
156    ///
157    /// * `envelope` - the signed delegation.
158    /// * `anchor` - the key the device anchors its trust in.
159    ///
160    /// # Returns
161    ///
162    /// The delegation, now known to be from the anchor and unaltered.
163    ///
164    /// # Errors
165    ///
166    /// Returns [`Refusal::Signature`] if it is not the anchor's, or a decoding
167    /// refusal if the body is not a valid delegation.
168    pub fn open(envelope: &[u8], anchor: &PublicIdentity) -> Result<Self> {
169        let body = Envelope::decode(envelope)?.verified_body(anchor)?;
170        Self::decode(body)
171    }
172
173    /// Returns the release key as an identity that can check a manifest.
174    ///
175    /// # Returns
176    ///
177    /// The delegated public key.
178    ///
179    /// # Errors
180    ///
181    /// Returns [`Refusal::Signature`] if the delegated bytes are not a usable
182    /// public key, so a malformed delegation cannot leave a device trusting
183    /// nothing while believing it trusts something.
184    pub fn signer(&self) -> Result<PublicIdentity> {
185        PublicIdentity::from_bytes(&self.release_key).map_err(|_| Refusal::Signature)
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    /// The anchor the tests delegate from.
194    fn anchor() -> DeviceIdentity {
195        DeviceIdentity::from_seed(&[1u8; 32])
196    }
197
198    /// A delegation naming a release key derived from `seed`.
199    fn delegation(epoch: u64, seed: u8) -> Delegation {
200        Delegation {
201            epoch,
202            release_key: DeviceIdentity::from_seed(&[seed; 32]).public().to_bytes(),
203            expires: 0,
204        }
205    }
206
207    #[test]
208    fn a_delegation_round_trips() {
209        let original = delegation(3, 2);
210        let mut buf = [0u8; DELEGATION_MAX];
211        let written = original.encode(&mut buf).expect("encode");
212        assert_eq!(
213            Delegation::decode(&buf[..written]).expect("decode"),
214            original
215        );
216    }
217
218    #[test]
219    fn the_encoding_fits_the_documented_buffer_size() {
220        let mut buf = [0u8; DELEGATION_MAX];
221        assert!(delegation(u64::MAX, 2).sign(&anchor(), &mut buf).is_ok());
222    }
223
224    #[test]
225    fn a_delegation_opens_against_its_anchor() {
226        let original = delegation(1, 2);
227        let mut buf = [0u8; DELEGATION_MAX];
228        let written = original.sign(&anchor(), &mut buf).expect("sign");
229        assert_eq!(
230            Delegation::open(&buf[..written], &anchor().public()).expect("open"),
231            original
232        );
233    }
234
235    #[test]
236    fn a_delegation_from_anyone_else_is_refused() {
237        let impostor = DeviceIdentity::from_seed(&[9u8; 32]);
238        let mut buf = [0u8; DELEGATION_MAX];
239        let written = delegation(1, 2).sign(&impostor, &mut buf).expect("sign");
240        assert_eq!(
241            Delegation::open(&buf[..written], &anchor().public()),
242            Err(Refusal::Signature),
243            "only the trust anchor may hand on the right to sign updates"
244        );
245    }
246
247    #[test]
248    fn altering_a_delegation_breaks_its_signature() {
249        let mut buf = [0u8; DELEGATION_MAX];
250        let written = delegation(1, 2).sign(&anchor(), &mut buf).expect("sign");
251        // Swap in a different release key inside the signed body.
252        buf[10] ^= 0x01;
253        assert_eq!(
254            Delegation::open(&buf[..written], &anchor().public()),
255            Err(Refusal::Signature)
256        );
257    }
258
259    #[test]
260    fn the_delegated_key_is_usable() {
261        let release = DeviceIdentity::from_seed(&[2u8; 32]);
262        let signer = delegation(1, 2).signer().expect("signer");
263        assert_eq!(signer.to_bytes(), release.public().to_bytes());
264    }
265}