blob: 1180ae8d0e348d9e0a08467b98db56b7e3cd2ce1 [file] [log] [blame]
The Android Open Source Project23580ca2008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "common.h"
18#include "verifier.h"
19
20#include "minzip/Zip.h"
21#include "mincrypt/rsa.h"
22#include "mincrypt/sha.h"
23
24#include <netinet/in.h> /* required for resolv.h */
25#include <resolv.h> /* for base64 codec */
26#include <string.h>
27
28/* Return an allocated buffer with the contents of a zip file entry. */
29static char *slurpEntry(const ZipArchive *pArchive, const ZipEntry *pEntry) {
30 if (!mzIsZipEntryIntact(pArchive, pEntry)) {
31 UnterminatedString fn = mzGetZipEntryFileName(pEntry);
32 LOGE("Invalid %.*s\n", fn.len, fn.str);
33 return NULL;
34 }
35
36 int len = mzGetZipEntryUncompLen(pEntry);
37 char *buf = malloc(len + 1);
38 if (buf == NULL) {
39 UnterminatedString fn = mzGetZipEntryFileName(pEntry);
40 LOGE("Can't allocate %d bytes for %.*s\n", len, fn.len, fn.str);
41 return NULL;
42 }
43
44 if (!mzReadZipEntry(pArchive, pEntry, buf, len)) {
45 UnterminatedString fn = mzGetZipEntryFileName(pEntry);
46 LOGE("Can't read %.*s\n", fn.len, fn.str);
47 free(buf);
48 return NULL;
49 }
50
51 buf[len] = '\0';
52 return buf;
53}
54
55
56struct DigestContext {
57 SHA_CTX digest;
58 unsigned *doneBytes;
59 unsigned totalBytes;
60};
61
62
63/* mzProcessZipEntryContents callback to update an SHA-1 hash context. */
64static bool updateHash(const unsigned char *data, int dataLen, void *cookie) {
65 struct DigestContext *context = (struct DigestContext *) cookie;
66 SHA_update(&context->digest, data, dataLen);
67 if (context->doneBytes != NULL) {
68 *context->doneBytes += dataLen;
69 if (context->totalBytes > 0) {
70 ui_set_progress(*context->doneBytes * 1.0 / context->totalBytes);
71 }
72 }
73 return true;
74}
75
76
77/* Get the SHA-1 digest of a zip file entry. */
78static bool digestEntry(const ZipArchive *pArchive, const ZipEntry *pEntry,
79 unsigned *doneBytes, unsigned totalBytes,
80 uint8_t digest[SHA_DIGEST_SIZE]) {
81 struct DigestContext context;
82 SHA_init(&context.digest);
83 context.doneBytes = doneBytes;
84 context.totalBytes = totalBytes;
85 if (!mzProcessZipEntryContents(pArchive, pEntry, updateHash, &context)) {
86 UnterminatedString fn = mzGetZipEntryFileName(pEntry);
87 LOGE("Can't digest %.*s\n", fn.len, fn.str);
88 return false;
89 }
90
91 memcpy(digest, SHA_final(&context.digest), SHA_DIGEST_SIZE);
92
93#ifdef LOG_VERBOSE
94 UnterminatedString fn = mzGetZipEntryFileName(pEntry);
95 char base64[SHA_DIGEST_SIZE * 3];
96 b64_ntop(digest, SHA_DIGEST_SIZE, base64, sizeof(base64));
97 LOGV("sha1(%.*s) = %s\n", fn.len, fn.str, base64);
98#endif
99
100 return true;
101}
102
103
104/* Find a /META-INF/xxx.SF signature file signed by a matching xxx.RSA file. */
105static const ZipEntry *verifySignature(const ZipArchive *pArchive,
106 const RSAPublicKey *pKeys, unsigned int numKeys) {
107 static const char prefix[] = "META-INF/";
108 static const char rsa[] = ".RSA", sf[] = ".SF";
109
110 unsigned int i, j;
111 for (i = 0; i < mzZipEntryCount(pArchive); ++i) {
112 const ZipEntry *rsaEntry = mzGetZipEntryAt(pArchive, i);
113 UnterminatedString rsaName = mzGetZipEntryFileName(rsaEntry);
114 int rsaLen = mzGetZipEntryUncompLen(rsaEntry);
115 if (rsaLen >= RSANUMBYTES && rsaName.len > sizeof(prefix) &&
116 !strncmp(rsaName.str, prefix, sizeof(prefix) - 1) &&
117 !strncmp(rsaName.str + rsaName.len - sizeof(rsa) + 1,
118 rsa, sizeof(rsa) - 1)) {
119 char *sfName = malloc(rsaName.len - sizeof(rsa) + sizeof(sf) + 1);
120 if (sfName == NULL) {
121 LOGE("Can't allocate %d bytes for filename\n", rsaName.len);
122 continue;
123 }
124
125 /* Replace .RSA with .SF */
126 strncpy(sfName, rsaName.str, rsaName.len - sizeof(rsa) + 1);
127 strcpy(sfName + rsaName.len - sizeof(rsa) + 1, sf);
128 const ZipEntry *sfEntry = mzFindZipEntry(pArchive, sfName);
The Android Open Source Project23580ca2008-10-21 07:00:00 -0700129
130 if (sfEntry == NULL) {
131 LOGW("Missing signature file %s\n", sfName);
The Android Open Source Projectff3d9382008-12-17 18:03:49 -0800132 free(sfName);
The Android Open Source Project23580ca2008-10-21 07:00:00 -0700133 continue;
134 }
135
The Android Open Source Projectff3d9382008-12-17 18:03:49 -0800136 free(sfName);
137
The Android Open Source Project23580ca2008-10-21 07:00:00 -0700138 uint8_t sfDigest[SHA_DIGEST_SIZE];
139 if (!digestEntry(pArchive, sfEntry, NULL, 0, sfDigest)) continue;
140
141 char *rsaBuf = slurpEntry(pArchive, rsaEntry);
142 if (rsaBuf == NULL) continue;
143
144 /* Try to verify the signature with all the keys. */
145 uint8_t *sig = (uint8_t *) rsaBuf + rsaLen - RSANUMBYTES;
146 for (j = 0; j < numKeys; ++j) {
147 if (RSA_verify(&pKeys[j], sig, RSANUMBYTES, sfDigest)) {
148 free(rsaBuf);
149 LOGI("Verified %.*s\n", rsaName.len, rsaName.str);
150 return sfEntry;
151 }
152 }
153
154 free(rsaBuf);
155 LOGW("Can't verify %.*s\n", rsaName.len, rsaName.str);
156 }
157 }
158
159 LOGE("No signature (%d files)\n", mzZipEntryCount(pArchive));
160 return NULL;
161}
162
163
164/* Verify /META-INF/MANIFEST.MF against the digest in a signature file. */
165static const ZipEntry *verifyManifest(const ZipArchive *pArchive,
166 const ZipEntry *sfEntry) {
167 static const char prefix[] = "SHA1-Digest-Manifest: ", eol[] = "\r\n";
168 uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE];
169
170 char *sfBuf = slurpEntry(pArchive, sfEntry);
171 if (sfBuf == NULL) return NULL;
172
173 char *line, *save;
174 for (line = strtok_r(sfBuf, eol, &save); line != NULL;
175 line = strtok_r(NULL, eol, &save)) {
176 if (!strncasecmp(prefix, line, sizeof(prefix) - 1)) {
177 UnterminatedString fn = mzGetZipEntryFileName(sfEntry);
178 const char *digest = line + sizeof(prefix) - 1;
179 int n = b64_pton(digest, expected, sizeof(expected));
180 if (n != SHA_DIGEST_SIZE) {
181 LOGE("Invalid base64 in %.*s: %s (%d)\n",
182 fn.len, fn.str, digest, n);
183 line = NULL;
184 }
185 break;
186 }
187 }
188
189 free(sfBuf);
190
191 if (line == NULL) {
192 LOGE("No digest manifest in signature file\n");
193 return false;
194 }
195
196 const char *mfName = "META-INF/MANIFEST.MF";
197 const ZipEntry *mfEntry = mzFindZipEntry(pArchive, mfName);
198 if (mfEntry == NULL) {
199 LOGE("No manifest file %s\n", mfName);
200 return NULL;
201 }
202
203 if (!digestEntry(pArchive, mfEntry, NULL, 0, actual)) return NULL;
204 if (memcmp(expected, actual, SHA_DIGEST_SIZE)) {
205 UnterminatedString fn = mzGetZipEntryFileName(sfEntry);
206 LOGE("Wrong digest for %s in %.*s\n", mfName, fn.len, fn.str);
207 return NULL;
208 }
209
210 LOGI("Verified %s\n", mfName);
211 return mfEntry;
212}
213
214
215/* Verify all the files in a Zip archive against the manifest. */
216static bool verifyArchive(const ZipArchive *pArchive, const ZipEntry *mfEntry) {
217 static const char namePrefix[] = "Name: ";
218 static const char contPrefix[] = " "; // Continuation of the filename
219 static const char digestPrefix[] = "SHA1-Digest: ";
220 static const char eol[] = "\r\n";
221
222 char *mfBuf = slurpEntry(pArchive, mfEntry);
223 if (mfBuf == NULL) return false;
224
225 /* we're using calloc() here, so the initial state of the array is false */
226 bool *unverified = (bool *) calloc(mzZipEntryCount(pArchive), sizeof(bool));
227 if (unverified == NULL) {
228 LOGE("Can't allocate valid flags\n");
229 free(mfBuf);
230 return false;
231 }
232
233 /* Mark all the files in the archive that need to be verified.
234 * As we scan the manifest and check signatures, we'll unset these flags.
235 * At the end, we'll make sure that all the flags are unset.
236 */
237
238 unsigned i, totalBytes = 0;
239 for (i = 0; i < mzZipEntryCount(pArchive); ++i) {
240 const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
241 UnterminatedString fn = mzGetZipEntryFileName(entry);
242 int len = mzGetZipEntryUncompLen(entry);
243
244 // Don't validate: directories, the manifest, *.RSA, and *.SF.
245
246 if (entry == mfEntry) {
247 LOGV("Skipping manifest %.*s\n", fn.len, fn.str);
248 } else if (fn.len > 0 && fn.str[fn.len-1] == '/' && len == 0) {
249 LOGV("Skipping directory %.*s\n", fn.len, fn.str);
250 } else if (!strncasecmp(fn.str, "META-INF/", 9) && (
251 !strncasecmp(fn.str + fn.len - 4, ".RSA", 4) ||
252 !strncasecmp(fn.str + fn.len - 3, ".SF", 3))) {
253 LOGV("Skipping signature %.*s\n", fn.len, fn.str);
254 } else {
255 unverified[i] = true;
256 totalBytes += len;
257 }
258 }
259
260 unsigned doneBytes = 0;
261 char *line, *save, *name = NULL;
262 for (line = strtok_r(mfBuf, eol, &save); line != NULL;
263 line = strtok_r(NULL, eol, &save)) {
264 if (!strncasecmp(line, namePrefix, sizeof(namePrefix) - 1)) {
265 // "Name:" introducing a new stanza
266 if (name != NULL) {
267 LOGE("No digest:\n %s\n", name);
268 break;
269 }
270
271 name = strdup(line + sizeof(namePrefix) - 1);
272 if (name == NULL) {
273 LOGE("Can't copy filename in %s\n", line);
274 break;
275 }
276 } else if (!strncasecmp(line, contPrefix, sizeof(contPrefix) - 1)) {
277 // Continuing a long name (nothing else should be continued)
278 const char *tail = line + sizeof(contPrefix) - 1;
279 if (name == NULL) {
280 LOGE("Unexpected continuation:\n %s\n", tail);
281 }
282
283 char *concat;
284 if (asprintf(&concat, "%s%s", name, tail) < 0) {
285 LOGE("Can't append continuation %s\n", tail);
286 break;
287 }
288 free(name);
289 name = concat;
290 } else if (!strncasecmp(line, digestPrefix, sizeof(digestPrefix) - 1)) {
291 // "Digest:" supplying a hash code for the current stanza
292 const char *base64 = line + sizeof(digestPrefix) - 1;
293 if (name == NULL) {
294 LOGE("Unexpected digest:\n %s\n", base64);
295 break;
296 }
297
298 const ZipEntry *entry = mzFindZipEntry(pArchive, name);
299 if (entry == NULL) {
300 LOGE("Missing file:\n %s\n", name);
301 break;
302 }
303 if (!mzIsZipEntryIntact(pArchive, entry)) {
304 LOGE("Corrupt file:\n %s\n", name);
305 break;
306 }
307 if (!unverified[mzGetZipEntryIndex(pArchive, entry)]) {
308 LOGE("Unexpected file:\n %s\n", name);
309 break;
310 }
311
312 uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE];
313 int n = b64_pton(base64, expected, sizeof(expected));
314 if (n != SHA_DIGEST_SIZE) {
315 LOGE("Invalid base64:\n %s\n %s\n", name, base64);
316 break;
317 }
318
319 if (!digestEntry(pArchive, entry, &doneBytes, totalBytes, actual) ||
320 memcmp(expected, actual, SHA_DIGEST_SIZE) != 0) {
321 LOGE("Wrong digest:\n %s\n", name);
322 break;
323 }
324
325 LOGI("Verified %s\n", name);
326 unverified[mzGetZipEntryIndex(pArchive, entry)] = false;
327 free(name);
328 name = NULL;
329 }
330 }
331
332 if (name != NULL) free(name);
333 free(mfBuf);
334
335 for (i = 0; i < mzZipEntryCount(pArchive) && !unverified[i]; ++i) ;
336 free(unverified);
337
338 // This means we didn't get to the end of the manifest successfully.
339 if (line != NULL) return false;
340
341 if (i < mzZipEntryCount(pArchive)) {
342 const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
343 UnterminatedString fn = mzGetZipEntryFileName(entry);
344 LOGE("No digest for %.*s\n", fn.len, fn.str);
345 return false;
346 }
347
348 return true;
349}
350
351
352bool verify_jar_signature(const ZipArchive *pArchive,
353 const RSAPublicKey *pKeys, int numKeys) {
354 const ZipEntry *sfEntry = verifySignature(pArchive, pKeys, numKeys);
355 if (sfEntry == NULL) return false;
356
357 const ZipEntry *mfEntry = verifyManifest(pArchive, sfEntry);
358 if (mfEntry == NULL) return false;
359
360 return verifyArchive(pArchive, mfEntry);
361}