001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk.extensions;
022
023
024
025 import javax.net.ssl.SSLContext;
026 import javax.net.ssl.SSLSocketFactory;
027
028 import com.unboundid.ldap.sdk.Control;
029 import com.unboundid.ldap.sdk.ExtendedRequest;
030 import com.unboundid.ldap.sdk.ExtendedResult;
031 import com.unboundid.ldap.sdk.InternalSDKHelper;
032 import com.unboundid.ldap.sdk.LDAPConnection;
033 import com.unboundid.ldap.sdk.LDAPException;
034 import com.unboundid.ldap.sdk.LDAPExtendedOperationException;
035 import com.unboundid.ldap.sdk.ResultCode;
036 import com.unboundid.util.ssl.SSLUtil;
037
038 import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
039 import static com.unboundid.util.Debug.*;
040
041
042
043 /**
044 * This class provides an implementation of the LDAP StartTLS extended request
045 * as defined in <A HREF="http://www.ietf.org/rfc/rfc4511.txt">RFC 4511</A>
046 * section 4.14. It may be used to establish a secure communication channel
047 * over an otherwise unencrypted connection.
048 * <BR><BR>
049 * Note that when using the StartTLS extended operation, you should establish
050 * a connection to the server's unencrypted LDAP port rather than its secure
051 * port. Then, you can use the StartTLS extended request in order to secure
052 * that connection.
053 * <BR><BR>
054 * <H2>Example</H2>
055 * The following example attempts to use the StartTLS extended request in order
056 * to secure communication on a previously insecure connection. In this case,
057 * it will use the {@code com.unboundid.util.ssl.SSLUtil} class in conjunction
058 * with the {@code com.unboundid.util.ssl.TrustStoreTrustManager} class to
059 * ensure that only certificates from trusted authorities will be accepted.
060 * <PRE>
061 * // Create an SSLContext that will be used to perform the cryptographic
062 * // processing.
063 * SSLUtil sslUtil = new SSLUtil(new TrustStoreTrustManager(trustStorePath));
064 * SSLContext sslContext = sslUtil.createSSLContext();
065 *
066 * // Create and process the extended request to secure a connection.
067 * StartTLSExtendedRequest startTLSRequest =
068 * new StartTLSExtendedRequest(sslContext);
069 * ExtendedResult startTLSResult;
070 * try
071 * {
072 * startTLSResult = connection.processExtendedOperation(startTLSRequest);
073 * // This doesn't necessarily mean that the operation was successful, since
074 * // some kinds of extended operations return non-success results under
075 * // normal conditions.
076 * }
077 * catch (LDAPException le)
078 * {
079 * // For an extended operation, this generally means that a problem was
080 * // encountered while trying to send the request or read the result.
081 * startTLSResult = new ExtendedResult(le);
082 * }
083 *
084 * // Make sure that we can use the connection to interact with the server.
085 * RootDSE rootDSE = connection.getRootDSE();
086 * </PRE>
087 */
088 public final class StartTLSExtendedRequest
089 extends ExtendedRequest
090 {
091 /**
092 * The OID (1.3.6.1.4.1.1466.20037) for the StartTLS extended request.
093 */
094 public static final String STARTTLS_REQUEST_OID = "1.3.6.1.4.1.1466.20037";
095
096
097
098 /**
099 * The serial version UID for this serializable class.
100 */
101 private static final long serialVersionUID = -3234194603452821233L;
102
103
104
105 // The SSL socket factory used to perform the negotiation.
106 private final SSLSocketFactory sslSocketFactory;
107
108
109
110 /**
111 * Creates a new StartTLS extended request using a default SSL context.
112 *
113 * @throws LDAPException If a problem occurs while trying to initialize a
114 * default SSL context.
115 */
116 public StartTLSExtendedRequest()
117 throws LDAPException
118 {
119 this((SSLSocketFactory) null, null);
120 }
121
122
123
124 /**
125 * Creates a new StartTLS extended request using a default SSL context.
126 *
127 * @param controls The set of controls to include in the request.
128 *
129 * @throws LDAPException If a problem occurs while trying to initialize a
130 * default SSL context.
131 */
132 public StartTLSExtendedRequest(final Control[] controls)
133 throws LDAPException
134 {
135 this((SSLSocketFactory) null, controls);
136 }
137
138
139
140 /**
141 * Creates a new StartTLS extended request using the provided SSL context.
142 *
143 * @param sslContext The SSL context to use to perform the negotiation. It
144 * may be {@code null} to indicate that a default SSL
145 * context should be used. If an SSL context is provided,
146 * then it must already be initialized.
147 *
148 * @throws LDAPException If a problem occurs while trying to initialize a
149 * default SSL context.
150 */
151 public StartTLSExtendedRequest(final SSLContext sslContext)
152 throws LDAPException
153 {
154 this(sslContext, null);
155 }
156
157
158
159 /**
160 * Creates a new StartTLS extended request using the provided SSL socket
161 * factory.
162 *
163 * @param sslSocketFactory The SSL socket factory to use to convert an
164 * insecure connection into a secure connection. It
165 * may be {@code null} to indicate that a default
166 * SSL socket factory should be used.
167 *
168 * @throws LDAPException If a problem occurs while trying to initialize a
169 * default SSL socket factory.
170 */
171 public StartTLSExtendedRequest(final SSLSocketFactory sslSocketFactory)
172 throws LDAPException
173 {
174 this(sslSocketFactory, null);
175 }
176
177
178
179 /**
180 * Creates a new StartTLS extended request.
181 *
182 * @param sslContext The SSL context to use to perform the negotiation. It
183 * may be {@code null} to indicate that a default SSL
184 * context should be used. If an SSL context is provided,
185 * then it must already be initialized.
186 * @param controls The set of controls to include in the request.
187 *
188 * @throws LDAPException If a problem occurs while trying to initialize a
189 * default SSL context.
190 */
191 public StartTLSExtendedRequest(final SSLContext sslContext,
192 final Control[] controls)
193 throws LDAPException
194 {
195 super(STARTTLS_REQUEST_OID, controls);
196
197 if (sslContext == null)
198 {
199 try
200 {
201 final SSLContext ctx =
202 SSLContext.getInstance(SSLUtil.getDefaultSSLProtocol());
203 ctx.init(null, null, null);
204 sslSocketFactory = ctx.getSocketFactory();
205 }
206 catch (Exception e)
207 {
208 debugException(e);
209 throw new LDAPException(ResultCode.LOCAL_ERROR,
210 ERR_STARTTLS_REQUEST_CANNOT_CREATE_DEFAULT_CONTEXT.get(e), e);
211 }
212 }
213 else
214 {
215 sslSocketFactory = sslContext.getSocketFactory();
216 }
217 }
218
219
220
221 /**
222 * Creates a new StartTLS extended request.
223 *
224 * @param sslSocketFactory The SSL socket factory to use to convert an
225 * insecure connection into a secure connection. It
226 * may be {@code null} to indicate that a default
227 * SSL socket factory should be used.
228 * @param controls The set of controls to include in the request.
229 *
230 * @throws LDAPException If a problem occurs while trying to initialize a
231 * default SSL context.
232 */
233 public StartTLSExtendedRequest(final SSLSocketFactory sslSocketFactory,
234 final Control[] controls)
235 throws LDAPException
236 {
237 super(STARTTLS_REQUEST_OID, controls);
238
239 if (sslSocketFactory == null)
240 {
241 try
242 {
243 final SSLContext ctx =
244 SSLContext.getInstance(SSLUtil.getDefaultSSLProtocol());
245 ctx.init(null, null, null);
246 this.sslSocketFactory = ctx.getSocketFactory();
247 }
248 catch (Exception e)
249 {
250 debugException(e);
251 throw new LDAPException(ResultCode.LOCAL_ERROR,
252 ERR_STARTTLS_REQUEST_CANNOT_CREATE_DEFAULT_CONTEXT.get(e), e);
253 }
254 }
255 else
256 {
257 this.sslSocketFactory = sslSocketFactory;
258 }
259 }
260
261
262
263 /**
264 * Creates a new StartTLS extended request from the provided generic extended
265 * request.
266 *
267 * @param extendedRequest The generic extended request to use to create this
268 * StartTLS extended request.
269 *
270 * @throws LDAPException If a problem occurs while decoding the request.
271 */
272 public StartTLSExtendedRequest(final ExtendedRequest extendedRequest)
273 throws LDAPException
274 {
275 this(extendedRequest.getControls());
276
277 if (extendedRequest.hasValue())
278 {
279 throw new LDAPException(ResultCode.DECODING_ERROR,
280 ERR_STARTTLS_REQUEST_HAS_VALUE.get());
281 }
282 }
283
284
285
286 /**
287 * Sends this StartTLS request to the server and performs the necessary
288 * client-side security processing if the operation is processed successfully.
289 * That this method is guaranteed to throw an {@code LDAPException} if the
290 * server returns a non-success result.
291 *
292 * @param connection The connection to use to communicate with the directory
293 * server.
294 * @param depth The current referral depth for this request. It should
295 * always be zero for the initial request, and should only
296 * be incremented when following referrals.
297 *
298 * @return The extended result received from the server if StartTLS processing
299 * was completed successfully.
300 *
301 * @throws LDAPException If the server returned a non-success result, or if
302 * a problem was encountered while performing
303 * client-side security processing.
304 */
305 @Override()
306 public ExtendedResult process(final LDAPConnection connection,
307 final int depth)
308 throws LDAPException
309 {
310 // Set an SO_TIMEOUT on the connection if it's not operating in synchronous
311 // mode to make it more responsive during the negotiation phase.
312 InternalSDKHelper.setSoTimeout(connection, 50);
313
314 final ExtendedResult result = super.process(connection, depth);
315 if (result.getResultCode() == ResultCode.SUCCESS)
316 {
317 InternalSDKHelper.convertToTLS(connection, sslSocketFactory);
318 }
319 else
320 {
321 throw new LDAPExtendedOperationException(result);
322 }
323
324 return result;
325 }
326
327
328
329 /**
330 * {@inheritDoc}
331 */
332 @Override()
333 public StartTLSExtendedRequest duplicate()
334 {
335 return duplicate(getControls());
336 }
337
338
339
340 /**
341 * {@inheritDoc}
342 */
343 @Override()
344 public StartTLSExtendedRequest duplicate(final Control[] controls)
345 {
346 try
347 {
348 final StartTLSExtendedRequest r =
349 new StartTLSExtendedRequest(sslSocketFactory, controls);
350 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
351 return r;
352 }
353 catch (Exception e)
354 {
355 // This should never happen, since an exception should only be thrown if
356 // there is no SSL context, but this instance already has a context.
357 debugException(e);
358 return null;
359 }
360 }
361
362
363
364 /**
365 * {@inheritDoc}
366 */
367 @Override()
368 public String getExtendedRequestName()
369 {
370 return INFO_EXTENDED_REQUEST_NAME_START_TLS.get();
371 }
372
373
374
375 /**
376 * {@inheritDoc}
377 */
378 @Override()
379 public void toString(final StringBuilder buffer)
380 {
381 buffer.append("StartTLSExtendedRequest(");
382
383 final Control[] controls = getControls();
384 if (controls.length > 0)
385 {
386 buffer.append("controls={");
387 for (int i=0; i < controls.length; i++)
388 {
389 if (i > 0)
390 {
391 buffer.append(", ");
392 }
393
394 buffer.append(controls[i]);
395 }
396 buffer.append('}');
397 }
398
399 buffer.append(')');
400 }
401 }