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