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