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