diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java index 958d9bcf3..3b914cb91 100644 --- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java +++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java @@ -36,6 +36,7 @@ import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Vector; @@ -1124,4 +1125,45 @@ protected Object engineGetParameter(String param) throws InvalidParameterExcepti } } } + + /** + * java.util.jar.JarFile#stream() + */ + public void test_stream() throws Exception { + /* + * Note only (and all of) the following should be contained in the file + * META-INF/ META-INF/MANIFEST.MF Blah.txt foo/ foo/bar/ foo/bar/A.class + */ + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + + final List names = new ArrayList<>(); + jarFile.stream().forEach((ZipEntry entry) -> names.add(entry.getName())); + assertEquals(Arrays.asList("META-INF/", "META-INF/MANIFEST.MF", "Blah.txt", "foo/", "foo/bar/", + "foo/bar/A.class"), names); + jarFile.close(); + } + + + /** + * hyts_metainf.jar contains an additional entry in META-INF (META-INF/bad_checksum.txt), + * that has been altered since jar signing - we expect to detect a mismatching digest. + */ + public void test_metainf_verification() throws Exception { + String jarFilename = "hyts_metainf.jar"; + Support_Resources.copyFile(resources, null, jarFilename); + try (JarFile jarFile = new JarFile(new File(resources, jarFilename))) { + + JarEntry jre = new JarEntry("META-INF/bad_checksum.txt"); + InputStream in = jarFile.getInputStream(jre); + + byte[] buffer = new byte[1024]; + try { + while (in.available() > 0) { + in.read(buffer); + } + fail("SecurityException expected"); + } catch (SecurityException expected) {} + } + } } diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarInputStreamTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarInputStreamTest.java index 9d4224ae5..bd66bbe4f 100644 --- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarInputStreamTest.java +++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarInputStreamTest.java @@ -403,4 +403,35 @@ public void test_getNextEntry() throws Exception { // expected } } + + /** + * hyts_metainf.jar contains an additional entry in META-INF (META-INF/bad_checksum.txt), + * that has been altered since jar signing - we expect to detect a mismatching digest. + */ + public void test_metainf_verification() throws Exception { + String jarFilename = "hyts_metainf.jar"; + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, jarFilename); + InputStream is = Support_Resources.getStream(jarFilename); + + try (JarInputStream jis = new JarInputStream(is, true)) { + JarEntry je = jis.getNextJarEntry(); + je = jis.getNextJarEntry(); + je = jis.getNextJarEntry(); + je = jis.getNextJarEntry(); + + if (!je.getName().equals("META-INF/bad_checksum.txt")) { + fail("Expected META-INF/bad_checksum.txt as a 4th entry, got:" + je.getName()); + } + byte[] buffer = new byte[1024]; + int length = 0; + try { + while (length >= 0) { + length = jis.read(buffer); + } + fail("SecurityException expected"); + } catch (SecurityException expected) {} + } + } + } diff --git a/ojluni/src/main/java/java/util/jar/Attributes.java b/ojluni/src/main/java/java/util/jar/Attributes.java index 04b6ff7f5..9193542d7 100644 --- a/ojluni/src/main/java/java/util/jar/Attributes.java +++ b/ojluni/src/main/java/java/util/jar/Attributes.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2014 The Android Open Source Project - * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, 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 @@ -72,7 +72,7 @@ public Attributes() { * @param size the initial number of attributes */ public Attributes(int size) { - map = new HashMap(size); + map = new HashMap<>(size); } /** @@ -82,7 +82,7 @@ public Attributes(int size) { * @param attr the specified Attributes */ public Attributes(Attributes attr) { - map = new HashMap(attr); + map = new HashMap<>(attr); } @@ -297,9 +297,9 @@ public Object clone() { * XXX Need to handle UTF8 values and break up lines longer than 72 bytes */ void write(DataOutputStream os) throws IOException { - Iterator it = entrySet().iterator(); + Iterator> it = entrySet().iterator(); while (it.hasNext()) { - Map.Entry e = (Map.Entry)it.next(); + Map.Entry e = it.next(); StringBuffer buffer = new StringBuffer( ((Name)e.getKey()).toString()); buffer.append(": "); @@ -341,9 +341,9 @@ void writeMain(DataOutputStream out) throws IOException // write out all attributes except for the version // we wrote out earlier - Iterator it = entrySet().iterator(); + Iterator> it = entrySet().iterator(); while (it.hasNext()) { - Map.Entry e = (Map.Entry)it.next(); + Map.Entry e = it.next(); String name = ((Name)e.getKey()).toString(); if ((version != null) && ! (name.equalsIgnoreCase(vername))) { @@ -500,7 +500,7 @@ private static boolean isDigit(char c) { */ public boolean equals(Object o) { if (o instanceof Name) { - Comparator c = ASCIICaseInsensitiveComparator.CASE_INSENSITIVE_ORDER; + Comparator c = ASCIICaseInsensitiveComparator.CASE_INSENSITIVE_ORDER; return c.compare(name, ((Name)o).name) == 0; } else { return false; @@ -551,8 +551,8 @@ public String toString() { * Name object for Class-Path * manifest attribute. Bundled extensions can use this attribute * to find other JAR files containing needed classes. - * @see - * Extensions Specification + * @see + * JAR file specification */ public static final Name CLASS_PATH = new Name("Class-Path"); @@ -568,8 +568,8 @@ public String toString() { /** * Name object for Sealed manifest attribute * used for sealing. - * @see - * Extension Sealing + * @see + * Package Sealing */ public static final Name SEALED = new Name("Sealed"); @@ -592,9 +592,12 @@ public String toString() { /** * Name object for Extension-Name manifest attribute * used for declaring dependencies on installed extensions. + * @deprecated Extension mechanism will be removed in a future release. + * Use class path instead. * @see * Installed extension dependency */ + @Deprecated public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation"); /** @@ -624,17 +627,23 @@ public String toString() { /** * Name object for Implementation-Vendor-Id * manifest attribute used for package versioning. - * @see - * Java Product Versioning Specification + * @deprecated Extension mechanism will be removed in a future release. + * Use class path instead. + * @see + * Optional Package Versioning */ + @Deprecated public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id"); /** - * Name object for Implementation-Vendor-URL + * Name object for Implementation-URL * manifest attribute used for package versioning. - * @see - * Java Product Versioning Specification + * @deprecated Extension mechanism will be removed in a future release. + * Use class path instead. + * @see + * Optional Package Versioning */ + @Deprecated public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL"); /** diff --git a/ojluni/src/main/java/java/util/jar/JarEntry.java b/ojluni/src/main/java/java/util/jar/JarEntry.java index 0b4944617..b0e6841bf 100644 --- a/ojluni/src/main/java/java/util/jar/JarEntry.java +++ b/ojluni/src/main/java/java/util/jar/JarEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, 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 @@ -81,6 +81,7 @@ public JarEntry(JarEntry je) { * * @return the Manifest Attributes for this * entry, or null if none + * @throws IOException if an I/O error has occurred */ public Attributes getAttributes() throws IOException { return attr; diff --git a/ojluni/src/main/java/java/util/jar/JarFile.java b/ojluni/src/main/java/java/util/jar/JarFile.java index 68a6d8594..cf8acd83c 100644 --- a/ojluni/src/main/java/java/util/jar/JarFile.java +++ b/ojluni/src/main/java/java/util/jar/JarFile.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2014 The Android Open Source Project - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, 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 @@ -29,6 +29,8 @@ import java.io.*; import java.lang.ref.SoftReference; import java.util.*; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import java.util.zip.*; import java.security.CodeSigner; import java.security.cert.Certificate; @@ -36,6 +38,7 @@ import sun.misc.IOUtils; import sun.security.action.GetPropertyAction; import sun.security.util.ManifestEntryVerifier; +import sun.security.util.SignatureFileVerifier; /** * The JarFile class is used to read the contents of a jar file @@ -49,6 +52,13 @@ * or method in this class will cause a {@link NullPointerException} to be * thrown. * + * If the verify flag is on when opening a signed jar file, the content of the + * file is verified against its signature embedded inside the file. Please note + * that the verification process does not include validating the signer's + * certificate. A caller should inspect the return value of + * {@link JarEntry#getCodeSigners()} to further determine if the signature + * can be trusted. + * * @author David Connelly * @see Manifest * @see java.util.zip.ZipFile @@ -63,8 +73,11 @@ class JarFile extends ZipFile { private JarVerifier jv; private boolean jvInitialized; private boolean verify; - private boolean computedHasClassPathAttribute; + + // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true) private boolean hasClassPathAttribute; + // true if manifest checked for special attributes + private volatile boolean hasCheckedSpecialAttributes; /** * The JAR manifest file name. @@ -155,6 +168,7 @@ public JarFile(File file, boolean verify, int mode) throws IOException { * * @throws IllegalStateException * may be thrown if the jar file has been closed + * @throws IOException if an I/O error has occurred */ public Manifest getManifest() throws IOException { return getManifestFromReference(); @@ -221,20 +235,42 @@ public ZipEntry getEntry(String name) { return null; } + private class JarEntryIterator implements Enumeration, + Iterator + { + final Enumeration e = JarFile.super.entries(); + + public boolean hasNext() { + return e.hasMoreElements(); + } + + public JarEntry next() { + ZipEntry ze = e.nextElement(); + return new JarFileEntry(ze); + } + + public boolean hasMoreElements() { + return hasNext(); + } + + public JarEntry nextElement() { + return next(); + } + } + /** * Returns an enumeration of the zip file entries. */ public Enumeration entries() { - final Enumeration enum_ = super.entries(); - return new Enumeration() { - public boolean hasMoreElements() { - return enum_.hasMoreElements(); - } - public JarFileEntry nextElement() { - ZipEntry ze = (ZipEntry)enum_.nextElement(); - return new JarFileEntry(ze); - } - }; + return new JarEntryIterator(); + } + + @Override + public Stream stream() { + return StreamSupport.stream(Spliterators.spliterator( + new JarEntryIterator(), size(), + Spliterator.ORDERED | Spliterator.DISTINCT | + Spliterator.IMMUTABLE | Spliterator.NONNULL), false); } private class JarFileEntry extends JarEntry { @@ -320,11 +356,13 @@ private void initializeVerifier() { String[] names = getMetaInfEntryNames(); if (names != null) { for (int i = 0; i < names.length; i++) { - JarEntry e = getJarEntry(names[i]); - if (e == null) { - throw new JarException("corrupted jar file"); - } - if (!e.isDirectory()) { + String uname = names[i].toUpperCase(Locale.ENGLISH); + if (MANIFEST_NAME.equals(uname) + || SignatureFileVerifier.isBlockOrSF(uname)) { + JarEntry e = getJarEntry(names[i]); + if (e == null) { + throw new JarException("corrupted jar file"); + } if (mev == null) { mev = new ManifestEntryVerifier (getManifestFromReference()); @@ -418,27 +456,27 @@ public synchronized InputStream getInputStream(ZipEntry ze) jv); } - // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute() + // Statics for hand-coded Boyer-Moore search + private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'}; // The bad character shift for "class-path" - private static int[] lastOcc; + private static final int[] CLASSPATH_LASTOCC; // The good suffix shift for "class-path" - private static int[] optoSft; - // Initialize the shift arrays to search for "class-path" - private static char[] src = {'c','l','a','s','s','-','p','a','t','h'}; + private static final int[] CLASSPATH_OPTOSFT; + static { - lastOcc = new int[128]; - optoSft = new int[10]; - lastOcc[(int)'c']=1; - lastOcc[(int)'l']=2; - lastOcc[(int)'s']=5; - lastOcc[(int)'-']=6; - lastOcc[(int)'p']=7; - lastOcc[(int)'a']=8; - lastOcc[(int)'t']=9; - lastOcc[(int)'h']=10; + CLASSPATH_LASTOCC = new int[128]; + CLASSPATH_OPTOSFT = new int[10]; + CLASSPATH_LASTOCC[(int)'c'] = 1; + CLASSPATH_LASTOCC[(int)'l'] = 2; + CLASSPATH_LASTOCC[(int)'s'] = 5; + CLASSPATH_LASTOCC[(int)'-'] = 6; + CLASSPATH_LASTOCC[(int)'p'] = 7; + CLASSPATH_LASTOCC[(int)'a'] = 8; + CLASSPATH_LASTOCC[(int)'t'] = 9; + CLASSPATH_LASTOCC[(int)'h'] = 10; for (int i=0; i<9; i++) - optoSft[i]=10; - optoSft[9]=1; + CLASSPATH_OPTOSFT[i] = 10; + CLASSPATH_OPTOSFT[9]=1; } private synchronized JarEntry getManEntry() { @@ -463,47 +501,64 @@ private synchronized JarEntry getManEntry() { return manEntry; } - // Returns true iff this jar file has a manifest with a class path - // attribute. Returns false if there is no manifest or the manifest - // does not contain a "Class-Path" attribute. Currently exported to - // core libraries via sun.misc.SharedSecrets. /** + * Returns {@code true} iff this JAR file has a manifest with the + * Class-Path attribute * @hide */ public boolean hasClassPathAttribute() throws IOException { - if (computedHasClassPathAttribute) { - return hasClassPathAttribute; + checkForSpecialAttributes(); + return hasClassPathAttribute; + } + + /** + * Returns true if the pattern {@code src} is found in {@code b}. + * The {@code lastOcc} and {@code optoSft} arrays are the precomputed + * bad character and good suffix shifts. + */ + private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) { + int len = src.length; + int last = b.length - len; + int i = 0; + next: + while (i<=last) { + for (int j=(len-1); j>=0; j--) { + char c = (char) b[i+j]; + c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; + if (c != src[j]) { + i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); + continue next; + } + } + return true; } + return false; + } - hasClassPathAttribute = false; - if (!isKnownToNotHaveClassPathAttribute()) { + /** + * On first invocation, check if the JAR file has the Class-Path + * attribute. A no-op on subsequent calls. + */ + private void checkForSpecialAttributes() throws IOException { + if (hasCheckedSpecialAttributes) return; + // Android-changed: Doesn't make sense on android: + //if (!isKnownNotToHaveSpecialAttributes()) { + { JarEntry manEntry = getManEntry(); if (manEntry != null) { byte[] b = getBytes(manEntry); - int last = b.length - src.length; - int i = 0; - next: - while (i<=last) { - for (int j=9; j>=0; j--) { - char c = (char) b[i+j]; - c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; - if (c != src[j]) { - i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); - continue next; - } - } + if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT)) hasClassPathAttribute = true; - break; - } } } - computedHasClassPathAttribute = true; - return hasClassPathAttribute; + hasCheckedSpecialAttributes = true; } - private static String javaHome; - private static String[] jarNames; - private boolean isKnownToNotHaveClassPathAttribute() { + + // Android-changed: Doesn't make sense on android: + /*private static String javaHome; + private static volatile String[] jarNames; + private boolean isKnownNotToHaveSpecialAttributes() { // Optimize away even scanning of manifest for jar files we // deliver which don't have a class-path attribute. If one of // these jars is changed to include such an attribute this code @@ -513,19 +568,20 @@ private boolean isKnownToNotHaveClassPathAttribute() { new GetPropertyAction("java.home")); } if (jarNames == null) { - String[] names = new String[10]; + String[] names = new String[11]; String fileSep = File.separator; int i = 0; names[i++] = fileSep + "rt.jar"; - names[i++] = fileSep + "sunrsasign.jar"; names[i++] = fileSep + "jsse.jar"; names[i++] = fileSep + "jce.jar"; names[i++] = fileSep + "charsets.jar"; names[i++] = fileSep + "dnsns.jar"; - names[i++] = fileSep + "ldapsec.jar"; + names[i++] = fileSep + "zipfs.jar"; names[i++] = fileSep + "localedata.jar"; + names[i++] = fileSep = "cldrdata.jar"; names[i++] = fileSep + "sunjce_provider.jar"; names[i++] = fileSep + "sunpkcs11.jar"; + names[i++] = fileSep + "sunec.jar"; jarNames = names; } @@ -540,7 +596,7 @@ private boolean isKnownToNotHaveClassPathAttribute() { } } return false; - } + }*/ JarEntry newEntry(ZipEntry ze) { return new JarFileEntry(ze); diff --git a/ojluni/src/main/java/java/util/jar/JarOutputStream.java b/ojluni/src/main/java/java/util/jar/JarOutputStream.java index 843b16aa8..369427726 100644 --- a/ojluni/src/main/java/java/util/jar/JarOutputStream.java +++ b/ojluni/src/main/java/java/util/jar/JarOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2012, 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 @@ -135,7 +135,7 @@ private static boolean hasMagic(byte[] edata) { * The bytes are assumed to be in Intel (little-endian) byte order. */ private static int get16(byte[] b, int off) { - return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8); + return Byte.toUnsignedInt(b[off]) | ( Byte.toUnsignedInt(b[off+1]) << 8); } /* diff --git a/ojluni/src/main/java/java/util/jar/JarVerifier.java b/ojluni/src/main/java/java/util/jar/JarVerifier.java index c95df113a..a86880207 100644 --- a/ojluni/src/main/java/java/util/jar/JarVerifier.java +++ b/ojluni/src/main/java/java/util/jar/JarVerifier.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2014 The Android Open Source Project - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, 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 @@ -33,6 +33,7 @@ import java.security.cert.CertificateException; import java.util.zip.ZipEntry; +import sun.misc.JarIndex; import sun.security.util.ManifestDigester; import sun.security.util.ManifestEntryVerifier; import sun.security.util.SignatureFileVerifier; @@ -49,21 +50,21 @@ class JarVerifier { /* a table mapping names to code signers, for jar entries that have had their actual hashes verified */ - private Hashtable verifiedSigners; + private Hashtable verifiedSigners; /* a table mapping names to code signers, for jar entries that have passed the .SF/.DSA/.EC -> MANIFEST check */ - private Hashtable sigFileSigners; + private Hashtable sigFileSigners; /* a hash table to hold .SF bytes */ - private Hashtable sigFileData; + private Hashtable sigFileData; /** "queue" of pending PKCS7 blocks that we couldn't parse * until we parsed the .SF file */ - private ArrayList pendingBlocks; + private ArrayList pendingBlocks; /* cache of CodeSigner objects */ - private ArrayList signerCache; + private ArrayList signerCache; /* Are we parsing a block? */ private boolean parsingBlockOrSF = false; @@ -91,16 +92,16 @@ class JarVerifier { private Object csdomain = new Object(); /** collect -DIGEST-MANIFEST values for blacklist */ - private List manifestDigests; + private List manifestDigests; public JarVerifier(byte rawBytes[]) { manifestRawBytes = rawBytes; - sigFileSigners = new Hashtable(); - verifiedSigners = new Hashtable(); - sigFileData = new Hashtable(11); - pendingBlocks = new ArrayList(); + sigFileSigners = new Hashtable<>(); + verifiedSigners = new Hashtable<>(); + sigFileData = new Hashtable<>(11); + pendingBlocks = new ArrayList<>(); baos = new ByteArrayOutputStream(); - manifestDigests = new ArrayList(); + manifestDigests = new ArrayList<>(); } /** @@ -140,13 +141,22 @@ public void beginEntry(JarEntry je, ManifestEntryVerifier mev) return; } + if (uname.equals(JarFile.MANIFEST_NAME) || + uname.equals(JarIndex.INDEX_NAME)) { + return; + } + if (SignatureFileVerifier.isBlockOrSF(uname)) { /* We parse only DSA, RSA or EC PKCS7 blocks. */ parsingBlockOrSF = true; baos.reset(); mev.setEntry(null, je); + return; } - return; + + // If a META-INF entry is not MF or block or SF, they should + // be normal entries. According to 2 above, no more block or + // SF will appear. Let's doneWithMeta. } } @@ -170,7 +180,9 @@ public void beginEntry(JarEntry je, ManifestEntryVerifier mev) name = name.substring(1); // only set the jev object for entries that have a signature - if (sigFileSigners.get(name) != null) { + // (either verified or not) + if (sigFileSigners.get(name) != null || + verifiedSigners.get(name) != null) { mev.setEntry(name, je); return; } @@ -249,10 +261,9 @@ private void processEntry(ManifestEntryVerifier mev) sigFileData.put(key, bytes); // check pending blocks, we can now process // anyone waiting for this .SF file - Iterator it = pendingBlocks.iterator(); + Iterator it = pendingBlocks.iterator(); while (it.hasNext()) { - SignatureFileVerifier sfv = - (SignatureFileVerifier) it.next(); + SignatureFileVerifier sfv = it.next(); if (sfv.needSignatureFile(key)) { if (debug != null) { debug.println( @@ -271,7 +282,7 @@ private void processEntry(ManifestEntryVerifier mev) String key = uname.substring(0, uname.lastIndexOf(".")); if (signerCache == null) - signerCache = new ArrayList(); + signerCache = new ArrayList<>(); if (manDig == null) { synchronized(manifestRawBytes) { @@ -288,7 +299,7 @@ private void processEntry(ManifestEntryVerifier mev) if (sfv.needSignatureFileBytes()) { // see if we have already parsed an external .SF file - byte[] bytes = (byte[]) sigFileData.get(key); + byte[] bytes = sigFileData.get(key); if (bytes == null) { // put this block on queue for later processing @@ -327,7 +338,7 @@ private void processEntry(ManifestEntryVerifier mev) * the given file in the jar. * @deprecated Deprecated. */ - @Deprecated // Android-changed added "Deprecated." + @Deprecated public java.security.cert.Certificate[] getCerts(String name) { return mapSignersToCertArray(getCodeSigners(name)); @@ -345,7 +356,7 @@ public java.security.cert.Certificate[] getCerts(JarFile jar, JarEntry entry) */ public CodeSigner[] getCodeSigners(String name) { - return (CodeSigner[])verifiedSigners.get(name); + return verifiedSigners.get(name); } public CodeSigner[] getCodeSigners(JarFile jar, JarEntry entry) @@ -378,15 +389,14 @@ private static java.security.cert.Certificate[] mapSignersToCertArray( CodeSigner[] signers) { if (signers != null) { - ArrayList certChains = new ArrayList(); + ArrayList certChains = new ArrayList<>(); for (int i = 0; i < signers.length; i++) { certChains.addAll( signers[i].getSignerCertPath().getCertificates()); } // Convert into a Certificate[] - return (java.security.cert.Certificate[]) - certChains.toArray( + return certChains.toArray( new java.security.cert.Certificate[certChains.size()]); } return null; @@ -420,8 +430,8 @@ void doneWithMeta() // MANIFEST.MF is always treated as signed and verified, // move its signers from sigFileSigners to verifiedSigners. if (sigFileSigners.containsKey(JarFile.MANIFEST_NAME)) { - verifiedSigners.put(JarFile.MANIFEST_NAME, - sigFileSigners.remove(JarFile.MANIFEST_NAME)); + CodeSigner[] codeSigners = sigFileSigners.remove(JarFile.MANIFEST_NAME); + verifiedSigners.put(JarFile.MANIFEST_NAME, codeSigners); } } @@ -515,10 +525,10 @@ public int available() throws IOException { // Extended JavaUtilJarAccess CodeSource API Support - private Map urlToCodeSourceMap = new HashMap(); - private Map signerToCodeSource = new HashMap(); + private Map> urlToCodeSourceMap = new HashMap<>(); + private Map signerToCodeSource = new HashMap<>(); private URL lastURL; - private Map lastURLMap; + private Map lastURLMap; /* * Create a unique mapping from codeSigner cache entries to CodeSource. @@ -526,19 +536,19 @@ public int available() throws IOException { * 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; + Map map; if (url == lastURL) { map = lastURLMap; } else { - map = (Map) urlToCodeSourceMap.get(url); + map = urlToCodeSourceMap.get(url); if (map == null) { - map = new HashMap(); + map = new HashMap<>(); urlToCodeSourceMap.put(url, map); } lastURLMap = map; lastURL = url; } - CodeSource cs = (CodeSource) map.get(signers); + CodeSource cs = map.get(signers); if (cs == null) { cs = new VerifierCodeSource(csdomain, url, signers); signerToCodeSource.put(signers, cs); @@ -546,16 +556,16 @@ private synchronized CodeSource mapSignersToCodeSource(URL url, CodeSigner[] sig return cs; } - private CodeSource[] mapSignersToCodeSources(URL url, List signers, boolean unsigned) { - List sources = new ArrayList(); + 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))); + sources.add(mapSignersToCodeSource(url, signers.get(i))); } if (unsigned) { sources.add(mapSignersToCodeSource(url, null)); } - return (CodeSource[]) sources.toArray(new CodeSource[sources.size()]); + return sources.toArray(new CodeSource[sources.size()]); } private CodeSigner[] emptySigner = new CodeSigner[0]; @@ -575,7 +585,7 @@ private CodeSigner[] findMatchingSigners(CodeSource cs) { * but this handles a CodeSource of any type, just in case. */ CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true); - List sourceList = new ArrayList(); + List sourceList = new ArrayList<>(); for (int i = 0; i < sources.length; i++) { sourceList.add(sources[i]); } @@ -596,6 +606,7 @@ private CodeSigner[] findMatchingSigners(CodeSource cs) { * signing data that can be compared by object reference identity. */ private static class VerifierCodeSource extends CodeSource { + private static final long serialVersionUID = -9047366145967768825L; URL vlocation; CodeSigner[] vsigners; @@ -663,16 +674,16 @@ private java.security.cert.Certificate[] getPrivateCertificates() { return vcerts; } } - private Map signerMap; + private Map signerMap; - private synchronized 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 = new HashMap<>(verifiedSigners.size() + sigFileSigners.size()); signerMap.putAll(verifiedSigners); signerMap.putAll(sigFileSigners); } @@ -680,15 +691,15 @@ private synchronized Map signerMap() { } public synchronized Enumeration entryNames(JarFile jar, final CodeSource[] cs) { - final Map map = signerMap(); - final Iterator itor = map.entrySet().iterator(); + 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); + List req = new ArrayList<>(cs.length); for (int i = 0; i < cs.length; i++) { CodeSigner[] match = findMatchingSigners(cs[i]); if (match != null) { @@ -697,11 +708,13 @@ public synchronized Enumeration entryNames(JarFile jar, final CodeSource } else { matchUnsigned = true; } + } else { + matchUnsigned = true; } } - final List signersReq = req; - final Enumeration enum2 = (matchUnsigned) ? unsignedEntryNames(jar) : emptyEnumeration; + final List signersReq = req; + final Enumeration enum2 = (matchUnsigned) ? unsignedEntryNames(jar) : emptyEnumeration; return new Enumeration() { @@ -713,14 +726,14 @@ public boolean hasMoreElements() { } while (itor.hasNext()) { - Map.Entry e = (Map.Entry) itor.next(); - if (signersReq.contains((CodeSigner[]) e.getValue())) { - name = (String) e.getKey(); + Map.Entry e = itor.next(); + if (signersReq.contains(e.getValue())) { + name = e.getKey(); return true; } } while (enum2.hasMoreElements()) { - name = (String) enum2.nextElement(); + name = enum2.nextElement(); return true; } return false; @@ -741,13 +754,13 @@ public String nextElement() { * Like entries() but screens out internal JAR mechanism entries * and includes signed entries with no ZIP data. */ - public Enumeration entries2(final JarFile jar, Enumeration e) { - final Map map = new HashMap(); + public Enumeration entries2(final JarFile jar, Enumeration e) { + final Map map = new HashMap<>(); map.putAll(signerMap()); - final Enumeration enum_ = e; + final Enumeration enum_ = e; return new Enumeration() { - Enumeration signers = null; + Enumeration signers = null; JarEntry entry; public boolean hasMoreElements() { @@ -755,7 +768,7 @@ public boolean hasMoreElements() { return true; } while (enum_.hasMoreElements()) { - ZipEntry ze = (ZipEntry) enum_.nextElement(); + ZipEntry ze = enum_.nextElement(); if (JarVerifier.isSigningRelated(ze.getName())) { continue; } @@ -766,7 +779,7 @@ public boolean hasMoreElements() { signers = Collections.enumeration(map.keySet()); } while (signers.hasMoreElements()) { - String name = (String) signers.nextElement(); + String name = signers.nextElement(); entry = jar.newEntry(new ZipEntry(name)); return true; } @@ -786,7 +799,7 @@ public JarEntry nextElement() { } }; } - private Enumeration emptyEnumeration = new Enumeration() { + private Enumeration emptyEnumeration = new Enumeration() { public boolean hasMoreElements() { return false; @@ -799,28 +812,12 @@ public String nextElement() { // 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; + return SignatureFileVerifier.isSigningRelated(name); } private Enumeration unsignedEntryNames(JarFile jar) { - final Map map = signerMap(); - final Enumeration entries = jar.entries(); + final Map map = signerMap(); + final Enumeration entries = jar.entries(); return new Enumeration() { String name; @@ -835,7 +832,7 @@ public boolean hasMoreElements() { } while (entries.hasMoreElements()) { String value; - ZipEntry e = (ZipEntry) entries.nextElement(); + ZipEntry e = entries.nextElement(); value = e.getName(); if (e.isDirectory() || isSigningRelated(value)) { continue; @@ -858,14 +855,14 @@ public String nextElement() { } }; } - private List jarCodeSigners; + private List jarCodeSigners; - private synchronized List getJarCodeSigners() { + private synchronized List getJarCodeSigners() { CodeSigner[] signers; if (jarCodeSigners == null) { - HashSet set = new HashSet(); + HashSet set = new HashSet<>(); set.addAll(signerMap().values()); - jarCodeSigners = new ArrayList(); + jarCodeSigners = new ArrayList<>(); jarCodeSigners.addAll(set); } return jarCodeSigners; @@ -880,7 +877,7 @@ public synchronized CodeSource[] getCodeSources(JarFile jar, URL url) { public CodeSource getCodeSource(URL url, String name) { CodeSigner[] signers; - signers = (CodeSigner[]) signerMap().get(name); + signers = signerMap().get(name); return mapSignersToCodeSource(url, signers); } @@ -894,7 +891,7 @@ public void setEagerValidation(boolean eager) { eagerValidation = eager; } - public synchronized List getManifestDigests() { + public synchronized List getManifestDigests() { return Collections.unmodifiableList(manifestDigests); } diff --git a/ojluni/src/main/java/java/util/jar/Manifest.java b/ojluni/src/main/java/java/util/jar/Manifest.java index 2bd4eb096..de842fc20 100644 --- a/ojluni/src/main/java/java/util/jar/Manifest.java +++ b/ojluni/src/main/java/java/util/jar/Manifest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, 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 @@ -51,7 +51,7 @@ public class Manifest implements Cloneable { private Attributes attr = new Attributes(); // manifest entries - private Map entries = new HashMap(); + private Map entries = new HashMap<>(); /** * Constructs a new, empty Manifest. @@ -63,7 +63,7 @@ public Manifest() { * Constructs a new Manifest from the specified input stream. * * @param is the input stream containing manifest data - * @throws IOException if an I/O error has occured + * @throws IOException if an I/O error has occurred */ public Manifest(InputStream is) throws IOException { read(is); @@ -148,11 +148,11 @@ public void write(OutputStream out) throws IOException { // Write out the main attributes for the manifest attr.writeMain(dos); // Now write out the pre-entry attributes - Iterator it = entries.entrySet().iterator(); + Iterator> it = entries.entrySet().iterator(); while (it.hasNext()) { - Map.Entry e = (Map.Entry)it.next(); + Map.Entry e = it.next(); StringBuffer buffer = new StringBuffer("Name: "); - String value = (String)e.getKey(); + String value = e.getKey(); if (value != null) { byte[] vb = value.getBytes("UTF8"); value = new String(vb, 0, 0, vb.length); @@ -161,7 +161,7 @@ public void write(OutputStream out) throws IOException { buffer.append("\r\n"); make72Safe(buffer); dos.writeBytes(buffer.toString()); - ((Attributes)e.getValue()).write(dos); + e.getValue().write(dos); } dos.flush(); } @@ -339,7 +339,7 @@ public int read() throws IOException { return -1; } } - return buf[pos++] & 0xff; + return Byte.toUnsignedInt(buf[pos++]); } public int read(byte[] b, int off, int len) throws IOException { diff --git a/ojluni/src/main/java/java/util/jar/Pack200.java b/ojluni/src/main/java/java/util/jar/Pack200.java index cf9759050..c58add965 100644 --- a/ojluni/src/main/java/java/util/jar/Pack200.java +++ b/ojluni/src/main/java/java/util/jar/Pack200.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003,2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2013, 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 @@ -44,8 +44,8 @@ * The unpacker engine is used by deployment applications to * transform the byte-stream back to JAR format. *

- * Here is an example using packer and unpacker:

- *

+ * Here is an example using  packer and unpacker:
+ * 
{@code
  *    import java.util.jar.Pack200;
  *    import java.util.jar.Pack200.*;
  *    ...
@@ -90,7 +90,7 @@
  *    } catch (IOException ioe) {
  *        ioe.printStackTrace();
  *    }
- * 
+ * } *

