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:
Alex Buynytskyy
2020-03-13 13:31:12 -07:00
parent 5bb02f0890
commit f5e605a00c
11 changed files with 385 additions and 155 deletions

View File

@@ -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",
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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