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