Monday, January 18, 2010

Connecting to a secure LDAP or Active Directory.

There are hundreds of blogs on how to connect to an AD/LDAP directory service, but there aren't a lot on how to connect to a secure one. Java's method for connecting to a secure directory services involves uses a keystore. If your requirements won't allow you to add the certifcate to your keystore then you'll need to dynamically create one using Java Code.

In my application I need authenticate against a user's directory service. It's a shared database setup and we need to be able to connect to hundreds of directory services. Because of this requirement it is impossible to manage the keystore on the app server and constantly add new certificates to them. You can dynamically use keystores; however, must customers don't know how to create them. Below are sudo instructions on how to create a tool that will allow you connect to a secure directory service when you are only given the certificate.



Let's get started...

You will need to implement your own SocketFactory and set it in the environment properties before you call "new InitialLdapContext".


environment.put("java.naming.ldap.factory.socket", "ldap.TestSocketFactory");

To create your "ldap.TestSocketFactory" you'll want to extend SocketFactory and implement the abstract methods. You'll also need to override the public static javax.net.SocketFactory getDefault() and have it return itself.

Example:
Public class TestSocketFactory extends SocketFactory{
private javax.net.SocketFactory _socketFactory;
public static javax.net.SocketFactory getDefault() {
return new TestSocketFactory();
}
public java.net.Socket createSocket() throws java.io.IOException {
return _socketFactory.createSocket();
}

public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
return _socketFactory.createSocket(s, i);
}

public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
return _socketFactory.createSocket(s, i, inetAddress, i1);
}

public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
return _socketFactory.createSocket(inetAddress, i);
}

public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
return _socketFactory.createSocket(inetAddress, i, inetAddress1, i1);
}
}

In your constructor you will need to do the following:

  • Initialize a new KeyStore and CertificateFactory objects
KeyStore store = KeyStore.getInstance("JKS");
store.load(null, "changeit".toCharArray());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
  • Load the Certificate
String fileName = You'll need to load this from a property, or theadLocalVariable.
X509Certificate cert = (X509Certificate) cf.generateCertificate(new FileInputStream(new File(fileName)));
  • Convert the certificate to a KeyStore
File tempFile = File.createTempFile("certKey", null);
OutputStream out = new FileOutputStream(tempFile);
store.store(out, "changeit".toCharArray());
store.setCertificateEntry("alias", cert);
out.close();
  • Load the KeyStore
InputStream in = new FileInputStream(tempFile);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(in, "changeit".toCharArray());
in.close();
  • Create the SocketFactory
TrustManagerFactory trustMnagerFactory = TrustManagerFactory.getInstance(SECURE_ALGORITHM);
trustMnagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(SECURE_ALGORITHM);
keyManagerFactory.init(keyStore, SSODelegate.CERTIFICATE_PASSWORD.toCharArray());
SSLContext sslc = SSLContext.getInstance(SECURE_PROTOCOL);
sslc.init(keyManagerFactory.getKeyManagers(), trustMnagerFactory.getTrustManagers(), new SecureRandom());
_socketFactory = sslc.getSocketFactory();
If you want to ignore validation on the certificate you can do the follow when you are creating the SocketFactory
sslc.init(keyManagerFactory.getKeyManagers(), getOverrideTrustManager(), new SecureRandom());

public static TrustManager[] getOverrideTrustManager() {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
return trustAllCerts;
}