blob: c273973605b6b0c61157203b6683f7cea439fb39 [file] [log] [blame]
Andrew Scull38127252022-06-13 13:11:00 +00001// Copyright 2022, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A library to verify and parse VBMeta images.
16
17mod descriptor;
18
19use avb_bindgen::{
20 avb_footer_validate_and_byteswap, avb_vbmeta_image_header_to_host_byte_order,
21 avb_vbmeta_image_verify, AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE, AvbFooter,
22 AvbVBMetaImageHeader, AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
23 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
24 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK,
25 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
26 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
27 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION,
28};
29use std::fs::File;
30use std::io::{self, Read, Seek, SeekFrom};
Alice Wang76ad4ca2022-12-13 12:09:58 +000031use std::mem::{size_of, transmute, MaybeUninit};
Andrew Scull38127252022-06-13 13:11:00 +000032use std::os::raw::c_uint;
33use std::path::Path;
34use std::ptr::null_mut;
Andrew Scull38127252022-06-13 13:11:00 +000035use thiserror::Error;
36
37pub use crate::descriptor::{Descriptor, Descriptors};
38
39/// Errors from parsing a VBMeta image.
40#[derive(Debug, Error)]
41pub enum VbMetaImageParseError {
42 /// There was an IO error.
43 #[error("IO error")]
44 Io(#[from] io::Error),
45 /// The image footer was invalid.
46 #[error("Invalid footer")]
47 InvalidFooter,
48 /// The image header was invalid.
49 #[error("Invalid header")]
50 InvalidHeader,
51 /// The image version is not supported.
52 #[error("Unsupported version")]
53 UnsupportedVersion,
54 /// There was an invalid descriptor in the image.
55 #[error("Invalid descriptor ")]
56 InvalidDescriptor,
57}
58
59/// Errors from verifying a VBMeta image.
60#[derive(Debug, Error)]
61pub enum VbMetaImageVerificationError {
62 /// There was an error parsing the VBMeta image.
63 #[error("Cannot parse VBMeta image")]
64 ParseError(#[from] VbMetaImageParseError),
65 /// The VBMeta image hash did not validate.
66 #[error("Hash mismatch")]
67 HashMismatch,
68 /// The VBMeta image signature did not validate.
69 #[error("Signature mismatch")]
70 SignatureMismatch,
71 /// An unexpected libavb error code was returned.
72 #[error("Unknown libavb error: {0}")]
73 UnknownLibavbError(c_uint),
74}
75
76/// A VBMeta Image.
77pub struct VbMetaImage {
78 header: AvbVBMetaImageHeader,
79 data: Box<[u8]>,
80}
81
82impl VbMetaImage {
83 /// Load and verify a VBMeta image from the given path.
84 pub fn verify_path<P: AsRef<Path>>(path: P) -> Result<Self, VbMetaImageVerificationError> {
85 let file = File::open(path).map_err(VbMetaImageParseError::Io)?;
86 let size = file.metadata().map_err(VbMetaImageParseError::Io)?.len();
87 Self::verify_reader_region(file, 0, size)
88 }
89
90 /// Load and verify a VBMeta image from a region within a reader.
91 pub fn verify_reader_region<R: Read + Seek>(
92 mut image: R,
93 offset: u64,
94 size: u64,
95 ) -> Result<Self, VbMetaImageVerificationError> {
96 // Check for a footer in the image or assume it's an entire VBMeta image.
97 image.seek(SeekFrom::Start(offset + size)).map_err(VbMetaImageParseError::Io)?;
Alice Wang285c7582022-12-13 13:44:52 +000098 let (vbmeta_offset, vbmeta_size) = match read_avb_footer(&mut image) {
99 Ok(footer) => {
100 if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
101 return Err(VbMetaImageParseError::InvalidFooter.into());
102 }
103 (footer.vbmeta_offset, footer.vbmeta_size)
Andrew Scull38127252022-06-13 13:11:00 +0000104 }
Alice Wang285c7582022-12-13 13:44:52 +0000105 Err(VbMetaImageParseError::InvalidFooter) => (0, size),
106 Err(e) => {
107 return Err(e.into());
108 }
Andrew Scull38127252022-06-13 13:11:00 +0000109 };
110 image.seek(SeekFrom::Start(offset + vbmeta_offset)).map_err(VbMetaImageParseError::Io)?;
111 // Verify the image before examining it to check the size.
112 let mut data = vec![0u8; vbmeta_size as usize];
113 image.read_exact(&mut data).map_err(VbMetaImageParseError::Io)?;
114 verify_vbmeta_image(&data)?;
115 // SAFETY: the image has been verified so we know there is a valid header at the start.
116 let header = unsafe {
117 let mut header = MaybeUninit::uninit();
118 let src = data.as_ptr() as *const _ as *const AvbVBMetaImageHeader;
119 avb_vbmeta_image_header_to_host_byte_order(src, header.as_mut_ptr());
120 header.assume_init()
121 };
122 // Calculate the true size of the verified image data.
123 let vbmeta_size = (size_of::<AvbVBMetaImageHeader>() as u64)
124 + header.authentication_data_block_size
125 + header.auxiliary_data_block_size;
126 data.truncate(vbmeta_size as usize);
127 Ok(Self { header, data: data.into_boxed_slice() })
128 }
129
130 /// Get the public key that verified the VBMeta image. If the image was not signed, there
131 /// is no such public key.
132 pub fn public_key(&self) -> Option<&[u8]> {
133 if self.header.algorithm_type == AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE {
134 return None;
135 }
136 let begin = size_of::<AvbVBMetaImageHeader>()
137 + self.header.authentication_data_block_size as usize
138 + self.header.public_key_offset as usize;
139 let end = begin + self.header.public_key_size as usize;
140 Some(&self.data[begin..end])
141 }
142
Alice Wang3356d6d2022-07-18 08:32:10 +0000143 /// Get the hash of the verified data in the VBMeta image from the authentication block. If the
144 /// image was not signed, there might not be a hash and, if there is, it's not known to be
145 /// correct.
146 pub fn hash(&self) -> Option<&[u8]> {
147 if self.header.algorithm_type == AvbAlgorithmType_AVB_ALGORITHM_TYPE_NONE {
148 return None;
149 }
150 let begin = size_of::<AvbVBMetaImageHeader>() + self.header.hash_offset as usize;
151 let end = begin + self.header.hash_size as usize;
152 Some(&self.data[begin..end])
153 }
154
Andrew Scull38127252022-06-13 13:11:00 +0000155 /// Get the descriptors of the VBMeta image.
156 pub fn descriptors(&self) -> Result<Descriptors<'_>, VbMetaImageParseError> {
157 Descriptors::from_image(&self.data)
158 }
159
160 /// Get the raw VBMeta image.
161 pub fn data(&self) -> &[u8] {
162 &self.data
163 }
164}
165
166/// Verify the data as a VBMeta image, translating errors that arise.
167fn verify_vbmeta_image(data: &[u8]) -> Result<(), VbMetaImageVerificationError> {
168 // SAFETY: the function only reads from the provided data and the NULL pointers disable the
169 // output arguments.
170 let res = unsafe { avb_vbmeta_image_verify(data.as_ptr(), data.len(), null_mut(), null_mut()) };
171 #[allow(non_upper_case_globals)]
172 match res {
173 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK
174 | AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => Ok(()),
175 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => {
176 Err(VbMetaImageParseError::InvalidHeader.into())
177 }
178 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => {
179 Err(VbMetaImageParseError::UnsupportedVersion.into())
180 }
181 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => {
182 Err(VbMetaImageVerificationError::HashMismatch)
183 }
184 AvbVBMetaVerifyResult_AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => {
185 Err(VbMetaImageVerificationError::SignatureMismatch)
186 }
187 err => Err(VbMetaImageVerificationError::UnknownLibavbError(err)),
188 }
189}
190
191/// Read the AVB footer, if present, given a reader that's positioned at the end of the image.
Alice Wang285c7582022-12-13 13:44:52 +0000192fn read_avb_footer<R: Read + Seek>(image: &mut R) -> Result<AvbFooter, VbMetaImageParseError> {
Andrew Scull38127252022-06-13 13:11:00 +0000193 image.seek(SeekFrom::Current(-(size_of::<AvbFooter>() as i64)))?;
Alice Wang76ad4ca2022-12-13 12:09:58 +0000194 let mut raw_footer = [0u8; size_of::<AvbFooter>()];
195 image.read_exact(&mut raw_footer)?;
Andrew Scull38127252022-06-13 13:11:00 +0000196 // SAFETY: the slice is the same size as the struct which only contains simple data types.
Alice Wang76ad4ca2022-12-13 12:09:58 +0000197 let mut footer = unsafe { transmute::<[u8; size_of::<AvbFooter>()], AvbFooter>(raw_footer) };
Andrew Scull38127252022-06-13 13:11:00 +0000198 // SAFETY: the function updates the struct in-place.
199 if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } {
Alice Wang285c7582022-12-13 13:44:52 +0000200 Ok(footer)
Andrew Scull38127252022-06-13 13:11:00 +0000201 } else {
Alice Wang285c7582022-12-13 13:44:52 +0000202 Err(VbMetaImageParseError::InvalidFooter)
Andrew Scull38127252022-06-13 13:11:00 +0000203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use anyhow::{Context, Result};
210 use std::fs::{self, OpenOptions};
211 use std::os::unix::fs::FileExt;
212 use std::process::Command;
213 use tempfile::TempDir;
214
215 #[test]
Alice Wangc3483872022-12-13 12:05:40 +0000216 fn unsigned_image_does_not_have_public_key() -> Result<()> {
Andrew Scull38127252022-06-13 13:11:00 +0000217 let test_dir = TempDir::new().unwrap();
218 let test_file = test_dir.path().join("test.img");
219 let mut cmd = Command::new("./avbtool");
220 cmd.args([
221 "make_vbmeta_image",
222 "--output",
223 test_file.to_str().unwrap(),
224 "--algorithm",
225 "NONE",
226 ]);
227 let status = cmd.status().context("make_vbmeta_image")?;
228 assert!(status.success());
229 let vbmeta = VbMetaImage::verify_path(test_file).context("verify_path")?;
230 assert!(vbmeta.public_key().is_none());
231 Ok(())
232 }
233
Alice Wanga3cc9a02022-11-29 09:48:16 +0000234 fn signed_image_has_valid_vbmeta(algorithm: &str, key: &str) -> Result<()> {
Andrew Scull38127252022-06-13 13:11:00 +0000235 let test_dir = TempDir::new().unwrap();
236 let test_file = test_dir.path().join("test.img");
237 let mut cmd = Command::new("./avbtool");
238 cmd.args([
239 "make_vbmeta_image",
240 "--output",
241 test_file.to_str().unwrap(),
242 "--algorithm",
243 algorithm,
244 "--key",
245 key,
246 ]);
247 let status = cmd.status().context("make_vbmeta_image")?;
248 assert!(status.success());
249 let vbmeta = VbMetaImage::verify_path(&test_file).context("verify_path")?;
250
251 // The image should contain the public part of the key pair.
252 let pubkey = vbmeta.public_key().unwrap();
253 let test_pubkey_file = test_dir.path().join("test.pubkey");
254 let mut cmd = Command::new("./avbtool");
255 cmd.args([
256 "extract_public_key",
257 "--key",
258 key,
259 "--output",
260 test_pubkey_file.to_str().unwrap(),
261 ]);
262 let status = cmd.status().context("extract_public_key")?;
263 assert!(status.success());
264 assert_eq!(pubkey, fs::read(test_pubkey_file).context("read public key")?);
265
266 // Flip a byte to make verification fail.
267 let file = OpenOptions::new()
268 .read(true)
269 .write(true)
270 .open(&test_file)
271 .context("open image to flip byte")?;
272 let mut data = [0; 1];
273 file.read_exact_at(&mut data, 81).context("read byte from image to flip")?;
274 data[0] = !data[0];
275 file.write_all_at(&data, 81).context("write flipped byte to image")?;
276 assert!(matches!(
277 VbMetaImage::verify_path(test_file),
278 Err(VbMetaImageVerificationError::HashMismatch)
279 ));
280 Ok(())
281 }
282
283 #[test]
284 fn test_rsa2048_signed_image() -> Result<()> {
Alice Wanga3cc9a02022-11-29 09:48:16 +0000285 signed_image_has_valid_vbmeta("SHA256_RSA2048", "data/testkey_rsa2048.pem")
Andrew Scull38127252022-06-13 13:11:00 +0000286 }
287
288 #[test]
289 fn test_rsa4096_signed_image() -> Result<()> {
Alice Wanga3cc9a02022-11-29 09:48:16 +0000290 signed_image_has_valid_vbmeta("SHA256_RSA4096", "data/testkey_rsa4096.pem")
Andrew Scull38127252022-06-13 13:11:00 +0000291 }
292
293 #[test]
294 fn test_rsa8192_signed_image() -> Result<()> {
Alice Wanga3cc9a02022-11-29 09:48:16 +0000295 signed_image_has_valid_vbmeta("SHA256_RSA8192", "data/testkey_rsa8192.pem")
Andrew Scull38127252022-06-13 13:11:00 +0000296 }
297}