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 */
021package com.unboundid.ldap.sdk.extensions;
022
023
024
025import javax.net.ssl.SSLContext;
026import javax.net.ssl.SSLSocketFactory;
027
028import com.unboundid.ldap.sdk.Control;
029import com.unboundid.ldap.sdk.ExtendedRequest;
030import com.unboundid.ldap.sdk.ExtendedResult;
031import com.unboundid.ldap.sdk.InternalSDKHelper;
032import com.unboundid.ldap.sdk.LDAPConnection;
033import com.unboundid.ldap.sdk.LDAPException;
034import com.unboundid.ldap.sdk.ResultCode;
035import com.unboundid.util.NotMutable;
036import com.unboundid.util.ThreadSafety;
037import com.unboundid.util.ThreadSafetyLevel;
038import com.unboundid.util.ssl.SSLUtil;
039
040import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
041import 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)
092public 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}