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