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