1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.locksettings.recoverablekeystore.certificate;
18
19import android.annotation.Nullable;
20
21import com.android.internal.annotations.VisibleForTesting;
22
23import java.security.cert.X509Certificate;
24import java.util.ArrayList;
25import java.util.Collections;
26import java.util.Date;
27import java.util.List;
28
29import org.w3c.dom.Element;
30
31/**
32 * Parses and holds the XML file containing the signature of the XML file containing the list of THM
33 * public-key certificates.
34 */
35public final class SigXml {
36
37    private static final String INTERMEDIATE_CERT_LIST_TAG = "intermediates";
38    private static final String INTERMEDIATE_CERT_ITEM_TAG = "cert";
39    private static final String SIGNER_CERT_NODE_TAG = "certificate";
40    private static final String SIGNATURE_NODE_TAG = "value";
41
42    private final List<X509Certificate> intermediateCerts;
43    private final X509Certificate signerCert;
44    private final byte[] signature;
45
46    private SigXml(
47            List<X509Certificate> intermediateCerts, X509Certificate signerCert, byte[] signature) {
48        this.intermediateCerts = intermediateCerts;
49        this.signerCert = signerCert;
50        this.signature = signature;
51    }
52
53    /**
54     * Verifies the signature contained in this XML file against a trusted root certificate and the
55     * binary content of another file. The signer's public-key certificate and possible intermediate
56     * CA certificates are included in this XML file, and will be validated against the trusted root
57     * certificate.
58     *
59     * @param trustedRoot     the trusted root certificate
60     * @param signedFileBytes the original file content that has been signed
61     * @throws CertValidationException if the signature verification fails, or the signer's
62     *                                 certificate contained in this XML file cannot be validated
63     *                                 based on the trusted root certificate
64     */
65    public void verifyFileSignature(X509Certificate trustedRoot, byte[] signedFileBytes)
66            throws CertValidationException {
67        verifyFileSignature(trustedRoot, signedFileBytes, /*validationDate=*/ null);
68    }
69
70    @VisibleForTesting
71    void verifyFileSignature(
72            X509Certificate trustedRoot, byte[] signedFileBytes, @Nullable Date validationDate)
73            throws CertValidationException {
74        CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, signerCert);
75        CertUtils.verifyRsaSha256Signature(signerCert.getPublicKey(), signature, signedFileBytes);
76    }
77
78    /**
79     * Parses a byte array as the content of the XML file containing the signature and related
80     * certificates.
81     *
82     * @param bytes the bytes of the XML file
83     * @return a {@code SigXml} instance that contains the parsing result
84     * @throws CertParsingException if any parsing error occurs
85     */
86    public static SigXml parse(byte[] bytes) throws CertParsingException {
87        Element rootNode = CertUtils.getXmlRootNode(bytes);
88        return new SigXml(
89                parseIntermediateCerts(rootNode), parseSignerCert(rootNode),
90                parseFileSignature(rootNode));
91    }
92
93    private static List<X509Certificate> parseIntermediateCerts(Element rootNode)
94            throws CertParsingException {
95        List<String> contents =
96                CertUtils.getXmlNodeContents(
97                        CertUtils.MUST_EXIST_UNENFORCED,
98                        rootNode,
99                        INTERMEDIATE_CERT_LIST_TAG,
100                        INTERMEDIATE_CERT_ITEM_TAG);
101        List<X509Certificate> res = new ArrayList<>();
102        for (String content : contents) {
103            res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
104        }
105        return Collections.unmodifiableList(res);
106    }
107
108    private static X509Certificate parseSignerCert(Element rootNode) throws CertParsingException {
109        List<String> contents =
110                CertUtils.getXmlNodeContents(
111                        CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNER_CERT_NODE_TAG);
112        return CertUtils.decodeCert(CertUtils.decodeBase64(contents.get(0)));
113    }
114
115    private static byte[] parseFileSignature(Element rootNode) throws CertParsingException {
116        List<String> contents =
117                CertUtils.getXmlNodeContents(
118                        CertUtils.MUST_EXIST_EXACTLY_ONE, rootNode, SIGNATURE_NODE_TAG);
119        return CertUtils.decodeBase64(contents.get(0));
120    }
121}
122