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: import ;
75: import ;
76:
77:
89: public class JarFile extends ZipFile
90: {
91:
92:
93:
94: public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
95:
96:
97: private static final String META_INF = "META-INF/";
98:
99:
100: private static final String PKCS7_DSA_SUFFIX = ".DSA";
101:
102:
103: private static final String PKCS7_RSA_SUFFIX = ".RSA";
104:
105:
106: private static final String DIGEST_KEY_SUFFIX = "-Digest";
107:
108:
109: private static final String SF_SUFFIX = ".SF";
110:
111:
117: static final Gnu provider = new Gnu();
118:
119:
120: private static final OID MD2_OID = new OID("1.2.840.113549.2.2");
121: private static final OID MD4_OID = new OID("1.2.840.113549.2.4");
122: private static final OID MD5_OID = new OID("1.2.840.113549.2.5");
123: private static final OID SHA1_OID = new OID("1.3.14.3.2.26");
124: private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1");
125: private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1");
126:
127:
131: private Manifest manifest;
132:
133:
134: boolean verify;
135:
136:
137: private boolean manifestRead = false;
138:
139:
140: boolean signaturesRead = false;
141:
142:
146: HashMap verified = new HashMap();
147:
148:
152: HashMap entryCerts;
153:
154:
158: private HashMap digestAlgorithms = new HashMap();
159:
160: static boolean DEBUG = false;
161: static void debug(Object msg)
162: {
163: System.err.print(JarFile.class.getName());
164: System.err.print(" >>> ");
165: System.err.println(msg);
166: }
167:
168:
169:
170:
179: public JarFile(String fileName) throws FileNotFoundException, IOException
180: {
181: this(fileName, true);
182: }
183:
184:
196: public JarFile(String fileName, boolean verify) throws
197: FileNotFoundException, IOException
198: {
199: super(fileName);
200: if (verify)
201: {
202: manifest = readManifest();
203: verify();
204: }
205: }
206:
207:
216: public JarFile(File file) throws FileNotFoundException, IOException
217: {
218: this(file, true);
219: }
220:
221:
233: public JarFile(File file, boolean verify) throws FileNotFoundException,
234: IOException
235: {
236: super(file);
237: if (verify)
238: {
239: manifest = readManifest();
240: verify();
241: }
242: }
243:
244:
262: public JarFile(File file, boolean verify, int mode) throws
263: FileNotFoundException, IOException, IllegalArgumentException
264: {
265: super(file, mode);
266: if (verify)
267: {
268: manifest = readManifest();
269: verify();
270: }
271: }
272:
273:
274:
275:
278: private void verify()
279: {
280:
281: if (manifest == null)
282: {
283: verify = false;
284: return;
285: }
286:
287: verify = true;
288:
289: }
290:
291:
294: private Manifest readManifest()
295: {
296: try
297: {
298: ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
299: if (manEntry != null)
300: {
301: InputStream in = super.getInputStream(manEntry);
302: manifestRead = true;
303: return new Manifest(in);
304: }
305: else
306: {
307: manifestRead = true;
308: return null;
309: }
310: }
311: catch (IOException ioe)
312: {
313: manifestRead = true;
314: return null;
315: }
316: }
317:
318:
324: public Enumeration<JarEntry> entries() throws IllegalStateException
325: {
326: return new JarEnumeration(super.entries(), this);
327: }
328:
329:
333: private static class JarEnumeration implements Enumeration<JarEntry>
334: {
335:
336: private final Enumeration<? extends ZipEntry> entries;
337: private final JarFile jarfile;
338:
339: JarEnumeration(Enumeration<? extends ZipEntry> e, JarFile f)
340: {
341: entries = e;
342: jarfile = f;
343: }
344:
345: public boolean hasMoreElements()
346: {
347: return entries.hasMoreElements();
348: }
349:
350: public JarEntry nextElement()
351: {
352: ZipEntry zip = (ZipEntry) entries.nextElement();
353: JarEntry jar = new JarEntry(zip);
354: Manifest manifest;
355: try
356: {
357: manifest = jarfile.getManifest();
358: }
359: catch (IOException ioe)
360: {
361: manifest = null;
362: }
363:
364: if (manifest != null)
365: {
366: jar.attr = manifest.getAttributes(jar.getName());
367: }
368:
369: synchronized(jarfile)
370: {
371: if (jarfile.verify && !jarfile.signaturesRead)
372: try
373: {
374: jarfile.readSignatures();
375: }
376: catch (IOException ioe)
377: {
378: if (JarFile.DEBUG)
379: {
380: JarFile.debug(ioe);
381: ioe.printStackTrace();
382: }
383: jarfile.signaturesRead = true;
384: }
385: }
386: jar.jarfile = jarfile;
387: return jar;
388: }
389: }
390:
391:
396: public synchronized ZipEntry getEntry(String name)
397: {
398: ZipEntry entry = super.getEntry(name);
399: if (entry != null)
400: {
401: JarEntry jarEntry = new JarEntry(entry);
402: Manifest manifest;
403: try
404: {
405: manifest = getManifest();
406: }
407: catch (IOException ioe)
408: {
409: manifest = null;
410: }
411:
412: if (manifest != null)
413: {
414: jarEntry.attr = manifest.getAttributes(name);
415: }
416:
417: if (verify && !signaturesRead)
418: try
419: {
420: readSignatures();
421: }
422: catch (IOException ioe)
423: {
424: if (DEBUG)
425: {
426: debug(ioe);
427: ioe.printStackTrace();
428: }
429: signaturesRead = true;
430: }
431: jarEntry.jarfile = this;
432: return jarEntry;
433: }
434: return null;
435: }
436:
437:
446: public synchronized InputStream getInputStream(ZipEntry entry) throws
447: ZipException, IOException
448: {
449:
450: if (!verified.containsKey(entry.getName()) && verify)
451: {
452: if (DEBUG)
453: debug("reading and verifying " + entry);
454: return new EntryInputStream(entry, super.getInputStream(entry), this);
455: }
456: else
457: {
458: if (DEBUG)
459: debug("reading already verified entry " + entry);
460: if (verify && verified.get(entry.getName()) == Boolean.FALSE)
461: throw new ZipException("digest for " + entry + " is invalid");
462: return super.getInputStream(entry);
463: }
464: }
465:
466:
475: public JarEntry getJarEntry(String name)
476: {
477: return (JarEntry) getEntry(name);
478: }
479:
480:
484: public synchronized Manifest getManifest() throws IOException
485: {
486: if (!manifestRead)
487: manifest = readManifest();
488:
489: return manifest;
490: }
491:
492:
493:
494: void readSignatures() throws IOException
495: {
496: Map pkcs7Dsa = new HashMap();
497: Map pkcs7Rsa = new HashMap();
498: Map sigFiles = new HashMap();
499:
500:
501:
502: for (Enumeration e = super.entries(); e.hasMoreElements(); )
503: {
504: ZipEntry ze = (ZipEntry) e.nextElement();
505: String name = ze.getName();
506: if (name.startsWith(META_INF))
507: {
508: String alias = name.substring(META_INF.length());
509: if (alias.lastIndexOf('.') >= 0)
510: alias = alias.substring(0, alias.lastIndexOf('.'));
511:
512: if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
513: {
514: if (DEBUG)
515: debug("reading PKCS7 info from " + name + ", alias=" + alias);
516: PKCS7SignedData sig = null;
517: try
518: {
519: sig = new PKCS7SignedData(super.getInputStream(ze));
520: }
521: catch (CertificateException ce)
522: {
523: IOException ioe = new IOException("certificate parsing error");
524: ioe.initCause(ce);
525: throw ioe;
526: }
527: catch (CRLException crle)
528: {
529: IOException ioe = new IOException("CRL parsing error");
530: ioe.initCause(crle);
531: throw ioe;
532: }
533: if (name.endsWith(PKCS7_DSA_SUFFIX))
534: pkcs7Dsa.put(alias, sig);
535: else if (name.endsWith(PKCS7_RSA_SUFFIX))
536: pkcs7Rsa.put(alias, sig);
537: }
538: else if (name.endsWith(SF_SUFFIX))
539: {
540: if (DEBUG)
541: debug("reading signature file for " + alias + ": " + name);
542: Manifest sf = new Manifest(super.getInputStream(ze));
543: sigFiles.put(alias, sf);
544: if (DEBUG)
545: debug("result: " + sf);
546: }
547: }
548: }
549:
550:
551: Set validCerts = new HashSet();
552: Map entryCerts = new HashMap();
553: for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); )
554: {
555: int valid = 0;
556: Map.Entry e = (Map.Entry) it.next();
557: String alias = (String) e.getKey();
558:
559: PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias);
560: if (sig != null)
561: {
562: Certificate[] certs = sig.getCertificates();
563: Set signerInfos = sig.getSignerInfos();
564: for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
565: verify(certs, (SignerInfo) it2.next(), alias, validCerts);
566: }
567:
568: sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
569: if (sig != null)
570: {
571: Certificate[] certs = sig.getCertificates();
572: Set signerInfos = sig.getSignerInfos();
573: for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
574: verify(certs, (SignerInfo) it2.next(), alias, validCerts);
575: }
576:
577:
578: if (validCerts.isEmpty())
579: {
580: it.remove();
581: continue;
582: }
583:
584: entryCerts.put(e.getValue(), new HashSet(validCerts));
585: validCerts.clear();
586: }
587:
588:
589:
590:
591: InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
592: ByteArrayOutputStream baStream = new ByteArrayOutputStream();
593: byte[] ba = new byte[1024];
594: while (true)
595: {
596: int len = in.read(ba);
597: if (len < 0)
598: break;
599: baStream.write(ba, 0, len);
600: }
601: in.close();
602:
603: HashMap hmManifestEntries = new HashMap();
604: Pattern p = Pattern.compile("Name: (.+?\r?\n(?: .+?\r?\n)*)"
605: + ".+?-Digest: .+?\r?\n\r?\n");
606: Matcher m = p.matcher(baStream.toString());
607: while (m.find())
608: {
609: String fileName = m.group(1).replaceAll("\r?\n ?", "");
610: hmManifestEntries.put(fileName, m.group());
611: }
612:
613:
614:
615: this.entryCerts = new HashMap();
616: for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); )
617: {
618: Map.Entry e = (Map.Entry) it.next();
619: Manifest sigfile = (Manifest) e.getKey();
620: Map entries = sigfile.getEntries();
621: Set certificates = (Set) e.getValue();
622:
623: for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
624: {
625: Map.Entry e2 = (Map.Entry) it2.next();
626: String entryname = String.valueOf(e2.getKey());
627: Attributes attr = (Attributes) e2.getValue();
628: if (verifyHashes(entryname, attr, hmManifestEntries))
629: {
630: if (DEBUG)
631: debug("entry " + entryname + " has certificates " + certificates);
632: Set s = (Set) this.entryCerts.get(entryname);
633: if (s != null)
634: s.addAll(certificates);
635: else
636: this.entryCerts.put(entryname, new HashSet(certificates));
637: }
638: }
639: }
640:
641: signaturesRead = true;
642: }
643:
644:
648: private void verify(Certificate[] certs, SignerInfo signerInfo,
649: String alias, Set validCerts)
650: {
651: Signature sig = null;
652: try
653: {
654: OID alg = signerInfo.getDigestEncryptionAlgorithmId();
655: if (alg.equals(DSA_ENCRYPTION_OID))
656: {
657: if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
658: return;
659: sig = Signature.getInstance("SHA1withDSA", provider);
660: }
661: else if (alg.equals(RSA_ENCRYPTION_OID))
662: {
663: OID hash = signerInfo.getDigestAlgorithmId();
664: if (hash.equals(MD2_OID))
665: sig = Signature.getInstance("md2WithRsaEncryption", provider);
666: else if (hash.equals(MD4_OID))
667: sig = Signature.getInstance("md4WithRsaEncryption", provider);
668: else if (hash.equals(MD5_OID))
669: sig = Signature.getInstance("md5WithRsaEncryption", provider);
670: else if (hash.equals(SHA1_OID))
671: sig = Signature.getInstance("sha1WithRsaEncryption", provider);
672: else
673: return;
674: }
675: else
676: {
677: if (DEBUG)
678: debug("unsupported signature algorithm: " + alg);
679: return;
680: }
681: }
682: catch (NoSuchAlgorithmException nsae)
683: {
684: if (DEBUG)
685: {
686: debug(nsae);
687: nsae.printStackTrace();
688: }
689: return;
690: }
691: ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX);
692: if (sigFileEntry == null)
693: return;
694: for (int i = 0; i < certs.length; i++)
695: {
696: if (!(certs[i] instanceof X509Certificate))
697: continue;
698: X509Certificate cert = (X509Certificate) certs[i];
699: if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) ||
700: !cert.getSerialNumber().equals(signerInfo.getSerialNumber()))
701: continue;
702: try
703: {
704: sig.initVerify(cert.getPublicKey());
705: InputStream in = super.getInputStream(sigFileEntry);
706: if (in == null)
707: continue;
708: byte[] buf = new byte[1024];
709: int len = 0;
710: while ((len = in.read(buf)) != -1)
711: sig.update(buf, 0, len);
712: if (sig.verify(signerInfo.getEncryptedDigest()))
713: {
714: if (DEBUG)
715: debug("signature for " + cert.getSubjectDN() + " is good");
716: validCerts.add(cert);
717: }
718: }
719: catch (IOException ioe)
720: {
721: continue;
722: }
723: catch (InvalidKeyException ike)
724: {
725: continue;
726: }
727: catch (SignatureException se)
728: {
729: continue;
730: }
731: }
732: }
733:
734:
743: private boolean verifyHashes(String entry, Attributes attr,
744: HashMap hmManifestEntries)
745: {
746: int verified = 0;
747:
748: String stringEntry = (String) hmManifestEntries.get(entry);
749: if (stringEntry == null)
750: {
751: if (DEBUG)
752: debug("could not find " + entry + " in manifest");
753: return false;
754: }
755:
756:
757: byte[] entryBytes = stringEntry.getBytes();
758:
759: for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
760: {
761: Map.Entry e = (Map.Entry) it.next();
762: String key = String.valueOf(e.getKey());
763: if (!key.endsWith(DIGEST_KEY_SUFFIX))
764: continue;
765: String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length());
766: try
767: {
768: byte[] hash = Base64InputStream.decode((String) e.getValue());
769: MessageDigest md = (MessageDigest) digestAlgorithms.get(alg);
770: if (md == null)
771: {
772: md = MessageDigest.getInstance(alg, provider);
773: digestAlgorithms.put(alg, md);
774: }
775: md.reset();
776: byte[] hash2 = md.digest(entryBytes);
777: if (DEBUG)
778: debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
779: + " expect=" + new java.math.BigInteger(hash).toString(16)
780: + " comp=" + new java.math.BigInteger(hash2).toString(16));
781: if (!Arrays.equals(hash, hash2))
782: return false;
783: verified++;
784: }
785: catch (IOException ioe)
786: {
787: if (DEBUG)
788: {
789: debug(ioe);
790: ioe.printStackTrace();
791: }
792: return false;
793: }
794: catch (NoSuchAlgorithmException nsae)
795: {
796: if (DEBUG)
797: {
798: debug(nsae);
799: nsae.printStackTrace();
800: }
801: return false;
802: }
803: }
804:
805:
806: return verified > 0;
807: }
808:
809:
812: private static class EntryInputStream extends FilterInputStream
813: {
814: private final JarFile jarfile;
815: private final long length;
816: private long pos;
817: private final ZipEntry entry;
818: private final byte[][] hashes;
819: private final MessageDigest[] md;
820: private boolean checked;
821:
822: EntryInputStream(final ZipEntry entry,
823: final InputStream in,
824: final JarFile jar)
825: throws IOException
826: {
827: super(in);
828: this.entry = entry;
829: this.jarfile = jar;
830:
831: length = entry.getSize();
832: pos = 0;
833: checked = false;
834:
835: Attributes attr;
836: Manifest manifest = jarfile.getManifest();
837: if (manifest != null)
838: attr = manifest.getAttributes(entry.getName());
839: else
840: attr = null;
841: if (DEBUG)
842: debug("verifying entry " + entry + " attr=" + attr);
843: if (attr == null)
844: {
845: hashes = new byte[0][];
846: md = new MessageDigest[0];
847: }
848: else
849: {
850: List hashes = new LinkedList();
851: List md = new LinkedList();
852: for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
853: {
854: Map.Entry e = (Map.Entry) it.next();
855: String key = String.valueOf(e.getKey());
856: if (key == null)
857: continue;
858: if (!key.endsWith(DIGEST_KEY_SUFFIX))
859: continue;
860: hashes.add(Base64InputStream.decode((String) e.getValue()));
861: try
862: {
863: int length = key.length() - DIGEST_KEY_SUFFIX.length();
864: String alg = key.substring(0, length);
865: md.add(MessageDigest.getInstance(alg, provider));
866: }
867: catch (NoSuchAlgorithmException nsae)
868: {
869: IOException ioe = new IOException("no such message digest: " + key);
870: ioe.initCause(nsae);
871: throw ioe;
872: }
873: }
874: if (DEBUG)
875: debug("digests=" + md);
876: this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]);
877: this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]);
878: }
879: }
880:
881: public boolean markSupported()
882: {
883: return false;
884: }
885:
886: public void mark(int readLimit)
887: {
888: }
889:
890: public void reset()
891: {
892: }
893:
894: public int read() throws IOException
895: {
896: int b = super.read();
897: if (b == -1)
898: {
899: eof();
900: return -1;
901: }
902: for (int i = 0; i < md.length; i++)
903: md[i].update((byte) b);
904: pos++;
905: if (length > 0 && pos >= length)
906: eof();
907: return b;
908: }
909:
910: public int read(byte[] buf, int off, int len) throws IOException
911: {
912: int count = super.read(buf, off, (int) Math.min(len, (length != 0
913: ? length - pos
914: : Integer.MAX_VALUE)));
915: if (count == -1 || (length > 0 && pos >= length))
916: {
917: eof();
918: return -1;
919: }
920: for (int i = 0; i < md.length; i++)
921: md[i].update(buf, off, count);
922: pos += count;
923: if (length != 0 && pos >= length)
924: eof();
925: return count;
926: }
927:
928: public int read(byte[] buf) throws IOException
929: {
930: return read(buf, 0, buf.length);
931: }
932:
933: public long skip(long bytes) throws IOException
934: {
935: byte[] b = new byte[1024];
936: long amount = 0;
937: while (amount < bytes)
938: {
939: int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
940: if (l == -1)
941: break;
942: amount += l;
943: }
944: return amount;
945: }
946:
947: private void eof() throws IOException
948: {
949: if (checked)
950: return;
951: checked = true;
952: for (int i = 0; i < md.length; i++)
953: {
954: byte[] hash = md[i].digest();
955: if (DEBUG)
956: debug("verifying " + md[i].getAlgorithm() + " expect="
957: + new java.math.BigInteger(hashes[i]).toString(16)
958: + " comp=" + new java.math.BigInteger(hash).toString(16));
959: if (!Arrays.equals(hash, hashes[i]))
960: {
961: synchronized(jarfile)
962: {
963: if (DEBUG)
964: debug(entry + " could NOT be verified");
965: jarfile.verified.put(entry.getName(), Boolean.FALSE);
966: }
967: return;
968:
969:
970: }
971: }
972:
973: synchronized(jarfile)
974: {
975: if (DEBUG)
976: debug(entry + " has been VERIFIED");
977: jarfile.verified.put(entry.getName(), Boolean.TRUE);
978: }
979: }
980: }
981: }