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