001package com.nimbusds.common.ldap; 002 003 004import java.security.GeneralSecurityException; 005import java.security.KeyStoreException; 006 007import javax.net.SocketFactory; 008import javax.net.ssl.KeyManager; 009import javax.net.ssl.TrustManager; 010import javax.net.ssl.SSLContext; 011import javax.net.ssl.SSLHandshakeException; 012 013import com.unboundid.ldap.sdk.ExtendedRequest; 014import com.unboundid.ldap.sdk.ExtendedResult; 015import com.unboundid.ldap.sdk.LDAPConnection; 016import com.unboundid.ldap.sdk.LDAPConnectionOptions; 017import com.unboundid.ldap.sdk.LDAPException; 018import com.unboundid.ldap.sdk.ResultCode; 019import com.unboundid.ldap.sdk.ServerSet; 020import com.unboundid.ldap.sdk.SingleServerSet; 021import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; 022import com.unboundid.util.ssl.SSLUtil; 023import com.unboundid.util.ssl.KeyStoreKeyManager; 024import com.unboundid.util.ssl.TrustAllTrustManager; 025import com.unboundid.util.ssl.TrustStoreTrustManager; 026 027import com.nimbusds.common.config.CustomKeyStoreConfiguration; 028import com.nimbusds.common.config.CustomTrustStoreConfiguration; 029 030 031/** 032 * Factory for establishing LDAP connections to a directory server. 033 */ 034public class LDAPConnectionFactory { 035 036 037 /** 038 * The custom trust store for LDAP over SSL/TLS. 039 */ 040 private final CustomTrustStoreConfiguration customTrustStore; 041 042 043 /** 044 * The custom key store for LDAP over SSL/TLS. 045 */ 046 private final CustomKeyStoreConfiguration customKeyStore; 047 048 049 050 /** 051 * Creates a new LDAP connection factory. 052 * 053 * @param customTrustStore The custom trust store configuration. Must 054 * not be {@code null}. 055 * @param customKeyStore The custom key store configuration. Must not 056 * be {@code null}. 057 */ 058 public LDAPConnectionFactory(final CustomTrustStoreConfiguration customTrustStore, 059 final CustomKeyStoreConfiguration customKeyStore) { 060 061 if (customTrustStore == null) 062 throw new IllegalArgumentException("The custom TLS/SSL trust store configuration must not be null"); 063 064 this.customTrustStore = customTrustStore; 065 066 if (customKeyStore == null) 067 throw new IllegalArgumentException("The custom TLS/SSL key store configuration must not be null"); 068 069 this.customKeyStore = customKeyStore; 070 } 071 072 073 /** 074 * Gets the custom trust store configuration. 075 * 076 * @return The custom trust store configuration. 077 */ 078 public CustomTrustStoreConfiguration getCustomTrustStoreConfiguration() { 079 080 return customTrustStore; 081 } 082 083 084 /** 085 * Gets the custom key store configuration. 086 * 087 * @return The custom key store configuration. 088 */ 089 public CustomKeyStoreConfiguration getCustomKeyStoreConfiguration() { 090 091 return customKeyStore; 092 } 093 094 095 /** 096 * Initialises the context for a secure LDAP connection by creating the 097 * required TLS/SSL trust and key managers. 098 * 099 * @param customTrustStore The custom trust store configuration. 100 * Must not be {@code null}. 101 * @param customKeyStore The custom key store configuration. Must 102 * not be {@code null}. 103 * @param trustSelfSignedCerts The trust policy for self-signed X.509 104 * certificates presented by the LDAP 105 * server. 106 * 107 * @return A helper for creating the SSL context and sockets. 108 * 109 * @throws KeyStoreException On a client key store exception. 110 */ 111 public static SSLUtil initSecureConnectionContext(final CustomTrustStoreConfiguration customTrustStore, 112 final CustomKeyStoreConfiguration customKeyStore, 113 final boolean trustSelfSignedCerts) 114 throws KeyStoreException { 115 116 // Configure a trust manager 117 TrustManager trustManager; 118 119 if (trustSelfSignedCerts) { 120 121 // Accept self-signed certs presented by the server, 122 // no checks against local trust store 123 124 boolean examineValidityDates = true; 125 126 trustManager = new TrustAllTrustManager(examineValidityDates); 127 } 128 else if (customTrustStore.enable) { 129 130 // Use custom trust store file provided with this 131 // web application 132 133 boolean examineValidityDates = true; 134 135 trustManager = new TrustStoreTrustManager(customTrustStore.file, 136 customTrustStore.password.toCharArray(), 137 customTrustStore.type, 138 examineValidityDates); 139 } 140 else { 141 // Fall back to default system trust manager (if any) 142 trustManager = null; 143 } 144 145 // Configure a key manager 146 KeyManager keyManager; 147 148 if (customKeyStore.enable) { 149 150 // Use custom key store file provided with this 151 // web application 152 153 String certificateAlias = null; 154 155 keyManager = new KeyStoreKeyManager(customKeyStore.file, 156 customKeyStore.password.toCharArray(), 157 customKeyStore.type, 158 certificateAlias); 159 } 160 else { 161 // Fall back to default system key manager (if any) 162 keyManager = null; 163 } 164 165 return new SSLUtil(keyManager, trustManager); 166 } 167 168 169 /** 170 * Creates a new socket factory according to the specified LDAP 171 * connection security settings. 172 * 173 * @param security The requested LDAP connection security. 174 * Must not be {@code null}. 175 * @param customTrustStore The custom trust store configuration. 176 * Must not be {@code null}. 177 * @param customKeyStore The custom key store configuration. Must 178 * not be {@code null}. 179 * @param trustSelfSignedCerts The trust policy for self-signed X.509 180 * certificates presented by the LDAP 181 * server. 182 * 183 * @return A configured SSL socket factory, {@code null} for a plain 184 * connection. 185 * 186 * @throws LDAPConnectionException On a failure to create an SSL socket 187 * factory. 188 */ 189 public static SocketFactory getSocketFactory(final LDAPConnectionSecurity security, 190 final CustomTrustStoreConfiguration customTrustStore, 191 final CustomKeyStoreConfiguration customKeyStore, 192 final boolean trustSelfSignedCerts) 193 throws LDAPConnectionException { 194 195 // Only SSL required special socket factory 196 if (security != LDAPConnectionSecurity.SSL) 197 return null; 198 199 try { 200 SSLUtil sslUtil = initSecureConnectionContext(customTrustStore, 201 customKeyStore, 202 trustSelfSignedCerts); 203 204 return sslUtil.createSSLSocketFactory(); 205 206 } catch (KeyStoreException e) { 207 208 throw new LDAPConnectionException("Key store exception: " + e.getMessage(), 209 LDAPConnectionException.CauseType.KEYSTORE_ERROR, 210 e); 211 212 } catch (GeneralSecurityException e) { 213 214 throw new LDAPConnectionException("Couldn't create SSL socket factory: " + e.getMessage(), 215 LDAPConnectionException.CauseType.TLS_SSL_ERROR, 216 e); 217 } 218 } 219 220 221 /** 222 * Applies StartTLS to the specified LDAP connection. 223 * 224 * @param con A preconfigured and established LDAP connection to have 225 * StartTLS applied to. Must not be {@code null}. 226 * 227 * @throws LDAPConnectionException If StartTLS couldn't be applied to 228 * the LDAP connection. 229 */ 230 private void applyStartTLS(final LDAPConnection con, 231 final boolean trustSelfSignedCerts) 232 throws LDAPConnectionException { 233 234 // Init key store 235 SSLUtil sslUtil; 236 237 try { 238 sslUtil = initSecureConnectionContext(customTrustStore, 239 customKeyStore, 240 trustSelfSignedCerts); 241 242 } catch (KeyStoreException e) { 243 244 throw new LDAPConnectionException("Key store exception: " + e.getMessage(), 245 LDAPConnectionException.CauseType.KEYSTORE_ERROR, 246 e); 247 } 248 249 // Init client-side TLS/SSL context 250 SSLContext sslContext; 251 252 try { 253 sslContext = sslUtil.createSSLContext(); 254 255 } catch (GeneralSecurityException e) { 256 257 throw new LDAPConnectionException("TLS/SSL error: " + e.getMessage(), 258 LDAPConnectionException.CauseType.TLS_SSL_ERROR, 259 e); 260 } 261 262 // Start TLS ext. op. (OID 1.3.6.1.4.1.1466.20037) 263 ExtendedResult extResult; 264 265 try { 266 ExtendedRequest extRequest = new StartTLSExtendedRequest(sslContext); 267 extResult = con.processExtendedOperation(extRequest); 268 269 } catch (LDAPException e) { 270 271 con.close(); 272 273 Throwable cause = e.getCause(); 274 275 if (cause != null && cause instanceof SSLHandshakeException) 276 throw new LDAPConnectionException("Bad server X.509 certificate: " + e.getMessage(), 277 LDAPConnectionException.CauseType.BAD_CERT, 278 e); 279 else 280 throw LDAPConnectionException.parse(e); // Reformat exception 281 } 282 283 if (extResult.getResultCode() != ResultCode.SUCCESS) { 284 285 // The StartTLS negotiation failed for some reason, 286 // the connection can no longer be used. 287 con.close(); 288 289 throw new LDAPConnectionException("StartTLS exception: " + extResult.getDiagnosticMessage(), 290 LDAPConnectionException.CauseType.STARTTLS_ERROR, 291 null); 292 } 293 } 294 295 296 /** 297 * Creates a new LDAP connection to the specified directory server. 298 * 299 * @param host The LDAP server host name / IP address. 300 * Must not be {@code null}. 301 * @param port The LDAP server port. 302 * @param security The LDAP connection security. Must not 303 * be {@code null}. 304 * @param timeout The timeout in milliseconds for LDAP 305 * connect requests. If zero the underlying 306 * LDAP client library will determine this 307 * value. 308 * @param trustSelfSignedCerts The trust policy for self-signed X.509 309 * certificates presented by the LDAP 310 * server. 311 * 312 * @return A new established unauthenticated LDAP connection ready for 313 * use. 314 * 315 * @throws LDAPConnectionException If a new LDAP connection could not 316 * be created. 317 */ 318 public LDAPConnection createLDAPConnection(final String host, 319 final int port, 320 final LDAPConnectionSecurity security, 321 final int timeout, 322 final boolean trustSelfSignedCerts) 323 throws LDAPConnectionException { 324 325 326 // Init LDAP server set 327 SocketFactory socketFactory = getSocketFactory(security, 328 customTrustStore, 329 customKeyStore, 330 trustSelfSignedCerts); 331 332 LDAPConnectionOptions opts = new LDAPConnectionOptions(); 333 opts.setConnectTimeoutMillis(timeout); 334 335 ServerSet ldapServerSet = new SingleServerSet(host, port, socketFactory, opts); 336 337 return createLDAPConnection(ldapServerSet, security, trustSelfSignedCerts); 338 } 339 340 341 /** 342 * Creates a new LDAP connection to the specified directory server set. 343 * 344 * @param ldapServerSet The LDAP server set. Must not be 345 * {@code null}. 346 * @param security The LDAP connection security. Must not 347 * be {@code null}. 348 * @param trustSelfSignedCerts The trust policy for self-signed X.509 349 * certificates presented by the LDAP 350 * server. 351 * 352 * @return A new established unauthenticated LDAP connection ready for 353 * use. 354 * 355 * @throws LDAPConnectionException If a new LDAP connection could not 356 * be created. 357 */ 358 public LDAPConnection createLDAPConnection(final ServerSet ldapServerSet, 359 final LDAPConnectionSecurity security, 360 final boolean trustSelfSignedCerts) 361 throws LDAPConnectionException { 362 363 LDAPConnection con; 364 365 try { 366 con = ldapServerSet.getConnection(); 367 368 } catch (LDAPException e) { 369 370 throw LDAPConnectionException.parse(e); 371 } 372 373 if (security != LDAPConnectionSecurity.STARTTLS) 374 return con; 375 376 // Post-process connection for Start TLS 377 applyStartTLS(con, trustSelfSignedCerts); 378 379 return con; 380 } 381}