37 |
|
|
38 |
package java.util.jar; |
package java.util.jar; |
39 |
|
|
40 |
|
import gnu.java.io.Base64InputStream; |
41 |
|
import gnu.java.security.OID; |
42 |
|
import gnu.java.security.pkcs.PKCS7SignedData; |
43 |
|
import gnu.java.security.pkcs.SignerInfo; |
44 |
|
|
45 |
|
import java.io.ByteArrayOutputStream; |
46 |
import java.io.File; |
import java.io.File; |
47 |
import java.io.FileNotFoundException; |
import java.io.FileNotFoundException; |
48 |
|
import java.io.FilterInputStream; |
49 |
import java.io.IOException; |
import java.io.IOException; |
50 |
import java.io.InputStream; |
import java.io.InputStream; |
51 |
|
|
52 |
|
import java.security.InvalidKeyException; |
53 |
|
import java.security.MessageDigest; |
54 |
|
import java.security.NoSuchAlgorithmException; |
55 |
|
import java.security.Signature; |
56 |
|
import java.security.SignatureException; |
57 |
|
import java.security.cert.CRLException; |
58 |
|
import java.security.cert.Certificate; |
59 |
|
import java.security.cert.CertificateException; |
60 |
|
import java.security.cert.X509Certificate; |
61 |
|
|
62 |
|
import java.util.Arrays; |
63 |
|
import java.util.HashMap; |
64 |
|
import java.util.HashSet; |
65 |
|
import java.util.Iterator; |
66 |
import java.util.Enumeration; |
import java.util.Enumeration; |
67 |
|
import java.util.LinkedList; |
68 |
|
import java.util.List; |
69 |
|
import java.util.Map; |
70 |
|
import java.util.Set; |
71 |
|
|
72 |
import java.util.zip.ZipEntry; |
import java.util.zip.ZipEntry; |
73 |
import java.util.zip.ZipException; |
import java.util.zip.ZipException; |
74 |
import java.util.zip.ZipFile; |
import java.util.zip.ZipFile; |
79 |
* Note that this class is not a subclass of java.io.File but a subclass of |
* Note that this class is not a subclass of java.io.File but a subclass of |
80 |
* java.util.zip.ZipFile and you can only read JarFiles with it (although |
* java.util.zip.ZipFile and you can only read JarFiles with it (although |
81 |
* there are constructors that take a File object). |
* there are constructors that take a File object). |
|
* <p> |
|
|
* XXX - verification of Manifest signatures is not yet implemented. |
|
82 |
* |
* |
83 |
* @since 1.2 |
* @since 1.2 |
84 |
* @author Mark Wielaard (mark@klomp.org) |
* @author Mark Wielaard (mark@klomp.org) |
85 |
|
* @author Casey Marshall (csm@gnu.org) wrote the certificate and entry |
86 |
|
* verification code. |
87 |
*/ |
*/ |
88 |
public class JarFile extends ZipFile |
public class JarFile extends ZipFile |
89 |
{ |
{ |
92 |
/** The name of the manifest entry: META-INF/MANIFEST.MF */ |
/** The name of the manifest entry: META-INF/MANIFEST.MF */ |
93 |
public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; |
public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; |
94 |
|
|
95 |
|
/** The META-INF directory entry. */ |
96 |
|
private static final String META_INF = "META-INF/"; |
97 |
|
|
98 |
|
/** The suffix for PKCS7 DSA signature entries. */ |
99 |
|
private static final String PKCS7_DSA_SUFFIX = ".DSA"; |
100 |
|
|
101 |
|
/** The suffix for PKCS7 RSA signature entries. */ |
102 |
|
private static final String PKCS7_RSA_SUFFIX = ".RSA"; |
103 |
|
|
104 |
|
/** The suffix for digest attributes. */ |
105 |
|
private static final String DIGEST_KEY_SUFFIX = "-Digest"; |
106 |
|
|
107 |
|
/** The suffix for signature files. */ |
108 |
|
private static final String SF_SUFFIX = ".SF"; |
109 |
|
|
110 |
|
// Signature OIDs. |
111 |
|
private static final OID MD2_OID = new OID("1.2.840.113549.2.2"); |
112 |
|
private static final OID MD4_OID = new OID("1.2.840.113549.2.4"); |
113 |
|
private static final OID MD5_OID = new OID("1.2.840.113549.2.5"); |
114 |
|
private static final OID SHA1_OID = new OID("1.3.14.3.2.26"); |
115 |
|
private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1"); |
116 |
|
private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1"); |
117 |
|
|
118 |
/** |
/** |
119 |
* The manifest of this file, if any, otherwise null. |
* The manifest of this file, if any, otherwise null. |
120 |
* Read when first needed. |
* Read when first needed. |
122 |
private Manifest manifest; |
private Manifest manifest; |
123 |
|
|
124 |
/** Whether to verify the manifest and all entries. */ |
/** Whether to verify the manifest and all entries. */ |
125 |
private boolean verify; |
boolean verify; |
126 |
|
|
127 |
/** Whether the has already been loaded. */ |
/** Whether the has already been loaded. */ |
128 |
private boolean manifestRead = false; |
private boolean manifestRead = false; |
129 |
|
|
130 |
|
/** Whether the signature files have been loaded. */ |
131 |
|
boolean signaturesRead = false; |
132 |
|
|
133 |
|
/** |
134 |
|
* A map between entry names and booleans, signaling whether or |
135 |
|
* not that entry has been verified. |
136 |
|
* Only be accessed with lock on this JarFile*/ |
137 |
|
HashMap verified = new HashMap(); |
138 |
|
|
139 |
|
/** |
140 |
|
* A mapping from entry name to certificates, if any. |
141 |
|
* Only accessed with lock on this JarFile. |
142 |
|
*/ |
143 |
|
HashMap entryCerts; |
144 |
|
|
145 |
|
static boolean DEBUG = false; |
146 |
|
static void debug(Object msg) |
147 |
|
{ |
148 |
|
System.err.print(JarFile.class.getName()); |
149 |
|
System.err.print(" >>> "); |
150 |
|
System.err.println(msg); |
151 |
|
} |
152 |
|
|
153 |
// Constructors |
// Constructors |
154 |
|
|
155 |
/** |
/** |
308 |
*/ |
*/ |
309 |
public Enumeration entries() throws IllegalStateException |
public Enumeration entries() throws IllegalStateException |
310 |
{ |
{ |
311 |
return new JarEnumeration(super.entries()); |
return new JarEnumeration(super.entries(), this); |
312 |
} |
} |
313 |
|
|
314 |
/** |
/** |
315 |
* Wraps a given Zip Entries Enumeration. For every zip entry a |
* Wraps a given Zip Entries Enumeration. For every zip entry a |
316 |
* JarEntry is created and the corresponding Attributes are looked up. |
* JarEntry is created and the corresponding Attributes are looked up. |
|
* XXX - Should also look up the certificates. |
|
317 |
*/ |
*/ |
318 |
private class JarEnumeration implements Enumeration |
private static class JarEnumeration implements Enumeration |
319 |
{ |
{ |
320 |
|
|
321 |
private final Enumeration entries; |
private final Enumeration entries; |
322 |
|
private final JarFile jarfile; |
323 |
|
|
324 |
JarEnumeration(Enumeration e) |
JarEnumeration(Enumeration e, JarFile f) |
325 |
{ |
{ |
326 |
entries = e; |
entries = e; |
327 |
|
jarfile = f; |
328 |
} |
} |
329 |
|
|
330 |
public boolean hasMoreElements() |
public boolean hasMoreElements() |
339 |
Manifest manifest; |
Manifest manifest; |
340 |
try |
try |
341 |
{ |
{ |
342 |
manifest = getManifest(); |
manifest = jarfile.getManifest(); |
343 |
} |
} |
344 |
catch (IOException ioe) |
catch (IOException ioe) |
345 |
{ |
{ |
350 |
{ |
{ |
351 |
jar.attr = manifest.getAttributes(jar.getName()); |
jar.attr = manifest.getAttributes(jar.getName()); |
352 |
} |
} |
353 |
// XXX jar.certs |
|
354 |
|
synchronized(jarfile) |
355 |
|
{ |
356 |
|
if (!jarfile.signaturesRead) |
357 |
|
try |
358 |
|
{ |
359 |
|
jarfile.readSignatures(); |
360 |
|
} |
361 |
|
catch (IOException ioe) |
362 |
|
{ |
363 |
|
if (JarFile.DEBUG) |
364 |
|
{ |
365 |
|
JarFile.debug(ioe); |
366 |
|
ioe.printStackTrace(); |
367 |
|
} |
368 |
|
jarfile.signaturesRead = true; // fudge it. |
369 |
|
} |
370 |
|
|
371 |
|
// Include the certificates only if we have asserted that the |
372 |
|
// signatures are valid. This means the certificates will not be |
373 |
|
// available if the entry hasn't been read yet. |
374 |
|
if (jarfile.entryCerts != null |
375 |
|
&& jarfile.verified.get(zip.getName()) == Boolean.TRUE) |
376 |
|
{ |
377 |
|
Set certs = (Set) jarfile.entryCerts.get(jar.getName()); |
378 |
|
if (certs != null) |
379 |
|
jar.certs = (Certificate[]) |
380 |
|
certs.toArray(new Certificate[certs.size()]); |
381 |
|
} |
382 |
|
} |
383 |
return jar; |
return jar; |
384 |
} |
} |
385 |
} |
} |
389 |
* It actually returns a JarEntry not a zipEntry |
* It actually returns a JarEntry not a zipEntry |
390 |
* @param name XXX |
* @param name XXX |
391 |
*/ |
*/ |
392 |
public ZipEntry getEntry(String name) |
public synchronized ZipEntry getEntry(String name) |
393 |
{ |
{ |
394 |
ZipEntry entry = super.getEntry(name); |
ZipEntry entry = super.getEntry(name); |
395 |
if (entry != null) |
if (entry != null) |
408 |
if (manifest != null) |
if (manifest != null) |
409 |
{ |
{ |
410 |
jarEntry.attr = manifest.getAttributes(name); |
jarEntry.attr = manifest.getAttributes(name); |
411 |
// XXX jarEntry.certs |
} |
412 |
|
|
413 |
|
if (!signaturesRead) |
414 |
|
try |
415 |
|
{ |
416 |
|
readSignatures(); |
417 |
|
} |
418 |
|
catch (IOException ioe) |
419 |
|
{ |
420 |
|
if (DEBUG) |
421 |
|
{ |
422 |
|
debug(ioe); |
423 |
|
ioe.printStackTrace(); |
424 |
|
} |
425 |
|
signaturesRead = true; |
426 |
|
} |
427 |
|
// See the comments in the JarEnumeration for why we do this |
428 |
|
// check. |
429 |
|
if (DEBUG) |
430 |
|
debug("entryCerts=" + entryCerts + " verified " + name |
431 |
|
+ " ? " + verified.get(name)); |
432 |
|
if (entryCerts != null && verified.get(name) == Boolean.TRUE) |
433 |
|
{ |
434 |
|
Set certs = (Set) entryCerts.get(name); |
435 |
|
if (certs != null) |
436 |
|
jarEntry.certs = (Certificate[]) |
437 |
|
certs.toArray(new Certificate[certs.size()]); |
438 |
} |
} |
439 |
return jarEntry; |
return jarEntry; |
440 |
} |
} |
442 |
} |
} |
443 |
|
|
444 |
/** |
/** |
445 |
* XXX should verify the inputstream |
* Returns an input stream for the given entry. If configured to |
446 |
* @param entry XXX |
* verify entries, the input stream returned will verify them while |
447 |
|
* the stream is read, but only on the first time. |
448 |
|
* |
449 |
|
* @param entry The entry to get the input stream for. |
450 |
* @exception ZipException XXX |
* @exception ZipException XXX |
451 |
* @exception IOException XXX |
* @exception IOException XXX |
452 |
*/ |
*/ |
453 |
public synchronized InputStream getInputStream(ZipEntry entry) throws |
public synchronized InputStream getInputStream(ZipEntry entry) throws |
454 |
ZipException, IOException |
ZipException, IOException |
455 |
{ |
{ |
456 |
return super.getInputStream(entry); // XXX verify |
// If we haven't verified the hash, do it now. |
457 |
|
if (!verified.containsKey(entry.getName()) && verify) |
458 |
|
{ |
459 |
|
if (DEBUG) |
460 |
|
debug("reading and verifying " + entry); |
461 |
|
return new EntryInputStream(entry, super.getInputStream(entry), this); |
462 |
|
} |
463 |
|
else |
464 |
|
{ |
465 |
|
if (DEBUG) |
466 |
|
debug("reading already verified entry " + entry); |
467 |
|
if (verify && verified.get(entry.getName()) == Boolean.FALSE) |
468 |
|
throw new ZipException("digest for " + entry + " is invalid"); |
469 |
|
return super.getInputStream(entry); |
470 |
|
} |
471 |
} |
} |
472 |
|
|
473 |
/** |
/** |
488 |
* Returns the manifest for this JarFile or null when the JarFile does not |
* Returns the manifest for this JarFile or null when the JarFile does not |
489 |
* contain a manifest file. |
* contain a manifest file. |
490 |
*/ |
*/ |
491 |
public Manifest getManifest() throws IOException |
public synchronized Manifest getManifest() throws IOException |
492 |
{ |
{ |
493 |
if (!manifestRead) |
if (!manifestRead) |
494 |
manifest = readManifest(); |
manifest = readManifest(); |
495 |
|
|
496 |
return manifest; |
return manifest; |
497 |
} |
} |
498 |
|
|
499 |
|
// Only called with lock on this JarFile. |
500 |
|
private void readSignatures() throws IOException |
501 |
|
{ |
502 |
|
Map pkcs7Dsa = new HashMap(); |
503 |
|
Map pkcs7Rsa = new HashMap(); |
504 |
|
Map sigFiles = new HashMap(); |
505 |
|
|
506 |
|
// Phase 1: Read all signature files. These contain the user |
507 |
|
// certificates as well as the signatures themselves. |
508 |
|
for (Enumeration e = super.entries(); e.hasMoreElements(); ) |
509 |
|
{ |
510 |
|
ZipEntry ze = (ZipEntry) e.nextElement(); |
511 |
|
String name = ze.getName(); |
512 |
|
if (name.startsWith(META_INF)) |
513 |
|
{ |
514 |
|
String alias = name.substring(META_INF.length()); |
515 |
|
if (alias.lastIndexOf('.') >= 0) |
516 |
|
alias = alias.substring(0, alias.lastIndexOf('.')); |
517 |
|
|
518 |
|
if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX)) |
519 |
|
{ |
520 |
|
if (DEBUG) |
521 |
|
debug("reading PKCS7 info from " + name + ", alias=" + alias); |
522 |
|
PKCS7SignedData sig = null; |
523 |
|
try |
524 |
|
{ |
525 |
|
sig = new PKCS7SignedData(super.getInputStream(ze)); |
526 |
|
} |
527 |
|
catch (CertificateException ce) |
528 |
|
{ |
529 |
|
IOException ioe = new IOException("certificate parsing error"); |
530 |
|
ioe.initCause(ce); |
531 |
|
throw ioe; |
532 |
|
} |
533 |
|
catch (CRLException crle) |
534 |
|
{ |
535 |
|
IOException ioe = new IOException("CRL parsing error"); |
536 |
|
ioe.initCause(crle); |
537 |
|
throw ioe; |
538 |
|
} |
539 |
|
if (name.endsWith(PKCS7_DSA_SUFFIX)) |
540 |
|
pkcs7Dsa.put(alias, sig); |
541 |
|
else if (name.endsWith(PKCS7_RSA_SUFFIX)) |
542 |
|
pkcs7Rsa.put(alias, sig); |
543 |
|
} |
544 |
|
else if (name.endsWith(SF_SUFFIX)) |
545 |
|
{ |
546 |
|
if (DEBUG) |
547 |
|
debug("reading signature file for " + alias + ": " + name); |
548 |
|
Manifest sf = new Manifest(super.getInputStream(ze)); |
549 |
|
sigFiles.put(alias, sf); |
550 |
|
if (DEBUG) |
551 |
|
debug("result: " + sf); |
552 |
|
} |
553 |
|
} |
554 |
|
} |
555 |
|
|
556 |
|
// Phase 2: verify the signatures on any signature files. |
557 |
|
Set validCerts = new HashSet(); |
558 |
|
Map entryCerts = new HashMap(); |
559 |
|
for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); ) |
560 |
|
{ |
561 |
|
int valid = 0; |
562 |
|
Map.Entry e = (Map.Entry) it.next(); |
563 |
|
String alias = (String) e.getKey(); |
564 |
|
|
565 |
|
PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias); |
566 |
|
if (sig != null) |
567 |
|
{ |
568 |
|
Certificate[] certs = sig.getCertificates(); |
569 |
|
Set signerInfos = sig.getSignerInfos(); |
570 |
|
for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) |
571 |
|
verify(certs, (SignerInfo) it2.next(), alias, validCerts); |
572 |
|
} |
573 |
|
|
574 |
|
sig = (PKCS7SignedData) pkcs7Rsa.get(alias); |
575 |
|
if (sig != null) |
576 |
|
{ |
577 |
|
Certificate[] certs = sig.getCertificates(); |
578 |
|
Set signerInfos = sig.getSignerInfos(); |
579 |
|
for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) |
580 |
|
verify(certs, (SignerInfo) it2.next(), alias, validCerts); |
581 |
|
} |
582 |
|
|
583 |
|
// It isn't a signature for anything. Punt it. |
584 |
|
if (validCerts.isEmpty()) |
585 |
|
{ |
586 |
|
it.remove(); |
587 |
|
continue; |
588 |
|
} |
589 |
|
|
590 |
|
entryCerts.put(e.getValue(), new HashSet(validCerts)); |
591 |
|
validCerts.clear(); |
592 |
|
} |
593 |
|
|
594 |
|
// Phase 3: verify the signature file signatures against the manifest, |
595 |
|
// mapping the entry name to the target certificates. |
596 |
|
this.entryCerts = new HashMap(); |
597 |
|
for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); ) |
598 |
|
{ |
599 |
|
Map.Entry e = (Map.Entry) it.next(); |
600 |
|
Manifest sigfile = (Manifest) e.getKey(); |
601 |
|
Map entries = sigfile.getEntries(); |
602 |
|
Set certificates = (Set) e.getValue(); |
603 |
|
|
604 |
|
for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); ) |
605 |
|
{ |
606 |
|
Map.Entry e2 = (Map.Entry) it2.next(); |
607 |
|
String entryname = String.valueOf(e2.getKey()); |
608 |
|
Attributes attr = (Attributes) e2.getValue(); |
609 |
|
if (verifyHashes(entryname, attr)) |
610 |
|
{ |
611 |
|
if (DEBUG) |
612 |
|
debug("entry " + entryname + " has certificates " + certificates); |
613 |
|
Set s = (Set) this.entryCerts.get(entryname); |
614 |
|
if (s != null) |
615 |
|
s.addAll(certificates); |
616 |
|
else |
617 |
|
this.entryCerts.put(entryname, new HashSet(certificates)); |
618 |
|
} |
619 |
|
} |
620 |
|
} |
621 |
|
|
622 |
|
signaturesRead = true; |
623 |
|
} |
624 |
|
|
625 |
|
/** |
626 |
|
* Tell if the given signer info is over the given alias's signature file, |
627 |
|
* given one of the certificates specified. |
628 |
|
*/ |
629 |
|
private void verify(Certificate[] certs, SignerInfo signerInfo, |
630 |
|
String alias, Set validCerts) |
631 |
|
{ |
632 |
|
Signature sig = null; |
633 |
|
try |
634 |
|
{ |
635 |
|
OID alg = signerInfo.getDigestEncryptionAlgorithmId(); |
636 |
|
if (alg.equals(DSA_ENCRYPTION_OID)) |
637 |
|
{ |
638 |
|
if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID)) |
639 |
|
return; |
640 |
|
sig = Signature.getInstance("SHA1withDSA"); |
641 |
|
} |
642 |
|
else if (alg.equals(RSA_ENCRYPTION_OID)) |
643 |
|
{ |
644 |
|
OID hash = signerInfo.getDigestAlgorithmId(); |
645 |
|
if (hash.equals(MD2_OID)) |
646 |
|
sig = Signature.getInstance("md2WithRsaEncryption"); |
647 |
|
else if (hash.equals(MD4_OID)) |
648 |
|
sig = Signature.getInstance("md4WithRsaEncryption"); |
649 |
|
else if (hash.equals(MD5_OID)) |
650 |
|
sig = Signature.getInstance("md5WithRsaEncryption"); |
651 |
|
else if (hash.equals(SHA1_OID)) |
652 |
|
sig = Signature.getInstance("sha1WithRsaEncryption"); |
653 |
|
else |
654 |
|
return; |
655 |
|
} |
656 |
|
else |
657 |
|
{ |
658 |
|
if (DEBUG) |
659 |
|
debug("unsupported signature algorithm: " + alg); |
660 |
|
return; |
661 |
|
} |
662 |
|
} |
663 |
|
catch (NoSuchAlgorithmException nsae) |
664 |
|
{ |
665 |
|
if (DEBUG) |
666 |
|
{ |
667 |
|
debug(nsae); |
668 |
|
nsae.printStackTrace(); |
669 |
|
} |
670 |
|
return; |
671 |
|
} |
672 |
|
ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX); |
673 |
|
if (sigFileEntry == null) |
674 |
|
return; |
675 |
|
for (int i = 0; i < certs.length; i++) |
676 |
|
{ |
677 |
|
if (!(certs[i] instanceof X509Certificate)) |
678 |
|
continue; |
679 |
|
X509Certificate cert = (X509Certificate) certs[i]; |
680 |
|
if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) || |
681 |
|
!cert.getSerialNumber().equals(signerInfo.getSerialNumber())) |
682 |
|
continue; |
683 |
|
try |
684 |
|
{ |
685 |
|
sig.initVerify(cert.getPublicKey()); |
686 |
|
InputStream in = super.getInputStream(sigFileEntry); |
687 |
|
if (in == null) |
688 |
|
continue; |
689 |
|
byte[] buf = new byte[1024]; |
690 |
|
int len = 0; |
691 |
|
while ((len = in.read(buf)) != -1) |
692 |
|
sig.update(buf, 0, len); |
693 |
|
if (sig.verify(signerInfo.getEncryptedDigest())) |
694 |
|
{ |
695 |
|
if (DEBUG) |
696 |
|
debug("signature for " + cert.getSubjectDN() + " is good"); |
697 |
|
validCerts.add(cert); |
698 |
|
} |
699 |
|
} |
700 |
|
catch (IOException ioe) |
701 |
|
{ |
702 |
|
continue; |
703 |
|
} |
704 |
|
catch (InvalidKeyException ike) |
705 |
|
{ |
706 |
|
continue; |
707 |
|
} |
708 |
|
catch (SignatureException se) |
709 |
|
{ |
710 |
|
continue; |
711 |
|
} |
712 |
|
} |
713 |
|
} |
714 |
|
|
715 |
|
/** |
716 |
|
* Verifies that the digest(s) in a signature file were, in fact, made |
717 |
|
* over the manifest entry for ENTRY. |
718 |
|
* |
719 |
|
* @param entry The entry name. |
720 |
|
* @param attr The attributes from the signature file to verify. |
721 |
|
*/ |
722 |
|
private boolean verifyHashes(String entry, Attributes attr) |
723 |
|
{ |
724 |
|
int verified = 0; |
725 |
|
|
726 |
|
// The bytes for ENTRY's manifest entry, which are signed in the |
727 |
|
// signature file. |
728 |
|
byte[] entryBytes = null; |
729 |
|
try |
730 |
|
{ |
731 |
|
entryBytes = readManifestEntry(super.getEntry(entry)); |
732 |
|
} |
733 |
|
catch (IOException ioe) |
734 |
|
{ |
735 |
|
if (DEBUG) |
736 |
|
{ |
737 |
|
debug(ioe); |
738 |
|
ioe.printStackTrace(); |
739 |
|
} |
740 |
|
return false; |
741 |
|
} |
742 |
|
|
743 |
|
for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) |
744 |
|
{ |
745 |
|
Map.Entry e = (Map.Entry) it.next(); |
746 |
|
String key = String.valueOf(e.getKey()); |
747 |
|
if (!key.endsWith(DIGEST_KEY_SUFFIX)) |
748 |
|
continue; |
749 |
|
String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length()); |
750 |
|
try |
751 |
|
{ |
752 |
|
byte[] hash = Base64InputStream.decode((String) e.getValue()); |
753 |
|
MessageDigest md = MessageDigest.getInstance(alg); |
754 |
|
md.update(entryBytes); |
755 |
|
byte[] hash2 = md.digest(); |
756 |
|
if (DEBUG) |
757 |
|
debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm() |
758 |
|
+ " expect=" + new java.math.BigInteger(hash).toString(16) |
759 |
|
+ " comp=" + new java.math.BigInteger(hash2).toString(16)); |
760 |
|
if (!Arrays.equals(hash, hash2)) |
761 |
|
return false; |
762 |
|
verified++; |
763 |
|
} |
764 |
|
catch (IOException ioe) |
765 |
|
{ |
766 |
|
if (DEBUG) |
767 |
|
{ |
768 |
|
debug(ioe); |
769 |
|
ioe.printStackTrace(); |
770 |
|
} |
771 |
|
return false; |
772 |
|
} |
773 |
|
catch (NoSuchAlgorithmException nsae) |
774 |
|
{ |
775 |
|
if (DEBUG) |
776 |
|
{ |
777 |
|
debug(nsae); |
778 |
|
nsae.printStackTrace(); |
779 |
|
} |
780 |
|
return false; |
781 |
|
} |
782 |
|
} |
783 |
|
|
784 |
|
// We have to find at least one valid digest. |
785 |
|
return verified > 0; |
786 |
|
} |
787 |
|
|
788 |
|
/** |
789 |
|
* Read the raw bytes that comprise a manifest entry. We can't use the |
790 |
|
* Manifest object itself, because that loses information (such as line |
791 |
|
* endings, and order of entries). |
792 |
|
*/ |
793 |
|
private byte[] readManifestEntry(ZipEntry entry) throws IOException |
794 |
|
{ |
795 |
|
InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME)); |
796 |
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
797 |
|
byte[] target = ("Name: " + entry.getName()).getBytes(); |
798 |
|
int t = 0, c, prev = -1, state = 0, l = -1; |
799 |
|
|
800 |
|
while ((c = in.read()) != -1) |
801 |
|
{ |
802 |
|
// if (DEBUG) |
803 |
|
// debug("read " |
804 |
|
// + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c))) |
805 |
|
// + " state=" + state + " prev=" |
806 |
|
// + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev))) |
807 |
|
// + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "") |
808 |
|
// + " l=" + l); |
809 |
|
switch (state) |
810 |
|
{ |
811 |
|
|
812 |
|
// Step 1: read until we find the "target" bytes: the start |
813 |
|
// of the entry we need to read. |
814 |
|
case 0: |
815 |
|
if (((byte) c) != target[t]) |
816 |
|
t = 0; |
817 |
|
else |
818 |
|
{ |
819 |
|
t++; |
820 |
|
if (t == target.length) |
821 |
|
{ |
822 |
|
out.write(target); |
823 |
|
state = 1; |
824 |
|
} |
825 |
|
} |
826 |
|
break; |
827 |
|
|
828 |
|
// Step 2: assert that there is a newline character after |
829 |
|
// the "target" bytes. |
830 |
|
case 1: |
831 |
|
if (c != '\n' && c != '\r') |
832 |
|
{ |
833 |
|
out.reset(); |
834 |
|
t = 0; |
835 |
|
state = 0; |
836 |
|
} |
837 |
|
else |
838 |
|
{ |
839 |
|
out.write(c); |
840 |
|
state = 2; |
841 |
|
} |
842 |
|
break; |
843 |
|
|
844 |
|
// Step 3: read this whole entry, until we reach an empty |
845 |
|
// line. |
846 |
|
case 2: |
847 |
|
if (c == '\n') |
848 |
|
{ |
849 |
|
out.write(c); |
850 |
|
// NL always terminates a line. |
851 |
|
if (l == 0 || (l == 1 && prev == '\r')) |
852 |
|
return out.toByteArray(); |
853 |
|
l = 0; |
854 |
|
} |
855 |
|
else |
856 |
|
{ |
857 |
|
// Here we see a blank line terminated by a CR, |
858 |
|
// followed by the next entry. Technically, `c' should |
859 |
|
// always be 'N' at this point. |
860 |
|
if (l == 1 && prev == '\r') |
861 |
|
return out.toByteArray(); |
862 |
|
out.write(c); |
863 |
|
l++; |
864 |
|
} |
865 |
|
prev = c; |
866 |
|
break; |
867 |
|
|
868 |
|
default: |
869 |
|
throw new RuntimeException("this statement should be unreachable"); |
870 |
|
} |
871 |
|
} |
872 |
|
|
873 |
|
// The last entry, with a single CR terminating the line. |
874 |
|
if (state == 2 && prev == '\r' && l == 0) |
875 |
|
return out.toByteArray(); |
876 |
|
|
877 |
|
// We should not reach this point, we didn't find the entry (or, possibly, |
878 |
|
// it is the last entry and is malformed). |
879 |
|
throw new IOException("could not find " + entry + " in manifest"); |
880 |
|
} |
881 |
|
|
882 |
|
/** |
883 |
|
* A utility class that verifies jar entries as they are read. |
884 |
|
*/ |
885 |
|
private static class EntryInputStream extends FilterInputStream |
886 |
|
{ |
887 |
|
private final JarFile jarfile; |
888 |
|
private final long length; |
889 |
|
private long pos; |
890 |
|
private final ZipEntry entry; |
891 |
|
private final byte[][] hashes; |
892 |
|
private final MessageDigest[] md; |
893 |
|
private boolean checked; |
894 |
|
|
895 |
|
EntryInputStream(final ZipEntry entry, |
896 |
|
final InputStream in, |
897 |
|
final JarFile jar) |
898 |
|
throws IOException |
899 |
|
{ |
900 |
|
super(in); |
901 |
|
this.entry = entry; |
902 |
|
this.jarfile = jar; |
903 |
|
|
904 |
|
length = entry.getSize(); |
905 |
|
pos = 0; |
906 |
|
checked = false; |
907 |
|
|
908 |
|
Attributes attr; |
909 |
|
Manifest manifest = jarfile.getManifest(); |
910 |
|
if (manifest != null) |
911 |
|
attr = manifest.getAttributes(entry.getName()); |
912 |
|
else |
913 |
|
attr = null; |
914 |
|
if (DEBUG) |
915 |
|
debug("verifying entry " + entry + " attr=" + attr); |
916 |
|
if (attr == null) |
917 |
|
{ |
918 |
|
hashes = new byte[0][]; |
919 |
|
md = new MessageDigest[0]; |
920 |
|
} |
921 |
|
else |
922 |
|
{ |
923 |
|
List hashes = new LinkedList(); |
924 |
|
List md = new LinkedList(); |
925 |
|
for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) |
926 |
|
{ |
927 |
|
Map.Entry e = (Map.Entry) it.next(); |
928 |
|
String key = String.valueOf(e.getKey()); |
929 |
|
if (key == null) |
930 |
|
continue; |
931 |
|
if (!key.endsWith(DIGEST_KEY_SUFFIX)) |
932 |
|
continue; |
933 |
|
hashes.add(Base64InputStream.decode((String) e.getValue())); |
934 |
|
try |
935 |
|
{ |
936 |
|
md.add(MessageDigest.getInstance |
937 |
|
(key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length()))); |
938 |
|
} |
939 |
|
catch (NoSuchAlgorithmException nsae) |
940 |
|
{ |
941 |
|
IOException ioe = new IOException("no such message digest: " + key); |
942 |
|
ioe.initCause(nsae); |
943 |
|
throw ioe; |
944 |
|
} |
945 |
|
} |
946 |
|
if (DEBUG) |
947 |
|
debug("digests=" + md); |
948 |
|
this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]); |
949 |
|
this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]); |
950 |
|
} |
951 |
|
} |
952 |
|
|
953 |
|
public boolean markSupported() |
954 |
|
{ |
955 |
|
return false; |
956 |
|
} |
957 |
|
|
958 |
|
public void mark(int readLimit) |
959 |
|
{ |
960 |
|
} |
961 |
|
|
962 |
|
public void reset() |
963 |
|
{ |
964 |
|
} |
965 |
|
|
966 |
|
public int read() throws IOException |
967 |
|
{ |
968 |
|
int b = super.read(); |
969 |
|
if (b == -1) |
970 |
|
{ |
971 |
|
eof(); |
972 |
|
return -1; |
973 |
|
} |
974 |
|
for (int i = 0; i < md.length; i++) |
975 |
|
md[i].update((byte) b); |
976 |
|
pos++; |
977 |
|
if (length > 0 && pos >= length) |
978 |
|
eof(); |
979 |
|
return b; |
980 |
|
} |
981 |
|
|
982 |
|
public int read(byte[] buf, int off, int len) throws IOException |
983 |
|
{ |
984 |
|
int count = super.read(buf, off, (int) Math.min(len, (length != 0 |
985 |
|
? length - pos |
986 |
|
: Integer.MAX_VALUE))); |
987 |
|
if (count == -1 || (length > 0 && pos >= length)) |
988 |
|
{ |
989 |
|
eof(); |
990 |
|
return -1; |
991 |
|
} |
992 |
|
for (int i = 0; i < md.length; i++) |
993 |
|
md[i].update(buf, off, count); |
994 |
|
pos += count; |
995 |
|
if (length != 0 && pos >= length) |
996 |
|
eof(); |
997 |
|
return count; |
998 |
|
} |
999 |
|
|
1000 |
|
public int read(byte[] buf) throws IOException |
1001 |
|
{ |
1002 |
|
return read(buf, 0, buf.length); |
1003 |
|
} |
1004 |
|
|
1005 |
|
public long skip(long bytes) throws IOException |
1006 |
|
{ |
1007 |
|
byte[] b = new byte[1024]; |
1008 |
|
long amount = 0; |
1009 |
|
while (amount < bytes) |
1010 |
|
{ |
1011 |
|
int l = read(b, 0, (int) Math.min(b.length, bytes - amount)); |
1012 |
|
if (l == -1) |
1013 |
|
break; |
1014 |
|
amount += l; |
1015 |
|
} |
1016 |
|
return amount; |
1017 |
|
} |
1018 |
|
|
1019 |
|
private void eof() throws IOException |
1020 |
|
{ |
1021 |
|
if (checked) |
1022 |
|
return; |
1023 |
|
checked = true; |
1024 |
|
for (int i = 0; i < md.length; i++) |
1025 |
|
{ |
1026 |
|
byte[] hash = md[i].digest(); |
1027 |
|
if (DEBUG) |
1028 |
|
debug("verifying " + md[i].getAlgorithm() + " expect=" |
1029 |
|
+ new java.math.BigInteger(hashes[i]).toString(16) |
1030 |
|
+ " comp=" + new java.math.BigInteger(hash).toString(16)); |
1031 |
|
if (!Arrays.equals(hash, hashes[i])) |
1032 |
|
{ |
1033 |
|
synchronized(jarfile) |
1034 |
|
{ |
1035 |
|
if (DEBUG) |
1036 |
|
debug(entry + " could NOT be verified"); |
1037 |
|
jarfile.verified.put(entry.getName(), Boolean.FALSE); |
1038 |
|
} |
1039 |
|
return; |
1040 |
|
// XXX ??? what do we do here? |
1041 |
|
// throw new ZipException("message digest mismatch"); |
1042 |
|
} |
1043 |
|
} |
1044 |
|
|
1045 |
|
synchronized(jarfile) |
1046 |
|
{ |
1047 |
|
if (DEBUG) |
1048 |
|
debug(entry + " has been VERIFIED"); |
1049 |
|
jarfile.verified.put(entry.getName(), Boolean.TRUE); |
1050 |
|
} |
1051 |
|
} |
1052 |
|
} |
1053 |
} |
} |