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