1 // Copyright 2021, 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 //! Routines for handling APEX payload
16 
17 use apex_manifest::apex_manifest::ApexManifest;
18 use protobuf::Message;
19 use std::fs::File;
20 use std::io::{self, Read};
21 use thiserror::Error;
22 use vbmeta::VbMetaImage;
23 use zip::result::ZipError;
24 use zip::ZipArchive;
25 
26 const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
27 const APEX_PAYLOAD_ENTRY: &str = "apex_payload.img";
28 const APEX_MANIFEST_ENTRY: &str = "apex_manifest.pb";
29 
30 /// Errors from parsing an APEX.
31 #[derive(Debug, Error)]
32 pub enum ApexParseError {
33     /// There was an IO error.
34     #[error("IO error: {0}")]
35     Io(#[from] io::Error),
36     /// The Zip archive was invalid.
37     #[error("Cannot read zip archive: {0}")]
38     InvalidZip(&'static str),
39     /// An expected file was missing from the APEX.
40     #[error("APEX doesn't contain {0}")]
41     MissingFile(&'static str),
42     /// There was no hashtree descriptor in the APEX payload's VBMeta image.
43     #[error("Non-hashtree descriptor found in payload's VBMeta image")]
44     DescriptorNotHashtree,
45     /// There was an error parsing the APEX payload's VBMeta image.
46     #[error("Could not parse payload's VBMeta image: {0}")]
47     PayloadVbmetaError(#[from] vbmeta::VbMetaImageParseError),
48     /// Data was missing from the VBMeta
49     #[error("Data missing from VBMeta: {0}")]
50     VbmetaMissingData(&'static str),
51     /// An error occurred parsing the APEX manifest as a protobuf
52     #[error("Error parsing manifest protobuf: {0}")]
53     ManifestProtobufError(#[from] protobuf::Error),
54 }
55 
56 /// Errors from verifying an APEX.
57 #[derive(Debug, Error)]
58 pub enum ApexVerificationError {
59     /// There was an error parsing the APEX.
60     #[error("Cannot parse APEX file")]
61     ParseError(#[from] ApexParseError),
62     /// There was an error validating the APEX payload's VBMeta image.
63     #[error("Could not parse payload's VBMeta image")]
64     PayloadVbmetaError(#[from] vbmeta::VbMetaImageVerificationError),
65     /// The APEX payload was not verified with the apex_pubkey.
66     #[error("APEX pubkey mismatch")]
67     ApexPubkeyMismatch,
68 }
69 
70 /// Information extracted from the APEX during AVB verification.
71 #[derive(Debug)]
72 pub struct ApexVerificationResult {
73     /// The name of the APEX, from its manifest.
74     pub name: Option<String>,
75     /// The version of the APEX, from its manifest.
76     pub version: Option<i64>,
77     /// The public key that verifies the payload signature.
78     pub public_key: Vec<u8>,
79     /// The root digest of the payload hashtree.
80     pub root_digest: Vec<u8>,
81 }
82 
83 /// Verify APEX payload by AVB verification and return information about the APEX.
84 /// This verifies that the VBMeta is correctly signed by the public key specified in the APEX.
85 /// It doesn't verify that that is the correct key, nor does it verify that the payload matches
86 /// the signed root hash - that is handled by dm-verity once apexd has mounted the APEX.
verify(path: &str) -> Result<ApexVerificationResult, ApexVerificationError>87 pub fn verify(path: &str) -> Result<ApexVerificationResult, ApexVerificationError> {
88     let apex_file = File::open(path).map_err(ApexParseError::Io)?;
89     let ApexZipInfo { public_key, image_offset, image_size, manifest } =
90         get_apex_zip_info(&apex_file)?;
91     let vbmeta = VbMetaImage::verify_reader_region(apex_file, image_offset, image_size)?;
92     let root_digest = find_root_digest(&vbmeta)?;
93     let vbmeta_public_key =
94         vbmeta.public_key().ok_or(ApexParseError::VbmetaMissingData("public key"))?;
95     if vbmeta_public_key != public_key {
96         return Err(ApexVerificationError::ApexPubkeyMismatch);
97     }
98     let (name, version) = if cfg!(dice_changes) {
99         let ApexManifestInfo { name, version } = decode_manifest(&manifest)?;
100         (Some(name), Some(version))
101     } else {
102         (None, None)
103     };
104     Ok(ApexVerificationResult { name, version, public_key, root_digest })
105 }
106 
find_root_digest(vbmeta: &VbMetaImage) -> Result<Vec<u8>, ApexParseError>107 fn find_root_digest(vbmeta: &VbMetaImage) -> Result<Vec<u8>, ApexParseError> {
108     // APEXs use the root digest from the first hashtree descriptor to describe the payload.
109     for descriptor in vbmeta.descriptors()?.iter() {
110         if let vbmeta::Descriptor::Hashtree(_) = descriptor {
111             return Ok(descriptor.to_hashtree()?.root_digest().to_vec());
112         }
113     }
114     Err(ApexParseError::DescriptorNotHashtree)
115 }
116 
117 struct ApexZipInfo {
118     public_key: Vec<u8>,
119     image_offset: u64,
120     image_size: u64,
121     manifest: Vec<u8>,
122 }
123 
get_apex_zip_info(apex_file: &File) -> Result<ApexZipInfo, ApexParseError>124 fn get_apex_zip_info(apex_file: &File) -> Result<ApexZipInfo, ApexParseError> {
125     let mut z = ZipArchive::new(apex_file).map_err(|err| from_zip_error(err, "?"))?;
126 
127     let mut public_key = Vec::new();
128     z.by_name(APEX_PUBKEY_ENTRY)
129         .map_err(|err| from_zip_error(err, APEX_PUBKEY_ENTRY))?
130         .read_to_end(&mut public_key)?;
131 
132     let (image_offset, image_size) = z
133         .by_name(APEX_PAYLOAD_ENTRY)
134         .map(|f| (f.data_start(), f.size()))
135         .map_err(|err| from_zip_error(err, APEX_PAYLOAD_ENTRY))?;
136 
137     let mut manifest = Vec::new();
138     z.by_name(APEX_MANIFEST_ENTRY)
139         .map_err(|err| from_zip_error(err, APEX_MANIFEST_ENTRY))?
140         .read_to_end(&mut manifest)?;
141 
142     Ok(ApexZipInfo { public_key, image_offset, image_size, manifest })
143 }
144 
145 struct ApexManifestInfo {
146     name: String,
147     version: i64,
148 }
149 
decode_manifest(mut manifest: &[u8]) -> Result<ApexManifestInfo, ApexParseError>150 fn decode_manifest(mut manifest: &[u8]) -> Result<ApexManifestInfo, ApexParseError> {
151     let manifest = ApexManifest::parse_from_reader(&mut manifest)?;
152     Ok(ApexManifestInfo { name: manifest.name, version: manifest.version })
153 }
154 
from_zip_error(err: ZipError, name: &'static str) -> ApexParseError155 fn from_zip_error(err: ZipError, name: &'static str) -> ApexParseError {
156     match err {
157         ZipError::Io(err) => ApexParseError::Io(err),
158         ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
159             ApexParseError::InvalidZip(s)
160         }
161         ZipError::FileNotFound => ApexParseError::MissingFile(name),
162     }
163 }
164 
165 #[cfg(test)]
166 mod tests {
167     use super::*;
168 
169     #[test]
apex_verification_returns_valid_result()170     fn apex_verification_returns_valid_result() {
171         let res = verify("apex.apexd_test.apex").unwrap();
172         let (expected_name, expected_version) = if cfg!(dice_changes) {
173             (Some("com.android.apex.test_package"), Some(1))
174         } else {
175             (None, None)
176         };
177         assert_eq!(res.name.as_deref(), expected_name);
178         assert_eq!(res.version, expected_version);
179         // The expected hex values were generated when we ran the method the first time.
180         assert_eq!(
181             hex::encode(res.root_digest),
182             "54265da77ae1fd619e39809ad99fedc576bb20c0c7a8002190fa64438436299f"
183         );
184         assert_eq!(
185             hex::encode(res.public_key),
186             "\
187             00001000963a5527aaf0145b3bb5f899a05034ccc76dafdd671dbf4e42c04df2eeba15\
188             6c884816d7d08ef8d834d4adc27979afed9eaf406694d0d600f0b6d31e3ab85da47d27\
189             9c223a1630e02332d920587617ea766a136057a3a3232a7c42f83fb3763e853be4026c\
190             067524a95fcbfcc6caadfb553210bb5385f5adc5caeb0e3f6a9aa56af88d8899d962eb\
191             807864feabeeacdd868697935fb4cc4843957e0d90ee4293c715c4e5b970e6545a17d1\
192             735f814c7d4dbdeaac97275a84f292e3715c158d38eb00eebd010dd2fa56595c0e5627\
193             06c7a94e566912f993e5e35c04b2a314d1bce1ceb10de6c50f8101ddb6ee993fc79959\
194             2e79ee73b77741ee5c076c89343684344a6d080e5529a046d506d104bf32903e39c363\
195             b020fee9d87e7c6ffdad120b630386e958416ac156bc2d7301836c79e926e8f185a640\
196             be05135e17018c88dde02cd7bd49655e9e9dff7f965fb8e68217236c18d23b6d7e7632\
197             184acb95b088598601c809d5e66c19f5e06b5e5ff1bbae7e3142959d9380db2d4a25c8\
198             757975232ea311016e830703a6023b0986e885f2eda066517fce09f33f359b6ef7cc5a\
199             2fdaced74257661bad184a653ea2d80d1af68de5821c06a472635f0276dc42d699f588\
200             ea6c46189ca1ad544bbd4951a766bc4119b0ea671cb16556762721723bf1db47c83c76\
201             a7cc2fd3b6029efec9908d9d4640294f6ea46f6e1a3195e9252c393e35698911a7c496\
202             138dc2dd8d9dcb470ae1c6d2224d13b160fb3ae4bc235f6133c2ff5f9232fb89adfdba\
203             48dcc47cf29a22cd47dcec0b1a179f352c9848a8e04ac37f35777a24312c821febc591\
204             84c8cdefc88e50b4d6bc9530ca743f4284c9773677d38527e6e8020fe367f0f16a6c49\
205             9a7f2da95ec6471f7382e5c0da98b531702cb55a560de7cafc7b6111aae0f896fb1fed\
206             d4997a954c6c083ef1fd3bb13fef3f95022523fb1fbe7f4a49e12e54a5206f95daa316\
207             ac009b7bee4039f769fd28033db6013df841c86d8345d44418fbc9f669e4ee3294b2ff\
208             29d048f53d768c0a41f9a280f0229d9912e8b2fb734617a9947be973ed1dc7bdeac9e2\
209             6028d59317098a44bacdb3b10ccde6ef02f7c94124461032a033701ce523b13142658c\
210             265385198903ccf227ad5ae88ec31e586cd8f855641fd2646dba8053d0d0924f132505\
211             8141f1c7433aa9686f48e3f3a972b56776eaf8bf22a740d1aea2ef473184d697de1dab\
212             9b62a227611c7500b11dea2e5eb8051807c0d1f2fe032acfd7701c017e629f99c74de5\
213             da4c2a542f17b9833beb14442aa7c2990b828473376ea03fdb4a650b88e821fe5026e8\
214             ffb7002d095c9877ee3a98a4488ed3287e9be4942a223f4e32bc26c2ebd02eec20dc82\
215             7493b44f4efaf9b2e175d4de2b07c32d6d359e234c9e50ef905ffa7f6907c313a3c9f4\
216             40d1efd5ec7cbeef06dcfd649f4c8219ad"
217         );
218     }
219 
220     #[test]
apex_no_manifest_fails_verification()221     fn apex_no_manifest_fails_verification() {
222         match verify("apex.apexd_test_v2_no_pb.apex").unwrap_err() {
223             ApexVerificationError::ParseError(ApexParseError::MissingFile(_)) => (),
224             e => panic!("Unexpected error {e}"),
225         }
226     }
227 
228     #[test]
apex_signature_mismatch_fails_verification()229     fn apex_signature_mismatch_fails_verification() {
230         match verify("apex.apexd_test_wrong_public_key.apex").unwrap_err() {
231             ApexVerificationError::ApexPubkeyMismatch => (),
232             e => panic!("Unexpected error {e}"),
233         }
234     }
235 }
236