Browse Source

Merge branch 'ssl-support'

tags/0.6.3m1rc1
Chris Smith 15 years ago
parent
commit
635364bada

+ 99
- 50
src/com/dmdirc/CertificateManager.java View File

@@ -23,6 +23,9 @@
23 23
 package com.dmdirc;
24 24
 
25 25
 import com.dmdirc.config.ConfigManager;
26
+import com.dmdirc.config.IdentityManager;
27
+import com.dmdirc.logger.ErrorLevel;
28
+import com.dmdirc.logger.Logger;
26 29
 import com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction;
27 30
 import com.dmdirc.ui.core.dialogs.sslcertificate.SSLCertificateDialogModel;
28 31
 
@@ -35,10 +38,12 @@ import java.security.KeyStoreException;
35 38
 import java.security.NoSuchAlgorithmException;
36 39
 import java.security.UnrecoverableKeyException;
37 40
 import java.security.cert.CertificateException;
41
+import java.security.cert.CertificateParsingException;
38 42
 import java.security.cert.PKIXParameters;
39 43
 import java.security.cert.TrustAnchor;
40 44
 import java.security.cert.X509Certificate;
41 45
 import java.util.ArrayList;
46
+import java.util.Arrays;
42 47
 import java.util.HashMap;
43 48
 import java.util.HashSet;
44 49
 import java.util.List;
@@ -52,6 +57,7 @@ import javax.naming.ldap.Rdn;
52 57
 import javax.net.ssl.KeyManager;
53 58
 import javax.net.ssl.KeyManagerFactory;
54 59
 import javax.net.ssl.X509TrustManager;
60
+import net.miginfocom.Base64;
55 61
 
56 62
 /**
57 63
  * Manages storage and validation of certificates used when connecting to
@@ -106,27 +112,26 @@ public class CertificateManager implements X509TrustManager {
106 112
      */
107 113
     protected void loadTrustedCAs() {
108 114
         try {
109
-            String filename = System.getProperty("java.home")
115
+            final String filename = System.getProperty("java.home")
110 116
                 + "/lib/security/cacerts".replace('/', File.separatorChar);
111
-            FileInputStream is = new FileInputStream(filename);
112
-            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
117
+            final FileInputStream is = new FileInputStream(filename);
118
+            final KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
113 119
             keystore.load(is, cacertpass.toCharArray());
114 120
 
115
-            // This class retrieves the most-trusted CAs from the keystore
116
-            PKIXParameters params = new PKIXParameters(keystore);
121
+            final PKIXParameters params = new PKIXParameters(keystore);
117 122
             for (TrustAnchor anchor : params.getTrustAnchors()) {
118 123
                 globalTrustedCAs.add(anchor.getTrustedCert());
119 124
             }
120 125
         } catch (CertificateException ex) {
121
-
126
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
122 127
         } catch (IOException ex) {
123
-
128
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
124 129
         } catch (InvalidAlgorithmParameterException ex) {
125
-
130
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
126 131
         } catch (KeyStoreException ex) {
127
-
132
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
128 133
         } catch (NoSuchAlgorithmException ex) {
129
-
134
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
130 135
         }
131 136
     }
132 137
 
