I'm trying to get an application running on top of Tomcat 6 to connect to an LDAP server over SSL.
I imported certificate of the server to keystore using:
C:\Program Files\Java\jdk1.6.0_32\jre\lib\security>keytool -importcert -trustcacerts -file mycert -alias ca_alias -keystore "c:\Program Files\Java\jdk1.6.0_32\jre\lib\security\cacerts"
When I start Tomcat with SSL debugging turned on, according to logs Tomcat is using the correct certificate file:
trustStore is: C:\Program Files\Java\jdk1.6.0_32\jre\lib\security\cacerts
However, Tomcat does not add the cert I just imported - all other certs in the cacerts file are printed to the log - and connection fails:
handling exception: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Restarting Tomcat does not help. I have verified with keytool -list command that the new cert indeed exists on the file.
Why Tomcat keeps on ignoring my new cert?
EDIT:
Seems that the issue was caused by Windows 7 VirtualStore. Keytool created a new copy of the cacert file, and Tomcat used the original file.
Check to see whether there is a key with the same CN information but a different alias.
I have had similar problems before when I tried to import a newer version of a certificate but left the older version in the keystore. My Java programs would simply find the first matching CN key in the keystore (which was the old expired one) and try to use that, even though there was a newer one which also matched the CN.
Also ensure that the authenticating Root certificate (and Intermediate certificate if applicable) exist in the keystore. If you're authenticating against one of the major security providers such as Verisign or Globalsign, they will usually provide you with the root and intermediate certificates. If these certificates exist in the keystore already, ensure they are still in validity. You need to have all the certificates from your personal certificate all the way down the authentication chain to the root, existing in your keystore, so that it understands how to validate your credentials.
What you described is exactly what I´ve been getting when using cmd.exe
and a regular user although member of administrative group on a Windows Server. You have to start cmd.exe
in administration mode to apply the changes in to cacerts files. At least on the Win2k8 OS´s.
If you do not do this carets will show you in the keytool.exe -list
view the newly added certs but Tomcat won´t see them. Not sure why so. But when you do add it with cmd.exe started as Administrator Tomcat is fine with the newly added certs.
You can also use Djavax.net.debug="ssl,handshake"
to see what Tomcat reads from cacerts file.
In my case I looked through so many things before I figured out what was wrong... I added the certificate to different keystores, I added all certificates in the chain (which is pointless btw), I downloaded the cert again for my own sanity and checked the serial number, and even inspected the downloaded cert to make sure it had all the correct information.
I ended up writing a TLS verifying client app in order to debug the issue. Not only did the remote server I was connecting to support only TLS 1.2 (disabled by default in my version of Java 7), the server also supported none of the ciphers that were enabled in my client. It turns out Java 7 had fewer than half of its supported ciphers enabled, many of them being really insecure and some of the most secure ones were disabled.
After some cross-checking, I came up with the following ordered list of TLS 1.2-supported secure ciphers:
new String[] {
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
"TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_PSK_WITH_AES_256_GCM_SHA384",
"TLS_DHE_PSK_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CCM",
"TLS_DHE_RSA_WITH_AES_128_CCM",
"TLS_DHE_PSK_WITH_AES_256_CCM",
"TLS_DHE_PSK_WITH_AES_128_CCM",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_AES_128_CCM_SHA256"
}
If there are any crypto experts around, feel free to update this list. I used Qualys SSL Labs, this Information Security SE answer, and IANA as my sources.
For those who want a sample of the source code I used, see below. I was using Apache Commons HttpClient 3.0, so you'll probably need to download the following binaries:
- https://archive.apache.org/dist/httpcomponents/commons-httpclient/3.0/binary/commons-httpclient-3.0.1.zip
- https://archive.apache.org/dist/commons/logging/binaries/commons-logging-1.0.4.zip
- https://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.3.zip
- https://archive.apache.org/dist/commons/lang/binaries/commons-lang-2.6-bin.zip
TLS12SocketFactory.java
import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.*;
import org.apache.commons.lang.StringUtils;
public class TLS12SocketFactory implements SecureProtocolSocketFactory {
private final SecureProtocolSocketFactory base;
public TLS12SocketFactory()
{
this.base = (SecureProtocolSocketFactory)Protocol.getProtocol("https").getSocketFactory();
}
private Socket acceptOnlyTLS12(Socket socket)
{
if(socket instanceof javax.net.ssl.SSLSocket) {
final javax.net.ssl.SSLSocket s = (javax.net.ssl.SSLSocket)socket;
// Set TLS 1.2
s.setEnabledProtocols(new String[]{ "TLSv1.2" });
// Using recommended ciphers from https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#table-tls-parameters-4
List<String> recommended = new ArrayList(Arrays.asList(new String[]{ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CCM", "TLS_DHE_RSA_WITH_AES_128_CCM", "TLS_DHE_PSK_WITH_AES_256_CCM", "TLS_DHE_PSK_WITH_AES_128_CCM", "TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256", "TLS_AES_128_CCM_SHA256" }));
recommended.retainAll(Arrays.asList(s.getSupportedCipherSuites()));
if(recommended.size() == 0) {
System.err.println("No supported modern ciphers. Update crypto policy or install JCE Unlimited Strength Jurisdiction Policy files." + System.lineSeparator());
} else if(recommended.size() < 3) {
System.out.println("Few supported modern ciphers. It's recommended to update crypto policy or install JCE Unlimited Strength Jurisdiction Policy files." + System.lineSeparator());
}
s.setEnabledCipherSuites(recommended.toArray(new String[0]));
// Log matched cipher and cert
s.addHandshakeCompletedListener(new javax.net.ssl.HandshakeCompletedListener() {
@Override
public void handshakeCompleted(javax.net.ssl.HandshakeCompletedEvent hce) {
String print = s.getInetAddress().getHostName() + System.lineSeparator() + hce.getCipherSuite() + System.lineSeparator();
try {
for(java.security.cert.Certificate cert : hce.getPeerCertificates()) {
List<String> certStrings = Arrays.asList(cert.toString().split("\r?\n"));
for(int line = 0; line < certStrings.size(); line++) {
if(certStrings.get(line).startsWith("Certificate Extensions:")) {
print += System.lineSeparator() + StringUtils.join(certStrings.subList(2, line-1), System.lineSeparator()) + System.lineSeparator();
break;
}
}
}
} catch (javax.net.ssl.SSLPeerUnverifiedException ex) {
print += "Non-certificate based cipher used" + System.lineSeparator();
}
System.out.println(print);
}
});
}
return socket;
}
@Override
public Socket createSocket(String host, int port) throws IOException
{
return acceptOnlyTLS12(base.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException
{
return acceptOnlyTLS12(base.createSocket(host, port, localAddress, localPort));
}
@Override
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException
{
return acceptOnlyTLS12(base.createSocket(host, port, localAddress, localPort, params));
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException
{
return acceptOnlyTLS12(base.createSocket(socket, host, port, autoClose));
}
}
Main.java
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.HttpClientParams;
public class Main {
public static void main(String[] args) {
List<java.net.URI> locations = new ArrayList<>();
for(String arg : args) {
java.net.URI location = java.net.URI.create(arg);
if(location.isAbsolute() && location.getScheme().equals("https")) {
locations.add(location);
} else {
System.out.println("Skipping invalid URL: " + arg);
}
}
System.out.println("Connecting to URL's");
System.out.println();
System.out.println("-------------------------");
TLS12SocketFactory tls12factory = new TLS12SocketFactory();
Protocol.registerProtocol("httpss", new Protocol("httpss", tls12factory, 443));
for(java.net.URI location : locations) {
System.out.println();
try {
// Form request
String tls12url = location.toString().replaceFirst("^https:", "httpss:");
HttpMethod method = new HeadMethod(tls12url);
// Send request
HttpClientParams params = new HttpClientParams();
params.setParameter(HttpClientParams.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
new HttpClient(params).executeMethod(method);
method.setFollowRedirects(true);
// Print response
System.out.println(location.toString());
System.out.println(method.getStatusLine().toString());
} catch (javax.net.ssl.SSLHandshakeException ex) {
System.out.println("There was an error making a secure connection to " + location.getHost());
ex.printStackTrace(System.out);
} catch (HttpException ex) {
System.out.println("There was an error with the external webpage");
ex.printStackTrace(System.out);
} catch (Exception ex) {
System.out.println("Could not complete request");
ex.printStackTrace(System.out);
}
}
System.out.println();
System.out.println("-------------------------");
System.out.println();
try {
// Load supported SSL Ciphers
System.out.println("Supported ciphers");
System.out.println();
System.out.println("-------------------------");
System.out.println();
javax.net.ssl.SSLSocket socket = (javax.net.ssl.SSLSocket)tls12factory.createSocket("www.google.com", 443);
for(String cipher : socket.getSupportedCipherSuites()) {
System.out.println(cipher);
}
System.out.println();
System.out.println("-------------------------");
System.out.println();
// Load enabled SSL Ciphers
System.out.println("Enabled ciphers");
System.out.println();
System.out.println("-------------------------");
System.out.println();
for(String cipher : socket.getEnabledCipherSuites()) {
System.out.println(cipher);
}
System.out.println();
System.out.println("-------------------------");
System.out.println();
// Load the JDK's cacerts keystore file
String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
System.out.println("Loading keystore");
System.out.println(filename);
System.out.println();
System.out.println("-------------------------");
System.out.println();
FileInputStream is = new FileInputStream(filename);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "changeit";
keystore.load(is, password.toCharArray());
// This class retrieves the most-trusted CAs from the keystore
PKIXParameters params = new PKIXParameters(keystore);
// Get the set of trust anchors, which contain the most-trusted CA certificates
for (TrustAnchor ta : params.getTrustAnchors()) {
// Print certificate
System.out.println(ta.getTrustedCert());
}
} catch (CertificateException | KeyStoreException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | IOException ex) {
System.out.println("Could not load keystore");
ex.printStackTrace(System.out);
}
}
}