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:
Narayan Kamath
2015-12-24 04:07:42 -08:00
committed by android-build-merger
24 changed files with 5698 additions and 1777 deletions

View File

@@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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 {
}
};
}
}

View File

@@ -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

View File

@@ -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(':');
}

View File

@@ -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 {

View File

@@ -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) {

View 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);
}

View 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);
}
}
}

View 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());
}
}

View 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();
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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 \

View File

@@ -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),
};
/*

View 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

View File

@@ -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));
}

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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) \