Java, LDAP, and Chained Self-Signed Certificates
I had a java / tomcat app (Artifactory) that was configured to use LDAP for user authentication, and I was asked to make it use LDAP over SSL with a self-signed certificate. That turned out to be not very well documented, so here are my notes on how to do it.
First of all, you need to understand how java applications use security certificates. There's a certificate store file (typically called
cacerts) that contains all the public ssl trusted root keys. That file is loaded by the java interpreter when it starts up and serves as the source of truth for which certificates to trust. If your application uses an 'officially' signed certificate, everything will automatically work.
However, you may need to use a self-signed certificate. In this case, java doesn't know about the authenticity of your certificate because it isn't a descendent of one of the official root certificates. Thus you need to manually add your server public key to a new
cacerts file and tell java to use that file instead of the default one.
Obtaining Public Certificates
In my case, I needed to make my tomcat application accept a self-signed certificate from my ldap server. The first step is to download the public certificate(s) from the ldap server (or whatever sort of server you are trying to use):
openssl s_client -connect 192.168.1.10:636 -showcerts < /dev/null > certs.txt
Next, examine the
certs.txt file. It will contain at least one certificate between
BEGIN CERTIFICATE and
END CERTIFICATE lines. It may as in my case contain two or more certificates. You need to put each of these certificates in separate files, preserving only the BEGIN and END lines and everything in between. All the other text in the certs.txt file can be discarded. I created two files,
Next, you need a copy of your existing java
cacerts file. As I mentioned, that file contains all the public ssl certificates that java trusts. In my case, that file was
/usr/lib/jvm/java-8-oracle/jre/lib/security/cacerts. I made a copy of that file in /tmp.
Use the keytool program to add the new certificates to the
cacerts file. You need to add every certificate that the server returned. Documentation on the internet is not clear on this point. Each certificate must be added with a different alias in the cacerts file. In my case:
$ for i in intermediate 192.168.1.10; do keytool -import -trustcacerts -file $i -keystore cacerts -alias $i
keytool will ask you for the cacerts file password each time it tries to add a certificate. The default file for the cacerts file is changeit. keytool will also ask you whether to trust each certificate as you add it. Choose 'yes'.
You can verify that your new certificates are part of the cacerts file by running
keytool -list -v -keystore cacerts |less.
You now have an updated cacerts files, next you need to tell java to use it. Place the file in an appropriate location (in my case /etc/opt/jfrog/artifactory/cacerts) and modify your startup JAVA_OPTIONS:
echo "export JAVA_OPTIONS=\"\$JAVA_OPTIONS -Djavax.net.ssl.trustStore=/etc/opt/jfrog/artifactory/cacerts\"" \ >> /etc/opt/jfrog/artifactory/default
this will obviously vary depending on what tomcat application you are using. Restart your java app and verify that the new cacerts file has been loaded by using
ps to check that the javax.net.ssl.trustStore option is set and points to your new cacerts file.
Now that java has your self-signed certificates, you need to modify your server configuration to use ssl. Again in my case I'm using Artifactory but other apps will need similar configuration changes. Navigate to the Artifactory web page and log on as an administrator. Go to
Admin->Security->LDAP and modify your ldap server to ldaps, i.e.
ldaps://192.168.1.10. Save your settings and use the 'Test LDAP Connection' box to confirm that user authentication is now working over SSL.
It can be useful to verify that java can make ssl connections without going through your tomcat application. To do this, use the java utility
SSLPoke, which you can download from Atlassian. To test that you can make ssl connections to an ldap server, use
$ /usr/bin/java -Djavax.net.ssl.trustStore=/etc/opt/jfrog/artifactory/cacerts SSLPoke 192.168.1.10 636
if that command succeeds, you know that your java is loading your self-signed certificates correctly.
You can also turn on ssl debugging by passing
-Djavax.net.debug=all in your java startup. That will print lots and lots of debug messages about ssl when your app tries to make ssl connections. In the case of Artifactory, those messages will be printed to
/etc/opt/jfrog/artifactory/logs/catalina/catalina.out. You can also check
/etc/opt/jfrog/artifactory/logs/artifactory.log for any other ldap error messages. If you see
Authentication failed. Probably a wrong manager dn or manager password: [LDAP: error code 49 - 80090308: LdapErr: nested exception is javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e
that means that the ldap server is not accepting your username/password. If you see
[Root exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPath BuilderException: unable to find valid certification path to requested target]
that means that java is not finding your self-signed certificates in the cacerts file.
Well, that's how you deal with self-signed certificates in java. It's a confusing process so I hope that these notes help others.