1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
|
/*
* 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.util.apk;
import static android.util.apk.ApkSignatureSchemeV3Verifier.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
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.ArrayMap;
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 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;
import java.util.Map;
/**
* APK Signature Scheme v4 verifier.
*
* @hide for internal use only.
*/
public class ApkSignatureSchemeV4Verifier {
static final int APK_SIGNATURE_SCHEME_DEFAULT = 0xffffffff;
/**
* Extracts and verifies APK Signature Scheme v4 signature of the provided APK and returns the
* certificates associated with each signer.
*/
public static VerifiedSigner extractCertificates(String apkFile)
throws SignatureNotFoundException, SecurityException {
Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> pair = extractSignature(apkFile);
return verify(apkFile, pair.first, pair.second, APK_SIGNATURE_SCHEME_DEFAULT);
}
/**
* Extracts APK Signature Scheme v4 signature of the provided APK.
*/
public static Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> extractSignature(
String apkFile) throws SignatureNotFoundException {
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.");
}
try {
final V4Signature signature = V4Signature.readFrom(signatureBytes);
if (!signature.isVersionSupported()) {
throw new SecurityException(
"v4 signature version " + signature.version + " is not supported");
}
final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
signature.hashingInfo);
final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
signature.signingInfos);
return Pair.create(hashingInfo, signingInfos);
} catch (IOException e) {
throw new SignatureNotFoundException("Failed to read V4 signature.", e);
}
}
/**
* Verifies APK Signature Scheme v4 signature and returns the
* certificates associated with each signer.
*/
public static VerifiedSigner verify(String apkFile, final V4Signature.HashingInfo hashingInfo,
final V4Signature.SigningInfos signingInfos, final int v3BlockId)
throws SignatureNotFoundException, SecurityException {
final V4Signature.SigningInfo signingInfo = findSigningInfoForBlockId(signingInfos,
v3BlockId);
// Verify signed data and extract certificates and apk digest.
final byte[] signedData = V4Signature.getSignedData(new File(apkFile).length(), hashingInfo,
signingInfo);
final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData);
// Populate digests enforced by IncFS driver.
Map<Integer, byte[]> contentDigests = new ArrayMap<>();
contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm),
hashingInfo.rawRootHash);
return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests);
}
private static V4Signature.SigningInfo findSigningInfoForBlockId(
final V4Signature.SigningInfos signingInfos, final int v3BlockId)
throws SignatureNotFoundException {
// Use default signingInfo for v3 block.
if (v3BlockId == APK_SIGNATURE_SCHEME_DEFAULT
|| v3BlockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
return signingInfos.signingInfo;
}
for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) {
if (v3BlockId == signingInfoBlock.blockId) {
try {
return V4Signature.SigningInfo.fromByteArray(signingInfoBlock.signingInfo);
} catch (IOException e) {
throw new SecurityException(
"Failed to read V4 signature block: " + signingInfoBlock.blockId, e);
}
}
}
throw new SecurityException(
"Failed to find V4 signature block corresponding to V3 blockId: " + v3BlockId);
}
private static Pair<Certificate, byte[]> 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 Pair.create(certificate, signingInfo.apkDigest);
}
private static int convertToContentDigestType(int hashAlgorithm) throws SecurityException {
if (hashAlgorithm == V4Signature.HASHING_ALGORITHM_SHA256) {
return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
}
throw new SecurityException("Unsupported hashAlgorithm: " + hashAlgorithm);
}
/**
* Verified APK Signature Scheme v4 signer, including V2/V3 digest.
*
* @hide for internal use only.
*/
public static class VerifiedSigner {
public final Certificate[] certs;
public final byte[] apkDigest;
// Algorithm -> digest map of signed digests in the signature.
// These are continuously enforced by the IncFS driver.
public final Map<Integer, byte[]> contentDigests;
public VerifiedSigner(Certificate[] certs, byte[] apkDigest,
Map<Integer, byte[]> contentDigests) {
this.certs = certs;
this.apkDigest = apkDigest;
this.contentDigests = contentDigests;
}
}
}
|