Merge changes from topic \'aosp-merge-24-12\'
am: e0e81bf407
* commit 'e0e81bf407508ec096c752948f8794af5fd480ca':
Special case system_server to not create the JIT code cache.
Don't use IntegralToString
Track libcore commit 85d69e16fe1e59e50c1ad228e754abf325088362.
Track API changes to java.lang.ref.Reference.
Remove merge markers from preloaded-classes
Move StrictJarFile from libcore to framework
Switch RecoverySystem impl to use sun.security.pkcs
API update for OpenJdk based libcore
Use libcore/known_oj_tags for the javadoc stage.
Stop preloading fortress classes
Use HexDump instead of java.lang.IntegralToString
Add core-oj to the list of core library jars.
This commit is contained in:
@@ -423,7 +423,7 @@ LOCAL_INTERMEDIATE_SOURCES := \
|
||||
$(framework_res_source_path)/com/android/internal/R.java
|
||||
|
||||
LOCAL_NO_STANDARD_LIBRARIES := true
|
||||
LOCAL_JAVA_LIBRARIES := core-libart conscrypt okhttp core-junit bouncycastle ext
|
||||
LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp core-junit bouncycastle ext
|
||||
|
||||
LOCAL_MODULE := framework
|
||||
|
||||
@@ -710,6 +710,7 @@ framework_docs_LOCAL_INTERMEDIATE_SOURCES := \
|
||||
$(framework_res_source_path)/com/android/internal/R.java
|
||||
|
||||
framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
|
||||
core-oj \
|
||||
core-libart \
|
||||
conscrypt \
|
||||
bouncycastle \
|
||||
@@ -733,6 +734,7 @@ framework_docs_LOCAL_DROIDDOC_HTML_DIR := docs/html
|
||||
# not be referenced in the documentation.
|
||||
framework_docs_LOCAL_DROIDDOC_OPTIONS := \
|
||||
-knowntags ./frameworks/base/docs/knowntags.txt \
|
||||
-knowntags ./libcore/known_oj_tags.txt \
|
||||
-hidePackage com.android.org.conscrypt \
|
||||
-since $(SRC_API_DIR)/1.xml 1 \
|
||||
-since $(SRC_API_DIR)/2.xml 2 \
|
||||
@@ -1093,7 +1095,7 @@ include $(CLEAR_VARS)
|
||||
LOCAL_SRC_FILES := $(ext_src_files)
|
||||
|
||||
LOCAL_NO_STANDARD_LIBRARIES := true
|
||||
LOCAL_JAVA_LIBRARIES := core-libart
|
||||
LOCAL_JAVA_LIBRARIES := core-oj core-libart
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := libphonenumber-platform
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
LOCAL_MODULE := ext
|
||||
|
||||
2796
api/current.txt
2796
api/current.txt
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.content.pm;
|
||||
|
||||
import com.android.internal.util.HexDump;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -118,7 +120,7 @@ public class ManifestDigest implements Parcelable {
|
||||
final int N = mDigest.length;
|
||||
for (int i = 0; i < N; i++) {
|
||||
final byte b = mDigest[i];
|
||||
IntegralToString.appendByteAsHex(sb, b, false);
|
||||
HexDump.appendByteAsHex(sb, b, false);
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append('}');
|
||||
@@ -142,4 +144,4 @@ public class ManifestDigest implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,10 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.jar.StrictJarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import android.util.jar.StrictJarFile;
|
||||
|
||||
/**
|
||||
* Parser for package files (APKs) on disk. This supports apps packaged either
|
||||
* as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.net.http;
|
||||
|
||||
import com.android.internal.util.HexDump;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.DateFormat;
|
||||
@@ -285,7 +287,7 @@ public class SslCertificate {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
byte b = bytes[i];
|
||||
IntegralToString.appendByteAsHex(sb, b, true);
|
||||
HexDump.appendByteAsHex(sb, b, true);
|
||||
if (i+1 != bytes.length) {
|
||||
sb.append(':');
|
||||
}
|
||||
|
||||
@@ -44,11 +44,8 @@ import java.util.Locale;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.apache.harmony.security.asn1.BerInputStream;
|
||||
import org.apache.harmony.security.pkcs7.ContentInfo;
|
||||
import org.apache.harmony.security.pkcs7.SignedData;
|
||||
import org.apache.harmony.security.pkcs7.SignerInfo;
|
||||
import org.apache.harmony.security.x509.Certificate;
|
||||
import sun.security.pkcs.PKCS7;
|
||||
import sun.security.pkcs.SignerInfo;
|
||||
|
||||
/**
|
||||
* RecoverySystem contains methods for interacting with the Android
|
||||
@@ -150,14 +147,13 @@ public class RecoverySystem {
|
||||
ProgressListener listener,
|
||||
File deviceCertsZipFile)
|
||||
throws IOException, GeneralSecurityException {
|
||||
long fileLen = packageFile.length();
|
||||
final long fileLen = packageFile.length();
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
|
||||
final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
|
||||
try {
|
||||
int lastPercent = 0;
|
||||
long lastPublishTime = System.currentTimeMillis();
|
||||
final long startTimeMillis = System.currentTimeMillis();
|
||||
if (listener != null) {
|
||||
listener.onProgress(lastPercent);
|
||||
listener.onProgress(0);
|
||||
}
|
||||
|
||||
raf.seek(fileLen - 6);
|
||||
@@ -168,8 +164,8 @@ public class RecoverySystem {
|
||||
throw new SignatureException("no signature in file (no footer)");
|
||||
}
|
||||
|
||||
int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
|
||||
int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
|
||||
final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
|
||||
final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
|
||||
|
||||
byte[] eocd = new byte[commentSize + 22];
|
||||
raf.seek(fileLen - (commentSize + 22));
|
||||
@@ -189,51 +185,30 @@ public class RecoverySystem {
|
||||
}
|
||||
}
|
||||
|
||||
// The following code is largely copied from
|
||||
// JarUtils.verifySignature(). We could just *call* that
|
||||
// method here if that function didn't read the entire
|
||||
// input (ie, the whole OTA package) into memory just to
|
||||
// compute its message digest.
|
||||
// Parse the signature
|
||||
PKCS7 block =
|
||||
new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
|
||||
|
||||
BerInputStream bis = new BerInputStream(
|
||||
new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
|
||||
ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
|
||||
SignedData signedData = info.getSignedData();
|
||||
if (signedData == null) {
|
||||
throw new IOException("signedData is null");
|
||||
}
|
||||
List<Certificate> encCerts = signedData.getCertificates();
|
||||
if (encCerts.isEmpty()) {
|
||||
throw new IOException("encCerts is empty");
|
||||
}
|
||||
// Take the first certificate from the signature (packages
|
||||
// should contain only one).
|
||||
Iterator<Certificate> it = encCerts.iterator();
|
||||
X509Certificate cert = null;
|
||||
if (it.hasNext()) {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
InputStream is = new ByteArrayInputStream(it.next().getEncoded());
|
||||
cert = (X509Certificate) cf.generateCertificate(is);
|
||||
} else {
|
||||
X509Certificate[] certificates = block.getCertificates();
|
||||
if (certificates == null || certificates.length == 0) {
|
||||
throw new SignatureException("signature contains no certificates");
|
||||
}
|
||||
X509Certificate cert = certificates[0];
|
||||
PublicKey signatureKey = cert.getPublicKey();
|
||||
|
||||
List<SignerInfo> sigInfos = signedData.getSignerInfos();
|
||||
SignerInfo sigInfo;
|
||||
if (!sigInfos.isEmpty()) {
|
||||
sigInfo = (SignerInfo)sigInfos.get(0);
|
||||
} else {
|
||||
throw new IOException("no signer infos!");
|
||||
SignerInfo[] signerInfos = block.getSignerInfos();
|
||||
if (signerInfos == null || signerInfos.length == 0) {
|
||||
throw new SignatureException("signature contains no signedData");
|
||||
}
|
||||
SignerInfo signerInfo = signerInfos[0];
|
||||
|
||||
// Check that the public key of the certificate contained
|
||||
// in the package equals one of our trusted public keys.
|
||||
|
||||
boolean verified = false;
|
||||
HashSet<X509Certificate> trusted = getTrustedCerts(
|
||||
deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
|
||||
|
||||
PublicKey signatureKey = cert.getPublicKey();
|
||||
boolean verified = false;
|
||||
for (X509Certificate c : trusted) {
|
||||
if (c.getPublicKey().equals(signatureKey)) {
|
||||
verified = true;
|
||||
@@ -246,61 +221,54 @@ public class RecoverySystem {
|
||||
|
||||
// The signature cert matches a trusted key. Now verify that
|
||||
// the digest in the cert matches the actual file data.
|
||||
|
||||
// The verifier in recovery only handles SHA1withRSA and
|
||||
// SHA256withRSA signatures. SignApk chooses which to use
|
||||
// based on the signature algorithm of the cert:
|
||||
//
|
||||
// "SHA256withRSA" cert -> "SHA256withRSA" signature
|
||||
// "SHA1withRSA" cert -> "SHA1withRSA" signature
|
||||
// "MD5withRSA" cert -> "SHA1withRSA" signature (for backwards compatibility)
|
||||
// any other cert -> SignApk fails
|
||||
//
|
||||
// Here we ignore whatever the cert says, and instead use
|
||||
// whatever algorithm is used by the signature.
|
||||
|
||||
String da = sigInfo.getDigestAlgorithm();
|
||||
String dea = sigInfo.getDigestEncryptionAlgorithm();
|
||||
String alg = null;
|
||||
if (da == null || dea == null) {
|
||||
// fall back to the cert algorithm if the sig one
|
||||
// doesn't look right.
|
||||
alg = cert.getSigAlgName();
|
||||
} else {
|
||||
alg = da + "with" + dea;
|
||||
}
|
||||
Signature sig = Signature.getInstance(alg);
|
||||
sig.initVerify(cert);
|
||||
|
||||
// The signature covers all of the OTA package except the
|
||||
// archive comment and its 2-byte length.
|
||||
long toRead = fileLen - commentSize - 2;
|
||||
long soFar = 0;
|
||||
raf.seek(0);
|
||||
byte[] buffer = new byte[4096];
|
||||
boolean interrupted = false;
|
||||
while (soFar < toRead) {
|
||||
interrupted = Thread.interrupted();
|
||||
if (interrupted) break;
|
||||
int size = buffer.length;
|
||||
if (soFar + size > toRead) {
|
||||
size = (int)(toRead - soFar);
|
||||
}
|
||||
int read = raf.read(buffer, 0, size);
|
||||
sig.update(buffer, 0, read);
|
||||
soFar += read;
|
||||
final ProgressListener listenerForInner = listener;
|
||||
SignerInfo verifyResult = block.verify(signerInfo, new InputStream() {
|
||||
// The signature covers all of the OTA package except the
|
||||
// archive comment and its 2-byte length.
|
||||
long toRead = fileLen - commentSize - 2;
|
||||
long soFar = 0;
|
||||
|
||||
if (listener != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
int p = (int)(soFar * 100 / toRead);
|
||||
if (p > lastPercent &&
|
||||
now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
|
||||
lastPercent = p;
|
||||
lastPublishTime = now;
|
||||
listener.onProgress(lastPercent);
|
||||
}
|
||||
int lastPercent = 0;
|
||||
long lastPublishTime = startTimeMillis;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (soFar >= toRead) {
|
||||
return -1;
|
||||
}
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int size = len;
|
||||
if (soFar + size > toRead) {
|
||||
size = (int)(toRead - soFar);
|
||||
}
|
||||
int read = raf.read(b, off, size);
|
||||
soFar += read;
|
||||
|
||||
if (listenerForInner != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
int p = (int)(soFar * 100 / toRead);
|
||||
if (p > lastPercent &&
|
||||
now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
|
||||
lastPercent = p;
|
||||
lastPublishTime = now;
|
||||
listenerForInner.onProgress(lastPercent);
|
||||
}
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
});
|
||||
|
||||
final boolean interrupted = Thread.interrupted();
|
||||
if (listener != null) {
|
||||
listener.onProgress(100);
|
||||
}
|
||||
@@ -309,7 +277,7 @@ public class RecoverySystem {
|
||||
throw new SignatureException("verification was interrupted");
|
||||
}
|
||||
|
||||
if (!sig.verify(sigInfo.getEncryptedDigest())) {
|
||||
if (verifyResult == null) {
|
||||
throw new SignatureException("signature digest verification failed");
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.Set;
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import com.android.org.conscrypt.Hex;
|
||||
import com.android.org.conscrypt.NativeCrypto;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
@@ -136,7 +137,7 @@ abstract class DirectoryCertificateSource implements CertificateSource {
|
||||
|
||||
private String getHash(X500Principal name) {
|
||||
int hash = NativeCrypto.X509_NAME_hash_old(name);
|
||||
return IntegralToString.intToHexString(hash, false, 8);
|
||||
return Hex.intToHexString(hash, 8);
|
||||
}
|
||||
|
||||
private X509Certificate readCertificate(String file) {
|
||||
|
||||
427
core/java/android/util/jar/StrictJarFile.java
Normal file
427
core/java/android/util/jar/StrictJarFile.java
Normal file
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package android.util.jar;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.io.Streams;
|
||||
|
||||
/**
|
||||
* A subset of the JarFile API implemented as a thin wrapper over
|
||||
* system/core/libziparchive.
|
||||
*
|
||||
* @hide for internal use only. Not API compatible (or as forgiving) as
|
||||
* {@link java.util.jar.JarFile}
|
||||
*/
|
||||
public final class StrictJarFile {
|
||||
|
||||
private final long nativeHandle;
|
||||
|
||||
// NOTE: It's possible to share a file descriptor with the native
|
||||
// code, at the cost of some additional complexity.
|
||||
private final RandomAccessFile raf;
|
||||
|
||||
private final StrictJarManifest manifest;
|
||||
private final StrictJarVerifier verifier;
|
||||
|
||||
private final boolean isSigned;
|
||||
|
||||
private final CloseGuard guard = CloseGuard.get();
|
||||
private boolean closed;
|
||||
|
||||
public StrictJarFile(String fileName) throws IOException, SecurityException {
|
||||
this.nativeHandle = nativeOpenJarFile(fileName);
|
||||
this.raf = new RandomAccessFile(fileName, "r");
|
||||
|
||||
try {
|
||||
// Read the MANIFEST and signature files up front and try to
|
||||
// parse them. We never want to accept a JAR File with broken signatures
|
||||
// or manifests, so it's best to throw as early as possible.
|
||||
HashMap<String, byte[]> metaEntries = getMetaEntries();
|
||||
this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
|
||||
this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
|
||||
Set<String> files = manifest.getEntries().keySet();
|
||||
for (String file : files) {
|
||||
if (findEntry(file) == null) {
|
||||
throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
isSigned = verifier.readCertificates() && verifier.isSignedJar();
|
||||
} catch (IOException | SecurityException e) {
|
||||
nativeClose(this.nativeHandle);
|
||||
IoUtils.closeQuietly(this.raf);
|
||||
throw e;
|
||||
}
|
||||
|
||||
guard.open("close");
|
||||
}
|
||||
|
||||
public StrictJarManifest getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public Iterator<ZipEntry> iterator() throws IOException {
|
||||
return new EntryIterator(nativeHandle, "");
|
||||
}
|
||||
|
||||
public ZipEntry findEntry(String name) {
|
||||
return nativeFindEntry(nativeHandle, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
|
||||
* This method MUST be called only after fully exhausting the InputStream belonging
|
||||
* to this entry.
|
||||
*
|
||||
* Returns {@code null} if this jar file isn't signed or if this method is
|
||||
* called before the stream is processed.
|
||||
*/
|
||||
public Certificate[][] getCertificateChains(ZipEntry ze) {
|
||||
if (isSigned) {
|
||||
return verifier.getCertificateChains(ze.getName());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all certificates for a given {@link ZipEntry} belonging to this jar.
|
||||
* This method MUST be called only after fully exhausting the InputStream belonging
|
||||
* to this entry.
|
||||
*
|
||||
* Returns {@code null} if this jar file isn't signed or if this method is
|
||||
* called before the stream is processed.
|
||||
*
|
||||
* @deprecated Switch callers to use getCertificateChains instead
|
||||
*/
|
||||
@Deprecated
|
||||
public Certificate[] getCertificates(ZipEntry ze) {
|
||||
if (isSigned) {
|
||||
Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
|
||||
|
||||
// Measure number of certs.
|
||||
int count = 0;
|
||||
for (Certificate[] chain : certChains) {
|
||||
count += chain.length;
|
||||
}
|
||||
|
||||
// Create new array and copy all the certs into it.
|
||||
Certificate[] certs = new Certificate[count];
|
||||
int i = 0;
|
||||
for (Certificate[] chain : certChains) {
|
||||
System.arraycopy(chain, 0, certs, i, chain.length);
|
||||
i += chain.length;
|
||||
}
|
||||
|
||||
return certs;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public InputStream getInputStream(ZipEntry ze) {
|
||||
final InputStream is = getZipInputStream(ze);
|
||||
|
||||
if (isSigned) {
|
||||
StrictJarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
|
||||
if (entry == null) {
|
||||
return is;
|
||||
}
|
||||
|
||||
return new JarFileInputStream(is, ze.getSize(), entry);
|
||||
}
|
||||
|
||||
return is;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (!closed) {
|
||||
guard.close();
|
||||
|
||||
nativeClose(nativeHandle);
|
||||
IoUtils.closeQuietly(raf);
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream getZipInputStream(ZipEntry ze) {
|
||||
if (ze.getMethod() == ZipEntry.STORED) {
|
||||
return new RAFStream(raf, ze.getDataOffset(),
|
||||
ze.getDataOffset() + ze.getSize());
|
||||
} else {
|
||||
final RAFStream wrapped = new RAFStream(
|
||||
raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
|
||||
|
||||
int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
|
||||
return new ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
|
||||
}
|
||||
}
|
||||
|
||||
static final class EntryIterator implements Iterator<ZipEntry> {
|
||||
private final long iterationHandle;
|
||||
private ZipEntry nextEntry;
|
||||
|
||||
EntryIterator(long nativeHandle, String prefix) throws IOException {
|
||||
iterationHandle = nativeStartIteration(nativeHandle, prefix);
|
||||
}
|
||||
|
||||
public ZipEntry next() {
|
||||
if (nextEntry != null) {
|
||||
final ZipEntry ze = nextEntry;
|
||||
nextEntry = null;
|
||||
return ze;
|
||||
}
|
||||
|
||||
return nativeNextEntry(iterationHandle);
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
if (nextEntry != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final ZipEntry ze = nativeNextEntry(iterationHandle);
|
||||
if (ze == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nextEntry = ze;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, byte[]> getMetaEntries() throws IOException {
|
||||
HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
|
||||
|
||||
Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
|
||||
while (entryIterator.hasNext()) {
|
||||
final ZipEntry entry = entryIterator.next();
|
||||
metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
|
||||
}
|
||||
|
||||
return metaEntries;
|
||||
}
|
||||
|
||||
static final class JarFileInputStream extends FilterInputStream {
|
||||
private final StrictJarVerifier.VerifierEntry entry;
|
||||
|
||||
private long count;
|
||||
private boolean done = false;
|
||||
|
||||
JarFileInputStream(InputStream is, long size, StrictJarVerifier.VerifierEntry e) {
|
||||
super(is);
|
||||
entry = e;
|
||||
|
||||
count = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (done) {
|
||||
return -1;
|
||||
}
|
||||
if (count > 0) {
|
||||
int r = super.read();
|
||||
if (r != -1) {
|
||||
entry.write(r);
|
||||
count--;
|
||||
} else {
|
||||
count = 0;
|
||||
}
|
||||
if (count == 0) {
|
||||
done = true;
|
||||
entry.verify();
|
||||
}
|
||||
return r;
|
||||
} else {
|
||||
done = true;
|
||||
entry.verify();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||
if (done) {
|
||||
return -1;
|
||||
}
|
||||
if (count > 0) {
|
||||
int r = super.read(buffer, byteOffset, byteCount);
|
||||
if (r != -1) {
|
||||
int size = r;
|
||||
if (count < size) {
|
||||
size = (int) count;
|
||||
}
|
||||
entry.write(buffer, byteOffset, size);
|
||||
count -= size;
|
||||
} else {
|
||||
count = 0;
|
||||
}
|
||||
if (count == 0) {
|
||||
done = true;
|
||||
entry.verify();
|
||||
}
|
||||
return r;
|
||||
} else {
|
||||
done = true;
|
||||
entry.verify();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
if (done) {
|
||||
return 0;
|
||||
}
|
||||
return super.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long byteCount) throws IOException {
|
||||
return Streams.skipByReading(this, byteCount);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static class ZipInflaterInputStream extends InflaterInputStream {
|
||||
private final ZipEntry entry;
|
||||
private long bytesRead = 0;
|
||||
|
||||
public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
|
||||
super(is, inf, bsize);
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
@Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||
final int i;
|
||||
try {
|
||||
i = super.read(buffer, byteOffset, byteCount);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Error reading data for " + entry.getName() + " near offset "
|
||||
+ bytesRead, e);
|
||||
}
|
||||
if (i == -1) {
|
||||
if (entry.getSize() != bytesRead) {
|
||||
throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs "
|
||||
+ entry.getSize());
|
||||
}
|
||||
} else {
|
||||
bytesRead += i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override public int available() throws IOException {
|
||||
if (closed) {
|
||||
// Our superclass will throw an exception, but there's a jtreg test that
|
||||
// explicitly checks that the InputStream returned from ZipFile.getInputStream
|
||||
// returns 0 even when closed.
|
||||
return 0;
|
||||
}
|
||||
return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a stream around a RandomAccessFile. The RandomAccessFile is shared
|
||||
* among all streams returned by getInputStream(), so we have to synchronize
|
||||
* access to it. (We can optimize this by adding buffering here to reduce
|
||||
* collisions.)
|
||||
*
|
||||
* <p>We could support mark/reset, but we don't currently need them.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static class RAFStream extends InputStream {
|
||||
private final RandomAccessFile sharedRaf;
|
||||
private long endOffset;
|
||||
private long offset;
|
||||
|
||||
|
||||
public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
|
||||
sharedRaf = raf;
|
||||
offset = initialOffset;
|
||||
this.endOffset = endOffset;
|
||||
}
|
||||
|
||||
public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
|
||||
this(raf, initialOffset, raf.length());
|
||||
}
|
||||
|
||||
@Override public int available() throws IOException {
|
||||
return (offset < endOffset ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override public int read() throws IOException {
|
||||
return Streams.readSingleByte(this);
|
||||
}
|
||||
|
||||
@Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||
synchronized (sharedRaf) {
|
||||
final long length = endOffset - offset;
|
||||
if (byteCount > length) {
|
||||
byteCount = (int) length;
|
||||
}
|
||||
sharedRaf.seek(offset);
|
||||
int count = sharedRaf.read(buffer, byteOffset, byteCount);
|
||||
if (count > 0) {
|
||||
offset += count;
|
||||
return count;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public long skip(long byteCount) throws IOException {
|
||||
if (byteCount > endOffset - offset) {
|
||||
byteCount = endOffset - offset;
|
||||
}
|
||||
offset += byteCount;
|
||||
return byteCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static native long nativeOpenJarFile(String fileName) throws IOException;
|
||||
private static native long nativeStartIteration(long nativeHandle, String prefix);
|
||||
private static native ZipEntry nativeNextEntry(long iterationHandle);
|
||||
private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
|
||||
private static native void nativeClose(long nativeHandle);
|
||||
}
|
||||
315
core/java/android/util/jar/StrictJarManifest.java
Normal file
315
core/java/android/util/jar/StrictJarManifest.java
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.util.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import libcore.io.Streams;
|
||||
|
||||
/**
|
||||
* The {@code StrictJarManifest} class is used to obtain attribute information for a
|
||||
* {@code StrictJarFile} and its entries.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class StrictJarManifest implements Cloneable {
|
||||
static final int LINE_LENGTH_LIMIT = 72;
|
||||
|
||||
private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
|
||||
|
||||
private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
|
||||
|
||||
private final Attributes mainAttributes;
|
||||
private final HashMap<String, Attributes> entries;
|
||||
|
||||
static final class Chunk {
|
||||
final int start;
|
||||
final int end;
|
||||
|
||||
Chunk(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, Chunk> chunks;
|
||||
|
||||
/**
|
||||
* The end of the main attributes section in the manifest is needed in
|
||||
* verification.
|
||||
*/
|
||||
private int mainEnd;
|
||||
|
||||
/**
|
||||
* Creates a new {@code StrictJarManifest} instance.
|
||||
*/
|
||||
public StrictJarManifest() {
|
||||
entries = new HashMap<String, Attributes>();
|
||||
mainAttributes = new Attributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code StrictJarManifest} instance using the attributes obtained
|
||||
* from the input stream.
|
||||
*
|
||||
* @param is
|
||||
* {@code InputStream} to parse for attributes.
|
||||
* @throws IOException
|
||||
* if an IO error occurs while creating this {@code StrictJarManifest}
|
||||
*/
|
||||
public StrictJarManifest(InputStream is) throws IOException {
|
||||
this();
|
||||
read(Streams.readFully(is));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code StrictJarManifest} instance. The new instance will have the
|
||||
* same attributes as those found in the parameter {@code StrictJarManifest}.
|
||||
*
|
||||
* @param man
|
||||
* {@code StrictJarManifest} instance to obtain attributes from.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public StrictJarManifest(StrictJarManifest man) {
|
||||
mainAttributes = (Attributes) man.mainAttributes.clone();
|
||||
entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
|
||||
.getEntries()).clone();
|
||||
}
|
||||
|
||||
StrictJarManifest(byte[] manifestBytes, boolean readChunks) throws IOException {
|
||||
this();
|
||||
if (readChunks) {
|
||||
chunks = new HashMap<String, Chunk>();
|
||||
}
|
||||
read(manifestBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the both the main attributes as well as the entry attributes
|
||||
* associated with this {@code StrictJarManifest}.
|
||||
*/
|
||||
public void clear() {
|
||||
entries.clear();
|
||||
mainAttributes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Attributes} associated with the parameter entry
|
||||
* {@code name}.
|
||||
*
|
||||
* @param name
|
||||
* the name of the entry to obtain {@code Attributes} from.
|
||||
* @return the Attributes for the entry or {@code null} if the entry does
|
||||
* not exist.
|
||||
*/
|
||||
public Attributes getAttributes(String name) {
|
||||
return getEntries().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map containing the {@code Attributes} for each entry in the
|
||||
* {@code StrictJarManifest}.
|
||||
*
|
||||
* @return the map of entry attributes.
|
||||
*/
|
||||
public Map<String, Attributes> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main {@code Attributes} of the {@code JarFile}.
|
||||
*
|
||||
* @return main {@code Attributes} associated with the source {@code
|
||||
* JarFile}.
|
||||
*/
|
||||
public Attributes getMainAttributes() {
|
||||
return mainAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this {@code StrictJarManifest}. The returned {@code StrictJarManifest}
|
||||
* will equal the {@code StrictJarManifest} from which it was cloned.
|
||||
*
|
||||
* @return a copy of this instance.
|
||||
*/
|
||||
@Override
|
||||
public Object clone() {
|
||||
return new StrictJarManifest(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes this {@code StrictJarManifest}'s name/attributes pairs to the given {@code OutputStream}.
|
||||
* The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
|
||||
* calling this method, or no attributes will be written.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an error occurs writing the {@code StrictJarManifest}.
|
||||
*/
|
||||
public void write(OutputStream os) throws IOException {
|
||||
write(this, os);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges name/attribute pairs read from the input stream {@code is} into this manifest.
|
||||
*
|
||||
* @param is
|
||||
* The {@code InputStream} to read from.
|
||||
* @throws IOException
|
||||
* If an error occurs reading the manifest.
|
||||
*/
|
||||
public void read(InputStream is) throws IOException {
|
||||
read(Streams.readFullyNoClose(is));
|
||||
}
|
||||
|
||||
private void read(byte[] buf) throws IOException {
|
||||
if (buf.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
StrictJarManifestReader im = new StrictJarManifestReader(buf, mainAttributes);
|
||||
mainEnd = im.getEndOfMainSection();
|
||||
im.readEntries(entries, chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code for this instance.
|
||||
*
|
||||
* @return this {@code StrictJarManifest}'s hashCode.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mainAttributes.hashCode() ^ getEntries().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the receiver is equal to the parameter object. Two {@code
|
||||
* StrictJarManifest}s are equal if they have identical main attributes as well as
|
||||
* identical entry attributes.
|
||||
*
|
||||
* @param o
|
||||
* the object to compare against.
|
||||
* @return {@code true} if the manifests are equal, {@code false} otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
if (o.getClass() != this.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!mainAttributes.equals(((StrictJarManifest) o).mainAttributes)) {
|
||||
return false;
|
||||
}
|
||||
return getEntries().equals(((StrictJarManifest) o).getEntries());
|
||||
}
|
||||
|
||||
Chunk getChunk(String name) {
|
||||
return chunks.get(name);
|
||||
}
|
||||
|
||||
void removeChunks() {
|
||||
chunks = null;
|
||||
}
|
||||
|
||||
int getMainAttributesEnd() {
|
||||
return mainEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out the attribute information of the specified manifest to the
|
||||
* specified {@code OutputStream}
|
||||
*
|
||||
* @param manifest
|
||||
* the manifest to write out.
|
||||
* @param out
|
||||
* The {@code OutputStream} to write to.
|
||||
* @throws IOException
|
||||
* If an error occurs writing the {@code StrictJarManifest}.
|
||||
*/
|
||||
static void write(StrictJarManifest manifest, OutputStream out) throws IOException {
|
||||
CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
|
||||
|
||||
Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
|
||||
String version = manifest.mainAttributes.getValue(versionName);
|
||||
if (version == null) {
|
||||
versionName = Attributes.Name.SIGNATURE_VERSION;
|
||||
version = manifest.mainAttributes.getValue(versionName);
|
||||
}
|
||||
if (version != null) {
|
||||
writeEntry(out, versionName, version, encoder, buffer);
|
||||
Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
|
||||
while (entries.hasNext()) {
|
||||
Attributes.Name name = (Attributes.Name) entries.next();
|
||||
if (!name.equals(versionName)) {
|
||||
writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
out.write(LINE_SEPARATOR);
|
||||
Iterator<String> i = manifest.getEntries().keySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
String key = i.next();
|
||||
writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
|
||||
Attributes attributes = manifest.entries.get(key);
|
||||
Iterator<?> entries = attributes.keySet().iterator();
|
||||
while (entries.hasNext()) {
|
||||
Attributes.Name name = (Attributes.Name) entries.next();
|
||||
writeEntry(out, name, attributes.getValue(name), encoder, buffer);
|
||||
}
|
||||
out.write(LINE_SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeEntry(OutputStream os, Attributes.Name name,
|
||||
String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
|
||||
String nameString = name.toString();
|
||||
os.write(nameString.getBytes(StandardCharsets.US_ASCII));
|
||||
os.write(VALUE_SEPARATOR);
|
||||
|
||||
encoder.reset();
|
||||
bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
|
||||
|
||||
CharBuffer cBuf = CharBuffer.wrap(value);
|
||||
|
||||
while (true) {
|
||||
CoderResult r = encoder.encode(cBuf, bBuf, true);
|
||||
if (CoderResult.UNDERFLOW == r) {
|
||||
r = encoder.flush(bBuf);
|
||||
}
|
||||
os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
|
||||
os.write(LINE_SEPARATOR);
|
||||
if (CoderResult.UNDERFLOW == r) {
|
||||
break;
|
||||
}
|
||||
os.write(' ');
|
||||
bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
184
core/java/android/util/jar/StrictJarManifestReader.java
Normal file
184
core/java/android/util/jar/StrictJarManifestReader.java
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.util.jar;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
/**
|
||||
* Reads a JAR file manifest. The specification is here:
|
||||
* http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
|
||||
*/
|
||||
class StrictJarManifestReader {
|
||||
// There are relatively few unique attribute names,
|
||||
// but a manifest might have thousands of entries.
|
||||
private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
|
||||
|
||||
private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
|
||||
|
||||
private final byte[] buf;
|
||||
|
||||
private final int endOfMainSection;
|
||||
|
||||
private int pos;
|
||||
|
||||
private Attributes.Name name;
|
||||
|
||||
private String value;
|
||||
|
||||
private int consecutiveLineBreaks = 0;
|
||||
|
||||
public StrictJarManifestReader(byte[] buf, Attributes main) throws IOException {
|
||||
this.buf = buf;
|
||||
while (readHeader()) {
|
||||
main.put(name, value);
|
||||
}
|
||||
this.endOfMainSection = pos;
|
||||
}
|
||||
|
||||
public void readEntries(Map<String, Attributes> entries, Map<String, StrictJarManifest.Chunk> chunks) throws IOException {
|
||||
int mark = pos;
|
||||
while (readHeader()) {
|
||||
if (!Attributes.Name.NAME.equals(name)) {
|
||||
throw new IOException("Entry is not named");
|
||||
}
|
||||
String entryNameValue = value;
|
||||
|
||||
Attributes entry = entries.get(entryNameValue);
|
||||
if (entry == null) {
|
||||
entry = new Attributes(12);
|
||||
}
|
||||
|
||||
while (readHeader()) {
|
||||
entry.put(name, value);
|
||||
}
|
||||
|
||||
if (chunks != null) {
|
||||
if (chunks.get(entryNameValue) != null) {
|
||||
// TODO A bug: there might be several verification chunks for
|
||||
// the same name. I believe they should be used to update
|
||||
// signature in order of appearance; there are two ways to fix
|
||||
// this: either use a list of chunks, or decide on used
|
||||
// signature algorithm in advance and reread the chunks while
|
||||
// updating the signature; for now a defensive error is thrown
|
||||
throw new IOException("A jar verifier does not support more than one entry with the same name");
|
||||
}
|
||||
chunks.put(entryNameValue, new StrictJarManifest.Chunk(mark, pos));
|
||||
mark = pos;
|
||||
}
|
||||
|
||||
entries.put(entryNameValue, entry);
|
||||
}
|
||||
}
|
||||
|
||||
public int getEndOfMainSection() {
|
||||
return endOfMainSection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single line from the manifest buffer.
|
||||
*/
|
||||
private boolean readHeader() throws IOException {
|
||||
if (consecutiveLineBreaks > 1) {
|
||||
// break a section on an empty line
|
||||
consecutiveLineBreaks = 0;
|
||||
return false;
|
||||
}
|
||||
readName();
|
||||
consecutiveLineBreaks = 0;
|
||||
readValue();
|
||||
// if the last line break is missed, the line
|
||||
// is ignored by the reference implementation
|
||||
return consecutiveLineBreaks > 0;
|
||||
}
|
||||
|
||||
private void readName() throws IOException {
|
||||
int mark = pos;
|
||||
|
||||
while (pos < buf.length) {
|
||||
if (buf[pos++] != ':') {
|
||||
continue;
|
||||
}
|
||||
|
||||
String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
|
||||
|
||||
if (buf[pos++] != ' ') {
|
||||
throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
|
||||
}
|
||||
|
||||
try {
|
||||
name = attributeNameCache.get(nameString);
|
||||
if (name == null) {
|
||||
name = new Attributes.Name(nameString);
|
||||
attributeNameCache.put(nameString, name);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// new Attributes.Name() throws IllegalArgumentException but we declare IOException
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void readValue() throws IOException {
|
||||
boolean lastCr = false;
|
||||
int mark = pos;
|
||||
int last = pos;
|
||||
valueBuffer.reset();
|
||||
while (pos < buf.length) {
|
||||
byte next = buf[pos++];
|
||||
switch (next) {
|
||||
case 0:
|
||||
throw new IOException("NUL character in a manifest");
|
||||
case '\n':
|
||||
if (lastCr) {
|
||||
lastCr = false;
|
||||
} else {
|
||||
consecutiveLineBreaks++;
|
||||
}
|
||||
continue;
|
||||
case '\r':
|
||||
lastCr = true;
|
||||
consecutiveLineBreaks++;
|
||||
continue;
|
||||
case ' ':
|
||||
if (consecutiveLineBreaks == 1) {
|
||||
valueBuffer.write(buf, mark, last - mark);
|
||||
mark = pos;
|
||||
consecutiveLineBreaks = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (consecutiveLineBreaks >= 1) {
|
||||
pos--;
|
||||
break;
|
||||
}
|
||||
last = pos;
|
||||
}
|
||||
|
||||
valueBuffer.write(buf, mark, last - mark);
|
||||
// A bit frustrating that that Charset.forName will be called
|
||||
// again.
|
||||
value = valueBuffer.toString(StandardCharsets.UTF_8.name());
|
||||
}
|
||||
}
|
||||
456
core/java/android/util/jar/StrictJarVerifier.java
Normal file
456
core/java/android/util/jar/StrictJarVerifier.java
Normal file
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.util.jar;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import libcore.io.Base64;
|
||||
import sun.security.jca.Providers;
|
||||
import sun.security.pkcs.PKCS7;
|
||||
|
||||
/**
|
||||
* Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
|
||||
* the verification of signed JARs. {@code JarFile} and {@code JarInputStream}
|
||||
* objects are expected to have a {@code JarVerifier} instance member which
|
||||
* can be used to carry out the tasks associated with verifying a signed JAR.
|
||||
* These tasks would typically include:
|
||||
* <ul>
|
||||
* <li>verification of all signed signature files
|
||||
* <li>confirmation that all signed data was signed only by the party or parties
|
||||
* specified in the signature block data
|
||||
* <li>verification that the contents of all signature files (i.e. {@code .SF}
|
||||
* files) agree with the JAR entries information found in the JAR manifest.
|
||||
* </ul>
|
||||
*/
|
||||
class StrictJarVerifier {
|
||||
/**
|
||||
* List of accepted digest algorithms. This list is in order from most
|
||||
* preferred to least preferred.
|
||||
*/
|
||||
private static final String[] DIGEST_ALGORITHMS = new String[] {
|
||||
"SHA-512",
|
||||
"SHA-384",
|
||||
"SHA-256",
|
||||
"SHA1",
|
||||
};
|
||||
|
||||
private final String jarName;
|
||||
private final StrictJarManifest manifest;
|
||||
private final HashMap<String, byte[]> metaEntries;
|
||||
private final int mainAttributesEnd;
|
||||
|
||||
private final Hashtable<String, HashMap<String, Attributes>> signatures =
|
||||
new Hashtable<String, HashMap<String, Attributes>>(5);
|
||||
|
||||
private final Hashtable<String, Certificate[]> certificates =
|
||||
new Hashtable<String, Certificate[]>(5);
|
||||
|
||||
private final Hashtable<String, Certificate[][]> verifiedEntries =
|
||||
new Hashtable<String, Certificate[][]>();
|
||||
|
||||
/**
|
||||
* Stores and a hash and a message digest and verifies that massage digest
|
||||
* matches the hash.
|
||||
*/
|
||||
static class VerifierEntry extends OutputStream {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final MessageDigest digest;
|
||||
|
||||
private final byte[] hash;
|
||||
|
||||
private final Certificate[][] certChains;
|
||||
|
||||
private final Hashtable<String, Certificate[][]> verifiedEntries;
|
||||
|
||||
VerifierEntry(String name, MessageDigest digest, byte[] hash,
|
||||
Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) {
|
||||
this.name = name;
|
||||
this.digest = digest;
|
||||
this.hash = hash;
|
||||
this.certChains = certChains;
|
||||
this.verifiedEntries = verifedEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a digest with one byte.
|
||||
*/
|
||||
@Override
|
||||
public void write(int value) {
|
||||
digest.update((byte) value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a digest with byte array.
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] buf, int off, int nbytes) {
|
||||
digest.update(buf, off, nbytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the digests stored in the manifest match the decrypted
|
||||
* digests from the .SF file. This indicates the validity of the
|
||||
* signing, not the integrity of the file, as its digest must be
|
||||
* calculated and verified when its contents are read.
|
||||
*
|
||||
* @throws SecurityException
|
||||
* if the digest value stored in the manifest does <i>not</i>
|
||||
* agree with the decrypted digest as recovered from the
|
||||
* <code>.SF</code> file.
|
||||
*/
|
||||
void verify() {
|
||||
byte[] d = digest.digest();
|
||||
if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
|
||||
throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
|
||||
}
|
||||
verifiedEntries.put(name, certChains);
|
||||
}
|
||||
}
|
||||
|
||||
private static SecurityException invalidDigest(String signatureFile, String name,
|
||||
String jarName) {
|
||||
throw new SecurityException(signatureFile + " has invalid digest for " + name +
|
||||
" in " + jarName);
|
||||
}
|
||||
|
||||
private static SecurityException failedVerification(String jarName, String signatureFile) {
|
||||
throw new SecurityException(jarName + " failed verification of " + signatureFile);
|
||||
}
|
||||
|
||||
private static SecurityException failedVerification(String jarName, String signatureFile,
|
||||
Throwable e) {
|
||||
throw new SecurityException(jarName + " failed verification of " + signatureFile, e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs and returns a new instance of {@code JarVerifier}.
|
||||
*
|
||||
* @param name
|
||||
* the name of the JAR file being verified.
|
||||
*/
|
||||
StrictJarVerifier(String name, StrictJarManifest manifest,
|
||||
HashMap<String, byte[]> metaEntries) {
|
||||
jarName = name;
|
||||
this.manifest = manifest;
|
||||
this.metaEntries = metaEntries;
|
||||
this.mainAttributesEnd = manifest.getMainAttributesEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked for each new JAR entry read operation from the input
|
||||
* stream. This method constructs and returns a new {@link VerifierEntry}
|
||||
* which contains the certificates used to sign the entry and its hash value
|
||||
* as specified in the JAR MANIFEST format.
|
||||
*
|
||||
* @param name
|
||||
* the name of an entry in a JAR file which is <b>not</b> in the
|
||||
* {@code META-INF} directory.
|
||||
* @return a new instance of {@link VerifierEntry} which can be used by
|
||||
* callers as an {@link OutputStream}.
|
||||
*/
|
||||
VerifierEntry initEntry(String name) {
|
||||
// If no manifest is present by the time an entry is found,
|
||||
// verification cannot occur. If no signature files have
|
||||
// been found, do not verify.
|
||||
if (manifest == null || signatures.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Attributes attributes = manifest.getAttributes(name);
|
||||
// entry has no digest
|
||||
if (attributes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
|
||||
Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
|
||||
HashMap<String, Attributes> hm = entry.getValue();
|
||||
if (hm.get(name) != null) {
|
||||
// Found an entry for entry name in .SF file
|
||||
String signatureFile = entry.getKey();
|
||||
Certificate[] certChain = certificates.get(signatureFile);
|
||||
if (certChain != null) {
|
||||
certChains.add(certChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// entry is not signed
|
||||
if (certChains.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);
|
||||
|
||||
for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
|
||||
final String algorithm = DIGEST_ALGORITHMS[i];
|
||||
final String hash = attributes.getValue(algorithm + "-Digest");
|
||||
if (hash == null) {
|
||||
continue;
|
||||
}
|
||||
byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
|
||||
|
||||
try {
|
||||
return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
|
||||
certChainsArray, verifiedEntries);
|
||||
} catch (NoSuchAlgorithmException ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new meta entry to the internal collection of data held on each JAR
|
||||
* entry in the {@code META-INF} directory including the manifest
|
||||
* file itself. Files associated with the signing of a JAR would also be
|
||||
* added to this collection.
|
||||
*
|
||||
* @param name
|
||||
* the name of the file located in the {@code META-INF}
|
||||
* directory.
|
||||
* @param buf
|
||||
* the file bytes for the file called {@code name}.
|
||||
* @see #removeMetaEntries()
|
||||
*/
|
||||
void addMetaEntry(String name, byte[] buf) {
|
||||
metaEntries.put(name.toUpperCase(Locale.US), buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the associated JAR file is signed, check on the validity of all of the
|
||||
* known signatures.
|
||||
*
|
||||
* @return {@code true} if the associated JAR is signed and an internal
|
||||
* check verifies the validity of the signature(s). {@code false} if
|
||||
* the associated JAR file has no entries at all in its {@code
|
||||
* META-INF} directory. This situation is indicative of an invalid
|
||||
* JAR file.
|
||||
* <p>
|
||||
* Will also return {@code true} if the JAR file is <i>not</i>
|
||||
* signed.
|
||||
* @throws SecurityException
|
||||
* if the JAR file is signed and it is determined that a
|
||||
* signature block file contains an invalid signature for the
|
||||
* corresponding signature file.
|
||||
*/
|
||||
synchronized boolean readCertificates() {
|
||||
if (metaEntries.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Iterator<String> it = metaEntries.keySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
String key = it.next();
|
||||
if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
|
||||
verifyCertificate(key);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the signature computed from {@code sfBytes} matches
|
||||
* that specified in {@code blockBytes} (which is a PKCS7 block). Returns
|
||||
* certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException}
|
||||
* if something goes wrong during verification.
|
||||
*/
|
||||
static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes)
|
||||
throws GeneralSecurityException {
|
||||
|
||||
Object obj = null;
|
||||
try {
|
||||
|
||||
obj = Providers.startJarVerification();
|
||||
PKCS7 block = new PKCS7(blockBytes);
|
||||
if (block.verify(sfBytes) == null) {
|
||||
throw new GeneralSecurityException("Failed to verify signature");
|
||||
}
|
||||
X509Certificate[] blockCerts = block.getCertificates();
|
||||
Certificate[] signerCertChain = null;
|
||||
if (blockCerts != null) {
|
||||
signerCertChain = new Certificate[blockCerts.length];
|
||||
for (int i = 0; i < blockCerts.length; ++i) {
|
||||
signerCertChain[i] = blockCerts[i];
|
||||
}
|
||||
}
|
||||
return signerCertChain;
|
||||
} catch (IOException e) {
|
||||
throw new GeneralSecurityException("IO exception verifying jar cert", e);
|
||||
} finally {
|
||||
Providers.stopJarVerification(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param certFile
|
||||
*/
|
||||
private void verifyCertificate(String certFile) {
|
||||
// Found Digital Sig, .SF should already have been read
|
||||
String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
|
||||
byte[] sfBytes = metaEntries.get(signatureFile);
|
||||
if (sfBytes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
|
||||
// Manifest entry is required for any verifications.
|
||||
if (manifestBytes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] sBlockBytes = metaEntries.get(certFile);
|
||||
try {
|
||||
Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes);
|
||||
if (signerCertChain != null) {
|
||||
certificates.put(signatureFile, signerCertChain);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw failedVerification(jarName, signatureFile, e);
|
||||
}
|
||||
|
||||
// Verify manifest hash in .sf file
|
||||
Attributes attributes = new Attributes();
|
||||
HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
|
||||
try {
|
||||
StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes);
|
||||
im.readEntries(entries, null);
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we actually have any signatures to look at?
|
||||
if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean createdBySigntool = false;
|
||||
String createdBy = attributes.getValue("Created-By");
|
||||
if (createdBy != null) {
|
||||
createdBySigntool = createdBy.indexOf("signtool") != -1;
|
||||
}
|
||||
|
||||
// Use .SF to verify the mainAttributes of the manifest
|
||||
// If there is no -Digest-Manifest-Main-Attributes entry in .SF
|
||||
// file, such as those created before java 1.5, then we ignore
|
||||
// such verification.
|
||||
if (mainAttributesEnd > 0 && !createdBySigntool) {
|
||||
String digestAttribute = "-Digest-Manifest-Main-Attributes";
|
||||
if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
|
||||
throw failedVerification(jarName, signatureFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Use .SF to verify the whole manifest.
|
||||
String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
|
||||
if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
|
||||
Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, Attributes> entry = it.next();
|
||||
StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey());
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
if (!verify(entry.getValue(), "-Digest", manifestBytes,
|
||||
chunk.start, chunk.end, createdBySigntool, false)) {
|
||||
throw invalidDigest(signatureFile, entry.getKey(), jarName);
|
||||
}
|
||||
}
|
||||
}
|
||||
metaEntries.put(signatureFile, null);
|
||||
signatures.put(signatureFile, entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a <code>boolean</code> indication of whether or not the
|
||||
* associated jar file is signed.
|
||||
*
|
||||
* @return {@code true} if the JAR is signed, {@code false}
|
||||
* otherwise.
|
||||
*/
|
||||
boolean isSignedJar() {
|
||||
return certificates.size() > 0;
|
||||
}
|
||||
|
||||
private boolean verify(Attributes attributes, String entry, byte[] data,
|
||||
int start, int end, boolean ignoreSecondEndline, boolean ignorable) {
|
||||
for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
|
||||
String algorithm = DIGEST_ALGORITHMS[i];
|
||||
String hash = attributes.getValue(algorithm + entry);
|
||||
if (hash == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
continue;
|
||||
}
|
||||
if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') {
|
||||
md.update(data, start, end - 1 - start);
|
||||
} else {
|
||||
md.update(data, start, end - start);
|
||||
}
|
||||
byte[] b = md.digest();
|
||||
byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
|
||||
return MessageDigest.isEqual(b, Base64.decode(hashBytes));
|
||||
}
|
||||
return ignorable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the {@link java.security.cert.Certificate} chains that
|
||||
* were used to verify the signature on the JAR entry called
|
||||
* {@code name}. Callers must not modify the returned arrays.
|
||||
*
|
||||
* @param name
|
||||
* the name of a JAR entry.
|
||||
* @return an array of {@link java.security.cert.Certificate} chains.
|
||||
*/
|
||||
Certificate[][] getCertificateChains(String name) {
|
||||
return verifiedEntries.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all entries from the internal collection of data held about each
|
||||
* JAR entry in the {@code META-INF} directory.
|
||||
*/
|
||||
void removeMetaEntries() {
|
||||
metaEntries.clear();
|
||||
}
|
||||
}
|
||||
@@ -145,8 +145,9 @@ public final class Zygote {
|
||||
native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags,
|
||||
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
|
||||
|
||||
private static void callPostForkChildHooks(int debugFlags, String instructionSet) {
|
||||
VM_HOOKS.postForkChild(debugFlags, instructionSet);
|
||||
private static void callPostForkChildHooks(int debugFlags, boolean isSystemServer,
|
||||
String instructionSet) {
|
||||
VM_HOOKS.postForkChild(debugFlags, isSystemServer, instructionSet);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,28 +19,29 @@ package com.android.internal.util;
|
||||
public class HexDump
|
||||
{
|
||||
private final static char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
private final static char[] HEX_LOWER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
public static String dumpHexString(byte[] array)
|
||||
{
|
||||
return dumpHexString(array, 0, array.length);
|
||||
}
|
||||
|
||||
|
||||
public static String dumpHexString(byte[] array, int offset, int length)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
|
||||
byte[] line = new byte[16];
|
||||
int lineIndex = 0;
|
||||
|
||||
|
||||
result.append("\n0x");
|
||||
result.append(toHexString(offset));
|
||||
|
||||
|
||||
for (int i = offset ; i < offset + length ; i++)
|
||||
{
|
||||
if (lineIndex == 16)
|
||||
{
|
||||
result.append(" ");
|
||||
|
||||
|
||||
for (int j = 0 ; j < 16 ; j++)
|
||||
{
|
||||
if (line[j] > ' ' && line[j] < '~')
|
||||
@@ -52,20 +53,20 @@ public class HexDump
|
||||
result.append(".");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result.append("\n0x");
|
||||
result.append(toHexString(i));
|
||||
lineIndex = 0;
|
||||
}
|
||||
|
||||
|
||||
byte b = array[i];
|
||||
result.append(" ");
|
||||
result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
|
||||
result.append(HEX_DIGITS[b & 0x0F]);
|
||||
|
||||
|
||||
line[lineIndex++] = b;
|
||||
}
|
||||
|
||||
|
||||
if (lineIndex != 16)
|
||||
{
|
||||
int count = (16 - lineIndex) * 3;
|
||||
@@ -74,7 +75,7 @@ public class HexDump
|
||||
{
|
||||
result.append(" ");
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0 ; i < lineIndex ; i++)
|
||||
{
|
||||
if (line[i] > ' ' && line[i] < '~')
|
||||
@@ -87,10 +88,10 @@ public class HexDump
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
public static String toHexString(byte b)
|
||||
{
|
||||
return toHexString(toByteArray(b));
|
||||
@@ -98,48 +99,59 @@ public class HexDump
|
||||
|
||||
public static String toHexString(byte[] array)
|
||||
{
|
||||
return toHexString(array, 0, array.length);
|
||||
return toHexString(array, 0, array.length, true);
|
||||
}
|
||||
|
||||
|
||||
public static String toHexString(byte[] array, boolean upperCase)
|
||||
{
|
||||
return toHexString(array, 0, array.length, upperCase);
|
||||
}
|
||||
|
||||
public static String toHexString(byte[] array, int offset, int length)
|
||||
{
|
||||
return toHexString(array, offset, length, true);
|
||||
}
|
||||
|
||||
public static String toHexString(byte[] array, int offset, int length, boolean upperCase)
|
||||
{
|
||||
char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
|
||||
char[] buf = new char[length * 2];
|
||||
|
||||
int bufIndex = 0;
|
||||
for (int i = offset ; i < offset + length; i++)
|
||||
for (int i = offset ; i < offset + length; i++)
|
||||
{
|
||||
byte b = array[i];
|
||||
buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
|
||||
buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
|
||||
buf[bufIndex++] = digits[(b >>> 4) & 0x0F];
|
||||
buf[bufIndex++] = digits[b & 0x0F];
|
||||
}
|
||||
|
||||
return new String(buf);
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
|
||||
public static String toHexString(int i)
|
||||
{
|
||||
return toHexString(toByteArray(i));
|
||||
}
|
||||
|
||||
|
||||
public static byte[] toByteArray(byte b)
|
||||
{
|
||||
byte[] array = new byte[1];
|
||||
array[0] = b;
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
public static byte[] toByteArray(int i)
|
||||
{
|
||||
byte[] array = new byte[4];
|
||||
|
||||
|
||||
array[3] = (byte)(i & 0xFF);
|
||||
array[2] = (byte)((i >> 8) & 0xFF);
|
||||
array[1] = (byte)((i >> 16) & 0xFF);
|
||||
array[0] = (byte)((i >> 24) & 0xFF);
|
||||
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
private static int toByte(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return (c - '0');
|
||||
@@ -148,7 +160,7 @@ public class HexDump
|
||||
|
||||
throw new RuntimeException ("Invalid hex char '" + c + "'");
|
||||
}
|
||||
|
||||
|
||||
public static byte[] hexStringToByteArray(String hexString)
|
||||
{
|
||||
int length = hexString.length();
|
||||
@@ -158,7 +170,15 @@ public class HexDump
|
||||
{
|
||||
buffer[i / 2] = (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1)));
|
||||
}
|
||||
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
|
||||
char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
|
||||
sb.append(digits[(b >> 4) & 0xf]);
|
||||
sb.append(digits[b & 0xf]);
|
||||
return sb;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ LOCAL_SRC_FILES:= \
|
||||
android_util_Process.cpp \
|
||||
android_util_StringBlock.cpp \
|
||||
android_util_XmlBlock.cpp \
|
||||
android_util_jar_StrictJarFile.cpp \
|
||||
android_graphics_Canvas.cpp \
|
||||
android_graphics_Picture.cpp \
|
||||
android/graphics/AutoDecodeCancel.cpp \
|
||||
|
||||
@@ -176,6 +176,7 @@ extern int register_android_app_backup_FullBackup(JNIEnv *env);
|
||||
extern int register_android_app_ActivityThread(JNIEnv *env);
|
||||
extern int register_android_app_NativeActivity(JNIEnv *env);
|
||||
extern int register_android_media_RemoteDisplay(JNIEnv *env);
|
||||
extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
|
||||
extern int register_android_view_InputChannel(JNIEnv* env);
|
||||
extern int register_android_view_InputDevice(JNIEnv* env);
|
||||
extern int register_android_view_InputEventReceiver(JNIEnv* env);
|
||||
@@ -1359,6 +1360,7 @@ static const RegJNIRec gRegJNI[] = {
|
||||
REG_JNI(register_android_app_backup_FullBackup),
|
||||
REG_JNI(register_android_app_ActivityThread),
|
||||
REG_JNI(register_android_app_NativeActivity),
|
||||
REG_JNI(register_android_util_jar_StrictJarFile),
|
||||
REG_JNI(register_android_view_InputChannel),
|
||||
REG_JNI(register_android_view_InputEventReceiver),
|
||||
REG_JNI(register_android_view_InputEventSender),
|
||||
@@ -1374,6 +1376,8 @@ static const RegJNIRec gRegJNI[] = {
|
||||
REG_JNI(register_android_animation_PropertyValuesHolder),
|
||||
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
|
||||
REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
|
||||
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
172
core/jni/android_util_jar_StrictJarFile.cpp
Normal file
172
core/jni/android_util_jar_StrictJarFile.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "StrictJarFile"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "JNIHelp.h"
|
||||
#include "JniConstants.h"
|
||||
#include "ScopedLocalRef.h"
|
||||
#include "ScopedUtfChars.h"
|
||||
#include "jni.h"
|
||||
#include "ziparchive/zip_archive.h"
|
||||
#include "cutils/log.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
// The method ID for ZipEntry.<init>(String,String,JJJIII[BJJ)
|
||||
static jmethodID zipEntryCtor;
|
||||
|
||||
static void throwIoException(JNIEnv* env, const int32_t errorCode) {
|
||||
jniThrowException(env, "java/io/IOException", ErrorCodeString(errorCode));
|
||||
}
|
||||
|
||||
static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, jstring entryName) {
|
||||
return env->NewObject(JniConstants::zipEntryClass,
|
||||
zipEntryCtor,
|
||||
entryName,
|
||||
NULL, // comment
|
||||
static_cast<jlong>(entry.crc32),
|
||||
static_cast<jlong>(entry.compressed_length),
|
||||
static_cast<jlong>(entry.uncompressed_length),
|
||||
static_cast<jint>(entry.method),
|
||||
static_cast<jint>(0), // time
|
||||
NULL, // byte[] extra
|
||||
static_cast<jlong>(entry.offset));
|
||||
}
|
||||
|
||||
static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileName) {
|
||||
ScopedUtfChars fileChars(env, fileName);
|
||||
if (fileChars.c_str() == NULL) {
|
||||
return static_cast<jlong>(-1);
|
||||
}
|
||||
|
||||
ZipArchiveHandle handle;
|
||||
int32_t error = OpenArchive(fileChars.c_str(), &handle);
|
||||
if (error) {
|
||||
CloseArchive(handle);
|
||||
throwIoException(env, error);
|
||||
return static_cast<jlong>(-1);
|
||||
}
|
||||
|
||||
return reinterpret_cast<jlong>(handle);
|
||||
}
|
||||
|
||||
class IterationHandle {
|
||||
public:
|
||||
IterationHandle() :
|
||||
cookie_(NULL) {
|
||||
}
|
||||
|
||||
void** CookieAddress() {
|
||||
return &cookie_;
|
||||
}
|
||||
|
||||
~IterationHandle() {
|
||||
EndIteration(cookie_);
|
||||
}
|
||||
|
||||
private:
|
||||
void* cookie_;
|
||||
};
|
||||
|
||||
|
||||
static jlong StrictJarFile_nativeStartIteration(JNIEnv* env, jobject, jlong nativeHandle,
|
||||
jstring prefix) {
|
||||
ScopedUtfChars prefixChars(env, prefix);
|
||||
if (prefixChars.c_str() == NULL) {
|
||||
return static_cast<jlong>(-1);
|
||||
}
|
||||
|
||||
IterationHandle* handle = new IterationHandle();
|
||||
int32_t error = 0;
|
||||
if (prefixChars.size() == 0) {
|
||||
error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
|
||||
handle->CookieAddress(), NULL, NULL);
|
||||
} else {
|
||||
ZipString entry_name(prefixChars.c_str());
|
||||
error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
|
||||
handle->CookieAddress(), &entry_name, NULL);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throwIoException(env, error);
|
||||
return static_cast<jlong>(-1);
|
||||
}
|
||||
|
||||
return reinterpret_cast<jlong>(handle);
|
||||
}
|
||||
|
||||
static jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterationHandle) {
|
||||
ZipEntry data;
|
||||
ZipString entryName;
|
||||
|
||||
IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
|
||||
const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
|
||||
if (error) {
|
||||
delete handle;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> entryNameCString(new char[entryName.name_length + 1]);
|
||||
memcpy(entryNameCString.get(), entryName.name, entryName.name_length);
|
||||
entryNameCString[entryName.name_length] = '\0';
|
||||
ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get()));
|
||||
|
||||
return newZipEntry(env, data, entryNameString.get());
|
||||
}
|
||||
|
||||
static jobject StrictJarFile_nativeFindEntry(JNIEnv* env, jobject, jlong nativeHandle,
|
||||
jstring entryName) {
|
||||
ScopedUtfChars entryNameChars(env, entryName);
|
||||
if (entryNameChars.c_str() == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ZipEntry data;
|
||||
const int32_t error = FindEntry(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
|
||||
ZipString(entryNameChars.c_str()), &data);
|
||||
if (error) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return newZipEntry(env, data, entryName);
|
||||
}
|
||||
|
||||
static void StrictJarFile_nativeClose(JNIEnv*, jobject, jlong nativeHandle) {
|
||||
CloseArchive(reinterpret_cast<ZipArchiveHandle>(nativeHandle));
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(StrictJarFile, nativeOpenJarFile, "(Ljava/lang/String;)J"),
|
||||
NATIVE_METHOD(StrictJarFile, nativeStartIteration, "(JLjava/lang/String;)J"),
|
||||
NATIVE_METHOD(StrictJarFile, nativeNextEntry, "(J)Ljava/util/zip/ZipEntry;"),
|
||||
NATIVE_METHOD(StrictJarFile, nativeFindEntry, "(JLjava/lang/String;)Ljava/util/zip/ZipEntry;"),
|
||||
NATIVE_METHOD(StrictJarFile, nativeClose, "(J)V"),
|
||||
};
|
||||
|
||||
void register_android_util_jar_StrictJarFile(JNIEnv* env) {
|
||||
jniRegisterNativeMethods(env, "android/util/jar/StrictJarFile", gMethods, NELEM(gMethods));
|
||||
|
||||
zipEntryCtor = env->GetMethodID(JniConstants::zipEntryClass, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/String;JJJII[BJ)V");
|
||||
LOG_ALWAYS_FATAL_IF(zipEntryCtor == NULL, "Unable to find ZipEntry.<init>");
|
||||
}
|
||||
|
||||
}; // namespace android
|
||||
@@ -581,7 +581,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra
|
||||
UnsetSigChldHandler();
|
||||
|
||||
env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags,
|
||||
is_system_server ? NULL : instructionSet);
|
||||
is_system_server, instructionSet);
|
||||
if (env->ExceptionCheck()) {
|
||||
RuntimeAbort(env, __LINE__, "Error calling post fork hooks.");
|
||||
}
|
||||
@@ -674,7 +674,7 @@ static const JNINativeMethod gMethods[] = {
|
||||
int register_com_android_internal_os_Zygote(JNIEnv* env) {
|
||||
gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName));
|
||||
gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks",
|
||||
"(ILjava/lang/String;)V");
|
||||
"(IZLjava/lang/String;)V");
|
||||
|
||||
return RegisterMethodsOrDie(env, "com/android/internal/os/Zygote", gMethods, NELEM(gMethods));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public final class HexDumpTest extends TestCase {
|
||||
public void testBytesToHexString() {
|
||||
assertEquals("abcdef", HexDump.toHexString(
|
||||
new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, false));
|
||||
assertEquals("ABCDEF", HexDump.toHexString(
|
||||
new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, true));
|
||||
}
|
||||
}
|
||||
@@ -1765,6 +1765,7 @@ android.util.StateSet
|
||||
android.util.SuperNotCalledException
|
||||
android.util.TypedValue
|
||||
android.util.Xml
|
||||
android.util.jar.StrictJarFile
|
||||
android.view.AbsSavedState
|
||||
android.view.AbsSavedState$1
|
||||
android.view.AbsSavedState$2
|
||||
@@ -3361,9 +3362,6 @@ java.util.jar.Attributes$Name
|
||||
java.util.jar.JarEntry
|
||||
java.util.jar.JarFile
|
||||
java.util.jar.JarFile$JarFileEnumerator
|
||||
java.util.jar.Manifest
|
||||
java.util.jar.ManifestReader
|
||||
java.util.jar.StrictJarFile
|
||||
java.util.logging.ConsoleHandler
|
||||
java.util.logging.ErrorManager
|
||||
java.util.logging.Filter
|
||||
@@ -3560,11 +3558,6 @@ org.apache.harmony.security.asn1.BerInputStream
|
||||
org.apache.harmony.security.asn1.BerOutputStream
|
||||
org.apache.harmony.security.asn1.DerInputStream
|
||||
org.apache.harmony.security.asn1.DerOutputStream
|
||||
org.apache.harmony.security.fortress.Engine
|
||||
org.apache.harmony.security.fortress.Engine$ServiceCacheEntry
|
||||
org.apache.harmony.security.fortress.Engine$SpiAndProvider
|
||||
org.apache.harmony.security.fortress.SecurityAccess
|
||||
org.apache.harmony.security.fortress.Services
|
||||
org.apache.harmony.security.provider.crypto.CryptoProvider
|
||||
org.apache.harmony.security.utils.AlgNameMapper
|
||||
org.apache.harmony.security.utils.AlgNameMapperSource
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.server.updates;
|
||||
|
||||
import com.android.server.EventLogTags;
|
||||
import com.android.internal.util.HexDump;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -155,7 +156,7 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
|
||||
try {
|
||||
MessageDigest dgst = MessageDigest.getInstance("SHA512");
|
||||
byte[] fingerprint = dgst.digest(content);
|
||||
return IntegralToString.bytesToHexString(fingerprint, false);
|
||||
return HexDump.toHexString(fingerprint, false);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.updates;
|
||||
|
||||
import com.android.internal.util.HexDump;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.test.AndroidTestCase;
|
||||
@@ -128,7 +130,7 @@ public class CertPinInstallReceiverTest extends AndroidTestCase {
|
||||
MessageDigest dgst = MessageDigest.getInstance("SHA512");
|
||||
byte[] encoded = content.getBytes();
|
||||
byte[] fingerprint = dgst.digest(encoded);
|
||||
return IntegralToString.bytesToHexString(fingerprint, false);
|
||||
return HexDump.toHexString(fingerprint, false);
|
||||
}
|
||||
|
||||
private static String getHashOfCurrentContent() throws Exception {
|
||||
|
||||
@@ -20,7 +20,7 @@ include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := core-libart core-junit framework
|
||||
LOCAL_JAVA_LIBRARIES := core-oj core-libart core-junit framework
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := junit-runner
|
||||
|
||||
LOCAL_MODULE:= android.test.runner
|
||||
|
||||
@@ -30,6 +30,9 @@ LOCAL_JAVACFLAGS := -source 6 -target 6
|
||||
built_framework_dep := $(call java-lib-deps,framework)
|
||||
built_framework_classes := $(call java-lib-files,framework)
|
||||
|
||||
built_oj_dep := $(call java-lib-deps,core-oj)
|
||||
built_oj_classes := $(call java-lib-files,core-oj)
|
||||
|
||||
built_core_dep := $(call java-lib-deps,core-libart)
|
||||
built_core_classes := $(call java-lib-files,core-libart)
|
||||
|
||||
@@ -56,7 +59,8 @@ LOCAL_BUILT_MODULE_STEM := javalib.jar
|
||||
include $(BUILD_SYSTEM)/base_rules.mk
|
||||
#######################################
|
||||
|
||||
$(LOCAL_BUILT_MODULE): $(built_core_dep) \
|
||||
$(LOCAL_BUILT_MODULE): $(built_oj_dep) \
|
||||
$(built_core_dep) \
|
||||
$(built_framework_dep) \
|
||||
$(built_ext_dep) \
|
||||
$(built_ext_data) \
|
||||
@@ -69,6 +73,7 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \
|
||||
$(hide) ls -l $(built_framework_classes)
|
||||
$(hide) java -ea -jar $(built_layoutlib_create_jar) \
|
||||
$@ \
|
||||
$(built_oj_classes) \
|
||||
$(built_core_classes) \
|
||||
$(built_framework_classes) \
|
||||
$(built_ext_classes) \
|
||||
|
||||
Reference in New Issue
Block a user