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