Merge "V4 uses V2 digest if V3 is not available." into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-03-25 01:36:01 +00:00
committed by Android (Google) Code Review
6 changed files with 74 additions and 42 deletions

View File

@@ -72,16 +72,16 @@ public class V4Signature {
* V4 signature data.
*/
public static class SigningInfo {
public final byte[] v3Digest; // used to match with the corresponding APK
public final byte[] apkDigest; // 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,
SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData,
byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
this.v3Digest = v3Digest;
this.apkDigest = apkDigest;
this.certificate = certificate;
this.additionalData = additionalData;
this.publicKey = publicKey;
@@ -94,13 +94,13 @@ public class V4Signature {
*/
public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
byte[] v3Digest = readBytes(buffer);
byte[] apkDigest = 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,
return new SigningInfo(apkDigest, certificate, additionalData, publicKey,
signatureAlgorithmId, signature);
}
}
@@ -150,7 +150,7 @@ public class V4Signature {
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.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize(
signingInfo.additionalData);
ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(size);
@@ -159,7 +159,7 @@ public class V4Signature {
buffer.put(hashingInfo.log2BlockSize);
writeBytes(buffer, hashingInfo.salt);
writeBytes(buffer, hashingInfo.rawRootHash);
writeBytes(buffer, signingInfo.v3Digest);
writeBytes(buffer, signingInfo.apkDigest);
writeBytes(buffer, signingInfo.certificate);
writeBytes(buffer, signingInfo.additionalData);
return buffer.array();

View File

@@ -24,6 +24,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
import android.util.ArrayMap;
@@ -117,7 +118,10 @@ public class ApkSignatureSchemeV2Verifier {
return vSigner.certs;
}
private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
/**
* Same as above returns the full signer object, containing additional info e.g. digest.
*/
public static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
throws SignatureNotFoundException, SecurityException, IOException {
try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
return verify(apk, verifyIntegrity);
@@ -209,9 +213,11 @@ public class ApkSignatureSchemeV2Verifier {
verityDigest, apk.length(), signatureInfo);
}
byte[] digest = pickBestDigestForV4(contentDigests);
return new VerifiedSigner(
signerCerts.toArray(new X509Certificate[signerCerts.size()][]),
verityRootHash);
verityRootHash, digest);
}
private static X509Certificate[] verifySigner(
@@ -426,11 +432,14 @@ public class ApkSignatureSchemeV2Verifier {
*/
public static class VerifiedSigner {
public final X509Certificate[][] certs;
public final byte[] verityRootHash;
public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) {
public final byte[] verityRootHash;
public final byte[] digest;
public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash, byte[] digest) {
this.certs = certs;
this.verityRootHash = verityRootHash;
this.digest = digest;
}
}

View File

@@ -16,8 +16,6 @@
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;
@@ -26,6 +24,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
import android.os.Build;
@@ -213,24 +212,11 @@ public class ApkSignatureSchemeV3Verifier {
verityDigest, apk.length(), signatureInfo);
}
result.digest = pickBestV3DigestForV4(contentDigests);
result.digest = pickBestDigestForV4(contentDigests);
return result;
}
// Keep in sync with pickBestV3DigestForV4 in apksigner.V3SchemeVerifier.
private static byte[] pickBestV3DigestForV4(Map<Integer, byte[]> contentDigests) {
final int[] orderedContentDigestTypes =
{CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
CONTENT_DIGEST_CHUNKED_SHA256};
for (int contentDigestType : orderedContentDigestTypes) {
if (contentDigests.containsKey(contentDigestType)) {
return contentDigests.get(contentDigestType);
}
}
return null;
}
private static VerifiedSigner verifySigner(
ByteBuffer signerBlock,
Map<Integer, byte[]> contentDigests,

View File

@@ -145,7 +145,7 @@ public class ApkSignatureSchemeV4Verifier {
"Public key mismatch between certificate and signature record");
}
return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest);
return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.apkDigest);
}
/**
@@ -155,11 +155,11 @@ public class ApkSignatureSchemeV4Verifier {
*/
public static class VerifiedSigner {
public final Certificate[] certs;
public byte[] v3Digest;
public byte[] apkDigest;
public VerifiedSigner(Certificate[] certs, byte[] v3Digest) {
public VerifiedSigner(Certificate[] certs, byte[] apkDigest) {
this.certs = certs;
this.v3Digest = v3Digest;
this.apkDigest = apkDigest;
}
}

View File

@@ -184,27 +184,45 @@ public class ApkSignatureVerifier {
Signature[] signerSigs = convertToSignatures(signerCerts);
if (verifyFull) {
// 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);
byte[] nonstreamingDigest = null;
Certificate[][] nonstreamingCerts = null;
try {
// v4 is an add-on and requires v2 or v3 signature to validate against its
// certificate and digest
ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
nonstreamingDigest = v3Signer.digest;
nonstreamingCerts = new Certificate[][]{v3Signer.certs};
} catch (SignatureNotFoundException e) {
try {
ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
ApkSignatureSchemeV2Verifier.verify(apkPath, false);
nonstreamingDigest = v2Signer.digest;
nonstreamingCerts = v2Signer.certs;
} catch (SignatureNotFoundException ee) {
throw new SecurityException(
"V4 verification failed to collect V2/V3 certificates from : "
+ apkPath, ee);
}
}
Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
if (nonstreamingSigs.length != signerSigs.length) {
throw new SecurityException(
"Invalid number of certificates: " + nonstreaming.certs.length);
"Invalid number of certificates: " + nonstreamingSigs.length);
}
for (int i = 0, size = signerSigs.length; i < size; ++i) {
if (!nonstreamingSigs[i].equals(signerSigs[i])) {
throw new SecurityException("V4 signature certificate does not match V3");
throw new SecurityException(
"V4 signature certificate does not match V2/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");
if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest,
vSigner.apkDigest.length)) {
throw new SecurityException("APK digest in V4 signature does not match V2/V3");
}
}

View File

@@ -421,6 +421,10 @@ final class ApkSigningBlockUtils {
static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
private static final int[] V4_CONTENT_DIGEST_ALGORITHMS =
{CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
CONTENT_DIGEST_CHUNKED_SHA256};
static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
@@ -571,6 +575,21 @@ final class ApkSigningBlockUtils {
}
}
/**
* Returns the best digest from the map of available digests.
* similarly to compareContentDigestAlgorithm.
*
* Keep in sync with pickBestDigestForV4 in apksigner's ApkSigningBlockUtils.
*/
static byte[] pickBestDigestForV4(Map<Integer, byte[]> contentDigests) {
for (int algo : V4_CONTENT_DIGEST_ALGORITHMS) {
if (contentDigests.containsKey(algo)) {
return contentDigests.get(algo);
}
}
return null;
}
/**
* Returns new byte buffer whose content is a shared subsequence of this buffer's content
* between the specified start (inclusive) and end (exclusive) positions. As opposed to