Browse Source

Merge pull request #809 from csmith/certs

Use HostChecker in CertificateManager.
pull/811/head
Greg Holmes 7 years ago
parent
commit
20b0ef1551

+ 10
- 82
src/main/java/com/dmdirc/tls/CertificateManager.java View File

@@ -17,13 +17,12 @@
17 17
 
18 18
 package com.dmdirc.tls;
19 19
 
20
+import com.dmdirc.config.provider.AggregateConfigProvider;
21
+import com.dmdirc.config.provider.ConfigProvider;
20 22
 import com.dmdirc.events.ServerCertificateProblemEncounteredEvent;
21 23
 import com.dmdirc.events.ServerCertificateProblemResolvedEvent;
22
-import com.dmdirc.interfaces.Connection;
23 24
 import com.dmdirc.events.eventbus.EventBus;
24
-import com.dmdirc.config.provider.AggregateConfigProvider;
25
-import com.dmdirc.config.provider.ConfigProvider;
26
-
25
+import com.dmdirc.interfaces.Connection;
27 26
 import java.io.FileInputStream;
28 27
 import java.io.FileNotFoundException;
29 28
 import java.io.IOException;
@@ -32,7 +31,6 @@ import java.security.InvalidAlgorithmParameterException;
32 31
 import java.security.KeyStore;
33 32
 import java.security.KeyStoreException;
34 33
 import java.security.cert.CertificateException;
35
-import java.security.cert.CertificateParsingException;
36 34
 import java.security.cert.PKIXParameters;
37 35
 import java.security.cert.TrustAnchor;
38 36
 import java.security.cert.X509Certificate;
@@ -47,14 +45,12 @@ import java.util.Map;
47 45
 import java.util.Set;
48 46
 import java.util.concurrent.Semaphore;
49 47
 import java.util.stream.Collectors;
50
-
51 48
 import javax.naming.InvalidNameException;
52 49
 import javax.naming.ldap.LdapName;
53 50
 import javax.naming.ldap.Rdn;
54 51
 import javax.net.ssl.KeyManager;
55 52
 import javax.net.ssl.KeyManagerFactory;
56 53
 import javax.net.ssl.X509TrustManager;
57
-
58 54
 import org.slf4j.Logger;
59 55
 import org.slf4j.LoggerFactory;
60 56
 
