001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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     */
021    package com.unboundid.ldap.sdk.extensions;
022    
023    
024    
025    import javax.net.ssl.SSLContext;
026    import javax.net.ssl.SSLSocketFactory;
027    
028    import com.unboundid.ldap.sdk.Control;
029    import com.unboundid.ldap.sdk.ExtendedRequest;
030    import com.unboundid.ldap.sdk.ExtendedResult;
031    import com.unboundid.ldap.sdk.InternalSDKHelper;
032    import com.unboundid.ldap.sdk.LDAPConnection;
033    import com.unboundid.ldap.sdk.LDAPException;
034    import com.unboundid.ldap.sdk.ResultCode;
035    import com.unboundid.util.NotMutable;
036    import com.unboundid.util.ThreadSafety;
037    import com.unboundid.util.ThreadSafetyLevel;
038    import com.unboundid.util.ssl.SSLUtil;
039    
040    import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
041    import 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)
092    public 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    }