blob: 2437af26770ba6cbca2726ec66a4cbfda4c41ce5 [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
Alex Buynytskyyf5e605a2020-03-13 13:31:12 -070019import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
20import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
Victor Hsieh07bc80c2018-01-11 16:15:47 -080021import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
Daniel Cashman67096e02017-12-28 12:46:33 -080022import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
23import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
24import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
25import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
26import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
27import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
Khaled Abdelmohsenf10fc7a2020-03-02 19:02:28 +000028import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
Daniel Cashman67096e02017-12-28 12:46:33 -080029import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
30
31import android.os.Build;
32import android.util.ArrayMap;
33import android.util.Pair;
34
35import java.io.ByteArrayInputStream;
Daniel Cashman67096e02017-12-28 12:46:33 -080036import java.io.IOException;
37import java.io.RandomAccessFile;
38import java.nio.BufferUnderflowException;
39import java.nio.ByteBuffer;
Victor Hsieh07bc80c2018-01-11 16:15:47 -080040import java.security.DigestException;
Daniel Cashman67096e02017-12-28 12:46:33 -080041import java.security.InvalidAlgorithmParameterException;
42import java.security.InvalidKeyException;
43import java.security.KeyFactory;
44import java.security.MessageDigest;
45import java.security.NoSuchAlgorithmException;
46import java.security.PublicKey;
47import java.security.Signature;
48import java.security.SignatureException;
Dan Cashmancd4cb812018-01-02 14:55:58 -080049import java.security.cert.CertificateEncodingException;
Daniel Cashman67096e02017-12-28 12:46:33 -080050import java.security.cert.CertificateException;
51import java.security.cert.CertificateFactory;
52import java.security.cert.X509Certificate;
53import java.security.spec.AlgorithmParameterSpec;
54import java.security.spec.InvalidKeySpecException;
55import java.security.spec.X509EncodedKeySpec;
56import java.util.ArrayList;
57import java.util.Arrays;
Daniel Cashmanef054082018-03-28 15:58:14 -070058import java.util.HashSet;
Daniel Cashman67096e02017-12-28 12:46:33 -080059import java.util.List;
60import java.util.Map;
61
62/**
63 * APK Signature Scheme v3 verifier.
64 *
65 * @hide for internal use only.
66 */
67public class ApkSignatureSchemeV3Verifier {
68
69 /**
70 * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
71 */
72 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
73
74 private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
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 */
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 not
96 * verify.
97 * @throws IOException if an I/O error occurs while reading the APK file.
98 */
99 public static VerifiedSigner verify(String apkFile)
100 throws SignatureNotFoundException, SecurityException, IOException {
101 return verify(apkFile, true);
102 }
103
104 /**
105 * Returns the certificates associated with each signer for the given APK without verification.
106 * This method is dangerous and should not be used, unless the caller is absolutely certain the
107 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3
108 * Block while gathering signer information. The APK contents are not verified.
109 *
110 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
111 * @throws IOException if an I/O error occurs while reading the APK file.
112 */
Gavin Corkeryed521ab2019-01-31 16:59:41 +0000113 public static VerifiedSigner unsafeGetCertsWithoutVerification(String apkFile)
Daniel Cashman67096e02017-12-28 12:46:33 -0800114 throws SignatureNotFoundException, SecurityException, IOException {
115 return verify(apkFile, false);
116 }
117
118 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
119 throws SignatureNotFoundException, SecurityException, IOException {
120 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
121 return verify(apk, verifyIntegrity);
122 }
123 }
124
125 /**
126 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
127 * associated with each signer.
128 *
129 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
130 * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
131 * verify.
132 * @throws IOException if an I/O error occurs while reading the APK file.
133 */
134 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
135 throws SignatureNotFoundException, SecurityException, IOException {
136 SignatureInfo signatureInfo = findSignature(apk);
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800137 return verify(apk, signatureInfo, verifyIntegrity);
Daniel Cashman67096e02017-12-28 12:46:33 -0800138 }
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 */
147 private 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 */
159 private static VerifiedSigner verify(
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800160 RandomAccessFile apk,
Daniel Cashman67096e02017-12-28 12:46:33 -0800161 SignatureInfo signatureInfo,
Victor Hsieh4ba1eea2018-03-02 14:26:19 -0800162 boolean doVerifyIntegrity) throws SecurityException, IOException {
Daniel Cashman67096e02017-12-28 12:46:33 -0800163 int signerCount = 0;
164 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
165 VerifiedSigner 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) {
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800207 ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
Daniel Cashman67096e02017-12-28 12:46:33 -0800208 }
209
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800210 if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
Victor Hsieh4ba1eea2018-03-02 14:26:19 -0800211 byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
212 result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
213 verityDigest, apk.length(), signatureInfo);
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800214 }
215
Yurii Zubrytskyi087cf412020-03-16 18:20:56 -0700216 result.digest = pickBestV3DigestForV4(contentDigests);
Alex Buynytskyyf5e605a2020-03-13 13:31:12 -0700217
Daniel Cashman67096e02017-12-28 12:46:33 -0800218 return result;
219 }
220
Yurii Zubrytskyi087cf412020-03-16 18:20:56 -0700221 // Keep in sync with pickBestV3DigestForV4 in apksigner.V3SchemeVerifier.
222 private static byte[] pickBestV3DigestForV4(Map<Integer, byte[]> contentDigests) {
223 final int[] orderedContentDigestTypes =
224 {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
225 CONTENT_DIGEST_CHUNKED_SHA256};
226 for (int contentDigestType : orderedContentDigestTypes) {
227 if (contentDigests.containsKey(contentDigestType)) {
228 return contentDigests.get(contentDigestType);
229 }
230 }
231 return null;
232 }
233
Daniel Cashman67096e02017-12-28 12:46:33 -0800234 private static VerifiedSigner verifySigner(
235 ByteBuffer signerBlock,
236 Map<Integer, byte[]> contentDigests,
237 CertificateFactory certFactory)
238 throws SecurityException, IOException, PlatformNotSupportedException {
239 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
240 int minSdkVersion = signerBlock.getInt();
241 int maxSdkVersion = signerBlock.getInt();
242
243 if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
244 // this signature isn't meant to be used with this platform, skip it.
245 throw new PlatformNotSupportedException(
246 "Signer not supported by this platform "
247 + "version. This platform: " + Build.VERSION.SDK_INT
248 + ", signer minSdkVersion: " + minSdkVersion
249 + ", maxSdkVersion: " + maxSdkVersion);
250 }
251
252 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
253 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
254
255 int signatureCount = 0;
256 int bestSigAlgorithm = -1;
257 byte[] bestSigAlgorithmSignatureBytes = null;
258 List<Integer> signaturesSigAlgorithms = new ArrayList<>();
259 while (signatures.hasRemaining()) {
260 signatureCount++;
261 try {
262 ByteBuffer signature = getLengthPrefixedSlice(signatures);
263 if (signature.remaining() < 8) {
264 throw new SecurityException("Signature record too short");
265 }
266 int sigAlgorithm = signature.getInt();
267 signaturesSigAlgorithms.add(sigAlgorithm);
268 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
269 continue;
270 }
271 if ((bestSigAlgorithm == -1)
272 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
273 bestSigAlgorithm = sigAlgorithm;
274 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
275 }
276 } catch (IOException | BufferUnderflowException e) {
277 throw new SecurityException(
278 "Failed to parse signature record #" + signatureCount,
279 e);
280 }
281 }
282 if (bestSigAlgorithm == -1) {
283 if (signatureCount == 0) {
284 throw new SecurityException("No signatures found");
285 } else {
286 throw new SecurityException("No supported signatures found");
287 }
288 }
289
290 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
291 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
292 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
293 String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
294 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
295 boolean sigVerified;
296 try {
297 PublicKey publicKey =
298 KeyFactory.getInstance(keyAlgorithm)
299 .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
300 Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
301 sig.initVerify(publicKey);
302 if (jcaSignatureAlgorithmParams != null) {
303 sig.setParameter(jcaSignatureAlgorithmParams);
304 }
305 sig.update(signedData);
306 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
307 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
308 | InvalidAlgorithmParameterException | SignatureException e) {
309 throw new SecurityException(
310 "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
311 }
312 if (!sigVerified) {
313 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
314 }
315
316 // Signature over signedData has verified.
317
318 byte[] contentDigest = null;
319 signedData.clear();
320 ByteBuffer digests = getLengthPrefixedSlice(signedData);
321 List<Integer> digestsSigAlgorithms = new ArrayList<>();
322 int digestCount = 0;
323 while (digests.hasRemaining()) {
324 digestCount++;
325 try {
326 ByteBuffer digest = getLengthPrefixedSlice(digests);
327 if (digest.remaining() < 8) {
328 throw new IOException("Record too short");
329 }
330 int sigAlgorithm = digest.getInt();
331 digestsSigAlgorithms.add(sigAlgorithm);
332 if (sigAlgorithm == bestSigAlgorithm) {
333 contentDigest = readLengthPrefixedByteArray(digest);
334 }
335 } catch (IOException | BufferUnderflowException e) {
336 throw new IOException("Failed to parse digest record #" + digestCount, e);
337 }
338 }
339
340 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
341 throw new SecurityException(
342 "Signature algorithms don't match between digests and signatures records");
343 }
344 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
345 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
346 if ((previousSignerDigest != null)
347 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
348 throw new SecurityException(
349 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
350 + " contents digest does not match the digest specified by a preceding signer");
351 }
352
353 ByteBuffer certificates = getLengthPrefixedSlice(signedData);
354 List<X509Certificate> certs = new ArrayList<>();
355 int certificateCount = 0;
356 while (certificates.hasRemaining()) {
357 certificateCount++;
358 byte[] encodedCert = readLengthPrefixedByteArray(certificates);
359 X509Certificate certificate;
360 try {
361 certificate = (X509Certificate)
362 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
363 } catch (CertificateException e) {
364 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
365 }
366 certificate = new VerbatimX509Certificate(
367 certificate, encodedCert);
368 certs.add(certificate);
369 }
370
371 if (certs.isEmpty()) {
372 throw new SecurityException("No certificates listed");
373 }
374 X509Certificate mainCertificate = certs.get(0);
375 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
376 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
377 throw new SecurityException(
378 "Public key mismatch between certificate and signature record");
379 }
380
381 int signedMinSDK = signedData.getInt();
382 if (signedMinSDK != minSdkVersion) {
383 throw new SecurityException(
384 "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
385 }
386
387 int signedMaxSDK = signedData.getInt();
388 if (signedMaxSDK != maxSdkVersion) {
389 throw new SecurityException(
390 "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
391 }
392
393 ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
394 return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
395 }
396
397 private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
398
399 private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
400 List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
401 X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
402 VerifiedProofOfRotation por = null;
403
404 while (attrs.hasRemaining()) {
405 ByteBuffer attr = getLengthPrefixedSlice(attrs);
406 if (attr.remaining() < 4) {
407 throw new IOException("Remaining buffer too short to contain additional attribute "
408 + "ID. Remaining: " + attr.remaining());
409 }
410 int id = attr.getInt();
411 switch(id) {
412 case PROOF_OF_ROTATION_ATTR_ID:
413 if (por != null) {
414 throw new SecurityException("Encountered multiple Proof-of-rotation records"
Dan Cashmancd4cb812018-01-02 14:55:58 -0800415 + " when verifying APK Signature Scheme v3 signature");
Daniel Cashman67096e02017-12-28 12:46:33 -0800416 }
417 por = verifyProofOfRotationStruct(attr, certFactory);
Dan Cashmancd4cb812018-01-02 14:55:58 -0800418 // make sure that the last certificate in the Proof-of-rotation record matches
419 // the one used to sign this APK.
420 try {
421 if (por.certs.size() > 0
422 && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
423 certChain[0].getEncoded())) {
424 throw new SecurityException("Terminal certificate in Proof-of-rotation"
425 + " record does not match APK signing certificate");
426 }
427 } catch (CertificateEncodingException e) {
428 throw new SecurityException("Failed to encode certificate when comparing"
429 + " Proof-of-rotation record and signing certificate", e);
430 }
431
Daniel Cashman67096e02017-12-28 12:46:33 -0800432 break;
433 default:
434 // not the droid we're looking for, move along, move along.
435 break;
436 }
437 }
438 return new VerifiedSigner(certChain, por);
439 }
440
441 private static VerifiedProofOfRotation verifyProofOfRotationStruct(
442 ByteBuffer porBuf,
443 CertificateFactory certFactory)
444 throws SecurityException, IOException {
445 int levelCount = 0;
446 int lastSigAlgorithm = -1;
447 X509Certificate lastCert = null;
448 List<X509Certificate> certs = new ArrayList<>();
449 List<Integer> flagsList = new ArrayList<>();
450
451 // Proof-of-rotation struct:
Dan Cashmana656b8b2018-01-26 13:53:59 -0800452 // A uint32 version code followed by basically a singly linked list of nodes, called levels
453 // here, each of which have the following structure:
Daniel Cashman67096e02017-12-28 12:46:33 -0800454 // * length-prefix for the entire level
455 // - length-prefixed signed data (if previous level exists)
456 // * length-prefixed X509 Certificate
457 // * uint32 signature algorithm ID describing how this signed data was signed
458 // - uint32 flags describing how to treat the cert contained in this level
459 // - uint32 signature algorithm ID to use to verify the signature of the next level. The
460 // algorithm here must match the one in the signed data section of the next level.
461 // - length-prefixed signature over the signed data in this level. The signature here
462 // is verified using the certificate from the previous level.
463 // The linking is provided by the certificate of each level signing the one of the next.
Dan Cashmana656b8b2018-01-26 13:53:59 -0800464
465 try {
466
467 // get the version code, but don't do anything with it: creator knew about all our flags
468 porBuf.getInt();
Daniel Cashmanef054082018-03-28 15:58:14 -0700469 HashSet<X509Certificate> certHistorySet = new HashSet<>();
Dan Cashmana656b8b2018-01-26 13:53:59 -0800470 while (porBuf.hasRemaining()) {
471 levelCount++;
Daniel Cashman67096e02017-12-28 12:46:33 -0800472 ByteBuffer level = getLengthPrefixedSlice(porBuf);
473 ByteBuffer signedData = getLengthPrefixedSlice(level);
474 int flags = level.getInt();
475 int sigAlgorithm = level.getInt();
476 byte[] signature = readLengthPrefixedByteArray(level);
477
478 if (lastCert != null) {
479 // Use previous level cert to verify current level
480 Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
481 getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
482 PublicKey publicKey = lastCert.getPublicKey();
483 Signature sig = Signature.getInstance(sigAlgParams.first);
484 sig.initVerify(publicKey);
485 if (sigAlgParams.second != null) {
486 sig.setParameter(sigAlgParams.second);
487 }
488 sig.update(signedData);
489 if (!sig.verify(signature)) {
490 throw new SecurityException("Unable to verify signature of certificate #"
491 + levelCount + " using " + sigAlgParams.first + " when verifying"
492 + " Proof-of-rotation record");
493 }
494 }
495
Dan Cashman6dbf8372018-01-26 05:37:54 -0800496 signedData.rewind();
Daniel Cashman67096e02017-12-28 12:46:33 -0800497 byte[] encodedCert = readLengthPrefixedByteArray(signedData);
498 int signedSigAlgorithm = signedData.getInt();
499 if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
500 throw new SecurityException("Signing algorithm ID mismatch for certificate #"
501 + levelCount + " when verifying Proof-of-rotation record");
502 }
503 lastCert = (X509Certificate)
504 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
505 lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
506
507 lastSigAlgorithm = sigAlgorithm;
Daniel Cashmanef054082018-03-28 15:58:14 -0700508 if (certHistorySet.contains(lastCert)) {
509 throw new SecurityException("Encountered duplicate entries in "
510 + "Proof-of-rotation record at certificate #" + levelCount + ". All "
511 + "signing certificates should be unique");
512 }
513 certHistorySet.add(lastCert);
Daniel Cashman67096e02017-12-28 12:46:33 -0800514 certs.add(lastCert);
515 flagsList.add(flags);
Daniel Cashman67096e02017-12-28 12:46:33 -0800516 }
Dan Cashmana656b8b2018-01-26 13:53:59 -0800517 } catch (IOException | BufferUnderflowException e) {
518 throw new IOException("Failed to parse Proof-of-rotation record", e);
519 } catch (NoSuchAlgorithmException | InvalidKeyException
520 | InvalidAlgorithmParameterException | SignatureException e) {
521 throw new SecurityException(
522 "Failed to verify signature over signed data for certificate #"
523 + levelCount + " when verifying Proof-of-rotation record", e);
524 } catch (CertificateException e) {
525 throw new SecurityException("Failed to decode certificate #" + levelCount
526 + " when verifying Proof-of-rotation record", e);
Daniel Cashman67096e02017-12-28 12:46:33 -0800527 }
528 return new VerifiedProofOfRotation(certs, flagsList);
529 }
530
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800531 static byte[] getVerityRootHash(String apkPath)
532 throws IOException, SignatureNotFoundException, SecurityException {
533 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
534 SignatureInfo signatureInfo = findSignature(apk);
535 VerifiedSigner vSigner = verify(apk, false);
536 return vSigner.verityRootHash;
537 }
538 }
539
540 static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
541 throws IOException, SignatureNotFoundException, SecurityException, DigestException,
542 NoSuchAlgorithmException {
543 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
544 SignatureInfo signatureInfo = findSignature(apk);
Victor Hsieh27300922018-09-28 09:31:44 -0700545 return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800546 }
547 }
548
Victor Hsieh25195132018-09-06 16:32:06 -0700549 static byte[] generateApkVerityRootHash(String apkPath)
Victor Hsieh5f761242018-01-20 10:30:12 -0800550 throws NoSuchAlgorithmException, DigestException, IOException,
551 SignatureNotFoundException {
552 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
553 SignatureInfo signatureInfo = findSignature(apk);
554 VerifiedSigner vSigner = verify(apk, false);
555 if (vSigner.verityRootHash == null) {
556 return null;
557 }
Victor Hsieh27300922018-09-28 09:31:44 -0700558 return VerityBuilder.generateApkVerityRootHash(
Victor Hsieh5f761242018-01-20 10:30:12 -0800559 apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
560 }
561 }
562
Daniel Cashman67096e02017-12-28 12:46:33 -0800563 /**
564 * Verified processed proof of rotation.
565 *
566 * @hide for internal use only.
567 */
568 public static class VerifiedProofOfRotation {
569 public final List<X509Certificate> certs;
570 public final List<Integer> flagsList;
571
572 public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
573 this.certs = certs;
574 this.flagsList = flagsList;
575 }
576 }
577
578 /**
579 * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
580 *
581 * @hide for internal use only.
582 */
583 public static class VerifiedSigner {
584 public final X509Certificate[] certs;
585 public final VerifiedProofOfRotation por;
586
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800587 public byte[] verityRootHash;
Alex Buynytskyyf5e605a2020-03-13 13:31:12 -0700588 public byte[] digest;
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800589
Daniel Cashman67096e02017-12-28 12:46:33 -0800590 public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
591 this.certs = certs;
592 this.por = por;
593 }
594
595 }
596
597 private static class PlatformNotSupportedException extends Exception {
598
599 PlatformNotSupportedException(String s) {
600 super(s);
601 }
602 }
603}