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. * V4 signature data.
*/ */
public static class SigningInfo { 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[] certificate; // ASN.1 DER form
public final byte[] additionalData; // a free-form binary data blob public final byte[] additionalData; // a free-form binary data blob
public final byte[] publicKey; // ASN.1 DER, must match the certificate 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 int signatureAlgorithmId; // see the APK v2 doc for the list
public final byte[] signature; public final byte[] signature;
SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData, SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData,
byte[] publicKey, int signatureAlgorithmId, byte[] signature) { byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
this.v3Digest = v3Digest; this.apkDigest = apkDigest;
this.certificate = certificate; this.certificate = certificate;
this.additionalData = additionalData; this.additionalData = additionalData;
this.publicKey = publicKey; this.publicKey = publicKey;
@@ -94,13 +94,13 @@ public class V4Signature {
*/ */
public static SigningInfo fromByteArray(byte[] bytes) throws IOException { public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
byte[] v3Digest = readBytes(buffer); byte[] apkDigest = readBytes(buffer);
byte[] certificate = readBytes(buffer); byte[] certificate = readBytes(buffer);
byte[] additionalData = readBytes(buffer); byte[] additionalData = readBytes(buffer);
byte[] publicKey = readBytes(buffer); byte[] publicKey = readBytes(buffer);
int signatureAlgorithmId = buffer.getInt(); int signatureAlgorithmId = buffer.getInt();
byte[] signature = readBytes(buffer); byte[] signature = readBytes(buffer);
return new SigningInfo(v3Digest, certificate, additionalData, publicKey, return new SigningInfo(apkDigest, certificate, additionalData, publicKey,
signatureAlgorithmId, signature); signatureAlgorithmId, signature);
} }
} }
@@ -150,7 +150,7 @@ public class V4Signature {
final int size = final int size =
4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize(
hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize(
signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize( signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize(
signingInfo.additionalData); signingInfo.additionalData);
ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(size); buffer.putInt(size);
@@ -159,7 +159,7 @@ public class V4Signature {
buffer.put(hashingInfo.log2BlockSize); buffer.put(hashingInfo.log2BlockSize);
writeBytes(buffer, hashingInfo.salt); writeBytes(buffer, hashingInfo.salt);
writeBytes(buffer, hashingInfo.rawRootHash); writeBytes(buffer, hashingInfo.rawRootHash);
writeBytes(buffer, signingInfo.v3Digest); writeBytes(buffer, signingInfo.apkDigest);
writeBytes(buffer, signingInfo.certificate); writeBytes(buffer, signingInfo.certificate);
writeBytes(buffer, signingInfo.additionalData); writeBytes(buffer, signingInfo.additionalData);
return buffer.array(); 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.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
import android.util.ArrayMap; import android.util.ArrayMap;
@@ -117,7 +118,10 @@ public class ApkSignatureSchemeV2Verifier {
return vSigner.certs; 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 { throws SignatureNotFoundException, SecurityException, IOException {
try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
return verify(apk, verifyIntegrity); return verify(apk, verifyIntegrity);
@@ -209,9 +213,11 @@ public class ApkSignatureSchemeV2Verifier {
verityDigest, apk.length(), signatureInfo); verityDigest, apk.length(), signatureInfo);
} }
byte[] digest = pickBestDigestForV4(contentDigests);
return new VerifiedSigner( return new VerifiedSigner(
signerCerts.toArray(new X509Certificate[signerCerts.size()][]), signerCerts.toArray(new X509Certificate[signerCerts.size()][]),
verityRootHash); verityRootHash, digest);
} }
private static X509Certificate[] verifySigner( private static X509Certificate[] verifySigner(
@@ -426,11 +432,14 @@ public class ApkSignatureSchemeV2Verifier {
*/ */
public static class VerifiedSigner { public static class VerifiedSigner {
public final X509Certificate[][] certs; 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.certs = certs;
this.verityRootHash = verityRootHash; this.verityRootHash = verityRootHash;
this.digest = digest;
} }
} }

View File

@@ -16,8 +16,6 @@
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;
@@ -26,6 +24,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
import android.os.Build; import android.os.Build;
@@ -213,24 +212,11 @@ public class ApkSignatureSchemeV3Verifier {
verityDigest, apk.length(), signatureInfo); verityDigest, apk.length(), signatureInfo);
} }
result.digest = pickBestV3DigestForV4(contentDigests); result.digest = pickBestDigestForV4(contentDigests);
return result; 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( private static VerifiedSigner verifySigner(
ByteBuffer signerBlock, ByteBuffer signerBlock,
Map<Integer, byte[]> contentDigests, Map<Integer, byte[]> contentDigests,

View File

@@ -145,7 +145,7 @@ public class ApkSignatureSchemeV4Verifier {
"Public key mismatch between certificate and signature record"); "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 static class VerifiedSigner {
public final Certificate[] certs; 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.certs = certs;
this.v3Digest = v3Digest; this.apkDigest = apkDigest;
} }
} }

View File

@@ -184,27 +184,45 @@ public class ApkSignatureVerifier {
Signature[] signerSigs = convertToSignatures(signerCerts); Signature[] signerSigs = convertToSignatures(signerCerts);
if (verifyFull) { if (verifyFull) {
// v4 is an add-on and requires v3 signature to validate against its certificates byte[] nonstreamingDigest = null;
ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming = Certificate[][] nonstreamingCerts = null;
ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs};
Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
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) { if (nonstreamingSigs.length != signerSigs.length) {
throw new SecurityException( 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) { for (int i = 0, size = signerSigs.length; i < size; ++i) {
if (!nonstreamingSigs[i].equals(signerSigs[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.equals(vSigner.apkDigest, nonstreamingDigest,
if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest, vSigner.apkDigest.length)) {
nonstreaming.digest, vSigner.v3Digest.length)) { throw new SecurityException("APK digest in V4 signature does not match V2/V3");
throw new SecurityException("V3 digest in V4 signature does not match V3");
} }
} }

View File

@@ -421,6 +421,10 @@ final class ApkSigningBlockUtils {
static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3; 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) { static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); 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 * 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 * between the specified start (inclusive) and end (exclusive) positions. As opposed to