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
 package com.dmdirc;
23
 package com.dmdirc;
24
 
24
 
25
 import com.dmdirc.config.ConfigManager;
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
 import com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction;
29
 import com.dmdirc.ui.core.dialogs.sslcertificate.CertificateAction;
27
 import com.dmdirc.ui.core.dialogs.sslcertificate.SSLCertificateDialogModel;
30
 import com.dmdirc.ui.core.dialogs.sslcertificate.SSLCertificateDialogModel;
28
 
31
 
35
 import java.security.NoSuchAlgorithmException;
38
 import java.security.NoSuchAlgorithmException;
36
 import java.security.UnrecoverableKeyException;
39
 import java.security.UnrecoverableKeyException;
37
 import java.security.cert.CertificateException;
40
 import java.security.cert.CertificateException;
41
+import java.security.cert.CertificateParsingException;
38
 import java.security.cert.PKIXParameters;
42
 import java.security.cert.PKIXParameters;
39
 import java.security.cert.TrustAnchor;
43
 import java.security.cert.TrustAnchor;
40
 import java.security.cert.X509Certificate;
44
 import java.security.cert.X509Certificate;
41
 import java.util.ArrayList;
45
 import java.util.ArrayList;
46
+import java.util.Arrays;
42
 import java.util.HashMap;
47
 import java.util.HashMap;
43
 import java.util.HashSet;
48
 import java.util.HashSet;
44
 import java.util.List;
49
 import java.util.List;
52
 import javax.net.ssl.KeyManager;
57
 import javax.net.ssl.KeyManager;
53
 import javax.net.ssl.KeyManagerFactory;
58
 import javax.net.ssl.KeyManagerFactory;
54
 import javax.net.ssl.X509TrustManager;
59
 import javax.net.ssl.X509TrustManager;
60
+import net.miginfocom.Base64;
55
 
61
 
56
 /**
62
 /**
57
  * Manages storage and validation of certificates used when connecting to
63
  * Manages storage and validation of certificates used when connecting to
106
      */
112
      */
107
     protected void loadTrustedCAs() {
113
     protected void loadTrustedCAs() {
108
         try {
114
         try {
109
-            String filename = System.getProperty("java.home")
115
+            final String filename = System.getProperty("java.home")
110
                 + "/lib/security/cacerts".replace('/', File.separatorChar);
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
             keystore.load(is, cacertpass.toCharArray());
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
             for (TrustAnchor anchor : params.getTrustAnchors()) {
122
             for (TrustAnchor anchor : params.getTrustAnchors()) {
118
                 globalTrustedCAs.add(anchor.getTrustedCert());
123
                 globalTrustedCAs.add(anchor.getTrustedCert());
119
             }
124
             }
120
         } catch (CertificateException ex) {
125
         } catch (CertificateException ex) {
121
-
126
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
122
         } catch (IOException ex) {
127
         } catch (IOException ex) {
123
-
128
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
124
         } catch (InvalidAlgorithmParameterException ex) {
129
         } catch (InvalidAlgorithmParameterException ex) {
125
-
130
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
126
         } catch (KeyStoreException ex) {
131
         } catch (KeyStoreException ex) {
127
-
132
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
128
         } catch (NoSuchAlgorithmException ex) {
133
         } catch (NoSuchAlgorithmException ex) {
129
-
134
+            Logger.appError(ErrorLevel.MEDIUM, "Unable to load trusted certificates", ex);
130
         }
135
         }
131
     }
136
     }
132
 
137
 
152
                 final KeyStore ks = KeyStore.getInstance("pkcs12");
157
                 final KeyStore ks = KeyStore.getInstance("pkcs12");
153
                 ks.load(fis, pass);
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
                 kmf.init(ks, pass);
162
                 kmf.init(ks, pass);
157
 
163
 
158
                 return kmf.getKeyManagers();
164
                 return kmf.getKeyManagers();
