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      * @param validationDate use null for current time
80      * @return the certificate path including the chosen certificate if the certificate is valid
81      * @throws CertValidationException if the chosen certificate cannot be validated based on the
82      *                                 trusted root certificate
83      */
getRandomEndpointCert(X509Certificate trustedRoot, @Nullable Date validationDate)84     public CertPath getRandomEndpointCert(X509Certificate trustedRoot,
85             @Nullable Date validationDate)throws CertValidationException {
86         return getEndpointCert(
87                 new SecureRandom().nextInt(this.endpointCerts.size()),
88                 validationDate,
89                 trustedRoot);
90     }
91 
92     @VisibleForTesting
getEndpointCert( int index, @Nullable Date validationDate, X509Certificate trustedRoot)93     CertPath getEndpointCert(
94             int index, @Nullable Date validationDate, X509Certificate trustedRoot)
95             throws CertValidationException {
96         X509Certificate chosenCert = endpointCerts.get(index);
97         return CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, chosenCert);
98     }
99 
100     /**
101      * Parses a byte array as the content of the XML file containing a list of endpoint
102      * certificates.
103      *
104      * @param bytes the bytes of the XML file
105      * @return a {@code CertXml} instance that contains the parsing result
106      * @throws CertParsingException if any parsing error occurs
107      */
parse(byte[] bytes)108     public static CertXml parse(byte[] bytes) throws CertParsingException {
109         Element rootNode = CertUtils.getXmlRootNode(bytes);
110         return new CertXml(
111                 parseSerial(rootNode),
112                 parseIntermediateCerts(rootNode),
113                 parseEndpointCerts(rootNode));
114     }
115 
parseSerial(Element rootNode)116     private static long parseSerial(Element rootNode) throws CertParsingException {
117         List<String> contents =
118                 CertUtils.getXmlNodeContents(
119                         CertUtils.MUST_EXIST_EXACTLY_ONE,
120                         rootNode,
121                         METADATA_NODE_TAG,
122                         METADATA_SERIAL_NODE_TAG);
123         return Long.parseLong(contents.get(0));
124     }
125 
parseIntermediateCerts(Element rootNode)126     private static List<X509Certificate> parseIntermediateCerts(Element rootNode)
127             throws CertParsingException {
128         List<String> contents =
129                 CertUtils.getXmlNodeContents(
130                         CertUtils.MUST_EXIST_UNENFORCED,
131                         rootNode,
132                         INTERMEDIATE_CERT_LIST_TAG,
133                         INTERMEDIATE_CERT_ITEM_TAG);
134         List<X509Certificate> res = new ArrayList<>();
135         for (String content : contents) {
136             res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
137         }
138         return Collections.unmodifiableList(res);
139     }
140 
parseEndpointCerts(Element rootNode)141     private static List<X509Certificate> parseEndpointCerts(Element rootNode)
142             throws CertParsingException {
143         List<String> contents =
144                 CertUtils.getXmlNodeContents(
145                         CertUtils.MUST_EXIST_AT_LEAST_ONE,
146                         rootNode,
147                         ENDPOINT_CERT_LIST_TAG,
148                         ENDPOINT_CERT_ITEM_TAG);
149         List<X509Certificate> res = new ArrayList<>();
150         for (String content : contents) {
151             res.add(CertUtils.decodeCert(CertUtils.decodeBase64(content)));
152         }
153         return Collections.unmodifiableList(res);
154     }
155 }
156