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