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