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 org.w3c.dom.Element;
  
  import java.security.SecureRandom;
  import java.security.cert.CertPath;
  import java.security.cert.X509Certificate;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Date;
  import java.util.List;
  
  /**
   * Parses and holds the XML file containing the list of THM public-key certificates and related
   * metadata.
   */
  public final class CertXml {
  
      private static final String METADATA_NODE_TAG = "metadata";
      private static final String METADATA_SERIAL_NODE_TAG = "serial";
      private static final String ENDPOINT_CERT_LIST_TAG = "endpoints";
      private static final String ENDPOINT_CERT_ITEM_TAG = "cert";
      private static final String INTERMEDIATE_CERT_LIST_TAG = "intermediates";
      private static final String INTERMEDIATE_CERT_ITEM_TAG = "cert";
  
      private final long serial;
      private final List<X509Certificate> intermediateCerts;
      private final List<X509Certificate> endpointCerts;
  
      private CertXml(
              long serial,
              List<X509Certificate> intermediateCerts,
              List<X509Certificate> endpointCerts) {
          this.serial = serial;
          this.intermediateCerts = intermediateCerts;
          this.endpointCerts = endpointCerts;
      }
  
      /** Gets the serial number of the XML file containing public-key certificates. */
      public long getSerial() {
          return serial;
      }
  
      @VisibleForTesting
      List<X509Certificate> getAllIntermediateCerts() {
          return intermediateCerts;
      }
  
      @VisibleForTesting
      List<X509Certificate> getAllEndpointCerts() {
          return endpointCerts;
      }
  
      /**
       * Chooses a random endpoint certificate from the XML file, validates the chosen certificate,
       * and returns the certificate path including the chosen certificate if it is valid.
       *
       * @param trustedRoot the trusted root certificate
       * @return the certificate path including the chosen certificate if the certificate is valid
       * @throws CertValidationException if the chosen certificate cannot be validated based on the
       *                                 trusted root certificate
       */
      public CertPath getRandomEndpointCert(X509Certificate trustedRoot)
              throws CertValidationException {
          return getEndpointCert(
                  new SecureRandom().nextInt(this.endpointCerts.size()),
                  /*validationDate=*/ null,
                  trustedRoot);
      }
  
      @VisibleForTesting
      CertPath getEndpointCert(
              int index, @Nullable Date validationDate, X509Certificate trustedRoot)
              throws CertValidationException {
          X509Certificate chosenCert = endpointCerts.get(index);
          return CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, chosenCert);
      }
  
      /**
       * Parses a byte array as the content of the XML file containing a list of endpoint
       * certificates.
       *
       * @param bytes the bytes of the XML file
       * @return a {@code CertXml} instance that contains the parsing result
       * @throws CertParsingException if any parsing error occurs
       */
      public static CertXml parse(byte[] bytes) throws CertParsingException {
          Element rootNode = CertUtils.getXmlRootNode(bytes);
          return new CertXml(
                  parseSerial(rootNode),
                  parseIntermediateCerts(rootNode),
                  parseEndpointCerts(rootNode));
      }
  
      private static long parseSerial(Element rootNode) throws CertParsingException {
          List<String> contents =
                  CertUtils.getXmlNodeContents(
                          CertUtils.MUST_EXIST_EXACTLY_ONE,
                          rootNode,
                          METADATA_NODE_TAG,
                          METADATA_SERIAL_NODE_TAG);
          return Long.parseLong(contents.get(0));
      }
  
      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 List<X509Certificate> parseEndpointCerts(Element rootNode)
              throws CertParsingException {
          List<String> contents =
                  CertUtils.getXmlNodeContents(
                          CertUtils.MUST_EXIST_AT_LEAST_ONE,
                          rootNode,
                          ENDPOINT_CERT_LIST_TAG,
                          ENDPOINT_CERT_ITEM_TAG);
          List<X509Certificate> res = new ArrayList<>();
          for (String content : contents) {
              res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
          }
          return Collections.unmodifiableList(res);
      }
  }
  