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