android: Cache certificates from multiple KeyStores
[strongswan.git] / src / frontends / android / src / org / strongswan / android / logic / TrustedCertificateManager.java
1 /*
2 * Copyright (C) 2012-2014 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 package org.strongswan.android.logic;
19
20 import java.security.KeyStore;
21 import java.security.KeyStoreException;
22 import java.security.cert.Certificate;
23 import java.security.cert.X509Certificate;
24 import java.util.ArrayList;
25 import java.util.Enumeration;
26 import java.util.Hashtable;
27 import java.util.concurrent.locks.ReentrantReadWriteLock;
28
29 import android.util.Log;
30
31 public class TrustedCertificateManager
32 {
33 private static final String TAG = TrustedCertificateManager.class.getSimpleName();
34 private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
35 private Hashtable<String, X509Certificate> mCACerts = new Hashtable<String, X509Certificate>();
36 private boolean mLoaded;
37 private final ArrayList<KeyStore> mKeyStores = new ArrayList<KeyStore>();
38
39 /**
40 * Private constructor to prevent instantiation from other classes.
41 */
42 private TrustedCertificateManager()
43 {
44 for (String name : new String[] { "LocalCertificateStore", "AndroidCAStore" })
45 {
46 KeyStore store;
47 try
48 {
49 store = KeyStore.getInstance(name);
50 store.load(null,null);
51 mKeyStores.add(store);
52 }
53 catch (Exception e)
54 {
55 Log.e(TAG, "Unable to load KeyStore: " + name);
56 e.printStackTrace();
57 }
58 }
59 }
60
61 /**
62 * This is not instantiated until the first call to getInstance()
63 */
64 private static class Singleton {
65 public static final TrustedCertificateManager mInstance = new TrustedCertificateManager();
66 }
67
68 /**
69 * Get the single instance of the CA certificate manager.
70 * @return CA certificate manager
71 */
72 public static TrustedCertificateManager getInstance()
73 {
74 return Singleton.mInstance;
75 }
76
77 /**
78 * Forces a load/reload of the cached CA certificates.
79 * As this takes a while it should be called asynchronously.
80 * @return reference to itself
81 */
82 public TrustedCertificateManager reload()
83 {
84 Log.d(TAG, "Force reload of cached CA certificates");
85 this.mLock.writeLock().lock();
86 loadCertificates();
87 this.mLock.writeLock().unlock();
88 return this;
89 }
90
91 /**
92 * Ensures that the certificates are loaded but does not force a reload.
93 * As this takes a while if the certificates are not loaded yet it should
94 * be called asynchronously.
95 * @return reference to itself
96 */
97 public TrustedCertificateManager load()
98 {
99 Log.d(TAG, "Ensure cached CA certificates are loaded");
100 this.mLock.writeLock().lock();
101 if (!this.mLoaded)
102 {
103 loadCertificates();
104 }
105 this.mLock.writeLock().unlock();
106 return this;
107 }
108
109 /**
110 * Opens the CA certificate KeyStore and loads the cached certificates.
111 * The lock must be locked when calling this method.
112 */
113 private void loadCertificates()
114 {
115 Log.d(TAG, "Load cached CA certificates");
116 Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
117 for (KeyStore store : this.mKeyStores)
118 {
119 fetchCertificates(certs, store);
120 }
121 this.mCACerts = certs;
122 this.mLoaded = true;
123 Log.d(TAG, "Cached CA certificates loaded");
124 }
125
126 /**
127 * Load all X.509 certificates from the given KeyStore.
128 * @param certs Hashtable to store certificates in
129 * @param store KeyStore to load certificates from
130 */
131 private void fetchCertificates(Hashtable<String, X509Certificate> certs, KeyStore store)
132 {
133 try
134 {
135 Enumeration<String> aliases = store.aliases();
136 while (aliases.hasMoreElements())
137 {
138 String alias = aliases.nextElement();
139 Certificate cert;
140 cert = store.getCertificate(alias);
141 if (cert != null && cert instanceof X509Certificate)
142 {
143 certs.put(alias, (X509Certificate)cert);
144 }
145 }
146 }
147 catch (KeyStoreException ex)
148 {
149 ex.printStackTrace();
150 }
151 }
152
153 /**
154 * Retrieve the CA certificate with the given alias.
155 * @param alias alias of the certificate to get
156 * @return the certificate, null if not found
157 */
158 public X509Certificate getCACertificateFromAlias(String alias)
159 {
160 X509Certificate certificate = null;
161
162 if (this.mLock.readLock().tryLock())
163 {
164 certificate = this.mCACerts.get(alias);
165 this.mLock.readLock().unlock();
166 }
167 else
168 { /* if we cannot get the lock load it directly from the KeyStore,
169 * should be fast for a single certificate */
170 for (KeyStore store : this.mKeyStores)
171 {
172 try
173 {
174 Certificate cert = store.getCertificate(alias);
175 if (cert != null && cert instanceof X509Certificate)
176 {
177 certificate = (X509Certificate)cert;
178 break;
179 }
180 }
181 catch (KeyStoreException e)
182 {
183 e.printStackTrace();
184 }
185 }
186 }
187 return certificate;
188 }
189
190 /**
191 * Get all CA certificates (from all keystores).
192 * @return Hashtable mapping aliases to certificates
193 */
194 @SuppressWarnings("unchecked")
195 public Hashtable<String, X509Certificate> getAllCACertificates()
196 {
197 Hashtable<String, X509Certificate> certs;
198 this.mLock.readLock().lock();
199 certs = (Hashtable<String, X509Certificate>)this.mCACerts.clone();
200 this.mLock.readLock().unlock();
201 return certs;
202 }
203
204 /**
205 * Get only the system-wide CA certificates.
206 * @return Hashtable mapping aliases to certificates
207 */
208 public Hashtable<String, X509Certificate> getSystemCACertificates()
209 {
210 return getCertificates("system:");
211 }
212
213 /**
214 * Get only the CA certificates installed by the user.
215 * @return Hashtable mapping aliases to certificates
216 */
217 public Hashtable<String, X509Certificate> getUserCACertificates()
218 {
219 return getCertificates("user:");
220 }
221
222 /**
223 * Get only the local CA certificates installed by the user.
224 * @return Hashtable mapping aliases to certificates
225 */
226 public Hashtable<String, X509Certificate> getLocalCACertificates()
227 {
228 return getCertificates("local:");
229 }
230
231 /**
232 * Get all certificates whose aliases start with the given prefix.
233 * @param prefix prefix to filter certificates
234 * @return Hashtable mapping aliases to certificates
235 */
236 private Hashtable<String, X509Certificate> getCertificates(String prefix)
237 {
238 Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
239 this.mLock.readLock().lock();
240 for (String alias : this.mCACerts.keySet())
241 {
242 if (alias.startsWith(prefix))
243 {
244 certs.put(alias, this.mCACerts.get(alias));
245 }
246 }
247 this.mLock.readLock().unlock();
248 return certs;
249 }
250 }