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