/*
 * 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));
    }
}
