blob: 1471870bd7d26488ba719496086b691695c36a08 [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.SIGNATURE_DSA_WITH_SHA256;
21import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
22import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
23import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
24import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
25import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
26import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
Victor Hsieh4acad4c2018-01-04 13:36:15 -080027import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
28import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
29import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
Daniel Cashman67096e02017-12-28 12:46:33 -080030import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
31import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
32import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
33import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
34import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
35import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
36import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
37
38import android.os.Build;
39import android.util.ArrayMap;
40import android.util.Pair;
41
42import java.io.ByteArrayInputStream;
Daniel Cashman67096e02017-12-28 12:46:33 -080043import java.io.IOException;
44import java.io.RandomAccessFile;
45import java.nio.BufferUnderflowException;
46import java.nio.ByteBuffer;
Victor Hsieh07bc80c2018-01-11 16:15:47 -080047import java.security.DigestException;
Daniel Cashman67096e02017-12-28 12:46:33 -080048import java.security.InvalidAlgorithmParameterException;
49import java.security.InvalidKeyException;
50import java.security.KeyFactory;
51import java.security.MessageDigest;
52import java.security.NoSuchAlgorithmException;
53import java.security.PublicKey;
54import java.security.Signature;
55import java.security.SignatureException;
Dan Cashmancd4cb812018-01-02 14:55:58 -080056import java.security.cert.CertificateEncodingException;
Daniel Cashman67096e02017-12-28 12:46:33 -080057import java.security.cert.CertificateException;
58import java.security.cert.CertificateFactory;
59import java.security.cert.X509Certificate;
60import java.security.spec.AlgorithmParameterSpec;
61import java.security.spec.InvalidKeySpecException;
62import java.security.spec.X509EncodedKeySpec;
63import java.util.ArrayList;
64import java.util.Arrays;
Daniel Cashmanef054082018-03-28 15:58:14 -070065import java.util.HashSet;
Daniel Cashman67096e02017-12-28 12:46:33 -080066import java.util.List;
67import java.util.Map;
68
69/**
70 * APK Signature Scheme v3 verifier.
71 *
72 * @hide for internal use only.
73 */
74public class ApkSignatureSchemeV3Verifier {
75
76 /**
77 * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
78 */
79 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
80
81 private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
82
83 /**
84 * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
85 *
86 * <p><b>NOTE: This method does not verify the signature.</b>
87 */
88 public static boolean hasSignature(String apkFile) throws IOException {
89 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
90 findSignature(apk);
91 return true;
92 } catch (SignatureNotFoundException e) {
93 return false;
94 }
95 }
96
97 /**
98 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
99 * associated with each signer.
100 *
101 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
102 * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
103 * verify.
104 * @throws IOException if an I/O error occurs while reading the APK file.
105 */
106 public static VerifiedSigner verify(String apkFile)
107 throws SignatureNotFoundException, SecurityException, IOException {
108 return verify(apkFile, true);
109 }
110
111 /**
112 * Returns the certificates associated with each signer for the given APK without verification.
113 * This method is dangerous and should not be used, unless the caller is absolutely certain the
114 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3
115 * Block while gathering signer information. The APK contents are not verified.
116 *
117 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
118 * @throws IOException if an I/O error occurs while reading the APK file.
119 */
120 public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
121 throws SignatureNotFoundException, SecurityException, IOException {
122 return verify(apkFile, false);
123 }
124
125 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
126 throws SignatureNotFoundException, SecurityException, IOException {
127 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
128 return verify(apk, verifyIntegrity);
129 }
130 }
131
132 /**
133 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
134 * associated with each signer.
135 *
136 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
137 * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
138 * verify.
139 * @throws IOException if an I/O error occurs while reading the APK file.
140 */
141 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
142 throws SignatureNotFoundException, SecurityException, IOException {
143 SignatureInfo signatureInfo = findSignature(apk);
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800144 return verify(apk, signatureInfo, verifyIntegrity);
Daniel Cashman67096e02017-12-28 12:46:33 -0800145 }
146
147 /**
148 * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
149 * additional information relevant for verifying the block against the file.
150 *
151 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
152 * @throws IOException if an I/O error occurs while reading the APK file.
153 */
154 private static SignatureInfo findSignature(RandomAccessFile apk)
155 throws IOException, SignatureNotFoundException {
156 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
157 }
158
159 /**
160 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
161 * Block.
162 *
163 * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
164 * against the APK file.
165 */
166 private static VerifiedSigner verify(
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800167 RandomAccessFile apk,
Daniel Cashman67096e02017-12-28 12:46:33 -0800168 SignatureInfo signatureInfo,
Victor Hsieh4ba1eea2018-03-02 14:26:19 -0800169 boolean doVerifyIntegrity) throws SecurityException, IOException {
Daniel Cashman67096e02017-12-28 12:46:33 -0800170 int signerCount = 0;
171 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
172 VerifiedSigner result = null;
173 CertificateFactory certFactory;
174 try {
175 certFactory = CertificateFactory.getInstance("X.509");
176 } catch (CertificateException e) {
177 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
178 }
179 ByteBuffer signers;
180 try {
181 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
182 } catch (IOException e) {
183 throw new SecurityException("Failed to read list of signers", e);
184 }
185 while (signers.hasRemaining()) {
186 try {
187 ByteBuffer signer = getLengthPrefixedSlice(signers);
188 result = verifySigner(signer, contentDigests, certFactory);
189 signerCount++;
190 } catch (PlatformNotSupportedException e) {
191 // this signer is for a different platform, ignore it.
192 continue;
193 } catch (IOException | BufferUnderflowException | SecurityException e) {
194 throw new SecurityException(
195 "Failed to parse/verify signer #" + signerCount + " block",
196 e);
197 }
198 }
199
200 if (signerCount < 1 || result == null) {
201 throw new SecurityException("No signers found");
202 }
203
204 if (signerCount != 1) {
205 throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
206 + "multiple signers found.");
207 }
208
209 if (contentDigests.isEmpty()) {
210 throw new SecurityException("No content digests found");
211 }
212
213 if (doVerifyIntegrity) {
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800214 ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
Daniel Cashman67096e02017-12-28 12:46:33 -0800215 }
216
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800217 if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
Victor Hsieh4ba1eea2018-03-02 14:26:19 -0800218 byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
219 result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
220 verityDigest, apk.length(), signatureInfo);
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800221 }
222
Daniel Cashman67096e02017-12-28 12:46:33 -0800223 return result;
224 }
225
226 private static VerifiedSigner verifySigner(
227 ByteBuffer signerBlock,
228 Map<Integer, byte[]> contentDigests,
229 CertificateFactory certFactory)
230 throws SecurityException, IOException, PlatformNotSupportedException {
231 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
232 int minSdkVersion = signerBlock.getInt();
233 int maxSdkVersion = signerBlock.getInt();
234
235 if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
236 // this signature isn't meant to be used with this platform, skip it.
237 throw new PlatformNotSupportedException(
238 "Signer not supported by this platform "
239 + "version. This platform: " + Build.VERSION.SDK_INT
240 + ", signer minSdkVersion: " + minSdkVersion
241 + ", maxSdkVersion: " + maxSdkVersion);
242 }
243
244 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
245 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
246
247 int signatureCount = 0;
248 int bestSigAlgorithm = -1;
249 byte[] bestSigAlgorithmSignatureBytes = null;
250 List<Integer> signaturesSigAlgorithms = new ArrayList<>();
251 while (signatures.hasRemaining()) {
252 signatureCount++;
253 try {
254 ByteBuffer signature = getLengthPrefixedSlice(signatures);
255 if (signature.remaining() < 8) {
256 throw new SecurityException("Signature record too short");
257 }
258 int sigAlgorithm = signature.getInt();
259 signaturesSigAlgorithms.add(sigAlgorithm);
260 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
261 continue;
262 }
263 if ((bestSigAlgorithm == -1)
264 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
265 bestSigAlgorithm = sigAlgorithm;
266 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
267 }
268 } catch (IOException | BufferUnderflowException e) {
269 throw new SecurityException(
270 "Failed to parse signature record #" + signatureCount,
271 e);
272 }
273 }
274 if (bestSigAlgorithm == -1) {
275 if (signatureCount == 0) {
276 throw new SecurityException("No signatures found");
277 } else {
278 throw new SecurityException("No supported signatures found");
279 }
280 }
281
282 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
283 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
284 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
285 String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
286 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
287 boolean sigVerified;
288 try {
289 PublicKey publicKey =
290 KeyFactory.getInstance(keyAlgorithm)
291 .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
292 Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
293 sig.initVerify(publicKey);
294 if (jcaSignatureAlgorithmParams != null) {
295 sig.setParameter(jcaSignatureAlgorithmParams);
296 }
297 sig.update(signedData);
298 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
299 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
300 | InvalidAlgorithmParameterException | SignatureException e) {
301 throw new SecurityException(
302 "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
303 }
304 if (!sigVerified) {
305 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
306 }
307
308 // Signature over signedData has verified.
309
310 byte[] contentDigest = null;
311 signedData.clear();
312 ByteBuffer digests = getLengthPrefixedSlice(signedData);
313 List<Integer> digestsSigAlgorithms = new ArrayList<>();
314 int digestCount = 0;
315 while (digests.hasRemaining()) {
316 digestCount++;
317 try {
318 ByteBuffer digest = getLengthPrefixedSlice(digests);
319 if (digest.remaining() < 8) {
320 throw new IOException("Record too short");
321 }
322 int sigAlgorithm = digest.getInt();
323 digestsSigAlgorithms.add(sigAlgorithm);
324 if (sigAlgorithm == bestSigAlgorithm) {
325 contentDigest = readLengthPrefixedByteArray(digest);
326 }
327 } catch (IOException | BufferUnderflowException e) {
328 throw new IOException("Failed to parse digest record #" + digestCount, e);
329 }
330 }
331
332 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
333 throw new SecurityException(
334 "Signature algorithms don't match between digests and signatures records");
335 }
336 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
337 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
338 if ((previousSignerDigest != null)
339 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
340 throw new SecurityException(
341 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
342 + " contents digest does not match the digest specified by a preceding signer");
343 }
344
345 ByteBuffer certificates = getLengthPrefixedSlice(signedData);
346 List<X509Certificate> certs = new ArrayList<>();
347 int certificateCount = 0;
348 while (certificates.hasRemaining()) {
349 certificateCount++;
350 byte[] encodedCert = readLengthPrefixedByteArray(certificates);
351 X509Certificate certificate;
352 try {
353 certificate = (X509Certificate)
354 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
355 } catch (CertificateException e) {
356 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
357 }
358 certificate = new VerbatimX509Certificate(
359 certificate, encodedCert);
360 certs.add(certificate);
361 }
362
363 if (certs.isEmpty()) {
364 throw new SecurityException("No certificates listed");
365 }
366 X509Certificate mainCertificate = certs.get(0);
367 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
368 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
369 throw new SecurityException(
370 "Public key mismatch between certificate and signature record");
371 }
372
373 int signedMinSDK = signedData.getInt();
374 if (signedMinSDK != minSdkVersion) {
375 throw new SecurityException(
376 "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
377 }
378
379 int signedMaxSDK = signedData.getInt();
380 if (signedMaxSDK != maxSdkVersion) {
381 throw new SecurityException(
382 "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
383 }
384
385 ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
386 return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
387 }
388
389 private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
390
391 private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
392 List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
393 X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
394 VerifiedProofOfRotation por = null;
395
396 while (attrs.hasRemaining()) {
397 ByteBuffer attr = getLengthPrefixedSlice(attrs);
398 if (attr.remaining() < 4) {
399 throw new IOException("Remaining buffer too short to contain additional attribute "
400 + "ID. Remaining: " + attr.remaining());
401 }
402 int id = attr.getInt();
403 switch(id) {
404 case PROOF_OF_ROTATION_ATTR_ID:
405 if (por != null) {
406 throw new SecurityException("Encountered multiple Proof-of-rotation records"
Dan Cashmancd4cb812018-01-02 14:55:58 -0800407 + " when verifying APK Signature Scheme v3 signature");
Daniel Cashman67096e02017-12-28 12:46:33 -0800408 }
409 por = verifyProofOfRotationStruct(attr, certFactory);
Dan Cashmancd4cb812018-01-02 14:55:58 -0800410 // make sure that the last certificate in the Proof-of-rotation record matches
411 // the one used to sign this APK.
412 try {
413 if (por.certs.size() > 0
414 && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
415 certChain[0].getEncoded())) {
416 throw new SecurityException("Terminal certificate in Proof-of-rotation"
417 + " record does not match APK signing certificate");
418 }
419 } catch (CertificateEncodingException e) {
420 throw new SecurityException("Failed to encode certificate when comparing"
421 + " Proof-of-rotation record and signing certificate", e);
422 }
423
Daniel Cashman67096e02017-12-28 12:46:33 -0800424 break;
425 default:
426 // not the droid we're looking for, move along, move along.
427 break;
428 }
429 }
430 return new VerifiedSigner(certChain, por);
431 }
432
433 private static VerifiedProofOfRotation verifyProofOfRotationStruct(
434 ByteBuffer porBuf,
435 CertificateFactory certFactory)
436 throws SecurityException, IOException {
437 int levelCount = 0;
438 int lastSigAlgorithm = -1;
439 X509Certificate lastCert = null;
440 List<X509Certificate> certs = new ArrayList<>();
441 List<Integer> flagsList = new ArrayList<>();
442
443 // Proof-of-rotation struct:
Dan Cashmana656b8b2018-01-26 13:53:59 -0800444 // A uint32 version code followed by basically a singly linked list of nodes, called levels
445 // here, each of which have the following structure:
Daniel Cashman67096e02017-12-28 12:46:33 -0800446 // * length-prefix for the entire level
447 // - length-prefixed signed data (if previous level exists)
448 // * length-prefixed X509 Certificate
449 // * uint32 signature algorithm ID describing how this signed data was signed
450 // - uint32 flags describing how to treat the cert contained in this level
451 // - uint32 signature algorithm ID to use to verify the signature of the next level. The
452 // algorithm here must match the one in the signed data section of the next level.
453 // - length-prefixed signature over the signed data in this level. The signature here
454 // is verified using the certificate from the previous level.
455 // The linking is provided by the certificate of each level signing the one of the next.
Dan Cashmana656b8b2018-01-26 13:53:59 -0800456
457 try {
458
459 // get the version code, but don't do anything with it: creator knew about all our flags
460 porBuf.getInt();
Daniel Cashmanef054082018-03-28 15:58:14 -0700461 HashSet<X509Certificate> certHistorySet = new HashSet<>();
Dan Cashmana656b8b2018-01-26 13:53:59 -0800462 while (porBuf.hasRemaining()) {
463 levelCount++;
Daniel Cashman67096e02017-12-28 12:46:33 -0800464 ByteBuffer level = getLengthPrefixedSlice(porBuf);
465 ByteBuffer signedData = getLengthPrefixedSlice(level);
466 int flags = level.getInt();
467 int sigAlgorithm = level.getInt();
468 byte[] signature = readLengthPrefixedByteArray(level);
469
470 if (lastCert != null) {
471 // Use previous level cert to verify current level
472 Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
473 getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
474 PublicKey publicKey = lastCert.getPublicKey();
475 Signature sig = Signature.getInstance(sigAlgParams.first);
476 sig.initVerify(publicKey);
477 if (sigAlgParams.second != null) {
478 sig.setParameter(sigAlgParams.second);
479 }
480 sig.update(signedData);
481 if (!sig.verify(signature)) {
482 throw new SecurityException("Unable to verify signature of certificate #"
483 + levelCount + " using " + sigAlgParams.first + " when verifying"
484 + " Proof-of-rotation record");
485 }
486 }
487
Dan Cashman6dbf8372018-01-26 05:37:54 -0800488 signedData.rewind();
Daniel Cashman67096e02017-12-28 12:46:33 -0800489 byte[] encodedCert = readLengthPrefixedByteArray(signedData);
490 int signedSigAlgorithm = signedData.getInt();
491 if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
492 throw new SecurityException("Signing algorithm ID mismatch for certificate #"
493 + levelCount + " when verifying Proof-of-rotation record");
494 }
495 lastCert = (X509Certificate)
496 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
497 lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
498
499 lastSigAlgorithm = sigAlgorithm;
Daniel Cashmanef054082018-03-28 15:58:14 -0700500 if (certHistorySet.contains(lastCert)) {
501 throw new SecurityException("Encountered duplicate entries in "
502 + "Proof-of-rotation record at certificate #" + levelCount + ". All "
503 + "signing certificates should be unique");
504 }
505 certHistorySet.add(lastCert);
Daniel Cashman67096e02017-12-28 12:46:33 -0800506 certs.add(lastCert);
507 flagsList.add(flags);
Daniel Cashman67096e02017-12-28 12:46:33 -0800508 }
Dan Cashmana656b8b2018-01-26 13:53:59 -0800509 } catch (IOException | BufferUnderflowException e) {
510 throw new IOException("Failed to parse Proof-of-rotation record", e);
511 } catch (NoSuchAlgorithmException | InvalidKeyException
512 | InvalidAlgorithmParameterException | SignatureException e) {
513 throw new SecurityException(
514 "Failed to verify signature over signed data for certificate #"
515 + levelCount + " when verifying Proof-of-rotation record", e);
516 } catch (CertificateException e) {
517 throw new SecurityException("Failed to decode certificate #" + levelCount
518 + " when verifying Proof-of-rotation record", e);
Daniel Cashman67096e02017-12-28 12:46:33 -0800519 }
520 return new VerifiedProofOfRotation(certs, flagsList);
521 }
522
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800523 static byte[] getVerityRootHash(String apkPath)
524 throws IOException, SignatureNotFoundException, SecurityException {
525 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
526 SignatureInfo signatureInfo = findSignature(apk);
527 VerifiedSigner vSigner = verify(apk, false);
528 return vSigner.verityRootHash;
529 }
530 }
531
532 static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
533 throws IOException, SignatureNotFoundException, SecurityException, DigestException,
534 NoSuchAlgorithmException {
535 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
536 SignatureInfo signatureInfo = findSignature(apk);
Victor Hsieh27300922018-09-28 09:31:44 -0700537 return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800538 }
539 }
540
Victor Hsieh25195132018-09-06 16:32:06 -0700541 static byte[] generateApkVerityRootHash(String apkPath)
Victor Hsieh5f761242018-01-20 10:30:12 -0800542 throws NoSuchAlgorithmException, DigestException, IOException,
543 SignatureNotFoundException {
544 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
545 SignatureInfo signatureInfo = findSignature(apk);
546 VerifiedSigner vSigner = verify(apk, false);
547 if (vSigner.verityRootHash == null) {
548 return null;
549 }
Victor Hsieh27300922018-09-28 09:31:44 -0700550 return VerityBuilder.generateApkVerityRootHash(
Victor Hsieh5f761242018-01-20 10:30:12 -0800551 apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
552 }
553 }
554
Daniel Cashman67096e02017-12-28 12:46:33 -0800555 private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
556 switch (sigAlgorithm) {
557 case SIGNATURE_RSA_PSS_WITH_SHA256:
558 case SIGNATURE_RSA_PSS_WITH_SHA512:
559 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
560 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
561 case SIGNATURE_ECDSA_WITH_SHA256:
562 case SIGNATURE_ECDSA_WITH_SHA512:
563 case SIGNATURE_DSA_WITH_SHA256:
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800564 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
565 case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
566 case SIGNATURE_VERITY_DSA_WITH_SHA256:
Daniel Cashman67096e02017-12-28 12:46:33 -0800567 return true;
568 default:
569 return false;
570 }
571 }
572
573 /**
574 * Verified processed proof of rotation.
575 *
576 * @hide for internal use only.
577 */
578 public static class VerifiedProofOfRotation {
579 public final List<X509Certificate> certs;
580 public final List<Integer> flagsList;
581
582 public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
583 this.certs = certs;
584 this.flagsList = flagsList;
585 }
586 }
587
588 /**
589 * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
590 *
591 * @hide for internal use only.
592 */
593 public static class VerifiedSigner {
594 public final X509Certificate[] certs;
595 public final VerifiedProofOfRotation por;
596
Victor Hsieh07bc80c2018-01-11 16:15:47 -0800597 public byte[] verityRootHash;
598
Daniel Cashman67096e02017-12-28 12:46:33 -0800599 public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
600 this.certs = certs;
601 this.por = por;
602 }
603
604 }
605
606 private static class PlatformNotSupportedException extends Exception {
607
608 PlatformNotSupportedException(String s) {
609 super(s);
610 }
611 }
612}