blob: 67a4f390af50447832f10c880a8bbbe8169185d8 [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);
129 free(sfName);
130
131 if (sfEntry == NULL) {
132 LOGW("Missing signature file %s\n", sfName);
133 continue;
134 }
135
136 uint8_t sfDigest[SHA_DIGEST_SIZE];
137 if (!digestEntry(pArchive, sfEntry, NULL, 0, sfDigest)) continue;
138
139 char *rsaBuf = slurpEntry(pArchive, rsaEntry);
140 if (rsaBuf == NULL) continue;
141
142 /* Try to verify the signature with all the keys. */
143 uint8_t *sig = (uint8_t *) rsaBuf + rsaLen - RSANUMBYTES;
144 for (j = 0; j < numKeys; ++j) {
145 if (RSA_verify(&pKeys[j], sig, RSANUMBYTES, sfDigest)) {
146 free(rsaBuf);
147 LOGI("Verified %.*s\n", rsaName.len, rsaName.str);
148 return sfEntry;
149 }
150 }
151
152 free(rsaBuf);
153 LOGW("Can't verify %.*s\n", rsaName.len, rsaName.str);
154 }
155 }
156
157 LOGE("No signature (%d files)\n", mzZipEntryCount(pArchive));
158 return NULL;
159}
160
161
162/* Verify /META-INF/MANIFEST.MF against the digest in a signature file. */
163static const ZipEntry *verifyManifest(const ZipArchive *pArchive,
164 const ZipEntry *sfEntry) {
165 static const char prefix[] = "SHA1-Digest-Manifest: ", eol[] = "\r\n";
166 uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE];
167
168 char *sfBuf = slurpEntry(pArchive, sfEntry);
169 if (sfBuf == NULL) return NULL;
170
171 char *line, *save;
172 for (line = strtok_r(sfBuf, eol, &save); line != NULL;
173 line = strtok_r(NULL, eol, &save)) {
174 if (!strncasecmp(prefix, line, sizeof(prefix) - 1)) {
175 UnterminatedString fn = mzGetZipEntryFileName(sfEntry);
176 const char *digest = line + sizeof(prefix) - 1;
177 int n = b64_pton(digest, expected, sizeof(expected));
178 if (n != SHA_DIGEST_SIZE) {
179 LOGE("Invalid base64 in %.*s: %s (%d)\n",
180 fn.len, fn.str, digest, n);
181 line = NULL;
182 }
183 break;
184 }
185 }
186
187 free(sfBuf);
188
189 if (line == NULL) {
190 LOGE("No digest manifest in signature file\n");
191 return false;
192 }
193
194 const char *mfName = "META-INF/MANIFEST.MF";
195 const ZipEntry *mfEntry = mzFindZipEntry(pArchive, mfName);
196 if (mfEntry == NULL) {
197 LOGE("No manifest file %s\n", mfName);
198 return NULL;
199 }
200
201 if (!digestEntry(pArchive, mfEntry, NULL, 0, actual)) return NULL;
202 if (memcmp(expected, actual, SHA_DIGEST_SIZE)) {
203 UnterminatedString fn = mzGetZipEntryFileName(sfEntry);
204 LOGE("Wrong digest for %s in %.*s\n", mfName, fn.len, fn.str);
205 return NULL;
206 }
207
208 LOGI("Verified %s\n", mfName);
209 return mfEntry;
210}
211
212
213/* Verify all the files in a Zip archive against the manifest. */
214static bool verifyArchive(const ZipArchive *pArchive, const ZipEntry *mfEntry) {
215 static const char namePrefix[] = "Name: ";
216 static const char contPrefix[] = " "; // Continuation of the filename
217 static const char digestPrefix[] = "SHA1-Digest: ";
218 static const char eol[] = "\r\n";
219
220 char *mfBuf = slurpEntry(pArchive, mfEntry);
221 if (mfBuf == NULL) return false;
222
223 /* we're using calloc() here, so the initial state of the array is false */
224 bool *unverified = (bool *) calloc(mzZipEntryCount(pArchive), sizeof(bool));
225 if (unverified == NULL) {
226 LOGE("Can't allocate valid flags\n");
227 free(mfBuf);
228 return false;
229 }
230
231 /* Mark all the files in the archive that need to be verified.
232 * As we scan the manifest and check signatures, we'll unset these flags.
233 * At the end, we'll make sure that all the flags are unset.
234 */
235
236 unsigned i, totalBytes = 0;
237 for (i = 0; i < mzZipEntryCount(pArchive); ++i) {
238 const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
239 UnterminatedString fn = mzGetZipEntryFileName(entry);
240 int len = mzGetZipEntryUncompLen(entry);
241
242 // Don't validate: directories, the manifest, *.RSA, and *.SF.
243
244 if (entry == mfEntry) {
245 LOGV("Skipping manifest %.*s\n", fn.len, fn.str);
246 } else if (fn.len > 0 && fn.str[fn.len-1] == '/' && len == 0) {
247 LOGV("Skipping directory %.*s\n", fn.len, fn.str);
248 } else if (!strncasecmp(fn.str, "META-INF/", 9) && (
249 !strncasecmp(fn.str + fn.len - 4, ".RSA", 4) ||
250 !strncasecmp(fn.str + fn.len - 3, ".SF", 3))) {
251 LOGV("Skipping signature %.*s\n", fn.len, fn.str);
252 } else {
253 unverified[i] = true;
254 totalBytes += len;
255 }
256 }
257
258 unsigned doneBytes = 0;
259 char *line, *save, *name = NULL;
260 for (line = strtok_r(mfBuf, eol, &save); line != NULL;
261 line = strtok_r(NULL, eol, &save)) {
262 if (!strncasecmp(line, namePrefix, sizeof(namePrefix) - 1)) {
263 // "Name:" introducing a new stanza
264 if (name != NULL) {
265 LOGE("No digest:\n %s\n", name);
266 break;
267 }
268
269 name = strdup(line + sizeof(namePrefix) - 1);
270 if (name == NULL) {
271 LOGE("Can't copy filename in %s\n", line);
272 break;
273 }
274 } else if (!strncasecmp(line, contPrefix, sizeof(contPrefix) - 1)) {
275 // Continuing a long name (nothing else should be continued)
276 const char *tail = line + sizeof(contPrefix) - 1;
277 if (name == NULL) {
278 LOGE("Unexpected continuation:\n %s\n", tail);
279 }
280
281 char *concat;
282 if (asprintf(&concat, "%s%s", name, tail) < 0) {
283 LOGE("Can't append continuation %s\n", tail);
284 break;
285 }
286 free(name);
287 name = concat;
288 } else if (!strncasecmp(line, digestPrefix, sizeof(digestPrefix) - 1)) {
289 // "Digest:" supplying a hash code for the current stanza
290 const char *base64 = line + sizeof(digestPrefix) - 1;
291 if (name == NULL) {
292 LOGE("Unexpected digest:\n %s\n", base64);
293 break;
294 }
295
296 const ZipEntry *entry = mzFindZipEntry(pArchive, name);
297 if (entry == NULL) {
298 LOGE("Missing file:\n %s\n", name);
299 break;
300 }
301 if (!mzIsZipEntryIntact(pArchive, entry)) {
302 LOGE("Corrupt file:\n %s\n", name);
303 break;
304 }
305 if (!unverified[mzGetZipEntryIndex(pArchive, entry)]) {
306 LOGE("Unexpected file:\n %s\n", name);
307 break;
308 }
309
310 uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE];
311 int n = b64_pton(base64, expected, sizeof(expected));
312 if (n != SHA_DIGEST_SIZE) {
313 LOGE("Invalid base64:\n %s\n %s\n", name, base64);
314 break;
315 }
316
317 if (!digestEntry(pArchive, entry, &doneBytes, totalBytes, actual) ||
318 memcmp(expected, actual, SHA_DIGEST_SIZE) != 0) {
319 LOGE("Wrong digest:\n %s\n", name);
320 break;
321 }
322
323 LOGI("Verified %s\n", name);
324 unverified[mzGetZipEntryIndex(pArchive, entry)] = false;
325 free(name);
326 name = NULL;
327 }
328 }
329
330 if (name != NULL) free(name);
331 free(mfBuf);
332
333 for (i = 0; i < mzZipEntryCount(pArchive) && !unverified[i]; ++i) ;
334 free(unverified);
335
336 // This means we didn't get to the end of the manifest successfully.
337 if (line != NULL) return false;
338
339 if (i < mzZipEntryCount(pArchive)) {
340 const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
341 UnterminatedString fn = mzGetZipEntryFileName(entry);
342 LOGE("No digest for %.*s\n", fn.len, fn.str);
343 return false;
344 }
345
346 return true;
347}
348
349
350bool verify_jar_signature(const ZipArchive *pArchive,
351 const RSAPublicKey *pKeys, int numKeys) {
352 const ZipEntry *sfEntry = verifySignature(pArchive, pKeys, numKeys);
353 if (sfEntry == NULL) return false;
354
355 const ZipEntry *mfEntry = verifyManifest(pArchive, sfEntry);
356 if (mfEntry == NULL) return false;
357
358 return verifyArchive(pArchive, mfEntry);
359}