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 import java.util.OptionalInt;
60 
61 /**
62  * APK Signature Scheme v3 verifier.
63  *
64  * @hide for internal use only.
65  */
66 public class ApkSignatureSchemeV3Verifier {
67 
68     /**
69      * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
70      */
71     public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
72 
73     static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
74     static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61;
75 
76     /**
77      * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
78      *
79      * <p><b>NOTE: This method does not verify the signature.</b>
80      */
hasSignature(String apkFile)81     public static boolean hasSignature(String apkFile) throws IOException {
82         try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
83             findSignature(apk);
84             return true;
85         } catch (SignatureNotFoundException e) {
86             return false;
87         }
88     }
89 
90     /**
91      * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
92      * associated with each signer.
93      *
94      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
95      * @throws SecurityException          if the APK Signature Scheme v3 signature of this APK does
96      *                                    not
97      *                                    verify.
98      * @throws IOException                if an I/O error occurs while reading the APK file.
99      */
verify(String apkFile)100     public static VerifiedSigner verify(String apkFile)
101             throws SignatureNotFoundException, SecurityException, IOException {
102         return verify(apkFile, true);
103     }
104 
105     /**
106      * Returns the certificates associated with each signer for the given APK without verification.
107      * This method is dangerous and should not be used, unless the caller is absolutely certain the
108      * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v3
109      * Block while gathering signer information.  The APK contents are not verified.
110      *
111      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
112      * @throws IOException                if an I/O error occurs while reading the APK file.
113      */
unsafeGetCertsWithoutVerification(String apkFile)114     public static VerifiedSigner unsafeGetCertsWithoutVerification(String apkFile)
115             throws SignatureNotFoundException, SecurityException, IOException {
116         return verify(apkFile, false);
117     }
118 
verify(String apkFile, boolean verifyIntegrity)119     private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
120             throws SignatureNotFoundException, SecurityException, IOException {
121         try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
122             return verify(apk, verifyIntegrity);
123         }
124     }
125 
126     /**
127      * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
128      * associated with each signer.
129      *
130      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
131      * @throws SecurityException          if an APK Signature Scheme v3 signature of this APK does
132      *                                    not
133      *                                    verify.
134      * @throws IOException                if an I/O error occurs while reading the APK file.
135      */
verify(RandomAccessFile apk, boolean verifyIntegrity)136     private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
137             throws SignatureNotFoundException, SecurityException, IOException {
138         ApkSignatureSchemeV3Verifier verifier = new ApkSignatureSchemeV3Verifier(apk,
139                 verifyIntegrity);
140         try {
141             SignatureInfo signatureInfo = findSignature(apk, APK_SIGNATURE_SCHEME_V31_BLOCK_ID);
142             return verifier.verify(signatureInfo, APK_SIGNATURE_SCHEME_V31_BLOCK_ID);
143         } catch (SignatureNotFoundException ignored) {
144             // This is expected if the APK is not using v3.1 to target rotation.
145         } catch (PlatformNotSupportedException ignored) {
146             // This is expected if the APK is targeting a platform version later than that of the
147             // device for rotation.
148         }
149         try {
150             SignatureInfo signatureInfo = findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
151             return verifier.verify(signatureInfo, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
152         } catch (PlatformNotSupportedException e) {
153             throw new SecurityException(e);
154         }
155     }
156 
157     /**
158      * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
159      * additional information relevant for verifying the block against the file.
160      *
161      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
162      * @throws IOException                if an I/O error occurs while reading the APK file.
163      */
findSignature(RandomAccessFile apk)164     public static SignatureInfo findSignature(RandomAccessFile apk)
165             throws IOException, SignatureNotFoundException {
166         return findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
167     }
168 
169     /*
170      * Returns the APK Signature Scheme v3 block in the provided {@code apk} file with the specified
171      * {@code blockId} and the additional information relevant for verifying the block against the
172      * file.
173      *
174      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3
175      * @throws IOException                if an I/O error occurs while reading the APK file
176      **/
findSignature(RandomAccessFile apk, int blockId)177     private static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
178             throws IOException, SignatureNotFoundException {
179         return ApkSigningBlockUtils.findSignature(apk, blockId);
180     }
181 
182     private final RandomAccessFile mApk;
183     private final boolean mVerifyIntegrity;
184     private OptionalInt mOptionalRotationMinSdkVersion = OptionalInt.empty();
185     private int mSignerMinSdkVersion;
186     private int mBlockId;
187 
ApkSignatureSchemeV3Verifier(RandomAccessFile apk, boolean verifyIntegrity)188     private ApkSignatureSchemeV3Verifier(RandomAccessFile apk, boolean verifyIntegrity) {
189         mApk = apk;
190         mVerifyIntegrity = verifyIntegrity;
191     }
192 
193     /**
194      * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
195      * Block.
196      *
197      * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
198      *                      against the APK file.
199      */
verify(SignatureInfo signatureInfo, int blockId)200     private VerifiedSigner verify(SignatureInfo signatureInfo, int blockId)
201             throws SecurityException, IOException, PlatformNotSupportedException {
202         mBlockId = blockId;
203         int signerCount = 0;
204         Map<Integer, byte[]> contentDigests = new ArrayMap<>();
205         Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> result = null;
206         CertificateFactory certFactory;
207         try {
208             certFactory = CertificateFactory.getInstance("X.509");
209         } catch (CertificateException e) {
210             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
211         }
212         ByteBuffer signers;
213         try {
214             signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
215         } catch (IOException e) {
216             throw new SecurityException("Failed to read list of signers", e);
217         }
218         while (signers.hasRemaining()) {
219             try {
220                 ByteBuffer signer = getLengthPrefixedSlice(signers);
221                 result = verifySigner(signer, contentDigests, certFactory);
222                 signerCount++;
223             } catch (PlatformNotSupportedException e) {
224                 // this signer is for a different platform, ignore it.
225                 continue;
226             } catch (IOException | BufferUnderflowException | SecurityException e) {
227                 throw new SecurityException(
228                         "Failed to parse/verify signer #" + signerCount + " block",
229                         e);
230             }
231         }
232 
233         if (signerCount < 1 || result == null) {
234             // There must always be a valid signer targeting the device SDK version for a v3.0
235             // signature.
236             if (blockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
237                 throw new SecurityException("No signers found");
238             }
239             throw new PlatformNotSupportedException(
240                     "None of the signers support the current platform version");
241         }
242 
243         if (signerCount != 1) {
244             throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
245                     + "multiple signers found.");
246         }
247 
248         if (contentDigests.isEmpty()) {
249             throw new SecurityException("No content digests found");
250         }
251 
252         if (mVerifyIntegrity) {
253             ApkSigningBlockUtils.verifyIntegrity(contentDigests, mApk, signatureInfo);
254         }
255 
256         byte[] verityRootHash = null;
257         if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
258             byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
259             verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
260                     verityDigest, mApk.getChannel().size(), signatureInfo);
261         }
262 
263         return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests,
264                 blockId);
265     }
266 
267     private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, CertificateFactory certFactory)268             verifySigner(
269                 ByteBuffer signerBlock,
270                 Map<Integer, byte[]> contentDigests,
271                 CertificateFactory certFactory)
272             throws SecurityException, IOException, PlatformNotSupportedException {
273         ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
274         int minSdkVersion = signerBlock.getInt();
275         int maxSdkVersion = signerBlock.getInt();
276 
277         if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
278             // if this is a v3.1 block then save the minimum SDK version for rotation for comparison
279             // against the v3.0 additional attribute.
280             if (mBlockId == APK_SIGNATURE_SCHEME_V31_BLOCK_ID) {
281                 if (!mOptionalRotationMinSdkVersion.isPresent()
282                         || mOptionalRotationMinSdkVersion.getAsInt() > minSdkVersion) {
283                     mOptionalRotationMinSdkVersion = OptionalInt.of(minSdkVersion);
284                 }
285             }
286             // this signature isn't meant to be used with this platform, skip it.
287             throw new PlatformNotSupportedException(
288                     "Signer not supported by this platform "
289                             + "version. This platform: " + Build.VERSION.SDK_INT
290                             + ", signer minSdkVersion: " + minSdkVersion
291                             + ", maxSdkVersion: " + maxSdkVersion);
292         }
293 
294         ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
295         byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
296 
297         int signatureCount = 0;
298         int bestSigAlgorithm = -1;
299         byte[] bestSigAlgorithmSignatureBytes = null;
300         List<Integer> signaturesSigAlgorithms = new ArrayList<>();
301         while (signatures.hasRemaining()) {
302             signatureCount++;
303             try {
304                 ByteBuffer signature = getLengthPrefixedSlice(signatures);
305                 if (signature.remaining() < 8) {
306                     throw new SecurityException("Signature record too short");
307                 }
308                 int sigAlgorithm = signature.getInt();
309                 signaturesSigAlgorithms.add(sigAlgorithm);
310                 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
311                     continue;
312                 }
313                 if ((bestSigAlgorithm == -1)
314                         || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
315                     bestSigAlgorithm = sigAlgorithm;
316                     bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
317                 }
318             } catch (IOException | BufferUnderflowException e) {
319                 throw new SecurityException(
320                         "Failed to parse signature record #" + signatureCount,
321                         e);
322             }
323         }
324         if (bestSigAlgorithm == -1) {
325             if (signatureCount == 0) {
326                 throw new SecurityException("No signatures found");
327             } else {
328                 throw new SecurityException("No supported signatures found");
329             }
330         }
331 
332         String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
333         Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
334                 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
335         String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
336         AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
337         boolean sigVerified;
338         try {
339             PublicKey publicKey =
340                     KeyFactory.getInstance(keyAlgorithm)
341                             .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
342             Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
343             sig.initVerify(publicKey);
344             if (jcaSignatureAlgorithmParams != null) {
345                 sig.setParameter(jcaSignatureAlgorithmParams);
346             }
347             sig.update(signedData);
348             sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
349         } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
350                 | InvalidAlgorithmParameterException | SignatureException e) {
351             throw new SecurityException(
352                     "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
353         }
354         if (!sigVerified) {
355             throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
356         }
357 
358         // Signature over signedData has verified.
359 
360         byte[] contentDigest = null;
361         signedData.clear();
362         ByteBuffer digests = getLengthPrefixedSlice(signedData);
363         List<Integer> digestsSigAlgorithms = new ArrayList<>();
364         int digestCount = 0;
365         while (digests.hasRemaining()) {
366             digestCount++;
367             try {
368                 ByteBuffer digest = getLengthPrefixedSlice(digests);
369                 if (digest.remaining() < 8) {
370                     throw new IOException("Record too short");
371                 }
372                 int sigAlgorithm = digest.getInt();
373                 digestsSigAlgorithms.add(sigAlgorithm);
374                 if (sigAlgorithm == bestSigAlgorithm) {
375                     contentDigest = readLengthPrefixedByteArray(digest);
376                 }
377             } catch (IOException | BufferUnderflowException e) {
378                 throw new IOException("Failed to parse digest record #" + digestCount, e);
379             }
380         }
381 
382         if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
383             throw new SecurityException(
384                     "Signature algorithms don't match between digests and signatures records");
385         }
386         int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
387         byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
388         if ((previousSignerDigest != null)
389                 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
390             throw new SecurityException(
391                     getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
392                             + " contents digest does not match the digest specified by a "
393                             + "preceding signer");
394         }
395 
396         ByteBuffer certificates = getLengthPrefixedSlice(signedData);
397         List<X509Certificate> certs = new ArrayList<>();
398         int certificateCount = 0;
399         while (certificates.hasRemaining()) {
400             certificateCount++;
401             byte[] encodedCert = readLengthPrefixedByteArray(certificates);
402             X509Certificate certificate;
403             try {
404                 certificate = (X509Certificate)
405                         certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
406             } catch (CertificateException e) {
407                 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
408             }
409             certificate = new VerbatimX509Certificate(certificate, encodedCert);
410             certs.add(certificate);
411         }
412 
413         if (certs.isEmpty()) {
414             throw new SecurityException("No certificates listed");
415         }
416         X509Certificate mainCertificate = certs.get(0);
417         byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
418         if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
419             throw new SecurityException(
420                     "Public key mismatch between certificate and signature record");
421         }
422 
423         int signedMinSDK = signedData.getInt();
424         if (signedMinSDK != minSdkVersion) {
425             throw new SecurityException(
426                     "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
427         }
428         mSignerMinSdkVersion = signedMinSDK;
429 
430         int signedMaxSDK = signedData.getInt();
431         if (signedMaxSDK != maxSdkVersion) {
432             throw new SecurityException(
433                     "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
434         }
435 
436         ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
437         return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
438     }
439 
440     private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
441     private static final int ROTATION_MIN_SDK_VERSION_ATTR_ID = 0x559f8b02;
442     private static final int ROTATION_ON_DEV_RELEASE_ATTR_ID = 0xc2a6b3ba;
443 
444     private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> certs, CertificateFactory certFactory)445             verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> certs,
446                 CertificateFactory certFactory) throws IOException, PlatformNotSupportedException {
447         X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
448         ApkSigningBlockUtils.VerifiedProofOfRotation por = null;
449 
450         while (attrs.hasRemaining()) {
451             ByteBuffer attr = getLengthPrefixedSlice(attrs);
452             if (attr.remaining() < 4) {
453                 throw new IOException("Remaining buffer too short to contain additional attribute "
454                         + "ID. Remaining: " + attr.remaining());
455             }
456             int id = attr.getInt();
457             switch (id) {
458                 case PROOF_OF_ROTATION_ATTR_ID:
459                     if (por != null) {
460                         throw new SecurityException("Encountered multiple Proof-of-rotation records"
461                                 + " when verifying APK Signature Scheme v3 signature");
462                     }
463                     por = verifyProofOfRotationStruct(attr, certFactory);
464                     // make sure that the last certificate in the Proof-of-rotation record matches
465                     // the one used to sign this APK.
466                     try {
467                         if (por.certs.size() > 0
468                                 && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
469                                 certChain[0].getEncoded())) {
470                             throw new SecurityException("Terminal certificate in Proof-of-rotation"
471                                     + " record does not match APK signing certificate");
472                         }
473                     } catch (CertificateEncodingException e) {
474                         throw new SecurityException("Failed to encode certificate when comparing"
475                                 + " Proof-of-rotation record and signing certificate", e);
476                     }
477 
478                     break;
479                 case ROTATION_MIN_SDK_VERSION_ATTR_ID:
480                     if (attr.remaining() < 4) {
481                         throw new IOException(
482                                 "Remaining buffer too short to contain rotation minSdkVersion "
483                                         + "value. Remaining: "
484                                         + attr.remaining());
485                     }
486                     int attrRotationMinSdkVersion = attr.getInt();
487                     if (!mOptionalRotationMinSdkVersion.isPresent()) {
488                         throw new SecurityException(
489                                 "Expected a v3.1 signing block targeting SDK version "
490                                         + attrRotationMinSdkVersion
491                                         + ", but a v3.1 block was not found");
492                     }
493                     int rotationMinSdkVersion = mOptionalRotationMinSdkVersion.getAsInt();
494                     if (rotationMinSdkVersion != attrRotationMinSdkVersion) {
495                         throw new SecurityException(
496                                 "Expected a v3.1 signing block targeting SDK version "
497                                         + attrRotationMinSdkVersion
498                                         + ", but the v3.1 block was targeting "
499                                         + rotationMinSdkVersion);
500                     }
501                     break;
502                 case ROTATION_ON_DEV_RELEASE_ATTR_ID:
503                     // This attribute should only be used in a v3.1 signer as it allows the v3.1
504                     // signature scheme to target a platform under development.
505                     if (mBlockId == APK_SIGNATURE_SCHEME_V31_BLOCK_ID) {
506                         // A platform under development uses the same SDK version as the most
507                         // recently released platform; if this platform's SDK version is the same as
508                         // the minimum of the signer, then only allow this signer to be used if this
509                         // is not a "REL" platform.
510                         if (Build.VERSION.SDK_INT == mSignerMinSdkVersion
511                                 && "REL".equals(Build.VERSION.CODENAME)) {
512                             // Set the rotation-min-sdk-version to be verified against the stripping
513                             // attribute in the v3.0 block.
514                             mOptionalRotationMinSdkVersion = OptionalInt.of(mSignerMinSdkVersion);
515                             throw new PlatformNotSupportedException(
516                                     "The device is running a release version of "
517                                             + mSignerMinSdkVersion
518                                             + ", but the signer is targeting a dev release");
519                         }
520                     }
521                     break;
522                 default:
523                     // not the droid we're looking for, move along, move along.
524                     break;
525             }
526         }
527         return Pair.create(certChain, por);
528     }
529 
getVerityRootHash(String apkPath)530     static byte[] getVerityRootHash(String apkPath)
531             throws IOException, SignatureNotFoundException, SecurityException {
532         try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
533             SignatureInfo signatureInfo = findSignature(apk);
534             VerifiedSigner vSigner = verify(apk, false);
535             return vSigner.verityRootHash;
536         }
537     }
538 
generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)539     static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
540             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
541             NoSuchAlgorithmException {
542         try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
543             SignatureInfo signatureInfo = findSignature(apk);
544             return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
545         }
546     }
547 
548     /**
549      * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
550      *
551      * @hide for internal use only.
552      */
553     public static class VerifiedSigner {
554         public final X509Certificate[] certs;
555         public final ApkSigningBlockUtils.VerifiedProofOfRotation por;
556 
557         public final byte[] verityRootHash;
558         // Algorithm -> digest map of signed digests in the signature.
559         // All these are verified if requested.
560         public final Map<Integer, byte[]> contentDigests;
561 
562         // ID of the signature block used to verify.
563         public final int blockId;
564 
VerifiedSigner(X509Certificate[] certs, ApkSigningBlockUtils.VerifiedProofOfRotation por, byte[] verityRootHash, Map<Integer, byte[]> contentDigests, int blockId)565         public VerifiedSigner(X509Certificate[] certs,
566                 ApkSigningBlockUtils.VerifiedProofOfRotation por,
567                 byte[] verityRootHash, Map<Integer, byte[]> contentDigests,
568                 int blockId) {
569             this.certs = certs;
570             this.por = por;
571             this.verityRootHash = verityRootHash;
572             this.contentDigests = contentDigests;
573             this.blockId = blockId;
574         }
575 
576     }
577 
578     private static class PlatformNotSupportedException extends Exception {
579 
PlatformNotSupportedException(String s)580         PlatformNotSupportedException(String s) {
581             super(s);
582         }
583     }
584 }
585