content: /*
   * Copyright (C) 2017 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.server.locksettings.recoverablekeystore.certificate;
  
  import android.annotation.Nullable;
  
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.security.cert.X509Certificate;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Date;
  import java.util.List;
  
  import org.w3c.dom.Element;
  
  /**
   * Parses and holds the XML file containing the signature of the XML file containing the list of THM
   * public-key certificates.
   */
  public final class SigXml {
  
      private static final String INTERMEDIATE_CERT_LIST_TAG = "intermediates";
      private static final String INTERMEDIATE_CERT_ITEM_TAG = "cert";
      private static final String SIGNER_CERT_NODE_TAG = "certificate";
      private static final String SIGNATURE_NODE_TAG = "value";
  
      private final List<X509Certificate> intermediateCerts;
      private final X509Certificate signerCert;
      private final byte[] signature;
  
      private SigXml(
              List<X509Certificate> intermediateCerts, X509Certificate signerCert, byte[] signature) {
          this.intermediateCerts = intermediateCerts;
          this.signerCert = signerCert;
          this.signature = signature;
      }
  
      /**
       * Verifies the signature contained in this XML file against a trusted root certificate and the
       * binary content of another file. The signer's public-key certificate and possible intermediate
       * CA certificates are included in this XML file, and will be validated against the trusted root
       * certificate.
       *
       * @param trustedRoot     the trusted root certificate
       * @param signedFileBytes the original file content that has been signed
       * @throws CertValidationException if the signature verification fails, or the signer's
       *                                 certificate contained in this XML file cannot be validated
       *                                 based on the trusted root certificate
       */
      public void verifyFileSignature(X509Certificate trustedRoot, byte[] signedFileBytes)
              throws CertValidationException {
          verifyFileSignature(trustedRoot, signedFileBytes, /*validationDate=*/ null);
      }
  
      @VisibleForTesting
      void verifyFileSignature(
              X509Certificate trustedRoot, byte[] signedFileBytes, @Nullable Date validationDate)
              throws CertValidationException {
          CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, signerCert);
          CertUtils.verifyRsaSha256Signature(signerCert.getPublicKey(), signature, signedFileBytes);
      }
  
      /**
       * Parses a byte array as the content of the XML file containing the signature and related
       * certificates.
       *
       * @param bytes the bytes of the XML file
       * @return a {@code SigXml} instance that contains the parsing result
       * @throws CertParsingException if any parsing error occurs
       */
      public static SigXml parse(byte[] bytes) throws CertParsingException {
          Element rootNode = CertUtils.getXmlRootNode(bytes);
          return new SigXml(
                  parseIntermediateCerts(rootNode), parseSignerCert(rootNode),
                  parseFileSignature(rootNode));
      }
  
      private static List<X509Certificate> parseIntermediateCerts(Element rootNode)
              throws CertParsingException {
          List<String> contents =
                  CertUtils.getXmlNodeContents(
                          CertUtils.MUST_EXIST_UNENFORCED,
                          rootNode,
                          INTERMEDIATE_CERT_LIST_TAG,
                          INTERMEDIATE_CERT_ITEM_TAG);
          List<X509Certificate> res = new ArrayList<>();
          for (String content : contents) {
              res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
          }
          return Collections.unmodifiableList(res);
      }
  
      private static X509Certificate parseSignerCert(Element rootNode) throws CertParsingException {
          List<String> contents =
                  CertUtils.getXmlNodeContents(
                          CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNER_CERT_NODE_TAG);
          return CertUtils.decodeCert(CertUtils.decodeBase64(contents.get(0)));
      }
  
      private static byte[] parseFileSignature(Element rootNode) throws CertParsingException {
          List<String> contents =
                  CertUtils.getXmlNodeContents(
                          CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNATURE_NODE_TAG);
          return CertUtils.decodeBase64(contents.get(0));
      }
  }
  