001    /*
002     * Copyright 2007-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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    
027    import com.unboundid.ldap.sdk.Control;
028    import com.unboundid.ldap.sdk.ExtendedRequest;
029    import com.unboundid.ldap.sdk.ExtendedResult;
030    import com.unboundid.ldap.sdk.InternalSDKHelper;
031    import com.unboundid.ldap.sdk.LDAPConnection;
032    import com.unboundid.ldap.sdk.LDAPException;
033    import com.unboundid.ldap.sdk.ResultCode;
034    import com.unboundid.util.NotMutable;
035    import com.unboundid.util.ThreadSafety;
036    import com.unboundid.util.ThreadSafetyLevel;
037    import com.unboundid.util.ssl.SSLUtil;
038    
039    import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
040    import static com.unboundid.util.Debug.*;
041    
042    
043    
044    /**
045     * This class provides an implementation of the LDAP StartTLS extended request
046     * as defined in <A HREF="http://www.ietf.org/rfc/rfc4511.txt">RFC 4511</A>
047     * section 4.14.  It may be used to establish a secure communication channel
048     * over an otherwise unencrypted connection.
049     * <BR><BR>
050     * Note that when using the StartTLS extended operation, you should establish
051     * a connection to the server's unencrypted LDAP port rather than its secure
052     * port.  Then, you can use the StartTLS extended request in order to secure
053     * that connection.
054     * <BR><BR>
055     * <H2>Example</H2>
056     * The following example attempts to use the StartTLS extended request in order
057     * to secure communication on a previously insecure connection.  In this case,
058     * it will use the {@link com.unboundid.util.ssl.SSLUtil} class in conjunction
059     * with the {@link com.unboundid.util.ssl.TrustAllTrustManager} class to
060     * simplify the process of performing the SSL negotiation by blindly trusting
061     * whatever certificate the server might happen to present.  In real-world
062     * applications, if stronger verification is required then it is recommended
063     * that you use an {@link SSLContext} that is configured to perform an
064     * appropriate level of validation.
065     * <PRE>
066     *   SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
067     *   SSLContext sslContext = sslUtil.createSSLContext();
068     *   ExtendedResult extendedResult = connection.processExtendedOperation(
069     *        new StartTLSExtendedRequest(sslContext));
070     *
071     *   // NOTE:  The processExtendedOperation method will only throw an exception
072     *   // if a problem occurs while trying to send the request or read the
073     *   // response.  It will not throw an exception because of a non-success
074     *   // response.
075     *
076     *   if (extendedResult.getResultCode() == ResultCode.SUCCESS)
077     *   {
078     *     System.out.println("Communication with the server is now secure.");
079     *   }
080     *   else
081     *   {
082     *     System.err.println("An error occurred while attempting to perform " +
083     *          "StartTLS negotiation.  The connection can no longer be used.");
084     *     connection.close();
085     *   }
086     * </PRE>
087     */
088    @NotMutable()
089    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
090    public final class StartTLSExtendedRequest
091           extends ExtendedRequest
092    {
093      /**
094       * The OID (1.3.6.1.4.1.1466.20037) for the StartTLS extended request.
095       */
096      public static final String STARTTLS_REQUEST_OID = "1.3.6.1.4.1.1466.20037";
097    
098    
099    
100      /**
101       * The serial version UID for this serializable class.
102       */
103      private static final long serialVersionUID = -3234194603452821233L;
104    
105    
106    
107      // The SSL context to use to perform the negotiation.
108      private final SSLContext sslContext;
109    
110    
111    
112      /**
113       * Creates a new StartTLS extended request using a default SSL context.
114       *
115       * @throws  LDAPException  If a problem occurs while trying to initialize a
116       *                         default SSL context.
117       */
118      public StartTLSExtendedRequest()
119             throws LDAPException
120      {
121        this(null, null);
122      }
123    
124    
125    
126      /**
127       * Creates a new StartTLS extended request using a default SSL context.
128       *
129       * @param  controls  The set of controls to include in the request.
130       *
131       * @throws  LDAPException  If a problem occurs while trying to initialize a
132       *                         default SSL context.
133       */
134      public StartTLSExtendedRequest(final Control[] controls)
135             throws LDAPException
136      {
137        this(null, controls);
138      }
139    
140    
141    
142      /**
143       * Creates a new StartTLS extended request using the provided SSL context.
144       *
145       * @param  sslContext  The SSL context to use to perform the negotiation.  It
146       *                     may be {@code null} to indicate that a default SSL
147       *                     context should be used.  If an SSL context is provided,
148       *                     then it must already be initialized.
149       *
150       * @throws  LDAPException  If a problem occurs while trying to initialize a
151       *                         default SSL context.
152       */
153      public StartTLSExtendedRequest(final SSLContext sslContext)
154             throws LDAPException
155      {
156        this(sslContext, null);
157      }
158    
159    
160    
161      /**
162       * Creates a new StartTLS extended request.
163       *
164       * @param  sslContext  The SSL context to use to perform the negotiation.  It
165       *                     may be {@code null} to indicate that a default SSL
166       *                     context should be used.  If an SSL context is provided,
167       *                     then it must already be initialized.
168       * @param  controls    The set of controls to include in the request.
169       *
170       * @throws  LDAPException  If a problem occurs while trying to initialize a
171       *                         default SSL context.
172       */
173      public StartTLSExtendedRequest(final SSLContext sslContext,
174                                     final Control[] controls)
175             throws LDAPException
176      {
177        super(STARTTLS_REQUEST_OID, controls);
178    
179        if (sslContext == null)
180        {
181          try
182          {
183            this.sslContext =
184                 SSLContext.getInstance(SSLUtil.getDefaultSSLProtocol());
185            this.sslContext.init(null, null, null);
186          }
187          catch (Exception e)
188          {
189            debugException(e);
190            throw new LDAPException(ResultCode.LOCAL_ERROR,
191                 ERR_STARTTLS_REQUEST_CANNOT_CREATE_DEFAULT_CONTEXT.get(e), e);
192          }
193        }
194        else
195        {
196          this.sslContext = sslContext;
197        }
198      }
199    
200    
201    
202      /**
203       * Creates a new StartTLS extended request from the provided generic extended
204       * request.
205       *
206       * @param  extendedRequest  The generic extended request to use to create this
207       *                          StartTLS extended request.
208       *
209       * @throws  LDAPException  If a problem occurs while decoding the request.
210       */
211      public StartTLSExtendedRequest(final ExtendedRequest extendedRequest)
212             throws LDAPException
213      {
214        this(extendedRequest.getControls());
215    
216        if (extendedRequest.hasValue())
217        {
218          throw new LDAPException(ResultCode.DECODING_ERROR,
219                                  ERR_STARTTLS_REQUEST_HAS_VALUE.get());
220        }
221      }
222    
223    
224    
225      /**
226       * {@inheritDoc}
227       */
228      @Override()
229      public ExtendedResult process(final LDAPConnection connection,
230                                    final int depth)
231             throws LDAPException
232      {
233        // Set an SO_TIMEOUT on the connection if it's not operating in synchronous
234        // mode to make it more responsive during the negotiation phase.
235        InternalSDKHelper.setSoTimeout(connection, 50);
236    
237        final ExtendedResult result = super.process(connection, depth);
238        if (result.getResultCode() == ResultCode.SUCCESS)
239        {
240          InternalSDKHelper.convertToTLS(connection, sslContext);
241        }
242    
243        return result;
244      }
245    
246    
247    
248      /**
249       * {@inheritDoc}
250       */
251      @Override()
252      public StartTLSExtendedRequest duplicate()
253      {
254        return duplicate(getControls());
255      }
256    
257    
258    
259      /**
260       * {@inheritDoc}
261       */
262      @Override()
263      public StartTLSExtendedRequest duplicate(final Control[] controls)
264      {
265        try
266        {
267          final StartTLSExtendedRequest r =
268               new StartTLSExtendedRequest(sslContext, controls);
269          r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
270          return r;
271        }
272        catch (Exception e)
273        {
274          // This should never happen, since an exception should only be thrown if
275          // there is no SSL context, but this instance already has a context.
276          debugException(e);
277          return null;
278        }
279      }
280    
281    
282    
283      /**
284       * {@inheritDoc}
285       */
286      @Override()
287      public String getExtendedRequestName()
288      {
289        return INFO_EXTENDED_REQUEST_NAME_START_TLS.get();
290      }
291    
292    
293    
294      /**
295       * {@inheritDoc}
296       */
297      @Override()
298      public void toString(final StringBuilder buffer)
299      {
300        buffer.append("StartTLSExtendedRequest(");
301    
302        final Control[] controls = getControls();
303        if (controls.length > 0)
304        {
305          buffer.append("controls={");
306          for (int i=0; i < controls.length; i++)
307          {
308            if (i > 0)
309            {
310              buffer.append(", ");
311            }
312    
313            buffer.append(controls[i]);
314          }
315          buffer.append('}');
316        }
317    
318        buffer.append(')');
319      }
320    }