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