--- a/jdk/src/share/classes/java/util/jar/JarVerifier.java Fri Feb 11 12:20:45 2011 -0800
+++ b/jdk/src/share/classes/java/util/jar/JarVerifier.java Sat Feb 12 05:09:36 2011 +0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,9 +26,11 @@
package java.util.jar;
import java.io.*;
+import java.net.URL;
import java.util.*;
import java.security.*;
import java.security.cert.CertificateException;
+import java.util.zip.ZipEntry;
import sun.security.util.ManifestDigester;
import sun.security.util.ManifestEntryVerifier;
@@ -81,6 +83,15 @@
/** the bytes for the manDig object */
byte manifestRawBytes[] = null;
+ /** controls eager signature validation */
+ boolean eagerValidation;
+
+ /** makes code source singleton instances unique to us */
+ private Object csdomain = new Object();
+
+ /** collect -DIGEST-MANIFEST values for blacklist */
+ private List manifestDigests;
+
public JarVerifier(byte rawBytes[]) {
manifestRawBytes = rawBytes;
sigFileSigners = new Hashtable();
@@ -88,6 +99,7 @@
sigFileData = new Hashtable(11);
pendingBlocks = new ArrayList();
baos = new ByteArrayOutputStream();
+ manifestDigests = new ArrayList();
}
/**
@@ -247,7 +259,7 @@
}
sfv.setSignatureFile(bytes);
- sfv.process(sigFileSigners);
+ sfv.process(sigFileSigners, manifestDigests);
}
}
return;
@@ -290,7 +302,7 @@
sfv.setSignatureFile(bytes);
}
}
- sfv.process(sigFileSigners);
+ sfv.process(sigFileSigners, manifestDigests);
} catch (IOException ioe) {
// e.g. sun.security.pkcs.ParsingException
@@ -312,12 +324,18 @@
/**
* Return an array of java.security.cert.Certificate objects for
* the given file in the jar.
+ * @deprecated
*/
public java.security.cert.Certificate[] getCerts(String name)
{
return mapSignersToCertArray(getCodeSigners(name));
}
+ public java.security.cert.Certificate[] getCerts(JarFile jar, JarEntry entry)
+ {
+ return mapSignersToCertArray(getCodeSigners(jar, entry));
+ }
+
/**
* return an array of CodeSigner objects for
* the given file in the jar. this array is not cloned.
@@ -328,6 +346,28 @@
return (CodeSigner[])verifiedSigners.get(name);
}
+ public CodeSigner[] getCodeSigners(JarFile jar, JarEntry entry)
+ {
+ String name = entry.getName();
+ if (eagerValidation && sigFileSigners.get(name) != null) {
+ /*
+ * Force a read of the entry data to generate the
+ * verification hash.
+ */
+ try {
+ InputStream s = jar.getInputStream(entry);
+ byte[] buffer = new byte[1024];
+ int n = buffer.length;
+ while (n != -1) {
+ n = s.read(buffer, 0, buffer.length);
+ }
+ s.close();
+ } catch (IOException e) {
+ }
+ }
+ return getCodeSigners(name);
+ }
+
/*
* Convert an array of signers into an array of concatenated certificate
* arrays.
@@ -444,4 +484,393 @@
}
}
+
+ // Extended JavaUtilJarAccess CodeSource API Support
+
+ private Map urlToCodeSourceMap = new HashMap();
+ private Map signerToCodeSource = new HashMap();
+ private URL lastURL;
+ private Map lastURLMap;
+
+ /*
+ * Create a unique mapping from codeSigner cache entries to CodeSource.
+ * In theory, multiple URLs origins could map to a single locally cached
+ * and shared JAR file although in practice there will be a single URL in use.
+ */
+ private synchronized CodeSource mapSignersToCodeSource(URL url, CodeSigner[] signers) {
+ Map map;
+ if (url == lastURL) {
+ map = lastURLMap;
+ } else {
+ map = (Map) urlToCodeSourceMap.get(url);
+ if (map == null) {
+ map = new HashMap();
+ urlToCodeSourceMap.put(url, map);
+ }
+ lastURLMap = map;
+ lastURL = url;
+ }
+ CodeSource cs = (CodeSource) map.get(signers);
+ if (cs == null) {
+ cs = new VerifierCodeSource(csdomain, url, signers);
+ signerToCodeSource.put(signers, cs);
+ }
+ return cs;
+ }
+
+ private CodeSource[] mapSignersToCodeSources(URL url, List signers, boolean unsigned) {
+ List sources = new ArrayList();
+
+ for (int i = 0; i < signers.size(); i++) {
+ sources.add(mapSignersToCodeSource(url, (CodeSigner[]) signers.get(i)));
+ }
+ if (unsigned) {
+ sources.add(mapSignersToCodeSource(url, null));
+ }
+ return (CodeSource[]) sources.toArray(new CodeSource[sources.size()]);
+ }
+ private CodeSigner[] emptySigner = new CodeSigner[0];
+
+ /*
+ * Match CodeSource to a CodeSigner[] in the signer cache.
+ */
+ private CodeSigner[] findMatchingSigners(CodeSource cs) {
+ if (cs instanceof VerifierCodeSource) {
+ VerifierCodeSource vcs = (VerifierCodeSource) cs;
+ if (vcs.isSameDomain(csdomain)) {
+ return ((VerifierCodeSource) cs).getPrivateSigners();
+ }
+ }
+
+ /*
+ * In practice signers should always be optimized above
+ * but this handles a CodeSource of any type, just in case.
+ */
+ CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true);
+ List sourceList = new ArrayList();
+ for (int i = 0; i < sources.length; i++) {
+ sourceList.add(sources[i]);
+ }
+ int j = sourceList.indexOf(cs);
+ if (j != -1) {
+ CodeSigner[] match;
+ match = ((VerifierCodeSource) sourceList.get(j)).getPrivateSigners();
+ if (match == null) {
+ match = emptySigner;
+ }
+ return match;
+ }
+ return null;
+ }
+
+ /*
+ * Instances of this class hold uncopied references to internal
+ * signing data that can be compared by object reference identity.
+ */
+ private static class VerifierCodeSource extends CodeSource {
+
+ URL vlocation;
+ CodeSigner[] vsigners;
+ java.security.cert.Certificate[] vcerts;
+ Object csdomain;
+
+ VerifierCodeSource(Object csdomain, URL location, CodeSigner[] signers) {
+ super(location, signers);
+ this.csdomain = csdomain;
+ vlocation = location;
+ vsigners = signers; // from signerCache
+ }
+
+ VerifierCodeSource(Object csdomain, URL location, java.security.cert.Certificate[] certs) {
+ super(location, certs);
+ this.csdomain = csdomain;
+ vlocation = location;
+ vcerts = certs; // from signerCache
+ }
+
+ /*
+ * All VerifierCodeSource instances are constructed based on
+ * singleton signerCache or signerCacheCert entries for each unique signer.
+ * No CodeSigner<->Certificate[] conversion is required.
+ * We use these assumptions to optimize equality comparisons.
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof VerifierCodeSource) {
+ VerifierCodeSource that = (VerifierCodeSource) obj;
+
+ /*
+ * Only compare against other per-signer singletons constructed
+ * on behalf of the same JarFile instance. Otherwise, compare
+ * things the slower way.
+ */
+ if (isSameDomain(that.csdomain)) {
+ if (that.vsigners != this.vsigners
+ || that.vcerts != this.vcerts) {
+ return false;
+ }
+ if (that.vlocation != null) {
+ return that.vlocation.equals(this.vlocation);
+ } else if (this.vlocation != null) {
+ return this.vlocation.equals(that.vlocation);
+ } else { // both null
+ return true;
+ }
+ }
+ }
+ return super.equals(obj);
+ }
+
+ boolean isSameDomain(Object csdomain) {
+ return this.csdomain == csdomain;
+ }
+
+ private CodeSigner[] getPrivateSigners() {
+ return vsigners;
+ }
+
+ private java.security.cert.Certificate[] getPrivateCertificates() {
+ return vcerts;
+ }
+ }
+ private Map signerMap;
+
+ private synchronized Map signerMap() {
+ if (signerMap == null) {
+ /*
+ * Snapshot signer state so it doesn't change on us. We care
+ * only about the asserted signatures. Verification of
+ * signature validity happens via the JarEntry apis.
+ */
+ signerMap = new HashMap(verifiedSigners.size() + sigFileSigners.size());
+ signerMap.putAll(verifiedSigners);
+ signerMap.putAll(sigFileSigners);
+ }
+ return signerMap;
+ }
+
+ public synchronized Enumeration<String> entryNames(JarFile jar, final CodeSource[] cs) {
+ final Map map = signerMap();
+ final Iterator itor = map.entrySet().iterator();
+ boolean matchUnsigned = false;
+
+ /*
+ * Grab a single copy of the CodeSigner arrays. Check
+ * to see if we can optimize CodeSigner equality test.
+ */
+ List req = new ArrayList(cs.length);
+ for (int i = 0; i < cs.length; i++) {
+ CodeSigner[] match = findMatchingSigners(cs[i]);
+ if (match != null) {
+ if (match.length > 0) {
+ req.add(match);
+ } else {
+ matchUnsigned = true;
+ }
+ }
+ }
+
+ final List signersReq = req;
+ final Enumeration enum2 = (matchUnsigned) ? unsignedEntryNames(jar) : emptyEnumeration;
+
+ return new Enumeration<String>() {
+
+ String name;
+
+ public boolean hasMoreElements() {
+ if (name != null) {
+ return true;
+ }
+
+ while (itor.hasNext()) {
+ Map.Entry e = (Map.Entry) itor.next();
+ if (signersReq.contains((CodeSigner[]) e.getValue())) {
+ name = (String) e.getKey();
+ return true;
+ }
+ }
+ while (enum2.hasMoreElements()) {
+ name = (String) enum2.nextElement();
+ return true;
+ }
+ return false;
+ }
+
+ public String nextElement() {
+ if (hasMoreElements()) {
+ String value = name;
+ name = null;
+ return value;
+ }
+ throw new NoSuchElementException();
+ }
+ };
+ }
+
+ /*
+ * Like entries() but screens out internal JAR mechanism entries
+ * and includes signed entries with no ZIP data.
+ */
+ public Enumeration<JarEntry> entries2(final JarFile jar, Enumeration e) {
+ final Map map = new HashMap();
+ map.putAll(signerMap());
+ final Enumeration enum_ = e;
+ return new Enumeration<JarEntry>() {
+
+ Enumeration signers = null;
+ JarEntry entry;
+
+ public boolean hasMoreElements() {
+ if (entry != null) {
+ return true;
+ }
+ while (enum_.hasMoreElements()) {
+ ZipEntry ze = (ZipEntry) enum_.nextElement();
+ if (JarVerifier.isSigningRelated(ze.getName())) {
+ continue;
+ }
+ entry = jar.newEntry(ze);
+ return true;
+ }
+ if (signers == null) {
+ signers = Collections.enumeration(map.keySet());
+ }
+ while (signers.hasMoreElements()) {
+ String name = (String) signers.nextElement();
+ entry = jar.newEntry(new ZipEntry(name));
+ return true;
+ }
+
+ // Any map entries left?
+ return false;
+ }
+
+ public JarEntry nextElement() {
+ if (hasMoreElements()) {
+ JarEntry je = entry;
+ map.remove(je.getName());
+ entry = null;
+ return je;
+ }
+ throw new NoSuchElementException();
+ }
+ };
+ }
+ private Enumeration emptyEnumeration = new Enumeration<String>() {
+
+ public boolean hasMoreElements() {
+ return false;
+ }
+
+ public String nextElement() {
+ throw new NoSuchElementException();
+ }
+ };
+
+ // true if file is part of the signature mechanism itself
+ static boolean isSigningRelated(String name) {
+ name = name.toUpperCase(Locale.ENGLISH);
+ if (!name.startsWith("META-INF/")) {
+ return false;
+ }
+ name = name.substring(9);
+ if (name.indexOf('/') != -1) {
+ return false;
+ }
+ if (name.endsWith(".DSA")
+ || name.endsWith(".RSA")
+ || name.endsWith(".SF")
+ || name.endsWith(".EC")
+ || name.startsWith("SIG-")
+ || name.equals("MANIFEST.MF")) {
+ return true;
+ }
+ return false;
+ }
+
+ private Enumeration<String> unsignedEntryNames(JarFile jar) {
+ final Map map = signerMap();
+ final Enumeration entries = jar.entries();
+ return new Enumeration<String>() {
+
+ String name;
+
+ /*
+ * Grab entries from ZIP directory but screen out
+ * metadata.
+ */
+ public boolean hasMoreElements() {
+ if (name != null) {
+ return true;
+ }
+ while (entries.hasMoreElements()) {
+ String value;
+ ZipEntry e = (ZipEntry) entries.nextElement();
+ value = e.getName();
+ if (e.isDirectory() || isSigningRelated(value)) {
+ continue;
+ }
+ if (map.get(value) == null) {
+ name = value;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String nextElement() {
+ if (hasMoreElements()) {
+ String value = name;
+ name = null;
+ return value;
+ }
+ throw new NoSuchElementException();
+ }
+ };
+ }
+ private List jarCodeSigners;
+
+ private synchronized List getJarCodeSigners() {
+ CodeSigner[] signers;
+ if (jarCodeSigners == null) {
+ HashSet set = new HashSet();
+ set.addAll(signerMap().values());
+ jarCodeSigners = new ArrayList();
+ jarCodeSigners.addAll(set);
+ }
+ return jarCodeSigners;
+ }
+
+ public synchronized CodeSource[] getCodeSources(JarFile jar, URL url) {
+ boolean hasUnsigned = unsignedEntryNames(jar).hasMoreElements();
+
+ return mapSignersToCodeSources(url, getJarCodeSigners(), hasUnsigned);
+ }
+
+ public CodeSource getCodeSource(URL url, String name) {
+ CodeSigner[] signers;
+
+ signers = (CodeSigner[]) signerMap().get(name);
+ return mapSignersToCodeSource(url, signers);
+ }
+
+ public CodeSource getCodeSource(URL url, JarFile jar, JarEntry je) {
+ CodeSigner[] signers;
+
+ return mapSignersToCodeSource(url, getCodeSigners(jar, je));
+ }
+
+ public void setEagerValidation(boolean eager) {
+ eagerValidation = eager;
+ }
+
+ public synchronized List getManifestDigests() {
+ return Collections.unmodifiableList(manifestDigests);
+ }
+
+ static CodeSource getUnsignedCS(URL url) {
+ return new VerifierCodeSource(null, url, (java.security.cert.Certificate[]) null);
+ }
}