@@ -90,6 +86,8 @@ public class CertificateManager implements X509TrustManager {
90 86
     private final ConfigProvider userSettings;
91 87
     /** Locator to use to find a system keystore. */
92 88
     private final KeyStoreLocator keyStoreLocator;
89
+    /** Checker to use for hostnames. */
90
+    private final CertificateHostChecker hostChecker;
93 91
 
94 92
     /**
95 93
      * Creates a new certificate manager for a client connecting to the specified server.
@@ -111,6 +109,7 @@ public class CertificateManager implements X509TrustManager {
111 109
         this.userSettings = userSettings;
112 110
         this.eventBus = eventBus;
113 111
         this.keyStoreLocator = new KeyStoreLocator();
112
+        this.hostChecker = new CertificateHostChecker();
114 113
 
115 114
         loadTrustedCAs();
116 115
     }
@@ -206,74 +205,16 @@ public class CertificateManager implements X509TrustManager {
206 205
         return TrustResult.UNTRUSTED_GENERAL;
207 206
     }
208 207
 
209
-    /**
210
-     * Determines whether the given certificate has a valid CN or alternate name for this server's
211
-     * hostname.
212
-     *
213
-     * @param certificate The certificate to be validated
214
-     *
215
-     * @return True if the certificate is valid for this server's host, false otherwise
216
-     */
217
-    public boolean isValidHost(final X509Certificate certificate) {
218
-        final Map<String, String> fields = getDNFieldsFromCert(certificate);
219
-        if (fields.containsKey("CN") && isMatchingServerName(fields.get("CN"))) {
220
-            return true;
221
-        }
222
-
223
-        try {
224
-            if (certificate.getSubjectAlternativeNames() != null) {
225
-                for (List<?> entry : certificate.getSubjectAlternativeNames()) {
226
-                    final int type = (Integer) entry.get(0);
227
-
228
-                    // DNS or IP
229
-                    if ((type == 2 || type == 7) && isMatchingServerName((String) entry.get(1))) {
230
-                        return true;
231
-                    }
232
-                }
233
-            }
234
-        } catch (CertificateParsingException ex) {
235
-            return false;
236
-        }
237
-
238
-        return false;
239
-    }
240
-
241
-    /**
242
-     * Checks whether the specified target matches the server name this certificate manager was
243
-     * initialised with.
244
-     *
245
-     * Target names may contain wildcards per RFC2818.
246
-     *
247
-     * @since 0.6.5
248
-     * @param target The target to compare to our server name
249
-     *
250
-     * @return True if the target matches, false otherwise
251
-     */
252
-    protected boolean isMatchingServerName(final String target) {
253
-        final String[] targetParts = target.split("\\.");
254
-        final String[] serverParts = serverName.split("\\.");
255
-
256
-        if (targetParts.length != serverParts.length) {
257
-            // Fail fast if they don't match
258
-            return false;
259
-        }
260
-
261
-        for (int i = 0; i < serverParts.length; i++) {
262
-            if (!serverParts[i].matches("\\Q" + targetParts[i].replace("*", "\\E.*\\Q") + "\\E")) {
263
-                return false;
264
-            }
265
-        }
266
-
267
-        return true;
268
-    }
269
-
270 208
     @Override
271 209
     public void checkServerTrusted(final X509Certificate[] chain, final String authType)
272 210
             throws CertificateException {
273 211
         this.chain = Arrays.copyOf(chain, chain.length);
274 212
         problems.clear();
275 213
 
276
-        checkHost(chain);
214
+        if (!hostChecker.isValidFor(chain[0], serverName)) {
215
+            problems.add(new CertificateDoesntMatchHostException(
216
+                    "Certificate was not issued to " + serverName));
217
+        }
277 218
 
278 219
         if (checkIssuer(chain)) {
279 220
             problems.clear();
@@ -343,19 +284,6 @@ public class CertificateManager implements X509TrustManager {
343 284
         return manual;
344 285
     }
345 286
 
346
-    /**
347
-     * Checks that the host of the leaf certificate is the same as the server we are connected to.
348
-     *
349
-     * @param chain The chain of certificates to check.
350
-     */
351
-    private void checkHost(final X509Certificate... chain) {
352
-        // Check that the cert is issued to the correct host
353
-        if (!isValidHost(chain[0])) {
354
-            problems.add(new CertificateDoesntMatchHostException(
355
-                    "Certificate was not issued to " + serverName));
356
-        }
357
-    }
358
-
359 287
     /**
360 288
      * Gets the chain of certificates currently being validated, if any.
361 289
      *

+ 23
- 17
src/main/java/com/dmdirc/ui/core/dialogs/sslcertificate/SSLCertificateDialogModel.java View File

@@ -19,6 +19,7 @@ package com.dmdirc.ui.core.dialogs.sslcertificate;
19 19
 
20 20
 import com.dmdirc.tls.CertificateAction;
21 21
 import com.dmdirc.tls.CertificateDoesntMatchHostException;
22
+import com.dmdirc.tls.CertificateHostChecker;
22 23
 import com.dmdirc.tls.CertificateManager;
23 24
 import com.dmdirc.tls.CertificateNotTrustedException;
24 25
 
@@ -47,6 +48,8 @@ public class SSLCertificateDialogModel {
47 48
     private final CertificateManager manager;
48 49
     /** The list of problems found with the certs, if any. */
49 50
     private final Collection<CertificateException> problems;
51
+    /** Checker to use for hostnames. */
52
+    private final CertificateHostChecker hostChecker;
50 53
 
51 54
     /**
52 55
      * Creates a new SSLCertificateDialogModel for the specified chain.
@@ -61,6 +64,7 @@ public class SSLCertificateDialogModel {
61 64
         this.chain = chain;
62 65
         this.problems = problems;
63 66
         this.manager = manager;
67
+        this.hostChecker = new CertificateHostChecker();
64 68
     }
65 69
 
66 70
     /**
@@ -75,7 +79,7 @@ public class SSLCertificateDialogModel {
75 79
         boolean first = true;
76 80
 
77 81
         for (X509Certificate cert : chain) {
78
-            boolean invalid = first && !manager.isValidHost(cert);
82
+            boolean invalid = first && !hostChecker.isValidFor(cert, manager.getServerName());
79 83
             first = false;
80 84
 
81 85
             try {
@@ -123,7 +127,7 @@ public class SSLCertificateDialogModel {
123 127
                 cert.getNotAfter().toString(), tooOld, false));
124 128
         res.add(group);
125 129
 
126
-        final boolean wrongName = index == 0 && !manager.isValidHost(cert);
130
+        final boolean wrongName = index == 0 && !hostChecker.isValidFor(cert, manager.getServerName());
127 131
         final String names = getAlternateNames(cert);
128 132
         final Map<String, String> fields = CertificateManager.getDNFieldsFromCert(cert);
129 133
 
@@ -160,7 +164,7 @@ public class SSLCertificateDialogModel {
160 164
      *
161 165
      * @return A comma-separated list of alternate names
162 166
      */
163
-    protected String getAlternateNames(final X509Certificate cert) {
167
+    private String getAlternateNames(final X509Certificate cert) {
164 168
         final StringBuilder res = new StringBuilder();
165 169
 
166 170
         try {
@@ -196,11 +200,13 @@ public class SSLCertificateDialogModel {
196 200
      * @param field   The name of the field to look for
197 201
      * @param invalid Whether or not the field is a cause for concern
198 202
      */
199
-    protected void addCertField(final Map<String, String> fields,
200
-            final List<CertificateInformationEntry> group, final String title,
201
-            final String field, final boolean invalid) {
202
-        group.add(new CertificateInformationEntry(title,
203
-                fields.containsKey(field) ? fields.get(field) : NOTPRESENT, invalid,
203
+    private void addCertField(
204
+            final Map<String, String> fields,
205
+            final List<CertificateInformationEntry> group,
206
+            final String title,
207
+            final String field,
208
+            final boolean invalid) {
209
+        group.add(new CertificateInformationEntry(title, fields.getOrDefault(field, NOTPRESENT), invalid,
204 210
                 !fields.containsKey(field)));
205 211
     }
206 212
 
@@ -212,22 +218,22 @@ public class SSLCertificateDialogModel {
212 218
     public List<CertificateSummaryEntry> getSummary() {
213 219
         final List<CertificateSummaryEntry> res = new ArrayList<>();
214 220
 
215
-        boolean outofdate = false;
216
-        boolean wronghost = false;
217
-        boolean nottrusted = false;
221
+        boolean outOfDate = false;
222
+        boolean wrongHost = false;
223
+        boolean notTrusted = false;
218 224
 
219 225
         for (CertificateException ex : problems) {
220 226
             if (ex instanceof CertificateExpiredException
221 227
                     || ex instanceof CertificateNotYetValidException) {
222
-                outofdate = true;
228
+                outOfDate = true;
223 229
             } else if (ex instanceof CertificateDoesntMatchHostException) {
224
-                wronghost = true;
230
+                wrongHost = true;
225 231
             } else if (ex instanceof CertificateNotTrustedException) {
226
-                nottrusted = true;
232
+                notTrusted = true;
227 233
             }
228 234
         }
229 235
 
230
-        if (outofdate) {
236
+        if (outOfDate) {
231 237
             res.add(new CertificateSummaryEntry("One or more certificates are "
232 238
                     + "not within their validity period", false));
233 239
         } else {
@@ -235,7 +241,7 @@ public class SSLCertificateDialogModel {
235 241
                     + "within their validity period", true));
236 242
         }
237 243
 
238
-        if (nottrusted) {
244
+        if (notTrusted) {
239 245
             res.add(new CertificateSummaryEntry("The certificate is not issued "
240 246
                     + "by a trusted authority", false));
241 247
         } else {
@@ -243,7 +249,7 @@ public class SSLCertificateDialogModel {
243 249
                     + "trusted", true));
244 250
         }
245 251
 
246
-        if (wronghost) {
252
+        if (wrongHost) {
247 253
             res.add(new CertificateSummaryEntry("The certificate is not issued "
248 254
                     + "to the host you are connecting to", false));
249 255
         } else {

Loading…
Cancel
Save