@@ -152,20 +157,21 @@ public class CertificateManager implements X509TrustManager {
152 157
                 final KeyStore ks = KeyStore.getInstance("pkcs12");
153 158
                 ks.load(fis, pass);
154 159
 
155
-                final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
160
+                final KeyManagerFactory kmf = KeyManagerFactory.getInstance(
161
+                        KeyManagerFactory.getDefaultAlgorithm());
156 162
                 kmf.init(ks, pass);
157 163
 
158 164
                 return kmf.getKeyManagers();
159 165
             } catch (KeyStoreException ex) {
160
-                ex.printStackTrace();
166
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
161 167
             } catch (IOException ex) {
162
-                ex.printStackTrace();
168
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
163 169
             } catch (CertificateException ex) {
164
-                ex.printStackTrace();
170
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
165 171
             } catch (NoSuchAlgorithmException ex) {
166
-                ex.printStackTrace();
172
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
167 173
             } catch (UnrecoverableKeyException ex) {
168
-                ex.printStackTrace();
174
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
169 175
             } finally {
170 176
                 if (fis != null) {
171 177
                     try {
@@ -187,6 +193,62 @@ public class CertificateManager implements X509TrustManager {
187 193
         throw new CertificateException("Not supported.");
188 194
     }
189 195
 
196
+    /**
197
+     * Determines if the specified certificate is trusted by the user.
198
+     *
199
+     * @param certificate The certificate to be checked
200
+     * @return True if the certificate matches one in the trusted certificate
201
+     * store, or if the certificate's details are marked as trusted in the
202
+     * DMDirc configuration file.
203
+     */
204
+    public boolean isTrusted(final X509Certificate certificate) {
205
+        try {
206
+            final String sig = Base64.encodeToString(certificate.getSignature(), false);
207
+
208
+            if (config.hasOption("ssl", "trusted") && config.getOptionList("ssl",
209
+                    "trusted").contains(sig)) {
210
+                return true;
211
+            } else {
212
+                for (X509Certificate trustedCert : globalTrustedCAs) {
213
+                    if (Arrays.equals(certificate.getSignature(), trustedCert.getSignature())
214
+                            && certificate.getIssuerDN().getName()
215
+                            .equals(trustedCert.getIssuerDN().getName())) {
216
+                        certificate.verify(trustedCert.getPublicKey());
217
+                        return true;
218
+                    }
219
+                }
220
+            }
221
+        } catch (Exception ex) {
222
+           return false;
223
+        }
224
+
225
+        return false;
226
+    }
227
+
228
+    public boolean isValidHost(final X509Certificate certificate) {
229
+        final Map<String, String> fields = getDNFieldsFromCert(certificate);
230
+        if (fields.containsKey("CN") && fields.get("CN").equals(serverName)) {
231
+            return true;
232
+        }
233
+
234
+        try {
235
+            if (certificate.getSubjectAlternativeNames() != null) {
236
+                for (List<?> entry : certificate.getSubjectAlternativeNames()) {
237
+                    final int type = ((Integer) entry.get(0)).intValue();
238
+
239
+                    // DNS or IP
240
+                    if ((type == 2 || type == 7) && entry.get(1).equals(serverName)) {
241
+                        return true;
242
+                    }
243
+                }
244
+            }
245
+        } catch (CertificateParsingException ex) {
246
+            return false;
247
+        }
248
+
249
+        return false;
250
+    }
251
+
190 252
     /** {@inheritDoc} */
191 253
     @Override
192 254
     public void checkServerTrusted(final X509Certificate[] chain, final String authType)
@@ -196,22 +258,7 @@ public class CertificateManager implements X509TrustManager {
196 258
 
197 259
         if (checkHost) {
198 260
             // Check that the cert is issued to the correct host
199
-            final Map<String, String> fields = getDNFieldsFromCert(chain[0]);
200
-            if (fields.containsKey("CN") && fields.get("CN").equals(serverName)) {
201
-                verified = true;
202
-            }
203
-            
204
-            if (chain[0].getSubjectAlternativeNames() != null && !verified) {
205
-                for (List<?> entry : chain[0].getSubjectAlternativeNames()) {
206
-                    final int type = ((Integer) entry.get(0)).intValue();
207
-
208
-                    // DNS or IP
209
-                    if ((type == 2 || type == 7) && entry.get(1).equals(serverName)) {
210
-                        verified = true;
211
-                        break;
212
-                    }
213
-                }
214
-            }
261
+            verified = isValidHost(chain[0]);
215 262
 
216 263
             if (!verified) {
217 264
                 problems.add(new CertificateDoesntMatchHostException(
@@ -233,18 +280,8 @@ public class CertificateManager implements X509TrustManager {
233 280
 
234 281
             if (checkIssuer) {
235 282
                 // Check that we trust an issuer
236
-                try {
237
-                    for (X509Certificate trustedCert : globalTrustedCAs) {
238
-                        if (cert.getSerialNumber().equals(trustedCert.getSerialNumber())
239
-                                && cert.getIssuerDN().getName().equals(trustedCert.getIssuerDN().getName())) {
240
-                            cert.verify(trustedCert.getPublicKey());
241
-                            verified = true;
242
-                            break;
243
-                        }
244
-                    }
245
-                } catch (Exception ex) {
246
-                   problems.add(new CertificateException("Issuer couldn't be verified", ex));
247
-                }
283
+
284
+                verified |= isTrusted(cert);
248 285
             }
249 286
         }
250 287
 
@@ -256,19 +293,22 @@ public class CertificateManager implements X509TrustManager {
256 293
             Main.getUI().showSSLCertificateDialog(
257 294
                     new SSLCertificateDialogModel(chain, problems, this));
258 295
 
259
-            /*actionSem.acquireUninterruptibly();
296
+            actionSem.acquireUninterruptibly();
260 297
             
261 298
             switch (action) {
262 299
                 case DISCONNECT:
263
-                    // TODO: implement
264
-                    break;
300
+                    throw new CertificateException("Not trusted");
265 301
                 case IGNORE_PERMANENTY:
266
-                    // TODO: implement
302
+                    final List<String> list = new ArrayList<String>(config
303
+                            .getOptionList("ssl", "trusted"));
304
+                    list.add(Base64.encodeToString(chain[0].getSignature(), false));
305
+                    IdentityManager.getConfigIdentity().setOption("ssl",
306
+                            "trusted", list);
267 307
                     break;
268 308
                 case IGNORE_TEMPORARILY:
269
-                    // TODO: implement
309
+                    // Do nothing, continue connecting
270 310
                     break;
271
-            }*/
311
+            }
272 312
         }
273 313
     }
274 314
 
@@ -283,6 +323,15 @@ public class CertificateManager implements X509TrustManager {
283 323
         actionSem.release();
284 324
     }
285 325
 
326
+    /**
327
+     * Retrieves the name of the server to which the user is trying to connect.
328
+     *
329
+     * @return The name of the server that the user is trying to connect to
330
+     */
331
+    public String getServerName() {
332
+        return serverName;
333
+    }
334
+
286 335
     /**
287 336
      * Reads the fields from the subject's designated name in the specified
288 337
      * certificate.

+ 2
- 2
src/com/dmdirc/addons/ui_swing/dialogs/sslcertificate/SSLCertificateDialog.java View File

@@ -112,9 +112,9 @@ public class SSLCertificateDialog extends StandardDialog implements ActionListen
112 112
         actions.setVisible(model.needsResponse());
113 113
         if (model.needsResponse()) {
114 114
             blurb.setText("Theres is a problem with the certificate used by " +
115
-                    chain.getName(0));
115
+                    model.getServerName());
116 116
         } else {
117
-            blurb.setText("Your connection to " + chain.getName(0) +
117
+            blurb.setText("Your connection to " + model.getServerName() +
118 118
                     " is encrypted using SSL.");
119 119
         }
120 120
     }

+ 75
- 15
src/com/dmdirc/ui/core/dialogs/sslcertificate/SSLCertificateDialogModel.java View File

@@ -25,9 +25,11 @@ package com.dmdirc.ui.core.dialogs.sslcertificate;
25 25
 import com.dmdirc.CertificateManager;
26 26
 import com.dmdirc.CertificateManager.CertificateDoesntMatchHostException;
27 27
 import com.dmdirc.CertificateManager.CertificateNotTrustedException;
28
+
28 29
 import java.security.cert.CertificateException;
29 30
 import java.security.cert.CertificateExpiredException;
30 31
 import java.security.cert.CertificateNotYetValidException;
32
+import java.security.cert.CertificateParsingException;
31 33
 import java.security.cert.X509Certificate;
32 34
 import java.util.ArrayList;
33 35
 import java.util.List;
@@ -77,17 +79,21 @@ public class SSLCertificateDialogModel {
77 79
     public List<CertificateChainEntry> getCertificateChain() {
78 80
         final List<CertificateChainEntry> res = new ArrayList<CertificateChainEntry>();
79 81
 
82
+        boolean first = true;
83
+
80 84
         for (X509Certificate cert : chain) {
81
-            boolean invalid = false;
85
+            boolean invalid = first && !manager.isValidHost(cert);
86
+            first = false;
82 87
 
83 88
             try {
84 89
                 cert.checkValidity();
85 90
             } catch (CertificateException ex) {
86
-                invalid = true;
91
+                invalid |= true;
87 92
             }
88 93
 
89
-            res.add(new CertificateChainEntry(CertificateManager.getDNFieldsFromCert(cert).get("CN"),
90
-                    false, invalid)); // TODO: false hardcoded, name?
94
+            res.add(new CertificateChainEntry(CertificateManager
95
+                    .getDNFieldsFromCert(cert).get("CN"),
96
+                    manager.isTrusted(cert), invalid));
91 97
         }
92 98
 
93 99
         return res;
@@ -106,21 +112,38 @@ public class SSLCertificateDialogModel {
106 112
         final X509Certificate cert = chain[index];
107 113
         List<CertificateInformationEntry> group;
108 114
 
115
+        boolean tooOld = false, tooNew = false;
116
+
117
+        try {
118
+            cert.checkValidity();
119
+        } catch (CertificateExpiredException ex) {
120
+            tooOld = true;
121
+        } catch (CertificateNotYetValidException ex) {
122
+            tooNew = true;
123
+        }
124
+
109 125
         group = new ArrayList<CertificateInformationEntry>();
110 126
         group.add(new CertificateInformationEntry("Valid from",
111
-                cert.getNotBefore().toString(), false, false)); // TODO: false!
127
+                cert.getNotBefore().toString(), tooNew, false));
112 128
         group.add(new CertificateInformationEntry("Valid to",
113
-                cert.getNotAfter().toString(), false, false)); // TODO: false!
129
+                cert.getNotAfter().toString(), tooOld, false));
114 130
         res.add(group);
115 131
 
132
+        final boolean wrongName = index == 0 && !manager.isValidHost(cert);
133
+        final String names = getAlternateNames(cert);
116 134
         final Map<String, String> fields = CertificateManager.getDNFieldsFromCert(cert);
135
+
117 136
         group = new ArrayList<CertificateInformationEntry>();
118
-        addCertField(fields, group, "Common name", "CN");
119
-        addCertField(fields, group, "Organisation", "O");
120
-        addCertField(fields, group, "Unit", "OU");
121
-        addCertField(fields, group, "Locality", "L");
122
-        addCertField(fields, group, "State", "ST");
123
-        addCertField(fields, group, "Country", "C");
137
+        addCertField(fields, group, "Common name", "CN", wrongName);
138
+
139
+        group.add(new CertificateInformationEntry("Alternate names", 
140
+                names == null ? NOTPRESENT : names, wrongName, names == null));
141
+
142
+        addCertField(fields, group, "Organisation", "O", false);
143
+        addCertField(fields, group, "Unit", "OU", false);
144
+        addCertField(fields, group, "Locality", "L", false);
145
+        addCertField(fields, group, "State", "ST", false);
146
+        addCertField(fields, group, "Country", "C", false);
124 147
         res.add(group);
125 148
 
126 149
         group = new ArrayList<CertificateInformationEntry>();
@@ -135,6 +158,33 @@ public class SSLCertificateDialogModel {
135 158
         return res;
136 159
     }
137 160
 
161
+    protected String getAlternateNames(final X509Certificate cert) {
162
+        final StringBuilder res = new StringBuilder();
163
+
164
+        try {
165
+            if (cert.getSubjectAlternativeNames() == null) {
166
+                return null;
167
+            }
168
+
169
+            for (List<?> entry : cert.getSubjectAlternativeNames()) {
170
+                final int type = ((Integer) entry.get(0)).intValue();
171
+
172
+                // DNS or IP
173
+                if (type == 2 || type == 7) {
174
+                    if (res.length() > 0) {
175
+                        res.append(", ");
176
+                    }
177
+
178
+                    res.append(entry.get(1));
179
+                }
180
+            }
181
+        } catch (CertificateParsingException ex) {
182
+            // Do nothing
183
+        }
184
+
185
+        return res.toString();
186
+    }
187
+
138 188
     /**
139 189
      * Adds a field to the specified group.
140 190
      *
@@ -142,12 +192,13 @@ public class SSLCertificateDialogModel {
142 192
      * @param group The group to add an entry to
143 193
      * @param title The user-friendly title of the field
144 194
      * @param field The name of the field to look for
195
+     * @param invalid Whether or not the field is a cause for concern
145 196
      */
146 197
     protected void addCertField(final Map<String, String> fields,
147 198
             final List<CertificateInformationEntry> group, final String title,
148
-            final String field) {
199
+            final String field, final boolean invalid) {
149 200
         group.add(new CertificateInformationEntry(title,
150
-                fields.containsKey(field) ? fields.get(field) : NOTPRESENT, false,
201
+                fields.containsKey(field) ? fields.get(field) : NOTPRESENT, invalid,
151 202
                 !fields.containsKey(field)));
152 203
     }
153 204
 
@@ -194,7 +245,7 @@ public class SSLCertificateDialogModel {
194 245
                     + "to the host you are connecting to", false));
195 246
         } else {
196 247
             res.add(new CertificateSummaryEntry("The certificate is issued "
197
-                    + "to the host you are connecting to", false));
248
+                    + "to the host you are connecting to", true));
198 249
         }
199 250
 
200 251
         return res;
@@ -210,6 +261,15 @@ public class SSLCertificateDialogModel {
210 261
         return !problems.isEmpty();
211 262
     }
212 263
 
264
+    /**
265
+     * Retrieves the name of the server to which the user is trying to connect.
266
+     *
267
+     * @return The name of the server that the user is trying to connect to
268
+     */
269
+    public String getServerName() {
270
+        return manager.getServerName();
271
+    }
272
+
213 273
     /**
214 274
      * Performs the specified action on the certificate chain/connection.
215 275
      * Should only be called once per instance, and only if

Loading…
Cancel
Save