|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /* |
|
6 * JARVER |
|
7 * |
|
8 * Jarnature Parsing & Verification |
|
9 */ |
|
10 |
|
11 #include "nssrenam.h" |
|
12 #include "jar.h" |
|
13 #include "jarint.h" |
|
14 #include "certdb.h" |
|
15 #include "certt.h" |
|
16 #include "secpkcs7.h" |
|
17 #include "secder.h" |
|
18 |
|
19 #define SZ 512 |
|
20 |
|
21 static int |
|
22 jar_validate_pkcs7(JAR *jar, JAR_Signer *signer, char *data, long length); |
|
23 |
|
24 static void |
|
25 jar_catch_bytes(void *arg, const char *buf, unsigned long len); |
|
26 |
|
27 static int |
|
28 jar_gather_signers(JAR *jar, JAR_Signer *signer, SEC_PKCS7ContentInfo *cinfo); |
|
29 |
|
30 static char * |
|
31 jar_eat_line(int lines, int eating, char *data, long *len); |
|
32 |
|
33 static JAR_Digest * |
|
34 jar_digest_section(char *manifest, long length); |
|
35 |
|
36 static JAR_Digest *jar_get_mf_digest(JAR *jar, char *path); |
|
37 |
|
38 static int |
|
39 jar_parse_digital_signature(char *raw_manifest, JAR_Signer *signer, |
|
40 long length, JAR *jar); |
|
41 |
|
42 static int |
|
43 jar_add_cert(JAR *jar, JAR_Signer *signer, int type, CERTCertificate *cert); |
|
44 |
|
45 static char *jar_basename(const char *path); |
|
46 |
|
47 static int |
|
48 jar_signal(int status, JAR *jar, const char *metafile, char *pathname); |
|
49 |
|
50 #ifdef DEBUG |
|
51 static int jar_insanity_check(char *data, long length); |
|
52 #endif |
|
53 |
|
54 int |
|
55 jar_parse_mf(JAR *jar, char *raw_manifest, long length, |
|
56 const char *path, const char *url); |
|
57 |
|
58 int |
|
59 jar_parse_sf(JAR *jar, char *raw_manifest, long length, |
|
60 const char *path, const char *url); |
|
61 |
|
62 int |
|
63 jar_parse_sig(JAR *jar, const char *path, char *raw_manifest, |
|
64 long length); |
|
65 |
|
66 int |
|
67 jar_parse_any(JAR *jar, int type, JAR_Signer *signer, |
|
68 char *raw_manifest, long length, const char *path, |
|
69 const char *url); |
|
70 |
|
71 static int |
|
72 jar_internal_digest(JAR *jar, const char *path, char *x_name, JAR_Digest *dig); |
|
73 |
|
74 /* |
|
75 * J A R _ p a r s e _ m a n i f e s t |
|
76 * |
|
77 * Pass manifest files to this function. They are |
|
78 * decoded and placed into internal representations. |
|
79 * |
|
80 * Accepts both signature and manifest files. Use |
|
81 * the same "jar" for both. |
|
82 * |
|
83 */ |
|
84 int |
|
85 JAR_parse_manifest(JAR *jar, char *raw_manifest, long length, |
|
86 const char *path, const char *url) |
|
87 { |
|
88 int filename_free = 0; |
|
89 |
|
90 /* fill in the path, if supplied. This is the location |
|
91 of the jar file on disk, if known */ |
|
92 |
|
93 if (jar->filename == NULL && path) { |
|
94 jar->filename = PORT_Strdup(path); |
|
95 if (jar->filename == NULL) |
|
96 return JAR_ERR_MEMORY; |
|
97 filename_free = 1; |
|
98 } |
|
99 |
|
100 /* fill in the URL, if supplied. This is the place |
|
101 from which the jar file was retrieved. */ |
|
102 |
|
103 if (jar->url == NULL && url) { |
|
104 jar->url = PORT_Strdup(url); |
|
105 if (jar->url == NULL) { |
|
106 if (filename_free) { |
|
107 PORT_Free(jar->filename); |
|
108 } |
|
109 return JAR_ERR_MEMORY; |
|
110 } |
|
111 } |
|
112 |
|
113 /* Determine what kind of file this is from the META-INF |
|
114 directory. It could be MF, SF, or a binary RSA/DSA file */ |
|
115 |
|
116 if (!PORT_Strncasecmp (raw_manifest, "Manifest-Version:", 17)) { |
|
117 return jar_parse_mf(jar, raw_manifest, length, path, url); |
|
118 } |
|
119 else if (!PORT_Strncasecmp (raw_manifest, "Signature-Version:", 18)) |
|
120 { |
|
121 return jar_parse_sf(jar, raw_manifest, length, path, url); |
|
122 } else { |
|
123 /* This is probably a binary signature */ |
|
124 return jar_parse_sig(jar, path, raw_manifest, length); |
|
125 } |
|
126 } |
|
127 |
|
128 /* |
|
129 * j a r _ p a r s e _ s i g |
|
130 * |
|
131 * Pass some manner of RSA or DSA digital signature |
|
132 * on, after checking to see if it comes at an appropriate state. |
|
133 * |
|
134 */ |
|
135 int |
|
136 jar_parse_sig(JAR *jar, const char *path, char *raw_manifest, |
|
137 long length) |
|
138 { |
|
139 JAR_Signer *signer; |
|
140 int status = JAR_ERR_ORDER; |
|
141 |
|
142 if (length <= 128) { |
|
143 /* signature is way too small */ |
|
144 return JAR_ERR_SIG; |
|
145 } |
|
146 |
|
147 /* make sure that MF and SF have already been processed */ |
|
148 |
|
149 if (jar->globalmeta == NULL) |
|
150 return JAR_ERR_ORDER; |
|
151 |
|
152 /* Determine whether or not this RSA file has |
|
153 has an associated SF file */ |
|
154 |
|
155 if (path) { |
|
156 char *owner; |
|
157 owner = jar_basename(path); |
|
158 |
|
159 if (owner == NULL) |
|
160 return JAR_ERR_MEMORY; |
|
161 |
|
162 signer = jar_get_signer(jar, owner); |
|
163 PORT_Free(owner); |
|
164 } else |
|
165 signer = jar_get_signer(jar, "*"); |
|
166 |
|
167 if (signer == NULL) |
|
168 return JAR_ERR_ORDER; |
|
169 |
|
170 |
|
171 /* Do not pass a huge pointer to this function, |
|
172 since the underlying security code is unaware. We will |
|
173 never pass >64k through here. */ |
|
174 |
|
175 if (length > 64000) { |
|
176 /* this digital signature is way too big */ |
|
177 return JAR_ERR_SIG; |
|
178 } |
|
179 |
|
180 /* don't expense unneeded calloc overhead on non-win16 */ |
|
181 status = jar_parse_digital_signature(raw_manifest, signer, length, jar); |
|
182 |
|
183 return status; |
|
184 } |
|
185 |
|
186 /* |
|
187 * j a r _ p a r s e _ m f |
|
188 * |
|
189 * Parse the META-INF/manifest.mf file, whose |
|
190 * information applies to all signers. |
|
191 * |
|
192 */ |
|
193 int |
|
194 jar_parse_mf(JAR *jar, char *raw_manifest, long length, |
|
195 const char *path, const char *url) |
|
196 { |
|
197 if (jar->globalmeta) { |
|
198 /* refuse a second manifest file, if passed for some reason */ |
|
199 return JAR_ERR_ORDER; |
|
200 } |
|
201 |
|
202 /* remember a digest for the global section */ |
|
203 jar->globalmeta = jar_digest_section(raw_manifest, length); |
|
204 if (jar->globalmeta == NULL) |
|
205 return JAR_ERR_MEMORY; |
|
206 return jar_parse_any(jar, jarTypeMF, NULL, raw_manifest, length, |
|
207 path, url); |
|
208 } |
|
209 |
|
210 /* |
|
211 * j a r _ p a r s e _ s f |
|
212 * |
|
213 * Parse META-INF/xxx.sf, a digitally signed file |
|
214 * pointing to a subset of MF sections. |
|
215 * |
|
216 */ |
|
217 int |
|
218 jar_parse_sf(JAR *jar, char *raw_manifest, long length, |
|
219 const char *path, const char *url) |
|
220 { |
|
221 JAR_Signer *signer = NULL; |
|
222 int status = JAR_ERR_MEMORY; |
|
223 |
|
224 if (jar->globalmeta == NULL) { |
|
225 /* It is a requirement that the MF file be passed before the SF file */ |
|
226 return JAR_ERR_ORDER; |
|
227 } |
|
228 |
|
229 signer = JAR_new_signer(); |
|
230 if (signer == NULL) |
|
231 goto loser; |
|
232 |
|
233 if (path) { |
|
234 signer->owner = jar_basename(path); |
|
235 if (signer->owner == NULL) |
|
236 goto loser; |
|
237 } |
|
238 |
|
239 /* check for priors. When someone doctors a jar file |
|
240 to contain identical path entries, prevent the second |
|
241 one from affecting JAR functions */ |
|
242 if (jar_get_signer(jar, signer->owner)) { |
|
243 /* someone is trying to spoof us */ |
|
244 status = JAR_ERR_ORDER; |
|
245 goto loser; |
|
246 } |
|
247 |
|
248 /* remember its digest */ |
|
249 signer->digest = JAR_calculate_digest (raw_manifest, length); |
|
250 if (signer->digest == NULL) |
|
251 goto loser; |
|
252 |
|
253 /* Add this signer to the jar */ |
|
254 ADDITEM(jar->signers, jarTypeOwner, signer->owner, signer, |
|
255 sizeof (JAR_Signer)); |
|
256 |
|
257 return jar_parse_any(jar, jarTypeSF, signer, raw_manifest, length, |
|
258 path, url); |
|
259 |
|
260 loser: |
|
261 if (signer) |
|
262 JAR_destroy_signer (signer); |
|
263 return status; |
|
264 } |
|
265 |
|
266 /* |
|
267 * j a r _ p a r s e _ a n y |
|
268 * |
|
269 * Parse a MF or SF manifest file. |
|
270 * |
|
271 */ |
|
272 int |
|
273 jar_parse_any(JAR *jar, int type, JAR_Signer *signer, |
|
274 char *raw_manifest, long length, const char *path, |
|
275 const char *url) |
|
276 { |
|
277 int status; |
|
278 long raw_len; |
|
279 JAR_Digest *dig, *mfdig = NULL; |
|
280 char line [SZ]; |
|
281 char x_name [SZ], x_md5 [SZ], x_sha [SZ]; |
|
282 char *x_info; |
|
283 char *sf_md5 = NULL, *sf_sha1 = NULL; |
|
284 |
|
285 *x_name = 0; |
|
286 *x_md5 = 0; |
|
287 *x_sha = 0; |
|
288 |
|
289 PORT_Assert( length > 0 ); |
|
290 raw_len = length; |
|
291 |
|
292 #ifdef DEBUG |
|
293 if ((status = jar_insanity_check(raw_manifest, raw_len)) < 0) |
|
294 return status; |
|
295 #endif |
|
296 |
|
297 /* null terminate the first line */ |
|
298 raw_manifest = jar_eat_line(0, PR_TRUE, raw_manifest, &raw_len); |
|
299 |
|
300 /* skip over the preliminary section */ |
|
301 /* This is one section at the top of the file with global metainfo */ |
|
302 while (raw_len > 0) { |
|
303 JAR_Metainfo *met; |
|
304 |
|
305 raw_manifest = jar_eat_line(1, PR_TRUE, raw_manifest, &raw_len); |
|
306 if (raw_len <= 0 || !*raw_manifest) |
|
307 break; |
|
308 |
|
309 met = PORT_ZNew(JAR_Metainfo); |
|
310 if (met == NULL) |
|
311 return JAR_ERR_MEMORY; |
|
312 |
|
313 /* Parse out the header & info */ |
|
314 if (PORT_Strlen (raw_manifest) >= SZ) { |
|
315 /* almost certainly nonsense */ |
|
316 PORT_Free(met); |
|
317 continue; |
|
318 } |
|
319 |
|
320 PORT_Strcpy (line, raw_manifest); |
|
321 x_info = line; |
|
322 |
|
323 while (*x_info && *x_info != ' ' && *x_info != '\t' && *x_info != ':') |
|
324 x_info++; |
|
325 |
|
326 if (*x_info) |
|
327 *x_info++ = 0; |
|
328 |
|
329 while (*x_info == ' ' || *x_info == '\t') |
|
330 x_info++; |
|
331 |
|
332 /* metainfo (name, value) pair is now (line, x_info) */ |
|
333 met->header = PORT_Strdup(line); |
|
334 met->info = PORT_Strdup(x_info); |
|
335 |
|
336 if (type == jarTypeMF) { |
|
337 ADDITEM (jar->metainfo, jarTypeMeta, |
|
338 /* pathname */ NULL, met, sizeof (JAR_Metainfo)); |
|
339 } |
|
340 |
|
341 /* For SF files, this metadata may be the digests |
|
342 of the MF file, still in the "met" structure. */ |
|
343 |
|
344 if (type == jarTypeSF) { |
|
345 if (!PORT_Strcasecmp(line, "MD5-Digest")) |
|
346 sf_md5 = (char *) met->info; |
|
347 |
|
348 if (!PORT_Strcasecmp(line, "SHA1-Digest") || |
|
349 !PORT_Strcasecmp(line, "SHA-Digest")) |
|
350 sf_sha1 = (char *) met->info; |
|
351 } |
|
352 |
|
353 if (type != jarTypeMF) { |
|
354 PORT_Free(met->header); |
|
355 if (type != jarTypeSF) { |
|
356 PORT_Free(met->info); |
|
357 } |
|
358 PORT_Free(met); |
|
359 } |
|
360 } |
|
361 |
|
362 if (type == jarTypeSF && jar->globalmeta) { |
|
363 /* this is a SF file which may contain a digest of the manifest.mf's |
|
364 global metainfo. */ |
|
365 |
|
366 int match = 0; |
|
367 JAR_Digest *glob = jar->globalmeta; |
|
368 |
|
369 if (sf_md5) { |
|
370 unsigned int md5_length; |
|
371 unsigned char *md5_digest; |
|
372 |
|
373 md5_digest = ATOB_AsciiToData (sf_md5, &md5_length); |
|
374 PORT_Assert( md5_length == MD5_LENGTH ); |
|
375 |
|
376 if (md5_length != MD5_LENGTH) |
|
377 return JAR_ERR_CORRUPT; |
|
378 |
|
379 match = PORT_Memcmp(md5_digest, glob->md5, MD5_LENGTH); |
|
380 } |
|
381 |
|
382 if (sf_sha1 && match == 0) { |
|
383 unsigned int sha1_length; |
|
384 unsigned char *sha1_digest; |
|
385 |
|
386 sha1_digest = ATOB_AsciiToData (sf_sha1, &sha1_length); |
|
387 PORT_Assert( sha1_length == SHA1_LENGTH ); |
|
388 |
|
389 if (sha1_length != SHA1_LENGTH) |
|
390 return JAR_ERR_CORRUPT; |
|
391 |
|
392 match = PORT_Memcmp(sha1_digest, glob->sha1, SHA1_LENGTH); |
|
393 } |
|
394 |
|
395 if (match != 0) { |
|
396 /* global digest doesn't match, SF file therefore invalid */ |
|
397 jar->valid = JAR_ERR_METADATA; |
|
398 return JAR_ERR_METADATA; |
|
399 } |
|
400 } |
|
401 |
|
402 /* done with top section of global data */ |
|
403 while (raw_len > 0) { |
|
404 *x_md5 = 0; |
|
405 *x_sha = 0; |
|
406 *x_name = 0; |
|
407 |
|
408 /* If this is a manifest file, attempt to get a digest of the following |
|
409 section, without damaging it. This digest will be saved later. */ |
|
410 |
|
411 if (type == jarTypeMF) { |
|
412 char *sec; |
|
413 long sec_len = raw_len; |
|
414 |
|
415 if (!*raw_manifest || *raw_manifest == '\n') { |
|
416 /* skip the blank line */ |
|
417 sec = jar_eat_line(1, PR_FALSE, raw_manifest, &sec_len); |
|
418 } else |
|
419 sec = raw_manifest; |
|
420 |
|
421 if (sec_len > 0 && !PORT_Strncasecmp(sec, "Name:", 5)) { |
|
422 if (type == jarTypeMF) |
|
423 mfdig = jar_digest_section(sec, sec_len); |
|
424 else |
|
425 mfdig = NULL; |
|
426 } |
|
427 } |
|
428 |
|
429 |
|
430 while (raw_len > 0) { |
|
431 raw_manifest = jar_eat_line(1, PR_TRUE, raw_manifest, &raw_len); |
|
432 if (raw_len <= 0 || !*raw_manifest) |
|
433 break; /* blank line, done with this entry */ |
|
434 |
|
435 if (PORT_Strlen(raw_manifest) >= SZ) { |
|
436 /* almost certainly nonsense */ |
|
437 continue; |
|
438 } |
|
439 |
|
440 /* Parse out the name/value pair */ |
|
441 PORT_Strcpy(line, raw_manifest); |
|
442 x_info = line; |
|
443 |
|
444 while (*x_info && *x_info != ' ' && *x_info != '\t' && |
|
445 *x_info != ':') |
|
446 x_info++; |
|
447 |
|
448 if (*x_info) |
|
449 *x_info++ = 0; |
|
450 |
|
451 while (*x_info == ' ' || *x_info == '\t') |
|
452 x_info++; |
|
453 |
|
454 if (!PORT_Strcasecmp(line, "Name")) |
|
455 PORT_Strcpy(x_name, x_info); |
|
456 else if (!PORT_Strcasecmp(line, "MD5-Digest")) |
|
457 PORT_Strcpy(x_md5, x_info); |
|
458 else if (!PORT_Strcasecmp(line, "SHA1-Digest") |
|
459 || !PORT_Strcasecmp(line, "SHA-Digest")) |
|
460 PORT_Strcpy(x_sha, x_info); |
|
461 |
|
462 /* Algorithm list is meta info we don't care about; keeping it out |
|
463 of metadata saves significant space for large jar files */ |
|
464 else if (!PORT_Strcasecmp(line, "Digest-Algorithms") |
|
465 || !PORT_Strcasecmp(line, "Hash-Algorithms")) |
|
466 continue; |
|
467 |
|
468 /* Meta info is only collected for the manifest.mf file, |
|
469 since the JAR_get_metainfo call does not support identity */ |
|
470 else if (type == jarTypeMF) { |
|
471 JAR_Metainfo *met; |
|
472 |
|
473 /* this is meta-data */ |
|
474 met = PORT_ZNew(JAR_Metainfo); |
|
475 if (met == NULL) |
|
476 return JAR_ERR_MEMORY; |
|
477 |
|
478 /* metainfo (name, value) pair is now (line, x_info) */ |
|
479 if ((met->header = PORT_Strdup(line)) == NULL) { |
|
480 PORT_Free(met); |
|
481 return JAR_ERR_MEMORY; |
|
482 } |
|
483 |
|
484 if ((met->info = PORT_Strdup(x_info)) == NULL) { |
|
485 PORT_Free(met->header); |
|
486 PORT_Free(met); |
|
487 return JAR_ERR_MEMORY; |
|
488 } |
|
489 |
|
490 ADDITEM (jar->metainfo, jarTypeMeta, |
|
491 x_name, met, sizeof (JAR_Metainfo)); |
|
492 } |
|
493 } |
|
494 |
|
495 if (!*x_name) { |
|
496 /* Whatever that was, it wasn't an entry, because we didn't get a |
|
497 name. We don't really have anything, so don't record this. */ |
|
498 continue; |
|
499 } |
|
500 |
|
501 dig = PORT_ZNew(JAR_Digest); |
|
502 if (dig == NULL) |
|
503 return JAR_ERR_MEMORY; |
|
504 |
|
505 if (*x_md5) { |
|
506 unsigned int binary_length; |
|
507 unsigned char *binary_digest; |
|
508 |
|
509 binary_digest = ATOB_AsciiToData (x_md5, &binary_length); |
|
510 PORT_Assert( binary_length == MD5_LENGTH ); |
|
511 if (binary_length != MD5_LENGTH) { |
|
512 PORT_Free(dig); |
|
513 return JAR_ERR_CORRUPT; |
|
514 } |
|
515 memcpy (dig->md5, binary_digest, MD5_LENGTH); |
|
516 dig->md5_status = jarHashPresent; |
|
517 } |
|
518 |
|
519 if (*x_sha ) { |
|
520 unsigned int binary_length; |
|
521 unsigned char *binary_digest; |
|
522 |
|
523 binary_digest = ATOB_AsciiToData (x_sha, &binary_length); |
|
524 PORT_Assert( binary_length == SHA1_LENGTH ); |
|
525 if (binary_length != SHA1_LENGTH) { |
|
526 PORT_Free(dig); |
|
527 return JAR_ERR_CORRUPT; |
|
528 } |
|
529 memcpy (dig->sha1, binary_digest, SHA1_LENGTH); |
|
530 dig->sha1_status = jarHashPresent; |
|
531 } |
|
532 |
|
533 PORT_Assert( type == jarTypeMF || type == jarTypeSF ); |
|
534 if (type == jarTypeMF) { |
|
535 ADDITEM (jar->hashes, jarTypeMF, x_name, dig, sizeof (JAR_Digest)); |
|
536 } else if (type == jarTypeSF) { |
|
537 ADDITEM (signer->sf, jarTypeSF, x_name, dig, sizeof (JAR_Digest)); |
|
538 } else { |
|
539 PORT_Free(dig); |
|
540 return JAR_ERR_ORDER; |
|
541 } |
|
542 |
|
543 /* we're placing these calculated digests of manifest.mf |
|
544 sections in a list where they can subsequently be forgotten */ |
|
545 if (type == jarTypeMF && mfdig) { |
|
546 ADDITEM (jar->manifest, jarTypeSect, |
|
547 x_name, mfdig, sizeof (JAR_Digest)); |
|
548 mfdig = NULL; |
|
549 } |
|
550 |
|
551 /* Retrieve our saved SHA1 digest from saved copy and check digests. |
|
552 This is just comparing the digest of the MF section as indicated in |
|
553 the SF file with the one we remembered from parsing the MF file */ |
|
554 |
|
555 if (type == jarTypeSF) { |
|
556 if ((status = jar_internal_digest(jar, path, x_name, dig)) < 0) |
|
557 return status; |
|
558 } |
|
559 } |
|
560 |
|
561 return 0; |
|
562 } |
|
563 |
|
564 static int |
|
565 jar_internal_digest(JAR *jar, const char *path, char *x_name, JAR_Digest *dig) |
|
566 { |
|
567 int cv; |
|
568 int status; |
|
569 |
|
570 JAR_Digest *savdig; |
|
571 |
|
572 savdig = jar_get_mf_digest(jar, x_name); |
|
573 if (savdig == NULL) { |
|
574 /* no .mf digest for this pathname */ |
|
575 status = jar_signal(JAR_ERR_ENTRY, jar, path, x_name); |
|
576 if (status < 0) |
|
577 return 0; /* was continue; */ |
|
578 return status; |
|
579 } |
|
580 |
|
581 /* check for md5 consistency */ |
|
582 if (dig->md5_status) { |
|
583 cv = PORT_Memcmp(savdig->md5, dig->md5, MD5_LENGTH); |
|
584 /* md5 hash of .mf file is not what expected */ |
|
585 if (cv) { |
|
586 status = jar_signal(JAR_ERR_HASH, jar, path, x_name); |
|
587 |
|
588 /* bad hash, man */ |
|
589 dig->md5_status = jarHashBad; |
|
590 savdig->md5_status = jarHashBad; |
|
591 |
|
592 if (status < 0) |
|
593 return 0; /* was continue; */ |
|
594 return status; |
|
595 } |
|
596 } |
|
597 |
|
598 /* check for sha1 consistency */ |
|
599 if (dig->sha1_status) { |
|
600 cv = PORT_Memcmp(savdig->sha1, dig->sha1, SHA1_LENGTH); |
|
601 /* sha1 hash of .mf file is not what expected */ |
|
602 if (cv) { |
|
603 status = jar_signal(JAR_ERR_HASH, jar, path, x_name); |
|
604 |
|
605 /* bad hash, man */ |
|
606 dig->sha1_status = jarHashBad; |
|
607 savdig->sha1_status = jarHashBad; |
|
608 |
|
609 if (status < 0) |
|
610 return 0; /* was continue; */ |
|
611 return status; |
|
612 } |
|
613 } |
|
614 return 0; |
|
615 } |
|
616 |
|
617 #ifdef DEBUG |
|
618 /* |
|
619 * j a r _ i n s a n i t y _ c h e c k |
|
620 * |
|
621 * Check for illegal characters (or possibly so) |
|
622 * in the manifest files, to detect potential memory |
|
623 * corruption by our neighbors. Debug only, since |
|
624 * not I18N safe. |
|
625 * |
|
626 */ |
|
627 static int |
|
628 jar_insanity_check(char *data, long length) |
|
629 { |
|
630 int c; |
|
631 long off; |
|
632 |
|
633 for (off = 0; off < length; off++) { |
|
634 c = data [off]; |
|
635 if (c == '\n' || c == '\r' || (c >= ' ' && c <= 128)) |
|
636 continue; |
|
637 return JAR_ERR_CORRUPT; |
|
638 } |
|
639 return 0; |
|
640 } |
|
641 #endif |
|
642 |
|
643 /* |
|
644 * j a r _ p a r s e _ d i g i t a l _ s i g n a t u r e |
|
645 * |
|
646 * Parse an RSA or DSA (or perhaps other) digital signature. |
|
647 * Right now everything is PKCS7. |
|
648 * |
|
649 */ |
|
650 static int |
|
651 jar_parse_digital_signature(char *raw_manifest, JAR_Signer *signer, |
|
652 long length, JAR *jar) |
|
653 { |
|
654 return jar_validate_pkcs7 (jar, signer, raw_manifest, length); |
|
655 } |
|
656 |
|
657 /* |
|
658 * j a r _ a d d _ c e r t |
|
659 * |
|
660 * Add information for the given certificate |
|
661 * (or whatever) to the JAR linked list. A pointer |
|
662 * is passed for some relevant reference, say |
|
663 * for example the original certificate. |
|
664 * |
|
665 */ |
|
666 static int |
|
667 jar_add_cert(JAR *jar, JAR_Signer *signer, int type, CERTCertificate *cert) |
|
668 { |
|
669 JAR_Cert *fing; |
|
670 unsigned char *keyData; |
|
671 |
|
672 if (cert == NULL) |
|
673 return JAR_ERR_ORDER; |
|
674 |
|
675 fing = PORT_ZNew(JAR_Cert); |
|
676 if (fing == NULL) |
|
677 goto loser; |
|
678 |
|
679 fing->cert = CERT_DupCertificate (cert); |
|
680 |
|
681 /* get the certkey */ |
|
682 fing->length = cert->derIssuer.len + 2 + cert->serialNumber.len; |
|
683 fing->key = keyData = (unsigned char *) PORT_ZAlloc(fing->length); |
|
684 if (fing->key == NULL) |
|
685 goto loser; |
|
686 keyData[0] = ((cert->derIssuer.len) >> 8) & 0xff; |
|
687 keyData[1] = ((cert->derIssuer.len) & 0xff); |
|
688 PORT_Memcpy(&keyData[2], cert->derIssuer.data, cert->derIssuer.len); |
|
689 PORT_Memcpy(&keyData[2+cert->derIssuer.len], cert->serialNumber.data, |
|
690 cert->serialNumber.len); |
|
691 |
|
692 ADDITEM (signer->certs, type, NULL, fing, sizeof (JAR_Cert)); |
|
693 return 0; |
|
694 |
|
695 loser: |
|
696 if (fing) { |
|
697 if (fing->cert) |
|
698 CERT_DestroyCertificate (fing->cert); |
|
699 PORT_Free(fing); |
|
700 } |
|
701 return JAR_ERR_MEMORY; |
|
702 } |
|
703 |
|
704 /* |
|
705 * e a t _ l i n e |
|
706 * |
|
707 * Reads and/or modifies input buffer "data" of length "*len". |
|
708 * This function does zero, one or two of the following tasks: |
|
709 * 1) if "lines" is non-zero, it reads and discards that many lines from |
|
710 * the input. NUL characters are treated as end-of-line characters, |
|
711 * not as end-of-input characters. The input is NOT NUL terminated. |
|
712 * Note: presently, all callers pass either 0 or 1 for lines. |
|
713 * 2) After skipping the specified number of input lines, if "eating" is |
|
714 * non-zero, it finds the end of the next line of input and replaces |
|
715 * the end of line character(s) with a NUL character. |
|
716 * This function modifies the input buffer, containing the file, in place. |
|
717 * This function handles PC, Mac, and Unix style text files. |
|
718 * On entry, *len contains the maximum number of characters that this |
|
719 * function should ever examine, starting with the character in *data. |
|
720 * On return, *len is reduced by the number of characters skipped by the |
|
721 * first task, if any; |
|
722 * If lines is zero and eating is false, this function returns |
|
723 * the value in the data argument, but otherwise does nothing. |
|
724 */ |
|
725 static char * |
|
726 jar_eat_line(int lines, int eating, char *data, long *len) |
|
727 { |
|
728 char *start = data; |
|
729 long maxLen = *len; |
|
730 |
|
731 if (maxLen <= 0) |
|
732 return start; |
|
733 |
|
734 #define GO_ON ((data - start) < maxLen) |
|
735 |
|
736 /* Eat the requisite number of lines, if any; |
|
737 prior to terminating the current line with a 0. */ |
|
738 for (/* yip */ ; lines > 0; lines--) { |
|
739 while (GO_ON && *data && *data != '\r' && *data != '\n') |
|
740 data++; |
|
741 |
|
742 /* Eat any leading CR */ |
|
743 if (GO_ON && *data == '\r') |
|
744 data++; |
|
745 |
|
746 /* After the CR, ok to eat one LF */ |
|
747 if (GO_ON && *data == '\n') |
|
748 data++; |
|
749 |
|
750 /* If there are NULs, this function probably put them there */ |
|
751 while (GO_ON && !*data) |
|
752 data++; |
|
753 } |
|
754 maxLen -= data - start; /* we have this many characters left. */ |
|
755 *len = maxLen; |
|
756 start = data; /* now start again here. */ |
|
757 if (maxLen > 0 && eating) { |
|
758 /* Terminate this line with a 0 */ |
|
759 while (GO_ON && *data && *data != '\n' && *data != '\r') |
|
760 data++; |
|
761 |
|
762 /* If not past the end, we are allowed to eat one CR */ |
|
763 if (GO_ON && *data == '\r') |
|
764 *data++ = 0; |
|
765 |
|
766 /* After the CR (if any), if not past the end, ok to eat one LF */ |
|
767 if (GO_ON && *data == '\n') |
|
768 *data++ = 0; |
|
769 } |
|
770 return start; |
|
771 } |
|
772 #undef GO_ON |
|
773 |
|
774 /* |
|
775 * j a r _ d i g e s t _ s e c t i o n |
|
776 * |
|
777 * Return the digests of the next section of the manifest file. |
|
778 * Does not damage the manifest file, unlike parse_manifest. |
|
779 * |
|
780 */ |
|
781 static JAR_Digest * |
|
782 jar_digest_section(char *manifest, long length) |
|
783 { |
|
784 long global_len; |
|
785 char *global_end; |
|
786 |
|
787 global_end = manifest; |
|
788 global_len = length; |
|
789 |
|
790 while (global_len > 0) { |
|
791 global_end = jar_eat_line(1, PR_FALSE, global_end, &global_len); |
|
792 if (global_len > 0 && (*global_end == 0 || *global_end == '\n')) |
|
793 break; |
|
794 } |
|
795 return JAR_calculate_digest (manifest, global_end - manifest); |
|
796 } |
|
797 |
|
798 /* |
|
799 * J A R _ v e r i f y _ d i g e s t |
|
800 * |
|
801 * Verifies that a precalculated digest matches the |
|
802 * expected value in the manifest. |
|
803 * |
|
804 */ |
|
805 int PR_CALLBACK |
|
806 JAR_verify_digest(JAR *jar, const char *name, JAR_Digest *dig) |
|
807 { |
|
808 JAR_Item *it; |
|
809 JAR_Digest *shindig; |
|
810 ZZLink *link; |
|
811 ZZList *list = jar->hashes; |
|
812 int result1 = 0; |
|
813 int result2 = 0; |
|
814 |
|
815 |
|
816 if (jar->valid < 0) { |
|
817 /* signature not valid */ |
|
818 return JAR_ERR_SIG; |
|
819 } |
|
820 if (ZZ_ListEmpty (list)) { |
|
821 /* empty list */ |
|
822 return JAR_ERR_PNF; |
|
823 } |
|
824 |
|
825 for (link = ZZ_ListHead (list); |
|
826 !ZZ_ListIterDone (list, link); |
|
827 link = link->next) { |
|
828 it = link->thing; |
|
829 if (it->type == jarTypeMF |
|
830 && it->pathname && !PORT_Strcmp(it->pathname, name)) { |
|
831 shindig = (JAR_Digest *) it->data; |
|
832 if (shindig->md5_status) { |
|
833 if (shindig->md5_status == jarHashBad) |
|
834 return JAR_ERR_HASH; |
|
835 result1 = memcmp (dig->md5, shindig->md5, MD5_LENGTH); |
|
836 } |
|
837 if (shindig->sha1_status) { |
|
838 if (shindig->sha1_status == jarHashBad) |
|
839 return JAR_ERR_HASH; |
|
840 result2 = memcmp (dig->sha1, shindig->sha1, SHA1_LENGTH); |
|
841 } |
|
842 return (result1 == 0 && result2 == 0) ? 0 : JAR_ERR_HASH; |
|
843 } |
|
844 } |
|
845 return JAR_ERR_PNF; |
|
846 } |
|
847 |
|
848 |
|
849 |
|
850 |
|
851 |
|
852 |
|
853 |
|
854 /* |
|
855 * J A R _ f e t c h _ c e r t |
|
856 * |
|
857 * Given an opaque identifier of a certificate, |
|
858 * return the full certificate. |
|
859 * |
|
860 * The new function, which retrieves by key. |
|
861 * |
|
862 */ |
|
863 CERTCertificate * |
|
864 JAR_fetch_cert(long length, void *key) |
|
865 { |
|
866 CERTIssuerAndSN issuerSN; |
|
867 CERTCertificate *cert = NULL; |
|
868 CERTCertDBHandle *certdb; |
|
869 |
|
870 certdb = JAR_open_database(); |
|
871 if (certdb) { |
|
872 unsigned char *keyData = (unsigned char *)key; |
|
873 issuerSN.derIssuer.len = (keyData[0] << 8) + keyData[0]; |
|
874 issuerSN.derIssuer.data = &keyData[2]; |
|
875 issuerSN.serialNumber.len = length - (2 + issuerSN.derIssuer.len); |
|
876 issuerSN.serialNumber.data = &keyData[2+issuerSN.derIssuer.len]; |
|
877 cert = CERT_FindCertByIssuerAndSN (certdb, &issuerSN); |
|
878 JAR_close_database (certdb); |
|
879 } |
|
880 return cert; |
|
881 } |
|
882 |
|
883 /* |
|
884 * j a r _ g e t _ m f _ d i g e s t |
|
885 * |
|
886 * Retrieve a corresponding saved digest over a section |
|
887 * of the main manifest file. |
|
888 * |
|
889 */ |
|
890 static JAR_Digest * |
|
891 jar_get_mf_digest(JAR *jar, char *pathname) |
|
892 { |
|
893 JAR_Item *it; |
|
894 JAR_Digest *dig; |
|
895 ZZLink *link; |
|
896 ZZList *list = jar->manifest; |
|
897 |
|
898 if (ZZ_ListEmpty (list)) |
|
899 return NULL; |
|
900 |
|
901 for (link = ZZ_ListHead (list); |
|
902 !ZZ_ListIterDone (list, link); |
|
903 link = link->next) { |
|
904 it = link->thing; |
|
905 if (it->type == jarTypeSect |
|
906 && it->pathname && !PORT_Strcmp(it->pathname, pathname)) { |
|
907 dig = (JAR_Digest *) it->data; |
|
908 return dig; |
|
909 } |
|
910 } |
|
911 return NULL; |
|
912 } |
|
913 |
|
914 /* |
|
915 * j a r _ b a s e n a m e |
|
916 * |
|
917 * Return the basename -- leading components of path stripped off, |
|
918 * extension ripped off -- of a path. |
|
919 * |
|
920 */ |
|
921 static char * |
|
922 jar_basename(const char *path) |
|
923 { |
|
924 char *pith, *e, *basename, *ext; |
|
925 |
|
926 if (path == NULL) |
|
927 return PORT_Strdup(""); |
|
928 |
|
929 pith = PORT_Strdup(path); |
|
930 basename = pith; |
|
931 while (1) { |
|
932 for (e = basename; *e && *e != '/' && *e != '\\'; e++) |
|
933 /* yip */ ; |
|
934 if (*e) |
|
935 basename = ++e; |
|
936 else |
|
937 break; |
|
938 } |
|
939 |
|
940 if ((ext = PORT_Strrchr(basename, '.')) != NULL) |
|
941 *ext = 0; |
|
942 |
|
943 /* We already have the space allocated */ |
|
944 PORT_Strcpy(pith, basename); |
|
945 return pith; |
|
946 } |
|
947 |
|
948 /* |
|
949 * + + + + + + + + + + + + + + + |
|
950 * |
|
951 * CRYPTO ROUTINES FOR JAR |
|
952 * |
|
953 * The following functions are the cryptographic |
|
954 * interface to PKCS7 for Jarnatures. |
|
955 * |
|
956 * + + + + + + + + + + + + + + + |
|
957 * |
|
958 */ |
|
959 |
|
960 /* |
|
961 * j a r _ c a t c h _ b y t e s |
|
962 * |
|
963 * In the event signatures contain enveloped data, it will show up here. |
|
964 * But note that the lib/pkcs7 routines aren't ready for it. |
|
965 * |
|
966 */ |
|
967 static void |
|
968 jar_catch_bytes(void *arg, const char *buf, unsigned long len) |
|
969 { |
|
970 /* Actually this should never be called, since there is |
|
971 presumably no data in the signature itself. */ |
|
972 } |
|
973 |
|
974 /* |
|
975 * j a r _ v a l i d a t e _ p k c s 7 |
|
976 * |
|
977 * Validate (and decode, if necessary) a binary pkcs7 |
|
978 * signature in DER format. |
|
979 * |
|
980 */ |
|
981 static int |
|
982 jar_validate_pkcs7(JAR *jar, JAR_Signer *signer, char *data, long length) |
|
983 { |
|
984 |
|
985 SEC_PKCS7ContentInfo *cinfo = NULL; |
|
986 SEC_PKCS7DecoderContext *dcx; |
|
987 PRBool goodSig; |
|
988 int status = 0; |
|
989 SECItem detdig; |
|
990 |
|
991 PORT_Assert( jar != NULL && signer != NULL ); |
|
992 |
|
993 if (jar == NULL || signer == NULL) |
|
994 return JAR_ERR_ORDER; |
|
995 |
|
996 signer->valid = JAR_ERR_SIG; |
|
997 |
|
998 /* We need a context if we can get one */ |
|
999 dcx = SEC_PKCS7DecoderStart(jar_catch_bytes, NULL /*cb_arg*/, |
|
1000 NULL /*getpassword*/, jar->mw, |
|
1001 NULL, NULL, NULL); |
|
1002 if (dcx == NULL) { |
|
1003 /* strange pkcs7 failure */ |
|
1004 return JAR_ERR_PK7; |
|
1005 } |
|
1006 |
|
1007 SEC_PKCS7DecoderUpdate (dcx, data, length); |
|
1008 cinfo = SEC_PKCS7DecoderFinish (dcx); |
|
1009 if (cinfo == NULL) { |
|
1010 /* strange pkcs7 failure */ |
|
1011 return JAR_ERR_PK7; |
|
1012 } |
|
1013 if (SEC_PKCS7ContentIsEncrypted (cinfo)) { |
|
1014 /* content was encrypted, fail */ |
|
1015 return JAR_ERR_PK7; |
|
1016 } |
|
1017 if (SEC_PKCS7ContentIsSigned (cinfo) == PR_FALSE) { |
|
1018 /* content was not signed, fail */ |
|
1019 return JAR_ERR_PK7; |
|
1020 } |
|
1021 |
|
1022 PORT_SetError(0); |
|
1023 |
|
1024 /* use SHA1 only */ |
|
1025 detdig.len = SHA1_LENGTH; |
|
1026 detdig.data = signer->digest->sha1; |
|
1027 goodSig = SEC_PKCS7VerifyDetachedSignature(cinfo, |
|
1028 certUsageObjectSigner, |
|
1029 &detdig, HASH_AlgSHA1, |
|
1030 PR_FALSE); |
|
1031 jar_gather_signers(jar, signer, cinfo); |
|
1032 if (goodSig == PR_TRUE) { |
|
1033 /* signature is valid */ |
|
1034 signer->valid = 0; |
|
1035 } else { |
|
1036 status = PORT_GetError(); |
|
1037 PORT_Assert( status < 0 ); |
|
1038 if (status >= 0) |
|
1039 status = JAR_ERR_SIG; |
|
1040 jar->valid = status; |
|
1041 signer->valid = status; |
|
1042 } |
|
1043 jar->pkcs7 = PR_TRUE; |
|
1044 signer->pkcs7 = PR_TRUE; |
|
1045 SEC_PKCS7DestroyContentInfo(cinfo); |
|
1046 return status; |
|
1047 } |
|
1048 |
|
1049 /* |
|
1050 * j a r _ g a t h e r _ s i g n e r s |
|
1051 * |
|
1052 * Add the single signer of this signature to the |
|
1053 * certificate linked list. |
|
1054 * |
|
1055 */ |
|
1056 static int |
|
1057 jar_gather_signers(JAR *jar, JAR_Signer *signer, SEC_PKCS7ContentInfo *cinfo) |
|
1058 { |
|
1059 int result; |
|
1060 CERTCertificate *cert; |
|
1061 CERTCertDBHandle *certdb; |
|
1062 SEC_PKCS7SignedData *sdp = cinfo->content.signedData; |
|
1063 SEC_PKCS7SignerInfo **pksigners, *pksigner; |
|
1064 |
|
1065 if (sdp == NULL) |
|
1066 return JAR_ERR_PK7; |
|
1067 |
|
1068 pksigners = sdp->signerInfos; |
|
1069 /* permit exactly one signer */ |
|
1070 if (pksigners == NULL || pksigners [0] == NULL || pksigners [1] != NULL) |
|
1071 return JAR_ERR_PK7; |
|
1072 |
|
1073 pksigner = *pksigners; |
|
1074 cert = pksigner->cert; |
|
1075 |
|
1076 if (cert == NULL) |
|
1077 return JAR_ERR_PK7; |
|
1078 |
|
1079 certdb = JAR_open_database(); |
|
1080 if (certdb == NULL) |
|
1081 return JAR_ERR_GENERAL; |
|
1082 |
|
1083 result = jar_add_cert(jar, signer, jarTypeSign, cert); |
|
1084 JAR_close_database (certdb); |
|
1085 return result; |
|
1086 } |
|
1087 |
|
1088 /* |
|
1089 * j a r _ o p e n _ d a t a b a s e |
|
1090 * |
|
1091 * Open the certificate database, |
|
1092 * for use by JAR functions. |
|
1093 * |
|
1094 */ |
|
1095 CERTCertDBHandle * |
|
1096 JAR_open_database(void) |
|
1097 { |
|
1098 return CERT_GetDefaultCertDB(); |
|
1099 } |
|
1100 |
|
1101 /* |
|
1102 * j a r _ c l o s e _ d a t a b a s e |
|
1103 * |
|
1104 * Close the certificate database. |
|
1105 * For use by JAR functions. |
|
1106 * |
|
1107 */ |
|
1108 int |
|
1109 JAR_close_database(CERTCertDBHandle *certdb) |
|
1110 { |
|
1111 return 0; |
|
1112 } |
|
1113 |
|
1114 |
|
1115 /* |
|
1116 * j a r _ s i g n a l |
|
1117 * |
|
1118 * Nonfatal errors come here to callback Java. |
|
1119 * |
|
1120 */ |
|
1121 static int |
|
1122 jar_signal(int status, JAR *jar, const char *metafile, char *pathname) |
|
1123 { |
|
1124 char *errstring = JAR_get_error (status); |
|
1125 if (jar->signal) { |
|
1126 (*jar->signal) (status, jar, metafile, pathname, errstring); |
|
1127 return 0; |
|
1128 } |
|
1129 return status; |
|
1130 } |
|
1131 |
|
1132 /* |
|
1133 * j a r _ a p p e n d |
|
1134 * |
|
1135 * Tack on an element to one of a JAR's linked |
|
1136 * lists, with rudimentary error handling. |
|
1137 * |
|
1138 */ |
|
1139 int |
|
1140 jar_append(ZZList *list, int type, char *pathname, void *data, size_t size) |
|
1141 { |
|
1142 JAR_Item *it = PORT_ZNew(JAR_Item); |
|
1143 ZZLink *entity; |
|
1144 |
|
1145 if (it == NULL) |
|
1146 goto loser; |
|
1147 |
|
1148 if (pathname) { |
|
1149 it->pathname = PORT_Strdup(pathname); |
|
1150 if (it->pathname == NULL) |
|
1151 goto loser; |
|
1152 } |
|
1153 |
|
1154 it->type = (jarType)type; |
|
1155 it->data = (unsigned char *) data; |
|
1156 it->size = size; |
|
1157 entity = ZZ_NewLink (it); |
|
1158 if (entity) { |
|
1159 ZZ_AppendLink (list, entity); |
|
1160 return 0; |
|
1161 } |
|
1162 |
|
1163 loser: |
|
1164 if (it) { |
|
1165 if (it->pathname) |
|
1166 PORT_Free(it->pathname); |
|
1167 PORT_Free(it); |
|
1168 } |
|
1169 return JAR_ERR_MEMORY; |
|
1170 } |