content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.util.apk;
  
  import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
  import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
  import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
  import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
  import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
  import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
  import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
  import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
  
  import android.os.Build;
  import android.util.ArrayMap;
  import android.util.Pair;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.RandomAccessFile;
  import java.nio.BufferUnderflowException;
  import java.nio.ByteBuffer;
  import java.security.DigestException;
  import java.security.InvalidAlgorithmParameterException;
  import java.security.InvalidKeyException;
  import java.security.KeyFactory;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.security.PublicKey;
  import java.security.Signature;
  import java.security.SignatureException;
  import java.security.cert.CertificateEncodingException;
  import java.security.cert.CertificateException;
  import java.security.cert.CertificateFactory;
  import java.security.cert.X509Certificate;
  import java.security.spec.AlgorithmParameterSpec;
  import java.security.spec.InvalidKeySpecException;
  import java.security.spec.X509EncodedKeySpec;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  
  /**
   * APK Signature Scheme v3 verifier.
   *
   * @hide for internal use only.
   */
  public class ApkSignatureSchemeV3Verifier {
  
      /**
       * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
       */
      public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
  
      private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
  
      /**
       * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
       *
       * <p><b>NOTE: This method does not verify the signature.</b>
       */
      public static boolean hasSignature(String apkFile) throws IOException {
          try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
              findSignature(apk);
              return true;
          } catch (SignatureNotFoundException e) {
              return false;
          }
      }
  
      /**
       * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
       * associated with each signer.
       *
       * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
       * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
       * verify.
       * @throws IOException if an I/O error occurs while reading the APK file.
       */
      public static VerifiedSigner verify(String apkFile)
              throws SignatureNotFoundException, SecurityException, IOException {
          return verify(apkFile, true);
      }
  
      /**
       * Returns the certificates associated with each signer for the given APK without verification.
       * This method is dangerous and should not be used, unless the caller is absolutely certain the
       * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v3
       * Block while gathering signer information.  The APK contents are not verified.
       *
       * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
       * @throws IOException if an I/O error occurs while reading the APK file.
       */
      public static VerifiedSigner unsafeGetCertsWithoutVerification(String apkFile)
              throws SignatureNotFoundException, SecurityException, IOException {
          return verify(apkFile, false);
      }
  
      private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
              throws SignatureNotFoundException, SecurityException, IOException {
          try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
              return verify(apk, verifyIntegrity);
          }
      }
  
      /**
       * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
       * associated with each signer.
       *
       * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
       * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
       *         verify.
       * @throws IOException if an I/O error occurs while reading the APK file.
       */
      private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
              throws SignatureNotFoundException, SecurityException, IOException {
          SignatureInfo signatureInfo = findSignature(apk);
          return verify(apk, signatureInfo, verifyIntegrity);
      }
  
      /**
       * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
       * additional information relevant for verifying the block against the file.
       *
       * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
       * @throws IOException if an I/O error occurs while reading the APK file.
       */
      private static SignatureInfo findSignature(RandomAccessFile apk)
              throws IOException, SignatureNotFoundException {
          return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
      }
  
      /**
       * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
       * Block.
       *
       * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
       *        against the APK file.
       */
      private static VerifiedSigner verify(
              RandomAccessFile apk,
              SignatureInfo signatureInfo,
              boolean doVerifyIntegrity) throws SecurityException, IOException {
          int signerCount = 0;
          Map<Integer, byte[]> contentDigests = new ArrayMap<>();
          VerifiedSigner result = null;
          CertificateFactory certFactory;
          try {
              certFactory = CertificateFactory.getInstance("X.509");
          } catch (CertificateException e) {
              throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
          }
          ByteBuffer signers;
          try {
              signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
          } catch (IOException e) {
              throw new SecurityException("Failed to read list of signers", e);
          }
          while (signers.hasRemaining()) {
              try {
                  ByteBuffer signer = getLengthPrefixedSlice(signers);
                  result = verifySigner(signer, contentDigests, certFactory);
                  signerCount++;
              } catch (PlatformNotSupportedException e) {
                  // this signer is for a different platform, ignore it.
                  continue;
              } catch (IOException | BufferUnderflowException | SecurityException e) {
                  throw new SecurityException(
                          "Failed to parse/verify signer #" + signerCount + " block",
                          e);
              }
          }
  
          if (signerCount < 1 || result == null) {
              throw new SecurityException("No signers found");
          }
  
          if (signerCount != 1) {
              throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
                      + "multiple signers found.");
          }
  
          if (contentDigests.isEmpty()) {
              throw new SecurityException("No content digests found");
          }
  
          if (doVerifyIntegrity) {
              ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
          }
  
          if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
              byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
              result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
                      verityDigest, apk.length(), signatureInfo);
          }
  
          return result;
      }
  
      private static VerifiedSigner verifySigner(
              ByteBuffer signerBlock,
              Map<Integer, byte[]> contentDigests,
              CertificateFactory certFactory)
              throws SecurityException, IOException, PlatformNotSupportedException {
          ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
          int minSdkVersion = signerBlock.getInt();
          int maxSdkVersion = signerBlock.getInt();
  
          if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
              // this signature isn't meant to be used with this platform, skip it.
              throw new PlatformNotSupportedException(
                      "Signer not supported by this platform "
                      + "version. This platform: " + Build.VERSION.SDK_INT
                      + ", signer minSdkVersion: " + minSdkVersion
                      + ", maxSdkVersion: " + maxSdkVersion);
          }
  
          ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
          byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
  
          int signatureCount = 0;
          int bestSigAlgorithm = -1;
          byte[] bestSigAlgorithmSignatureBytes = null;
          List<Integer> signaturesSigAlgorithms = new ArrayList<>();
          while (signatures.hasRemaining()) {
              signatureCount++;
              try {
                  ByteBuffer signature = getLengthPrefixedSlice(signatures);
                  if (signature.remaining() < 8) {
                      throw new SecurityException("Signature record too short");
                  }
                  int sigAlgorithm = signature.getInt();
                  signaturesSigAlgorithms.add(sigAlgorithm);
                  if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
                      continue;
                  }
                  if ((bestSigAlgorithm == -1)
                          || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
                      bestSigAlgorithm = sigAlgorithm;
                      bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
                  }
              } catch (IOException | BufferUnderflowException e) {
                  throw new SecurityException(
                          "Failed to parse signature record #" + signatureCount,
                          e);
              }
          }
          if (bestSigAlgorithm == -1) {
              if (signatureCount == 0) {
                  throw new SecurityException("No signatures found");
              } else {
                  throw new SecurityException("No supported signatures found");
              }
          }
  
          String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
          Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
                  getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
          String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
          AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
          boolean sigVerified;
          try {
              PublicKey publicKey =
                      KeyFactory.getInstance(keyAlgorithm)
                              .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
              Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
              sig.initVerify(publicKey);
              if (jcaSignatureAlgorithmParams != null) {
                  sig.setParameter(jcaSignatureAlgorithmParams);
              }
              sig.update(signedData);
              sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
          } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
                  | InvalidAlgorithmParameterException | SignatureException e) {
              throw new SecurityException(
                      "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
          }
          if (!sigVerified) {
              throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
          }
  
          // Signature over signedData has verified.
  
          byte[] contentDigest = null;
          signedData.clear();
          ByteBuffer digests = getLengthPrefixedSlice(signedData);
          List<Integer> digestsSigAlgorithms = new ArrayList<>();
          int digestCount = 0;
          while (digests.hasRemaining()) {
              digestCount++;
              try {
                  ByteBuffer digest = getLengthPrefixedSlice(digests);
                  if (digest.remaining() < 8) {
                      throw new IOException("Record too short");
                  }
                  int sigAlgorithm = digest.getInt();
                  digestsSigAlgorithms.add(sigAlgorithm);
                  if (sigAlgorithm == bestSigAlgorithm) {
                      contentDigest = readLengthPrefixedByteArray(digest);
                  }
              } catch (IOException | BufferUnderflowException e) {
                  throw new IOException("Failed to parse digest record #" + digestCount, e);
              }
          }
  
          if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
              throw new SecurityException(
                      "Signature algorithms don't match between digests and signatures records");
          }
          int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
          byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
          if ((previousSignerDigest != null)
                  && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
              throw new SecurityException(
                      getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
                      + " contents digest does not match the digest specified by a preceding signer");
          }
  
          ByteBuffer certificates = getLengthPrefixedSlice(signedData);
          List<X509Certificate> certs = new ArrayList<>();
          int certificateCount = 0;
          while (certificates.hasRemaining()) {
              certificateCount++;
              byte[] encodedCert = readLengthPrefixedByteArray(certificates);
              X509Certificate certificate;
              try {
                  certificate = (X509Certificate)
                          certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
              } catch (CertificateException e) {
                  throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
              }
              certificate = new VerbatimX509Certificate(
                      certificate, encodedCert);
              certs.add(certificate);
          }
  
          if (certs.isEmpty()) {
              throw new SecurityException("No certificates listed");
          }
          X509Certificate mainCertificate = certs.get(0);
          byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
          if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
              throw new SecurityException(
                      "Public key mismatch between certificate and signature record");
          }
  
          int signedMinSDK = signedData.getInt();
          if (signedMinSDK != minSdkVersion) {
              throw new SecurityException(
                      "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
          }
  
          int signedMaxSDK = signedData.getInt();
          if (signedMaxSDK != maxSdkVersion) {
              throw new SecurityException(
                      "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
          }
  
          ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
          return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
      }
  
      private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
  
      private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
              List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
          X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
          VerifiedProofOfRotation por = null;
  
          while (attrs.hasRemaining()) {
              ByteBuffer attr = getLengthPrefixedSlice(attrs);
              if (attr.remaining() < 4) {
                  throw new IOException("Remaining buffer too short to contain additional attribute "
                          + "ID. Remaining: " + attr.remaining());
              }
              int id = attr.getInt();
              switch(id) {
                  case PROOF_OF_ROTATION_ATTR_ID:
                      if (por != null) {
                          throw new SecurityException("Encountered multiple Proof-of-rotation records"
                                  + " when verifying APK Signature Scheme v3 signature");
                      }
                      por = verifyProofOfRotationStruct(attr, certFactory);
                      // make sure that the last certificate in the Proof-of-rotation record matches
                      // the one used to sign this APK.
                      try {
                          if (por.certs.size() > 0
                                  && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
                                          certChain[0].getEncoded())) {
                              throw new SecurityException("Terminal certificate in Proof-of-rotation"
                                      + " record does not match APK signing certificate");
                          }
                      } catch (CertificateEncodingException e) {
                          throw new SecurityException("Failed to encode certificate when comparing"
                                  + " Proof-of-rotation record and signing certificate", e);
                      }
  
                      break;
                  default:
                      // not the droid we're looking for, move along, move along.
                      break;
              }
          }
          return new VerifiedSigner(certChain, por);
      }
  
      private static VerifiedProofOfRotation verifyProofOfRotationStruct(
              ByteBuffer porBuf,
              CertificateFactory certFactory)
              throws SecurityException, IOException {
          int levelCount = 0;
          int lastSigAlgorithm = -1;
          X509Certificate lastCert = null;
          List<X509Certificate> certs = new ArrayList<>();
          List<Integer> flagsList = new ArrayList<>();
  
          // Proof-of-rotation struct:
          // A uint32 version code followed by basically a singly linked list of nodes, called levels
          // here, each of which have the following structure:
          // * length-prefix for the entire level
          //     - length-prefixed signed data (if previous level exists)
          //         * length-prefixed X509 Certificate
          //         * uint32 signature algorithm ID describing how this signed data was signed
          //     - uint32 flags describing how to treat the cert contained in this level
          //     - uint32 signature algorithm ID to use to verify the signature of the next level. The
          //         algorithm here must match the one in the signed data section of the next level.
          //     - length-prefixed signature over the signed data in this level.  The signature here
          //         is verified using the certificate from the previous level.
          // The linking is provided by the certificate of each level signing the one of the next.
  
          try {
  
              // get the version code, but don't do anything with it: creator knew about all our flags
              porBuf.getInt();
              HashSet<X509Certificate> certHistorySet = new HashSet<>();
              while (porBuf.hasRemaining()) {
                  levelCount++;
                  ByteBuffer level = getLengthPrefixedSlice(porBuf);
                  ByteBuffer signedData = getLengthPrefixedSlice(level);
                  int flags = level.getInt();
                  int sigAlgorithm = level.getInt();
                  byte[] signature = readLengthPrefixedByteArray(level);
  
                  if (lastCert != null) {
                      // Use previous level cert to verify current level
                      Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
                              getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
                      PublicKey publicKey = lastCert.getPublicKey();
                      Signature sig = Signature.getInstance(sigAlgParams.first);
                      sig.initVerify(publicKey);
                      if (sigAlgParams.second != null) {
                          sig.setParameter(sigAlgParams.second);
                      }
                      sig.update(signedData);
                      if (!sig.verify(signature)) {
                          throw new SecurityException("Unable to verify signature of certificate #"
                                  + levelCount + " using " + sigAlgParams.first + " when verifying"
                                  + " Proof-of-rotation record");
                      }
                  }
  
                  signedData.rewind();
                  byte[] encodedCert = readLengthPrefixedByteArray(signedData);
                  int signedSigAlgorithm = signedData.getInt();
                  if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
                      throw new SecurityException("Signing algorithm ID mismatch for certificate #"
                              + levelCount + " when verifying Proof-of-rotation record");
                  }
                  lastCert = (X509Certificate)
                          certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
                  lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
  
                  lastSigAlgorithm = sigAlgorithm;
                  if (certHistorySet.contains(lastCert)) {
                      throw new SecurityException("Encountered duplicate entries in "
                              + "Proof-of-rotation record at certificate #" + levelCount + ".  All "
                              + "signing certificates should be unique");
                  }
                  certHistorySet.add(lastCert);
                  certs.add(lastCert);
                  flagsList.add(flags);
              }
          } catch (IOException | BufferUnderflowException e) {
              throw new IOException("Failed to parse Proof-of-rotation record", e);
          } catch (NoSuchAlgorithmException | InvalidKeyException
                  | InvalidAlgorithmParameterException | SignatureException e) {
              throw new SecurityException(
                      "Failed to verify signature over signed data for certificate #"
                              + levelCount + " when verifying Proof-of-rotation record", e);
          } catch (CertificateException e) {
              throw new SecurityException("Failed to decode certificate #" + levelCount
                      + " when verifying Proof-of-rotation record", e);
          }
          return new VerifiedProofOfRotation(certs, flagsList);
      }
  
      static byte[] getVerityRootHash(String apkPath)
              throws IOException, SignatureNotFoundException, SecurityException {
          try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
              SignatureInfo signatureInfo = findSignature(apk);
              VerifiedSigner vSigner = verify(apk, false);
              return vSigner.verityRootHash;
          }
      }
  
      static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
              throws IOException, SignatureNotFoundException, SecurityException, DigestException,
                     NoSuchAlgorithmException {
          try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
              SignatureInfo signatureInfo = findSignature(apk);
              return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
          }
      }
  
      static byte[] generateApkVerityRootHash(String apkPath)
              throws NoSuchAlgorithmException, DigestException, IOException,
                     SignatureNotFoundException {
          try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
              SignatureInfo signatureInfo = findSignature(apk);
              VerifiedSigner vSigner = verify(apk, false);
              if (vSigner.verityRootHash == null) {
                  return null;
              }
              return VerityBuilder.generateApkVerityRootHash(
                      apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
          }
      }
  
      private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
          switch (sigAlgorithm) {
              case SIGNATURE_RSA_PSS_WITH_SHA256:
              case SIGNATURE_RSA_PSS_WITH_SHA512:
              case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
              case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
              case SIGNATURE_ECDSA_WITH_SHA256:
              case SIGNATURE_ECDSA_WITH_SHA512:
              case SIGNATURE_DSA_WITH_SHA256:
              case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
              case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
              case SIGNATURE_VERITY_DSA_WITH_SHA256:
                  return true;
              default:
                  return false;
          }
      }
  
      /**
       * Verified processed proof of rotation.
       *
       * @hide for internal use only.
       */
      public static class VerifiedProofOfRotation {
          public final List<X509Certificate> certs;
          public final List<Integer> flagsList;
  
          public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
              this.certs = certs;
              this.flagsList = flagsList;
          }
      }
  
      /**
       * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
       *
       * @hide for internal use only.
       */
      public static class VerifiedSigner {
          public final X509Certificate[] certs;
          public final VerifiedProofOfRotation por;
  
          public byte[] verityRootHash;
  
          public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
              this.certs = certs;
              this.por = por;
          }
  
      }
  
      private static class PlatformNotSupportedException extends Exception {
  
          PlatformNotSupportedException(String s) {
              super(s);
          }
      }
  }
  