File Source: CertificateManager.java
/*
P/P * Method: com.dmdirc.CertificateManager__static_init
*/
1 /*
2 * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 package com.dmdirc;
24
25 import com.dmdirc.config.ConfigManager;
26 import com.dmdirc.config.IdentityManager;
27 import com.dmdirc.logger.ErrorLevel;
28 import com.dmdirc.logger.Logger;
29 import com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction;
30 import com.dmdirc.ui.core.dialogs.sslcertificate.SSLCertificateDialogModel;
31
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.security.InvalidAlgorithmParameterException;
36 import java.security.KeyStore;
37 import java.security.KeyStoreException;
38 import java.security.NoSuchAlgorithmException;
39 import java.security.UnrecoverableKeyException;
40 import java.security.cert.CertificateException;
41 import java.security.cert.CertificateParsingException;
42 import java.security.cert.PKIXParameters;
43 import java.security.cert.TrustAnchor;
44 import java.security.cert.X509Certificate;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Set;
52 import java.util.concurrent.Semaphore;
53
54 import javax.naming.InvalidNameException;
55 import javax.naming.ldap.LdapName;
56 import javax.naming.ldap.Rdn;
57 import javax.net.ssl.KeyManager;
58 import javax.net.ssl.KeyManagerFactory;
59 import javax.net.ssl.X509TrustManager;
60 import net.miginfocom.Base64;
61
62 /**
63 * Manages storage and validation of certificates used when connecting to
64 * SSL servers.
65 *
66 * @since 0.6.3m1
67 * @author chris
68 */
69 public class CertificateManager implements X509TrustManager {
70
71 /** The password for the global java cacert file. */
72 private final String cacertpass;
73
74 /** The server name the user is trying to connect to. */
75 private final String serverName;
76
77 /** The configuration manager to use for settings. */
78 private final ConfigManager config;
79
80 /** The set of CAs from the global cacert file. */
81 private Set<X509Certificate> globalTrustedCAs = new HashSet<X509Certificate>();
82
83 /** Whether or not to check specified parts of the certificate. */
84 private boolean checkDate, checkIssuer, checkHost;
85
86 /** Used to synchronise the manager with the certificate dialog. */
87 private final Semaphore actionSem = new Semaphore(0);
88
89 /** The action to perform. */
90 private CertificateAction action;
91
92 /**
93 * Creates a new certificate manager for a client connecting to the
94 * specified server.
95 *
96 * @param serverName The name the user used to connect to the server
97 * @param config The configuration manager to use
98 */
/*
P/P * Method: void com.dmdirc.CertificateManager(String, ConfigManager)
*
* Preconditions:
* config != null
*
* Presumptions:
* com.dmdirc.config.ConfigManager:getOption(...)@102 != null
*
* Postconditions:
* this.actionSem == &new Semaphore(CertificateManager#2)
* this.cacertpass != null
* init'ed(this.checkDate)
* init'ed(this.checkHost)
* init'ed(this.checkIssuer)
* this.config == config
* this.config != null
* this.globalTrustedCAs == &new HashSet(CertificateManager#1)
* this.serverName == serverName
* init'ed(this.serverName)
* ...
*/
99 public CertificateManager(final String serverName, final ConfigManager config) {
100 this.serverName = serverName;
101 this.config = config;
102 this.cacertpass = config.getOption("ssl", "cacertpass");
103 this.checkDate = config.getOptionBool("ssl", "checkdate");
104 this.checkIssuer = config.getOptionBool("ssl", "checkissuer");
105 this.checkHost = config.getOptionBool("ssl", "checkhost");
106
107 loadTrustedCAs();
108 }
109
110 /**
111 * Loads the trusted CA certificates from the Java cacerts store.
112 */
113 protected void loadTrustedCAs() {
/*
P/P * Method: void loadTrustedCAs()
*
* Preconditions:
* (soft) this.cacertpass != null
* (soft) this.globalTrustedCAs != null
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.MEDIUM)
* init'ed(java.io.File.separatorChar)
* java.security.KeyStore:getInstance(...)@120 != null
* java.security.cert.PKIXParameters:getTrustAnchors(...)@124 != null
* java.util.Iterator:next(...)@124 != null
*/
114 FileInputStream is = null;
115
116 try {
117 final String filename = System.getProperty("java.home")
118 + "/lib/security/cacerts".replace('/', File.separatorChar);
119 is = new FileInputStream(filename);
120 final KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
121 keystore.load(is, cacertpass.toCharArray());
122
123 final PKIXParameters params = new PKIXParameters(keystore);
124 for (TrustAnchor anchor : params.getTrustAnchors()) {
125 globalTrustedCAs.add(anchor.getTrustedCert());
126 }
127 } catch (CertificateException ex) {
128 Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
129 } catch (IOException ex) {
130 Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
131 } catch (InvalidAlgorithmParameterException ex) {
132 Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
133 } catch (KeyStoreException ex) {
134 Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
135 } catch (NoSuchAlgorithmException ex) {
136 Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
137 } finally {
138 if (is != null) {
139 try {
140 is.close();
141 } catch (IOException ex) {
142 // ...
143 }
144 }
145 }
146 }
147
148 /**
149 * Retrieves a KeyManager[] for the client certicate specified in the
150 * configuration, if there is one.
151 *
152 * @return A KeyManager to use for the SSL connection
153 */
154 public KeyManager[] getKeyManager() {
/*
P/P * Method: KeyManager[] getKeyManager()
*
* Preconditions:
* this.config != null
*
* Presumptions:
* com.dmdirc.config.ConfigManager:getOption(...)@161 != null
* init'ed(com.dmdirc.logger.ErrorLevel.MEDIUM)
* java.security.KeyStore:getInstance(...)@167 != null
* javax.net.ssl.KeyManagerFactory:getInstance(...)@170 != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:hasOptionString(...)@155: {0}, {1}
* com.dmdirc.config.ConfigManager:hasOptionString(...)@160: {0}, {1}
*/
155 if (config.hasOptionString("ssl", "clientcert.file")) {
156 FileInputStream fis = null;
157 try {
158 final char[] pass;
159
160 if (config.hasOptionString("ssl", "clientcert.pass")) {
161 pass = config.getOption("ssl", "clientcert.pass").toCharArray();
162 } else {
163 pass = null;
164 }
165
166 fis = new FileInputStream(config.getOption("ssl", "clientcert.file"));
167 final KeyStore ks = KeyStore.getInstance("pkcs12");
168 ks.load(fis, pass);
169
170 final KeyManagerFactory kmf = KeyManagerFactory.getInstance(
171 KeyManagerFactory.getDefaultAlgorithm());
172 kmf.init(ks, pass);
173
174 return kmf.getKeyManagers();
175 } catch (KeyStoreException ex) {
176 Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
177 } catch (IOException ex) {
178 Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
179 } catch (CertificateException ex) {
180 Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
181 } catch (NoSuchAlgorithmException ex) {
182 Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
183 } catch (UnrecoverableKeyException ex) {
184 Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
185 } finally {
186 if (fis != null) {
187 try {
188 fis.close();
189 } catch (IOException ex) {
190 // ...
191 }
192 }
193 }
194 }
195
196 return null;
197 }
198
199 /** {@inheritDoc} */
200 @Override
201 public void checkClientTrusted(final X509Certificate[] chain, final String authType)
202 throws CertificateException {
/*
P/P * Method: void checkClientTrusted(X509Certificate[], String)
* checkClientTrusted fails for all possible inputs
*/
203 throw new CertificateException("Not supported.");
204 }
205
206 /**
207 * Determines if the specified certificate is trusted by the user.
208 *
209 * @param certificate The certificate to be checked
210 * @return True if the certificate matches one in the trusted certificate
211 * store, or if the certificate's details are marked as trusted in the
212 * DMDirc configuration file.
213 */
214 public boolean isTrusted(final X509Certificate certificate) {
215 try {
/*
P/P * Method: bool isTrusted(X509Certificate)
*
* Preconditions:
* (soft) certificate != null
* (soft) this.config != null
* (soft) this.globalTrustedCAs != null
*
* Presumptions:
* com.dmdirc.config.ConfigManager:getOptionList(...)@218 != null
* java.security.Principal:getName(...)@223 != null
* java.security.cert.X509Certificate:getIssuerDN(...)@223 != null
* java.util.Iterator:next(...)@222 != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:hasOptionString(...)@218: {0}, {1}
* java.lang.String:equals(...)@223: {0}, {1}
* java.util.Arrays:equals(...)@223: {0}, {1}
* java.util.Iterator:hasNext(...)@222: {0}, {1}
* java.util.List:contains(...)@218: {0}, {1}
*/
216 final String sig = Base64.encodeToString(certificate.getSignature(), false);
217
218 if (config.hasOptionString("ssl", "trusted") && config.getOptionList("ssl",
219 "trusted").contains(sig)) {
220 return true;
221 } else {
222 for (X509Certificate trustedCert : globalTrustedCAs) {
223 if (Arrays.equals(certificate.getSignature(), trustedCert.getSignature())
224 && certificate.getIssuerDN().getName()
225 .equals(trustedCert.getIssuerDN().getName())) {
226 certificate.verify(trustedCert.getPublicKey());
227 return true;
228 }
229 }
230 }
231 } catch (Exception ex) {
232 return false;
233 }
234
235 return false;
236 }
237
238 public boolean isValidHost(final X509Certificate certificate) {
/*
P/P * Method: bool isValidHost(X509Certificate)
*
* Preconditions:
* (soft) certificate != null
*
* Presumptions:
* java.security.cert.X509Certificate:getSubjectAlternativeNames(...)@246 != null
* java.util.Iterator:next(...)@246 != null
* java.util.List:get(...)@247 != null
* java.util.List:get(...)@250 != null
* java.util.Map:get(...)@240 != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* java.lang.Integer:intValue(...)@247: {-231..1, 3..6, 8..232-1}, {7}
* java.lang.Object:equals(...)@250: {0}, {1}
* java.lang.String:equals(...)@240: {0}, {1}
* java.security.cert.X509Certificate:getSubjectAlternativeNames(...)@245: Addr_Set{null}, Inverse{null}
* java.util.Iterator:hasNext(...)@246: {0}, {1}
* java.util.Map:containsKey(...)@240: {0}, {1}
*/
239 final Map<String, String> fields = getDNFieldsFromCert(certificate);
240 if (fields.containsKey("CN") && fields.get("CN").equals(serverName)) {
241 return true;
242 }
243
244 try {
245 if (certificate.getSubjectAlternativeNames() != null) {
246 for (List<?> entry : certificate.getSubjectAlternativeNames()) {
247 final int type = ((Integer) entry.get(0)).intValue();
248
249 // DNS or IP
250 if ((type == 2 || type == 7) && entry.get(1).equals(serverName)) {
251 return true;
252 }
253 }
254 }
255 } catch (CertificateParsingException ex) {
256 return false;
257 }
258
259 return false;
260 }
261
262 /** {@inheritDoc} */
263 @Override
264 public void checkServerTrusted(final X509Certificate[] chain, final String authType)
265 throws CertificateException {
/*
P/P * Method: void checkServerTrusted(X509Certificate[], String)
*
* Preconditions:
* chain != null
* init'ed(this.checkHost)
* (soft) chain.length in {1..232-1}
* (soft) chain[0] != null
* (soft) chain[...] != null
* (soft) com.dmdirc.CertificateManager$1__static_init.new int[](CertificateManager$1__static_init#1)[...] != 1
* (soft) com/dmdirc/Main.controller != null
* (soft) this.action != null
* (soft) this.actionSem != null
* (soft) init'ed(this.checkDate)
* ...
*
* Presumptions:
* com.dmdirc.config.IdentityManager:getConfigIdentity(...)@319 != null
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction:ordinal(...)@312 >= 0
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction:values(...).length >= 1
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction:ordinal(...)@312 < com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction:values(...).length
*
* Test Vectors:
* com.dmdirc.CertificateManager$1__static_init.new int[](CertificateManager$1__static_init#1)[...]: {2}, {-231..0, 3..232-1}
* this.checkDate: {0}, {1}
* this.checkHost: {0}, {1}
* this.checkIssuer: {0}, {1}
* java.util.List:isEmpty(...)@302: {1}, {0}
*/
266 final List<CertificateException> problems = new ArrayList<CertificateException>();
267 boolean verified = false;
268
269 if (checkHost) {
270 // Check that the cert is issued to the correct host
271 verified = isValidHost(chain[0]);
272
273 if (!verified) {
274 problems.add(new CertificateDoesntMatchHostException(
275 "Certificate was not issued to " + serverName));
276 }
277
278 verified = false;
279 }
280
281 for (X509Certificate cert : chain) {
282 if (checkDate) {
283 // Check that the certificate is in-date
284 try {
285 cert.checkValidity();
286 } catch (CertificateException ex) {
287 problems.add(ex);
288 }
289 }
290
291 if (checkIssuer) {
292 // Check that we trust an issuer
293
294 verified |= isTrusted(cert);
295 }
296 }
297
298 if (!verified && checkIssuer) {
299 problems.add(new CertificateNotTrustedException("Issuer is not trusted"));
300 }
301
302 if (!problems.isEmpty()) {
303 final SSLCertificateDialogModel test = new SSLCertificateDialogModel(chain, problems, this);
304 Main.getUI().showSSLCertificateDialog(test);
305
306 try {
307 actionSem.acquire();
308 } catch (InterruptedException ie) {
309 throw new CertificateException("Thread aborted, ");
310 }
311
/*
P/P * Method: com.dmdirc.CertificateManager$1__static_init
*
* Presumptions:
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction.DISCONNECT != null
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction.IGNORE_PERMANENTY != null
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction.IGNORE_TEMPORARILY != null
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction:ordinal(...)@312 >= 0
* com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction:ordinal(...)@312 < com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction:values(...).length@312
* ...
*
* Postconditions:
* new int[](CertificateManager$1__static_init#1) num objects == 1
*/
312 switch (action) {
313 case DISCONNECT:
314 throw new CertificateException("Not trusted");
315 case IGNORE_PERMANENTY:
316 final List<String> list = new ArrayList<String>(config
317 .getOptionList("ssl", "trusted"));
318 list.add(Base64.encodeToString(chain[0].getSignature(), false));
319 IdentityManager.getConfigIdentity().setOption("ssl",
320 "trusted", list);
321 break;
322 case IGNORE_TEMPORARILY:
323 // Do nothing, continue connecting
324 break;
325 }
326 }
327 }
328
329 /**
330 * Sets the action to perform for the request that's in progress.
331 *
332 * @param action The action that's been selected
333 */
334 public void setAction(final CertificateAction action) {
/*
P/P * Method: void setAction(CertificateAction)
*
* Preconditions:
* this.actionSem != null
*
* Postconditions:
* this.action == action
* init'ed(this.action)
*/
335 this.action = action;
336
337 actionSem.release();
338 }
339
340 /**
341 * Retrieves the name of the server to which the user is trying to connect.
342 *
343 * @return The name of the server that the user is trying to connect to
344 */
345 public String getServerName() {
/*
P/P * Method: String getServerName()
*
* Postconditions:
* return_value == this.serverName
* init'ed(return_value)
*/
346 return serverName;
347 }
348
349 /**
350 * Reads the fields from the subject's designated name in the specified
351 * certificate.
352 *
353 * @param cert The certificate to read
354 * @return A map of the fields in the certificate's subject's designated
355 * name
356 */
357 public static Map<String, String> getDNFieldsFromCert(final X509Certificate cert) {
/*
P/P * Method: Map getDNFieldsFromCert(X509Certificate)
*
* Preconditions:
* (soft) cert != null
*
* Presumptions:
* java.security.cert.X509Certificate:getSubjectX500Principal(...)@361 != null
* java.util.Iterator:next(...)@362 != null
* javax.naming.ldap.LdapName:getRdns(...)@362 != null
* javax.naming.ldap.Rdn:getValue(...)@363 != null
*
* Postconditions:
* return_value == &new HashMap(getDNFieldsFromCert#1)
* new HashMap(getDNFieldsFromCert#1) num objects == 1
*/
358 final Map<String, String> res = new HashMap<String, String>();
359
360 try {
361 final LdapName name = new LdapName(cert.getSubjectX500Principal().getName());
362 for (Rdn rdn : name.getRdns()) {
363 res.put(rdn.getType(), rdn.getValue().toString());
364 }
365 } catch (InvalidNameException ex) {
366 // Don't care
367 }
368
369 return res;
370 }
371
372 /** {@inheritDoc} */
373 @Override
374 public X509Certificate[] getAcceptedIssuers() {
/*
P/P * Method: X509Certificate[] getAcceptedIssuers()
*
* Preconditions:
* this.globalTrustedCAs != null
*
* Presumptions:
* java.util.Set:size(...)@375 >= 0
*
* Postconditions:
* init'ed(return_value)
*/
375 return globalTrustedCAs.toArray(new X509Certificate[globalTrustedCAs.size()]);
376 }
377
378 /**
379 * An exception to indicate that the host on a certificate doesn't match
380 * the host we're trying to connect to.
381 */
382 public static class CertificateDoesntMatchHostException extends CertificateException {
383
384 /**
385 * A version number for this class. It should be changed whenever the
386 * class structure is changed (or anything else that would prevent
387 * serialized objects being unserialized with the new class).
388 */
389 private static final long serialVersionUID = 1;
390
391 /**
392 * Creates a new CertificateDoesntMatchHostException
393 *
394 * @param msg A description of the problem
395 */
396 public CertificateDoesntMatchHostException(String msg) {
/*
P/P * Method: void com.dmdirc.CertificateManager$CertificateDoesntMatchHostException(String)
*/
397 super(msg);
398 }
399
400 }
401
402 /**
403 * An exception to indicate that we do not trust the issuer of the
404 * certificate (or the CA).
405 */
406 public static class CertificateNotTrustedException extends CertificateException {
407
408 /**
409 * A version number for this class. It should be changed whenever the
410 * class structure is changed (or anything else that would prevent
411 * serialized objects being unserialized with the new class).
412 */
413 private static final long serialVersionUID = 1;
414
415 /**
416 * Creates a new CertificateNotTrustedException
417 *
418 * @param msg A description of the problem
419 */
420 public CertificateNotTrustedException(String msg) {
/*
P/P * Method: void com.dmdirc.CertificateManager$CertificateNotTrustedException(String)
*/
421 super(msg);
422 }
423
424 }
425
426 }
SofCheck Inspector Build Version : 2.17854
| CertificateManager.java |
2009-Jun-25 01:54:24 |
| CertificateManager.class |
2009-Sep-02 17:04:11 |
| CertificateManager$1.class |
2009-Sep-02 17:04:11 |
| CertificateManager$CertificateDoesntMatchHostException.class |
2009-Sep-02 17:04:11 |
| CertificateManager$CertificateNotTrustedException.class |
2009-Sep-02 17:04:11 |