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