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::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("no valid signature found")]
69    SignatureVerificationFailed,
70
71    #[error("invalid manifest")]
72    InvalidManifest(#[from] OtaManifestError),
73}
74
75/// The parsed contents of a `SignedManifest`.
76pub struct RawManifest<'a> {
77    /// The signed manifest version.
78    pub version: u32,
79    /// The unparsed OTA manifest payload.
80    pub manifest_payload: &'a [u8],
81    /// The signatures found in the signed manifest.
82    pub signatures: Vec<Vec<u8>>,
83    /// The bytes that are signed: magic, version, manifest_size, and manifest_payload.
84    pub signed_bytes: &'a [u8],
85}
86
87impl<'a> RawManifest<'a> {
88    /// Verifies the signatures against the provided list of public keys.
89    /// Returns `Ok(())` if at least one signature is valid.
90    pub fn verify(
91        &self,
92        public_keys: &[UnparsedPublicKey<Vec<u8>>],
93    ) -> Result<(), SignedManifestError> {
94        if !self.signatures.iter().any(|signature| {
95            public_keys.iter().any(|key| key.verify(self.signed_bytes, signature).is_ok())
96        }) {
97            return Err(SignedManifestError::SignatureVerificationFailed);
98        }
99        Ok(())
100    }
101}
102
103/// Parse a `SignedManifest` without verifying its signatures or parsing its payload.
104///
105/// Returns the `RawManifest` on success.
106pub fn parse_raw(bytes: &[u8]) -> Result<RawManifest<'_>, SignedManifestError> {
107    let (header, rest) =
108        Header::read_from_prefix(bytes).map_err(|_| SignedManifestError::Truncated {
109            file_size: bytes.len(),
110            expected_size: std::mem::size_of::<Header>(),
111        })?;
112
113    if header.magic != MAGIC {
114        return Err(SignedManifestError::InvalidMagic(header.magic));
115    }
116
117    let version = header.version.get();
118    if version != VERSION {
119        return Err(SignedManifestError::UnknownVersion(version));
120    }
121
122    let manifest_size = header.manifest_size.get_usize();
123    if manifest_size > MAX_MANIFEST_SIZE {
124        return Err(SignedManifestError::ManifestSizeTooLarge(manifest_size));
125    }
126
127    let (manifest_payload, after_manifest) =
128        rest.split_at_checked(manifest_size).ok_or_else(|| SignedManifestError::Truncated {
129            file_size: bytes.len(),
130            expected_size: std::mem::size_of::<Header>() + manifest_size,
131        })?;
132
133    let (signature_size_val, signature_bytes) =
134        U32::read_from_prefix(after_manifest).map_err(|_| SignedManifestError::Truncated {
135            file_size: bytes.len(),
136            expected_size: std::mem::size_of::<Header>()
137                + manifest_size
138                + std::mem::size_of::<U32>(),
139        })?;
140
141    let signature_size = signature_size_val.get_usize();
142    if signature_size > MAX_SIGNATURE_SIZE {
143        return Err(SignedManifestError::SignatureSizeTooLarge(signature_size));
144    }
145
146    let (signature_payload, _) =
147        signature_bytes.split_at_checked(signature_size).ok_or_else(|| {
148            SignedManifestError::Truncated {
149                file_size: bytes.len(),
150                expected_size: std::mem::size_of::<Header>()
151                    + manifest_size
152                    + std::mem::size_of::<U32>()
153                    + signature_size,
154            }
155        })?;
156
157    let signatures_msg = proto::Signatures::decode(signature_payload)
158        .map_err(SignedManifestError::InvalidSignatures)?;
159
160    // The signed portion comprises the magic, version, manifest_size, and manifest bytes.
161    let signed_bytes = &bytes[..std::mem::size_of::<Header>() + manifest_size];
162
163    Ok(RawManifest {
164        version,
165        manifest_payload,
166        signatures: signatures_msg.signatures,
167        signed_bytes,
168    })
169}
170
171/// Parse and verify a `SignedManifest`.
172///
173/// Returns the parsed `OtaManifest` on success.
174pub fn parse_and_verify(
175    bytes: &[u8],
176    public_keys: &[UnparsedPublicKey<Vec<u8>>],
177) -> Result<OtaManifest, SignedManifestError> {
178    let raw = parse_raw(bytes)?;
179    let () = raw.verify(public_keys)?;
180    Ok(parse_ota_manifest(raw.manifest_payload)?)
181}
182
183/// Helper function to generate a valid `SignedManifest` bytes for testing.
184pub fn generate(
185    manifest: OtaManifest,
186    key: &ring::signature::Ed25519KeyPair,
187) -> Result<Vec<u8>, SignedManifestError> {
188    let manifest_bytes = manifest.serialize();
189    if manifest_bytes.len() > MAX_MANIFEST_SIZE {
190        return Err(SignedManifestError::ManifestSizeTooLarge(manifest_bytes.len()));
191    }
192    let manifest_size = manifest_bytes.len() as u32;
193
194    let header =
195        Header { magic: MAGIC, version: U32::new(VERSION), manifest_size: U32::new(manifest_size) };
196
197    let mut signed_bytes = Vec::with_capacity(std::mem::size_of::<Header>() + manifest_bytes.len());
198    signed_bytes.extend_from_slice(header.as_bytes());
199    signed_bytes.extend_from_slice(&manifest_bytes);
200
201    let signature = key.sign(&signed_bytes);
202
203    let signatures_msg = proto::Signatures { signatures: vec![signature.as_ref().to_vec()] };
204    let signatures_bytes = signatures_msg.encode_to_vec();
205    if signatures_bytes.len() > MAX_SIGNATURE_SIZE {
206        return Err(SignedManifestError::SignatureSizeTooLarge(signatures_bytes.len()));
207    }
208    let signatures_size = signatures_bytes.len() as u32;
209
210    let mut out = signed_bytes;
211    out.extend_from_slice(U32::new(signatures_size).as_bytes());
212    out.extend_from_slice(&signatures_bytes);
213    Ok(out)
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use assert_matches::assert_matches;
220    use ring::signature::KeyPair as _;
221
222    fn make_ota_manifest() -> OtaManifest {
223        OtaManifest {
224            build_info_version: "1.2.3.4".parse().unwrap(),
225            board: "test-board".to_string(),
226            epoch: 1,
227            mode: crate::update_mode::UpdateMode::Normal,
228            blob_base_url: "http://example.com".to_string(),
229            images: vec![],
230            blobs: vec![],
231        }
232    }
233
234    fn make_keypair() -> ring::signature::Ed25519KeyPair {
235        let rng = ring::rand::SystemRandom::new();
236        let pkcs8 = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
237        ring::signature::Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap()
238    }
239
240    fn make_public_key(keypair: &ring::signature::Ed25519KeyPair) -> UnparsedPublicKey<Vec<u8>> {
241        UnparsedPublicKey::new(&ring::signature::ED25519, keypair.public_key().as_ref().to_vec())
242    }
243
244    #[test]
245    fn test_parse_and_verify_success() {
246        let keypair = make_keypair();
247        let manifest = make_ota_manifest();
248        let bytes = generate(manifest.clone(), &keypair).unwrap();
249
250        let trusted_keys = vec![make_public_key(&keypair)];
251        let parsed = parse_and_verify(&bytes, &trusted_keys).unwrap();
252        assert_eq!(parsed, manifest);
253    }
254
255    #[test]
256    fn test_parse_and_verify_wrong_magic() {
257        let keypair = make_keypair();
258        let manifest = make_ota_manifest();
259        let mut bytes = generate(manifest.clone(), &keypair).unwrap();
260
261        bytes[0] ^= 0xff;
262
263        let trusted_keys = vec![make_public_key(&keypair)];
264        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
265        assert_matches!(err, SignedManifestError::InvalidMagic(_));
266    }
267
268    #[test]
269    fn test_parse_and_verify_wrong_version() {
270        let keypair = make_keypair();
271        let manifest = make_ota_manifest();
272        let mut bytes = generate(manifest.clone(), &keypair).unwrap();
273
274        // Version starts at byte offset 4, length 4, little endian.
275        bytes[4] ^= 0x01;
276
277        let trusted_keys = vec![make_public_key(&keypair)];
278        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
279        assert_matches!(err, SignedManifestError::UnknownVersion(_));
280    }
281
282    #[test]
283    fn test_parse_and_verify_truncated_header() {
284        let trusted_keys = vec![];
285        let bytes = vec![0; std::mem::size_of::<Header>() - 1];
286        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
287        assert_matches!(err, SignedManifestError::Truncated { .. });
288    }
289
290    #[test]
291    fn test_parse_and_verify_truncated_signature() {
292        let keypair = make_keypair();
293        let manifest = make_ota_manifest();
294        let mut bytes = generate(manifest.clone(), &keypair).unwrap();
295
296        // Truncate the signature payload
297        bytes.truncate(bytes.len() - 1);
298
299        let trusted_keys = vec![make_public_key(&keypair)];
300        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
301        assert_matches!(err, SignedManifestError::Truncated { .. });
302    }
303
304    #[test]
305    fn test_parse_and_verify_bad_signature() {
306        let keypair = make_keypair();
307        let manifest = make_ota_manifest();
308        let bytes = generate(manifest.clone(), &keypair).unwrap();
309
310        let wrong_keypair = make_keypair();
311        let trusted_keys = vec![make_public_key(&wrong_keypair)];
312
313        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
314        assert_matches!(err, SignedManifestError::SignatureVerificationFailed);
315    }
316}