1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44: import ;
45:
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60: import ;
61: import ;
62: import ;
63: import ;
64: import ;
65: import ;
66: import ;
67: import ;
68: import ;
69: import ;
70: import ;
71: import ;
72: import ;
73:
74:
86: public class JarFile extends ZipFile
87: {
88:
89:
90:
91: public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
92:
93:
94: private static final String META_INF = "META-INF/";
95:
96:
97: private static final String PKCS7_DSA_SUFFIX = ".DSA";
98:
99:
100: private static final String PKCS7_RSA_SUFFIX = ".RSA";
101:
102:
103: private static final String DIGEST_KEY_SUFFIX = "-Digest";
104:
105:
106: private static final String SF_SUFFIX = ".SF";
107:
108:
109: private static final OID MD2_OID = new OID("1.2.840.113549.2.2");
110: private static final OID MD4_OID = new OID("1.2.840.113549.2.4");
111: private static final OID MD5_OID = new OID("1.2.840.113549.2.5");
112: private static final OID SHA1_OID = new OID("1.3.14.3.2.26");
113: private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1");
114: private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1");
115:
116:
120: private Manifest manifest;
121:
122:
123: boolean verify;
124:
125:
126: private boolean manifestRead = false;
127:
128:
129: boolean signaturesRead = false;
130:
131:
135: HashMap verified = new HashMap();
136:
137:
141: HashMap entryCerts;
142:
143: static boolean DEBUG = false;
144: static void debug(Object msg)
145: {
146: System.err.print(JarFile.class.getName());
147: System.err.print(" >>> ");
148: System.err.println(msg);
149: }
150:
151:
152:
153:
162: public JarFile(String fileName) throws FileNotFoundException, IOException
163: {
164: this(fileName, true);
165: }
166:
167:
179: public JarFile(String fileName, boolean verify) throws
180: FileNotFoundException, IOException
181: {
182: super(fileName);
183: if (verify)
184: {
185: manifest = readManifest();
186: verify();
187: }
188: }
189:
190:
199: public JarFile(File file) throws FileNotFoundException, IOException
200: {
201: this(file, true);
202: }
203:
204:
216: public JarFile(File file, boolean verify) throws FileNotFoundException,
217: IOException
218: {
219: super(file);
220: if (verify)
221: {
222: manifest = readManifest();
223: verify();
224: }
225: }
226:
227:
245: public JarFile(File file, boolean verify, int mode) throws
246: FileNotFoundException, IOException, IllegalArgumentException
247: {
248: super(file, mode);
249: if (verify)
250: {
251: manifest = readManifest();
252: verify();
253: }
254: }
255:
256:
257:
258:
261: private void verify()
262: {
263:
264: if (manifest == null)
265: {
266: verify = false;
267: return;
268: }
269:
270: verify = true;
271:
272: }
273:
274:
277: private Manifest readManifest()
278: {
279: try
280: {
281: ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
282: if (manEntry != null)
283: {
284: InputStream in = super.getInputStream(manEntry);
285: manifestRead = true;
286: return new Manifest(in);
287: }
288: else
289: {
290: manifestRead = true;
291: return null;
292: }
293: }
294: catch (IOException ioe)
295: {
296: manifestRead = true;
297: return null;
298: }
299: }
300:
301:
307: public Enumeration entries() throws IllegalStateException
308: {
309: return new JarEnumeration(super.entries(), this);
310: }
311:
312:
316: private static class JarEnumeration implements Enumeration
317: {
318:
319: private final Enumeration entries;
320: private final JarFile jarfile;
321:
322: JarEnumeration(Enumeration e, JarFile f)
323: {
324: entries = e;
325: jarfile = f;
326: }
327:
328: public boolean hasMoreElements()
329: {
330: return entries.hasMoreElements();
331: }
332:
333: public Object nextElement()
334: {
335: ZipEntry zip = (ZipEntry) entries.nextElement();
336: JarEntry jar = new JarEntry(zip);
337: Manifest manifest;
338: try
339: {
340: manifest = jarfile.getManifest();
341: }
342: catch (IOException ioe)
343: {
344: manifest = null;
345: }
346:
347: if (manifest != null)
348: {
349: jar.attr = manifest.getAttributes(jar.getName());
350: }
351:
352: synchronized(jarfile)
353: {
354: if (jarfile.verify && !jarfile.signaturesRead)
355: try
356: {
357: jarfile.readSignatures();
358: }
359: catch (IOException ioe)
360: {
361: if (JarFile.DEBUG)
362: {
363: JarFile.debug(ioe);
364: ioe.printStackTrace();
365: }
366: jarfile.signaturesRead = true;
367: }
368:
369:
370:
371:
372: if (jarfile.entryCerts != null
373: && jarfile.verified.get(zip.getName()) == Boolean.TRUE)
374: {
375: Set certs = (Set) jarfile.entryCerts.get(jar.getName());
376: if (certs != null)
377: jar.certs = (Certificate[])
378: certs.toArray(new Certificate[certs.size()]);
379: }
380: }
381: return jar;
382: }
383: }
384:
385:
390: public synchronized ZipEntry getEntry(String name)
391: {
392: ZipEntry entry = super.getEntry(name);
393: if (entry != null)
394: {
395: JarEntry jarEntry = new JarEntry(entry);
396: Manifest manifest;
397: try
398: {
399: manifest = getManifest();
400: }
401: catch (IOException ioe)
402: {
403: manifest = null;
404: }
405:
406: if (manifest != null)
407: {
408: jarEntry.attr = manifest.getAttributes(name);
409: }
410:
411: if (verify && !signaturesRead)
412: try
413: {
414: readSignatures();
415: }
416: catch (IOException ioe)
417: {
418: if (DEBUG)
419: {
420: debug(ioe);
421: ioe.printStackTrace();
422: }
423: signaturesRead = true;
424: }
425:
426:
427: if (DEBUG)
428: debug("entryCerts=" + entryCerts + " verified " + name
429: + " ? " + verified.get(name));
430: if (entryCerts != null && verified.get(name) == Boolean.TRUE)
431: {
432: Set certs = (Set) entryCerts.get(name);
433: if (certs != null)
434: jarEntry.certs = (Certificate[])
435: certs.toArray(new Certificate[certs.size()]);
436: }
437: return jarEntry;
438: }
439: return null;
440: }
441:
442:
451: public synchronized InputStream getInputStream(ZipEntry entry) throws
452: ZipException, IOException
453: {
454:
455: if (!verified.containsKey(entry.getName()) && verify)
456: {
457: if (DEBUG)
458: debug("reading and verifying " + entry);
459: return new EntryInputStream(entry, super.getInputStream(entry), this);
460: }
461: else
462: {
463: if (DEBUG)
464: debug("reading already verified entry " + entry);
465: if (verify && verified.get(entry.getName()) == Boolean.FALSE)
466: throw new ZipException("digest for " + entry + " is invalid");
467: return super.getInputStream(entry);
468: }
469: }
470:
471:
480: public JarEntry getJarEntry(String name)
481: {
482: return (JarEntry) getEntry(name);
483: }
484:
485:
489: public synchronized Manifest getManifest() throws IOException
490: {
491: if (!manifestRead)
492: manifest = readManifest();
493:
494: return manifest;
495: }
496:
497:
498:
499: void readSignatures() throws IOException
500: {
501: Map pkcs7Dsa = new HashMap();
502: Map pkcs7Rsa = new HashMap();
503: Map sigFiles = new HashMap();
504:
505:
506:
507: for (Enumeration e = super.entries(); e.hasMoreElements(); )
508: {
509: ZipEntry ze = (ZipEntry) e.nextElement();
510: String name = ze.getName();
511: if (name.startsWith(META_INF))
512: {
513: String alias = name.substring(META_INF.length());
514: if (alias.lastIndexOf('.') >= 0)
515: alias = alias.substring(0, alias.lastIndexOf('.'));
516:
517: if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
518: {
519: if (DEBUG)
520: debug("reading PKCS7 info from " + name + ", alias=" + alias);
521: PKCS7SignedData sig = null;
522: try
523: {
524: sig = new PKCS7SignedData(super.getInputStream(ze));
525: }
526: catch (CertificateException ce)
527: {
528: IOException ioe = new IOException("certificate parsing error");
529: ioe.initCause(ce);
530: throw ioe;
531: }
532: catch (CRLException crle)
533: {
534: IOException ioe = new IOException("CRL parsing error");
535: ioe.initCause(crle);
536: throw ioe;
537: }
538: if (name.endsWith(PKCS7_DSA_SUFFIX))
539: pkcs7Dsa.put(alias, sig);
540: else if (name.endsWith(PKCS7_RSA_SUFFIX))
541: pkcs7Rsa.put(alias, sig);
542: }
543: else if (name.endsWith(SF_SUFFIX))
544: {
545: if (DEBUG)
546: debug("reading signature file for " + alias + ": " + name);
547: Manifest sf = new Manifest(super.getInputStream(ze));
548: sigFiles.put(alias, sf);
549: if (DEBUG)
550: debug("result: " + sf);
551: }
552: }
553: }
554:
555:
556: Set validCerts = new HashSet();
557: Map entryCerts = new HashMap();
558: for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); )
559: {
560: int valid = 0;
561: Map.Entry e = (Map.Entry) it.next();
562: String alias = (String) e.getKey();
563:
564: PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.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: sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
574: if (sig != null)
575: {
576: Certificate[] certs = sig.getCertificates();
577: Set signerInfos = sig.getSignerInfos();
578: for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
579: verify(certs, (SignerInfo) it2.next(), alias, validCerts);
580: }
581:
582:
583: if (validCerts.isEmpty())
584: {
585: it.remove();
586: continue;
587: }
588:
589: entryCerts.put(e.getValue(), new HashSet(validCerts));
590: validCerts.clear();
591: }
592:
593:
594:
595: this.entryCerts = new HashMap();
596: for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); )
597: {
598: Map.Entry e = (Map.Entry) it.next();
599: Manifest sigfile = (Manifest) e.getKey();
600: Map entries = sigfile.getEntries();
601: Set certificates = (Set) e.getValue();
602:
603: for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
604: {
605: Map.Entry e2 = (Map.Entry) it2.next();
606: String entryname = String.valueOf(e2.getKey());
607: Attributes attr = (Attributes) e2.getValue();
608: if (verifyHashes(entryname, attr))
609: {
610: if (DEBUG)
611: debug("entry " + entryname + " has certificates " + certificates);
612: Set s = (Set) this.entryCerts.get(entryname);
613: if (s != null)
614: s.addAll(certificates);
615: else
616: this.entryCerts.put(entryname, new HashSet(certificates));
617: }
618: }
619: }
620:
621: signaturesRead = true;
622: }
623:
624:
628: private void verify(Certificate[] certs, SignerInfo signerInfo,
629: String alias, Set validCerts)
630: {
631: Signature sig = null;
632: try
633: {
634: OID alg = signerInfo.getDigestEncryptionAlgorithmId();
635: if (alg.equals(DSA_ENCRYPTION_OID))
636: {
637: if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
638: return;
639: sig = Signature.getInstance("SHA1withDSA");
640: }
641: else if (alg.equals(RSA_ENCRYPTION_OID))
642: {
643: OID hash = signerInfo.getDigestAlgorithmId();
644: if (hash.equals(MD2_OID))
645: sig = Signature.getInstance("md2WithRsaEncryption");
646: else if (hash.equals(MD4_OID))
647: sig = Signature.getInstance("md4WithRsaEncryption");
648: else if (hash.equals(MD5_OID))
649: sig = Signature.getInstance("md5WithRsaEncryption");
650: else if (hash.equals(SHA1_OID))
651: sig = Signature.getInstance("sha1WithRsaEncryption");
652: else
653: return;
654: }
655: else
656: {
657: if (DEBUG)
658: debug("unsupported signature algorithm: " + alg);
659: return;
660: }
661: }
662: catch (NoSuchAlgorithmException nsae)
663: {
664: if (DEBUG)
665: {
666: debug(nsae);
667: nsae.printStackTrace();
668: }
669: return;
670: }
671: ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX);
672: if (sigFileEntry == null)
673: return;
674: for (int i = 0; i < certs.length; i++)
675: {
676: if (!(certs[i] instanceof X509Certificate))
677: continue;
678: X509Certificate cert = (X509Certificate) certs[i];
679: if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) ||
680: !cert.getSerialNumber().equals(signerInfo.getSerialNumber()))
681: continue;
682: try
683: {
684: sig.initVerify(cert.getPublicKey());
685: InputStream in = super.getInputStream(sigFileEntry);
686: if (in == null)
687: continue;
688: byte[] buf = new byte[1024];
689: int len = 0;
690: while ((len = in.read(buf)) != -1)
691: sig.update(buf, 0, len);
692: if (sig.verify(signerInfo.getEncryptedDigest()))
693: {
694: if (DEBUG)
695: debug("signature for " + cert.getSubjectDN() + " is good");
696: validCerts.add(cert);
697: }
698: }
699: catch (IOException ioe)
700: {
701: continue;
702: }
703: catch (InvalidKeyException ike)
704: {
705: continue;
706: }
707: catch (SignatureException se)
708: {
709: continue;
710: }
711: }
712: }
713:
714:
721: private boolean verifyHashes(String entry, Attributes attr)
722: {
723: int verified = 0;
724:
725:
726:
727: byte[] entryBytes = null;
728: try
729: {
730: ZipEntry e = super.getEntry(entry);
731: if (e == null)
732: {
733: if (DEBUG)
734: debug("verifyHashes: no entry '" + entry + "'");
735: return false;
736: }
737: entryBytes = readManifestEntry(e);
738: }
739: catch (IOException ioe)
740: {
741: if (DEBUG)
742: {
743: debug(ioe);
744: ioe.printStackTrace();
745: }
746: return false;
747: }
748:
749: for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
750: {
751: Map.Entry e = (Map.Entry) it.next();
752: String key = String.valueOf(e.getKey());
753: if (!key.endsWith(DIGEST_KEY_SUFFIX))
754: continue;
755: String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length());
756: try
757: {
758: byte[] hash = Base64InputStream.decode((String) e.getValue());
759: MessageDigest md = MessageDigest.getInstance(alg);
760: md.update(entryBytes);
761: byte[] hash2 = md.digest();
762: if (DEBUG)
763: debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
764: + " expect=" + new java.math.BigInteger(hash).toString(16)
765: + " comp=" + new java.math.BigInteger(hash2).toString(16));
766: if (!Arrays.equals(hash, hash2))
767: return false;
768: verified++;
769: }
770: catch (IOException ioe)
771: {
772: if (DEBUG)
773: {
774: debug(ioe);
775: ioe.printStackTrace();
776: }
777: return false;
778: }
779: catch (NoSuchAlgorithmException nsae)
780: {
781: if (DEBUG)
782: {
783: debug(nsae);
784: nsae.printStackTrace();
785: }
786: return false;
787: }
788: }
789:
790:
791: return verified > 0;
792: }
793:
794:
799: private byte[] readManifestEntry(ZipEntry entry) throws IOException
800: {
801: InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
802: ByteArrayOutputStream out = new ByteArrayOutputStream();
803: byte[] target = ("Name: " + entry.getName()).getBytes();
804: int t = 0, c, prev = -1, state = 0, l = -1;
805:
806: while ((c = in.read()) != -1)
807: {
808:
809:
810:
811:
812:
813:
814:
815: switch (state)
816: {
817:
818:
819:
820: case 0:
821: if (((byte) c) != target[t])
822: t = 0;
823: else
824: {
825: t++;
826: if (t == target.length)
827: {
828: out.write(target);
829: state = 1;
830: }
831: }
832: break;
833:
834:
835:
836: case 1:
837: if (c != '\n' && c != '\r')
838: {
839: out.reset();
840: t = 0;
841: state = 0;
842: }
843: else
844: {
845: out.write(c);
846: state = 2;
847: }
848: break;
849:
850:
851:
852: case 2:
853: if (c == '\n')
854: {
855: out.write(c);
856:
857: if (l == 0 || (l == 1 && prev == '\r'))
858: return out.toByteArray();
859: l = 0;
860: }
861: else
862: {
863:
864:
865:
866: if (l == 1 && prev == '\r')
867: return out.toByteArray();
868: out.write(c);
869: l++;
870: }
871: prev = c;
872: break;
873:
874: default:
875: throw new RuntimeException("this statement should be unreachable");
876: }
877: }
878:
879:
880: if (state == 2 && prev == '\r' && l == 0)
881: return out.toByteArray();
882:
883:
884:
885: throw new IOException("could not find " + entry + " in manifest");
886: }
887:
888:
891: private static class EntryInputStream extends FilterInputStream
892: {
893: private final JarFile jarfile;
894: private final long length;
895: private long pos;
896: private final ZipEntry entry;
897: private final byte[][] hashes;
898: private final MessageDigest[] md;
899: private boolean checked;
900:
901: EntryInputStream(final ZipEntry entry,
902: final InputStream in,
903: final JarFile jar)
904: throws IOException
905: {
906: super(in);
907: this.entry = entry;
908: this.jarfile = jar;
909:
910: length = entry.getSize();
911: pos = 0;
912: checked = false;
913:
914: Attributes attr;
915: Manifest manifest = jarfile.getManifest();
916: if (manifest != null)
917: attr = manifest.getAttributes(entry.getName());
918: else
919: attr = null;
920: if (DEBUG)
921: debug("verifying entry " + entry + " attr=" + attr);
922: if (attr == null)
923: {
924: hashes = new byte[0][];
925: md = new MessageDigest[0];
926: }
927: else
928: {
929: List hashes = new LinkedList();
930: List md = new LinkedList();
931: for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
932: {
933: Map.Entry e = (Map.Entry) it.next();
934: String key = String.valueOf(e.getKey());
935: if (key == null)
936: continue;
937: if (!key.endsWith(DIGEST_KEY_SUFFIX))
938: continue;
939: hashes.add(Base64InputStream.decode((String) e.getValue()));
940: try
941: {
942: md.add(MessageDigest.getInstance
943: (key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length())));
944: }
945: catch (NoSuchAlgorithmException nsae)
946: {
947: IOException ioe = new IOException("no such message digest: " + key);
948: ioe.initCause(nsae);
949: throw ioe;
950: }
951: }
952: if (DEBUG)
953: debug("digests=" + md);
954: this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]);
955: this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]);
956: }
957: }
958:
959: public boolean markSupported()
960: {
961: return false;
962: }
963:
964: public void mark(int readLimit)
965: {
966: }
967:
968: public void reset()
969: {
970: }
971:
972: public int read() throws IOException
973: {
974: int b = super.read();
975: if (b == -1)
976: {
977: eof();
978: return -1;
979: }
980: for (int i = 0; i < md.length; i++)
981: md[i].update((byte) b);
982: pos++;
983: if (length > 0 && pos >= length)
984: eof();
985: return b;
986: }
987:
988: public int read(byte[] buf, int off, int len) throws IOException
989: {
990: int count = super.read(buf, off, (int) Math.min(len, (length != 0
991: ? length - pos
992: : Integer.MAX_VALUE)));
993: if (count == -1 || (length > 0 && pos >= length))
994: {
995: eof();
996: return -1;
997: }
998: for (int i = 0; i < md.length; i++)
999: md[i].update(buf, off, count);
1000: pos += count;
1001: if (length != 0 && pos >= length)
1002: eof();
1003: return count;
1004: }
1005:
1006: public int read(byte[] buf) throws IOException
1007: {
1008: return read(buf, 0, buf.length);
1009: }
1010:
1011: public long skip(long bytes) throws IOException
1012: {
1013: byte[] b = new byte[1024];
1014: long amount = 0;
1015: while (amount < bytes)
1016: {
1017: int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
1018: if (l == -1)
1019: break;
1020: amount += l;
1021: }
1022: return amount;
1023: }
1024:
1025: private void eof() throws IOException
1026: {
1027: if (checked)
1028: return;
1029: checked = true;
1030: for (int i = 0; i < md.length; i++)
1031: {
1032: byte[] hash = md[i].digest();
1033: if (DEBUG)
1034: debug("verifying " + md[i].getAlgorithm() + " expect="
1035: + new java.math.BigInteger(hashes[i]).toString(16)
1036: + " comp=" + new java.math.BigInteger(hash).toString(16));
1037: if (!Arrays.equals(hash, hashes[i]))
1038: {
1039: synchronized(jarfile)
1040: {
1041: if (DEBUG)
1042: debug(entry + " could NOT be verified");
1043: jarfile.verified.put(entry.getName(), Boolean.FALSE);
1044: }
1045: return;
1046:
1047:
1048: }
1049: }
1050:
1051: synchronized(jarfile)
1052: {
1053: if (DEBUG)
1054: debug(entry + " has been VERIFIED");
1055: jarfile.verified.put(entry.getName(), Boolean.TRUE);
1056: }
1057: }
1058: }
1059: }