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.
|
* 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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user