• 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.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