1
7
8 package java.util.jar;
9
10 import java.io.*;
11 import java.net.URL;
12 import java.util.*;
13 import java.util.zip.*;
14 import java.security.*;
15 import java.security.cert.CertificateException;
16
17 import sun.security.util.ManifestDigester;
18 import sun.security.util.ManifestEntryVerifier;
19 import sun.security.util.SignatureFileVerifier;
20 import sun.security.util.Debug;
21
22
27 class JarVerifier {
28
29
30 static final Debug debug = Debug.getInstance("jar");
31
32
34 private Hashtable verifiedSigners;
35
36
38 private Hashtable sigFileSigners;
39
40
41 private Hashtable sigFileData;
42
43
45 private ArrayList pendingBlocks;
46
47
48 private ArrayList signerCache;
49
50
51 private boolean parsingBlockOrSF = false;
52
53
54 private boolean parsingMeta = true;
55
56
57 private boolean anyToVerify = true;
58
59
61 private ByteArrayOutputStream baos;
62
63
64 private ManifestDigester manDig;
65
66
67 byte manifestRawBytes[] = null;
68
69
70 boolean eagerValidation;
71
72
73 private Object csdomain = new Object();
74
75
76 private List manifestDigests;
77
78 public JarVerifier(byte rawBytes[]) {
79 manifestRawBytes = rawBytes;
80 sigFileSigners = new Hashtable();
81 verifiedSigners = new Hashtable();
82 sigFileData = new Hashtable(11);
83 pendingBlocks = new ArrayList();
84 baos = new ByteArrayOutputStream();
85 manifestDigests = new ArrayList();
86 }
87
88
93 public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
94 throws IOException
95 {
96 if (je == null)
97 return;
98
99 if (debug != null) {
100 debug.println("beginEntry "+je.getName());
101 }
102
103 String name = je.getName();
104
105
114
115 if (parsingMeta) {
116 String uname = name.toUpperCase(Locale.ENGLISH);
117 if ((uname.startsWith("META-INF/") ||
118 uname.startsWith("/META-INF/"))) {
119
120 if (je.isDirectory()) {
121 mev.setEntry(null, je);
122 return;
123 }
124
125 if (SignatureFileVerifier.isBlockOrSF(uname)) {
126
127 parsingBlockOrSF = true;
128 baos.reset();
129 mev.setEntry(null, je);
130 }
131 return;
132 }
133 }
134
135 if (parsingMeta) {
136 doneWithMeta();
137 }
138
139 if (je.isDirectory()) {
140 mev.setEntry(null, je);
141 return;
142 }
143
144 if (name.startsWith("./"))
147 name = name.substring(2);
148
149 if (name.startsWith("/"))
152 name = name.substring(1);
153
154 if (sigFileSigners.get(name) != null) {
156 mev.setEntry(name, je);
157 return;
158 }
159
160 mev.setEntry(null, je);
162
163 return;
164 }
165
166
169
170 public void update(int b, ManifestEntryVerifier mev)
171 throws IOException
172 {
173 if (b != -1) {
174 if (parsingBlockOrSF) {
175 baos.write(b);
176 } else {
177 mev.update((byte)b);
178 }
179 } else {
180 processEntry(mev);
181 }
182 }
183
184
187
188 public void update(int n, byte[] b, int off, int len,
189 ManifestEntryVerifier mev)
190 throws IOException
191 {
192 if (n != -1) {
193 if (parsingBlockOrSF) {
194 baos.write(b, off, n);
195 } else {
196 mev.update(b, off, n);
197 }
198 } else {
199 processEntry(mev);
200 }
201 }
202
203
206 private void processEntry(ManifestEntryVerifier mev)
207 throws IOException
208 {
209 if (!parsingBlockOrSF) {
210 JarEntry je = mev.getEntry();
211 if ((je != null) && (je.signers == null)) {
212 je.signers = mev.verify(verifiedSigners, sigFileSigners);
213 je.certs = mapSignersToCertArray(je.signers);
214 }
215 } else {
216
217 try {
218 parsingBlockOrSF = false;
219
220 if (debug != null) {
221 debug.println("processEntry: processing block");
222 }
223
224 String uname = mev.getEntry().getName()
225 .toUpperCase(Locale.ENGLISH);
226
227 if (uname.endsWith(".SF")) {
228 String key = uname.substring(0, uname.length()-3);
229 byte bytes[] = baos.toByteArray();
230 sigFileData.put(key, bytes);
232 Iterator it = pendingBlocks.iterator();
235 while (it.hasNext()) {
236 SignatureFileVerifier sfv =
237 (SignatureFileVerifier) it.next();
238 if (sfv.needSignatureFile(key)) {
239 if (debug != null) {
240 debug.println(
241 "processEntry: processing pending block");
242 }
243
244 sfv.setSignatureFile(bytes);
245 sfv.process(sigFileSigners, manifestDigests);
246 }
247 }
248 return;
249 }
250
251
253 String key = uname.substring(0, uname.lastIndexOf("."));
254
255 if (signerCache == null)
256 signerCache = new ArrayList();
257
258 if (manDig == null) {
259 synchronized(manifestRawBytes) {
260 if (manDig == null) {
261 manDig = new ManifestDigester(manifestRawBytes);
262 manifestRawBytes = null;
263 }
264 }
265 }
266
267 SignatureFileVerifier sfv =
268 new SignatureFileVerifier(signerCache,
269 manDig, uname, baos.toByteArray());
270
271 if (sfv.needSignatureFileBytes()) {
272 byte[] bytes = (byte[]) sigFileData.get(key);
274
275 if (bytes == null) {
276 if (debug != null) {
280 debug.println("adding pending block");
281 }
282 pendingBlocks.add(sfv);
283 return;
284 } else {
285 sfv.setSignatureFile(bytes);
286 }
287 }
288 sfv.process(sigFileSigners, manifestDigests);
289
290 } catch (sun.security.pkcs.ParsingException pe) {
291 if (debug != null) debug.println("processEntry caught: "+pe);
292 } catch (IOException ioe) {
294 if (debug != null) debug.println("processEntry caught: "+ioe);
295 } catch (SignatureException se) {
297 if (debug != null) debug.println("processEntry caught: "+se);
298 } catch (NoSuchAlgorithmException nsae) {
300 if (debug != null) debug.println("processEntry caught: "+nsae);
301 } catch (CertificateException ce) {
303 if (debug != null) debug.println("processEntry caught: "+ce);
304 }
306 }
307 }
308
309
314 public java.security.cert.Certificate[] getCerts(String name)
315 {
316 return mapSignersToCertArray(getCodeSigners(name));
317 }
318
319 public java.security.cert.Certificate[] getCerts(JarFile jar, JarEntry entry)
320 {
321 return mapSignersToCertArray(getCodeSigners(jar, entry));
322 }
323
324
328 public CodeSigner[] getCodeSigners(String name)
329 {
330 return (CodeSigner[])verifiedSigners.get(name);
331 }
332
333 public CodeSigner[] getCodeSigners(JarFile jar, JarEntry entry)
334 {
335 String name = entry.getName();
336 if (eagerValidation && sigFileSigners.get(name) != null) {
337
341 try {
342 InputStream s = jar.getInputStream(entry);
343 byte[] buffer = new byte[1024];
344 int n = buffer.length;
345 while (n != -1) {
346 n = s.read(buffer, 0, buffer.length);
347 }
348 s.close();
349 } catch (IOException e) {
350 }
351 }
352 return getCodeSigners(name);
353 }
354
355
359 private static java.security.cert.Certificate[] mapSignersToCertArray(
360 CodeSigner[] signers) {
361
362 if (signers != null) {
363 ArrayList certChains = new ArrayList();
364 for (int i = 0; i < signers.length; i++) {
365 certChains.addAll(
366 signers[i].getSignerCertPath().getCertificates());
367 }
368
369 return (java.security.cert.Certificate[])
371 certChains.toArray(
372 new java.security.cert.Certificate[certChains.size()]);
373 }
374 return null;
375 }
376
377
382 boolean nothingToVerify()
383 {
384 return (anyToVerify == false);
385 }
386
387
393 void doneWithMeta()
394 {
395 parsingMeta = false;
396 anyToVerify = !sigFileSigners.isEmpty();
397 baos = null;
398 sigFileData = null;
399 pendingBlocks = null;
400 signerCache = null;
401 manDig = null;
402 }
403
404 static class VerifierStream extends java.io.InputStream {
405
406 private InputStream is;
407 private JarVerifier jv;
408 private ManifestEntryVerifier mev;
409 private long numLeft;
410
411 VerifierStream(Manifest man,
412 JarEntry je,
413 InputStream is,
414 JarVerifier jv) throws IOException
415 {
416 this.is = is;
417 this.jv = jv;
418 this.mev = new ManifestEntryVerifier(man);
419 this.jv.beginEntry(je, mev);
420 this.numLeft = je.getSize();
421 if (this.numLeft == 0)
422 this.jv.update(-1, this.mev);
423 }
424
425 public int read() throws IOException
426 {
427 if (numLeft > 0) {
428 int b = is.read();
429 jv.update(b, mev);
430 numLeft--;
431 if (numLeft == 0)
432 jv.update(-1, mev);
433 return b;
434 } else {
435 return -1;
436 }
437 }
438
439 public int read(byte b[], int off, int len) throws IOException {
440 if ((numLeft > 0) && (numLeft < len)) {
441 len = (int)numLeft;
442 }
443
444 if (numLeft > 0) {
445 int n = is.read(b, off, len);
446 jv.update(n, b, off, len, mev);
447 numLeft -= n;
448 if (numLeft == 0)
449 jv.update(-1, b, off, len, mev);
450 return n;
451 } else {
452 return -1;
453 }
454 }
455
456 public void close()
457 throws IOException
458 {
459 if (is != null)
460 is.close();
461 is = null;
462 mev = null;
463 jv = null;
464 }
465
466 public int available() throws IOException {
467 return is.available();
468 }
469
470 }
471
472
474 private Map urlToCodeSourceMap = new HashMap();
475 private Map signerToCodeSource = new HashMap();
476 private URL lastURL;
477 private Map lastURLMap;
478
479
484 private synchronized CodeSource mapSignersToCodeSource(URL url, CodeSigner[] signers) {
485 Map map;
486 if (url == lastURL) {
487 map = lastURLMap;
488 } else {
489 map = (Map)urlToCodeSourceMap.get(url);
490 if (map == null) {
491 map = new HashMap();
492 urlToCodeSourceMap.put(url, map);
493 }
494 lastURLMap = map;
495 lastURL = url;
496 }
497 CodeSource cs = (CodeSource) map.get(signers);
498 if (cs == null) {
499 cs = new VerifierCodeSource(csdomain, url, signers);
500 signerToCodeSource.put(signers, cs);
501 }
502 return cs;
503 }
504
505 private CodeSource[] mapSignersToCodeSources(URL url, List signers, boolean unsigned) {
506 List sources = new ArrayList();
507
508 for (int i=0; i < signers.size(); i++) {
509 sources.add(mapSignersToCodeSource(url, (CodeSigner[]) signers.get(i)));
510 }
511 if (unsigned) {
512 sources.add(mapSignersToCodeSource(url, null));
513 }
514 return (CodeSource[])sources.toArray(new CodeSource[sources.size()]);
515 }
516
517 private CodeSigner[] emptySigner = new CodeSigner[0];
518
519
522 private CodeSigner[] findMatchingSigners(CodeSource cs) {
523 if (cs instanceof VerifierCodeSource) {
524 VerifierCodeSource vcs = (VerifierCodeSource) cs;
525 if (vcs.isSameDomain(csdomain)) {
526 return ((VerifierCodeSource)cs).getPrivateSigners();
527 }
528 }
529
530
534 CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true);
535 List sourceList = new ArrayList();
536 for (int i=0; i < sources.length; i++) {
537 sourceList.add(sources[i]);
538 }
539 int j = sourceList.indexOf(cs);
540 if (j != -1) {
541 CodeSigner[] match;
542 match = ((VerifierCodeSource)sourceList.get(j)).getPrivateSigners();
543 if (match == null) {
544 match = emptySigner;
545 }
546 return match;
547 }
548 return null;
549 }
550
551
555 private static class VerifierCodeSource extends CodeSource {
556 URL vlocation;
557 CodeSigner[] vsigners;
558 java.security.cert.Certificate[] vcerts;
559 Object csdomain;
560
561 VerifierCodeSource(Object csdomain, URL location, CodeSigner[] signers) {
562 super(location, signers);
563 this.csdomain = csdomain;
564 vlocation = location;
565 vsigners = signers; }
567
568 VerifierCodeSource(Object csdomain, URL location, java.security.cert.Certificate[] certs) {
569 super(location, certs);
570 this.csdomain = csdomain;
571 vlocation = location;
572 vcerts = certs; }
574
575
581 public boolean equals(Object obj) {
582 if (obj == this) {
583 return true;
584 }
585 if (obj instanceof VerifierCodeSource) {
586 VerifierCodeSource that = (VerifierCodeSource) obj;
587
588
593 if (isSameDomain(that.csdomain)) {
594 if (that.vsigners != this.vsigners ||
595 that.vcerts != this.vcerts) {
596 return false;
597 }
598 if (that.vlocation != null) {
599 return that.vlocation.equals(this.vlocation);
600 } else if (this.vlocation != null) {
601 return this.vlocation.equals(that.vlocation);
602 } else { return true;
604 }
605 }
606 }
607 return super.equals(obj);
608 }
609
610 boolean isSameDomain(Object csdomain) {
611 return this.csdomain == csdomain;
612 }
613
614 private CodeSigner[] getPrivateSigners() {
615 return vsigners;
616 }
617
618 private java.security.cert.Certificate[] getPrivateCertificates() {
619 return vcerts;
620 }
621 }
622
623 private Map signerMap;
624
625 private synchronized Map signerMap() {
626 if (signerMap == null) {
627
632 signerMap = new HashMap(verifiedSigners.size() + sigFileSigners.size());
633 signerMap.putAll(verifiedSigners);
634 signerMap.putAll(sigFileSigners);
635 }
636 return signerMap;
637 }
638
639 public synchronized Enumeration<String> entryNames(JarFile jar, final CodeSource[] cs) {
640 final Map map = signerMap();
641 final Iterator itor = map.entrySet().iterator();
642 boolean matchUnsigned = false;
643
644
648 List req = new ArrayList(cs.length);
649 for (int i=0; i < cs.length; i++) {
650 CodeSigner[] match = findMatchingSigners(cs[i]);
651 if (match != null) {
652 if (match.length > 0) {
653 req.add(match);
654 } else {
655 matchUnsigned = true;
656 }
657 }
658 }
659
660 final List signersReq = req;
661 final Enumeration enum2 = (matchUnsigned) ? unsignedEntryNames(jar) : emptyEnumeration;
662
663 return new Enumeration<String>() {
664 String name;
665
666 public boolean hasMoreElements() {
667 if (name != null) {
668 return true;
669 }
670
671 while (itor.hasNext()) {
672 Map.Entry e = (Map.Entry)itor.next();
673 if (signersReq.contains((CodeSigner[])e.getValue())) {
674 name = (String)e.getKey();
675 return true;
676 }
677 }
678 while (enum2.hasMoreElements()) {
679 name = (String)enum2.nextElement();
680 return true;
681 }
682 return false;
683 }
684 public String nextElement() {
685 if (hasMoreElements()) {
686 String value = name;
687 name = null;
688 return value;
689 }
690 throw new NoSuchElementException();
691 }
692 };
693 }
694
695
699 public Enumeration<JarEntry> entries2(final JarFile jar, Enumeration e) {
700 final Map map = new HashMap();
701 map.putAll(signerMap());
702 final Enumeration enum_ = e;
703 return new Enumeration<JarEntry>() {
704 Enumeration signers = null;
705 JarEntry entry;
706 public boolean hasMoreElements() {
707 if (entry != null) {
708 return true;
709 }
710 while (enum_.hasMoreElements()) {
711 ZipEntry ze = (ZipEntry) enum_.nextElement();
712 if (JarVerifier.isSigningRelated(ze.getName())) {
713 continue;
714 }
715 entry = jar.newEntry(ze);
716 return true;
717 }
718 if (signers == null) {
719 signers = Collections.enumeration(map.keySet());
720 }
721 while (signers.hasMoreElements()) {
722 String name = (String)signers.nextElement();
723 entry = jar.newEntry(new ZipEntry(name));
724 return true;
725 }
726
727 return false;
729 }
730 public JarEntry nextElement() {
731 if (hasMoreElements()) {
732 JarEntry je = entry;
733 map.remove(je.getName());
734 entry = null;
735 return je;
736 }
737 throw new NoSuchElementException();
738 }
739 };
740 }
741
742 private Enumeration emptyEnumeration = new Enumeration<String>() {
743 public boolean hasMoreElements() {
744 return false;
745 }
746 public String nextElement() {
747 throw new NoSuchElementException();
748 }
749 };
750
751 static boolean isSigningRelated(String name) {
753 name = name.toUpperCase(Locale.ENGLISH);
754 if (!name.startsWith("META-INF/")) {
755 return false;
756 }
757 name = name.substring(9);
758 if (name.indexOf('/') != -1) {
759 return false;
760 }
761 if (name.endsWith(".DSA") ||
762 name.endsWith(".RSA") ||
763 name.endsWith(".SF") ||
764 name.endsWith(".EC") ||
765 name.startsWith("SIG-") ||
766 name.equals("MANIFEST.MF")) {
767 return true;
768 }
769 return false;
770 }
771
772 private Enumeration<String> unsignedEntryNames(JarFile jar) {
773 final Map map = signerMap();
774 final Enumeration entries = jar.entries();
775 return new Enumeration<String>() {
776 String name;
777
778
782 public boolean hasMoreElements() {
783 if (name != null) {
784 return true;
785 }
786 while (entries.hasMoreElements()) {
787 String value;
788 ZipEntry e = (ZipEntry) entries.nextElement();
789 value = e.getName();
790 if (e.isDirectory() || isSigningRelated(value)) {
791 continue;
792 }
793 if (map.get(value) == null) {
794 name = value;
795 return true;
796 }
797 }
798 return false;
799 }
800 public String nextElement() {
801 if (hasMoreElements()) {
802 String value = name;
803 name = null;
804 return value;
805 }
806 throw new NoSuchElementException();
807 }
808 };
809 }
810
811 private List jarCodeSigners;
812
813 private synchronized List getJarCodeSigners() {
814 CodeSigner[] signers;
815 if (jarCodeSigners == null) {
816 HashSet set = new HashSet();
817 set.addAll(signerMap().values());
818 jarCodeSigners = new ArrayList();
819 jarCodeSigners.addAll(set);
820 }
821 return jarCodeSigners;
822 }
823
824 public synchronized CodeSource[] getCodeSources(JarFile jar, URL url) {
825 boolean hasUnsigned = unsignedEntryNames(jar).hasMoreElements();
826
827 return mapSignersToCodeSources(url, getJarCodeSigners(), hasUnsigned);
828 }
829
830 public CodeSource getCodeSource(URL url, String name) {
831 CodeSigner[] signers;
832
833 signers = (CodeSigner[]) signerMap().get(name);
834 return mapSignersToCodeSource(url, signers);
835 }
836
837 public CodeSource getCodeSource(URL url, JarFile jar, JarEntry je) {
838 CodeSigner[] signers;
839
840 return mapSignersToCodeSource(url, getCodeSigners(jar, je));
841 }
842
843 public void setEagerValidation(boolean eager) {
844 eagerValidation = eager;
845 }
846
847 public synchronized List getManifestDigests() {
848 return Collections.unmodifiableList(manifestDigests);
849 }
850
851 static CodeSource getUnsignedCS(URL url) {
852 return new VerifierCodeSource(null, url, (java.security.cert.Certificate[]) null);
853 }
854 }
855