/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2008 Sun Microsystems, Inc. */ package org.opends.server.protocols.jmx; import static org.opends.server.loggers.debug.DebugLogger.*; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.DebugLogLevel; import java.io.IOException; import java.io.Serializable; import java.net.Socket; import java.rmi.server.RMIClientSocketFactory; import java.util.Map; // JSSE import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; /** * A <code>DirectoryRMIClientSocketFactory</code> instance is used by the * RMI runtime in order to obtain client sockets for RMI calls via SSL. * <p> * This class implements <code>RMIClientSocketFactory</code> over the * Secure Sockets Layer (SSL) or Transport Layer Security (TLS) protocols. * </p> */ public class DirectoryRMIClientSocketFactory implements RMIClientSocketFactory, Serializable { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The serial version identifier required to satisfy the compiler because * this class implements the <CODE>java.io.Serializable</CODE> interface. * This value was generated using the <CODE>serialver</CODE> command-line * utility included with the Java SDK. */ private static final long serialVersionUID = -6701450600497520362L; /** * the static thread-local connection environment used by the RMI * client to configure this factory. */ private static InheritableThreadLocal<Map> tlMapConnectionEnv = new InheritableThreadLocal<Map>(); /** * The static thread-local target server hostname used by the RMI * client to configure this factory. */ private static InheritableThreadLocal<String> tlStrServerHostname = new InheritableThreadLocal<String>(); /** * the connection mode. <code>true</code> indicates that the client * will be authenticated by its certificate (SSL protocol). * <code>false</code> indicates that we have to perform an lDAP * authentication */ private final boolean needClientCertificate; /** * the ssl socket factory (do not serialize). */ private transient SSLSocketFactory sslSocketFactory = null; /** * the host to connect to (do not serialize). */ private transient String serverHostname = null; /** * Constructs a new <code>DirectoryRMIClientSocketFactory</code>. * * @param wellknown * <code>true</code> for wellknown, <code>false</code> * otherwise */ public DirectoryRMIClientSocketFactory(boolean wellknown) { this.needClientCertificate = wellknown; // We don't force the initialization of the SSLSocketFactory // at construction time - because the RMI client socket factory is // created on the server side, where that initialization is a // priori // meaningless, unless both server and client run in the same JVM. // So contrarily to what we do for the server side, the // initialization // of the SSLSocketFactory will be delayed until the first time // createSocket() is called. } /** * Sets the thread-local connection environment. * * @param connectionEnv the new connection env */ public static void setConnectionEnv(Map connectionEnv) { tlMapConnectionEnv.set(connectionEnv); } /** * Returns the thread-local connection environment. * * @return Map the connection environment used by new connections */ public static Map getConnectionEnv() { return tlMapConnectionEnv.get(); } /** * Sets the thread-local target server hostname. * * @param serverHostname * the target server hostname */ public static void setServerHostname(String serverHostname) { tlStrServerHostname.set(serverHostname); } /** * Returns the thread-local target server hostname. * * @return String the target server hostname */ public static String getServerHostname() { return tlStrServerHostname.get(); } /** * Returns the connection mode as configured at construction time. * * @return boolean <code>true</code> for wellknown, * <code>false</code> otherwise */ public boolean getNeedClientCertificate() { return needClientCertificate; } /** * Creates an SSL socket configured with the right trust stores and the * right target host. * <p> * The keystore and truststore used come from client configuration and * depend on the connection mode specified at construction time. * <p> * The target host is the host specified except if a different host was * specified in the connection environment. * * @param host * the target host as known by the RMI stack * @param port * the target port number * @return an SSL socket * * @throws IOException * if an error occurs while configuring the socket */ public Socket createSocket(String host, int port) throws IOException { // // gets ssl socket factory SSLSocketFactory sslSocketFactory = getSSLSocketFactory(); String realhost = getRealServerHostname(host); final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( realhost, port); return sslSocket; } /** * Indicates whether some other object is "equal to" this one. * <p> * Because each RMI client might have its own configuration, we do not * try to optimize anything. Each RMI connector uses its own RMI client * socket factory. In other words, Directory RMI clients never share * the same client socket factory. * </p> * * @param obj * the reference object with which to compare * @return <code>true</code> if this object is the same as the obj * argument <code>false</code> otherwise */ public boolean equals(Object obj) { return super.equals(obj); } /** * Returns a hash code value for this * <code>DirectoryRMIClientSocketFactory</code>. * * @return a hash code value for this * <code>DirectoryRMIClientSocketFactory</code> */ public int hashCode() { return super.hashCode(); } /** * Returns the real server hostname to connect to. * * @param rmiHostname * the target hostname as known by RMI stack * @return String the real hostname to connect to * @throws IOException * if an error occurs */ private synchronized String getRealServerHostname(String rmiHostname) throws IOException { if (serverHostname == null) { // // does the client specify another host by // using thread-local static parameter ? serverHostname = getServerHostname(); // // if not found here, don't look for real host in static // anymore if (serverHostname == null) { serverHostname = ""; } } if (serverHostname.length() > 0) { return serverHostname; } else { return rmiHostname; } } /** * Returns the ssl socket factory used by this RMI client socket * factory. * * @return SSLSocketFactory the ssl socket factory * @throws IOException * if an error occurs */ private synchronized SSLSocketFactory getSSLSocketFactory() throws IOException { if (sslSocketFactory == null) { if (debugEnabled()) { TRACER.debugVerbose("sslSocketFactory is null, get a new one"); } // socket factory not yet initialized // initialize the trust Map connectionEnv = getConnectionEnv(); TrustManager[] tms = null; // // Check if a trust manager array was provided in the // connection // Env. If yes, use it for this SSL Connection if ((connectionEnv != null) && (connectionEnv .containsKey(JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY))) { try { tms = (TrustManager[]) connectionEnv .get(JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } tms = null; } if (tms == null) { // // Why? The value is not invalid ? // Too bad: we raised an exception throw new IOException("invalid type or null value for key [" + JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY + "] in connection environment : " + connectionEnv .get(JmxConnectionHandler.TRUST_MANAGER_ARRAY_KEY)); } } // now we have the array of trust Manager // we can initialize the ssl ctx SSLContext ctx = null; try { ctx = SSLContext.getInstance("TLSv1"); ctx.init(null, tms, null); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } throw new IOException("Unable to initialize SSL context : " + e.getMessage()); } // create the SSLSocket sslSocketFactory = ctx.getSocketFactory(); } return sslSocketFactory; } }