1 // 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
17 mod descriptor;
18
19 use avb_bindgen::{
20 avb_footer_validate_and_byteswap, avb_vbmeta_image_header_to_host_byte_order,
21 avb_vbmeta_image_verify, AvbAlgorithmType, AvbFooter, AvbVBMetaImageHeader,
22 AvbVBMetaVerifyResult,
23 };
24 use std::fs::File;
25 use std::io::{self, Read, Seek, SeekFrom};
26 use std::mem::{size_of, transmute, MaybeUninit};
27 use std::path::Path;
28 use std::ptr::null_mut;
29 use thiserror::Error;
30
31 pub use crate::descriptor::{Descriptor, Descriptors};
32
33 /// Errors from parsing a VBMeta image.
34 #[derive(Debug, Error)]
35 pub enum VbMetaImageParseError {
36 /// There was an IO error.
37 #[error("IO error")]
38 Io(#[from] io::Error),
39 /// The image footer was invalid.
40 #[error("Invalid footer")]
41 InvalidFooter,
42 /// The image header was invalid.
43 #[error("Invalid header")]
44 InvalidHeader,
45 /// The image version is not supported.
46 #[error("Unsupported version")]
47 UnsupportedVersion,
48 /// There was an invalid descriptor in the image.
49 #[error("Invalid descriptor ")]
50 InvalidDescriptor,
51 }
52
53 /// Errors from verifying a VBMeta image.
54 #[derive(Debug, Error)]
55 pub enum VbMetaImageVerificationError {
56 /// There was an error parsing the VBMeta image.
57 #[error("Cannot parse VBMeta image")]
58 ParseError(#[from] VbMetaImageParseError),
59 /// The VBMeta image hash did not validate.
60 #[error("Hash mismatch")]
61 HashMismatch,
62 /// The VBMeta image signature did not validate.
63 #[error("Signature mismatch")]
64 SignatureMismatch,
65 }
66
67 /// A VBMeta Image.
68 pub struct VbMetaImage {
69 header: AvbVBMetaImageHeader,
70 data: Box<[u8]>,
71 }
72
73 impl VbMetaImage {
74 /// Load and verify a VBMeta image from the given path.
verify_path<P: AsRef<Path>>(path: P) -> Result<Self, VbMetaImageVerificationError>75 pub fn verify_path<P: AsRef<Path>>(path: P) -> Result<Self, VbMetaImageVerificationError> {
76 let file = File::open(path).map_err(VbMetaImageParseError::Io)?;
77 let size = file.metadata().map_err(VbMetaImageParseError::Io)?.len();
78 Self::verify_reader_region(file, 0, size)
79 }
80
81 /// Load and verify a VBMeta image from a region within a reader.
verify_reader_region<R: Read + Seek>( mut image: R, offset: u64, size: u64, ) -> Result<Self, VbMetaImageVerificationError>82 pub fn verify_reader_region<R: Read + Seek>(
83 mut image: R,
84 offset: u64,
85 size: u64,
86 ) -> Result<Self, VbMetaImageVerificationError> {
87 // Check for a footer in the image or assume it's an entire VBMeta image.
88 image.seek(SeekFrom::Start(offset + size)).map_err(VbMetaImageParseError::Io)?;
89 let (vbmeta_offset, vbmeta_size) = match read_avb_footer(&mut image) {
90 Ok(footer) => {
91 if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
92 return Err(VbMetaImageParseError::InvalidFooter.into());
93 }
94 (footer.vbmeta_offset, footer.vbmeta_size)
95 }
96 Err(VbMetaImageParseError::InvalidFooter) => (0, size),
97 Err(e) => {
98 return Err(e.into());
99 }
100 };
101 image.seek(SeekFrom::Start(offset + vbmeta_offset)).map_err(VbMetaImageParseError::Io)?;
102 // Verify the image before examining it to check the size.
103 let mut data = vec![0u8; vbmeta_size as usize];
104 image.read_exact(&mut data).map_err(VbMetaImageParseError::Io)?;
105 verify_vbmeta_image(&data)?;
106 // SAFETY: the image has been verified so we know there is a valid header at the start.
107 let header = unsafe {
108 let mut header = MaybeUninit::uninit();
109 let src = data.as_ptr() as *const _ as *const AvbVBMetaImageHeader;
110 avb_vbmeta_image_header_to_host_byte_order(src, header.as_mut_ptr());
111 header.assume_init()
112 };
113 // Calculate the true size of the verified image data.
114 let vbmeta_size = (size_of::<AvbVBMetaImageHeader>() as u64)
115 + header.authentication_data_block_size
116 + header.auxiliary_data_block_size;
117 data.truncate(vbmeta_size as usize);
118 Ok(Self { header, data: data.into_boxed_slice() })
119 }
120
121 /// Get the public key that verified the VBMeta image. If the image was not signed, there
122 /// is no such public key.
public_key(&self) -> Option<&[u8]>123 pub fn public_key(&self) -> Option<&[u8]> {
124 if self.header.algorithm_type == AvbAlgorithmType::AVB_ALGORITHM_TYPE_NONE as u32 {
125 return None;
126 }
127 let begin = size_of::<AvbVBMetaImageHeader>()
128 + self.header.authentication_data_block_size as usize
129 + self.header.public_key_offset as usize;
130 let end = begin + self.header.public_key_size as usize;
131 Some(&self.data[begin..end])
132 }
133
134 /// Get the hash of the verified data in the VBMeta image from the authentication block. If the
135 /// image was not signed, there might not be a hash and, if there is, it's not known to be
136 /// correct.
hash(&self) -> Option<&[u8]>137 pub fn hash(&self) -> Option<&[u8]> {
138 if self.header.algorithm_type == AvbAlgorithmType::AVB_ALGORITHM_TYPE_NONE as u32 {
139 return None;
140 }
141 let begin = size_of::<AvbVBMetaImageHeader>() + self.header.hash_offset as usize;
142 let end = begin + self.header.hash_size as usize;
143 Some(&self.data[begin..end])
144 }
145
146 /// Get the descriptors of the VBMeta image.
descriptors(&self) -> Result<Descriptors<'_>, VbMetaImageParseError>147 pub fn descriptors(&self) -> Result<Descriptors<'_>, VbMetaImageParseError> {
148 Descriptors::from_image(&self.data)
149 }
150
151 /// Returns the rollback_index of the VBMeta image.
rollback_index(&self) -> u64152 pub fn rollback_index(&self) -> u64 {
153 self.header.rollback_index
154 }
155
156 /// Get the raw VBMeta image.
data(&self) -> &[u8]157 pub fn data(&self) -> &[u8] {
158 &self.data
159 }
160 }
161
162 /// Verify the data as a VBMeta image, translating errors that arise.
verify_vbmeta_image(data: &[u8]) -> Result<(), VbMetaImageVerificationError>163 fn verify_vbmeta_image(data: &[u8]) -> Result<(), VbMetaImageVerificationError> {
164 // SAFETY: the function only reads from the provided data and the NULL pointers disable the
165 // output arguments.
166 let res = unsafe { avb_vbmeta_image_verify(data.as_ptr(), data.len(), null_mut(), null_mut()) };
167 match res {
168 AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK
169 | AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => Ok(()),
170 AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => {
171 Err(VbMetaImageParseError::InvalidHeader.into())
172 }
173 AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => {
174 Err(VbMetaImageParseError::UnsupportedVersion.into())
175 }
176 AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => {
177 Err(VbMetaImageVerificationError::HashMismatch)
178 }
179 AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => {
180 Err(VbMetaImageVerificationError::SignatureMismatch)
181 }
182 }
183 }
184
185 /// Read the AVB footer, if present, given a reader that's positioned at the end of the image.
read_avb_footer<R: Read + Seek>(image: &mut R) -> Result<AvbFooter, VbMetaImageParseError>186 fn read_avb_footer<R: Read + Seek>(image: &mut R) -> Result<AvbFooter, VbMetaImageParseError> {
187 image.seek(SeekFrom::Current(-(size_of::<AvbFooter>() as i64)))?;
188 let mut raw_footer = [0u8; size_of::<AvbFooter>()];
189 image.read_exact(&mut raw_footer)?;
190 // SAFETY: the slice is the same size as the struct which only contains simple data types.
191 let mut footer = unsafe { transmute::<[u8; size_of::<AvbFooter>()], AvbFooter>(raw_footer) };
192 // SAFETY: the function updates the struct in-place.
193 if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } {
194 Ok(footer)
195 } else {
196 Err(VbMetaImageParseError::InvalidFooter)
197 }
198 }
199
200 #[cfg(test)]
201 mod tests {
202 use super::*;
203 use anyhow::{Context, Result};
204 use std::fs::{self, OpenOptions};
205 use std::os::unix::fs::FileExt;
206 use std::process::Command;
207 use tempfile::TempDir;
208
209 #[test]
unsigned_image_does_not_have_public_key() -> Result<()>210 fn unsigned_image_does_not_have_public_key() -> Result<()> {
211 let test_dir = TempDir::new().unwrap();
212 let test_file = test_dir.path().join("test.img");
213 let mut cmd = Command::new("./avbtool");
214 cmd.args([
215 "make_vbmeta_image",
216 "--output",
217 test_file.to_str().unwrap(),
218 "--algorithm",
219 "NONE",
220 ]);
221 let status = cmd.status().context("make_vbmeta_image")?;
222 assert!(status.success());
223 let vbmeta = VbMetaImage::verify_path(test_file).context("verify_path")?;
224 assert!(vbmeta.public_key().is_none());
225 Ok(())
226 }
227
signed_image_has_valid_vbmeta(algorithm: &str, key: &str) -> Result<()>228 fn signed_image_has_valid_vbmeta(algorithm: &str, key: &str) -> Result<()> {
229 let test_dir = TempDir::new().unwrap();
230 let test_file = test_dir.path().join("test.img");
231 let mut cmd = Command::new("./avbtool");
232 cmd.args([
233 "make_vbmeta_image",
234 "--output",
235 test_file.to_str().unwrap(),
236 "--algorithm",
237 algorithm,
238 "--key",
239 key,
240 ]);
241 let status = cmd.status().context("make_vbmeta_image")?;
242 assert!(status.success());
243 let vbmeta = VbMetaImage::verify_path(&test_file).context("verify_path")?;
244
245 // The image should contain the public part of the key pair.
246 let pubkey = vbmeta.public_key().unwrap();
247 let test_pubkey_file = test_dir.path().join("test.pubkey");
248 let mut cmd = Command::new("./avbtool");
249 cmd.args([
250 "extract_public_key",
251 "--key",
252 key,
253 "--output",
254 test_pubkey_file.to_str().unwrap(),
255 ]);
256 let status = cmd.status().context("extract_public_key")?;
257 assert!(status.success());
258 assert_eq!(pubkey, fs::read(test_pubkey_file).context("read public key")?);
259
260 // Flip a byte to make verification fail.
261 let file = OpenOptions::new()
262 .read(true)
263 .write(true)
264 .open(&test_file)
265 .context("open image to flip byte")?;
266 let mut data = [0; 1];
267 file.read_exact_at(&mut data, 81).context("read byte from image to flip")?;
268 data[0] = !data[0];
269 file.write_all_at(&data, 81).context("write flipped byte to image")?;
270 assert!(matches!(
271 VbMetaImage::verify_path(test_file),
272 Err(VbMetaImageVerificationError::HashMismatch)
273 ));
274 Ok(())
275 }
276
277 #[test]
test_rsa2048_signed_image() -> Result<()>278 fn test_rsa2048_signed_image() -> Result<()> {
279 signed_image_has_valid_vbmeta("SHA256_RSA2048", "data/testkey_rsa2048.pem")
280 }
281
282 #[test]
test_rsa4096_signed_image() -> Result<()>283 fn test_rsa4096_signed_image() -> Result<()> {
284 signed_image_has_valid_vbmeta("SHA256_RSA4096", "data/testkey_rsa4096.pem")
285 }
286
287 #[test]
test_rsa8192_signed_image() -> Result<()>288 fn test_rsa8192_signed_image() -> Result<()> {
289 signed_image_has_valid_vbmeta("SHA256_RSA8192", "data/testkey_rsa8192.pem")
290 }
291
292 #[test]
test_rollback_index() -> Result<()>293 fn test_rollback_index() -> Result<()> {
294 let vbmeta = VbMetaImage::verify_path("test_microdroid_vendor_image.img")?;
295 assert_eq!(5, vbmeta.rollback_index());
296 Ok(())
297 }
298
299 #[test]
test_rollback_index_default_zero() -> Result<()>300 fn test_rollback_index_default_zero() -> Result<()> {
301 let vbmeta =
302 VbMetaImage::verify_path("test_microdroid_vendor_image_no_rollback_index.img")?;
303 assert_eq!(0, vbmeta.rollback_index());
304 Ok(())
305 }
306 }
307