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 java.io.ByteArrayInputStream; 31 import java.io.File; 32 import java.io.IOException; 33 import java.security.InvalidAlgorithmParameterException; 34 import java.security.InvalidKeyException; 35 import java.security.KeyFactory; 36 import java.security.NoSuchAlgorithmException; 37 import java.security.PublicKey; 38 import java.security.Signature; 39 import java.security.SignatureException; 40 import java.security.cert.Certificate; 41 import java.security.cert.CertificateException; 42 import java.security.cert.CertificateFactory; 43 import java.security.cert.X509Certificate; 44 import java.security.spec.AlgorithmParameterSpec; 45 import java.security.spec.InvalidKeySpecException; 46 import java.security.spec.X509EncodedKeySpec; 47 import java.util.Arrays; 48 import java.util.Map; 49 50 /** 51 * APK Signature Scheme v4 verifier. 52 * 53 * @hide for internal use only. 54 */ 55 public class ApkSignatureSchemeV4Verifier { 56 static final int APK_SIGNATURE_SCHEME_DEFAULT = 0xffffffff; 57 58 /** 59 * Extracts and verifies APK Signature Scheme v4 signature of the provided APK and returns the 60 * certificates associated with each signer. 61 */ extractCertificates(String apkFile)62 public static VerifiedSigner extractCertificates(String apkFile) 63 throws SignatureNotFoundException, SecurityException { 64 Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> pair = extractSignature(apkFile); 65 return verify(apkFile, pair.first, pair.second, APK_SIGNATURE_SCHEME_DEFAULT); 66 } 67 68 /** 69 * Extracts APK Signature Scheme v4 signature of the provided APK. 70 */ extractSignature( String apkFile)71 public static Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> extractSignature( 72 String apkFile) throws SignatureNotFoundException { 73 final File apk = new File(apkFile); 74 final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( 75 apk.getAbsolutePath()); 76 if (signatureBytes == null || signatureBytes.length == 0) { 77 throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS."); 78 } 79 try { 80 final V4Signature signature = V4Signature.readFrom(signatureBytes); 81 if (!signature.isVersionSupported()) { 82 throw new SecurityException( 83 "v4 signature version " + signature.version + " is not supported"); 84 } 85 final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray( 86 signature.hashingInfo); 87 final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray( 88 signature.signingInfos); 89 return Pair.create(hashingInfo, signingInfos); 90 } catch (IOException e) { 91 throw new SignatureNotFoundException("Failed to read V4 signature.", e); 92 } 93 } 94 95 /** 96 * Verifies APK Signature Scheme v4 signature and returns the 97 * certificates associated with each signer. 98 */ verify(String apkFile, final V4Signature.HashingInfo hashingInfo, final V4Signature.SigningInfos signingInfos, final int v3BlockId)99 public static VerifiedSigner verify(String apkFile, final V4Signature.HashingInfo hashingInfo, 100 final V4Signature.SigningInfos signingInfos, final int v3BlockId) 101 throws SignatureNotFoundException, SecurityException { 102 final V4Signature.SigningInfo signingInfo = findSigningInfoForBlockId(signingInfos, 103 v3BlockId); 104 105 // Verify signed data and extract certificates and apk digest. 106 final byte[] signedData = V4Signature.getSignedData(new File(apkFile).length(), hashingInfo, 107 signingInfo); 108 final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData); 109 110 // Populate digests enforced by IncFS driver. 111 Map<Integer, byte[]> contentDigests = new ArrayMap<>(); 112 contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm), 113 hashingInfo.rawRootHash); 114 115 return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests); 116 } 117 findSigningInfoForBlockId( final V4Signature.SigningInfos signingInfos, final int v3BlockId)118 private static V4Signature.SigningInfo findSigningInfoForBlockId( 119 final V4Signature.SigningInfos signingInfos, final int v3BlockId) 120 throws SignatureNotFoundException { 121 // Use default signingInfo for v3 block. 122 if (v3BlockId == APK_SIGNATURE_SCHEME_DEFAULT 123 || v3BlockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) { 124 return signingInfos.signingInfo; 125 } 126 for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) { 127 if (v3BlockId == signingInfoBlock.blockId) { 128 try { 129 return V4Signature.SigningInfo.fromByteArray(signingInfoBlock.signingInfo); 130 } catch (IOException e) { 131 throw new SecurityException( 132 "Failed to read V4 signature block: " + signingInfoBlock.blockId, e); 133 } 134 } 135 } 136 throw new SecurityException( 137 "Failed to find V4 signature block corresponding to V3 blockId: " + v3BlockId); 138 } 139 verifySigner(V4Signature.SigningInfo signingInfo, final byte[] signedData)140 private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo, 141 final byte[] signedData) throws SecurityException { 142 if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) { 143 throw new SecurityException("No supported signatures found"); 144 } 145 146 final int signatureAlgorithmId = signingInfo.signatureAlgorithmId; 147 final byte[] signatureBytes = signingInfo.signature; 148 final byte[] publicKeyBytes = signingInfo.publicKey; 149 final byte[] encodedCert = signingInfo.certificate; 150 151 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId); 152 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = 153 getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId); 154 String jcaSignatureAlgorithm = signatureAlgorithmParams.first; 155 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; 156 boolean sigVerified; 157 try { 158 PublicKey publicKey = 159 KeyFactory.getInstance(keyAlgorithm) 160 .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); 161 Signature sig = Signature.getInstance(jcaSignatureAlgorithm); 162 sig.initVerify(publicKey); 163 if (jcaSignatureAlgorithmParams != null) { 164 sig.setParameter(jcaSignatureAlgorithmParams); 165 } 166 sig.update(signedData); 167 sigVerified = sig.verify(signatureBytes); 168 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException 169 | InvalidAlgorithmParameterException | SignatureException e) { 170 throw new SecurityException( 171 "Failed to verify " + jcaSignatureAlgorithm + " signature", e); 172 } 173 if (!sigVerified) { 174 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); 175 } 176 177 // Signature over signedData has verified. 178 CertificateFactory certFactory; 179 try { 180 certFactory = CertificateFactory.getInstance("X.509"); 181 } catch (CertificateException e) { 182 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 183 } 184 185 X509Certificate certificate; 186 try { 187 certificate = (X509Certificate) 188 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); 189 } catch (CertificateException e) { 190 throw new SecurityException("Failed to decode certificate", e); 191 } 192 certificate = new VerbatimX509Certificate(certificate, encodedCert); 193 194 byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded(); 195 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { 196 throw new SecurityException( 197 "Public key mismatch between certificate and signature record"); 198 } 199 200 return Pair.create(certificate, signingInfo.apkDigest); 201 } 202 convertToContentDigestType(int hashAlgorithm)203 private static int convertToContentDigestType(int hashAlgorithm) throws SecurityException { 204 if (hashAlgorithm == V4Signature.HASHING_ALGORITHM_SHA256) { 205 return CONTENT_DIGEST_VERITY_CHUNKED_SHA256; 206 } 207 throw new SecurityException("Unsupported hashAlgorithm: " + hashAlgorithm); 208 } 209 210 /** 211 * Verified APK Signature Scheme v4 signer, including V2/V3 digest. 212 * 213 * @hide for internal use only. 214 */ 215 public static class VerifiedSigner { 216 public final Certificate[] certs; 217 public final byte[] apkDigest; 218 219 // Algorithm -> digest map of signed digests in the signature. 220 // These are continuously enforced by the IncFS driver. 221 public final Map<Integer, byte[]> contentDigests; 222 VerifiedSigner(Certificate[] certs, byte[] apkDigest, Map<Integer, byte[]> contentDigests)223 public VerifiedSigner(Certificate[] certs, byte[] apkDigest, 224 Map<Integer, byte[]> contentDigests) { 225 this.certs = certs; 226 this.apkDigest = apkDigest; 227 this.contentDigests = contentDigests; 228 } 229 230 } 231 } 232