blob: 119f5d0de0a06c9859c795ec07407485be887640 [file] [log] [blame]
Chad Brubakerd3af9622015-11-16 10:48:20 -08001/*
2 * Copyright (C) 2015 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 android.security.net.config;
18
19import android.os.Environment;
20import android.os.UserHandle;
21import android.util.ArraySet;
Chad Brubaker01e96822016-07-07 10:17:24 -070022import android.util.Log;
Chad Brubakerd3af9622015-11-16 10:48:20 -080023import android.util.Pair;
24import java.io.BufferedInputStream;
25import java.io.File;
26import java.io.FileInputStream;
27import java.io.InputStream;
28import java.io.IOException;
29import java.security.cert.Certificate;
30import java.security.cert.CertificateException;
31import java.security.cert.CertificateFactory;
32import java.security.cert.X509Certificate;
Chad Brubakeraa6c3c32015-12-18 13:43:28 -080033import java.util.Collections;
Chad Brubakerd3af9622015-11-16 10:48:20 -080034import java.util.Set;
35import libcore.io.IoUtils;
36
Chad Brubaker7845e442015-12-01 13:03:27 -080037import com.android.org.conscrypt.Hex;
Chad Brubakerd3af9622015-11-16 10:48:20 -080038import com.android.org.conscrypt.NativeCrypto;
39
40import javax.security.auth.x500.X500Principal;
41
42/**
43 * {@link CertificateSource} based on a directory where certificates are stored as individual files
44 * named after a hash of their SubjectName for more efficient lookups.
45 * @hide
46 */
47abstract class DirectoryCertificateSource implements CertificateSource {
Chad Brubaker01e96822016-07-07 10:17:24 -070048 private static final String LOG_TAG = "DirectoryCertificateSrc";
Chad Brubakerd3af9622015-11-16 10:48:20 -080049 private final File mDir;
50 private final Object mLock = new Object();
51 private final CertificateFactory mCertFactory;
52
53 private Set<X509Certificate> mCertificates;
54
55 protected DirectoryCertificateSource(File caDir) {
56 mDir = caDir;
57 try {
58 mCertFactory = CertificateFactory.getInstance("X.509");
59 } catch (CertificateException e) {
60 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
61 }
62 }
63
64 protected abstract boolean isCertMarkedAsRemoved(String caFile);
65
66 @Override
67 public Set<X509Certificate> getCertificates() {
68 // TODO: loading all of these is wasteful, we should instead use a keystore style API.
69 synchronized (mLock) {
70 if (mCertificates != null) {
71 return mCertificates;
72 }
73
74 Set<X509Certificate> certs = new ArraySet<X509Certificate>();
75 if (mDir.isDirectory()) {
76 for (String caFile : mDir.list()) {
77 if (isCertMarkedAsRemoved(caFile)) {
78 continue;
79 }
80 X509Certificate cert = readCertificate(caFile);
81 if (cert != null) {
82 certs.add(cert);
83 }
84 }
85 }
86 mCertificates = certs;
87 return mCertificates;
88 }
89 }
90
91 @Override
92 public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) {
93 return findCert(cert.getSubjectX500Principal(), new CertSelector() {
94 @Override
95 public boolean match(X509Certificate ca) {
96 return ca.getPublicKey().equals(cert.getPublicKey());
97 }
98 });
99 }
100
Chad Brubakerfa9beeb2015-11-25 13:12:55 -0800101 @Override
102 public X509Certificate findByIssuerAndSignature(final X509Certificate cert) {
103 return findCert(cert.getIssuerX500Principal(), new CertSelector() {
104 @Override
105 public boolean match(X509Certificate ca) {
106 try {
107 cert.verify(ca.getPublicKey());
108 return true;
109 } catch (Exception e) {
110 return false;
111 }
112 }
113 });
114 }
115
Chad Brubakeraa6c3c32015-12-18 13:43:28 -0800116 @Override
117 public Set<X509Certificate> findAllByIssuerAndSignature(final X509Certificate cert) {
118 return findCerts(cert.getIssuerX500Principal(), new CertSelector() {
119 @Override
120 public boolean match(X509Certificate ca) {
121 try {
122 cert.verify(ca.getPublicKey());
123 return true;
124 } catch (Exception e) {
125 return false;
126 }
127 }
128 });
129 }
130
Chad Brubakerbf9a82a2016-03-25 10:12:19 -0700131 @Override
132 public void handleTrustStorageUpdate() {
133 synchronized (mLock) {
134 mCertificates = null;
135 }
136 }
137
Chad Brubakerd3af9622015-11-16 10:48:20 -0800138 private static interface CertSelector {
139 boolean match(X509Certificate cert);
140 }
141
Chad Brubakeraa6c3c32015-12-18 13:43:28 -0800142 private Set<X509Certificate> findCerts(X500Principal subj, CertSelector selector) {
143 String hash = getHash(subj);
144 Set<X509Certificate> certs = null;
145 for (int index = 0; index >= 0; index++) {
146 String fileName = hash + "." + index;
147 if (!new File(mDir, fileName).exists()) {
148 break;
149 }
150 if (isCertMarkedAsRemoved(fileName)) {
151 continue;
152 }
153 X509Certificate cert = readCertificate(fileName);
Chad Brubaker01e96822016-07-07 10:17:24 -0700154 if (cert == null) {
155 continue;
156 }
Chad Brubakeraa6c3c32015-12-18 13:43:28 -0800157 if (!subj.equals(cert.getSubjectX500Principal())) {
158 continue;
159 }
160 if (selector.match(cert)) {
161 if (certs == null) {
162 certs = new ArraySet<X509Certificate>();
163 }
164 certs.add(cert);
165 }
166 }
167 return certs != null ? certs : Collections.<X509Certificate>emptySet();
168 }
169
Chad Brubakerd3af9622015-11-16 10:48:20 -0800170 private X509Certificate findCert(X500Principal subj, CertSelector selector) {
171 String hash = getHash(subj);
172 for (int index = 0; index >= 0; index++) {
173 String fileName = hash + "." + index;
174 if (!new File(mDir, fileName).exists()) {
175 break;
176 }
177 if (isCertMarkedAsRemoved(fileName)) {
178 continue;
179 }
180 X509Certificate cert = readCertificate(fileName);
Chad Brubaker01e96822016-07-07 10:17:24 -0700181 if (cert == null) {
182 continue;
183 }
Chad Brubakerd3af9622015-11-16 10:48:20 -0800184 if (!subj.equals(cert.getSubjectX500Principal())) {
185 continue;
186 }
187 if (selector.match(cert)) {
188 return cert;
189 }
190 }
191 return null;
192 }
193
194 private String getHash(X500Principal name) {
195 int hash = NativeCrypto.X509_NAME_hash_old(name);
Chad Brubaker7845e442015-12-01 13:03:27 -0800196 return Hex.intToHexString(hash, 8);
Chad Brubakerd3af9622015-11-16 10:48:20 -0800197 }
198
199 private X509Certificate readCertificate(String file) {
200 InputStream is = null;
201 try {
202 is = new BufferedInputStream(new FileInputStream(new File(mDir, file)));
203 return (X509Certificate) mCertFactory.generateCertificate(is);
204 } catch (CertificateException | IOException e) {
Chad Brubaker01e96822016-07-07 10:17:24 -0700205 Log.e(LOG_TAG, "Failed to read certificate from " + file, e);
Chad Brubakerd3af9622015-11-16 10:48:20 -0800206 return null;
207 } finally {
208 IoUtils.closeQuietly(is);
209 }
210 }
211}