package com.idega.core.ldap.client.jndi; /** * JndiSocketFactory.java * */ import java.io.*; import java.net.*; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.security.KeyStore; // XXX 1.3 <-> 1.4 //import com.sun.net.ssl.*; // jdk 1.3 requires alljssl.jar in external import javax.net.ssl.*; // jdk 1.4 includes ssl in standard distro /** * <p>Socket factory for SSL jndi links that returns an SSL socket. * It incorporates a keystore, which must contain the certs used * to authenticate the client.</p> * * <p>This code is based on sample code made freely available by author * Spencer W. Thomas on his web site http://hubris.engin.umich.edu/java/ * On Wed 24 May, 2000.</p> * * <p><b>Warning</b></p> * * <p>This class relies heavily on an internal, single, static SSLSocketFactory. * multiple objects of this type in fact will use the same internal SSLSocketFactory. * (This is why a single static init() method sets up everything for the entire * class.) The reason for this structure is that JndiSocketFactory is dynmaically * invoked by the jndi connection, and we have no other chance to initialise the * object.</p> */ public class JndiSocketFactory extends SSLSocketFactory { /** * This is the (static) factory internally shared between * all JndiSocketFactory objects. */ private static SSLSocketFactory factory = null; /** * A single default object of this class. It is * initialised when first called for, and then reused * whenever called again. */ private static JndiSocketFactory default_factory = null; private static KeyStore clientKeystore; /** * The sun 'JKS' keystore is the simplest and most commonly * available keystore type. */ private static final String DEFAULT_KEYSTORE_TYPE = "JKS"; /** * The 'KSE' keystore is used by the Glen Iris group to * wrap openssl calls. It has some peculiarities requiring * special handling. */ private static final String PKI_INTERNAL_TYPE = "KSE"; /** * the class loader to use for loading security providers * 'n stuff. Defaults to the system loader. */ private static ClassLoader myClassLoader = null; /** * Register a custom class loader to be used by * the class when getting security providers. */ public static void setClassLoader(ClassLoader newLoader) { myClassLoader = newLoader; } /** * checks that myClassLoader is initialised, uses the System * default loader if it isn't, and returns the guaranteed * initialised loader. */ private static ClassLoader getClassLoader() { if (myClassLoader == null) { myClassLoader = ClassLoader.getSystemClassLoader(); } return myClassLoader; } /** * Enable debugging... */ public static void setDebugOn() { /* all turn on all debugging ssl turn on ssl debugging The following can be used with ssl: record enable per-record tracing handshake print each handshake message keygen print key generation data session print session activity handshake debugging can be widened with: data hex dump of each handshake message verbose verbose handshake message printing record debugging can be widened with: plaintext hex dump of record plaintext */ System.setProperty("javax.net.debug", "ssl handshake verbose"); } /** * <p>Initialize the socket factory with a particular key store(s) and * security provider. The minimum requirement is for a keystore * containing trusted directory servers (the 'castore', or trusted * certificate authority store, since the servers are usually signed * by a common CA, whose cert would be held in this file).</p> * * <p>Further options include a private key store (the 'clientstore') * that allows for client-authenticated ssl and SASL).</p> * * <p>Finally, it is possible to configure a non-standard keystore * type and security provider. The keystore type defaults to Sun's * JKS (at time of writting, the only keystore type that the default * Sun security provider will handle).</p> * * <p>Nb. - it is possible to set a custom class loader (using * 'registerClassLoader()' ) in which case this loader can be used * to load the security provider.</p> * * @param caKeystoreFile A keystore file name of public certificates (trusted CA signs) * @param clientKeystoreFile A keystore file name of the client's certificates, containing private keys. * (may be null if only simple, 'server authenticated' ssl is being used). * @param caPassphrase A password for the caKeystoreFile certificate. * (may be null if only simple, 'server authenticated' ssl is being used, and keystore type is 'JKS'). * <b>Calling Program must manually clear passphrase after init() call.</b> * @param clientPassphrase A password for the clientKeystoreFile certificate. * (may be null if only simple, 'server authenticated' ssl is being used). * <b>Calling Program must manually clear passphrase after init() call.</b> * @param caKeystoreType The type of cakeystore file. (null => 'JKS') * @param clientKeystoreType The type of clientkeystore file. (null => 'JKS') */ public static void init(String caKeystoreFile, String clientKeystoreFile, char[] caPassphrase, char[] clientPassphrase, String caKeystoreType, String clientKeystoreType) throws Exception { checkFileSanity(caKeystoreFile, clientKeystoreFile, clientPassphrase); // use the client store if there is no caKeystoreFile. if (caKeystoreFile == null) { caKeystoreFile = clientKeystoreFile; } SSLContext sslctx; /* XXX HERE BE DRAGONS * * ... attempting to make file compile and run under both 1.3 (using com.sun.net.ssl.*) and * javax.net.ssl.* (where they moved SSLContext in v1.4). By leaving it unspecified, we * hope that the compiler will pick up the right one at compile time. Probably means grief * if you run a 1.3 compiled version on 1.4 or vica-versa... */ // XXX 1.3 <-> 1.4 // XXX This is a STOOPID Idea, since we can't set the imports at run time, so it // shouldn't work, because it will get con-foos-ed about which version of "SSLContext" to load. // // TODO: could we tweak it by using fully qualified class names? Doesn't matter now I suppose, // so much other 1.4 stuff is in the code now... /* if ("1.4".compareTo(System.getProperty("java.version")) >= 0) { sslctx = SSLContext.getInstance("TLS"); // TLS for java 1.4 } else { sslctx = SSLContext.getInstance("SSLv3"); // "SSLv3" for java 1.3 ... } */ // ONLY SUPPORTS Java 1.4+ // evil undocumented feature - can change SSL protocol on command line // (needed for mainframe TOPSECRET folks who have crippled TLS implementatiuon and want to use SSLv3) String protocol = System.getProperty("sslversion", "TLS"); // TLS for java 1.4 if (!"TLS".equals(protocol)) { System.out.println("SECURITY: Using non-standard ssl version: '" + protocol + "'"); } //TODO: proper logging sslctx = SSLContext.getInstance(protocol); /* * The KeyManagerFactory manages the clients certificates *and* private keys, * which allow the client to authenticate itself to others. It is not required * for 'simple' SSL. */ KeyManagerFactory clientKeyManagerFactory = null; TrustManagerFactory caTrustManagerFactory; KeyStore caKeystore; if ((clientPassphrase!=null) && (clientPassphrase.length>0)) { /* * Special hack for Glen Iris PKI group, until the pki * keystore is fully functional. * * We make heavy use of reflection to allow code to * compile and run when the com.ca.pki... libraries * aren't available. */ if (PKI_INTERNAL_TYPE.equals(clientKeystoreType)) { try { Class c = getClassLoader().loadClass("com.ca.commons.security.openssl.ParsePkcs12"); if (c==null) { System.out.println("Snot"); return; } Constructor constructor = c.getConstructor(new Class[] {String.class, byte[].class}); // get password safely - rely on calling routine to clear clientPassphrase int size = clientPassphrase.length; byte[] password = new byte[size]; for (int i=0; i<size; i++) { password[i] = (byte) clientPassphrase[i]; } Object pkcs12Parser = constructor.newInstance(new Object[] {clientKeystoreFile, password}); Method getSunKeyStore = c.getMethod("getSunKeyStore", new Class[] {String.class} ); clientKeystore = (KeyStore) getSunKeyStore.invoke(pkcs12Parser, new Object[] {"MyFriend"}); for (int i=0; i<size; i++) { password[i] = 0; } } catch (Exception e) { System.err.println("unable to load pkcs12 parser (not in class path?)"); e.printStackTrace(); return; } } else { /* * Create a keystore to hold the client certificates and private keys. */ if (clientKeystoreType == null) { clientKeystoreType = DEFAULT_KEYSTORE_TYPE; } clientKeystore = KeyStore.getInstance(clientKeystoreType); // key manager key store /* * Load the keystore from the client keystore file using the client * keystore password. */ if (clientKeystoreFile != null) { clientKeystore.load(new FileInputStream(clientKeystoreFile), clientPassphrase); } } /* * Create a key manager using the default sun X509 key manager */ clientKeyManagerFactory = KeyManagerFactory.getInstance("SunX509"); /* * Initialise the client keystore manager with the just loaded keystore, * and the keystore password. */ clientKeyManagerFactory.init(clientKeystore, clientPassphrase); } /* * Initialise the list of key managers (may be null if the client keystore is not * being used). */ KeyManager[] keyManagers = null; if (clientKeyManagerFactory != null) { keyManagers = clientKeyManagerFactory.getKeyManagers(); } /* * Initialise the trusted server certificate keystore. */ if (caKeystoreType == null) { caKeystoreType = DEFAULT_KEYSTORE_TYPE; } caKeystore = KeyStore.getInstance(caKeystoreType); /* * Load the keys from the 'certificate authority' keystore (the trusted server keystore) file. */ if (caKeystoreFile != null) { // caPassword may be null for some keystores (e.g. a 'JKS' keystore), and it is not an error. //if (caPassphrase == null && DEFAULT_KEYSTORE_TYPE.equals(caKeystoreType) == false) // throw new Exception("Internal SSL Initialisation error: No password for non standard trusted server (CA) keystore."); caKeystore.load(new FileInputStream(caKeystoreFile), caPassphrase); } /* * Create a trust manager factory using the default java X509 certificate based trust manager. */ caTrustManagerFactory = TrustManagerFactory.getInstance("SunX509"); /* * Initialise the trust manager with the keystore containing the trusted server certs. */ caTrustManagerFactory.init(caKeystore); /* * Get the list of trust managers from the trust manager factory, to initialise the * ssl context with. */ TrustManager[] trustManagers = caTrustManagerFactory.getTrustManagers(); sslctx.init(keyManagers, trustManagers, null); factory = sslctx.getSocketFactory(); } /** * Checks that the files containing the keystores really exist. * Throws an exception (that can be bubbled through to the gui) * if they don't. This is much clearer than relying on the * Sun ssl stuff to meaningfully report back the error :-). */ private static void checkFileSanity(String caKeystoreFile, String clientKeystoreFile, char[] clientPassphrase) throws Exception { if (clientKeystoreFile == null && caKeystoreFile == null) { throw new Exception("SSL Initialisation error: No valid keystore files available."); } if (caKeystoreFile != null) { if (new File(caKeystoreFile).exists() == false) { throw new Exception("SSL Initialisation error: file '" + caKeystoreFile + "' does not exist."); } } if (clientKeystoreFile != null && clientPassphrase != null) { if (new File(clientKeystoreFile).exists() == false) { throw new Exception("SSL Initialisation error: file '" + clientKeystoreFile + "' does not exist."); } } } // DEBUG PRINT CODE /* KeyManager[] myKM = new KeyManager[keyManagers.length]; for (int i=0; i<keyManagers.length; i++) { myKM[i] = new MyX509KeyManager((X509KeyManager)keyManagers[i]); } TrustManager[] myTM = new TrustManager[trustManagers.length]; for (int i=0; i<trustManagers.length; i++) { myTM[i] = new MyX509TrustManager((X509TrustManager)trustManagers[i]); } System.out.println("Number of Keymanagers = " + myKM.length); if (myKM.length >=1) { KeyManager bloop = myKM[0]; if (bloop == null) System.out.println("Bloop is Null???!"); System.out.println("bloop is a " + bloop.getClass()); if (bloop instanceof X509KeyManager) { System.out.println("bloop is X509KeyManager!"); String[] clients = ((X509KeyManager)bloop).getClientAliases("SunX509", null); System.out.println("Num clients = " + clients.length); for (int i=0; i<clients.length; i++) System.out.println("client: " + i + " = " + clients[i]); } } System.out.println("Number of Trustmanagers = " + myTM.length); if (myTM.length >=1) { TrustManager bloop = myTM[0]; if (bloop == null) System.out.println("Bloop is Null???!"); System.out.println("bloop is a " + bloop.getClass()); if (bloop instanceof X509TrustManager) { System.out.println("bloop is X509TrustManager!"); ((X509TrustManager)bloop).getAcceptedIssuers(); } } */ /** * Constructor */ public JndiSocketFactory() {} /** * Return an instance of this class. * * @return An instance of JndiSocketFactory. */ public static SocketFactory getDefault() { synchronized(JndiSocketFactory.class) { if (default_factory == null) { default_factory = new JndiSocketFactory(); } } return default_factory; } public static KeyStore getClientKeyStore() { return clientKeystore; } /** * Return an SSLSocket (upcast to Socket) given host and port. * * @param host Name of the host to which the socket will be opened. * @param port Port to connect to. * @return An SSLSocket instance (as a Socket). * @throws IOException If the connection can't be established. * @throws UnknownHostException If the host is not known. */ public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return factory.createSocket(host, port); } /** * Return an SSLSocket (upcast to Socket) given host and port. * * @param host Address of the server host. * @param port Port to connect to. * @return An SSLSocket instance (as a Socket). * @throws IOException If the connection can't be established. * @throws UnknownHostException If the host is not known. */ public Socket createSocket(InetAddress host, int port) throws IOException, UnknownHostException { return factory.createSocket(host, port); } /** * Return an SSLSocket (upcast to Socket) given host and port. * The client is bound to the specified network address and port. * * @param host Address of the server host. * @param port Port to connect to. * @param client_host Address of this (client) host. * @param port Port to connect from. * @return An SSLSocket instance (as a Socket). * @throws IOException If the connection can't be established. * @throws UnknownHostException If the host is not known. */ public Socket createSocket(InetAddress host, int port, InetAddress client_host, int client_port) throws IOException, UnknownHostException { return factory.createSocket(host, port, client_host, client_port); } /** * Return an SSLSocket (upcast to Socket) given host and port. * The client is bound to the specified network address and port. * * @param host Address of the server host. * @param port Port to connect to. * @param client_host Address of this (client) host. * @param port Port to connect from. * @return An SSLSocket instance (as a Socket). * @throws IOException If the connection can't be established. * @throws UnknownHostException If the host is not known. */ public Socket createSocket(String host, int port, InetAddress client_host, int client_port) throws IOException, UnknownHostException { return factory.createSocket(host, port, client_host, client_port); } /** * Return an SSLSocket layered on top of the given Socket. */ public Socket createSocket(Socket socket, String host, int port, boolean autoclose) throws IOException, UnknownHostException { return factory.createSocket(socket, host, port, autoclose); } /** * Return default cipher suites. */ public String[] getDefaultCipherSuites() { return factory.getDefaultCipherSuites(); } /** * Return supported cipher suites. */ public String[] getSupportedCipherSuites() { return factory.getSupportedCipherSuites(); } }