Skip to main content

update_package/
signed_manifest.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Support for parsing and generating signed manifests.
6
7use crate::manifest::{OtaManifest, OtaManifestError, parse_ota_manifest};
8use ota_manifest_proto::fuchsia::update::manifest as proto;
9use prost::Message as _;
10use ring::signature::{KeyPair as _, UnparsedPublicKey};
11use zerocopy::byteorder::little_endian::U32;
12use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
13
14/// The header of a Signed Manifest.
15#[derive(Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
16#[repr(C)]
17struct Header {
18    magic: [u8; 4],
19    version: U32,
20    manifest_size: U32,
21}
22
23/// Magic bytes for the Signed Manifest: FuChsIA OTA Format.
24pub const MAGIC: [u8; 4] = [0xfc, 0x1a, 0x07, 0xaf];
25
26/// Version for the Signed Manifest.
27pub const VERSION: u32 = 1;
28
29/// Maximum allowed payload size for the `OtaManifest` portion (10 MiB).
30pub const MAX_MANIFEST_SIZE: usize = 10 * 1024 * 1024;
31
32/// Maximum allowed payload size for the `Signatures` portion (1 MiB).
33pub const MAX_SIGNATURE_SIZE: usize = 1024 * 1024;
34
35trait U32Ext {
36    fn get_usize(&self) -> usize;
37}
38
39impl U32Ext for U32 {
40    fn get_usize(&self) -> usize {
41        const { assert!(usize::BITS >= u32::BITS) }
42        self.get() as usize
43    }
44}
45
46/// An error encountered while parsing or verifying a signed manifest.
47#[derive(Debug, thiserror::Error)]
48#[allow(missing_docs)]
49pub enum SignedManifestError {
50    #[error("file truncated: file size {file_size} is less than required size {expected_size}")]
51    Truncated { file_size: usize, expected_size: usize },
52
53    #[error("invalid magic: {0:?}")]
54    InvalidMagic([u8; 4]),
55
56    #[error("unknown version: {0}")]
57    UnknownVersion(u32),
58
59    #[error("manifest size too large: {0} > {MAX_MANIFEST_SIZE}")]
60    ManifestSizeTooLarge(usize),
61
62    #[error("signature size too large: {0} > {MAX_SIGNATURE_SIZE}")]
63    SignatureSizeTooLarge(usize),
64
65    #[error("failed to deserialize signatures")]
66    InvalidSignatures(#[source] prost::DecodeError),
67
68    #[error("root signature verification failed")]
69    RootSignatureVerificationFailed,
70
71    #[error("manifest signature verification failed")]
72    ManifestSignatureVerificationFailed,
73
74    #[error("invalid manifest")]
75    InvalidManifest(#[from] OtaManifestError),
76}
77
78/// The parsed contents of a `SignedManifest`.
79pub struct RawManifest<'a> {
80    /// The signed manifest version.
81    pub version: u32,
82    /// The unparsed OTA manifest payload.
83    pub manifest_payload: &'a [u8],
84    /// The signatures found in the signed manifest.
85    pub signatures: proto::Signatures,
86    /// The bytes that are signed: magic, version, manifest_size, and manifest_payload.
87    pub signed_bytes: &'a [u8],
88}
89
90impl<'a> RawManifest<'a> {
91    /// Verifies the signatures against the provided list of public keys.
92    /// Returns `Ok(())` if at least one signature is valid.
93    pub fn verify(
94        &self,
95        public_keys: &[UnparsedPublicKey<Vec<u8>>],
96    ) -> Result<(), SignedManifestError> {
97        if !public_keys.iter().any(|root_key| {
98            root_key
99                .verify(
100                    &self.signatures.manifest_public_key,
101                    &self.signatures.manifest_key_signature,
102                )
103                .is_ok()
104        }) {
105            return Err(SignedManifestError::RootSignatureVerificationFailed);
106        }
107
108        let manifest_public_key =
109            UnparsedPublicKey::new(&ring::signature::ED25519, &self.signatures.manifest_public_key);
110        match manifest_public_key.verify(self.signed_bytes, &self.signatures.manifest_signature) {
111            Ok(()) => Ok(()),
112            Err(ring::error::Unspecified) => {
113                Err(SignedManifestError::ManifestSignatureVerificationFailed)
114            }
115        }
116    }
117}
118
119/// Parse a `SignedManifest` without verifying its signatures or parsing its payload.
120///
121/// Returns the `RawManifest` on success.
122pub fn parse_raw(bytes: &[u8]) -> Result<RawManifest<'_>, SignedManifestError> {
123    let (header, rest) =
124        Header::read_from_prefix(bytes).map_err(|_| SignedManifestError::Truncated {
125            file_size: bytes.len(),
126            expected_size: std::mem::size_of::<Header>(),
127        })?;
128
129    if header.magic != MAGIC {
130        return Err(SignedManifestError::InvalidMagic(header.magic));
131    }
132
133    let version = header.version.get();
134    if version != VERSION {
135        return Err(SignedManifestError::UnknownVersion(version));
136    }
137
138    let manifest_size = header.manifest_size.get_usize();
139    if manifest_size > MAX_MANIFEST_SIZE {
140        return Err(SignedManifestError::ManifestSizeTooLarge(manifest_size));
141    }
142
143    let (manifest_payload, after_manifest) =
144        rest.split_at_checked(manifest_size).ok_or_else(|| SignedManifestError::Truncated {
145            file_size: bytes.len(),
146            expected_size: std::mem::size_of::<Header>() + manifest_size,
147        })?;
148
149    let (signature_size_val, signature_bytes) =
150        U32::read_from_prefix(after_manifest).map_err(|_| SignedManifestError::Truncated {
151            file_size: bytes.len(),
152            expected_size: std::mem::size_of::<Header>()
153                + manifest_size
154                + std::mem::size_of::<U32>(),
155        })?;
156
157    let signature_size = signature_size_val.get_usize();
158    if signature_size > MAX_SIGNATURE_SIZE {
159        return Err(SignedManifestError::SignatureSizeTooLarge(signature_size));
160    }
161
162    let (signature_payload, _) =
163        signature_bytes.split_at_checked(signature_size).ok_or_else(|| {
164            SignedManifestError::Truncated {
165                file_size: bytes.len(),
166                expected_size: std::mem::size_of::<Header>()
167                    + manifest_size
168                    + std::mem::size_of::<U32>()
169                    + signature_size,
170            }
171        })?;
172
173    let signatures_msg = proto::Signatures::decode(signature_payload)
174        .map_err(SignedManifestError::InvalidSignatures)?;
175
176    // The signed portion comprises the magic, version, manifest_size, and manifest bytes.
177    let signed_bytes = &bytes[..std::mem::size_of::<Header>() + manifest_size];
178
179    Ok(RawManifest { version, manifest_payload, signatures: signatures_msg, signed_bytes })
180}
181
182/// Parse and verify a `SignedManifest`.
183///
184/// Returns the parsed `OtaManifest` on success.
185pub fn parse_and_verify(
186    bytes: &[u8],
187    public_keys: &[UnparsedPublicKey<Vec<u8>>],
188) -> Result<OtaManifest, SignedManifestError> {
189    let raw = parse_raw(bytes)?;
190    let () = raw.verify(public_keys)?;
191    Ok(parse_ota_manifest(raw.manifest_payload)?)
192}
193
194/// Helper function to generate valid `SignedManifest` bytes, using a provided manifest key
195/// signature or signing the manifest public key with `manifest_key`.
196pub fn generate(
197    manifest: OtaManifest,
198    manifest_key: &ring::signature::Ed25519KeyPair,
199    key_signature: Option<&[u8]>,
200) -> Result<Vec<u8>, SignedManifestError> {
201    let manifest_bytes = manifest.serialize();
202    if manifest_bytes.len() > MAX_MANIFEST_SIZE {
203        return Err(SignedManifestError::ManifestSizeTooLarge(manifest_bytes.len()));
204    }
205    let manifest_size = manifest_bytes.len() as u32;
206
207    let header =
208        Header { magic: MAGIC, version: U32::new(VERSION), manifest_size: U32::new(manifest_size) };
209
210    let mut signed_bytes = Vec::with_capacity(std::mem::size_of::<Header>() + manifest_bytes.len());
211    signed_bytes.extend_from_slice(header.as_bytes());
212    signed_bytes.extend_from_slice(&manifest_bytes);
213
214    let manifest_signature = manifest_key.sign(&signed_bytes).as_ref().to_vec();
215    let manifest_public_key = manifest_key.public_key().as_ref().to_vec();
216    let manifest_key_signature = match key_signature {
217        Some(sig) => sig.to_vec(),
218        None => manifest_key.sign(&manifest_public_key).as_ref().to_vec(),
219    };
220
221    let signatures_msg =
222        proto::Signatures { manifest_signature, manifest_public_key, manifest_key_signature };
223    let signatures_bytes = signatures_msg.encode_to_vec();
224    if signatures_bytes.len() > MAX_SIGNATURE_SIZE {
225        return Err(SignedManifestError::SignatureSizeTooLarge(signatures_bytes.len()));
226    }
227    let signatures_size = signatures_bytes.len() as u32;
228
229    let mut out = signed_bytes;
230    out.extend_from_slice(U32::new(signatures_size).as_bytes());
231    out.extend_from_slice(&signatures_bytes);
232    Ok(out)
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use assert_matches::assert_matches;
239
240    fn make_ota_manifest() -> OtaManifest {
241        OtaManifest {
242            product_bundle_version: "1.2.3.4".parse().unwrap(),
243            board: "test-board".to_string(),
244            epoch: 1,
245            mode: crate::update_mode::UpdateMode::Normal,
246            blob_base_url: "http://example.com".to_string(),
247            images: vec![],
248            blobs: vec![],
249        }
250    }
251
252    fn make_keypair() -> ring::signature::Ed25519KeyPair {
253        let rng = ring::rand::SystemRandom::new();
254        let pkcs8 = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
255        ring::signature::Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap()
256    }
257
258    fn make_public_key(keypair: &ring::signature::Ed25519KeyPair) -> UnparsedPublicKey<Vec<u8>> {
259        UnparsedPublicKey::new(&ring::signature::ED25519, keypair.public_key().as_ref().to_vec())
260    }
261
262    #[test]
263    fn test_parse_and_verify_success() {
264        let manifest_key = make_keypair();
265        let manifest = make_ota_manifest();
266        let bytes = generate(manifest.clone(), &manifest_key, None).unwrap();
267
268        let trusted_keys = vec![make_public_key(&manifest_key)];
269        let parsed = parse_and_verify(&bytes, &trusted_keys).unwrap();
270        assert_eq!(parsed, manifest);
271    }
272
273    #[test]
274    fn test_parse_and_verify_wrong_magic() {
275        let manifest_key = make_keypair();
276        let manifest = make_ota_manifest();
277        let mut bytes = generate(manifest, &manifest_key, None).unwrap();
278
279        bytes[0] ^= 0xff;
280
281        let trusted_keys = vec![make_public_key(&manifest_key)];
282        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
283        assert_matches!(err, SignedManifestError::InvalidMagic(_));
284    }
285
286    #[test]
287    fn test_parse_and_verify_wrong_version() {
288        let manifest_key = make_keypair();
289        let manifest = make_ota_manifest();
290        let mut bytes = generate(manifest, &manifest_key, None).unwrap();
291
292        // Version starts at byte offset 4, length 4, little endian.
293        bytes[4] ^= 0x01;
294
295        let trusted_keys = vec![make_public_key(&manifest_key)];
296        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
297        assert_matches!(err, SignedManifestError::UnknownVersion(_));
298    }
299
300    #[test]
301    fn test_parse_and_verify_truncated_header() {
302        let trusted_keys = vec![];
303        let bytes = vec![0; std::mem::size_of::<Header>() - 1];
304        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
305        assert_matches!(err, SignedManifestError::Truncated { .. });
306    }
307
308    #[test]
309    fn test_parse_and_verify_truncated_signature() {
310        let manifest_key = make_keypair();
311        let manifest = make_ota_manifest();
312        let mut bytes = generate(manifest, &manifest_key, None).unwrap();
313
314        // Truncate the signature payload
315        bytes.truncate(bytes.len() - 1);
316
317        let trusted_keys = vec![make_public_key(&manifest_key)];
318        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
319        assert_matches!(err, SignedManifestError::Truncated { .. });
320    }
321
322    #[test]
323    fn test_parse_and_verify_bad_root_signature() {
324        let manifest_key = make_keypair();
325        let root_key = make_keypair();
326        let manifest = make_ota_manifest();
327        let bad_sig = vec![0u8; 64];
328        let bytes = generate(manifest, &manifest_key, Some(&bad_sig)).unwrap();
329
330        let trusted_keys = vec![make_public_key(&root_key)];
331
332        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
333        assert_matches!(err, SignedManifestError::RootSignatureVerificationFailed);
334    }
335
336    #[test]
337    fn test_parse_and_verify_bad_manifest_signature() {
338        let manifest_key = make_keypair();
339        let manifest = make_ota_manifest();
340        let mut bytes = generate(manifest, &manifest_key, None).unwrap();
341
342        // Corrupt one byte of the manifest payload (which starts after the header)
343        let payload_start = std::mem::size_of::<Header>();
344        bytes[payload_start] ^= 0xFF;
345
346        let trusted_keys = vec![make_public_key(&manifest_key)];
347
348        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
349        assert_matches!(err, SignedManifestError::ManifestSignatureVerificationFailed);
350    }
351
352    #[test]
353    fn test_generate_with_key_signature() {
354        let manifest_key = make_keypair();
355        let root_key = make_keypair();
356        let manifest = make_ota_manifest();
357
358        let manifest_public_key = manifest_key.public_key().as_ref();
359        let manifest_key_sig = root_key.sign(manifest_public_key).as_ref().to_vec();
360
361        let bytes = generate(manifest.clone(), &manifest_key, Some(&manifest_key_sig)).unwrap();
362
363        let trusted_keys = vec![make_public_key(&root_key)];
364        let parsed = parse_and_verify(&bytes, &trusted_keys).unwrap();
365        assert_eq!(parsed, manifest);
366    }
367}