159
             } catch (KeyStoreException ex) {
165
             } catch (KeyStoreException ex) {
160
-                ex.printStackTrace();
166
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
161
             } catch (IOException ex) {
167
             } catch (IOException ex) {
162
-                ex.printStackTrace();
168
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
163
             } catch (CertificateException ex) {
169
             } catch (CertificateException ex) {
164
-                ex.printStackTrace();
170
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
165
             } catch (NoSuchAlgorithmException ex) {
171
             } catch (NoSuchAlgorithmException ex) {
166
-                ex.printStackTrace();
172
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
167
             } catch (UnrecoverableKeyException ex) {
173
             } catch (UnrecoverableKeyException ex) {
168
-                ex.printStackTrace();
174
+                Logger.appError(ErrorLevel.MEDIUM, "Unable to get key manager", ex);
169
             } finally {
175
             } finally {
170
                 if (fis != null) {
176
                 if (fis != null) {
171
                     try {
177
                     try {
187
         throw new CertificateException("Not supported.");
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
     /** {@inheritDoc} */
252
     /** {@inheritDoc} */
191
     @Override
253
     @Override
192
     public void checkServerTrusted(final X509Certificate[] chain, final String authType)
254
     public void checkServerTrusted(final X509Certificate[] chain, final String authType)
196
 
258
 
197
         if (checkHost) {
259
         if (checkHost) {
198
             // Check that the cert is issued to the correct host
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
             if (!verified) {
263
             if (!verified) {
217
                 problems.add(new CertificateDoesntMatchHostException(
264
                 problems.add(new CertificateDoesntMatchHostException(
233
 
280
 
234
             if (checkIssuer) {
281
             if (checkIssuer) {
235
                 // Check that we trust an issuer
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
             Main.getUI().showSSLCertificateDialog(
293
             Main.getUI().showSSLCertificateDialog(
257
                     new SSLCertificateDialogModel(chain, problems, this));
294
                     new SSLCertificateDialogModel(chain, problems, this));
258
 
295
 
259
-            /*actionSem.acquireUninterruptibly();
296
+            actionSem.acquireUninterruptibly();
260
             
297
             
261
             switch (action) {
298
             switch (action) {
262
                 case DISCONNECT:
299
                 case DISCONNECT:
263
-                    // TODO: implement
264
-                    break;
300
+                    throw new CertificateException("Not trusted");
265
                 case IGNORE_PERMANENTY:
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
                     break;
307
                     break;
268
                 case IGNORE_TEMPORARILY:
308
                 case IGNORE_TEMPORARILY:
269
-                    // TODO: implement
309
+                    // Do nothing, continue connecting
270
                     break;
310
                     break;
271
-            }*/
311
+            }
272
         }
312
         }
273
     }
313
     }
274
 
314
 
283
         actionSem.release();
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
      * Reads the fields from the subject's designated name in the specified
336
      * Reads the fields from the subject's designated name in the specified
288
      * certificate.
337
      * certificate.

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

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

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

25
 import com.dmdirc.CertificateManager;
25
 import com.dmdirc.CertificateManager;
26
 import com.dmdirc.CertificateManager.CertificateDoesntMatchHostException;
26
 import com.dmdirc.CertificateManager.CertificateDoesntMatchHostException;
27
 import com.dmdirc.CertificateManager.CertificateNotTrustedException;
27
 import com.dmdirc.CertificateManager.CertificateNotTrustedException;
28
+
28
 import java.security.cert.CertificateException;
29
 import java.security.cert.CertificateException;
29
 import java.security.cert.CertificateExpiredException;
30
 import java.security.cert.CertificateExpiredException;
30
 import java.security.cert.CertificateNotYetValidException;
31
 import java.security.cert.CertificateNotYetValidException;
32
+import java.security.cert.CertificateParsingException;
31
 import java.security.cert.X509Certificate;
33
 import java.security.cert.X509Certificate;
32
 import java.util.ArrayList;
34
 import java.util.ArrayList;
33
 import java.util.List;
35
 import java.util.List;
77
     public List<CertificateChainEntry> getCertificateChain() {
79
     public List<CertificateChainEntry> getCertificateChain() {
78
         final List<CertificateChainEntry> res = new ArrayList<CertificateChainEntry>();
80
         final List<CertificateChainEntry> res = new ArrayList<CertificateChainEntry>();
79
 
81
 
82
+        boolean first = true;
83
+
80
         for (X509Certificate cert : chain) {
84
         for (X509Certificate cert : chain) {
81
-            boolean invalid = false;
85
+            boolean invalid = first && !manager.isValidHost(cert);
86
+            first = false;
82
 
87
 
83
             try {
88
             try {
84
                 cert.checkValidity();
89
                 cert.checkValidity();
85
             } catch (CertificateException ex) {
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
         return res;
99
         return res;
106
         final X509Certificate cert = chain[index];
112
         final X509Certificate cert = chain[index];
107
         List<CertificateInformationEntry> group;
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
         group = new ArrayList<CertificateInformationEntry>();
125
         group = new ArrayList<CertificateInformationEntry>();
110
         group.add(new CertificateInformationEntry("Valid from",
126
         group.add(new CertificateInformationEntry("Valid from",
111
-                cert.getNotBefore().toString(), false, false)); // TODO: false!
127
+                cert.getNotBefore().toString(), tooNew, false));
112
         group.add(new CertificateInformationEntry("Valid to",
128
         group.add(new CertificateInformationEntry("Valid to",
113
-                cert.getNotAfter().toString(), false, false)); // TODO: false!
129
+                cert.getNotAfter().toString(), tooOld, false));
114
         res.add(group);
130
         res.add(group);
115
 
131
 
132
+        final boolean wrongName = index == 0 && !manager.isValidHost(cert);
133
+        final String names = getAlternateNames(cert);
116
         final Map<String, String> fields = CertificateManager.getDNFieldsFromCert(cert);
134
         final Map<String, String> fields = CertificateManager.getDNFieldsFromCert(cert);
135
+
117
         group = new ArrayList<CertificateInformationEntry>();
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
         res.add(group);
147
         res.add(group);
125
 
148
 
126
         group = new ArrayList<CertificateInformationEntry>();
149
         group = new ArrayList<CertificateInformationEntry>();
135
         return res;
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
      * Adds a field to the specified group.
189
      * Adds a field to the specified group.
140
      *
190
      *
142
      * @param group The group to add an entry to
192
      * @param group The group to add an entry to
143
      * @param title The user-friendly title of the field
193
      * @param title The user-friendly title of the field
144
      * @param field The name of the field to look for
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
     protected void addCertField(final Map<String, String> fields,
197
     protected void addCertField(final Map<String, String> fields,
147
             final List<CertificateInformationEntry> group, final String title,
198
             final List<CertificateInformationEntry> group, final String title,
148
-            final String field) {
199
+            final String field, final boolean invalid) {
149
         group.add(new CertificateInformationEntry(title,
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
                 !fields.containsKey(field)));
202
                 !fields.containsKey(field)));
152
     }
203
     }
153
 
204
 
194
                     + "to the host you are connecting to", false));
245
                     + "to the host you are connecting to", false));
195
         } else {
246
         } else {
196
             res.add(new CertificateSummaryEntry("The certificate is issued "
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
         return res;
251
         return res;
210
         return !problems.isEmpty();
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
      * Performs the specified action on the certificate chain/connection.
274
      * Performs the specified action on the certificate chain/connection.
215
      * Should only be called once per instance, and only if
275
      * Should only be called once per instance, and only if

Loading…
Cancel
Save