1 /*
   2  * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.naming.ldap;
  27 
  28 import java.util.Iterator;
  29 import java.security.AccessController;
  30 import java.security.PrivilegedAction;
  31 import javax.naming.ConfigurationException;
  32 import javax.naming.NamingException;
  33 import com.sun.naming.internal.VersionHelper;
  34 import java.util.ServiceLoader;
  35 import java.util.ServiceConfigurationError;
  36 
  37 /**
  38  * This class implements the LDAPv3 Extended Request for StartTLS as
  39  * defined in
  40  * <a href="http://www.ietf.org/rfc/rfc2830.txt">Lightweight Directory
  41  * Access Protocol (v3): Extension for Transport Layer Security</a>
  42  *
  43  * The object identifier for StartTLS is 1.3.6.1.4.1.1466.20037
  44  * and no extended request value is defined.
  45  *<p>
  46  * {@code StartTlsRequest}/{@code StartTlsResponse} are used to establish
  47  * a TLS connection over the existing LDAP connection associated with
  48  * the JNDI context on which {@code extendedOperation()} is invoked.
  49  * Typically, a JNDI program uses these classes as follows.
  50  * <blockquote><pre>
  51  * import javax.naming.ldap.*;
  52  *
  53  * // Open an LDAP association
  54  * LdapContext ctx = new InitialLdapContext();
  55  *
  56  * // Perform a StartTLS extended operation
  57  * StartTlsResponse tls =
  58  *     (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
  59  *
  60  * // Open a TLS connection (over the existing LDAP association) and get details
  61  * // of the negotiated TLS session: cipher suite, peer certificate, etc.
  62  * SSLSession session = tls.negotiate();
  63  *
  64  * // ... use ctx to perform protected LDAP operations
  65  *
  66  * // Close the TLS connection (revert back to the underlying LDAP association)
  67  * tls.close();
  68  *
  69  * // ... use ctx to perform unprotected LDAP operations
  70  *
  71  * // Close the LDAP association
  72  * ctx.close;
  73  * </pre></blockquote>
  74  *
  75  * @since 1.4
  76  * @see StartTlsResponse
  77  * @author Vincent Ryan
  78  */
  79 public class StartTlsRequest implements ExtendedRequest {
  80 
  81     // Constant
  82 
  83     /**
  84      * The StartTLS extended request's assigned object identifier
  85      * is 1.3.6.1.4.1.1466.20037.
  86      */
  87     public static final String OID = "1.3.6.1.4.1.1466.20037";
  88 
  89 
  90     // Constructors
  91 
  92     /**
  93      * Constructs a StartTLS extended request.
  94      */
  95     public StartTlsRequest() {
  96     }
  97 
  98 
  99     // ExtendedRequest methods
 100 
 101     /**
 102      * Retrieves the StartTLS request's object identifier string.
 103      *
 104      * @return The object identifier string, "1.3.6.1.4.1.1466.20037".
 105      */
 106     public String getID() {
 107         return OID;
 108     }
 109 
 110     /**
 111      * Retrieves the StartTLS request's ASN.1 BER encoded value.
 112      * Since the request has no defined value, null is always
 113      * returned.
 114      *
 115      * @return The null value.
 116      */
 117     public byte[] getEncodedValue() {
 118         return null;
 119     }
 120 
 121     /**
 122      * Creates an extended response object that corresponds to the
 123      * LDAP StartTLS extended request.
 124      * <p>
 125      * The result must be a concrete subclass of StartTlsResponse
 126      * and must have a public zero-argument constructor.
 127      * <p>
 128      * This method locates the implementation class by locating
 129      * configuration files that have the name:
 130      * <blockquote>{@code
 131      *     META-INF/services/javax.naming.ldap.StartTlsResponse
 132      * }</blockquote>
 133      * The configuration files and their corresponding implementation classes must
 134      * be accessible to the calling thread's context class loader.
 135      * <p>
 136      * Each configuration file should contain a list of fully-qualified class
 137      * names, one per line.  Space and tab characters surrounding each name, as
 138      * well as blank lines, are ignored.  The comment character is {@code '#'}
 139      * ({@code 0x23}); on each line all characters following the first comment
 140      * character are ignored.  The file must be encoded in UTF-8.
 141      * <p>
 142      * This method will return an instance of the first implementation
 143      * class that it is able to load and instantiate successfully from
 144      * the list of class names collected from the configuration files.
 145      * This method uses the calling thread's context classloader to find the
 146      * configuration files and to load the implementation class.
 147      * <p>
 148      * If no class can be found in this way, this method will use
 149      * an implementation-specific way to locate an implementation.
 150      * If none is found, a NamingException is thrown.
 151      *
 152      * @param id         The object identifier of the extended response.
 153      *                   Its value must be "1.3.6.1.4.1.1466.20037" or null.
 154      *                   Both values are equivalent.
 155      * @param berValue   The possibly null ASN.1 BER encoded value of the
 156      *                   extended response. This is the raw BER bytes
 157      *                   including the tag and length of the response value.
 158      *                   It does not include the response OID.
 159      *                   Its value is ignored because a Start TLS response
 160      *                   is not expected to contain any response value.
 161      * @param offset     The starting position in berValue of the bytes to use.
 162      *                   Its value is ignored because a Start TLS response
 163      *                   is not expected to contain any response value.
 164      * @param length     The number of bytes in berValue to use.
 165      *                   Its value is ignored because a Start TLS response
 166      *                   is not expected to contain any response value.
 167      * @return           The StartTLS extended response object.
 168      * @exception        NamingException If a naming exception was encountered
 169      *                   while creating the StartTLS extended response object.
 170      */
 171     public ExtendedResponse createExtendedResponse(String id, byte[] berValue,
 172         int offset, int length) throws NamingException {
 173 
 174         // Confirm that the object identifier is correct
 175         if ((id != null) && (!id.equals(OID))) {
 176             throw new ConfigurationException(
 177                 "Start TLS received the following response instead of " +
 178                 OID + ": " + id);
 179         }
 180 
 181         StartTlsResponse resp = null;
 182 
 183         ServiceLoader<StartTlsResponse> sl = ServiceLoader.load(
 184                 StartTlsResponse.class, getContextClassLoader());
 185         Iterator<StartTlsResponse> iter = sl.iterator();
 186 
 187         while (resp == null && privilegedHasNext(iter)) {
 188             resp = iter.next();
 189         }
 190         if (resp != null) {
 191             return resp;
 192         }
 193         try {
 194             VersionHelper helper = VersionHelper.getVersionHelper();
 195             Class<?> clas = helper.loadClass(
 196                 "com.sun.jndi.ldap.ext.StartTlsResponseImpl");
 197 
 198             resp = (StartTlsResponse) clas.newInstance();
 199 
 200         } catch (IllegalAccessException e) {
 201             throw wrapException(e);
 202 
 203         } catch (InstantiationException e) {
 204             throw wrapException(e);
 205 
 206         } catch (ClassNotFoundException e) {
 207             throw wrapException(e);
 208         }
 209 
 210         return resp;
 211     }
 212 
 213     /*
 214      * Wrap an exception, thrown while attempting to load the StartTlsResponse
 215      * class, in a configuration exception.
 216      */
 217     private ConfigurationException wrapException(Exception e) {
 218         ConfigurationException ce = new ConfigurationException(
 219             "Cannot load implementation of javax.naming.ldap.StartTlsResponse");
 220 
 221         ce.setRootCause(e);
 222         return ce;
 223     }
 224 
 225     /*
 226      * Acquire the class loader associated with this thread.
 227      */
 228     private final ClassLoader getContextClassLoader() {
 229         return AccessController.doPrivileged(
 230             new PrivilegedAction<ClassLoader>() {
 231                 public ClassLoader run() {
 232                     return Thread.currentThread().getContextClassLoader();
 233                 }
 234             }
 235         );
 236     }
 237 
 238     private final static boolean privilegedHasNext(final Iterator<StartTlsResponse> iter) {
 239         Boolean answer = AccessController.doPrivileged(
 240             new PrivilegedAction<Boolean>() {
 241             public Boolean run() {
 242                 return Boolean.valueOf(iter.hasNext());
 243             }
 244         });
 245         return answer.booleanValue();
 246     }
 247 
 248     private static final long serialVersionUID = 4441679576360753397L;
 249 }