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 com.android.apksig.internal.apk.v3;
  
  import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement;
  import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements;
  import static com.android.apksig.internal.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
  import static com.android.apksig.internal.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
  
  import com.android.apksig.apk.ApkFormatException;
  import com.android.apksig.internal.apk.ApkSigningBlockUtils;
  import com.android.apksig.internal.apk.SignatureAlgorithm;
  import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate;
  import com.android.apksig.internal.util.X509CertificateUtils;
  
  import java.io.IOException;
  import java.nio.BufferUnderflowException;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.security.InvalidAlgorithmParameterException;
  import java.security.InvalidKeyException;
  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.X509Certificate;
  import java.security.spec.AlgorithmParameterSpec;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.List;
  
  /**
   * APK Signer Lineage.
   *
   * <p>The signer lineage contains a history of signing certificates with each ancestor attesting to
   * the validity of its descendant.  Each additional descendant represents a new identity that can be
   * used to sign an APK, and each generation has accompanying attributes which represent how the
   * APK would like to view the older signing certificates, specifically how they should be trusted in
   * certain situations.
   *
   * <p> Its primary use is to enable APK Signing Certificate Rotation.  The Android platform verifies
   * the APK Signer Lineage, and if the current signing certificate for the APK is in the Signer
   * Lineage, and the Lineage contains the certificate the platform associates with the APK, it will
   * allow upgrades to the new certificate.
   *
   * @see <a href="https://source.android.com/security/apksigning/index.html">Application Signing</a>
   */
  public class V3SigningCertificateLineage {
  
      private final static int FIRST_VERSION = 1;
      private final static int CURRENT_VERSION = FIRST_VERSION;
  
      /**
       * Deserializes the binary representation of an {@link V3SigningCertificateLineage}. Also
       * verifies that the structure is well-formed, e.g. that the signature for each node is from its
       * parent.
       */
      public static List<SigningCertificateNode> readSigningCertificateLineage(ByteBuffer inputBytes)
              throws IOException {
          List<SigningCertificateNode> result = new ArrayList<>();
          int nodeCount = 0;
          if (inputBytes == null || !inputBytes.hasRemaining()) {
              return null;
          }
  
          ApkSigningBlockUtils.checkByteOrderLittleEndian(inputBytes);
  
          // FORMAT (little endian):
          // * uint32: version code
          // * sequence of length-prefixed (uint32): nodes
          //   * length-prefixed bytes: signed data
          //     * length-prefixed bytes: certificate
          //     * uint32: signature algorithm id
          //   * uint32: flags
          //   * uint32: signature algorithm id (used by to sign next cert in lineage)
          //   * length-prefixed bytes: signature over above signed data
  
          X509Certificate lastCert = null;
          int lastSigAlgorithmId = 0;
  
          try {
              int version = inputBytes.getInt();
              if (version != CURRENT_VERSION) {
                  // we only have one version to worry about right now, so just check it
                  throw new IllegalArgumentException("Encoded SigningCertificateLineage has a version"
                          + " different than any of which we are aware");
              }
              HashSet<X509Certificate> certHistorySet = new HashSet<>();
              while (inputBytes.hasRemaining()) {
                  nodeCount++;
                  ByteBuffer nodeBytes = getLengthPrefixedSlice(inputBytes);
                  ByteBuffer signedData = getLengthPrefixedSlice(nodeBytes);
                  int flags = nodeBytes.getInt();
                  int sigAlgorithmId = nodeBytes.getInt();
                  SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.findById(lastSigAlgorithmId);
                  byte[] signature = readLengthPrefixedByteArray(nodeBytes);
  
                  if (lastCert != null) {
                      // Use previous level cert to verify current level
                      String jcaSignatureAlgorithm =
                              sigAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst();
                      AlgorithmParameterSpec jcaSignatureAlgorithmParams =
                              sigAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond();
                      PublicKey publicKey = lastCert.getPublicKey();
                      Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
                      sig.initVerify(publicKey);
                      if (jcaSignatureAlgorithmParams != null) {
                          sig.setParameter(jcaSignatureAlgorithmParams);
                      }
                      sig.update(signedData);
                      if (!sig.verify(signature)) {
                          throw new SecurityException("Unable to verify signature of certificate #"
                                  + nodeCount + " using " + jcaSignatureAlgorithm + " when verifying"
                                  + " V3SigningCertificateLineage object");
                      }
                  }
  
                  signedData.rewind();
                  byte[] encodedCert = readLengthPrefixedByteArray(signedData);
                  int signedSigAlgorithm = signedData.getInt();
                  if (lastCert != null && lastSigAlgorithmId != signedSigAlgorithm) {
                      throw new SecurityException("Signing algorithm ID mismatch for certificate #"
                              + nodeBytes + " when verifying V3SigningCertificateLineage object");
                  }
                  lastCert = X509CertificateUtils.generateCertificate(encodedCert);
                  lastCert = new GuaranteedEncodedFormX509Certificate(lastCert, encodedCert);
                  if (certHistorySet.contains(lastCert)) {
                      throw new SecurityException("Encountered duplicate entries in "
                              + "SigningCertificateLineage at certificate #" + nodeCount + ".  All "
                              + "signing certificates should be unique");
                  }
                  certHistorySet.add(lastCert);
                  lastSigAlgorithmId = sigAlgorithmId;
                  result.add(new SigningCertificateNode(
                          lastCert, SignatureAlgorithm.findById(signedSigAlgorithm),
                          SignatureAlgorithm.findById(sigAlgorithmId), signature, flags));
              }
          } catch(ApkFormatException | BufferUnderflowException e){
              throw new IOException("Failed to parse V3SigningCertificateLineage object", e);
          } catch(NoSuchAlgorithmException | InvalidKeyException
                  | InvalidAlgorithmParameterException | SignatureException e){
              throw new SecurityException(
                      "Failed to verify signature over signed data for certificate #" + nodeCount
                              + " when parsing V3SigningCertificateLineage object", e);
          } catch(CertificateException e){
              throw new SecurityException("Failed to decode certificate #" + nodeCount
                      + " when parsing V3SigningCertificateLineage object", e);
          }
          return result;
      }
  
      /**
       * encode the in-memory representation of this {@code V3SigningCertificateLineage}
       */
      public static byte[] encodeSigningCertificateLineage(
              List<SigningCertificateNode> signingCertificateLineage) {
          // FORMAT (little endian):
          // * version code
          // * sequence of length-prefixed (uint32): nodes
          //   * length-prefixed bytes: signed data
          //     * length-prefixed bytes: certificate
          //     * uint32: signature algorithm id
          //   * uint32: flags
          //   * uint32: signature algorithm id (used by to sign next cert in lineage)
  
          List<byte[]> nodes = new ArrayList<>();
          for (SigningCertificateNode node : signingCertificateLineage) {
              nodes.add(encodeSigningCertificateNode(node));
          }
          byte [] encodedSigningCertificateLineage = encodeAsSequenceOfLengthPrefixedElements(nodes);
  
          // add the version code (uint32) on top of the encoded nodes
          int payloadSize = 4 + encodedSigningCertificateLineage.length;
          ByteBuffer encodedWithVersion = ByteBuffer.allocate(payloadSize);
          encodedWithVersion.order(ByteOrder.LITTLE_ENDIAN);
          encodedWithVersion.putInt(CURRENT_VERSION);
          encodedWithVersion.put(encodedSigningCertificateLineage);
          return encodedWithVersion.array();
      }
  
      public static byte[] encodeSigningCertificateNode(SigningCertificateNode node) {
          // FORMAT (little endian):
          // * length-prefixed bytes: signed data
          //   * length-prefixed bytes: certificate
          //   * uint32: signature algorithm id
          // * uint32: flags
          // * uint32: signature algorithm id (used by to sign next cert in lineage)
          // * length-prefixed bytes: signature over signed data
          int parentSigAlgorithmId = 0;
          if (node.parentSigAlgorithm != null) {
              parentSigAlgorithmId = node.parentSigAlgorithm.getId();
          }
          int sigAlgorithmId = 0;
          if (node.sigAlgorithm != null) {
              sigAlgorithmId = node.sigAlgorithm.getId();
          }
          byte[] prefixedSignedData = encodeSignedData(node.signingCert, parentSigAlgorithmId);
          byte[] prefixedSignature = encodeAsLengthPrefixedElement(node.signature);
          int payloadSize = prefixedSignedData.length + 4 + 4 + prefixedSignature.length;
          ByteBuffer result = ByteBuffer.allocate(payloadSize);
          result.order(ByteOrder.LITTLE_ENDIAN);
          result.put(prefixedSignedData);
          result.putInt(node.flags);
          result.putInt(sigAlgorithmId);
          result.put(prefixedSignature);
          return result.array();
      }
  
      public static byte[] encodeSignedData(X509Certificate certificate, int flags) {
          try {
              byte[] prefixedCertificate = encodeAsLengthPrefixedElement(certificate.getEncoded());
              int payloadSize = 4 + prefixedCertificate.length;
              ByteBuffer result = ByteBuffer.allocate(payloadSize);
              result.order(ByteOrder.LITTLE_ENDIAN);
              result.put(prefixedCertificate);
              result.putInt(flags);
              return encodeAsLengthPrefixedElement(result.array());
          } catch (CertificateEncodingException e) {
              throw new RuntimeException(
                      "Failed to encode V3SigningCertificateLineage certificate", e);
          }
      }
  
      /**
       * Represents one signing certificate in the {@link V3SigningCertificateLineage}, which
       * generally means it is/was used at some point to sign the same APK of the others in the
       * lineage.
       */
      public static class SigningCertificateNode {
  
          public SigningCertificateNode(
                  X509Certificate signingCert,
                  SignatureAlgorithm parentSigAlgorithm,
                  SignatureAlgorithm sigAlgorithm,
                  byte[] signature,
                  int flags) {
              this.signingCert = signingCert;
              this.parentSigAlgorithm = parentSigAlgorithm;
              this.sigAlgorithm = sigAlgorithm;
              this.signature = signature;
              this.flags = flags;
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (!(o instanceof SigningCertificateNode)) return false;
  
              SigningCertificateNode that = (SigningCertificateNode) o;
              if (!signingCert.equals(that.signingCert)) return false;
              if (parentSigAlgorithm != that.parentSigAlgorithm) return false;
              if (sigAlgorithm != that.sigAlgorithm) return false;
              if (!Arrays.equals(signature, that.signature)) return false;
              if (flags != that.flags) return false;
  
              // we made it
              return true;
          }
  
          /**
           * the signing cert for this node.  This is part of the data signed by the parent node.
           */
          public final X509Certificate signingCert;
  
          /**
           * the algorithm used by the this node's parent to bless this data.  Its ID value is part of
           * the data signed by the parent node. {@code null} for first node.
           */
          public final SignatureAlgorithm parentSigAlgorithm;
  
          /**
           * the algorithm used by the this nodeto bless the next node's data.  Its ID value is part
           * of the signed data of the next node. {@code null} for the last node.
           */
          public SignatureAlgorithm sigAlgorithm;
  
          /**
           * signature over the signed data (above).  The signature is from this node's parent
           * signing certificate, which should correspond to the signing certificate used to sign an
           * APK before rotating to this one, and is formed using {@code signatureAlgorithm}.
           */
          public final byte[] signature;
  
          /**
           * the flags detailing how the platform should treat this signing cert
           */
          public int flags;
      }
  }
  