# HG changeset patch # User redestad # Date 1588879826 -7200 # Thu May 07 21:30:26 2020 +0200 # Node ID ea14bb9efccf99d99711b1ddbee9764d29c712e6 # Parent 88b19513bd023ebc4c799ce557b84b0b3397a30c imported patch jarf_signature diff --git a/src/java.base/share/classes/java/util/jar/JarFile.java b/src/java.base/share/classes/java/util/jar/JarFile.java --- a/src/java.base/share/classes/java/util/jar/JarFile.java +++ b/src/java.base/share/classes/java/util/jar/JarFile.java @@ -715,21 +715,15 @@ } if (verify) { - String[] names = JUZFA.getMetaInfEntryNames(this); - if (names != null) { - for (String nameLower : names) { - String name = nameLower.toUpperCase(Locale.ENGLISH); - 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 - getManifest(); - return; - } - } + // Gets the manifest name, but only if there are + // signature-related files. If so we can assume + // that the jar is signed and that we therefore + // need a JarVerifier and Manifest + String name = JUZFA.getManifestName(this, true); + if (name != null) { + manEntry = getEntry0(name); + getManifest(); + return; } // No signature-related files; don't instantiate a // verifier @@ -737,6 +731,7 @@ } } + /* * Initializes the verifier object by reading all the manifest * entries and passing them to the verifier. @@ -746,27 +741,21 @@ // Verify "META-INF/" entries... try { - String[] names = JUZFA.getMetaInfEntryNames(this); - if (names != null) { - for (String name : names) { - String uname = name.toUpperCase(Locale.ENGLISH); - if (MANIFEST_NAME.equals(uname) - || SignatureFileVerifier.isBlockOrSF(uname)) { - JarEntry e = getJarEntry(name); - if (e == null) { - throw new JarException("corrupted jar file"); - } - if (mev == null) { - mev = new ManifestEntryVerifier - (getManifestFromReference()); - } - 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); - } - } + List names = JUZFA.getManifestAndSignatureRelatedFiles(this); + for (String name : names) { + JarEntry e = getJarEntry(name); + if (e == null) { + throw new JarException("corrupted jar file"); + } + if (mev == null) { + mev = new ManifestEntryVerifier + (getManifestFromReference()); + } + 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); } } } catch (IOException ex) { @@ -940,14 +929,9 @@ if (manEntry == null) { // If not found, then iterate through all the "META-INF/" // entries to find a match. - String[] names = JUZFA.getMetaInfEntryNames(this); - if (names != null) { - for (String name : names) { - if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) { - manEntry = getEntry0(name); - break; - } - } + String name = JUZFA.getManifestName(this, false); + if (name != null) { + manEntry = getEntry0(name); } } this.manEntry = manEntry; @@ -1213,7 +1197,7 @@ ensureInitialization(); if (jv != null) { if (jv.eagerValidation) { - CodeSource cs = null; + CodeSource cs; JarEntry je = getJarEntry(name); if (je != null) { cs = jv.getCodeSource(url, this, je); diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -45,6 +45,8 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; +import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.NoSuchElementException; import java.util.Set; @@ -1010,32 +1012,52 @@ } /** - * Returns the names of all non-directory entries that begin with - * "META-INF/" (case ignored). This method is used in JarFile, via - * SharedSecrets, as an optimization when looking up manifest and - * signature file entries. Returns null if no entries were found. + * Returns the names of the META-INF/MANIFEST.MF entry - if exists - + * and any signature-related files under META-INF. This method is used in + * JarFile, via SharedSecrets, as an optimization. */ - private String[] getMetaInfEntryNames() { + private List getManifestAndSignatureRelatedFiles() { synchronized (this) { ensureOpen(); Source zsrc = res.zsrc; - if (zsrc.metanames == null) { - return null; + int[] metanames = zsrc.signatureMetaNames; + List files = null; + if (zsrc.manifestPos >= 0) { + files = new ArrayList<>(); + files.add(getEntryName(zsrc.manifestPos)); } - String[] names = new String[zsrc.metanames.length]; - byte[] cen = zsrc.cen; - for (int i = 0; i < names.length; i++) { - int pos = zsrc.metanames[i]; - // This will only be invoked on JarFile, which is guaranteed - // to use (or be compatible with) UTF-8 encoding. - names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos), - UTF_8.INSTANCE); + if (metanames != null) { + if (files == null) { + files = new ArrayList<>(); + } + for (int i = 0; i < metanames.length; i++) { + files.add(getEntryName(metanames[i])); + } } - return names; + return files == null ? List.of() : files; } } /** + * Returns the name of the META-INF/MANIFEST.MF entry, ignoring + * case. If {@code onlyIfSignatureRelatedFiles} is true, we only return the + * manifest if there is also at least one signature-related file. + * This method is used in JarFile, via SharedSecrets, as an optimization + * when looking up the manifest file. + */ + private String getManifestName(boolean onlyIfSignatureRelatedFiles) { + synchronized (this) { + ensureOpen(); + Source zsrc = res.zsrc; + int pos = zsrc.manifestPos; + if (pos >= 0 && (!onlyIfSignatureRelatedFiles || zsrc.signatureMetaNames != null)) { + return getEntryName(pos); + } + } + return null; + } + + /** * Returns the versions for which there exists a non-directory * entry that begin with "META-INF/versions/" (case ignored). * This method is used in JarFile, via SharedSecrets, as an @@ -1059,8 +1081,12 @@ return zip.res.zsrc.startsWithLoc; } @Override - public String[] getMetaInfEntryNames(JarFile jar) { - return ((ZipFile)jar).getMetaInfEntryNames(); + public List getManifestAndSignatureRelatedFiles(JarFile jar) { + return ((ZipFile)jar).getManifestAndSignatureRelatedFiles(); + } + @Override + public String getManifestName(JarFile jar, boolean onlyIfHasSignatureRelatedFiles) { + return ((ZipFile)jar).getManifestName(onlyIfHasSignatureRelatedFiles); } @Override public int[] getMetaInfVersions(JarFile jar) { @@ -1105,7 +1131,8 @@ private long locpos; // position of first LOC header (usually 0) private byte[] comment; // zip file comment // list of meta entries in META-INF dir - private int[] metanames; + private int manifestPos = -1; // position of the META-INF/MANIFEST.MF, if exists + private int[] signatureMetaNames; // positions of signature related entries, if such exist private int[] metaVersions; // list of unique versions found in META-INF/versions/ private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) @@ -1254,7 +1281,8 @@ cen = null; entries = null; table = null; - metanames = null; + manifestPos = -1; + signatureMetaNames = null; metaVersions = EMPTY_META_VERSIONS; } @@ -1438,7 +1466,7 @@ int next; // list for all meta entries - ArrayList metanamesList = null; + ArrayList signatureNames = null; // Set of all version numbers seen in META-INF/versions/ Set metaVersionsSet = null; @@ -1476,19 +1504,26 @@ idx = addEntry(idx, hash, next, pos); // Adds name to metanames. if (isMetaName(cen, entryPos, nlen)) { - if (metanamesList == null) - metanamesList = new ArrayList<>(4); - metanamesList.add(pos); + if (isManifestName(cen, entryPos + META_INF_LENGTH, + nlen - META_INF_LENGTH)) { + manifestPos = entryPos; + } else { + if (isSignatureRelated(cen, entryPos, nlen)) { + if (signatureNames == null) + signatureNames = new ArrayList<>(4); + signatureNames.add(pos); + } - // If this is a versioned entry, parse the version - // and store it for later. This optimizes lookup - // performance in multi-release jar files - int version = getMetaVersion(cen, - entryPos + META_INF_LENGTH, nlen - META_INF_LENGTH); - if (version > 0) { - if (metaVersionsSet == null) - metaVersionsSet = new TreeSet<>(); - metaVersionsSet.add(version); + // If this is a versioned entry, parse the version + // and store it for later. This optimizes lookup + // performance in multi-release jar files + int version = getMetaVersion(cen, + entryPos + META_INF_LENGTH, nlen - META_INF_LENGTH); + if (version > 0) { + if (metaVersionsSet == null) + metaVersionsSet = new TreeSet<>(); + metaVersionsSet.add(version); + } } } // skip ext and comment @@ -1497,10 +1532,11 @@ i++; } total = i; - if (metanamesList != null) { - metanames = new int[metanamesList.size()]; - for (int j = 0, len = metanames.length; j < len; j++) { - metanames[j] = metanamesList.get(j); + if (signatureNames != null) { + int len = signatureNames.size(); + signatureMetaNames = new int[len]; + for (int j = 0; j < len; j++) { + signatureMetaNames[j] = signatureNames.get(j); } } if (metaVersionsSet != null) { @@ -1595,6 +1631,45 @@ } /* + * Check if the bytes represents a name equals to MANIFEST.MF + */ + private static boolean isManifestName(byte[] name, int off, int len) { + return (len == 11 // "MANIFEST.MF".length() + && (name[off++] | 0x20) == 'm' + && (name[off++] | 0x20) == 'a' + && (name[off++] | 0x20) == 'n' + && (name[off++] | 0x20) == 'i' + && (name[off++] | 0x20) == 'f' + && (name[off++] | 0x20) == 'e' + && (name[off++] | 0x20) == 's' + && (name[off++] | 0x20) == 't' + && (name[off++] | 0x20) == '.' + && (name[off++] | 0x20) == 'm' + && (name[off++] | 0x20) == 'f'); + } + + private boolean isSignatureRelated(byte[] cen, int start, int len) { + // Check if entry ends with .EC and .SF + if (cen[start + len - 3] == '.') { + int b1 = cen[start + len - 2] | 0x20; + int b2 = cen[start + len - 1] | 0x20; + if ((b1 == 'e' && b2 == 'c') || (b1 == 's' && b2 == 'f')) { + return true; + } + } + // Check if entry ends with .DSA and .RSA + if (cen[start + len - 4] == '.') { + int b1 = cen[start + len - 3] | 0x20; + int b2 = cen[start + len - 2] | 0x20; + int b3 = cen[start + len - 1] | 0x20; + if ((b1 == 'r' || b1 == 'd') && b2 == 's' && b3 == 'a') { + return true; + } + } + return false; + } + + /* * If the bytes represents a non-directory name beginning * with "versions/", continuing with a positive integer, * followed by a '/', then return that integer value. diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java @@ -26,6 +26,7 @@ package jdk.internal.access; import java.util.Enumeration; +import java.util.List; import java.util.function.Function; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -34,7 +35,8 @@ public interface JavaUtilZipFileAccess { public boolean startsWithLocHeader(ZipFile zip); - public String[] getMetaInfEntryNames(JarFile zip); + public List getManifestAndSignatureRelatedFiles(JarFile zip); + public String getManifestName(JarFile zip, boolean onlyIfSignatureRelatedFiles); public int[] getMetaInfVersions(JarFile zip); public JarEntry getEntry(ZipFile zip, String name, Function func); public Enumeration entries(ZipFile zip, Function func);