001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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.LDAPExtendedOperationException;
035import com.unboundid.ldap.sdk.ResultCode;
036import com.unboundid.util.NotMutable;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039import com.unboundid.util.ssl.SSLUtil;
040
041import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
042import 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)
093public 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}