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. |
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 |
|
private boolean signaturesRead = false; |
132 |
|
|
133 |
|
/** A map between entry names and booleans, signaling whether or |
134 |
|
not that entry has been verified. */ |
135 |
|
private HashMap verified = new HashMap(); |
136 |
|
|
137 |
|
/** A mapping from entry name to certificates, if any. */ |
138 |
|
private HashMap entryCerts; |
139 |
|
|
140 |
|
private static boolean DEBUG = false; |
141 |
|
private static void debug(Object msg) |
142 |
|
{ |
143 |
|
System.err.print(JarFile.class.getName()); |
144 |
|
System.err.print(" >>> "); |
145 |
|
System.err.println(msg); |
146 |
|
} |
147 |
|
|
148 |
// Constructors |
// Constructors |
149 |
|
|
150 |
/** |
/** |
309 |
/** |
/** |
310 |
* Wraps a given Zip Entries Enumeration. For every zip entry a |
* Wraps a given Zip Entries Enumeration. For every zip entry a |
311 |
* 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. |
|
312 |
*/ |
*/ |
313 |
private class JarEnumeration implements Enumeration |
private class JarEnumeration implements Enumeration |
314 |
{ |
{ |
343 |
{ |
{ |
344 |
jar.attr = manifest.getAttributes(jar.getName()); |
jar.attr = manifest.getAttributes(jar.getName()); |
345 |
} |
} |
346 |
// XXX jar.certs |
|
347 |
|
if (!signaturesRead) |
348 |
|
try |
349 |
|
{ |
350 |
|
readSignatures(); |
351 |
|
} |
352 |
|
catch (IOException ioe) |
353 |
|
{ |
354 |
|
if (DEBUG) |
355 |
|
{ |
356 |
|
debug(ioe); |
357 |
|
ioe.printStackTrace(); |
358 |
|
} |
359 |
|
signaturesRead = true; // fudge it. |
360 |
|
} |
361 |
|
|
362 |
|
// Include the certificates only if we have asserted that the |
363 |
|
// signatures are valid. This means the certificates will not be |
364 |
|
// available if the entry hasn't been read yet. |
365 |
|
if (entryCerts != null && verified.containsKey(zip.getName()) |
366 |
|
&& ((Boolean) verified.get(zip.getName())).booleanValue()) |
367 |
|
{ |
368 |
|
Set certs = (Set) entryCerts.get(jar.getName()); |
369 |
|
if (certs != null) |
370 |
|
jar.certs = (Certificate[]) |
371 |
|
certs.toArray(new Certificate[certs.size()]); |
372 |
|
} |
373 |
return jar; |
return jar; |
374 |
} |
} |
375 |
} |
} |
398 |
if (manifest != null) |
if (manifest != null) |
399 |
{ |
{ |
400 |
jarEntry.attr = manifest.getAttributes(name); |
jarEntry.attr = manifest.getAttributes(name); |
401 |
// XXX jarEntry.certs |
} |
402 |
|
|
403 |
|
if (!signaturesRead) |
404 |
|
try |
405 |
|
{ |
406 |
|
readSignatures(); |
407 |
|
} |
408 |
|
catch (IOException ioe) |
409 |
|
{ |
410 |
|
if (DEBUG) |
411 |
|
{ |
412 |
|
debug(ioe); |
413 |
|
ioe.printStackTrace(); |
414 |
|
} |
415 |
|
signaturesRead = true; |
416 |
|
} |
417 |
|
// See the comments in the JarEnumeration for why we do this |
418 |
|
// check. |
419 |
|
if (DEBUG) |
420 |
|
debug("entryCerts=" + entryCerts + " verified " + name |
421 |
|
+ " ? " + verified.get(name)); |
422 |
|
if (entryCerts != null && verified.containsKey(name) |
423 |
|
&& ((Boolean) verified.get(name)).booleanValue()) |
424 |
|
{ |
425 |
|
Set certs = (Set) entryCerts.get(name); |
426 |
|
if (certs != null) |
427 |
|
jarEntry.certs = (Certificate[]) |
428 |
|
certs.toArray(new Certificate[certs.size()]); |
429 |
} |
} |
430 |
return jarEntry; |
return jarEntry; |
431 |
} |
} |
433 |
} |
} |
434 |
|
|
435 |
/** |
/** |
436 |
* XXX should verify the inputstream |
* Returns an input stream for the given entry. If configured to |
437 |
* @param entry XXX |
* verify entries, the input stream returned will verify them while |
438 |
|
* the stream is read, but only on the first time. |
439 |
|
* |
440 |
|
* @param entry The entry to get the input stream for. |
441 |
* @exception ZipException XXX |
* @exception ZipException XXX |
442 |
* @exception IOException XXX |
* @exception IOException XXX |
443 |
*/ |
*/ |
444 |
public synchronized InputStream getInputStream(ZipEntry entry) throws |
public synchronized InputStream getInputStream(ZipEntry entry) throws |
445 |
ZipException, IOException |
ZipException, IOException |
446 |
{ |
{ |
447 |
return super.getInputStream(entry); // XXX verify |
// If we haven't verified the hash, do it now. |
448 |
|
if (!verified.containsKey(entry.getName()) && verify) |
449 |
|
{ |
450 |
|
if (DEBUG) |
451 |
|
debug("reading and verifying " + entry); |
452 |
|
return new EntryInputStream(entry); |
453 |
|
} |
454 |
|
else |
455 |
|
{ |
456 |
|
if (DEBUG) |
457 |
|
debug("reading already verified entry " + entry); |
458 |
|
if (!((Boolean) verified.get(entry.getName())).booleanValue()) |
459 |
|
throw new ZipException("digest for " + entry + " is invalid"); |
460 |
|
return super.getInputStream(entry); |
461 |
|
} |
462 |
} |
} |
463 |
|
|
464 |
/** |
/** |
486 |
|
|
487 |
return manifest; |
return manifest; |
488 |
} |
} |
489 |
|
|
490 |
|
private void readSignatures() throws IOException |
491 |
|
{ |
492 |
|
Map pkcs7Dsa = new HashMap(); |
493 |
|
Map pkcs7Rsa = new HashMap(); |
494 |
|
Map sigFiles = new HashMap(); |
495 |
|
|
496 |
|
// Phase 1: Read all signature files. These contain the user |
497 |
|
// certificates as well as the signatures themselves. |
498 |
|
for (Enumeration e = super.entries(); e.hasMoreElements(); ) |
499 |
|
{ |
500 |
|
ZipEntry ze = (ZipEntry) e.nextElement(); |
501 |
|
String name = ze.getName(); |
502 |
|
if (name.startsWith(META_INF)) |
503 |
|
{ |
504 |
|
String alias = name.substring(META_INF.length()); |
505 |
|
if (alias.lastIndexOf('.') >= 0) |
506 |
|
alias = alias.substring(0, alias.lastIndexOf('.')); |
507 |
|
|
508 |
|
if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX)) |
509 |
|
{ |
510 |
|
if (DEBUG) |
511 |
|
debug("reading PKCS7 info from " + name + ", alias=" + alias); |
512 |
|
PKCS7SignedData sig = null; |
513 |
|
try |
514 |
|
{ |
515 |
|
sig = new PKCS7SignedData(super.getInputStream(ze)); |
516 |
|
} |
517 |
|
catch (CertificateException ce) |
518 |
|
{ |
519 |
|
IOException ioe = new IOException("certificate parsing error"); |
520 |
|
ioe.initCause(ce); |
521 |
|
throw ioe; |
522 |
|
} |
523 |
|
catch (CRLException crle) |
524 |
|
{ |
525 |
|
IOException ioe = new IOException("CRL parsing error"); |
526 |
|
ioe.initCause(crle); |
527 |
|
throw ioe; |
528 |
|
} |
529 |
|
if (name.endsWith(PKCS7_DSA_SUFFIX)) |
530 |
|
pkcs7Dsa.put(alias, sig); |
531 |
|
else if (name.endsWith(PKCS7_RSA_SUFFIX)) |
532 |
|
pkcs7Rsa.put(alias, sig); |
533 |
|
} |
534 |
|
else if (name.endsWith(SF_SUFFIX)) |
535 |
|
{ |
536 |
|
if (DEBUG) |
537 |
|
debug("reading signature file for " + alias + ": " + name); |
538 |
|
Manifest sf = new Manifest(super.getInputStream(ze)); |
539 |
|
sigFiles.put(alias, sf); |
540 |
|
if (DEBUG) |
541 |
|
debug("result: " + sf); |
542 |
|
} |
543 |
|
} |
544 |
|
} |
545 |
|
|
546 |
|
// Phase 2: verify the signatures on any signature files. |
547 |
|
Set validCerts = new HashSet(); |
548 |
|
Map entryCerts = new HashMap(); |
549 |
|
for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); ) |
550 |
|
{ |
551 |
|
int valid = 0; |
552 |
|
Map.Entry e = (Map.Entry) it.next(); |
553 |
|
String alias = (String) e.getKey(); |
554 |
|
|
555 |
|
PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias); |
556 |
|
if (sig != null) |
557 |
|
{ |
558 |
|
Certificate[] certs = sig.getCertificates(); |
559 |
|
Set signerInfos = sig.getSignerInfos(); |
560 |
|
for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) |
561 |
|
verify(certs, (SignerInfo) it2.next(), alias, validCerts); |
562 |
|
} |
563 |
|
|
564 |
|
sig = (PKCS7SignedData) pkcs7Rsa.get(alias); |
565 |
|
if (sig != null) |
566 |
|
{ |
567 |
|
Certificate[] certs = sig.getCertificates(); |
568 |
|
Set signerInfos = sig.getSignerInfos(); |
569 |
|
for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) |
570 |
|
verify(certs, (SignerInfo) it2.next(), alias, validCerts); |
571 |
|
} |
572 |
|
|
573 |
|
// It isn't a signature for anything. Punt it. |
574 |
|
if (validCerts.isEmpty()) |
575 |
|
{ |
576 |
|
it.remove(); |
577 |
|
continue; |
578 |
|
} |
579 |
|
|
580 |
|
entryCerts.put(e.getValue(), new HashSet(validCerts)); |
581 |
|
validCerts.clear(); |
582 |
|
} |
583 |
|
|
584 |
|
// Phase 3: verify the signature file signatures against the manifest, |
585 |
|
// mapping the entry name to the target certificates. |
586 |
|
this.entryCerts = new HashMap(); |
587 |
|
for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); ) |
588 |
|
{ |
589 |
|
Map.Entry e = (Map.Entry) it.next(); |
590 |
|
Manifest sigfile = (Manifest) e.getKey(); |
591 |
|
Map entries = sigfile.getEntries(); |
592 |
|
Set certificates = (Set) e.getValue(); |
593 |
|
|
594 |
|
for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); ) |
595 |
|
{ |
596 |
|
Map.Entry e2 = (Map.Entry) it2.next(); |
597 |
|
String entryname = String.valueOf(e2.getKey()); |
598 |
|
Attributes attr = (Attributes) e2.getValue(); |
599 |
|
if (verifyHashes(entryname, attr)) |
600 |
|
{ |
601 |
|
if (DEBUG) |
602 |
|
debug("entry " + entryname + " has certificates " + certificates); |
603 |
|
Set s = (Set) this.entryCerts.get(entryname); |
604 |
|
if (s != null) |
605 |
|
s.addAll(certificates); |
606 |
|
else |
607 |
|
this.entryCerts.put(entryname, new HashSet(certificates)); |
608 |
|
} |
609 |
|
} |
610 |
|
} |
611 |
|
|
612 |
|
signaturesRead = true; |
613 |
|
} |
614 |
|
|
615 |
|
/** |
616 |
|
* Tell if the given signer info is over the given alias's signature file, |
617 |
|
* given one of the certificates specified. |
618 |
|
*/ |
619 |
|
private void verify(Certificate[] certs, SignerInfo signerInfo, |
620 |
|
String alias, Set validCerts) |
621 |
|
{ |
622 |
|
Signature sig = null; |
623 |
|
try |
624 |
|
{ |
625 |
|
OID alg = signerInfo.getDigestEncryptionAlgorithmId(); |
626 |
|
if (alg.equals(DSA_ENCRYPTION_OID)) |
627 |
|
{ |
628 |
|
if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID)) |
629 |
|
return; |
630 |
|
sig = Signature.getInstance("SHA1withDSA"); |
631 |
|
} |
632 |
|
else if (alg.equals(RSA_ENCRYPTION_OID)) |
633 |
|
{ |
634 |
|
OID hash = signerInfo.getDigestAlgorithmId(); |
635 |
|
if (hash.equals(MD2_OID)) |
636 |
|
sig = Signature.getInstance("md2WithRsaEncryption"); |
637 |
|
else if (hash.equals(MD4_OID)) |
638 |
|
sig = Signature.getInstance("md4WithRsaEncryption"); |
639 |
|
else if (hash.equals(MD5_OID)) |
640 |
|
sig = Signature.getInstance("md5WithRsaEncryption"); |
641 |
|
else if (hash.equals(SHA1_OID)) |
642 |
|
sig = Signature.getInstance("sha1WithRsaEncryption"); |
643 |
|
else |
644 |
|
return; |
645 |
|
} |
646 |
|
} |
647 |
|
catch (NoSuchAlgorithmException nsae) |
648 |
|
{ |
649 |
|
if (DEBUG) |
650 |
|
{ |
651 |
|
debug(nsae); |
652 |
|
nsae.printStackTrace(); |
653 |
|
} |
654 |
|
return; |
655 |
|
} |
656 |
|
ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX); |
657 |
|
if (sigFileEntry == null) |
658 |
|
return; |
659 |
|
for (int i = 0; i < certs.length; i++) |
660 |
|
{ |
661 |
|
if (!(certs[i] instanceof X509Certificate)) |
662 |
|
continue; |
663 |
|
X509Certificate cert = (X509Certificate) certs[i]; |
664 |
|
if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) || |
665 |
|
!cert.getSerialNumber().equals(signerInfo.getSerialNumber())) |
666 |
|
continue; |
667 |
|
try |
668 |
|
{ |
669 |
|
sig.initVerify(cert.getPublicKey()); |
670 |
|
InputStream in = super.getInputStream(sigFileEntry); |
671 |
|
if (in == null) |
672 |
|
continue; |
673 |
|
byte[] buf = new byte[1024]; |
674 |
|
int len = 0; |
675 |
|
while ((len = in.read(buf)) != -1) |
676 |
|
sig.update(buf, 0, len); |
677 |
|
if (sig.verify(signerInfo.getEncryptedDigest())) |
678 |
|
{ |
679 |
|
if (DEBUG) |
680 |
|
debug("signature for " + cert.getSubjectDN() + " is good"); |
681 |
|
validCerts.add(cert); |
682 |
|
} |
683 |
|
} |
684 |
|
catch (IOException ioe) |
685 |
|
{ |
686 |
|
continue; |
687 |
|
} |
688 |
|
catch (InvalidKeyException ike) |
689 |
|
{ |
690 |
|
continue; |
691 |
|
} |
692 |
|
catch (SignatureException se) |
693 |
|
{ |
694 |
|
continue; |
695 |
|
} |
696 |
|
} |
697 |
|
} |
698 |
|
|
699 |
|
/** |
700 |
|
* Verifies that the digest(s) in a signature file were, in fact, made |
701 |
|
* over the manifest entry for ENTRY. |
702 |
|
* |
703 |
|
* @param entry The entry name. |
704 |
|
* @param attr The attributes from the signature file to verify. |
705 |
|
*/ |
706 |
|
private boolean verifyHashes(String entry, Attributes attr) |
707 |
|
{ |
708 |
|
int verified = 0; |
709 |
|
|
710 |
|
// The bytes for ENTRY's manifest entry, which are signed in the |
711 |
|
// signature file. |
712 |
|
byte[] entryBytes = null; |
713 |
|
try |
714 |
|
{ |
715 |
|
entryBytes = readManifestEntry(super.getEntry(entry)); |
716 |
|
} |
717 |
|
catch (IOException ioe) |
718 |
|
{ |
719 |
|
if (DEBUG) |
720 |
|
{ |
721 |
|
debug(ioe); |
722 |
|
ioe.printStackTrace(); |
723 |
|
} |
724 |
|
return false; |
725 |
|
} |
726 |
|
|
727 |
|
for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) |
728 |
|
{ |
729 |
|
Map.Entry e = (Map.Entry) it.next(); |
730 |
|
String key = String.valueOf(e.getKey()); |
731 |
|
if (!key.endsWith(DIGEST_KEY_SUFFIX)) |
732 |
|
continue; |
733 |
|
String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length()); |
734 |
|
try |
735 |
|
{ |
736 |
|
byte[] hash = Base64InputStream.decode((String) e.getValue()); |
737 |
|
MessageDigest md = MessageDigest.getInstance(alg); |
738 |
|
md.update(entryBytes); |
739 |
|
byte[] hash2 = md.digest(); |
740 |
|
if (DEBUG) |
741 |
|
debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm() |
742 |
|
+ " expect=" + new java.math.BigInteger(hash).toString(16) |
743 |
|
+ " comp=" + new java.math.BigInteger(hash2).toString(16)); |
744 |
|
if (!Arrays.equals(hash, hash2)) |
745 |
|
return false; |
746 |
|
verified++; |
747 |
|
} |
748 |
|
catch (IOException ioe) |
749 |
|
{ |
750 |
|
if (DEBUG) |
751 |
|
{ |
752 |
|
debug(ioe); |
753 |
|
ioe.printStackTrace(); |
754 |
|
} |
755 |
|
return false; |
756 |
|
} |
757 |
|
catch (NoSuchAlgorithmException nsae) |
758 |
|
{ |
759 |
|
if (DEBUG) |
760 |
|
{ |
761 |
|
debug(nsae); |
762 |
|
nsae.printStackTrace(); |
763 |
|
} |
764 |
|
return false; |
765 |
|
} |
766 |
|
} |
767 |
|
|
768 |
|
// We have to find at least one valid digest. |
769 |
|
return verified > 0; |
770 |
|
} |
771 |
|
|
772 |
|
/** |
773 |
|
* Read the raw bytes that comprise a manifest entry. We can't use the |
774 |
|
* Manifest object itself, because that loses information (such as line |
775 |
|
* endings, and order of entries). |
776 |
|
*/ |
777 |
|
private byte[] readManifestEntry(ZipEntry entry) throws IOException |
778 |
|
{ |
779 |
|
InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME)); |
780 |
|
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
781 |
|
byte[] target = ("Name: " + entry.getName()).getBytes(); |
782 |
|
int t = 0, c, prev = -1, state = 0, l = -1; |
783 |
|
|
784 |
|
while ((c = in.read()) != -1) |
785 |
|
{ |
786 |
|
// if (DEBUG) |
787 |
|
// debug("read " |
788 |
|
// + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c))) |
789 |
|
// + " state=" + state + " prev=" |
790 |
|
// + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev))) |
791 |
|
// + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "") |
792 |
|
// + " l=" + l); |
793 |
|
switch (state) |
794 |
|
{ |
795 |
|
|
796 |
|
// Step 1: read until we find the "target" bytes: the start |
797 |
|
// of the entry we need to read. |
798 |
|
case 0: |
799 |
|
if (((byte) c) != target[t]) |
800 |
|
t = 0; |
801 |
|
else |
802 |
|
{ |
803 |
|
t++; |
804 |
|
if (t == target.length) |
805 |
|
{ |
806 |
|
out.write(target); |
807 |
|
state = 1; |
808 |
|
} |
809 |
|
} |
810 |
|
break; |
811 |
|
|
812 |
|
// Step 2: assert that there is a newline character after |
813 |
|
// the "target" bytes. |
814 |
|
case 1: |
815 |
|
if (c != '\n' && c != '\r') |
816 |
|
{ |
817 |
|
out.reset(); |
818 |
|
t = 0; |
819 |
|
state = 0; |
820 |
|
} |
821 |
|
else |
822 |
|
{ |
823 |
|
out.write(c); |
824 |
|
state = 2; |
825 |
|
} |
826 |
|
break; |
827 |
|
|
828 |
|
// Step 3: read this whole entry, until we reach an empty |
829 |
|
// line. |
830 |
|
case 2: |
831 |
|
if (c == '\n') |
832 |
|
{ |
833 |
|
out.write(c); |
834 |
|
// NL always terminates a line. |
835 |
|
if (l == 0 || (l == 1 && prev == '\r')) |
836 |
|
return out.toByteArray(); |
837 |
|
l = 0; |
838 |
|
} |
839 |
|
else |
840 |
|
{ |
841 |
|
// Here we see a blank line terminated by a CR, |
842 |
|
// followed by the next entry. Technically, `c' should |
843 |
|
// always be 'N' at this point. |
844 |
|
if (l == 1 && prev == '\r') |
845 |
|
return out.toByteArray(); |
846 |
|
out.write(c); |
847 |
|
l++; |
848 |
|
} |
849 |
|
prev = c; |
850 |
|
break; |
851 |
|
|
852 |
|
default: |
853 |
|
throw new RuntimeException("this statement should be unreachable"); |
854 |
|
} |
855 |
|
} |
856 |
|
|
857 |
|
// The last entry, with a single CR terminating the line. |
858 |
|
if (state == 2 && prev == '\r' && l == 0) |
859 |
|
return out.toByteArray(); |
860 |
|
|
861 |
|
// We should not reach this point, we didn't find the entry (or, possibly, |
862 |
|
// it is the last entry and is malformed). |
863 |
|
throw new IOException("could not find " + entry + " in manifest"); |
864 |
|
} |
865 |
|
|
866 |
|
/** |
867 |
|
* A utility class that verifies jar entries as they are read. |
868 |
|
*/ |
869 |
|
private class EntryInputStream extends FilterInputStream |
870 |
|
{ |
871 |
|
private final long length; |
872 |
|
private long pos; |
873 |
|
private final ZipEntry entry; |
874 |
|
private final byte[][] hashes; |
875 |
|
private final MessageDigest[] md; |
876 |
|
private boolean checked; |
877 |
|
|
878 |
|
EntryInputStream(final ZipEntry entry) throws IOException |
879 |
|
{ |
880 |
|
super(JarFile.super.getInputStream(entry)); |
881 |
|
this.entry = entry; |
882 |
|
|
883 |
|
length = entry.getSize(); |
884 |
|
pos = 0; |
885 |
|
checked = false; |
886 |
|
|
887 |
|
Attributes attr = manifest.getAttributes(entry.getName()); |
888 |
|
if (DEBUG) |
889 |
|
debug("verifying entry " + entry + " attr=" + attr); |
890 |
|
if (attr == null) |
891 |
|
{ |
892 |
|
hashes = new byte[0][]; |
893 |
|
md = new MessageDigest[0]; |
894 |
|
} |
895 |
|
else |
896 |
|
{ |
897 |
|
List hashes = new LinkedList(); |
898 |
|
List md = new LinkedList(); |
899 |
|
for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) |
900 |
|
{ |
901 |
|
Map.Entry e = (Map.Entry) it.next(); |
902 |
|
String key = String.valueOf(e.getKey()); |
903 |
|
if (key == null) |
904 |
|
continue; |
905 |
|
if (!key.endsWith(DIGEST_KEY_SUFFIX)) |
906 |
|
continue; |
907 |
|
hashes.add(Base64InputStream.decode((String) e.getValue())); |
908 |
|
try |
909 |
|
{ |
910 |
|
md.add(MessageDigest.getInstance |
911 |
|
(key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length()))); |
912 |
|
} |
913 |
|
catch (NoSuchAlgorithmException nsae) |
914 |
|
{ |
915 |
|
IOException ioe = new IOException("no such message digest: " + key); |
916 |
|
ioe.initCause(nsae); |
917 |
|
throw ioe; |
918 |
|
} |
919 |
|
} |
920 |
|
if (DEBUG) |
921 |
|
debug("digests=" + md); |
922 |
|
this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]); |
923 |
|
this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]); |
924 |
|
} |
925 |
|
} |
926 |
|
|
927 |
|
public boolean markSupported() |
928 |
|
{ |
929 |
|
return false; |
930 |
|
} |
931 |
|
|
932 |
|
public void mark(int readLimit) |
933 |
|
{ |
934 |
|
} |
935 |
|
|
936 |
|
public void reset() |
937 |
|
{ |
938 |
|
} |
939 |
|
|
940 |
|
public int read() throws IOException |
941 |
|
{ |
942 |
|
int b = super.read(); |
943 |
|
if (b == -1) |
944 |
|
{ |
945 |
|
eof(); |
946 |
|
return -1; |
947 |
|
} |
948 |
|
for (int i = 0; i < md.length; i++) |
949 |
|
md[i].update((byte) b); |
950 |
|
pos++; |
951 |
|
if (length > 0 && pos >= length) |
952 |
|
eof(); |
953 |
|
return b; |
954 |
|
} |
955 |
|
|
956 |
|
public int read(byte[] buf, int off, int len) throws IOException |
957 |
|
{ |
958 |
|
int count = super.read(buf, off, (int) Math.min(len, (length != 0 |
959 |
|
? length - pos |
960 |
|
: Integer.MAX_VALUE))); |
961 |
|
if (count == -1 || (length > 0 && pos >= length)) |
962 |
|
{ |
963 |
|
eof(); |
964 |
|
return -1; |
965 |
|
} |
966 |
|
for (int i = 0; i < md.length; i++) |
967 |
|
md[i].update(buf, off, count); |
968 |
|
pos += count; |
969 |
|
if (length != 0 && pos >= length) |
970 |
|
eof(); |
971 |
|
return count; |
972 |
|
} |
973 |
|
|
974 |
|
public int read(byte[] buf) throws IOException |
975 |
|
{ |
976 |
|
return read(buf, 0, buf.length); |
977 |
|
} |
978 |
|
|
979 |
|
public long skip(long bytes) throws IOException |
980 |
|
{ |
981 |
|
byte[] b = new byte[1024]; |
982 |
|
long amount = 0; |
983 |
|
while (amount < bytes) |
984 |
|
{ |
985 |
|
int l = read(b, 0, (int) Math.min(b.length, bytes - amount)); |
986 |
|
if (l == -1) |
987 |
|
break; |
988 |
|
amount += l; |
989 |
|
} |
990 |
|
return amount; |
991 |
|
} |
992 |
|
|
993 |
|
private void eof() throws IOException |
994 |
|
{ |
995 |
|
if (checked) |
996 |
|
return; |
997 |
|
checked = true; |
998 |
|
for (int i = 0; i < md.length; i++) |
999 |
|
{ |
1000 |
|
byte[] hash = md[i].digest(); |
1001 |
|
if (DEBUG) |
1002 |
|
debug("verifying " + md[i].getAlgorithm() + " expect=" |
1003 |
|
+ new java.math.BigInteger(hashes[i]).toString(16) |
1004 |
|
+ " comp=" + new java.math.BigInteger(hash).toString(16)); |
1005 |
|
if (!Arrays.equals(hash, hashes[i])) |
1006 |
|
{ |
1007 |
|
if (DEBUG) |
1008 |
|
debug(entry + " could NOT be verified"); |
1009 |
|
verified.put(entry.getName(), Boolean.FALSE); |
1010 |
|
return; |
1011 |
|
// XXX ??? what do we do here? |
1012 |
|
// throw new ZipException("message digest mismatch"); |
1013 |
|
} |
1014 |
|
} |
1015 |
|
if (DEBUG) |
1016 |
|
debug(entry + " has been VERIFIED"); |
1017 |
|
verified.put(entry.getName(), Boolean.TRUE); |
1018 |
|
} |
1019 |
|
} |
1020 |
} |
} |