001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.ldap.client.api; 021 022 023import org.apache.directory.api.i18n.I18n; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027import javax.net.ssl.TrustManager; 028import javax.net.ssl.TrustManagerFactory; 029import javax.net.ssl.X509TrustManager; 030 031import java.io.FileInputStream; 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.Serializable; 035import java.security.KeyStore; 036import java.security.KeyStoreException; 037import java.security.NoSuchAlgorithmException; 038import java.security.cert.CertificateException; 039import java.security.cert.X509Certificate; 040import java.util.ArrayList; 041import java.util.Date; 042import java.util.List; 043 044 045/** 046 * Implement the X509TrustManager interface which will be used during JSSE truststore manager initialisation for LDAP 047 * client-to-server communications over TLS/SSL. 048 * It is used during certificate validation operations within JSSE. 049 * 050 * Note: This class allows self-signed certificates to pass the validation checks. 051 * 052 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 053 */ 054public final class LdapClientTrustStoreManager implements X509TrustManager, Serializable 055{ 056 /** Default serialVersionUID */ 057 private static final long serialVersionUID = 1L; 058 059 // Logging 060 private static final String CLS_NM = LdapClientTrustStoreManager.class.getName(); 061 private static final Logger LOG = LoggerFactory.getLogger( CLS_NM ); 062 063 // Config variables 064 private boolean isExamineValidityDates; 065 private char[] trustStorePw; 066 067 // This is found on the classpath if trust.store.onclasspath = true (default), otherwise must include exact location on filepath: 068 private String trustStoreFile; 069 private String trustStoreFormat; 070 071 private X509TrustManager[] x509TrustManagers; 072 073 074 /** 075 * Constructor used by connection configuration utility to load trust store manager. 076 * 077 * @param trustStoreFile contains name of trust store file. 078 * @param trustStorePw contains the password for trust store 079 * @param trustStoreFormat contains the format for trust store 080 * @param isExamineValidity boolean var determines if certificate will be examined for valid dates on load. 081 */ 082 public LdapClientTrustStoreManager( String trustStoreFile, char[] trustStorePw, 083 String trustStoreFormat, boolean isExamineValidity ) 084 { 085 if ( trustStoreFile == null ) 086 { 087 // Cannot continue, throw an unchecked exception: 088 throw new RuntimeException( I18n.err( I18n.ERR_04174_INPUT_FILE_NAME_NULL ) ); 089 } 090 091 // contains the file name of a valid JSSE TrustStore found on classpath: 092 this.trustStoreFile = trustStoreFile; 093 094 // the password to the JSSE TrustStore: 095 this.trustStorePw = trustStorePw.clone(); 096 097 // If true, verify the current date is within the validity period for every certificate in the TrustStore: 098 this.isExamineValidityDates = isExamineValidity; 099 100 if ( trustStoreFormat == null ) 101 { 102 this.trustStoreFormat = KeyStore.getDefaultType(); 103 } 104 else 105 { 106 this.trustStoreFormat = trustStoreFormat; 107 } 108 } 109 110 111 /** 112 * Determine if client certificate is to be trusted. 113 * 114 * @param x509Chain The certificate chain 115 * @param authNType The key exchange algorithm being used 116 * @throws CertificateException If the trustManager cannot be found 117 */ 118 public synchronized void checkClientTrusted( X509Certificate[] x509Chain, String authNType ) throws CertificateException 119 { 120 // For each certificate in the chain, check validity: 121 for ( X509TrustManager trustMgr : getTrustManagers( x509Chain ) ) 122 { 123 trustMgr.checkClientTrusted( x509Chain, authNType ); 124 } 125 } 126 127 128 /** 129 * Determine if server certificate is to be trusted. 130 * 131 * @param x509Chain The certificate chain 132 * @param authNType The key exchange algorithm being used 133 * @throws CertificateException If the trustManager cannot be found 134 */ 135 public synchronized void checkServerTrusted( X509Certificate[] x509Chain, String authNType ) throws 136 CertificateException 137 { 138 for ( X509TrustManager trustManager : getTrustManagers( x509Chain ) ) 139 { 140 trustManager.checkServerTrusted( x509Chain, authNType ); 141 } 142 } 143 144 145 /** 146 * Return the list of accepted issuers for this trust manager. 147 * 148 * @return array of accepted issuers 149 */ 150 public synchronized X509Certificate[] getAcceptedIssuers() 151 { 152 List<X509Certificate> certificates = new ArrayList<>(); 153 154 for ( X509TrustManager trustManager : x509TrustManagers ) 155 { 156 for ( X509Certificate certificate : trustManager.getAcceptedIssuers() ) 157 { 158 certificates.add( certificate ); 159 } 160 } 161 162 return certificates.toArray( new X509Certificate[]{} ); 163 } 164 165 166 /** 167 * Return array of trust managers to caller. Will verify that current date is within certs validity period. 168 * 169 * @param x509Chain contains input X.509 certificate chain. 170 * @return array of X.509 trust managers. 171 * @throws CertificateException if trustStoreFile instance variable is null. 172 */ 173 private synchronized X509TrustManager[] getTrustManagers( X509Certificate[] x509Chain ) throws 174 CertificateException 175 { 176 if ( LOG.isInfoEnabled() ) 177 { 178 LOG.info( I18n.msg( I18n.MSG_04176_TRUST_MANAGER_ON_CLASSPATH, CLS_NM ) ); 179 } 180 181 return getTrustManagersOnClasspath( x509Chain ); 182 } 183 184 185 /** 186 * Return array of trust managers to caller. Will verify that current date is within certs validity period. 187 * 188 * @param x509Chain contains input X.509 certificate chain. 189 * @return array of X.509 trust managers. 190 * @throws CertificateException if trustStoreFile instance variable is null. 191 */ 192 private synchronized X509TrustManager[] getTrustManagersOnClasspath( X509Certificate[] x509Chain ) throws 193 CertificateException 194 { 195 // If true, verify the current date is within each certificates validity period. 196 if ( isExamineValidityDates ) 197 { 198 Date currentDate = new Date(); 199 200 for ( X509Certificate x509Cert : x509Chain ) 201 { 202 x509Cert.checkValidity( currentDate ); 203 } 204 } 205 206 InputStream trustStoreInputStream; 207 208 if ( trustStoreFile != null ) 209 { 210 try 211 { 212 trustStoreInputStream = new FileInputStream( trustStoreFile ); 213 } 214 catch ( IOException ioe ) 215 { 216 throw new CertificateException( I18n.err( I18n.ERR_04175_TRUST_STORE_FILE_NULL ) ); 217 } 218 } 219 else 220 { 221 trustStoreInputStream = getTrustStoreInputStream(); 222 } 223 224 if ( trustStoreInputStream == null ) 225 { 226 throw new CertificateException( I18n.err( I18n.ERR_04176_TRUST_MANAGER_NOT_FOUND ) ); 227 } 228 try 229 { 230 trustStoreInputStream.close(); 231 } 232 catch ( IOException e ) 233 { 234 // Eat this ioexception because it shouldn't be a problem, but log just in case: 235 LOG.warn( I18n.msg( I18n.MSG_04175_TRUST_MANAGER_IO_EXCEPTION, e.getMessage() ) ); 236 } 237 238 return loadTrustManagers( getTrustStore() ); 239 } 240 241 242 /** 243 * Return an array of X.509 TrustManagers. 244 * 245 * @param trustStore handle to input trustStore 246 * @return array of trust managers 247 * @throws CertificateException if problem occurs during TrustManager initialization. 248 */ 249 private X509TrustManager[] loadTrustManagers( KeyStore trustStore ) throws CertificateException 250 { 251 try 252 { 253 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory 254 .getDefaultAlgorithm() ); 255 trustManagerFactory.init( trustStore ); 256 TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); 257 x509TrustManagers = new X509TrustManager[trustManagers.length]; 258 259 for ( int i = 0; i < trustManagers.length; i++ ) 260 { 261 x509TrustManagers[i] = ( X509TrustManager ) trustManagers[i]; 262 } 263 } 264 catch ( NoSuchAlgorithmException e ) 265 { 266 throw new CertificateException( I18n.err( I18n.ERR_04177_NO_SUCH_ALGORITHM ), e ); 267 } 268 catch ( KeyStoreException e ) 269 { 270 throw new CertificateException( I18n.err( I18n.ERR_04178_CANT_LOAD_KEY_STORE ), e ); 271 } 272 273 return x509TrustManagers; 274 } 275 276 277 /** 278 * Load the TrustStore file into JSSE KeyStore instance. 279 * 280 * @return instance of JSSE KeyStore containing the LDAP Client's TrustStore file info. * 281 * @throws CertificateException if cannot process file load. 282 */ 283 private KeyStore getTrustStore() throws CertificateException 284 { 285 KeyStore trustStore; 286 287 try 288 { 289 trustStore = KeyStore.getInstance( trustStoreFormat ); 290 } 291 catch ( KeyStoreException e ) 292 { 293 throw new CertificateException( I18n.err( I18n.ERR_04178_CANT_LOAD_KEY_STORE ), e ); 294 } 295 296 InputStream trustStoreInputStream = null; 297 298 try 299 { 300 if ( trustStoreFile != null ) 301 { 302 trustStoreInputStream = new FileInputStream( trustStoreFile ); 303 } 304 else 305 { 306 trustStoreInputStream = getTrustStoreInputStream(); 307 } 308 309 trustStore.load( trustStoreInputStream, trustStorePw ); 310 } 311 catch ( NoSuchAlgorithmException e ) 312 { 313 throw new CertificateException( I18n.err( I18n.ERR_04177_NO_SUCH_ALGORITHM ), e ); 314 } 315 catch ( IOException e ) 316 { 317 throw new CertificateException( I18n.err( I18n.ERR_04178_CANT_LOAD_KEY_STORE ), e ); 318 } 319 finally 320 { 321 // Close the input stream. 322 if ( trustStoreInputStream != null ) 323 { 324 try 325 { 326 trustStoreInputStream.close(); 327 } 328 catch ( IOException e ) 329 { 330 // Eat this ioexception because it shouldn't be a problem, but log just in case: 331 LOG.warn( I18n.err( I18n.ERR_04179_TRUST_STORE_CANT_BE_READ, e.getMessage() ) ); 332 } 333 } 334 } 335 336 return trustStore; 337 } 338 339 340 /** 341 * Read the trust store off the classpath. 342 * 343 * @return handle to inputStream containing the trust store 344 * @throws CertificateException If the file cannot be found 345 */ 346 private InputStream getTrustStoreInputStream() throws CertificateException 347 { 348 ClassLoader classloader = Thread.currentThread().getContextClassLoader(); 349 InputStream result = classloader.getResourceAsStream( trustStoreFile ); 350 351 if ( null == result ) 352 { 353 throw new CertificateException( I18n.err( I18n.ERR_04180_FILE_DOES_NOT_EXIST_ON_CLASSPATH ) ); 354 } 355 356 return result; 357 } 358}