1 /*
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 
17 package android.security.net.config;
18 
19 import android.os.Environment;
20 import android.os.UserHandle;
21 import android.util.ArraySet;
22 import android.util.Log;
23 import android.util.Pair;
24 import java.io.BufferedInputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.InputStream;
28 import java.io.IOException;
29 import java.security.cert.Certificate;
30 import java.security.cert.CertificateException;
31 import java.security.cert.CertificateFactory;
32 import java.security.cert.X509Certificate;
33 import java.util.Collections;
34 import java.util.Set;
35 import libcore.io.IoUtils;
36 
37 import com.android.org.conscrypt.Hex;
38 import com.android.org.conscrypt.NativeCrypto;
39 
40 import 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  */
47 abstract class DirectoryCertificateSource implements CertificateSource {
48     private static final String LOG_TAG = "DirectoryCertificateSrc";
49     private final File mDir;
50     private final Object mLock = new Object();
51     private final CertificateFactory mCertFactory;
52 
53     private Set<X509Certificate> mCertificates;
54 
DirectoryCertificateSource(File caDir)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 
isCertMarkedAsRemoved(String caFile)64     protected abstract boolean isCertMarkedAsRemoved(String caFile);
65 
66     @Override
getCertificates()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
findBySubjectAndPublicKey(final X509Certificate cert)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 
101     @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 
116     @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 
131     @Override
132     public void handleTrustStorageUpdate() {
133         synchronized (mLock) {
134             mCertificates = null;
135         }
136     }
137 
138     private static interface CertSelector {
139         boolean match(X509Certificate cert);
140     }
141 
142     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);
154             if (cert == null) {
155                 continue;
156             }
157             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 
170     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);
181             if (cert == null) {
182                 continue;
183             }
184             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);
196         return Hex.intToHexString(hash, 8);
197     }
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) {
205             Log.e(LOG_TAG, "Failed to read certificate from " + file, e);
206             return null;
207         } finally {
208             IoUtils.closeQuietly(is);
209         }
210     }
211 }
212