blob: c9e67fe1c9efd82bc1bec0ef4ae7408cd073a69f [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
19import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
20import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
21import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
22import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
23import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
24import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
25import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
Victor Hsieh4acad4c2018-01-04 13:36:15 -080026import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
27import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
28import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
Daniel Cashman67096e02017-12-28 12:46:33 -080029import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
30import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
31import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
32import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
33import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
34import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
35import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
36
37import android.os.Build;
38import android.util.ArrayMap;
39import android.util.Pair;
40
41import java.io.ByteArrayInputStream;
Daniel Cashman67096e02017-12-28 12:46:33 -080042import java.io.IOException;
43import java.io.RandomAccessFile;
44import java.nio.BufferUnderflowException;
45import java.nio.ByteBuffer;
46import java.security.InvalidAlgorithmParameterException;
47import java.security.InvalidKeyException;
48import java.security.KeyFactory;
49import java.security.MessageDigest;
50import java.security.NoSuchAlgorithmException;
51import java.security.PublicKey;
52import java.security.Signature;
53import java.security.SignatureException;
Dan Cashmancd4cb812018-01-02 14:55:58 -080054import java.security.cert.CertificateEncodingException;
Daniel Cashman67096e02017-12-28 12:46:33 -080055import java.security.cert.CertificateException;
56import java.security.cert.CertificateFactory;
57import java.security.cert.X509Certificate;
58import java.security.spec.AlgorithmParameterSpec;
59import java.security.spec.InvalidKeySpecException;
60import java.security.spec.X509EncodedKeySpec;
61import java.util.ArrayList;
62import java.util.Arrays;
63import java.util.List;
64import java.util.Map;
65
66/**
67 * APK Signature Scheme v3 verifier.
68 *
69 * @hide for internal use only.
70 */
71public class ApkSignatureSchemeV3Verifier {
72
73 /**
74 * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
75 */
76 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
77
78 private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
79
80 /**
81 * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
82 *
83 * <p><b>NOTE: This method does not verify the signature.</b>
84 */
85 public static boolean hasSignature(String apkFile) throws IOException {
86 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
87 findSignature(apk);
88 return true;
89 } catch (SignatureNotFoundException e) {
90 return false;
91 }
92 }
93
94 /**
95 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
96 * associated with each signer.
97 *
98 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
99 * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
100 * verify.
101 * @throws IOException if an I/O error occurs while reading the APK file.
102 */
103 public static VerifiedSigner verify(String apkFile)
104 throws SignatureNotFoundException, SecurityException, IOException {
105 return verify(apkFile, true);
106 }
107
108 /**
109 * Returns the certificates associated with each signer for the given APK without verification.
110 * This method is dangerous and should not be used, unless the caller is absolutely certain the
111 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3
112 * Block while gathering signer information. The APK contents are not verified.
113 *
114 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
115 * @throws IOException if an I/O error occurs while reading the APK file.
116 */
117 public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
118 throws SignatureNotFoundException, SecurityException, IOException {
119 return verify(apkFile, false);
120 }
121
122 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
123 throws SignatureNotFoundException, SecurityException, IOException {
124 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
125 return verify(apk, verifyIntegrity);
126 }
127 }
128
129 /**
130 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
131 * associated with each signer.
132 *
133 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
134 * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
135 * verify.
136 * @throws IOException if an I/O error occurs while reading the APK file.
137 */
138 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
139 throws SignatureNotFoundException, SecurityException, IOException {
140 SignatureInfo signatureInfo = findSignature(apk);
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800141 return verify(apk, signatureInfo, verifyIntegrity);
Daniel Cashman67096e02017-12-28 12:46:33 -0800142 }
143
144 /**
145 * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
146 * additional information relevant for verifying the block against the file.
147 *
148 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
149 * @throws IOException if an I/O error occurs while reading the APK file.
150 */
151 private static SignatureInfo findSignature(RandomAccessFile apk)
152 throws IOException, SignatureNotFoundException {
153 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
154 }
155
156 /**
157 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
158 * Block.
159 *
160 * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
161 * against the APK file.
162 */
163 private static VerifiedSigner verify(
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800164 RandomAccessFile apk,
Daniel Cashman67096e02017-12-28 12:46:33 -0800165 SignatureInfo signatureInfo,
166 boolean doVerifyIntegrity) throws SecurityException {
167 int signerCount = 0;
168 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
169 VerifiedSigner result = null;
170 CertificateFactory certFactory;
171 try {
172 certFactory = CertificateFactory.getInstance("X.509");
173 } catch (CertificateException e) {
174 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
175 }
176 ByteBuffer signers;
177 try {
178 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
179 } catch (IOException e) {
180 throw new SecurityException("Failed to read list of signers", e);
181 }
182 while (signers.hasRemaining()) {
183 try {
184 ByteBuffer signer = getLengthPrefixedSlice(signers);
185 result = verifySigner(signer, contentDigests, certFactory);
186 signerCount++;
187 } catch (PlatformNotSupportedException e) {
188 // this signer is for a different platform, ignore it.
189 continue;
190 } catch (IOException | BufferUnderflowException | SecurityException e) {
191 throw new SecurityException(
192 "Failed to parse/verify signer #" + signerCount + " block",
193 e);
194 }
195 }
196
197 if (signerCount < 1 || result == null) {
198 throw new SecurityException("No signers found");
199 }
200
201 if (signerCount != 1) {
202 throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
203 + "multiple signers found.");
204 }
205
206 if (contentDigests.isEmpty()) {
207 throw new SecurityException("No content digests found");
208 }
209
210 if (doVerifyIntegrity) {
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800211 ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
Daniel Cashman67096e02017-12-28 12:46:33 -0800212 }
213
214 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:
435 // is basically a singly linked list of nodes, called levels here, each of which have the
436 // following structure:
437 // * 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.
447 while (porBuf.hasRemaining()) {
448 levelCount++;
449 try {
450 ByteBuffer level = getLengthPrefixedSlice(porBuf);
451 ByteBuffer signedData = getLengthPrefixedSlice(level);
452 int flags = level.getInt();
453 int sigAlgorithm = level.getInt();
454 byte[] signature = readLengthPrefixedByteArray(level);
455
456 if (lastCert != null) {
457 // Use previous level cert to verify current level
458 Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
459 getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
460 PublicKey publicKey = lastCert.getPublicKey();
461 Signature sig = Signature.getInstance(sigAlgParams.first);
462 sig.initVerify(publicKey);
463 if (sigAlgParams.second != null) {
464 sig.setParameter(sigAlgParams.second);
465 }
466 sig.update(signedData);
467 if (!sig.verify(signature)) {
468 throw new SecurityException("Unable to verify signature of certificate #"
469 + levelCount + " using " + sigAlgParams.first + " when verifying"
470 + " Proof-of-rotation record");
471 }
472 }
473
474 byte[] encodedCert = readLengthPrefixedByteArray(signedData);
475 int signedSigAlgorithm = signedData.getInt();
476 if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
477 throw new SecurityException("Signing algorithm ID mismatch for certificate #"
478 + levelCount + " when verifying Proof-of-rotation record");
479 }
480 lastCert = (X509Certificate)
481 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
482 lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
483
484 lastSigAlgorithm = sigAlgorithm;
485 certs.add(lastCert);
486 flagsList.add(flags);
487 } catch (IOException | BufferUnderflowException e) {
488 throw new IOException("Failed to parse Proof-of-rotation record", e);
489 } catch (NoSuchAlgorithmException | InvalidKeyException
490 | InvalidAlgorithmParameterException | SignatureException e) {
491 throw new SecurityException(
492 "Failed to verify signature over signed data for certificate #"
493 + levelCount + " when verifying Proof-of-rotation record", e);
494 } catch (CertificateException e) {
495 throw new SecurityException("Failed to decode certificate #" + levelCount
496 + " when verifying Proof-of-rotation record", e);
497 }
498 }
499 return new VerifiedProofOfRotation(certs, flagsList);
500 }
501
502 private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
503 switch (sigAlgorithm) {
504 case SIGNATURE_RSA_PSS_WITH_SHA256:
505 case SIGNATURE_RSA_PSS_WITH_SHA512:
506 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
507 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
508 case SIGNATURE_ECDSA_WITH_SHA256:
509 case SIGNATURE_ECDSA_WITH_SHA512:
510 case SIGNATURE_DSA_WITH_SHA256:
Victor Hsieh4acad4c2018-01-04 13:36:15 -0800511 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
512 case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
513 case SIGNATURE_VERITY_DSA_WITH_SHA256:
Daniel Cashman67096e02017-12-28 12:46:33 -0800514 return true;
515 default:
516 return false;
517 }
518 }
519
520 /**
521 * Verified processed proof of rotation.
522 *
523 * @hide for internal use only.
524 */
525 public static class VerifiedProofOfRotation {
526 public final List<X509Certificate> certs;
527 public final List<Integer> flagsList;
528
529 public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
530 this.certs = certs;
531 this.flagsList = flagsList;
532 }
533 }
534
535 /**
536 * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
537 *
538 * @hide for internal use only.
539 */
540 public static class VerifiedSigner {
541 public final X509Certificate[] certs;
542 public final VerifiedProofOfRotation por;
543
544 public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
545 this.certs = certs;
546 this.por = por;
547 }
548
549 }
550
551 private static class PlatformNotSupportedException extends Exception {
552
553 PlatformNotSupportedException(String s) {
554 super(s);
555 }
556 }
557}