001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2010 Sun Microsystems, Inc. 025 * Portions copyright 2012-2014 ForgeRock AS. 026 */ 027 028package org.forgerock.opendj.ldap; 029 030import java.io.File; 031import java.io.FileInputStream; 032import java.io.IOException; 033import java.security.GeneralSecurityException; 034import java.security.KeyStore; 035import java.security.NoSuchAlgorithmException; 036import java.security.cert.CertificateException; 037import java.security.cert.CertificateExpiredException; 038import java.security.cert.CertificateNotYetValidException; 039import java.security.cert.X509Certificate; 040import java.util.Date; 041import java.util.logging.Level; 042import java.util.logging.Logger; 043 044import javax.net.ssl.TrustManager; 045import javax.net.ssl.TrustManagerFactory; 046import javax.net.ssl.X509TrustManager; 047 048import org.forgerock.opendj.ldap.schema.Schema; 049import org.forgerock.util.Reject; 050 051/** 052 * This class contains methods for creating common types of trust manager. 053 */ 054public final class TrustManagers { 055 056 /** 057 * An X509TrustManager which rejects certificate chains whose subject DN 058 * does not match a specified host name. 059 */ 060 private static final class CheckHostName implements X509TrustManager { 061 062 private final X509TrustManager trustManager; 063 064 private final String hostNamePattern; 065 066 private CheckHostName(final X509TrustManager trustManager, final String hostNamePattern) { 067 this.trustManager = trustManager; 068 this.hostNamePattern = hostNamePattern; 069 } 070 071 /** {@inheritDoc} */ 072 public void checkClientTrusted(final X509Certificate[] chain, final String authType) 073 throws CertificateException { 074 verifyHostName(chain); 075 trustManager.checkClientTrusted(chain, authType); 076 } 077 078 /** {@inheritDoc} */ 079 public void checkServerTrusted(final X509Certificate[] chain, final String authType) 080 throws CertificateException { 081 verifyHostName(chain); 082 trustManager.checkServerTrusted(chain, authType); 083 } 084 085 /** {@inheritDoc} */ 086 public X509Certificate[] getAcceptedIssuers() { 087 return trustManager.getAcceptedIssuers(); 088 } 089 090 /** 091 * Checks whether a host name matches the provided pattern. It accepts 092 * the use of wildcards in the pattern, e.g. {@code *.example.com}. 093 * 094 * @param hostName 095 * The host name. 096 * @param pattern 097 * The host name pattern, which may contain wild cards. 098 * @return {@code true} if the host name matched the pattern, otherwise 099 * {@code false}. 100 */ 101 private boolean hostNameMatchesPattern(final String hostName, final String pattern) { 102 final String[] nameElements = hostName.split("\\."); 103 final String[] patternElements = pattern.split("\\."); 104 105 boolean hostMatch = nameElements.length == patternElements.length; 106 for (int i = 0; i < nameElements.length && hostMatch; i++) { 107 final String ne = nameElements[i]; 108 final String pe = patternElements[i]; 109 if (!pe.equals("*")) { 110 hostMatch = ne.equalsIgnoreCase(pe); 111 } 112 } 113 return hostMatch; 114 } 115 116 private void verifyHostName(final X509Certificate[] chain) { 117 try { 118 // TODO: NPE if root DN. 119 final DN dn = 120 DN.valueOf(chain[0].getSubjectX500Principal().getName(), Schema 121 .getCoreSchema()); 122 final String value = 123 dn.iterator().next().iterator().next().getAttributeValue().toString(); 124 if (!hostNameMatchesPattern(value, hostNamePattern)) { 125 throw new CertificateException( 126 "The host name contained in the certificate chain subject DN \'" 127 + chain[0].getSubjectX500Principal() 128 + "' does not match the host name \'" + hostNamePattern + "'"); 129 } 130 } catch (final Throwable t) { 131 LOG.log(Level.WARNING, "Error parsing subject dn: " 132 + chain[0].getSubjectX500Principal(), t); 133 } 134 } 135 } 136 137 /** 138 * An X509TrustManager which rejects certificates which have expired or are 139 * not yet valid. 140 */ 141 private static final class CheckValidityDates implements X509TrustManager { 142 143 private final X509TrustManager trustManager; 144 145 private CheckValidityDates(final X509TrustManager trustManager) { 146 this.trustManager = trustManager; 147 } 148 149 /** {@inheritDoc} */ 150 public void checkClientTrusted(final X509Certificate[] chain, final String authType) 151 throws CertificateException { 152 verifyExpiration(chain); 153 trustManager.checkClientTrusted(chain, authType); 154 } 155 156 /** {@inheritDoc} */ 157 public void checkServerTrusted(final X509Certificate[] chain, final String authType) 158 throws CertificateException { 159 verifyExpiration(chain); 160 trustManager.checkServerTrusted(chain, authType); 161 } 162 163 /** {@inheritDoc} */ 164 public X509Certificate[] getAcceptedIssuers() { 165 return trustManager.getAcceptedIssuers(); 166 } 167 168 private void verifyExpiration(final X509Certificate[] chain) throws CertificateException { 169 final Date currentDate = new Date(); 170 for (final X509Certificate c : chain) { 171 try { 172 c.checkValidity(currentDate); 173 } catch (final CertificateExpiredException e) { 174 LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \"" 175 + c.getSubjectDN().getName() + "\" because it" + " expired on " 176 + String.valueOf(c.getNotAfter())); 177 178 throw e; 179 } catch (final CertificateNotYetValidException e) { 180 LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \"" 181 + c.getSubjectDN().getName() + "\" because it" + " is not valid until " 182 + String.valueOf(c.getNotBefore())); 183 184 throw e; 185 } 186 } 187 } 188 } 189 190 /** 191 * An X509TrustManager which does not trust any certificates. 192 */ 193 private static final class DistrustAll implements X509TrustManager { 194 /** Single instance. */ 195 private static final DistrustAll INSTANCE = new DistrustAll(); 196 197 /** Prevent instantiation. */ 198 private DistrustAll() { 199 // Nothing to do. 200 } 201 202 /** {@inheritDoc} */ 203 public void checkClientTrusted(final X509Certificate[] chain, final String authType) 204 throws CertificateException { 205 throw new CertificateException(); 206 } 207 208 /** {@inheritDoc} */ 209 public void checkServerTrusted(final X509Certificate[] chain, final String authType) 210 throws CertificateException { 211 throw new CertificateException(); 212 } 213 214 /** {@inheritDoc} */ 215 public X509Certificate[] getAcceptedIssuers() { 216 return new X509Certificate[0]; 217 } 218 } 219 220 /** 221 * An X509TrustManager which trusts all certificates. 222 */ 223 private static final class TrustAll implements X509TrustManager { 224 225 /** Single instance. */ 226 private static final TrustAll INSTANCE = new TrustAll(); 227 228 /** Prevent instantiation. */ 229 private TrustAll() { 230 // Nothing to do. 231 } 232 233 /** {@inheritDoc} */ 234 public void checkClientTrusted(final X509Certificate[] chain, final String authType) 235 throws CertificateException { 236 } 237 238 /** {@inheritDoc} */ 239 public void checkServerTrusted(final X509Certificate[] chain, final String authType) 240 throws CertificateException { 241 } 242 243 /** {@inheritDoc} */ 244 public X509Certificate[] getAcceptedIssuers() { 245 return new X509Certificate[0]; 246 } 247 } 248 249 private static final Logger LOG = Logger.getLogger(TrustManagers.class.getName()); 250 251 /** 252 * Wraps the provided {@code X509TrustManager} by adding additional 253 * validation which rejects certificate chains whose subject DN does not 254 * match the specified host name pattern. The pattern may contain 255 * wild-cards, for example {@code *.example.com}. 256 * 257 * @param hostNamePattern 258 * A host name pattern which the RDN value contained in 259 * certificate subject DNs must match. 260 * @param trustManager 261 * The trust manager to be wrapped. 262 * @return The wrapped trust manager. 263 * @throws NullPointerException 264 * If {@code trustManager} or {@code hostNamePattern} was 265 * {@code null}. 266 */ 267 public static X509TrustManager checkHostName(final String hostNamePattern, 268 final X509TrustManager trustManager) { 269 Reject.ifNull(trustManager, hostNamePattern); 270 return new CheckHostName(trustManager, hostNamePattern); 271 } 272 273 /** 274 * Creates a new {@code X509TrustManager} which will use the named trust 275 * store file to determine whether to trust a certificate. It will use the 276 * default trust store format for the JVM (e.g. {@code JKS}) and will not 277 * use a password to open the trust store. 278 * 279 * @param file 280 * The trust store file name. 281 * @return A new {@code X509TrustManager} which will use the named trust 282 * store file to determine whether to trust a certificate. 283 * @throws GeneralSecurityException 284 * If the trust store could not be loaded, perhaps due to 285 * incorrect format, or missing algorithms. 286 * @throws IOException 287 * If the trust store file could not be found or could not be 288 * read. 289 * @throws NullPointerException 290 * If {@code file} was {@code null}. 291 */ 292 public static X509TrustManager checkUsingTrustStore(final String file) 293 throws GeneralSecurityException, IOException { 294 return checkUsingTrustStore(file, null, null); 295 } 296 297 /** 298 * Creates a new {@code X509TrustManager} which will use the named trust 299 * store file to determine whether to trust a certificate. It will use the 300 * provided trust store format and password. 301 * 302 * @param file 303 * The trust store file name. 304 * @param password 305 * The trust store password, which may be {@code null}. 306 * @param format 307 * The trust store format, which may be {@code null} to indicate 308 * that the default trust store format for the JVM (e.g. 309 * {@code JKS}) should be used. 310 * @return A new {@code X509TrustManager} which will use the named trust 311 * store file to determine whether to trust a certificate. 312 * @throws GeneralSecurityException 313 * If the trust store could not be loaded, perhaps due to 314 * incorrect format, or missing algorithms. 315 * @throws IOException 316 * If the trust store file could not be found or could not be 317 * read. 318 * @throws NullPointerException 319 * If {@code file} was {@code null}. 320 */ 321 public static X509TrustManager checkUsingTrustStore(final String file, final char[] password, 322 final String format) throws GeneralSecurityException, IOException { 323 Reject.ifNull(file); 324 325 final File trustStoreFile = new File(file); 326 final String trustStoreFormat = format != null ? format : KeyStore.getDefaultType(); 327 328 final KeyStore keyStore = KeyStore.getInstance(trustStoreFormat); 329 330 FileInputStream fos = null; 331 try { 332 fos = new FileInputStream(trustStoreFile); 333 keyStore.load(fos, password); 334 } finally { 335 if (fos != null) { 336 try { 337 fos.close(); 338 } catch (final IOException ignored) { 339 // Ignore. 340 } 341 } 342 } 343 344 final TrustManagerFactory tmf = 345 TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 346 tmf.init(keyStore); 347 348 X509TrustManager x509tm = null; 349 for (final TrustManager tm : tmf.getTrustManagers()) { 350 if (tm instanceof X509TrustManager) { 351 x509tm = (X509TrustManager) tm; 352 break; 353 } 354 } 355 356 if (x509tm == null) { 357 throw new NoSuchAlgorithmException(); 358 } 359 360 return x509tm; 361 } 362 363 /** 364 * Wraps the provided {@code X509TrustManager} by adding additional 365 * validation which rejects certificate chains containing certificates which 366 * have expired or are not yet valid. 367 * 368 * @param trustManager 369 * The trust manager to be wrapped. 370 * @return The wrapped trust manager. 371 * @throws NullPointerException 372 * If {@code trustManager} was {@code null}. 373 */ 374 public static X509TrustManager checkValidityDates(final X509TrustManager trustManager) { 375 Reject.ifNull(trustManager); 376 return new CheckValidityDates(trustManager); 377 } 378 379 /** 380 * Returns an {@code X509TrustManager} which does not trust any 381 * certificates. 382 * 383 * @return An {@code X509TrustManager} which does not trust any 384 * certificates. 385 */ 386 public static X509TrustManager distrustAll() { 387 return DistrustAll.INSTANCE; 388 } 389 390 /** 391 * Returns an {@code X509TrustManager} which trusts all certificates. 392 * 393 * @return An {@code X509TrustManager} which trusts all certificates. 394 */ 395 public static X509TrustManager trustAll() { 396 return TrustAll.INSTANCE; 397 } 398 399 /** Prevent insantiation. */ 400 private TrustManagers() { 401 // Nothing to do. 402 } 403 404}