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 a valid `SignedManifest` bytes for testing.
195pub fn generate(
196    manifest: OtaManifest,
197    manifest_key: &ring::signature::Ed25519KeyPair,
198    root_key: &ring::signature::Ed25519KeyPair,
199) -> Result<Vec<u8>, SignedManifestError> {
200    let manifest_bytes = manifest.serialize();
201    if manifest_bytes.len() > MAX_MANIFEST_SIZE {
202        return Err(SignedManifestError::ManifestSizeTooLarge(manifest_bytes.len()));
203    }
204    let manifest_size = manifest_bytes.len() as u32;
205
206    let header =
207        Header { magic: MAGIC, version: U32::new(VERSION), manifest_size: U32::new(manifest_size) };
208
209    let mut signed_bytes = Vec::with_capacity(std::mem::size_of::<Header>() + manifest_bytes.len());
210    signed_bytes.extend_from_slice(header.as_bytes());
211    signed_bytes.extend_from_slice(&manifest_bytes);
212
213    let manifest_signature = manifest_key.sign(&signed_bytes).as_ref().to_vec();
214    let manifest_public_key = manifest_key.public_key().as_ref().to_vec();
215    let manifest_key_signature = root_key.sign(&manifest_public_key).as_ref().to_vec();
216
217    let signatures_msg =
218        proto::Signatures { manifest_signature, manifest_public_key, manifest_key_signature };
219    let signatures_bytes = signatures_msg.encode_to_vec();
220    if signatures_bytes.len() > MAX_SIGNATURE_SIZE {
221        return Err(SignedManifestError::SignatureSizeTooLarge(signatures_bytes.len()));
222    }
223    let signatures_size = signatures_bytes.len() as u32;
224
225    let mut out = signed_bytes;
226    out.extend_from_slice(U32::new(signatures_size).as_bytes());
227    out.extend_from_slice(&signatures_bytes);
228    Ok(out)
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use assert_matches::assert_matches;
235
236    fn make_ota_manifest() -> OtaManifest {
237        OtaManifest {
238            build_info_version: "1.2.3.4".parse().unwrap(),
239            board: "test-board".to_string(),
240            epoch: 1,
241            mode: crate::update_mode::UpdateMode::Normal,
242            blob_base_url: "http://example.com".to_string(),
243            images: vec![],
244            blobs: vec![],
245        }
246    }
247
248    fn make_keypair() -> ring::signature::Ed25519KeyPair {
249        let rng = ring::rand::SystemRandom::new();
250        let pkcs8 = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
251        ring::signature::Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap()
252    }
253
254    fn make_public_key(keypair: &ring::signature::Ed25519KeyPair) -> UnparsedPublicKey<Vec<u8>> {
255        UnparsedPublicKey::new(&ring::signature::ED25519, keypair.public_key().as_ref().to_vec())
256    }
257
258    #[test]
259    fn test_parse_and_verify_success() {
260        let manifest_key = make_keypair();
261        let root_key = make_keypair();
262        let manifest = make_ota_manifest();
263        let bytes = generate(manifest.clone(), &manifest_key, &root_key).unwrap();
264
265        let trusted_keys = vec![make_public_key(&root_key)];
266        let parsed = parse_and_verify(&bytes, &trusted_keys).unwrap();
267        assert_eq!(parsed, manifest);
268    }
269
270    #[test]
271    fn test_parse_and_verify_wrong_magic() {
272        let manifest_key = make_keypair();
273        let root_key = make_keypair();
274        let manifest = make_ota_manifest();
275        let mut bytes = generate(manifest, &manifest_key, &root_key).unwrap();
276
277        bytes[0] ^= 0xff;
278
279        let trusted_keys = vec![make_public_key(&root_key)];
280        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
281        assert_matches!(err, SignedManifestError::InvalidMagic(_));
282    }
283
284    #[test]
285    fn test_parse_and_verify_wrong_version() {
286        let manifest_key = make_keypair();
287        let root_key = make_keypair();
288        let manifest = make_ota_manifest();
289        let mut bytes = generate(manifest, &manifest_key, &root_key).unwrap();
290
291        // Version starts at byte offset 4, length 4, little endian.
292        bytes[4] ^= 0x01;
293
294        let trusted_keys = vec![make_public_key(&root_key)];
295        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
296        assert_matches!(err, SignedManifestError::UnknownVersion(_));
297    }
298
299    #[test]
300    fn test_parse_and_verify_truncated_header() {
301        let trusted_keys = vec![];
302        let bytes = vec![0; std::mem::size_of::<Header>() - 1];
303        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
304        assert_matches!(err, SignedManifestError::Truncated { .. });
305    }
306
307    #[test]
308    fn test_parse_and_verify_truncated_signature() {
309        let manifest_key = make_keypair();
310        let root_key = make_keypair();
311        let manifest = make_ota_manifest();
312        let mut bytes = generate(manifest, &manifest_key, &root_key).unwrap();
313
314        // Truncate the signature payload
315        bytes.truncate(bytes.len() - 1);
316
317        let trusted_keys = vec![make_public_key(&root_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 bytes = generate(manifest, &manifest_key, &root_key).unwrap();
328
329        let wrong_root_key = make_keypair();
330        let trusted_keys = vec![make_public_key(&wrong_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 root_key = make_keypair();
340        let manifest = make_ota_manifest();
341        let mut bytes = generate(manifest, &manifest_key, &root_key).unwrap();
342
343        // Corrupt one byte of the manifest payload (which starts after the header)
344        let payload_start = std::mem::size_of::<Header>();
345        bytes[payload_start] ^= 0xFF;
346
347        let trusted_keys = vec![make_public_key(&root_key)];
348
349        let err = parse_and_verify(&bytes, &trusted_keys).unwrap_err();
350        assert_matches!(err, SignedManifestError::ManifestSignatureVerificationFailed);
351    }
352}