JarFile class is used to read the contents of a jar file
@@ -179,7 +178,7 @@ class JarFile extends ZipFile {
byte[] b = getBytes(manEntry);
man = new Manifest(new ByteArrayInputStream(b));
if (!jvInitialized) {
- jv = new JarVerifier(b, man);
+ jv = new JarVerifier(b);
}
} else {
man = new Manifest(super.getInputStream(manEntry));
@@ -298,7 +297,10 @@ class JarFile extends ZipFile {
if (names != null) {
for (int i = 0; i < names.length; i++) {
String name = names[i].toUpperCase(Locale.ENGLISH);
- if (SignatureFileVerifier.isBlockOrSF(name)) {
+ if (name.endsWith(".DSA") ||
+ name.endsWith(".RSA") ||
+ name.endsWith(".EC") ||
+ name.endsWith(".SF")) {
// Assume since we found a signature-related file
// that the jar is signed and that we therefore
// need a JarVerifier and Manifest
@@ -327,17 +329,17 @@ class JarFile extends ZipFile {
if (names != null) {
for (int i = 0; i < names.length; i++) {
JarEntry e = getJarEntry(names[i]);
- if (!e.isDirectory() &&
- SignatureFileVerifier.isBlock(names[i])) {
+ if (!e.isDirectory()) {
if (mev == null) {
mev = new ManifestEntryVerifier
(getManifestFromReference());
}
- String key = names[i].substring(
- 0, names[i].lastIndexOf("."));
- jv.verifyBlock(names[i],
- getBytes(e),
- super.getInputStream(getJarEntry(key + ".SF")));
+ byte[] b = getBytes(e);
+ if (b != null && b.length > 0) {
+ jv.beginEntry(e, mev);
+ jv.update(b.length, b, 0, b.length, mev);
+ jv.update(-1, null, 0, 0, mev);
+ }
}
}
}
diff --git a/src/share/classes/java/util/jar/JarInputStream.java b/src/share/classes/java/util/jar/JarInputStream.java
index b84219ea87a347d372635e8003fe0f51aa748067..67f27be2975db825af0ace5b9a4966d53345f384 100644
--- a/src/share/classes/java/util/jar/JarInputStream.java
+++ b/src/share/classes/java/util/jar/JarInputStream.java
@@ -95,7 +95,7 @@ class JarInputStream extends ZipInputStream {
man.read(new ByteArrayInputStream(bytes));
closeEntry();
if (doVerify) {
- jv = new JarVerifier(bytes, man);
+ jv = new JarVerifier(bytes);
mev = new ManifestEntryVerifier(man);
}
return (JarEntry)super.getNextEntry();
diff --git a/src/share/classes/java/util/jar/JarVerifier.java b/src/share/classes/java/util/jar/JarVerifier.java
index 8c69ff5c583ee29a08ece117cace47b7d8b840b2..4f84ac28effd82e57f9cbcacf4dc917bb3261ea1 100644
--- a/src/share/classes/java/util/jar/JarVerifier.java
+++ b/src/share/classes/java/util/jar/JarVerifier.java
@@ -48,18 +48,35 @@ class JarVerifier {
/* a table mapping names to code signers, for jar entries that have
had their actual hashes verified */
- private Map verifiedSigners;
+ private Hashtable verifiedSigners;
/* a table mapping names to code signers, for jar entries that have
passed the .SF/.DSA/.EC -> MANIFEST check */
- private Map sigFileSigners;
+ private Hashtable sigFileSigners;
+
+ /* a hash table to hold .SF bytes */
+ private Hashtable sigFileData;
+
+ /** "queue" of pending PKCS7 blocks that we couldn't parse
+ * until we parsed the .SF file */
+ private ArrayList pendingBlocks;
/* cache of CodeSigner objects */
private ArrayList signerCache;
+ /* Are we parsing a block? */
+ private boolean parsingBlockOrSF = false;
+
+ /* Are we done parsing META-INF entries? */
+ private boolean parsingMeta = true;
+
/* Are there are files to verify? */
private boolean anyToVerify = true;
+ /* The output stream to use when keeping track of files we are interested
+ in */
+ private ByteArrayOutputStream baos;
+
/** The ManifestDigester object */
private volatile ManifestDigester manDig;
@@ -75,20 +92,20 @@ class JarVerifier {
/** collect -DIGEST-MANIFEST values for blacklist */
private List manifestDigests;
- /** The manifest object */
- Manifest man = null;
-
- public JarVerifier(byte rawBytes[], Manifest man) {
- this.man = man;
+ public JarVerifier(byte rawBytes[]) {
manifestRawBytes = rawBytes;
- sigFileSigners = new HashMap();
- verifiedSigners = new HashMap();
+ sigFileSigners = new Hashtable();
+ verifiedSigners = new Hashtable();
+ sigFileData = new Hashtable(11);
+ pendingBlocks = new ArrayList();
+ baos = new ByteArrayOutputStream();
manifestDigests = new ArrayList();
}
/**
- * This method scans to see which entry we're parsing and keeps
- * various state information depending on the file being parsed.
+ * This method scans to see which entry we're parsing and
+ * keeps various state information depending on what type of
+ * file is being parsed.
*/
public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
throws IOException
@@ -112,6 +129,30 @@ class JarVerifier {
* b. digest mismatch between the actual jar entry and the manifest
*/
+ if (parsingMeta) {
+ String uname = name.toUpperCase(Locale.ENGLISH);
+ if ((uname.startsWith("META-INF/") ||
+ uname.startsWith("/META-INF/"))) {
+
+ if (je.isDirectory()) {
+ mev.setEntry(null, je);
+ return;
+ }
+
+ if (SignatureFileVerifier.isBlockOrSF(uname)) {
+ /* We parse only DSA, RSA or EC PKCS7 blocks. */
+ parsingBlockOrSF = true;
+ baos.reset();
+ mev.setEntry(null, je);
+ }
+ return;
+ }
+ }
+
+ if (parsingMeta) {
+ doneWithMeta();
+ }
+
if (je.isDirectory()) {
mev.setEntry(null, je);
return;
@@ -147,7 +188,11 @@ class JarVerifier {
throws IOException
{
if (b != -1) {
- mev.update((byte)b);
+ if (parsingBlockOrSF) {
+ baos.write(b);
+ } else {
+ mev.update((byte)b);
+ }
} else {
processEntry(mev);
}
@@ -162,7 +207,11 @@ class JarVerifier {
throws IOException
{
if (n != -1) {
- mev.update(b, off, n);
+ if (parsingBlockOrSF) {
+ baos.write(b, off, n);
+ } else {
+ mev.update(b, off, n);
+ }
} else {
processEntry(mev);
}
@@ -174,10 +223,101 @@ class JarVerifier {
private void processEntry(ManifestEntryVerifier mev)
throws IOException
{
- JarEntry je = mev.getEntry();
- if ((je != null) && (je.signers == null)) {
- je.signers = mev.verify(verifiedSigners, sigFileSigners);
- je.certs = mapSignersToCertArray(je.signers);
+ if (!parsingBlockOrSF) {
+ JarEntry je = mev.getEntry();
+ if ((je != null) && (je.signers == null)) {
+ je.signers = mev.verify(verifiedSigners, sigFileSigners);
+ je.certs = mapSignersToCertArray(je.signers);
+ }
+ } else {
+
+ try {
+ parsingBlockOrSF = false;
+
+ if (debug != null) {
+ debug.println("processEntry: processing block");
+ }
+
+ String uname = mev.getEntry().getName()
+ .toUpperCase(Locale.ENGLISH);
+
+ if (uname.endsWith(".SF")) {
+ String key = uname.substring(0, uname.length()-3);
+ byte bytes[] = baos.toByteArray();
+ // add to sigFileData in case future blocks need it
+ sigFileData.put(key, bytes);
+ // check pending blocks, we can now process
+ // anyone waiting for this .SF file
+ Iterator it = pendingBlocks.iterator();
+ while (it.hasNext()) {
+ SignatureFileVerifier sfv =
+ (SignatureFileVerifier) it.next();
+ if (sfv.needSignatureFile(key)) {
+ if (debug != null) {
+ debug.println(
+ "processEntry: processing pending block");
+ }
+
+ sfv.setSignatureFile(bytes);
+ sfv.process(sigFileSigners, manifestDigests);
+ }
+ }
+ return;
+ }
+
+ // now we are parsing a signature block file
+
+ String key = uname.substring(0, uname.lastIndexOf("."));
+
+ if (signerCache == null)
+ signerCache = new ArrayList();
+
+ if (manDig == null) {
+ synchronized(manifestRawBytes) {
+ if (manDig == null) {
+ manDig = new ManifestDigester(manifestRawBytes);
+ manifestRawBytes = null;
+ }
+ }
+ }
+
+ SignatureFileVerifier sfv =
+ new SignatureFileVerifier(signerCache,
+ manDig, uname, baos.toByteArray());
+
+ if (sfv.needSignatureFileBytes()) {
+ // see if we have already parsed an external .SF file
+ byte[] bytes = (byte[]) sigFileData.get(key);
+
+ if (bytes == null) {
+ // put this block on queue for later processing
+ // since we don't have the .SF bytes yet
+ // (uname, block);
+ if (debug != null) {
+ debug.println("adding pending block");
+ }
+ pendingBlocks.add(sfv);
+ return;
+ } else {
+ sfv.setSignatureFile(bytes);
+ }
+ }
+ sfv.process(sigFileSigners, manifestDigests);
+
+ } catch (IOException ioe) {
+ // e.g. sun.security.pkcs.ParsingException
+ if (debug != null) debug.println("processEntry caught: "+ioe);
+ // ignore and treat as unsigned
+ } catch (SignatureException se) {
+ if (debug != null) debug.println("processEntry caught: "+se);
+ // ignore and treat as unsigned
+ } catch (NoSuchAlgorithmException nsae) {
+ if (debug != null) debug.println("processEntry caught: "+nsae);
+ // ignore and treat as unsigned
+ } catch (CertificateException ce) {
+ if (debug != null) debug.println("processEntry caught: "+ce);
+ // ignore and treat as unsigned
+ }
}
}
@@ -214,15 +354,15 @@ class JarVerifier {
* Force a read of the entry data to generate the
* verification hash.
*/
- try (InputStream s = jar.getInputStream(entry)) {
+ 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) {
- // Ignore. When an exception is thrown, code signer
- // will not be assigned.
}
}
return getCodeSigners(name);
@@ -268,7 +408,11 @@ class JarVerifier {
*/
void doneWithMeta()
{
+ parsingMeta = false;
anyToVerify = !sigFileSigners.isEmpty();
+ baos = null;
+ sigFileData = null;
+ pendingBlocks = null;
signerCache = null;
manDig = null;
// MANIFEST.MF is always treated as signed and verified,
@@ -279,41 +423,6 @@ class JarVerifier {
}
}
- /**
- * Verifies a PKCS7 SignedData block
- * @param key name of block
- * @param block the pkcs7 file
- * @param ins the clear data
- */
- void verifyBlock(String key, byte[] block, InputStream ins) {
- try {
- if (signerCache == null)
- signerCache = new ArrayList();
-
- if (manDig == null) {
- synchronized(manifestRawBytes) {
- if (manDig == null) {
- manDig = new ManifestDigester(manifestRawBytes);
- manifestRawBytes = null;
- }
- }
- }
- SignatureFileVerifier sfv =
- new SignatureFileVerifier(signerCache, man,
- manDig, key, block);
-
- if (sfv.needSignatureFile()) {
- // see if we have already parsed an external .SF file
- sfv.setSignatureFile(ins);
- }
- sfv.process(sigFileSigners, manifestDigests);
- } catch (Exception e) {
- if (debug != null) {
- e.printStackTrace();
- }
- }
- }
-
static class VerifierStream extends java.io.InputStream {
private InputStream is;
@@ -444,7 +553,10 @@ class JarVerifier {
* but this handles a CodeSource of any type, just in case.
*/
CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true);
- List sourceList = Arrays.asList(sources);
+ 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;
diff --git a/src/share/classes/sun/security/pkcs/PKCS7.java b/src/share/classes/sun/security/pkcs/PKCS7.java
index 72a2e5f9f3ef08c65240b1e08700e22f32c7060a..54e5215125603546b698225c35f467e5dfc687a0 100644
--- a/src/share/classes/sun/security/pkcs/PKCS7.java
+++ b/src/share/classes/sun/security/pkcs/PKCS7.java
@@ -38,7 +38,6 @@ import java.security.*;
import sun.security.util.*;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificateIssuerName;
-import sun.security.x509.KeyUsageExtension;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;
import sun.security.x509.X509CRLImpl;
@@ -493,7 +492,7 @@ public class PKCS7 {
// CRLs (optional)
if (crls != null && crls.length != 0) {
// cast to X509CRLImpl[] since X509CRLImpl implements DerEncoder
- Set- * This method must be called in a row, reading chunks from a single - * manifest file by order. After all chunks are read, caller must call - * {@code update(null)} to fully consume the manifest. - *
- * The entry names and attributes read will be merged in with the current - * manifest entries. The {@link #read} method cannot be called inside a - * row of update calls. - *
- * Along with the calls, caller can call {@link #getMainAttributes()},
- * {@link #getAttributes(java.lang.String)} or {@link #getEntries()}
- * to get already available contents. However, in order not to return
- * partial result, when the main attributes in the new manifest is not
- * consumed completely, {@link #getMainAttributes()} throws an
- * {@code IllegalStateException}. When a certain named entry is not
- * consumed completely, {@link #getAttributes(java.lang.String)}
- * returns the old {@code Attributes} for the name (if it exists).
- *
- * @param data null for last call, otherwise, feeding chunks
- * @param offset offset into data to begin read
- * @param length length of data after offset to read
- * @exception IOException if an I/O error has occurred
- * @exception IllegalStateException if {@code update(null)} is called
- * without any previous {@code update(non-null)} call
- */
- public void update(byte[] data, int offset, int length) throws IOException {
-
- // The last call
- if (data == null) {
- if (state == 0) {
- throw new IllegalStateException("No data to update");
- }
- // We accept manifest not ended with \n or \n\n
- if (hasLastByte()) {
- consumeCurrent();
- }
- // We accept empty lines at the end
- if (!currentAttr.isEmpty()) {
- consumeAttr();
- }
- state = 0; // back to non-update state
- current = null;
- currentAttr = null;
- return;
- }
-
- // The first call
- if (state == 0) {
- current = new byte[1024];
- currentAttr = super.getMainAttributes(); // the main attribute
- state = 1;
- }
-
- int end = offset + length;
-
- while (offset < end) {
- switch (data[offset]) {
- case '\r':
- break; // always skip
- case '\n':
- if (hasLastByte() && lastByte() == '\n') { // new section
- consumeCurrent();
- consumeAttr();
- if (state == 1) {
- state = 2;
- }
- currentAttr = new Attributes(2);
- } else {
- if (hasLastByte()) {
- // save \n into current but do not parse,
- // there might be a continuation later
- ensureCapacity();
- current[currentPos++] = data[offset];
- } else if (state == 1) {
- // there can be multiple empty lines between
- // sections, but cannot be at the beginning
- throw new IOException("invalid manifest format");
- }
- }
- break;
- case ' ':
- if (!hasLastByte()) {
- throw new IOException("invalid manifest format");
- } else if (lastByte() == '\n') {
- currentPos--; // continuation, remove last \n
- } else { // a very normal ' '
- ensureCapacity();
- current[currentPos++] = data[offset];
- }
- break;
- default:
- if (hasLastByte() && lastByte() == '\n') {
- // The start of a new pair, not continuation
- consumeCurrent(); // the last line read
- }
- ensureCapacity();
- current[currentPos++] = data[offset];
- break;
- }
- offset++;
- }
- }
-
- /**
- * Returns the main Attributes for the Manifest.
- * @exception IllegalStateException the main attributes is being read
- * @return the main Attributes for the Manifest
- */
- public Attributes getMainAttributes() {
- if (state == 1) {
- throw new IllegalStateException();
- }
- return super.getMainAttributes();
- }
-
- /**
- * Reads the Manifest from the specified InputStream. The entry
- * names and attributes read will be merged in with the current
- * manifest entries.
- *
- * @param is the input stream
- * @exception IOException if an I/O error has occurred
- * @exception IllegalStateException if called between two {@link #update}
- * calls
- */
- public void read(InputStream is) throws IOException {
- if (state != 0) {
- throw new IllegalStateException("Cannot call read between updates");
- }
- super.read(is);
- }
-
- /*
- * ---------- Helper methods -----------------
- */
-
- private void ensureCapacity() {
- if (currentPos >= current.length-1) {
- current = Arrays.copyOf(current, current.length*2);
- }
- }
-
- private boolean hasLastByte() {
- return currentPos > 0;
- }
-
- private byte lastByte() {
- return current[currentPos-1];
- }
-
- // Parse current as key:value and save into currentAttr.
- // There MUST be something inside current.
- private void consumeCurrent() throws IOException {
- // current normally has a \n end, except for the last line
- if (current[currentPos-1] == '\n') currentPos--;
- for (int i=0; i