1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.util.apk; 18 19 import static android.util.apk.ApkSignatureSchemeV3Verifier.APK_SIGNATURE_SCHEME_V3_BLOCK_ID; 20 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; 21 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; 22 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; 23 import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; 24 25 import android.os.incremental.IncrementalManager; 26 import android.os.incremental.V4Signature; 27 import android.util.ArrayMap; 28 import android.util.Pair; 29 30 import com.android.internal.security.VerityUtils; 31 32 import java.io.ByteArrayInputStream; 33 import java.io.EOFException; 34 import java.io.File; 35 import java.io.FileInputStream; 36 import java.io.IOException; 37 import java.security.DigestException; 38 import java.security.InvalidAlgorithmParameterException; 39 import java.security.InvalidKeyException; 40 import java.security.KeyFactory; 41 import java.security.NoSuchAlgorithmException; 42 import java.security.PublicKey; 43 import java.security.Signature; 44 import java.security.SignatureException; 45 import java.security.cert.Certificate; 46 import java.security.cert.CertificateException; 47 import java.security.cert.CertificateFactory; 48 import java.security.cert.X509Certificate; 49 import java.security.spec.AlgorithmParameterSpec; 50 import java.security.spec.InvalidKeySpecException; 51 import java.security.spec.X509EncodedKeySpec; 52 import java.util.Arrays; 53 import java.util.Map; 54 55 /** 56 * APK Signature Scheme v4 verifier. 57 * 58 * @hide for internal use only. 59 */ 60 public class ApkSignatureSchemeV4Verifier { 61 static final int APK_SIGNATURE_SCHEME_DEFAULT = 0xffffffff; 62 63 /** 64 * Extracts and verifies APK Signature Scheme v4 signature of the provided APK and returns the 65 * certificates associated with each signer. 66 */ extractCertificates(String apkFile)67 public static VerifiedSigner extractCertificates(String apkFile) 68 throws SignatureNotFoundException, SignatureException, SecurityException { 69 Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> pair = extractSignature(apkFile); 70 return verify(apkFile, pair.first, pair.second, APK_SIGNATURE_SCHEME_DEFAULT); 71 } 72 73 /** 74 * Extracts APK Signature Scheme v4 signature of the provided APK. 75 */ extractSignature( String apkFile)76 public static Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> extractSignature( 77 String apkFile) throws SignatureNotFoundException, SignatureException { 78 try { 79 final File apk = new File(apkFile); 80 boolean needsConsistencyCheck; 81 82 // 1. Try IncFS first. IncFS verifies the file according to the integrity metadata 83 // (including the root hash of Merkle tree) it keeps track of with signature check. No 84 // further consistentcy check is needed. 85 byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( 86 apk.getAbsolutePath()); 87 V4Signature signature; 88 if (signatureBytes != null && signatureBytes.length > 0) { 89 needsConsistencyCheck = false; 90 signature = V4Signature.readFrom(signatureBytes); 91 } else if (android.security.Flags.extendVbChainToUpdatedApk()) { 92 // 2. Try fs-verity next. fs-verity checks against the Merkle tree, but the 93 // v4 signature file (including a raw root hash) is managed separately. We need to 94 // ensure the signed data from the file is consistent with the actual file. 95 needsConsistencyCheck = true; 96 97 final File idsig = new File(apk.getAbsolutePath() + V4Signature.EXT); 98 try (var fis = new FileInputStream(idsig.getAbsolutePath())) { 99 signature = V4Signature.readFrom(fis); 100 } catch (IOException e) { 101 throw new SignatureNotFoundException( 102 "Failed to obtain signature bytes from .idsig"); 103 } 104 } else { 105 throw new SignatureNotFoundException( 106 "Failed to obtain signature bytes from IncFS."); 107 } 108 if (!signature.isVersionSupported()) { 109 throw new SecurityException( 110 "v4 signature version " + signature.version + " is not supported"); 111 } 112 final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray( 113 signature.hashingInfo); 114 final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray( 115 signature.signingInfos); 116 117 if (needsConsistencyCheck) { 118 final byte[] actualDigest = VerityUtils.getFsverityDigest(apk.getAbsolutePath()); 119 if (actualDigest == null) { 120 throw new SecurityException("The APK does not have fs-verity"); 121 } 122 final byte[] computedDigest = 123 VerityUtils.generateFsVerityDigest(apk.length(), hashingInfo); 124 if (!Arrays.equals(computedDigest, actualDigest)) { 125 throw new SignatureException("Actual digest does not match the v4 signature"); 126 } 127 } 128 129 return Pair.create(hashingInfo, signingInfos); 130 } catch (EOFException e) { 131 throw new SignatureException("V4 signature is invalid.", e); 132 } catch (IOException e) { 133 throw new SignatureNotFoundException("Failed to read V4 signature.", e); 134 } catch (DigestException | NoSuchAlgorithmException e) { 135 throw new SecurityException("Failed to calculate the digest", e); 136 } 137 } 138 139 /** 140 * Verifies APK Signature Scheme v4 signature and returns the 141 * certificates associated with each signer. 142 */ verify(String apkFile, final V4Signature.HashingInfo hashingInfo, final V4Signature.SigningInfos signingInfos, final int v3BlockId)143 public static VerifiedSigner verify(String apkFile, final V4Signature.HashingInfo hashingInfo, 144 final V4Signature.SigningInfos signingInfos, final int v3BlockId) 145 throws SignatureNotFoundException, SecurityException { 146 final V4Signature.SigningInfo signingInfo = findSigningInfoForBlockId(signingInfos, 147 v3BlockId); 148 149 // Verify signed data and extract certificates and apk digest. 150 final byte[] signedData = V4Signature.getSignedData(new File(apkFile).length(), hashingInfo, 151 signingInfo); 152 final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData); 153 154 // Populate digests enforced by IncFS driver and fs-verity. 155 Map<Integer, byte[]> contentDigests = new ArrayMap<>(); 156 contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm), 157 hashingInfo.rawRootHash); 158 159 return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests); 160 } 161 findSigningInfoForBlockId( final V4Signature.SigningInfos signingInfos, final int v3BlockId)162 private static V4Signature.SigningInfo findSigningInfoForBlockId( 163 final V4Signature.SigningInfos signingInfos, final int v3BlockId) 164 throws SignatureNotFoundException { 165 // Use default signingInfo for v3 block. 166 if (v3BlockId == APK_SIGNATURE_SCHEME_DEFAULT 167 || v3BlockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) { 168 return signingInfos.signingInfo; 169 } 170 for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) { 171 if (v3BlockId == signingInfoBlock.blockId) { 172 try { 173 return V4Signature.SigningInfo.fromByteArray(signingInfoBlock.signingInfo); 174 } catch (IOException e) { 175 throw new SecurityException( 176 "Failed to read V4 signature block: " + signingInfoBlock.blockId, e); 177 } 178 } 179 } 180 throw new SecurityException( 181 "Failed to find V4 signature block corresponding to V3 blockId: " + v3BlockId); 182 } 183 verifySigner(V4Signature.SigningInfo signingInfo, final byte[] signedData)184 private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo, 185 final byte[] signedData) throws SecurityException { 186 if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) { 187 throw new SecurityException("No supported signatures found"); 188 } 189 190 final int signatureAlgorithmId = signingInfo.signatureAlgorithmId; 191 final byte[] signatureBytes = signingInfo.signature; 192 final byte[] publicKeyBytes = signingInfo.publicKey; 193 final byte[] encodedCert = signingInfo.certificate; 194 195 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId); 196 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = 197 getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId); 198 String jcaSignatureAlgorithm = signatureAlgorithmParams.first; 199 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; 200 boolean sigVerified; 201 try { 202 PublicKey publicKey = 203 KeyFactory.getInstance(keyAlgorithm) 204 .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); 205 Signature sig = Signature.getInstance(jcaSignatureAlgorithm); 206 sig.initVerify(publicKey); 207 if (jcaSignatureAlgorithmParams != null) { 208 sig.setParameter(jcaSignatureAlgorithmParams); 209 } 210 sig.update(signedData); 211 sigVerified = sig.verify(signatureBytes); 212 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException 213 | InvalidAlgorithmParameterException | SignatureException e) { 214 throw new SecurityException( 215 "Failed to verify " + jcaSignatureAlgorithm + " signature", e); 216 } 217 if (!sigVerified) { 218 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); 219 } 220 221 // Signature over signedData has verified. 222 CertificateFactory certFactory; 223 try { 224 certFactory = CertificateFactory.getInstance("X.509"); 225 } catch (CertificateException e) { 226 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 227 } 228 229 X509Certificate certificate; 230 try { 231 certificate = (X509Certificate) 232 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); 233 } catch (CertificateException e) { 234 throw new SecurityException("Failed to decode certificate", e); 235 } 236 certificate = new VerbatimX509Certificate(certificate, encodedCert); 237 238 byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded(); 239 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { 240 throw new SecurityException( 241 "Public key mismatch between certificate and signature record"); 242 } 243 244 return Pair.create(certificate, signingInfo.apkDigest); 245 } 246 convertToContentDigestType(int hashAlgorithm)247 private static int convertToContentDigestType(int hashAlgorithm) throws SecurityException { 248 if (hashAlgorithm == V4Signature.HASHING_ALGORITHM_SHA256) { 249 return CONTENT_DIGEST_VERITY_CHUNKED_SHA256; 250 } 251 throw new SecurityException("Unsupported hashAlgorithm: " + hashAlgorithm); 252 } 253 254 /** 255 * Verified APK Signature Scheme v4 signer, including V2/V3 digest. 256 * 257 * @hide for internal use only. 258 */ 259 public static class VerifiedSigner { 260 public final Certificate[] certs; 261 public final byte[] apkDigest; 262 263 // Algorithm -> digest map of signed digests in the signature. 264 // These are continuously enforced by the IncFS driver and fs-verity. 265 public final Map<Integer, byte[]> contentDigests; 266 VerifiedSigner(Certificate[] certs, byte[] apkDigest, Map<Integer, byte[]> contentDigests)267 public VerifiedSigner(Certificate[] certs, byte[] apkDigest, 268 Map<Integer, byte[]> contentDigests) { 269 this.certs = certs; 270 this.apkDigest = apkDigest; 271 this.contentDigests = contentDigests; 272 } 273 274 } 275 } 276