/* * This file contains two classes: LdapTlsExample, which is a program that * uses the StartTLS LDAP extension, and MySocketFactory, which is used to * control attributes of the SSL session that will be used. */ import java.io.*; import java.net.*; import java.util.*; import javax.naming.ldap.*; import javax.net.ssl.*; import javax.naming.*; import javax.naming.directory.*; /** * This class extends SSLSocketFactory to allow SSL sockets to be created * with characteristics specific to our own needs. Because this class extends * the SSLSocketFactory abstract class, it must implement several methods * which in this program we don't expect ever to call. */ class MySocketFactory extends SSLSocketFactory { /** * Whether to use anonymous cipher suites */ boolean useAnonymous = false; boolean isTracing = false; public MySocketFactory(boolean isTracing, boolean useAnonymous) { this.useAnonymous = useAnonymous; this.isTracing = isTracing; } /** * Required implementation of SSLSocketFactory.createSocket which returns * an SSL socket layered above an existing socket. */ public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { LdapTlsExample.trace(isTracing, "MySocketFactory.createSocket() called"); try { // Get a reference to the default SSL Socket factory SSLSocketFactory ssf = (SSLSocketFactory)getDefault(); // Get the default SSL Socket factory to create an SSL socket // for us, passing in the arguments we've been given SSLSocket sslSock = (SSLSocket)ssf.createSocket(s, host, port, autoClose); // Before returning that to our caller, we have the opportunity // to change some of the SSL characteristics. Specifically here // the cipher suites we enable depend on whether the user wants // to use anonymous or not. String[] supportedSuites = sslSock.getSupportedCipherSuites(); // Make separate lists of which ciphers are anonymous and which // require certificates HashSet anonSet = new HashSet(); HashSet certSet = new HashSet(); for (int i=0; iTo keep the program simple, a minimal number of run-time options is * supported. Specifically, you can tell the program the name of the * server-node, and whether to enable anonymous cipher suites. Other options * that you might want to change require modifications to the code. These * include such things as: * *

Built using JDK1.4 and tested on Windows2000 * @author Nick Hudson * @version 1.0 */ public class LdapTlsExample { static Object syncObject = new Object(); /** * Report a message to System.out * @param doTrace true if you want the message to appear, false otherwise * @param message the message to be sent to System.out */ public static void trace(boolean doTrace, String message) { if (doTrace) { synchronized(syncObject) { System.out.println(message); } } } /** * Display usage information */ public static void usage() { trace(true,"\nUsage: java LdapTlsExample " + " [trace]"); trace(true," server : the server to talk to"); trace(true," port : port number to use"); trace(true," use-anon : types of cipher suites to propose :-"); trace(true," 'n' : non-anonymous only"); trace(true," 'y' : anonymous only"); trace(true," trace : (optional) tracing to enable:-"); trace(true," 'j' : turn on JSSE debug tracing"); trace(true," 't' : turn on program tracing\n"); trace(true, "For non-anonymous ciphers, you can specify the location of a"); trace(true,"trust store using a command such as:\n"); trace(true, " java -Djavax.net.ssl.trustStore=\"myfile.truststore\"" + " LdapTlsExample ..."); } /** * Main entry point for the program. * @param args [trace], where "use-anon" is * "y" or "n" to specify whether anonymous ciphers should be * proposed instead of non-anonymous ciphers, and "trace" is an * optional argument which specifies the level of run-time tracing, * where a 't' indicates program tracing, and a 'j' indicates JSSE * tracing. */ public static void main(String[] args) { String serverSpec = null; boolean enableAnonSuites = false; boolean isTracing = false; // Try and parse command line arguments. try { // The port-number is hard-coded to be 389. You can alter this serverSpec = "ldap://" + args[0] + ":389"; enableAnonSuites = (args[2].equalsIgnoreCase("y")); if (args.length > 2) { String traceOptions = args[3].toLowerCase(); isTracing = (traceOptions.indexOf("t") != -1); if (traceOptions.indexOf("j") != -1) { // Enable JSSE tracing trace(isTracing, "Enabling JSSE debug tracing"); System.setProperty("javax.net.debug","ssl"); } } } catch (Exception e) { trace(true,e.toString()); usage(); return; } try { // Create a SocketFactory that will be given to LDAP for // building SSL sockets MySocketFactory msf = new MySocketFactory(isTracing, enableAnonSuites); // Set up environment for creating initial context Hashtable env = new Hashtable(11); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // Must use the name of the server that is found in its certificate env.put(Context.PROVIDER_URL, serverSpec ); // Create initial context trace(isTracing,"Creating new Ldapcontext"); LdapContext ctx = new InitialLdapContext(env, null); // Start TLS trace(isTracing,"Performing StartTlsRequest"); StartTlsResponse tls = null; try { tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest()); } catch (NamingException e) { trace(true,"Unable to establish SSL connection:\n" +e); return; } // The default JSSE implementation will compare the hostname of // the server with the hostname in the server's certificate, and // will not proceed unless they match. To override this behaviour, // you have to provide your own HostNameVerifier object. The // example below simply bypasses the check tls.setHostnameVerifier(new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }); // Negotiate SSL on the connection using our own SocketFactory trace(isTracing,"Negotiating SSL"); SSLSession sess = tls.negotiate(msf); // ... do something useful with ctx that requires secure connection NamingEnumeration results = null; SearchControls ctrls = null; ctrls = new SearchControls(SearchControls.SUBTREE_SCOPE, 0, // maxresults 0, // timelimit null, // desiredattrs false,false); String baseDN = ""; trace(true,"Performing LDAP search.."); results = ctx.search(baseDN, // dn "objectclass=*", // filter ctrls); trace(true,"..Search complete"); while (results.hasMore()) { SearchResult sr = (SearchResult)results.next(); System.out.println("Entry : " + sr.getName()); Attributes ats = sr.getAttributes(); NamingEnumeration entryAttributes = ats.getAll(); while (entryAttributes.hasMore()) { Attribute oneAttribute = (Attribute)entryAttributes.next(); System.out.print(" " + oneAttribute.getID() + " : " ); NamingEnumeration attributeValues = oneAttribute.getAll(); boolean doneOneValue = false; while (attributeValues.hasMore()) { Object attrValue = attributeValues.next(); if (doneOneValue) { System.out.print(", "); } System.out.print(attrValue); doneOneValue = true; } System.out.println(); } System.out.println(); System.out.println(); } // Stop TLS trace(isTracing,"Closing TLS connection"); tls.close(); // Close the context when we're done trace(isTracing,"Closing LDAP connection"); ctx.close(); } catch (Exception e) { System.out.println("exception: " + e); e.printStackTrace(); } } }