• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
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             }
105             if (!signature.isVersionSupported()) {
106                 throw new SecurityException(
107                         "v4 signature version " + signature.version + " is not supported");
108             }
109             final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
110                     signature.hashingInfo);
111             final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
112                     signature.signingInfos);
113 
114             if (needsConsistencyCheck) {
115                 final byte[] actualDigest = VerityUtils.getFsverityDigest(apk.getAbsolutePath());
116                 if (actualDigest == null) {
117                     throw new SecurityException("The APK does not have fs-verity");
118                 }
119                 final byte[] computedDigest =
120                         VerityUtils.generateFsVerityDigest(apk.length(), hashingInfo);
121                 if (!Arrays.equals(computedDigest, actualDigest)) {
122                     throw new SignatureException("Actual digest does not match the v4 signature");
123                 }
124             }
125 
126             return Pair.create(hashingInfo, signingInfos);
127         } catch (EOFException e) {
128             throw new SignatureException("V4 signature is invalid.", e);
129         } catch (IOException e) {
130             throw new SignatureNotFoundException("Failed to read V4 signature.", e);
131         } catch (DigestException | NoSuchAlgorithmException e) {
132             throw new SecurityException("Failed to calculate the digest", e);
133         }
134     }
135 
136     /**
137      * Verifies APK Signature Scheme v4 signature and returns the
138      * certificates associated with each signer.
139      */
verify(String apkFile, final V4Signature.HashingInfo hashingInfo, final V4Signature.SigningInfos signingInfos, final int v3BlockId)140     public static VerifiedSigner verify(String apkFile, final V4Signature.HashingInfo hashingInfo,
141             final V4Signature.SigningInfos signingInfos, final int v3BlockId)
142             throws SignatureNotFoundException, SecurityException {
143         final V4Signature.SigningInfo signingInfo = findSigningInfoForBlockId(signingInfos,
144                 v3BlockId);
145 
146         // Verify signed data and extract certificates and apk digest.
147         final byte[] signedData = V4Signature.getSignedData(new File(apkFile).length(), hashingInfo,
148                 signingInfo);
149         final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData);
150 
151         // Populate digests enforced by IncFS driver and fs-verity.
152         Map<Integer, byte[]> contentDigests = new ArrayMap<>();
153         contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm),
154                 hashingInfo.rawRootHash);
155 
156         return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests);
157     }
158 
findSigningInfoForBlockId( final V4Signature.SigningInfos signingInfos, final int v3BlockId)159     private static V4Signature.SigningInfo findSigningInfoForBlockId(
160             final V4Signature.SigningInfos signingInfos, final int v3BlockId)
161             throws SignatureNotFoundException {
162         // Use default signingInfo for v3 block.
163         if (v3BlockId == APK_SIGNATURE_SCHEME_DEFAULT
164                 || v3BlockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
165             return signingInfos.signingInfo;
166         }
167         for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) {
168             if (v3BlockId == signingInfoBlock.blockId) {
169                 try {
170                     return V4Signature.SigningInfo.fromByteArray(signingInfoBlock.signingInfo);
171                 } catch (IOException e) {
172                     throw new SecurityException(
173                             "Failed to read V4 signature block: " + signingInfoBlock.blockId, e);
174                 }
175             }
176         }
177         throw new SecurityException(
178                 "Failed to find V4 signature block corresponding to V3 blockId: " + v3BlockId);
179     }
180 
verifySigner(V4Signature.SigningInfo signingInfo, final byte[] signedData)181     private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo,
182             final byte[] signedData) throws SecurityException {
183         if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
184             throw new SecurityException("No supported signatures found");
185         }
186 
187         final int signatureAlgorithmId = signingInfo.signatureAlgorithmId;
188         final byte[] signatureBytes = signingInfo.signature;
189         final byte[] publicKeyBytes = signingInfo.publicKey;
190         final byte[] encodedCert = signingInfo.certificate;
191 
192         String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId);
193         Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
194                 getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId);
195         String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
196         AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
197         boolean sigVerified;
198         try {
199             PublicKey publicKey =
200                     KeyFactory.getInstance(keyAlgorithm)
201                             .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
202             Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
203             sig.initVerify(publicKey);
204             if (jcaSignatureAlgorithmParams != null) {
205                 sig.setParameter(jcaSignatureAlgorithmParams);
206             }
207             sig.update(signedData);
208             sigVerified = sig.verify(signatureBytes);
209         } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
210                 | InvalidAlgorithmParameterException | SignatureException e) {
211             throw new SecurityException(
212                     "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
213         }
214         if (!sigVerified) {
215             throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
216         }
217 
218         // Signature over signedData has verified.
219         CertificateFactory certFactory;
220         try {
221             certFactory = CertificateFactory.getInstance("X.509");
222         } catch (CertificateException e) {
223             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
224         }
225 
226         X509Certificate certificate;
227         try {
228             certificate = (X509Certificate)
229                     certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
230         } catch (CertificateException e) {
231             throw new SecurityException("Failed to decode certificate", e);
232         }
233         certificate = new VerbatimX509Certificate(certificate, encodedCert);
234 
235         byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded();
236         if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
237             throw new SecurityException(
238                     "Public key mismatch between certificate and signature record");
239         }
240 
241         return Pair.create(certificate, signingInfo.apkDigest);
242     }
243 
convertToContentDigestType(int hashAlgorithm)244     private static int convertToContentDigestType(int hashAlgorithm) throws SecurityException {
245         if (hashAlgorithm == V4Signature.HASHING_ALGORITHM_SHA256) {
246             return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
247         }
248         throw new SecurityException("Unsupported hashAlgorithm: " + hashAlgorithm);
249     }
250 
251     /**
252      * Verified APK Signature Scheme v4 signer, including V2/V3 digest.
253      *
254      * @hide for internal use only.
255      */
256     public static class VerifiedSigner {
257         public final Certificate[] certs;
258         public final byte[] apkDigest;
259 
260         // Algorithm -> digest map of signed digests in the signature.
261         // These are continuously enforced by the IncFS driver and fs-verity.
262         public final Map<Integer, byte[]> contentDigests;
263 
VerifiedSigner(Certificate[] certs, byte[] apkDigest, Map<Integer, byte[]> contentDigests)264         public VerifiedSigner(Certificate[] certs, byte[] apkDigest,
265                 Map<Integer, byte[]> contentDigests) {
266             this.certs = certs;
267             this.apkDigest = apkDigest;
268             this.contentDigests = contentDigests;
269         }
270 
271     }
272 }
273