1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package java.util.jar; 19 20 import dalvik.system.CloseGuard; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.RandomAccessFile; 24 import java.security.cert.Certificate; 25 import java.util.HashMap; 26 import java.util.Iterator; 27 import java.util.zip.Inflater; 28 import java.util.zip.ZipEntry; 29 import java.util.zip.ZipFile; 30 import libcore.io.IoUtils; 31 import libcore.io.Streams; 32 33 /** 34 * A subset of the JarFile API implemented as a thin wrapper over 35 * system/core/libziparchive. 36 * 37 * @hide for internal use only. Not API compatible (or as forgiving) as 38 * {@link java.util.jar.JarFile} 39 */ 40 public final class StrictJarFile { 41 42 private final long nativeHandle; 43 44 // NOTE: It's possible to share a file descriptor with the native 45 // code, at the cost of some additional complexity. 46 private final RandomAccessFile raf; 47 48 private final Manifest manifest; 49 private final JarVerifier verifier; 50 51 private final boolean isSigned; 52 53 private final CloseGuard guard = CloseGuard.get(); 54 private boolean closed; 55 56 public StrictJarFile(String fileName) throws IOException { 57 this.nativeHandle = nativeOpenJarFile(fileName); 58 this.raf = new RandomAccessFile(fileName, "r"); 59 60 try { 61 // Read the MANIFEST and signature files up front and try to 62 // parse them. We never want to accept a JAR File with broken signatures 63 // or manifests, so it's best to throw as early as possible. 64 HashMap<String, byte[]> metaEntries = getMetaEntries(); 65 this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true); 66 this.verifier = new JarVerifier(fileName, manifest, metaEntries); 67 68 isSigned = verifier.readCertificates() && verifier.isSignedJar(); 69 } catch (IOException ioe) { 70 nativeClose(this.nativeHandle); 71 throw ioe; 72 } 73 74 guard.open("close"); 75 } 76 77 public Manifest getManifest() { 78 return manifest; 79 } 80 81 public Iterator<ZipEntry> iterator() throws IOException { 82 return new EntryIterator(nativeHandle, ""); 83 } 84 85 public ZipEntry findEntry(String name) { 86 return nativeFindEntry(nativeHandle, name); 87 } 88 89 /** 90 * Return all certificate chains for a given {@link ZipEntry} belonging to this jar. 91 * This method MUST be called only after fully exhausting the InputStream belonging 92 * to this entry. 93 * 94 * Returns {@code null} if this jar file isn't signed or if this method is 95 * called before the stream is processed. 96 */ 97 public Certificate[][] getCertificateChains(ZipEntry ze) { 98 if (isSigned) { 99 return verifier.getCertificateChains(ze.getName()); 100 } 101 102 return null; 103 } 104 105 /** 106 * Return all certificates for a given {@link ZipEntry} belonging to this jar. 107 * This method MUST be called only after fully exhausting the InputStream belonging 108 * to this entry. 109 * 110 * Returns {@code null} if this jar file isn't signed or if this method is 111 * called before the stream is processed. 112 * 113 * @deprecated Switch callers to use getCertificateChains instead 114 */ 115 @Deprecated 116 public Certificate[] getCertificates(ZipEntry ze) { 117 if (isSigned) { 118 Certificate[][] certChains = verifier.getCertificateChains(ze.getName()); 119 120 // Measure number of certs. 121 int count = 0; 122 for (Certificate[] chain : certChains) { 123 count += chain.length; 124 } 125 126 // Create new array and copy all the certs into it. 127 Certificate[] certs = new Certificate[count]; 128 int i = 0; 129 for (Certificate[] chain : certChains) { 130 System.arraycopy(chain, 0, certs, i, chain.length); 131 i += chain.length; 132 } 133 134 return certs; 135 } 136 137 return null; 138 } 139 140 public InputStream getInputStream(ZipEntry ze) { 141 final InputStream is = getZipInputStream(ze); 142 143 if (isSigned) { 144 JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName()); 145 if (entry == null) { 146 return is; 147 } 148 149 return new JarFile.JarFileInputStream(is, ze.getSize(), entry); 150 } 151 152 return is; 153 } 154 155 public void close() throws IOException { 156 if (!closed) { 157 guard.close(); 158 159 nativeClose(nativeHandle); 160 IoUtils.closeQuietly(raf); 161 closed = true; 162 } 163 } 164 165 private InputStream getZipInputStream(ZipEntry ze) { 166 if (ze.getMethod() == ZipEntry.STORED) { 167 return new ZipFile.RAFStream(raf, ze.getDataOffset(), 168 ze.getDataOffset() + ze.getSize()); 169 } else { 170 final ZipFile.RAFStream wrapped = new ZipFile.RAFStream( 171 raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize()); 172 173 int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L)); 174 return new ZipFile.ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze); 175 } 176 } 177 178 static final class EntryIterator implements Iterator<ZipEntry> { 179 private final long iterationHandle; 180 private ZipEntry nextEntry; 181 182 EntryIterator(long nativeHandle, String prefix) throws IOException { 183 iterationHandle = nativeStartIteration(nativeHandle, prefix); 184 } 185 186 public ZipEntry next() { 187 if (nextEntry != null) { 188 final ZipEntry ze = nextEntry; 189 nextEntry = null; 190 return ze; 191 } 192 193 return nativeNextEntry(iterationHandle); 194 } 195 196 public boolean hasNext() { 197 if (nextEntry != null) { 198 return true; 199 } 200 201 final ZipEntry ze = nativeNextEntry(iterationHandle); 202 if (ze == null) { 203 return false; 204 } 205 206 nextEntry = ze; 207 return true; 208 } 209 210 public void remove() { 211 throw new UnsupportedOperationException(); 212 } 213 } 214 215 private HashMap<String, byte[]> getMetaEntries() throws IOException { 216 HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>(); 217 218 Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/"); 219 while (entryIterator.hasNext()) { 220 final ZipEntry entry = entryIterator.next(); 221 metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry))); 222 } 223 224 return metaEntries; 225 } 226 227 private static native long nativeOpenJarFile(String fileName) throws IOException; 228 private static native long nativeStartIteration(long nativeHandle, String prefix); 229 private static native ZipEntry nativeNextEntry(long iterationHandle); 230 private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName); 231 private static native void nativeClose(long nativeHandle); 232 } 233