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