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}