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 
17 package com.android.server.locksettings.recoverablekeystore.certificate;
18 
19 import android.annotation.Nullable;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 
23 import org.w3c.dom.Element;
24 
25 import java.security.SecureRandom;
26 import java.security.cert.CertPath;
27 import java.security.cert.X509Certificate;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Date;
31 import java.util.List;
32 
33 /**
34  * Parses and holds the XML file containing the list of THM public-key certificates and related
35  * metadata.
36  */
37 public final class CertXml {
38 
39     private static final String METADATA_NODE_TAG = "metadata";
40     private static final String METADATA_SERIAL_NODE_TAG = "serial";
41     private static final String ENDPOINT_CERT_LIST_TAG = "endpoints";
42     private static final String ENDPOINT_CERT_ITEM_TAG = "cert";
43     private static final String INTERMEDIATE_CERT_LIST_TAG = "intermediates";
44     private static final String INTERMEDIATE_CERT_ITEM_TAG = "cert";
45 
46     private final long serial;
47     private final List<X509Certificate> intermediateCerts;
48     private final List<X509Certificate> endpointCerts;
49 
CertXml( long serial, List<X509Certificate> intermediateCerts, List<X509Certificate> endpointCerts)50     private CertXml(
51             long serial,
52             List<X509Certificate> intermediateCerts,
53             List<X509Certificate> endpointCerts) {
54         this.serial = serial;
55         this.intermediateCerts = intermediateCerts;
56         this.endpointCerts = endpointCerts;
57     }
58 
59     /** Gets the serial number of the XML file containing public-key certificates. */
getSerial()60     public long getSerial() {
61         return serial;
62     }
63 
64     @VisibleForTesting
getAllIntermediateCerts()65     List<X509Certificate> getAllIntermediateCerts() {
66         return intermediateCerts;
67     }
68 
69     @VisibleForTesting
getAllEndpointCerts()70     List<X509Certificate> getAllEndpointCerts() {
71         return endpointCerts;
72     }
73 
74     /**
75      * Chooses a random endpoint certificate from the XML file, validates the chosen certificate,
76      * and returns the certificate path including the chosen certificate if it is valid.
77      *
78      * @param trustedRoot the trusted root certificate
79      * @return the certificate path including the chosen certificate if the certificate is valid
80      * @throws CertValidationException if the chosen certificate cannot be validated based on the
81      *                                 trusted root certificate
82      */
getRandomEndpointCert(X509Certificate trustedRoot)83     public CertPath getRandomEndpointCert(X509Certificate trustedRoot)
84             throws CertValidationException {
85         return getEndpointCert(
86                 new SecureRandom().nextInt(this.endpointCerts.size()),
87                 /*validationDate=*/ null,
88                 trustedRoot);
89     }
90 
91     @VisibleForTesting
getEndpointCert( int index, @Nullable Date validationDate, X509Certificate trustedRoot)92     CertPath getEndpointCert(
93             int index, @Nullable Date validationDate, X509Certificate trustedRoot)
94             throws CertValidationException {
95         X509Certificate chosenCert = endpointCerts.get(index);
96         return CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, chosenCert);
97     }
98 
99     /**
100      * Parses a byte array as the content of the XML file containing a list of endpoint
101      * certificates.
102      *
103      * @param bytes the bytes of the XML file
104      * @return a {@code CertXml} instance that contains the parsing result
105      * @throws CertParsingException if any parsing error occurs
106      */
parse(byte[] bytes)107     public static CertXml parse(byte[] bytes) throws CertParsingException {
108         Element rootNode = CertUtils.getXmlRootNode(bytes);
109         return new CertXml(
110                 parseSerial(rootNode),
111                 parseIntermediateCerts(rootNode),
112                 parseEndpointCerts(rootNode));
113     }
114 
parseSerial(Element rootNode)115     private static long parseSerial(Element rootNode) throws CertParsingException {
116         List<String> contents =
117                 CertUtils.getXmlNodeContents(
118                         CertUtils.MUST_EXIST_EXACTLY_ONE,
119                         rootNode,
120                         METADATA_NODE_TAG,
121                         METADATA_SERIAL_NODE_TAG);
122         return Long.parseLong(contents.get(0));
123     }
124 
parseIntermediateCerts(Element rootNode)125     private static List<X509Certificate> parseIntermediateCerts(Element rootNode)
126             throws CertParsingException {
127         List<String> contents =
128                 CertUtils.getXmlNodeContents(
129                         CertUtils.MUST_EXIST_UNENFORCED,
130                         rootNode,
131                         INTERMEDIATE_CERT_LIST_TAG,
132                         INTERMEDIATE_CERT_ITEM_TAG);
133         List<X509Certificate> res = new ArrayList<>();
134         for (String content : contents) {
135             res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
136         }
137         return Collections.unmodifiableList(res);
138     }
139 
parseEndpointCerts(Element rootNode)140     private static List<X509Certificate> parseEndpointCerts(Element rootNode)
141             throws CertParsingException {
142         List<String> contents =
143                 CertUtils.getXmlNodeContents(
144                         CertUtils.MUST_EXIST_AT_LEAST_ONE,
145                         rootNode,
146                         ENDPOINT_CERT_LIST_TAG,
147                         ENDPOINT_CERT_ITEM_TAG);
148         List<X509Certificate> res = new ArrayList<>();
149         for (String content : contents) {
150             res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
151         }
152         return Collections.unmodifiableList(res);
153     }
154 }
155