Merge "Updated v4 signature processing." into rvc-dev am: 764e7971d8
Change-Id: Ia595b07e19eae3f9c2127caec4289b1a84a960c9
This commit is contained in:
@@ -986,7 +986,6 @@ filegroup {
|
|||||||
srcs: [
|
srcs: [
|
||||||
"core/java/android/os/incremental/IIncrementalService.aidl",
|
"core/java/android/os/incremental/IIncrementalService.aidl",
|
||||||
"core/java/android/os/incremental/IncrementalNewFileParams.aidl",
|
"core/java/android/os/incremental/IncrementalNewFileParams.aidl",
|
||||||
"core/java/android/os/incremental/IncrementalSignature.aidl",
|
|
||||||
],
|
],
|
||||||
path: "core/java",
|
path: "core/java",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package android.os.incremental;
|
package android.os.incremental;
|
||||||
|
|
||||||
import android.os.incremental.IncrementalSignature;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All the parameters to create a new file on IncFS
|
* All the parameters to create a new file on IncFS
|
||||||
* FileId is a 16 byte-long identifier.
|
* FileId is a 16 byte-long identifier.
|
||||||
@@ -27,5 +25,5 @@ parcelable IncrementalNewFileParams {
|
|||||||
long size;
|
long size;
|
||||||
byte[] fileId;
|
byte[] fileId;
|
||||||
byte[] metadata;
|
byte[] metadata;
|
||||||
@nullable IncrementalSignature signature;
|
@nullable byte[] signature;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 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.os.incremental;
|
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
parcelable IncrementalSignature {
|
|
||||||
/*
|
|
||||||
* Stable AIDL doesn't support constants, but here's the possible values
|
|
||||||
* const int HASH_ALGO_NONE = 0;
|
|
||||||
* const int HASH_ALGO_SHA256 = 1;
|
|
||||||
*/
|
|
||||||
|
|
||||||
int hashAlgorithm = 0;
|
|
||||||
byte[] rootHash;
|
|
||||||
byte[] additionalData;
|
|
||||||
byte[] signature;
|
|
||||||
}
|
|
||||||
@@ -20,8 +20,6 @@ import android.annotation.NonNull;
|
|||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -180,11 +178,12 @@ public final class IncrementalStorage {
|
|||||||
if (id == null && metadata == null) {
|
if (id == null && metadata == null) {
|
||||||
throw new IOException("File ID and metadata cannot both be null");
|
throw new IOException("File ID and metadata cannot both be null");
|
||||||
}
|
}
|
||||||
|
validateV4Signature(v4signatureBytes);
|
||||||
final IncrementalNewFileParams params = new IncrementalNewFileParams();
|
final IncrementalNewFileParams params = new IncrementalNewFileParams();
|
||||||
params.size = size;
|
params.size = size;
|
||||||
params.metadata = (metadata == null ? new byte[0] : metadata);
|
params.metadata = (metadata == null ? new byte[0] : metadata);
|
||||||
params.fileId = idToBytes(id);
|
params.fileId = idToBytes(id);
|
||||||
params.signature = parseV4Signature(v4signatureBytes);
|
params.signature = v4signatureBytes;
|
||||||
int res = mService.makeFile(mId, path, params);
|
int res = mService.makeFile(mId, path, params);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
throw new IOException("makeFile() failed with errno " + -res);
|
throw new IOException("makeFile() failed with errno " + -res);
|
||||||
@@ -415,27 +414,23 @@ public final class IncrementalStorage {
|
|||||||
return new UUID(msb, lsb);
|
return new UUID(msb, lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int INCFS_HASH_SHA256 = 1;
|
|
||||||
private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
|
private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
|
||||||
private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
|
private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize and validate v4 signature bytes.
|
* Deserialize and validate v4 signature bytes.
|
||||||
*/
|
*/
|
||||||
private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
|
private static void validateV4Signature(@Nullable byte[] v4signatureBytes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (v4signatureBytes == null || v4signatureBytes.length == 0) {
|
if (v4signatureBytes == null || v4signatureBytes.length == 0) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final V4Signature signature;
|
final V4Signature signature;
|
||||||
try (DataInputStream input = new DataInputStream(
|
try {
|
||||||
new ByteArrayInputStream(v4signatureBytes))) {
|
signature = V4Signature.readFrom(v4signatureBytes);
|
||||||
try {
|
} catch (IOException e) {
|
||||||
signature = V4Signature.readFrom(input);
|
throw new IOException("Failed to read v4 signature:", e);
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IOException("Failed to read v4 signature:", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!signature.isVersionSupported()) {
|
if (!signature.isVersionSupported()) {
|
||||||
@@ -443,25 +438,27 @@ public final class IncrementalStorage {
|
|||||||
+ " is not supported");
|
+ " is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
final byte[] rootHash = signature.verityRootHash;
|
final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
|
||||||
final byte[] additionalData = signature.v3Digest;
|
signature.hashingInfo);
|
||||||
final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;
|
final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
|
||||||
|
signature.signingInfo);
|
||||||
|
|
||||||
if (rootHash.length != INCFS_MAX_HASH_SIZE) {
|
if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
|
||||||
throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
|
throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
|
||||||
}
|
}
|
||||||
if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
|
if (hashingInfo.log2BlockSize != V4Signature.LOG2_BLOCK_SIZE_4096_BYTES) {
|
||||||
|
throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize);
|
||||||
|
}
|
||||||
|
if (hashingInfo.salt != null && hashingInfo.salt.length > 0) {
|
||||||
|
throw new IOException("Unsupported salt: " + hashingInfo.salt);
|
||||||
|
}
|
||||||
|
if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
|
||||||
|
throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
|
||||||
|
}
|
||||||
|
if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
|
"additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
IncrementalSignature result = new IncrementalSignature();
|
|
||||||
result.hashAlgorithm = INCFS_HASH_SHA256;
|
|
||||||
result.rootHash = rootHash;
|
|
||||||
result.additionalData = additionalData;
|
|
||||||
result.signature = pkcs7Signature;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,9 +20,12 @@ import android.os.ParcelFileDescriptor;
|
|||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.EOFException;
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* V4 signature fields.
|
* V4 signature fields.
|
||||||
@@ -31,30 +34,95 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class V4Signature {
|
public class V4Signature {
|
||||||
public static final String EXT = ".idsig";
|
public static final String EXT = ".idsig";
|
||||||
public static final int SUPPORTED_VERSION = 1;
|
public static final int SUPPORTED_VERSION = 2;
|
||||||
|
|
||||||
public final int version;
|
public static final int HASHING_ALGORITHM_SHA256 = 1;
|
||||||
public final byte[] verityRootHash;
|
public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
|
||||||
public final byte[] v3Digest;
|
|
||||||
public final byte[] pkcs7SignatureBlock;
|
/**
|
||||||
|
* IncFS hashing data.
|
||||||
|
*/
|
||||||
|
public static class HashingInfo {
|
||||||
|
public final int hashAlgorithm; // only 1 == SHA256 supported
|
||||||
|
public final byte log2BlockSize; // only 12 (block size 4096) supported now
|
||||||
|
public final byte[] salt; // used exactly as in fs-verity, 32 bytes max
|
||||||
|
public final byte[] rawRootHash; // salted digest of the first Merkle tree page
|
||||||
|
|
||||||
|
HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) {
|
||||||
|
this.hashAlgorithm = hashAlgorithm;
|
||||||
|
this.log2BlockSize = log2BlockSize;
|
||||||
|
this.salt = salt;
|
||||||
|
this.rawRootHash = rawRootHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs HashingInfo from byte array.
|
||||||
|
*/
|
||||||
|
public static HashingInfo fromByteArray(byte[] bytes) throws IOException {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
final int hashAlgorithm = buffer.getInt();
|
||||||
|
final byte log2BlockSize = buffer.get();
|
||||||
|
byte[] salt = readBytes(buffer);
|
||||||
|
byte[] rawRootHash = readBytes(buffer);
|
||||||
|
return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V4 signature data.
|
||||||
|
*/
|
||||||
|
public static class SigningInfo {
|
||||||
|
public final byte[] v3Digest; // used to match with the corresponding APK
|
||||||
|
public final byte[] certificate; // ASN.1 DER form
|
||||||
|
public final byte[] additionalData; // a free-form binary data blob
|
||||||
|
public final byte[] publicKey; // ASN.1 DER, must match the certificate
|
||||||
|
public final int signatureAlgorithmId; // see the APK v2 doc for the list
|
||||||
|
public final byte[] signature;
|
||||||
|
|
||||||
|
SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData,
|
||||||
|
byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
|
||||||
|
this.v3Digest = v3Digest;
|
||||||
|
this.certificate = certificate;
|
||||||
|
this.additionalData = additionalData;
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.signatureAlgorithmId = signatureAlgorithmId;
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs SigningInfo from byte array.
|
||||||
|
*/
|
||||||
|
public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
byte[] v3Digest = readBytes(buffer);
|
||||||
|
byte[] certificate = readBytes(buffer);
|
||||||
|
byte[] additionalData = readBytes(buffer);
|
||||||
|
byte[] publicKey = readBytes(buffer);
|
||||||
|
int signatureAlgorithmId = buffer.getInt();
|
||||||
|
byte[] signature = readBytes(buffer);
|
||||||
|
return new SigningInfo(v3Digest, certificate, additionalData, publicKey,
|
||||||
|
signatureAlgorithmId, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int version; // Always 2 for now.
|
||||||
|
public final byte[] hashingInfo;
|
||||||
|
public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a V4Signature from .idsig file.
|
* Construct a V4Signature from .idsig file.
|
||||||
*/
|
*/
|
||||||
public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
|
public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
|
||||||
final ParcelFileDescriptor dupedFd = pfd.dup();
|
try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) {
|
||||||
final ParcelFileDescriptor.AutoCloseInputStream fdInputStream =
|
|
||||||
new ParcelFileDescriptor.AutoCloseInputStream(dupedFd);
|
|
||||||
try (DataInputStream stream = new DataInputStream(fdInputStream)) {
|
|
||||||
return readFrom(stream);
|
return readFrom(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a V4Signature from .idsig file.
|
* Construct a V4Signature from a byte array.
|
||||||
*/
|
*/
|
||||||
public static V4Signature readFrom(byte[] bytes) throws IOException {
|
public static V4Signature readFrom(byte[] bytes) throws IOException {
|
||||||
try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) {
|
try (InputStream stream = new ByteArrayInputStream(bytes)) {
|
||||||
return readFrom(stream);
|
return readFrom(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,51 +131,131 @@ public class V4Signature {
|
|||||||
* Store the V4Signature to a byte-array.
|
* Store the V4Signature to a byte-array.
|
||||||
*/
|
*/
|
||||||
public byte[] toByteArray() {
|
public byte[] toByteArray() {
|
||||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
|
||||||
try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) {
|
this.writeTo(stream);
|
||||||
this.writeTo(steam);
|
return stream.toByteArray();
|
||||||
steam.flush();
|
|
||||||
}
|
|
||||||
return byteArrayOutputStream.toByteArray();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isVersionSupported() {
|
/**
|
||||||
|
* Combines necessary data to a signed data blob.
|
||||||
|
* The blob can be validated against signingInfo.signature.
|
||||||
|
*
|
||||||
|
* @param fileSize - size of the signed file (APK)
|
||||||
|
*/
|
||||||
|
public static byte[] getSigningData(long fileSize, HashingInfo hashingInfo,
|
||||||
|
SigningInfo signingInfo) {
|
||||||
|
final int size =
|
||||||
|
4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize(
|
||||||
|
hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize(
|
||||||
|
signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize(
|
||||||
|
signingInfo.additionalData);
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.putInt(size);
|
||||||
|
buffer.putLong(fileSize);
|
||||||
|
buffer.putInt(hashingInfo.hashAlgorithm);
|
||||||
|
buffer.put(hashingInfo.log2BlockSize);
|
||||||
|
writeBytes(buffer, hashingInfo.salt);
|
||||||
|
writeBytes(buffer, hashingInfo.rawRootHash);
|
||||||
|
writeBytes(buffer, signingInfo.v3Digest);
|
||||||
|
writeBytes(buffer, signingInfo.certificate);
|
||||||
|
writeBytes(buffer, signingInfo.additionalData);
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVersionSupported() {
|
||||||
return this.version == SUPPORTED_VERSION;
|
return this.version == SUPPORTED_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
static V4Signature readFrom(DataInputStream stream) throws IOException {
|
private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) {
|
||||||
final int version = stream.readInt();
|
|
||||||
byte[] verityRootHash = readBytes(stream);
|
|
||||||
byte[] v3Digest = readBytes(stream);
|
|
||||||
byte[] pkcs7SignatureBlock = readBytes(stream);
|
|
||||||
return new V4Signature(version, verityRootHash, v3Digest, pkcs7SignatureBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
V4Signature(int version, byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) {
|
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.verityRootHash = verityRootHash;
|
this.hashingInfo = hashingInfo;
|
||||||
this.v3Digest = v3Digest;
|
this.signingInfo = signingInfo;
|
||||||
this.pkcs7SignatureBlock = pkcs7SignatureBlock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeTo(DataOutputStream stream) throws IOException {
|
private static V4Signature readFrom(InputStream stream) throws IOException {
|
||||||
stream.writeInt(this.version);
|
final int version = readIntLE(stream);
|
||||||
writeBytes(stream, this.verityRootHash);
|
final byte[] hashingInfo = readBytes(stream);
|
||||||
writeBytes(stream, this.v3Digest);
|
final byte[] signingInfo = readBytes(stream);
|
||||||
writeBytes(stream, this.pkcs7SignatureBlock);
|
return new V4Signature(version, hashingInfo, signingInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] readBytes(DataInputStream stream) throws IOException {
|
private void writeTo(OutputStream stream) throws IOException {
|
||||||
byte[] result = new byte[stream.readInt()];
|
writeIntLE(stream, this.version);
|
||||||
stream.read(result);
|
writeBytes(stream, this.hashingInfo);
|
||||||
return result;
|
writeBytes(stream, this.signingInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException {
|
// Utility methods.
|
||||||
stream.writeInt(bytes.length);
|
private static int bytesSize(byte[] bytes) {
|
||||||
|
return 4/*length*/ + (bytes == null ? 0 : bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readFully(InputStream stream, byte[] buffer) throws IOException {
|
||||||
|
int len = buffer.length;
|
||||||
|
int n = 0;
|
||||||
|
while (n < len) {
|
||||||
|
int count = stream.read(buffer, n, len - n);
|
||||||
|
if (count < 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
n += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readIntLE(InputStream stream) throws IOException {
|
||||||
|
final byte[] buffer = new byte[4];
|
||||||
|
readFully(stream, buffer);
|
||||||
|
return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeIntLE(OutputStream stream, int v) throws IOException {
|
||||||
|
final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(
|
||||||
|
v).array();
|
||||||
|
stream.write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readBytes(InputStream stream) throws IOException {
|
||||||
|
try {
|
||||||
|
final int size = readIntLE(stream);
|
||||||
|
final byte[] bytes = new byte[size];
|
||||||
|
readFully(stream, bytes);
|
||||||
|
return bytes;
|
||||||
|
} catch (EOFException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readBytes(ByteBuffer buffer) throws IOException {
|
||||||
|
if (buffer.remaining() < 4) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
final int size = buffer.getInt();
|
||||||
|
if (buffer.remaining() < size) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
final byte[] bytes = new byte[size];
|
||||||
|
buffer.get(bytes);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeBytes(OutputStream stream, byte[] bytes) throws IOException {
|
||||||
|
if (bytes == null) {
|
||||||
|
writeIntLE(stream, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writeIntLE(stream, bytes.length);
|
||||||
stream.write(bytes);
|
stream.write(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void writeBytes(ByteBuffer buffer, byte[] bytes) {
|
||||||
|
if (bytes == null) {
|
||||||
|
buffer.putInt(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer.putInt(bytes.length);
|
||||||
|
buffer.put(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package android.util.apk;
|
package android.util.apk;
|
||||||
|
|
||||||
|
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
|
||||||
|
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
|
||||||
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
|
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
|
||||||
import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
|
import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
|
||||||
import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
|
import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
|
||||||
@@ -211,6 +213,12 @@ public class ApkSignatureSchemeV3Verifier {
|
|||||||
verityDigest, apk.length(), signatureInfo);
|
verityDigest, apk.length(), signatureInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) {
|
||||||
|
result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA512);
|
||||||
|
} else if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) {
|
||||||
|
result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA256);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,6 +576,7 @@ public class ApkSignatureSchemeV3Verifier {
|
|||||||
public final VerifiedProofOfRotation por;
|
public final VerifiedProofOfRotation por;
|
||||||
|
|
||||||
public byte[] verityRootHash;
|
public byte[] verityRootHash;
|
||||||
|
public byte[] digest;
|
||||||
|
|
||||||
public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
|
public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
|
||||||
this.certs = certs;
|
this.certs = certs;
|
||||||
|
|||||||
@@ -16,13 +16,32 @@
|
|||||||
|
|
||||||
package android.util.apk;
|
package android.util.apk;
|
||||||
|
|
||||||
|
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
|
||||||
|
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
|
||||||
|
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
|
||||||
|
|
||||||
import android.os.incremental.IncrementalManager;
|
import android.os.incremental.IncrementalManager;
|
||||||
|
import android.os.incremental.V4Signature;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.SignatureException;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import sun.security.pkcs.PKCS7;
|
import java.security.cert.CertificateFactory;
|
||||||
import sun.security.pkcs.ParsingException;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APK Signature Scheme v4 verifier.
|
* APK Signature Scheme v4 verifier.
|
||||||
@@ -30,24 +49,118 @@ import sun.security.pkcs.ParsingException;
|
|||||||
* @hide for internal use only.
|
* @hide for internal use only.
|
||||||
*/
|
*/
|
||||||
public class ApkSignatureSchemeV4Verifier {
|
public class ApkSignatureSchemeV4Verifier {
|
||||||
|
/**
|
||||||
|
* Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the
|
||||||
|
* certificates associated with each signer.
|
||||||
|
*/
|
||||||
|
public static VerifiedSigner extractCertificates(String apkFile)
|
||||||
|
throws SignatureNotFoundException, SecurityException {
|
||||||
|
final File apk = new File(apkFile);
|
||||||
|
final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
|
||||||
|
apk.getAbsolutePath());
|
||||||
|
if (signatureBytes == null || signatureBytes.length == 0) {
|
||||||
|
throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final V4Signature signature;
|
||||||
|
final V4Signature.HashingInfo hashingInfo;
|
||||||
|
final V4Signature.SigningInfo signingInfo;
|
||||||
|
try {
|
||||||
|
signature = V4Signature.readFrom(signatureBytes);
|
||||||
|
|
||||||
|
if (!signature.isVersionSupported()) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"v4 signature version " + signature.version + " is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo);
|
||||||
|
signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SignatureNotFoundException("Failed to read V4 signature.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] signedData = V4Signature.getSigningData(apk.length(), hashingInfo,
|
||||||
|
signingInfo);
|
||||||
|
|
||||||
|
return verifySigner(signingInfo, signedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VerifiedSigner verifySigner(V4Signature.SigningInfo signingInfo,
|
||||||
|
final byte[] signedData) throws SecurityException {
|
||||||
|
if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
|
||||||
|
throw new SecurityException("No supported signatures found");
|
||||||
|
}
|
||||||
|
|
||||||
|
final int signatureAlgorithmId = signingInfo.signatureAlgorithmId;
|
||||||
|
final byte[] signatureBytes = signingInfo.signature;
|
||||||
|
final byte[] publicKeyBytes = signingInfo.publicKey;
|
||||||
|
final byte[] encodedCert = signingInfo.certificate;
|
||||||
|
|
||||||
|
String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId);
|
||||||
|
Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
|
||||||
|
getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId);
|
||||||
|
String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
|
||||||
|
AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
|
||||||
|
boolean sigVerified;
|
||||||
|
try {
|
||||||
|
PublicKey publicKey =
|
||||||
|
KeyFactory.getInstance(keyAlgorithm)
|
||||||
|
.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||||
|
Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
|
||||||
|
sig.initVerify(publicKey);
|
||||||
|
if (jcaSignatureAlgorithmParams != null) {
|
||||||
|
sig.setParameter(jcaSignatureAlgorithmParams);
|
||||||
|
}
|
||||||
|
sig.update(signedData);
|
||||||
|
sigVerified = sig.verify(signatureBytes);
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
|
||||||
|
| InvalidAlgorithmParameterException | SignatureException e) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Failed to verify " + jcaSignatureAlgorithm + " signature", e);
|
||||||
|
}
|
||||||
|
if (!sigVerified) {
|
||||||
|
throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature over signedData has verified.
|
||||||
|
CertificateFactory certFactory;
|
||||||
|
try {
|
||||||
|
certFactory = CertificateFactory.getInstance("X.509");
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
X509Certificate certificate;
|
||||||
|
try {
|
||||||
|
certificate = (X509Certificate)
|
||||||
|
certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
throw new SecurityException("Failed to decode certificate", e);
|
||||||
|
}
|
||||||
|
certificate = new VerbatimX509Certificate(certificate, encodedCert);
|
||||||
|
|
||||||
|
byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded();
|
||||||
|
if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Public key mismatch between certificate and signature record");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts APK Signature Scheme v4 signatures of the provided APK and returns the certificates
|
* Verified APK Signature Scheme v4 signer, including V3 digest.
|
||||||
* associated with each signer.
|
*
|
||||||
|
* @hide for internal use only.
|
||||||
*/
|
*/
|
||||||
public static Certificate[] extractCertificates(String apkFile)
|
public static class VerifiedSigner {
|
||||||
throws SignatureNotFoundException, SecurityException {
|
public final Certificate[] certs;
|
||||||
final byte[] rawSignature = IncrementalManager.unsafeGetFileSignature(
|
public byte[] v3Digest;
|
||||||
new File(apkFile).getAbsolutePath());
|
|
||||||
if (rawSignature == null || rawSignature.length == 0) {
|
public VerifiedSigner(Certificate[] certs, byte[] v3Digest) {
|
||||||
throw new SignatureNotFoundException("Failed to obtain raw signature from IncFS.");
|
this.certs = certs;
|
||||||
|
this.v3Digest = v3Digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
PKCS7 pkcs7 = new PKCS7(rawSignature);
|
|
||||||
return pkcs7.getCertificates();
|
|
||||||
} catch (ParsingException e) {
|
|
||||||
throw new SecurityException("Failed to parse signature and extract certificates", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ public class ApkSignatureVerifier {
|
|||||||
/**
|
/**
|
||||||
* Verifies the provided APK using V4 schema.
|
* Verifies the provided APK using V4 schema.
|
||||||
*
|
*
|
||||||
* @param verifyFull whether to verify all contents of this APK or just collect certificates.
|
* @param verifyFull whether to verify (V4 vs V3) or just collect certificates.
|
||||||
* @return the certificates associated with each signer.
|
* @return the certificates associated with each signer.
|
||||||
* @throws SignatureNotFoundException if there are no V4 signatures in the APK
|
* @throws SignatureNotFoundException if there are no V4 signatures in the APK
|
||||||
* @throws PackageParserException if there was a problem collecting certificates
|
* @throws PackageParserException if there was a problem collecting certificates
|
||||||
@@ -178,30 +178,34 @@ public class ApkSignatureVerifier {
|
|||||||
throws SignatureNotFoundException, PackageParserException {
|
throws SignatureNotFoundException, PackageParserException {
|
||||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
|
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
|
||||||
try {
|
try {
|
||||||
Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
|
ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
|
||||||
Certificate[][] signerCerts = new Certificate[][]{certs};
|
ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
|
||||||
|
Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
|
||||||
Signature[] signerSigs = convertToSignatures(signerCerts);
|
Signature[] signerSigs = convertToSignatures(signerCerts);
|
||||||
|
|
||||||
if (verifyFull) {
|
if (verifyFull) {
|
||||||
// v4 is an add-on and requires v2/v3 signature to validate against its certificates
|
// v4 is an add-on and requires v3 signature to validate against its certificates
|
||||||
final PackageParser.SigningDetails nonstreaming = verifyV3AndBelowSignatures(
|
ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming =
|
||||||
apkPath, minSignatureSchemeVersion, false);
|
ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
|
||||||
if (nonstreaming.signatureSchemeVersion <= SignatureSchemeVersion.JAR) {
|
Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs};
|
||||||
|
Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
|
||||||
|
|
||||||
|
if (nonstreamingSigs.length != signerSigs.length) {
|
||||||
throw new SecurityException(
|
throw new SecurityException(
|
||||||
"V4 signing block can only be verified along with V2 and above.");
|
"Invalid number of certificates: " + nonstreaming.certs.length);
|
||||||
}
|
|
||||||
if (nonstreaming.signatures.length == 0
|
|
||||||
|| nonstreaming.signatures.length != signerSigs.length) {
|
|
||||||
throw new SecurityException("Invalid number of signatures in "
|
|
||||||
+ nonstreaming.signatureSchemeVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0, size = signerSigs.length; i < size; ++i) {
|
for (int i = 0, size = signerSigs.length; i < size; ++i) {
|
||||||
if (!nonstreaming.signatures[i].equals(signerSigs[i])) {
|
if (!nonstreamingSigs[i].equals(signerSigs[i])) {
|
||||||
throw new SecurityException("V4 signature certificate does not match "
|
throw new SecurityException("V4 signature certificate does not match V3");
|
||||||
+ nonstreaming.signatureSchemeVersion);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/151240006): add support for v2 digest and make it mandatory.
|
||||||
|
if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest,
|
||||||
|
nonstreaming.digest, vSigner.v3Digest.length)) {
|
||||||
|
throw new SecurityException("V3 digest in V4 signature does not match V3");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PackageParser.SigningDetails(signerSigs,
|
return new PackageParser.SigningDetails(signerSigs,
|
||||||
|
|||||||
@@ -172,26 +172,25 @@ static bool readChunk(int fd, std::vector<uint8_t>& data) {
|
|||||||
|
|
||||||
BlockHeader readHeader(std::span<uint8_t>& data);
|
BlockHeader readHeader(std::span<uint8_t>& data);
|
||||||
|
|
||||||
static inline int32_t readBEInt32(borrowed_fd fd) {
|
static inline int32_t readLEInt32(borrowed_fd fd) {
|
||||||
int32_t result;
|
int32_t result;
|
||||||
ReadFully(fd, &result, sizeof(result));
|
ReadFully(fd, &result, sizeof(result));
|
||||||
result = int32_t(be32toh(result));
|
result = int32_t(le32toh(result));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline std::vector<char> readBytes(borrowed_fd fd) {
|
static inline std::vector<char> readBytes(borrowed_fd fd) {
|
||||||
int32_t size = readBEInt32(fd);
|
int32_t size = readLEInt32(fd);
|
||||||
std::vector<char> result(size);
|
std::vector<char> result(size);
|
||||||
ReadFully(fd, result.data(), size);
|
ReadFully(fd, result.data(), size);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int32_t skipIdSigHeaders(borrowed_fd fd) {
|
static inline int32_t skipIdSigHeaders(borrowed_fd fd) {
|
||||||
readBEInt32(fd); // version
|
readLEInt32(fd); // version
|
||||||
readBytes(fd); // verityRootHash
|
readBytes(fd); // hashingInfo
|
||||||
readBytes(fd); // v3Digest
|
readBytes(fd); // signingInfo
|
||||||
readBytes(fd); // pkcs7SignatureBlock
|
return readLEInt32(fd); // size of the verity tree
|
||||||
return readBEInt32(fd); // size of the verity tree
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) {
|
static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) {
|
||||||
|
|||||||
@@ -178,15 +178,9 @@ static std::tuple<int, incfs::FileId, incfs::NewFileParams> toMakeFileParams(
|
|||||||
nfp.size = params.size;
|
nfp.size = params.size;
|
||||||
nfp.metadata = {(const char*)params.metadata.data(), (IncFsSize)params.metadata.size()};
|
nfp.metadata = {(const char*)params.metadata.data(), (IncFsSize)params.metadata.size()};
|
||||||
if (!params.signature) {
|
if (!params.signature) {
|
||||||
nfp.verification = {};
|
nfp.signature = {};
|
||||||
} else {
|
} else {
|
||||||
nfp.verification.hashAlgorithm = IncFsHashAlgortithm(params.signature->hashAlgorithm);
|
nfp.signature = {(const char*)params.signature->data(), (IncFsSize)params.signature->size()};
|
||||||
nfp.verification.rootHash = {(const char*)params.signature->rootHash.data(),
|
|
||||||
(IncFsSize)params.signature->rootHash.size()};
|
|
||||||
nfp.verification.additionalData = {(const char*)params.signature->additionalData.data(),
|
|
||||||
(IncFsSize)params.signature->additionalData.size()};
|
|
||||||
nfp.verification.signature = {(const char*)params.signature->signature.data(),
|
|
||||||
(IncFsSize)params.signature->signature.size()};
|
|
||||||
}
|
}
|
||||||
return {0, id, nfp};
|
return {0, id, nfp};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1155,7 +1155,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_
|
|||||||
// Create new lib file without signature info
|
// Create new lib file without signature info
|
||||||
incfs::NewFileParams libFileParams{};
|
incfs::NewFileParams libFileParams{};
|
||||||
libFileParams.size = uncompressedLen;
|
libFileParams.size = uncompressedLen;
|
||||||
libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE;
|
libFileParams.signature = {};
|
||||||
// Metadata of the new lib file is its relative path
|
// Metadata of the new lib file is its relative path
|
||||||
IncFsSpan libFileMetadata;
|
IncFsSpan libFileMetadata;
|
||||||
libFileMetadata.data = targetLibPath.c_str();
|
libFileMetadata.data = targetLibPath.c_str();
|
||||||
|
|||||||
Reference in New Issue
Block a user