* A Pack200 file compressed with gzip can be hosted on HTTP/1.1 web servers. * The deployment applications can use "Accept-Encoding=pack200-gzip". This @@ -112,7 +112,7 @@ private Pack200() {} //prevent instantiation // Static methods of the Pack200 class. /** * Obtain new instance of a class that implements Packer. - * + *

    *
  • If the system property java.util.jar.Pack200.Packer * is defined, then the value is taken to be the fully-qualified name * of a concrete implementation class, which must implement Packer. @@ -122,6 +122,7 @@ private Pack200() {} //prevent instantiation *

  • If an implementation has not been specified with the system * property, then the system-default implementation class is instantiated, * and the result is returned.

  • + *
* *

Note: The returned object is not guaranteed to operate * correctly if multiple threads use it at the same time. @@ -137,7 +138,7 @@ public synchronized static Packer newPacker() { /** * Obtain new instance of a class that implements Unpacker. - * + *

    *
  • If the system property java.util.jar.Pack200.Unpacker * is defined, then the value is taken to be the fully-qualified * name of a concrete implementation class, which must implement Unpacker. @@ -147,6 +148,7 @@ public synchronized static Packer newPacker() { *

  • If an implementation has not been specified with the * system property, then the system-default implementation class * is instantiated, and the result is returned.

  • + *
* *

Note: The returned object is not guaranteed to operate * correctly if multiple threads use it at the same time. @@ -350,14 +352,14 @@ public interface Packer { * directory will be passed also. *

* Examples: - *


+         * 
{@code
          *     Map p = packer.properties();
          *     p.put(PASS_FILE_PFX+0, "mutants/Rogue.class");
          *     p.put(PASS_FILE_PFX+1, "mutants/Wolverine.class");
          *     p.put(PASS_FILE_PFX+2, "mutants/Storm.class");
          *     # Pass all files in an entire directory hierarchy:
          *     p.put(PASS_FILE_PFX+3, "police/");
-         * 
. + * }
*/ String PASS_FILE_PFX = "pack.pass.file."; @@ -378,12 +380,12 @@ public interface Packer { * This is the default value for this property. *

* Examples: - *


+         * 
{@code
          *     Map p = pack200.getProperties();
          *     p.put(UNKNOWN_ATTRIBUTE, ERROR);
          *     p.put(UNKNOWN_ATTRIBUTE, STRIP);
          *     p.put(UNKNOWN_ATTRIBUTE, PASS);
-         * 
+ * }
*/ String UNKNOWN_ATTRIBUTE = "pack.unknown.attribute"; @@ -456,12 +458,12 @@ public interface Packer { * The unpacker's progress as a percentage, as periodically * updated by the unpacker. * Values of 0 - 100 are normal, and -1 indicates a stall. - * Observe this property with a {@link PropertyChangeListener}. + * Progress can be monitored by polling the value of this + * property. *

* At a minimum, the unpacker must set progress to 0 * at the beginning of a packing operation, and to 100 * at the end. - * @see #addPropertyChangeListener */ String PROGRESS = "pack.progress"; @@ -574,21 +576,49 @@ public interface Packer { * Registers a listener for PropertyChange events on the properties map. * This is typically used by applications to update a progress bar. * + *

The default implementation of this method does nothing and has + * no side-effects.

+ * + *

WARNING: This method is omitted from the interface + * declaration in all subset Profiles of Java SE that do not include + * the {@code java.beans} package.

+ * @see #properties * @see #PROGRESS * @param listener An object to be invoked when a property is changed. - */ - void addPropertyChangeListener(PropertyChangeListener listener) ; + * @deprecated The dependency on {@code PropertyChangeListener} creates + * a significant impediment to future modularization of the + * Java platform. This method will be removed in a future + * release. + * Applications that need to monitor progress of the packer + * can poll the value of the {@link #PROGRESS PROGRESS} + * property instead. + */ + @Deprecated + default void addPropertyChangeListener(PropertyChangeListener listener) { + } /** * Remove a listener for PropertyChange events, added by * the {@link #addPropertyChangeListener}. * + *

The default implementation of this method does nothing and has + * no side-effects.

+ * + *

WARNING: This method is omitted from the interface + * declaration in all subset Profiles of Java SE that do not include + * the {@code java.beans} package.

+ * * @see #addPropertyChangeListener * @param listener The PropertyChange listener to be removed. + * @deprecated The dependency on {@code PropertyChangeListener} creates + * a significant impediment to future modularization of the + * Java platform. This method will be removed in a future + * release. */ - void removePropertyChangeListener(PropertyChangeListener listener); - + @Deprecated + default void removePropertyChangeListener(PropertyChangeListener listener) { + } } /** @@ -640,12 +670,12 @@ public interface Unpacker { * The unpacker's progress as a percentage, as periodically * updated by the unpacker. * Values of 0 - 100 are normal, and -1 indicates a stall. - * Observe this property with a {@link PropertyChangeListener}. + * Progress can be monitored by polling the value of this + * property. *

* At a minimum, the unpacker must set progress to 0 * at the beginning of a packing operation, and to 100 * at the end. - * @see #addPropertyChangeListener */ String PROGRESS = "unpack.progress"; @@ -705,20 +735,49 @@ public interface Unpacker { * Registers a listener for PropertyChange events on the properties map. * This is typically used by applications to update a progress bar. * + *

The default implementation of this method does nothing and has + * no side-effects.

+ * + *

WARNING: This method is omitted from the interface + * declaration in all subset Profiles of Java SE that do not include + * the {@code java.beans} package.

+ * * @see #properties * @see #PROGRESS * @param listener An object to be invoked when a property is changed. - */ - void addPropertyChangeListener(PropertyChangeListener listener) ; + * @deprecated The dependency on {@code PropertyChangeListener} creates + * a significant impediment to future modularization of the + * Java platform. This method will be removed in a future + * release. + * Applications that need to monitor progress of the + * unpacker can poll the value of the {@link #PROGRESS + * PROGRESS} property instead. + */ + @Deprecated + default void addPropertyChangeListener(PropertyChangeListener listener) { + } /** * Remove a listener for PropertyChange events, added by * the {@link #addPropertyChangeListener}. * + *

The default implementation of this method does nothing and has + * no side-effects.

+ * + *

WARNING: This method is omitted from the interface + * declaration in all subset Profiles of Java SE that do not include + * the {@code java.beans} package.

+ * * @see #addPropertyChangeListener * @param listener The PropertyChange listener to be removed. + * @deprecated The dependency on {@code PropertyChangeListener} creates + * a significant impediment to future modularization of the + * Java platform. This method will be removed in a future + * release. */ - void removePropertyChangeListener(PropertyChangeListener listener); + @Deprecated + default void removePropertyChangeListener(PropertyChangeListener listener) { + } } // Private stuff.... @@ -726,13 +785,13 @@ public interface Unpacker { private static final String PACK_PROVIDER = "java.util.jar.Pack200.Packer"; private static final String UNPACK_PROVIDER = "java.util.jar.Pack200.Unpacker"; - private static Class packerImpl; - private static Class unpackerImpl; + private static Class packerImpl; + private static Class unpackerImpl; private synchronized static Object newInstance(String prop) { String implName = "(unknown)"; try { - Class impl = (PACK_PROVIDER.equals(prop))? packerImpl: unpackerImpl; + Class impl = (PACK_PROVIDER.equals(prop))? packerImpl: unpackerImpl; if (impl == null) { // The first time, we must decide which class to use. implName = java.security.AccessController.doPrivileged( diff --git a/ojluni/src/main/java/sun/security/util/SignatureFileVerifier.java b/ojluni/src/main/java/sun/security/util/SignatureFileVerifier.java index fc379a42d..fa0f5301d 100644 --- a/ojluni/src/main/java/sun/security/util/SignatureFileVerifier.java +++ b/ojluni/src/main/java/sun/security/util/SignatureFileVerifier.java @@ -152,7 +152,6 @@ public static boolean isBlockOrSF(String s) { return false; } - /* BEGIN android-removed /** * Yet another utility method used by JarVerifier and JarSigner * to determine what files are signature related, which includes @@ -162,7 +161,7 @@ public static boolean isBlockOrSF(String s) { * * @param s file name * @return true if the input file name is signature related - * + */ public static boolean isSigningRelated(String name) { name = name.toUpperCase(Locale.ENGLISH); if (!name.startsWith("META-INF/")) { @@ -198,7 +197,6 @@ public static boolean isSigningRelated(String name) { } return false; } - * END android-removed*/ /** get digest from cache */ @@ -268,6 +266,7 @@ private void processImpl(Hashtable signers, name); } + CodeSigner[] newSigners = getSigners(infos, block); // make sure we have something to do all this work for... @@ -459,6 +458,7 @@ private boolean verifySection(Attributes sfAttr, if (digest != null) { boolean ok = false; + byte[] expected = Base64.getMimeDecoder().decode((String)se.getValue()); byte[] computed; diff --git a/support/src/test/java/tests/resources/hyts_metainf.jar b/support/src/test/java/tests/resources/hyts_metainf.jar new file mode 100644 index 000000000..ff92bed87 Binary files /dev/null and b/support/src/test/java/tests/resources/hyts_metainf.jar differ