Updated v4 signature processing.
Passing to libincfs.so. Obtaining and verifying, including v3 digest check. go/apk-v4-signature-format Test: atest PackageManagerShellCommandTest Bug: b/151241461 Change-Id: Id61f5716b9f9b55d6ab1ebca5a7ecb1c6e54570a
This commit is contained in:
@@ -973,7 +973,6 @@ filegroup {
|
||||
srcs: [
|
||||
"core/java/android/os/incremental/IIncrementalService.aidl",
|
||||
"core/java/android/os/incremental/IncrementalNewFileParams.aidl",
|
||||
"core/java/android/os/incremental/IncrementalSignature.aidl",
|
||||
],
|
||||
path: "core/java",
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package android.os.incremental;
|
||||
|
||||
import android.os.incremental.IncrementalSignature;
|
||||
|
||||
/**
|
||||
* All the parameters to create a new file on IncFS
|
||||
* FileId is a 16 byte-long identifier.
|
||||
@@ -27,5 +25,5 @@ parcelable IncrementalNewFileParams {
|
||||
long size;
|
||||
byte[] fileId;
|
||||
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.os.RemoteException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -180,11 +178,12 @@ public final class IncrementalStorage {
|
||||
if (id == null && metadata == null) {
|
||||
throw new IOException("File ID and metadata cannot both be null");
|
||||
}
|
||||
validateV4Signature(v4signatureBytes);
|
||||
final IncrementalNewFileParams params = new IncrementalNewFileParams();
|
||||
params.size = size;
|
||||
params.metadata = (metadata == null ? new byte[0] : metadata);
|
||||
params.fileId = idToBytes(id);
|
||||
params.signature = parseV4Signature(v4signatureBytes);
|
||||
params.signature = v4signatureBytes;
|
||||
int res = mService.makeFile(mId, path, params);
|
||||
if (res != 0) {
|
||||
throw new IOException("makeFile() failed with errno " + -res);
|
||||
@@ -415,27 +414,23 @@ public final class IncrementalStorage {
|
||||
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_ADD_DATA_SIZE = 128;
|
||||
|
||||
/**
|
||||
* Deserialize and validate v4 signature bytes.
|
||||
*/
|
||||
private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
|
||||
private static void validateV4Signature(@Nullable byte[] v4signatureBytes)
|
||||
throws IOException {
|
||||
if (v4signatureBytes == null || v4signatureBytes.length == 0) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
final V4Signature signature;
|
||||
try (DataInputStream input = new DataInputStream(
|
||||
new ByteArrayInputStream(v4signatureBytes))) {
|
||||
try {
|
||||
signature = V4Signature.readFrom(input);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Failed to read v4 signature:", e);
|
||||
}
|
||||
try {
|
||||
signature = V4Signature.readFrom(v4signatureBytes);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Failed to read v4 signature:", e);
|
||||
}
|
||||
|
||||
if (!signature.isVersionSupported()) {
|
||||
@@ -443,25 +438,27 @@ public final class IncrementalStorage {
|
||||
+ " is not supported");
|
||||
}
|
||||
|
||||
final byte[] rootHash = signature.verityRootHash;
|
||||
final byte[] additionalData = signature.v3Digest;
|
||||
final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;
|
||||
final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
|
||||
signature.hashingInfo);
|
||||
final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
|
||||
signature.signingInfo);
|
||||
|
||||
if (rootHash.length != INCFS_MAX_HASH_SIZE) {
|
||||
throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
|
||||
if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
|
||||
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(
|
||||
"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.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* V4 signature fields.
|
||||
@@ -31,30 +34,95 @@ import java.io.IOException;
|
||||
*/
|
||||
public class V4Signature {
|
||||
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 final byte[] verityRootHash;
|
||||
public final byte[] v3Digest;
|
||||
public final byte[] pkcs7SignatureBlock;
|
||||
public static final int HASHING_ALGORITHM_SHA256 = 1;
|
||||
public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
|
||||
final ParcelFileDescriptor dupedFd = pfd.dup();
|
||||
final ParcelFileDescriptor.AutoCloseInputStream fdInputStream =
|
||||
new ParcelFileDescriptor.AutoCloseInputStream(dupedFd);
|
||||
try (DataInputStream stream = new DataInputStream(fdInputStream)) {
|
||||
try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) {
|
||||
return readFrom(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a V4Signature from .idsig file.
|
||||
* Construct a V4Signature from a byte array.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -63,51 +131,131 @@ public class V4Signature {
|
||||
* Store the V4Signature to a byte-array.
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||
try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) {
|
||||
this.writeTo(steam);
|
||||
steam.flush();
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
|
||||
this.writeTo(stream);
|
||||
return stream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
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;
|
||||
}
|
||||
|
||||
static V4Signature readFrom(DataInputStream stream) throws IOException {
|
||||
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) {
|
||||
private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) {
|
||||
this.version = version;
|
||||
this.verityRootHash = verityRootHash;
|
||||
this.v3Digest = v3Digest;
|
||||
this.pkcs7SignatureBlock = pkcs7SignatureBlock;
|
||||
this.hashingInfo = hashingInfo;
|
||||
this.signingInfo = signingInfo;
|
||||
}
|
||||
|
||||
void writeTo(DataOutputStream stream) throws IOException {
|
||||
stream.writeInt(this.version);
|
||||
writeBytes(stream, this.verityRootHash);
|
||||
writeBytes(stream, this.v3Digest);
|
||||
writeBytes(stream, this.pkcs7SignatureBlock);
|
||||
private static V4Signature readFrom(InputStream stream) throws IOException {
|
||||
final int version = readIntLE(stream);
|
||||
final byte[] hashingInfo = readBytes(stream);
|
||||
final byte[] signingInfo = readBytes(stream);
|
||||
return new V4Signature(version, hashingInfo, signingInfo);
|
||||
}
|
||||
|
||||
private static byte[] readBytes(DataInputStream stream) throws IOException {
|
||||
byte[] result = new byte[stream.readInt()];
|
||||
stream.read(result);
|
||||
return result;
|
||||
private void writeTo(OutputStream stream) throws IOException {
|
||||
writeIntLE(stream, this.version);
|
||||
writeBytes(stream, this.hashingInfo);
|
||||
writeBytes(stream, this.signingInfo);
|
||||
}
|
||||
|
||||
private static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException {
|
||||
stream.writeInt(bytes.length);
|
||||
// Utility methods.
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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.compareSignatureAlgorithm;
|
||||
import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
|
||||
@@ -211,6 +213,12 @@ public class ApkSignatureSchemeV3Verifier {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -568,6 +576,7 @@ public class ApkSignatureSchemeV3Verifier {
|
||||
public final VerifiedProofOfRotation por;
|
||||
|
||||
public byte[] verityRootHash;
|
||||
public byte[] digest;
|
||||
|
||||
public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
|
||||
this.certs = certs;
|
||||
|
||||
@@ -16,13 +16,32 @@
|
||||
|
||||
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.V4Signature;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
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 sun.security.pkcs.PKCS7;
|
||||
import sun.security.pkcs.ParsingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
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.
|
||||
@@ -30,24 +49,118 @@ import sun.security.pkcs.ParsingException;
|
||||
* @hide for internal use only.
|
||||
*/
|
||||
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
|
||||
* associated with each signer.
|
||||
* Verified APK Signature Scheme v4 signer, including V3 digest.
|
||||
*
|
||||
* @hide for internal use only.
|
||||
*/
|
||||
public static Certificate[] extractCertificates(String apkFile)
|
||||
throws SignatureNotFoundException, SecurityException {
|
||||
final byte[] rawSignature = IncrementalManager.unsafeGetFileSignature(
|
||||
new File(apkFile).getAbsolutePath());
|
||||
if (rawSignature == null || rawSignature.length == 0) {
|
||||
throw new SignatureNotFoundException("Failed to obtain raw signature from IncFS.");
|
||||
public static class VerifiedSigner {
|
||||
public final Certificate[] certs;
|
||||
public byte[] v3Digest;
|
||||
|
||||
public VerifiedSigner(Certificate[] certs, byte[] v3Digest) {
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
* @throws SignatureNotFoundException if there are no V4 signatures in the APK
|
||||
* @throws PackageParserException if there was a problem collecting certificates
|
||||
@@ -178,30 +178,34 @@ public class ApkSignatureVerifier {
|
||||
throws SignatureNotFoundException, PackageParserException {
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
|
||||
try {
|
||||
Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
|
||||
Certificate[][] signerCerts = new Certificate[][]{certs};
|
||||
ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
|
||||
ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
|
||||
Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
|
||||
Signature[] signerSigs = convertToSignatures(signerCerts);
|
||||
|
||||
if (verifyFull) {
|
||||
// v4 is an add-on and requires v2/v3 signature to validate against its certificates
|
||||
final PackageParser.SigningDetails nonstreaming = verifyV3AndBelowSignatures(
|
||||
apkPath, minSignatureSchemeVersion, false);
|
||||
if (nonstreaming.signatureSchemeVersion <= SignatureSchemeVersion.JAR) {
|
||||
// v4 is an add-on and requires v3 signature to validate against its certificates
|
||||
ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming =
|
||||
ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
|
||||
Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs};
|
||||
Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
|
||||
|
||||
if (nonstreamingSigs.length != signerSigs.length) {
|
||||
throw new SecurityException(
|
||||
"V4 signing block can only be verified along with V2 and above.");
|
||||
}
|
||||
if (nonstreaming.signatures.length == 0
|
||||
|| nonstreaming.signatures.length != signerSigs.length) {
|
||||
throw new SecurityException("Invalid number of signatures in "
|
||||
+ nonstreaming.signatureSchemeVersion);
|
||||
"Invalid number of certificates: " + nonstreaming.certs.length);
|
||||
}
|
||||
|
||||
for (int i = 0, size = signerSigs.length; i < size; ++i) {
|
||||
if (!nonstreaming.signatures[i].equals(signerSigs[i])) {
|
||||
throw new SecurityException("V4 signature certificate does not match "
|
||||
+ nonstreaming.signatureSchemeVersion);
|
||||
if (!nonstreamingSigs[i].equals(signerSigs[i])) {
|
||||
throw new SecurityException("V4 signature certificate does not match V3");
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -172,26 +172,25 @@ static bool readChunk(int fd, std::vector<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;
|
||||
ReadFully(fd, &result, sizeof(result));
|
||||
result = int32_t(be32toh(result));
|
||||
result = int32_t(le32toh(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline std::vector<char> readBytes(borrowed_fd fd) {
|
||||
int32_t size = readBEInt32(fd);
|
||||
int32_t size = readLEInt32(fd);
|
||||
std::vector<char> result(size);
|
||||
ReadFully(fd, result.data(), size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline int32_t skipIdSigHeaders(borrowed_fd fd) {
|
||||
readBEInt32(fd); // version
|
||||
readBytes(fd); // verityRootHash
|
||||
readBytes(fd); // v3Digest
|
||||
readBytes(fd); // pkcs7SignatureBlock
|
||||
return readBEInt32(fd); // size of the verity tree
|
||||
readLEInt32(fd); // version
|
||||
readBytes(fd); // hashingInfo
|
||||
readBytes(fd); // signingInfo
|
||||
return readLEInt32(fd); // size of the verity tree
|
||||
}
|
||||
|
||||
static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) {
|
||||
|
||||
@@ -178,15 +178,9 @@ static std::tuple<int, incfs::FileId, incfs::NewFileParams> toMakeFileParams(
|
||||
nfp.size = params.size;
|
||||
nfp.metadata = {(const char*)params.metadata.data(), (IncFsSize)params.metadata.size()};
|
||||
if (!params.signature) {
|
||||
nfp.verification = {};
|
||||
nfp.signature = {};
|
||||
} else {
|
||||
nfp.verification.hashAlgorithm = IncFsHashAlgortithm(params.signature->hashAlgorithm);
|
||||
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()};
|
||||
nfp.signature = {(const char*)params.signature->data(), (IncFsSize)params.signature->size()};
|
||||
}
|
||||
return {0, id, nfp};
|
||||
}
|
||||
|
||||
@@ -1155,7 +1155,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_
|
||||
// Create new lib file without signature info
|
||||
incfs::NewFileParams libFileParams{};
|
||||
libFileParams.size = uncompressedLen;
|
||||
libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE;
|
||||
libFileParams.signature = {};
|
||||
// Metadata of the new lib file is its relative path
|
||||
IncFsSpan libFileMetadata;
|
||||
libFileMetadata.data = targetLibPath.c_str();
|
||||
|
||||
Reference in New Issue
Block a user