1 /*
2  * Copyright (C) 2016 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.SIGNATURE_DSA_WITH_SHA256;
21 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
22 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
23 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
24 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
25 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
26 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
27 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
28 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
29 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
30 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
31 import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
32 import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
33 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
34 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
35 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
36 import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
37 
38 import android.util.ArrayMap;
39 import android.util.Pair;
40 
41 import java.io.ByteArrayInputStream;
42 import java.io.IOException;
43 import java.io.RandomAccessFile;
44 import java.nio.BufferUnderflowException;
45 import java.nio.ByteBuffer;
46 import java.security.DigestException;
47 import java.security.InvalidAlgorithmParameterException;
48 import java.security.InvalidKeyException;
49 import java.security.KeyFactory;
50 import java.security.MessageDigest;
51 import java.security.NoSuchAlgorithmException;
52 import java.security.PublicKey;
53 import java.security.Signature;
54 import java.security.SignatureException;
55 import java.security.cert.CertificateException;
56 import java.security.cert.CertificateFactory;
57 import java.security.cert.X509Certificate;
58 import java.security.spec.AlgorithmParameterSpec;
59 import java.security.spec.InvalidKeySpecException;
60 import java.security.spec.X509EncodedKeySpec;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.List;
64 import java.util.Map;
65 
66 /**
67  * APK Signature Scheme v2 verifier.
68  *
69  * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
70  * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
71  * uncompressed contents of ZIP entries.
72  *
73  * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
74  *
75  * @hide for internal use only.
76  */
77 public class ApkSignatureSchemeV2Verifier {
78 
79     /**
80      * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
81      */
82     public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
83 
84     private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
85 
86     /**
87      * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
88      *
89      * <p><b>NOTE: This method does not verify the signature.</b>
90      */
hasSignature(String apkFile)91     public static boolean hasSignature(String apkFile) throws IOException {
92         try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
93             findSignature(apk);
94             return true;
95         } catch (SignatureNotFoundException e) {
96             return false;
97         }
98     }
99 
100     /**
101      * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
102      * associated with each signer.
103      *
104      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
105      * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
106      * @throws IOException if an I/O error occurs while reading the APK file.
107      */
verify(String apkFile)108     public static X509Certificate[][] verify(String apkFile)
109             throws SignatureNotFoundException, SecurityException, IOException {
110         VerifiedSigner vSigner = verify(apkFile, true);
111         return vSigner.certs;
112     }
113 
114     /**
115      * Returns the certificates associated with each signer for the given APK without verification.
116      * This method is dangerous and should not be used, unless the caller is absolutely certain the
117      * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v2
118      * Block while gathering signer information.  The APK contents are not verified.
119      *
120      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
121      * @throws IOException if an I/O error occurs while reading the APK file.
122      */
unsafeGetCertsWithoutVerification(String apkFile)123     public static X509Certificate[][] unsafeGetCertsWithoutVerification(String apkFile)
124             throws SignatureNotFoundException, SecurityException, IOException {
125         VerifiedSigner vSigner = verify(apkFile, false);
126         return vSigner.certs;
127     }
128 
verify(String apkFile, boolean verifyIntegrity)129     private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
130             throws SignatureNotFoundException, SecurityException, IOException {
131         try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
132             return verify(apk, verifyIntegrity);
133         }
134     }
135 
136     /**
137      * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
138      * associated with each signer.
139      *
140      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
141      * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not
142      *         verify.
143      * @throws IOException if an I/O error occurs while reading the APK file.
144      */
verify(RandomAccessFile apk, boolean verifyIntegrity)145     private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
146             throws SignatureNotFoundException, SecurityException, IOException {
147         SignatureInfo signatureInfo = findSignature(apk);
148         return verify(apk, signatureInfo, verifyIntegrity);
149     }
150 
151     /**
152      * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
153      * additional information relevant for verifying the block against the file.
154      *
155      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
156      * @throws IOException if an I/O error occurs while reading the APK file.
157      */
findSignature(RandomAccessFile apk)158     private static SignatureInfo findSignature(RandomAccessFile apk)
159             throws IOException, SignatureNotFoundException {
160         return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
161     }
162 
163     /**
164      * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2
165      * Block.
166      *
167      * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it
168      *        against the APK file.
169      */
verify( RandomAccessFile apk, SignatureInfo signatureInfo, boolean doVerifyIntegrity)170     private static VerifiedSigner verify(
171             RandomAccessFile apk,
172             SignatureInfo signatureInfo,
173             boolean doVerifyIntegrity) throws SecurityException, IOException {
174         int signerCount = 0;
175         Map<Integer, byte[]> contentDigests = new ArrayMap<>();
176         List<X509Certificate[]> signerCerts = new ArrayList<>();
177         CertificateFactory certFactory;
178         try {
179             certFactory = CertificateFactory.getInstance("X.509");
180         } catch (CertificateException e) {
181             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
182         }
183         ByteBuffer signers;
184         try {
185             signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
186         } catch (IOException e) {
187             throw new SecurityException("Failed to read list of signers", e);
188         }
189         while (signers.hasRemaining()) {
190             signerCount++;
191             try {
192                 ByteBuffer signer = getLengthPrefixedSlice(signers);
193                 X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
194                 signerCerts.add(certs);
195             } catch (IOException | BufferUnderflowException | SecurityException e) {
196                 throw new SecurityException(
197                         "Failed to parse/verify signer #" + signerCount + " block",
198                         e);
199             }
200         }
201 
202         if (signerCount < 1) {
203             throw new SecurityException("No signers found");
204         }
205 
206         if (contentDigests.isEmpty()) {
207             throw new SecurityException("No content digests found");
208         }
209 
210         if (doVerifyIntegrity) {
211             ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
212         }
213 
214         byte[] verityRootHash = null;
215         if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
216             byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
217             verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
218                     verityDigest, apk.length(), signatureInfo);
219         }
220 
221         return new VerifiedSigner(
222                 signerCerts.toArray(new X509Certificate[signerCerts.size()][]),
223                 verityRootHash);
224     }
225 
verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, CertificateFactory certFactory)226     private static X509Certificate[] verifySigner(
227             ByteBuffer signerBlock,
228             Map<Integer, byte[]> contentDigests,
229             CertificateFactory certFactory) throws SecurityException, IOException {
230         ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
231         ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
232         byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
233 
234         int signatureCount = 0;
235         int bestSigAlgorithm = -1;
236         byte[] bestSigAlgorithmSignatureBytes = null;
237         List<Integer> signaturesSigAlgorithms = new ArrayList<>();
238         while (signatures.hasRemaining()) {
239             signatureCount++;
240             try {
241                 ByteBuffer signature = getLengthPrefixedSlice(signatures);
242                 if (signature.remaining() < 8) {
243                     throw new SecurityException("Signature record too short");
244                 }
245                 int sigAlgorithm = signature.getInt();
246                 signaturesSigAlgorithms.add(sigAlgorithm);
247                 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
248                     continue;
249                 }
250                 if ((bestSigAlgorithm == -1)
251                         || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
252                     bestSigAlgorithm = sigAlgorithm;
253                     bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
254                 }
255             } catch (IOException | BufferUnderflowException e) {
256                 throw new SecurityException(
257                         "Failed to parse signature record #" + signatureCount,
258                         e);
259             }
260         }
261         if (bestSigAlgorithm == -1) {
262             if (signatureCount == 0) {
263                 throw new SecurityException("No signatures found");
264             } else {
265                 throw new SecurityException("No supported signatures found");
266             }
267         }
268 
269         String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
270         Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
271                 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
272         String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
273         AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
274         boolean sigVerified;
275         try {
276             PublicKey publicKey =
277                     KeyFactory.getInstance(keyAlgorithm)
278                             .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
279             Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
280             sig.initVerify(publicKey);
281             if (jcaSignatureAlgorithmParams != null) {
282                 sig.setParameter(jcaSignatureAlgorithmParams);
283             }
284             sig.update(signedData);
285             sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
286         } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
287                 | InvalidAlgorithmParameterException | SignatureException e) {
288             throw new SecurityException(
289                     "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
290         }
291         if (!sigVerified) {
292             throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
293         }
294 
295         // Signature over signedData has verified.
296 
297         byte[] contentDigest = null;
298         signedData.clear();
299         ByteBuffer digests = getLengthPrefixedSlice(signedData);
300         List<Integer> digestsSigAlgorithms = new ArrayList<>();
301         int digestCount = 0;
302         while (digests.hasRemaining()) {
303             digestCount++;
304             try {
305                 ByteBuffer digest = getLengthPrefixedSlice(digests);
306                 if (digest.remaining() < 8) {
307                     throw new IOException("Record too short");
308                 }
309                 int sigAlgorithm = digest.getInt();
310                 digestsSigAlgorithms.add(sigAlgorithm);
311                 if (sigAlgorithm == bestSigAlgorithm) {
312                     contentDigest = readLengthPrefixedByteArray(digest);
313                 }
314             } catch (IOException | BufferUnderflowException e) {
315                 throw new IOException("Failed to parse digest record #" + digestCount, e);
316             }
317         }
318 
319         if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
320             throw new SecurityException(
321                     "Signature algorithms don't match between digests and signatures records");
322         }
323         int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
324         byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
325         if ((previousSignerDigest != null)
326                 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
327             throw new SecurityException(
328                     getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
329                     + " contents digest does not match the digest specified by a preceding signer");
330         }
331 
332         ByteBuffer certificates = getLengthPrefixedSlice(signedData);
333         List<X509Certificate> certs = new ArrayList<>();
334         int certificateCount = 0;
335         while (certificates.hasRemaining()) {
336             certificateCount++;
337             byte[] encodedCert = readLengthPrefixedByteArray(certificates);
338             X509Certificate certificate;
339             try {
340                 certificate = (X509Certificate)
341                         certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
342             } catch (CertificateException e) {
343                 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
344             }
345             certificate = new VerbatimX509Certificate(
346                     certificate, encodedCert);
347             certs.add(certificate);
348         }
349 
350         if (certs.isEmpty()) {
351             throw new SecurityException("No certificates listed");
352         }
353         X509Certificate mainCertificate = certs.get(0);
354         byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
355         if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
356             throw new SecurityException(
357                     "Public key mismatch between certificate and signature record");
358         }
359 
360         ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
361         verifyAdditionalAttributes(additionalAttrs);
362 
363         return certs.toArray(new X509Certificate[certs.size()]);
364     }
365 
366     // Attribute to check whether a newer APK Signature Scheme signature was stripped
367     private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d;
368 
verifyAdditionalAttributes(ByteBuffer attrs)369     private static void verifyAdditionalAttributes(ByteBuffer attrs)
370             throws SecurityException, IOException {
371         while (attrs.hasRemaining()) {
372             ByteBuffer attr = getLengthPrefixedSlice(attrs);
373             if (attr.remaining() < 4) {
374                 throw new IOException("Remaining buffer too short to contain additional attribute "
375                         + "ID. Remaining: " + attr.remaining());
376             }
377             int id = attr.getInt();
378             switch (id) {
379                 case STRIPPING_PROTECTION_ATTR_ID:
380                     if (attr.remaining() < 4) {
381                         throw new IOException("V2 Signature Scheme Stripping Protection Attribute "
382                                 + " value too small.  Expected 4 bytes, but found "
383                                 + attr.remaining());
384                     }
385                     int vers = attr.getInt();
386                     if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
387                         throw new SecurityException("V2 signature indicates APK is signed using APK"
388                                 + " Signature Scheme v3, but none was found. Signature stripped?");
389                     }
390                     break;
391                 default:
392                     // not the droid we're looking for, move along, move along.
393                     break;
394             }
395         }
396         return;
397     }
398 
getVerityRootHash(String apkPath)399     static byte[] getVerityRootHash(String apkPath)
400             throws IOException, SignatureNotFoundException, SecurityException {
401         try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
402             SignatureInfo signatureInfo = findSignature(apk);
403             VerifiedSigner vSigner = verify(apk, false);
404             return vSigner.verityRootHash;
405         }
406     }
407 
generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)408     static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
409             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
410                    NoSuchAlgorithmException {
411         try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
412             SignatureInfo signatureInfo = findSignature(apk);
413             return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
414         }
415     }
416 
generateApkVerityRootHash(String apkPath)417     static byte[] generateApkVerityRootHash(String apkPath)
418             throws IOException, SignatureNotFoundException, DigestException,
419                    NoSuchAlgorithmException {
420         try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
421             SignatureInfo signatureInfo = findSignature(apk);
422             VerifiedSigner vSigner = verify(apk, false);
423             if (vSigner.verityRootHash == null) {
424                 return null;
425             }
426             return VerityBuilder.generateApkVerityRootHash(
427                     apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
428         }
429     }
430 
isSupportedSignatureAlgorithm(int sigAlgorithm)431     private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
432         switch (sigAlgorithm) {
433             case SIGNATURE_RSA_PSS_WITH_SHA256:
434             case SIGNATURE_RSA_PSS_WITH_SHA512:
435             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
436             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
437             case SIGNATURE_ECDSA_WITH_SHA256:
438             case SIGNATURE_ECDSA_WITH_SHA512:
439             case SIGNATURE_DSA_WITH_SHA256:
440             case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
441             case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
442             case SIGNATURE_VERITY_DSA_WITH_SHA256:
443                 return true;
444             default:
445                 return false;
446         }
447     }
448 
449     /**
450      * Verified APK Signature Scheme v2 signer.
451      *
452      * @hide for internal use only.
453      */
454     public static class VerifiedSigner {
455         public final X509Certificate[][] certs;
456         public final byte[] verityRootHash;
457 
VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash)458         public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) {
459             this.certs = certs;
460             this.verityRootHash = verityRootHash;
461         }
462 
463     }
464 }
465