android: Make TrustedCertificateManager an Observable
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / logic / TrustedCertificateManager.java
1 /*
2 * Copyright (C) 2012-2015 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 android.util.Log;
21
22 import java.security.KeyStore;
23 import java.security.KeyStoreException;
24 import java.security.cert.Certificate;
25 import java.security.cert.X509Certificate;
26 import java.util.ArrayList;
27 import java.util.Enumeration;
28 import java.util.Hashtable;
29 import java.util.Observable;
30 import java.util.concurrent.locks.ReentrantReadWriteLock;
31
32 public class TrustedCertificateManager extends Observable
33 {
34 private static final String TAG = TrustedCertificateManager.class.getSimpleName();
35 private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
36 private Hashtable<String, X509Certificate> mCACerts = new Hashtable<String, X509Certificate>();
37 private volatile boolean mReload;
38 private boolean mLoaded;
39 private final ArrayList<KeyStore> mKeyStores = new ArrayList<KeyStore>();
40
41 public enum TrustedCertificateSource
42 {
43 SYSTEM("system:"),
44 USER("user:"),
45 LOCAL("local:");
46
47 private final String mPrefix;
48
49 private TrustedCertificateSource(String prefix)
50 {
51 mPrefix = prefix;
52 }
53
54 private String getPrefix()
55 {
56 return mPrefix;
57 }
58 }
59
60 /**
61 * Private constructor to prevent instantiation from other classes.
62 */
63 private TrustedCertificateManager()
64 {
65 for (String name : new String[]{"LocalCertificateStore", "AndroidCAStore"})
66 {
67 KeyStore store;
68 try
69 {
70 store = KeyStore.getInstance(name);
71 store.load(null, null);
72 mKeyStores.add(store);
73 }
74 catch (Exception e)
75 {
76 Log.e(TAG, "Unable to load KeyStore: " + name);
77 e.printStackTrace();
78 }
79 }
80 }
81
82 /**
83 * This is not instantiated until the first call to getInstance()
84 */
85 private static class Singleton
86 {
87 public static final TrustedCertificateManager mInstance = new TrustedCertificateManager();
88 }
89
90 /**
91 * Get the single instance of the CA certificate manager.
92 *
93 * @return CA certificate manager
94 */
95 public static TrustedCertificateManager getInstance()
96 {
97 return Singleton.mInstance;
98 }
99
100 /**
101 * Invalidates the current load state so that the next call to load()
102 * will force a reload of the cached CA certificates.
103 *
104 * Observers are notified when this method is called.
105 *
106 * @return reference to itself
107 */
108 public TrustedCertificateManager reset()
109 {
110 Log.d(TAG, "Force reload of cached CA certificates on next load");
111 this.mReload = true;
112 this.setChanged();
113 this.notifyObservers();
114 return this;
115 }
116
117 /**
118 * Ensures that the certificates are loaded but does not force a reload.
119 * As this takes a while if the certificates are not loaded yet it should
120 * be called asynchronously.
121 *
122 * Observers are only notified when the certificates are initially loaded, not when reloaded.
123 *
124 * @return reference to itself
125 */
126 public TrustedCertificateManager load()
127 {
128 Log.d(TAG, "Ensure cached CA certificates are loaded");
129 this.mLock.writeLock().lock();
130 if (!this.mLoaded || this.mReload)
131 {
132 this.mReload = false;
133 loadCertificates();
134 }
135 this.mLock.writeLock().unlock();
136 return this;
137 }
138
139 /**
140 * Opens the CA certificate KeyStore and loads the cached certificates.
141 * The lock must be locked when calling this method.
142 */
143 private void loadCertificates()
144 {
145 Log.d(TAG, "Load cached CA certificates");
146 Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
147 for (KeyStore store : this.mKeyStores)
148 {
149 fetchCertificates(certs, store);
150 }
151 this.mCACerts = certs;
152 if (!this.mLoaded)
153 {
154 this.setChanged();
155 this.notifyObservers();
156 this.mLoaded = true;
157 }
158 Log.d(TAG, "Cached CA certificates loaded");
159 }
160
161 /**
162 * Load all X.509 certificates from the given KeyStore.
163 *
164 * @param certs Hashtable to store certificates in
165 * @param store KeyStore to load certificates from
166 */
167 private void fetchCertificates(Hashtable<String, X509Certificate> certs, KeyStore store)
168 {
169 try
170 {
171 Enumeration<String> aliases = store.aliases();
172 while (aliases.hasMoreElements())
173 {
174 String alias = aliases.nextElement();
175 Certificate cert;
176 cert = store.getCertificate(alias);
177 if (cert != null && cert instanceof X509Certificate)
178 {
179 certs.put(alias, (X509Certificate)cert);
180 }
181 }
182 }
183 catch (KeyStoreException ex)
184 {
185 ex.printStackTrace();
186 }
187 }
188
189 /**
190 * Retrieve the CA certificate with the given alias.
191 *
192 * @param alias alias of the certificate to get
193 * @return the certificate, null if not found
194 */
195 public X509Certificate getCACertificateFromAlias(String alias)
196 {
197 X509Certificate certificate = null;
198
199 if (this.mLock.readLock().tryLock())
200 {
201 certificate = this.mCACerts.get(alias);
202 this.mLock.readLock().unlock();
203 }
204 else
205 { /* if we cannot get the lock load it directly from the KeyStore,
206 * should be fast for a single certificate */
207 for (KeyStore store : this.mKeyStores)
208 {
209 try
210 {
211 Certificate cert = store.getCertificate(alias);
212 if (cert != null && cert instanceof X509Certificate)
213 {
214 certificate = (X509Certificate)cert;
215 break;
216 }
217 }
218 catch (KeyStoreException e)
219 {
220 e.printStackTrace();
221 }
222 }
223 }
224 return certificate;
225 }
226
227 /**
228 * Get all CA certificates (from all keystores).
229 *
230 * @return Hashtable mapping aliases to certificates
231 */
232 @SuppressWarnings("unchecked")
233 public Hashtable<String, X509Certificate> getAllCACertificates()
234 {
235 Hashtable<String, X509Certificate> certs;
236 this.mLock.readLock().lock();
237 certs = (Hashtable<String, X509Certificate>)this.mCACerts.clone();
238 this.mLock.readLock().unlock();
239 return certs;
240 }
241
242 /**
243 * Get all certificates from the given source.
244 *
245 * @param source type to filter certificates
246 * @return Hashtable mapping aliases to certificates
247 */
248 public Hashtable<String, X509Certificate> getCACertificates(TrustedCertificateSource source)
249 {
250 Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
251 this.mLock.readLock().lock();
252 for (String alias : this.mCACerts.keySet())
253 {
254 if (alias.startsWith(source.getPrefix()))
255 {
256 certs.put(alias, this.mCACerts.get(alias));
257 }
258 }
259 this.mLock.readLock().unlock();
260 return certs;
261 }
262 }