diff options
Diffstat (limited to 'verifier.cpp')
-rw-r--r-- | verifier.cpp | 329 |
1 files changed, 161 insertions, 168 deletions
diff --git a/verifier.cpp b/verifier.cpp index 44098f70..00e13aa7 100644 --- a/verifier.cpp +++ b/verifier.cpp | |||
@@ -21,6 +21,7 @@ | |||
21 | #include <stdlib.h> | 21 | #include <stdlib.h> |
22 | #include <string.h> | 22 | #include <string.h> |
23 | 23 | ||
24 | #include <functional> | ||
24 | #include <algorithm> | 25 | #include <algorithm> |
25 | #include <memory> | 26 | #include <memory> |
26 | 27 | ||
@@ -108,201 +109,193 @@ static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_d | |||
108 | return *sig_der != NULL; | 109 | return *sig_der != NULL; |
109 | } | 110 | } |
110 | 111 | ||
111 | // Look for an RSA signature embedded in the .ZIP file comment given | 112 | /* |
112 | // the path to the zip. Verify it matches one of the given public | 113 | * Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. Verifies |
113 | // keys. | 114 | * that it matches one of the given public keys. A callback function can be optionally provided for |
114 | // | 115 | * posting the progress. |
115 | // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered | 116 | * |
116 | // or no key matches the signature). | 117 | * Returns VERIFY_SUCCESS or VERIFY_FAILURE (if any error is encountered or no key matches the |
117 | 118 | * signature). | |
118 | int verify_file(unsigned char* addr, size_t length, | 119 | */ |
119 | const std::vector<Certificate>& keys) { | 120 | int verify_file(unsigned char* addr, size_t length, const std::vector<Certificate>& keys, |
120 | ui->SetProgress(0.0); | 121 | const std::function<void(float)>& set_progress) { |
121 | 122 | if (set_progress) { | |
122 | // An archive with a whole-file signature will end in six bytes: | 123 | set_progress(0.0); |
123 | // | 124 | } |
124 | // (2-byte signature start) $ff $ff (2-byte comment size) | 125 | |
125 | // | 126 | // An archive with a whole-file signature will end in six bytes: |
126 | // (As far as the ZIP format is concerned, these are part of the | 127 | // |
127 | // archive comment.) We start by reading this footer, this tells | 128 | // (2-byte signature start) $ff $ff (2-byte comment size) |
128 | // us how far back from the end we have to start reading to find | 129 | // |
129 | // the whole comment. | 130 | // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by |
131 | // reading this footer, this tells us how far back from the end we have to start reading to find | ||
132 | // the whole comment. | ||
130 | 133 | ||
131 | #define FOOTER_SIZE 6 | 134 | #define FOOTER_SIZE 6 |
132 | 135 | ||
133 | if (length < FOOTER_SIZE) { | 136 | if (length < FOOTER_SIZE) { |
134 | LOG(ERROR) << "not big enough to contain footer"; | 137 | LOG(ERROR) << "not big enough to contain footer"; |
135 | return VERIFY_FAILURE; | 138 | return VERIFY_FAILURE; |
136 | } | 139 | } |
137 | 140 | ||
138 | unsigned char* footer = addr + length - FOOTER_SIZE; | 141 | unsigned char* footer = addr + length - FOOTER_SIZE; |
139 | 142 | ||
140 | if (footer[2] != 0xff || footer[3] != 0xff) { | 143 | if (footer[2] != 0xff || footer[3] != 0xff) { |
141 | LOG(ERROR) << "footer is wrong"; | 144 | LOG(ERROR) << "footer is wrong"; |
142 | return VERIFY_FAILURE; | 145 | return VERIFY_FAILURE; |
143 | } | 146 | } |
144 | 147 | ||
145 | size_t comment_size = footer[4] + (footer[5] << 8); | 148 | size_t comment_size = footer[4] + (footer[5] << 8); |
146 | size_t signature_start = footer[0] + (footer[1] << 8); | 149 | size_t signature_start = footer[0] + (footer[1] << 8); |
147 | LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start | 150 | LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start |
148 | << " bytes from end"; | 151 | << " bytes from end"; |
149 | 152 | ||
150 | if (signature_start <= FOOTER_SIZE) { | 153 | if (signature_start <= FOOTER_SIZE) { |
151 | LOG(ERROR) << "Signature start is in the footer"; | 154 | LOG(ERROR) << "Signature start is in the footer"; |
152 | return VERIFY_FAILURE; | 155 | return VERIFY_FAILURE; |
153 | } | 156 | } |
154 | 157 | ||
155 | #define EOCD_HEADER_SIZE 22 | 158 | #define EOCD_HEADER_SIZE 22 |
156 | 159 | ||
157 | // The end-of-central-directory record is 22 bytes plus any | 160 | // The end-of-central-directory record is 22 bytes plus any comment length. |
158 | // comment length. | 161 | size_t eocd_size = comment_size + EOCD_HEADER_SIZE; |
159 | size_t eocd_size = comment_size + EOCD_HEADER_SIZE; | ||
160 | 162 | ||
161 | if (length < eocd_size) { | 163 | if (length < eocd_size) { |
162 | LOG(ERROR) << "not big enough to contain EOCD"; | 164 | LOG(ERROR) << "not big enough to contain EOCD"; |
163 | return VERIFY_FAILURE; | 165 | return VERIFY_FAILURE; |
164 | } | 166 | } |
165 | 167 | ||
166 | // Determine how much of the file is covered by the signature. | 168 | // Determine how much of the file is covered by the signature. This is everything except the |
167 | // This is everything except the signature data and length, which | 169 | // signature data and length, which includes all of the EOCD except for the comment length field |
168 | // includes all of the EOCD except for the comment length field (2 | 170 | // (2 bytes) and the comment data. |
169 | // bytes) and the comment data. | 171 | size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2; |
170 | size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2; | ||
171 | 172 | ||
172 | unsigned char* eocd = addr + length - eocd_size; | 173 | unsigned char* eocd = addr + length - eocd_size; |
173 | 174 | ||
174 | // If this is really is the EOCD record, it will begin with the | 175 | // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06. |
175 | // magic number $50 $4b $05 $06. | 176 | if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) { |
176 | if (eocd[0] != 0x50 || eocd[1] != 0x4b || | 177 | LOG(ERROR) << "signature length doesn't match EOCD marker"; |
177 | eocd[2] != 0x05 || eocd[3] != 0x06) { | 178 | return VERIFY_FAILURE; |
178 | LOG(ERROR) << "signature length doesn't match EOCD marker"; | 179 | } |
179 | return VERIFY_FAILURE; | ||
180 | } | ||
181 | 180 | ||
182 | for (size_t i = 4; i < eocd_size-3; ++i) { | 181 | for (size_t i = 4; i < eocd_size-3; ++i) { |
183 | if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && | 182 | if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { |
184 | eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { | 183 | // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will |
185 | // if the sequence $50 $4b $05 $06 appears anywhere after | 184 | // find the later (wrong) one, which could be exploitable. Fail the verification if this |
186 | // the real one, libziparchive will find the later (wrong) one, | 185 | // sequence occurs anywhere after the real one. |
187 | // which could be exploitable. Fail verification if | 186 | LOG(ERROR) << "EOCD marker occurs after start of EOCD"; |
188 | // this sequence occurs anywhere after the real one. | 187 | return VERIFY_FAILURE; |
189 | LOG(ERROR) << "EOCD marker occurs after start of EOCD"; | ||
190 | return VERIFY_FAILURE; | ||
191 | } | ||
192 | } | 188 | } |
189 | } | ||
193 | 190 | ||
194 | bool need_sha1 = false; | 191 | bool need_sha1 = false; |
195 | bool need_sha256 = false; | 192 | bool need_sha256 = false; |
196 | for (const auto& key : keys) { | 193 | for (const auto& key : keys) { |
197 | switch (key.hash_len) { | 194 | switch (key.hash_len) { |
198 | case SHA_DIGEST_LENGTH: need_sha1 = true; break; | 195 | case SHA_DIGEST_LENGTH: need_sha1 = true; break; |
199 | case SHA256_DIGEST_LENGTH: need_sha256 = true; break; | 196 | case SHA256_DIGEST_LENGTH: need_sha256 = true; break; |
200 | } | ||
201 | } | 197 | } |
198 | } | ||
202 | 199 | ||
203 | SHA_CTX sha1_ctx; | 200 | SHA_CTX sha1_ctx; |
204 | SHA256_CTX sha256_ctx; | 201 | SHA256_CTX sha256_ctx; |
205 | SHA1_Init(&sha1_ctx); | 202 | SHA1_Init(&sha1_ctx); |
206 | SHA256_Init(&sha256_ctx); | 203 | SHA256_Init(&sha256_ctx); |
207 | 204 | ||
208 | double frac = -1.0; | 205 | double frac = -1.0; |
209 | size_t so_far = 0; | 206 | size_t so_far = 0; |
210 | while (so_far < signed_len) { | 207 | while (so_far < signed_len) { |
211 | // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a | 208 | // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a |
212 | // 1196MiB full OTA and 60% for an 89MiB incremental OTA. | 209 | // 1196MiB full OTA and 60% for an 89MiB incremental OTA. |
213 | // http://b/28135231. | 210 | // http://b/28135231. |
214 | size_t size = std::min(signed_len - so_far, 16 * MiB); | 211 | size_t size = std::min(signed_len - so_far, 16 * MiB); |
215 | 212 | ||
216 | if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); | 213 | if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); |
217 | if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); | 214 | if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); |
218 | so_far += size; | 215 | so_far += size; |
219 | 216 | ||
220 | double f = so_far / (double)signed_len; | 217 | if (set_progress) { |
221 | if (f > frac + 0.02 || size == so_far) { | 218 | double f = so_far / (double)signed_len; |
222 | ui->SetProgress(f); | 219 | if (f > frac + 0.02 || size == so_far) { |
223 | frac = f; | 220 | set_progress(f); |
224 | } | 221 | frac = f; |
222 | } | ||
225 | } | 223 | } |
224 | } | ||
226 | 225 | ||
227 | uint8_t sha1[SHA_DIGEST_LENGTH]; | 226 | uint8_t sha1[SHA_DIGEST_LENGTH]; |
228 | SHA1_Final(sha1, &sha1_ctx); | 227 | SHA1_Final(sha1, &sha1_ctx); |
229 | uint8_t sha256[SHA256_DIGEST_LENGTH]; | 228 | uint8_t sha256[SHA256_DIGEST_LENGTH]; |
230 | SHA256_Final(sha256, &sha256_ctx); | 229 | SHA256_Final(sha256, &sha256_ctx); |
231 | |||
232 | uint8_t* sig_der = nullptr; | ||
233 | size_t sig_der_length = 0; | ||
234 | |||
235 | uint8_t* signature = eocd + eocd_size - signature_start; | ||
236 | size_t signature_size = signature_start - FOOTER_SIZE; | ||
237 | |||
238 | LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) << ", length: " | ||
239 | << signature_size << "): " << print_hex(signature, signature_size); | ||
240 | 230 | ||
241 | if (!read_pkcs7(signature, signature_size, &sig_der, &sig_der_length)) { | 231 | uint8_t* sig_der = nullptr; |
242 | LOG(ERROR) << "Could not find signature DER block"; | 232 | size_t sig_der_length = 0; |
243 | return VERIFY_FAILURE; | ||
244 | } | ||
245 | 233 | ||
246 | /* | 234 | uint8_t* signature = eocd + eocd_size - signature_start; |
247 | * Check to make sure at least one of the keys matches the signature. Since | 235 | size_t signature_size = signature_start - FOOTER_SIZE; |
248 | * any key can match, we need to try each before determining a verification | ||
249 | * failure has happened. | ||
250 | */ | ||
251 | size_t i = 0; | ||
252 | for (const auto& key : keys) { | ||
253 | const uint8_t* hash; | ||
254 | int hash_nid; | ||
255 | switch (key.hash_len) { | ||
256 | case SHA_DIGEST_LENGTH: | ||
257 | hash = sha1; | ||
258 | hash_nid = NID_sha1; | ||
259 | break; | ||
260 | case SHA256_DIGEST_LENGTH: | ||
261 | hash = sha256; | ||
262 | hash_nid = NID_sha256; | ||
263 | break; | ||
264 | default: | ||
265 | continue; | ||
266 | } | ||
267 | 236 | ||
268 | // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that | 237 | LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) << ", length: " |
269 | // the signing tool appends after the signature itself. | 238 | << signature_size << "): " << print_hex(signature, signature_size); |
270 | if (key.key_type == Certificate::KEY_TYPE_RSA) { | ||
271 | if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der, | ||
272 | sig_der_length, key.rsa.get())) { | ||
273 | LOG(INFO) << "failed to verify against RSA key " << i; | ||
274 | continue; | ||
275 | } | ||
276 | 239 | ||
277 | LOG(INFO) << "whole-file signature verified against RSA key " << i; | 240 | if (!read_pkcs7(signature, signature_size, &sig_der, &sig_der_length)) { |
278 | free(sig_der); | 241 | LOG(ERROR) << "Could not find signature DER block"; |
279 | return VERIFY_SUCCESS; | 242 | return VERIFY_FAILURE; |
280 | } else if (key.key_type == Certificate::KEY_TYPE_EC | 243 | } |
281 | && key.hash_len == SHA256_DIGEST_LENGTH) { | ||
282 | if (!ECDSA_verify(0, hash, key.hash_len, sig_der, | ||
283 | sig_der_length, key.ec.get())) { | ||
284 | LOG(INFO) << "failed to verify against EC key " << i; | ||
285 | continue; | ||
286 | } | ||
287 | 244 | ||
288 | LOG(INFO) << "whole-file signature verified against EC key " << i; | 245 | // Check to make sure at least one of the keys matches the signature. Since any key can match, |
289 | free(sig_der); | 246 | // we need to try each before determining a verification failure has happened. |
290 | return VERIFY_SUCCESS; | 247 | size_t i = 0; |
291 | } else { | 248 | for (const auto& key : keys) { |
292 | LOG(INFO) << "Unknown key type " << key.key_type; | 249 | const uint8_t* hash; |
293 | } | 250 | int hash_nid; |
294 | i++; | 251 | switch (key.hash_len) { |
295 | } | 252 | case SHA_DIGEST_LENGTH: |
253 | hash = sha1; | ||
254 | hash_nid = NID_sha1; | ||
255 | break; | ||
256 | case SHA256_DIGEST_LENGTH: | ||
257 | hash = sha256; | ||
258 | hash_nid = NID_sha256; | ||
259 | break; | ||
260 | default: | ||
261 | continue; | ||
262 | } | ||
263 | |||
264 | // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends | ||
265 | // after the signature itself. | ||
266 | if (key.key_type == Certificate::KEY_TYPE_RSA) { | ||
267 | if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der, sig_der_length, key.rsa.get())) { | ||
268 | LOG(INFO) << "failed to verify against RSA key " << i; | ||
269 | continue; | ||
270 | } | ||
271 | |||
272 | LOG(INFO) << "whole-file signature verified against RSA key " << i; | ||
273 | free(sig_der); | ||
274 | return VERIFY_SUCCESS; | ||
275 | } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) { | ||
276 | if (!ECDSA_verify(0, hash, key.hash_len, sig_der, sig_der_length, key.ec.get())) { | ||
277 | LOG(INFO) << "failed to verify against EC key " << i; | ||
278 | continue; | ||
279 | } | ||
280 | |||
281 | LOG(INFO) << "whole-file signature verified against EC key " << i; | ||
282 | free(sig_der); | ||
283 | return VERIFY_SUCCESS; | ||
284 | } else { | ||
285 | LOG(INFO) << "Unknown key type " << key.key_type; | ||
286 | } | ||
287 | i++; | ||
288 | } | ||
296 | 289 | ||
297 | if (need_sha1) { | 290 | if (need_sha1) { |
298 | LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH); | 291 | LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH); |
299 | } | 292 | } |
300 | if (need_sha256) { | 293 | if (need_sha256) { |
301 | LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH); | 294 | LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH); |
302 | } | 295 | } |
303 | free(sig_der); | 296 | free(sig_der); |
304 | LOG(ERROR) << "failed to verify whole-file signature"; | 297 | LOG(ERROR) << "failed to verify whole-file signature"; |
305 | return VERIFY_FAILURE; | 298 | return VERIFY_FAILURE; |
306 | } | 299 | } |
307 | 300 | ||
308 | std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { | 301 | std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { |