blob: abd04cc2b0e715bedb1a1c93b8def73b28978a46 [file] [log] [blame]
Daniel Cashman67096e02017-12-28 12:46:33 -08001/*
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
17package android.util.apk;
18
Victor Hsieh07bc80c2018-01-11 16:15:47 -080019import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
Daniel Cashman67096e02017-12-28 12:46:33 -080020import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
21import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
22import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
23import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
24import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
25import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
Khaled Abdelmohsenf10fc7a2020-03-02 19:02:28 +000026import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
Daniel Cashman67096e02017-12-28 12:46:33 -080027import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
28
29import android.os.Build;
30import android.util.ArrayMap;
31import android.util.Pair;
32
33import java.io.ByteArrayInputStream;
Daniel Cashman67096e02017-12-28 12:46:33 -080034import java.io.IOException;
35import java.io.RandomAccessFile;
36import java.nio.BufferUnderflowException;
37import java.nio.ByteBuffer;
Victor Hsieh07bc80c2018-01-11 16:15:47 -080038import java.security.DigestException;
Daniel Cashman67096e02017-12-28 12:46:33 -080039import java.security.InvalidAlgorithmParameterException;
40import java.security.InvalidKeyException;
41import java.security.KeyFactory;
42import java.security.MessageDigest;
43import java.security.NoSuchAlgorithmException;
44import java.security.PublicKey;
45import java.security.Signature;
46import java.security.SignatureException;
Dan Cashmancd4cb812018-01-02 14:55:58 -080047import java.security.cert.CertificateEncodingException;
Daniel Cashman67096e02017-12-28 12:46:33 -080048import java.security.cert.CertificateException;
49import java.security.cert.CertificateFactory;
50import java.security.cert.X509Certificate;
51import java.security.spec.AlgorithmParameterSpec;
52import java.security.spec.InvalidKeySpecException;
53import java.security.spec.X509EncodedKeySpec;
54import java.util.ArrayList;
55import java.util.Arrays;
Daniel Cashmanef054082018-03-28 15:58:14 -070056import java.util.HashSet;
Daniel Cashman67096e02017-12-28 12:46:33 -080057import java.util.List;
58import java.util.Map;
59
60/**
61 * APK Signature Scheme v3 verifier.
62 *
63 * @hide for internal use only.
64 */
65public 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 */
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 not
94 * verify.
95 * @throws IOException if an I/O error occurs while reading the APK file.
96 */
97 public static VerifiedSigner verify(String apkFile)
98 throws SignatureNotFoundException, SecurityException, IOException {
99 return verify(apkFile, true);
100 }
101
102 /**
103 * Returns the certificates associated with each signer for the given APK without verification.
104 * This method is dangerous and should not be used, unless the caller is absolutely certain the
105 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3
106 * Block while gathering signer information. The APK contents are not verified.
107 *
108 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
109 * @throws IOException if an I/O error occurs while reading the APK file.
110 */
Gavin Corkeryed521ab2019-01-31 16:59:41 +0000111 public static VerifiedSigner unsafeGetCertsWithoutVerification(String apkFile)
Daniel Cashman67096e02017-12-28 12:46:33 -0800112 throws SignatureNotFoundException, SecurityException, IOException {
113 return verify(apkFile, false);
114 }
115
116 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
117 throws SignatureNotFoundException, SecurityException, IOException {
118 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
119 return verify(apk, verifyIntegrity);
120 }
121 }
122
123 /**
124 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
125 * associated with each signer.
126 *
127 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
128 * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
129 * verify.
130 * @throws IOException if an I/O error occurs while reading the APK file.
131 */
132 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
133 throws SignatureNotFoundException, SecurityException, IOException {
134 SignatureInfo signatureInfo = findSignature(apk);
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800135 return verify(apk, signatureInfo, verifyIntegrity);
Daniel Cashman67096e02017-12-28 12:46:33 -0800136 }
137
138 /**
139 * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
140 * additional information relevant for verifying the block against the file.
141 *
142 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
143 * @throws IOException if an I/O error occurs while reading the APK file.
144 */
145 private static SignatureInfo findSignature(RandomAccessFile apk)
146 throws IOException, SignatureNotFoundException {
147 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
148 }
149
150 /**
151 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
152 * Block.
153 *
154 * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
155 * against the APK file.
156 */
157 private static VerifiedSigner verify(
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800158 RandomAccessFile apk,
Daniel Cashman67096e02017-12-28 12:46:33 -0800159 SignatureInfo signatureInfo,
Victor Hsieh4ba1eea2018-03-02 14:26:19 -0800160 boolean doVerifyIntegrity) throws SecurityException, IOException {
Daniel Cashman67096e02017-12-28 12:46:33 -0800161 int signerCount = 0;
162 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
163 VerifiedSigner result = null;
164 CertificateFactory certFactory;
165 try {
166 certFactory = CertificateFactory.getInstance("X.509");
167 } catch (CertificateException e) {
168 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
169 }
170 ByteBuffer signers;
171 try {
172 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
173 } catch (IOException e) {
174 throw new SecurityException("Failed to read list of signers", e);
175 }
176 while (signers.hasRemaining()) {
177 try {
178 ByteBuffer signer = getLengthPrefixedSlice(signers);
179 result = verifySigner(signer, contentDigests, certFactory);
180 signerCount++;
181 } catch (PlatformNotSupportedException e) {
182 // this signer is for a different platform, ignore it.
183 continue;
184 } catch (IOException | BufferUnderflowException | SecurityException e) {
185 throw new SecurityException(
186 "Failed to parse/verify signer #" + signerCount + " block",
187 e);
188 }
189 }
190
191 if (signerCount < 1 || result == null) {
192 throw new SecurityException("No signers found");
193 }
194
195 if (signerCount != 1) {
196 throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
197 + "multiple signers found.");
198 }
199
200 if (contentDigests.isEmpty()) {
201 throw new SecurityException("No content digests found");
202 }
203
204 if (doVerifyIntegrity) {
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800205 ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
Daniel Cashman67096e02017-12-28 12:46:33 -0800206 }
207
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800208 if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
Victor Hsieh4ba1eea2018-03-02 14:26:19 -0800209 byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
210 result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
211 verityDigest, apk.length(), signatureInfo);
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800212 }
213
Daniel Cashman67096e02017-12-28 12:46:33 -0800214 return result;
215 }
216
217 private static VerifiedSigner verifySigner(
218 ByteBuffer signerBlock,
219 Map<Integer, byte[]> contentDigests,
220 CertificateFactory certFactory)
221 throws SecurityException, IOException, PlatformNotSupportedException {
222 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
223 int minSdkVersion = signerBlock.getInt();
224 int maxSdkVersion = signerBlock.getInt();
225
226 if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
227 // this signature isn't meant to be used with this platform, skip it.
228 throw new PlatformNotSupportedException(
229 "Signer not supported by this platform "
230 + "version. This platform: " + Build.VERSION.SDK_INT
231 + ", signer minSdkVersion: " + minSdkVersion
232 + ", maxSdkVersion: " + maxSdkVersion);
233 }
234
235 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
236 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
237
238 int signatureCount = 0;
239 int bestSigAlgorithm = -1;
240 byte[] bestSigAlgorithmSignatureBytes = null;
241 List<Integer> signaturesSigAlgorithms = new ArrayList<>();
242 while (signatures.hasRemaining()) {
243 signatureCount++;
244 try {
245 ByteBuffer signature = getLengthPrefixedSlice(signatures);
246 if (signature.remaining() < 8) {
247 throw new SecurityException("Signature record too short");
248 }
249 int sigAlgorithm = signature.getInt();
250 signaturesSigAlgorithms.add(sigAlgorithm);
251 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
252 continue;
253 }
254 if ((bestSigAlgorithm == -1)
255 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
256 bestSigAlgorithm = sigAlgorithm;
257 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
258 }
259 } catch (IOException | BufferUnderflowException e) {
260 throw new SecurityException(
261 "Failed to parse signature record #" + signatureCount,
262 e);
263 }
264 }
265 if (bestSigAlgorithm == -1) {
266 if (signatureCount == 0) {
267 throw new SecurityException("No signatures found");
268 } else {
269 throw new SecurityException("No supported signatures found");
270 }
271 }
272
273 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
274 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
275 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
276 String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
277 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
278 boolean sigVerified;
279 try {
280 PublicKey publicKey =
281 KeyFactory.getInstance(keyAlgorithm)
282 .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
283 Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
284 sig.initVerify(publicKey);
285 if (jcaSignatureAlgorithmParams != null) {
286 sig.setParameter(jcaSignatureAlgorithmParams);
287 }
288 sig.update(signedData);
289 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
290 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
291 | InvalidAlgorithmParameterException | SignatureException e) {
292 throw new SecurityException(
293 "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
294 }
295 if (!sigVerified) {
296 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
297 }
298
299 // Signature over signedData has verified.
300
301 byte[] contentDigest = null;
302 signedData.clear();
303 ByteBuffer digests = getLengthPrefixedSlice(signedData);
304 List<Integer> digestsSigAlgorithms = new ArrayList<>();
305 int digestCount = 0;
306 while (digests.hasRemaining()) {
307 digestCount++;
308 try {
309 ByteBuffer digest = getLengthPrefixedSlice(digests);
310 if (digest.remaining() < 8) {
311 throw new IOException("Record too short");
312 }
313 int sigAlgorithm = digest.getInt();
314 digestsSigAlgorithms.add(sigAlgorithm);
315 if (sigAlgorithm == bestSigAlgorithm) {
316 contentDigest = readLengthPrefixedByteArray(digest);
317 }
318 } catch (IOException | BufferUnderflowException e) {
319 throw new IOException("Failed to parse digest record #" + digestCount, e);
320 }
321 }
322
323 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
324 throw new SecurityException(
325 "Signature algorithms don't match between digests and signatures records");
326 }
327 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
328 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
329 if ((previousSignerDigest != null)
330 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
331 throw new SecurityException(
332 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
333 + " contents digest does not match the digest specified by a preceding signer");
334 }
335
336 ByteBuffer certificates = getLengthPrefixedSlice(signedData);
337 List<X509Certificate> certs = new ArrayList<>();
338 int certificateCount = 0;
339 while (certificates.hasRemaining()) {
340 certificateCount++;
341 byte[] encodedCert = readLengthPrefixedByteArray(certificates);
342 X509Certificate certificate;
343 try {
344 certificate = (X509Certificate)
345 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
346 } catch (CertificateException e) {
347 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
348 }
349 certificate = new VerbatimX509Certificate(
350 certificate, encodedCert);
351 certs.add(certificate);
352 }
353
354 if (certs.isEmpty()) {
355 throw new SecurityException("No certificates listed");
356 }
357 X509Certificate mainCertificate = certs.get(0);
358 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
359 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
360 throw new SecurityException(
361 "Public key mismatch between certificate and signature record");
362 }
363
364 int signedMinSDK = signedData.getInt();
365 if (signedMinSDK != minSdkVersion) {
366 throw new SecurityException(
367 "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
368 }
369
370 int signedMaxSDK = signedData.getInt();
371 if (signedMaxSDK != maxSdkVersion) {
372 throw new SecurityException(
373 "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
374 }
375
376 ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
377 return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
378 }
379
380 private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
381
382 private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
383 List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
384 X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
385 VerifiedProofOfRotation por = null;
386
387 while (attrs.hasRemaining()) {
388 ByteBuffer attr = getLengthPrefixedSlice(attrs);
389 if (attr.remaining() < 4) {
390 throw new IOException("Remaining buffer too short to contain additional attribute "
391 + "ID. Remaining: " + attr.remaining());
392 }
393 int id = attr.getInt();
394 switch(id) {
395 case PROOF_OF_ROTATION_ATTR_ID:
396 if (por != null) {
397 throw new SecurityException("Encountered multiple Proof-of-rotation records"
Dan Cashmancd4cb812018-01-02 14:55:58 -0800398 + " when verifying APK Signature Scheme v3 signature");
Daniel Cashman67096e02017-12-28 12:46:33 -0800399 }
400 por = verifyProofOfRotationStruct(attr, certFactory);
Dan Cashmancd4cb812018-01-02 14:55:58 -0800401 // make sure that the last certificate in the Proof-of-rotation record matches
402 // the one used to sign this APK.
403 try {
404 if (por.certs.size() > 0
405 && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
406 certChain[0].getEncoded())) {
407 throw new SecurityException("Terminal certificate in Proof-of-rotation"
408 + " record does not match APK signing certificate");
409 }
410 } catch (CertificateEncodingException e) {
411 throw new SecurityException("Failed to encode certificate when comparing"
412 + " Proof-of-rotation record and signing certificate", e);
413 }
414
Daniel Cashman67096e02017-12-28 12:46:33 -0800415 break;
416 default:
417 // not the droid we're looking for, move along, move along.
418 break;
419 }
420 }
421 return new VerifiedSigner(certChain, por);
422 }
423
424 private static VerifiedProofOfRotation verifyProofOfRotationStruct(
425 ByteBuffer porBuf,
426 CertificateFactory certFactory)
427 throws SecurityException, IOException {
428 int levelCount = 0;
429 int lastSigAlgorithm = -1;
430 X509Certificate lastCert = null;
431 List<X509Certificate> certs = new ArrayList<>();
432 List<Integer> flagsList = new ArrayList<>();
433
434 // Proof-of-rotation struct:
Dan Cashmana656b8b2018-01-26 13:53:59 -0800435 // A uint32 version code followed by basically a singly linked list of nodes, called levels
436 // here, each of which have the following structure:
Daniel Cashman67096e02017-12-28 12:46:33 -0800437 // * length-prefix for the entire level
438 // - length-prefixed signed data (if previous level exists)
439 // * length-prefixed X509 Certificate
440 // * uint32 signature algorithm ID describing how this signed data was signed
441 // - uint32 flags describing how to treat the cert contained in this level
442 // - uint32 signature algorithm ID to use to verify the signature of the next level. The
443 // algorithm here must match the one in the signed data section of the next level.
444 // - length-prefixed signature over the signed data in this level. The signature here
445 // is verified using the certificate from the previous level.
446 // The linking is provided by the certificate of each level signing the one of the next.
Dan Cashmana656b8b2018-01-26 13:53:59 -0800447
448 try {
449
450 // get the version code, but don't do anything with it: creator knew about all our flags
451 porBuf.getInt();
Daniel Cashmanef054082018-03-28 15:58:14 -0700452 HashSet<X509Certificate> certHistorySet = new HashSet<>();
Dan Cashmana656b8b2018-01-26 13:53:59 -0800453 while (porBuf.hasRemaining()) {
454 levelCount++;
Daniel Cashman67096e02017-12-28 12:46:33 -0800455 ByteBuffer level = getLengthPrefixedSlice(porBuf);
456 ByteBuffer signedData = getLengthPrefixedSlice(level);
457 int flags = level.getInt();
458 int sigAlgorithm = level.getInt();
459 byte[] signature = readLengthPrefixedByteArray(level);
460
461 if (lastCert != null) {
462 // Use previous level cert to verify current level
463 Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
464 getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
465 PublicKey publicKey = lastCert.getPublicKey();
466 Signature sig = Signature.getInstance(sigAlgParams.first);
467 sig.initVerify(publicKey);
468 if (sigAlgParams.second != null) {
469 sig.setParameter(sigAlgParams.second);
470 }
471 sig.update(signedData);
472 if (!sig.verify(signature)) {
473 throw new SecurityException("Unable to verify signature of certificate #"
474 + levelCount + " using " + sigAlgParams.first + " when verifying"
475 + " Proof-of-rotation record");
476 }
477 }
478
Dan Cashman6dbf8372018-01-26 05:37:54 -0800479 signedData.rewind();
Daniel Cashman67096e02017-12-28 12:46:33 -0800480 byte[] encodedCert = readLengthPrefixedByteArray(signedData);
481 int signedSigAlgorithm = signedData.getInt();
482 if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
483 throw new SecurityException("Signing algorithm ID mismatch for certificate #"
484 + levelCount + " when verifying Proof-of-rotation record");
485 }
486 lastCert = (X509Certificate)
487 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
488 lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
489
490 lastSigAlgorithm = sigAlgorithm;
Daniel Cashmanef054082018-03-28 15:58:14 -0700491 if (certHistorySet.contains(lastCert)) {
492 throw new SecurityException("Encountered duplicate entries in "
493 + "Proof-of-rotation record at certificate #" + levelCount + ". All "
494 + "signing certificates should be unique");
495 }
496 certHistorySet.add(lastCert);
Daniel Cashman67096e02017-12-28 12:46:33 -0800497 certs.add(lastCert);
498 flagsList.add(flags);
Daniel Cashman67096e02017-12-28 12:46:33 -0800499 }
Dan Cashmana656b8b2018-01-26 13:53:59 -0800500 } catch (IOException | BufferUnderflowException e) {
501 throw new IOException("Failed to parse Proof-of-rotation record", e);
502 } catch (NoSuchAlgorithmException | InvalidKeyException
503 | InvalidAlgorithmParameterException | SignatureException e) {
504 throw new SecurityException(
505 "Failed to verify signature over signed data for certificate #"
506 + levelCount + " when verifying Proof-of-rotation record", e);
507 } catch (CertificateException e) {
508 throw new SecurityException("Failed to decode certificate #" + levelCount
509 + " when verifying Proof-of-rotation record", e);
Daniel Cashman67096e02017-12-28 12:46:33 -0800510 }
511 return new VerifiedProofOfRotation(certs, flagsList);
512 }
513
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800514 static byte[] getVerityRootHash(String apkPath)
515 throws IOException, SignatureNotFoundException, SecurityException {
516 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
517 SignatureInfo signatureInfo = findSignature(apk);
518 VerifiedSigner vSigner = verify(apk, false);
519 return vSigner.verityRootHash;
520 }
521 }
522
523 static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
524 throws IOException, SignatureNotFoundException, SecurityException, DigestException,
525 NoSuchAlgorithmException {
526 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
527 SignatureInfo signatureInfo = findSignature(apk);
Victor Hsieh27300922018-09-28 09:31:44 -0700528 return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800529 }
530 }
531
Victor Hsieh25195132018-09-06 16:32:06 -0700532 static byte[] generateApkVerityRootHash(String apkPath)
Victor Hsieh5f761242018-01-20 10:30:12 -0800533 throws NoSuchAlgorithmException, DigestException, IOException,
534 SignatureNotFoundException {
535 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
536 SignatureInfo signatureInfo = findSignature(apk);
537 VerifiedSigner vSigner = verify(apk, false);
538 if (vSigner.verityRootHash == null) {
539 return null;
540 }
Victor Hsieh27300922018-09-28 09:31:44 -0700541 return VerityBuilder.generateApkVerityRootHash(
Victor Hsieh5f761242018-01-20 10:30:12 -0800542 apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
543 }
544 }
545
Daniel Cashman67096e02017-12-28 12:46:33 -0800546 /**
547 * Verified processed proof of rotation.
548 *
549 * @hide for internal use only.
550 */
551 public static class VerifiedProofOfRotation {
552 public final List<X509Certificate> certs;
553 public final List<Integer> flagsList;
554
555 public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
556 this.certs = certs;
557 this.flagsList = flagsList;
558 }
559 }
560
561 /**
562 * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
563 *
564 * @hide for internal use only.
565 */
566 public static class VerifiedSigner {
567 public final X509Certificate[] certs;
568 public final VerifiedProofOfRotation por;
569
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800570 public byte[] verityRootHash;
571
Daniel Cashman67096e02017-12-28 12:46:33 -0800572 public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
573 this.certs = certs;
574 this.por = por;
575 }
576
577 }
578
579 private static class PlatformNotSupportedException extends Exception {
580
581 PlatformNotSupportedException(String s) {
582 super(s);
583 }
584 }
585}