Merge "V4 uses V2 digest if V3 is not available." into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c6f4f74723
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user