There is some code in our system for automatically generating self-signed certificates into a key store which is then used by Jetty. If a key for a given host already exists then nothing happens but if it doesn't exist, we generate a new key, like this:
public void generateKey(String commonName) {
X500Name x500Name = new X500Name("CN=" + commonName);
CertAndKeyGen keyPair = new CertAndKeyGen("DSA", "SHA1withDSA");
keyPair.generate(1024);
PrivateKey privateKey = keyPair.getPrivateKey();
X509Certificate certificate = keyPair.getSelfCertificate(x500Name, 20*365*24*60*60);
Certificate[] chain = { certificate };
keyStore.setEntry(commonName, privateKey, "secret".toCharArray(), chain);
}
This all works fine as long as there is only one key and certificate in the key store. Once you have multiple keys, weird things happen when you try to connect:
java.io.IOException: HTTPS hostname wrong: should be <127.0.0.1>
This was quite a mystifying error but I finally managed to track it down by writing a unit test which connects to the server and asserts that the CN on the certificate matches the hostname. What I found was quite interesting - Jetty seems to arbitrarily choose which certificate to present to the client, but in a consistent fashion.
For instance:
- If "CN=localhost" and "CN=cheese.mydomain" are in the key store, it always chose "CN=cheese.mydomain".
- If "CN=127.0.0.1" and "CN=cheese.mydomain" are in the key store, it always chose "CN=cheese.mydomain".
- If "CN=192.168.222.100" (cheese.mydomain) and "CN=cheese.mydomain" are in the key store, it always chose "CN=192.168.222.100".
I wrote some code which loops through the certificates in the store to print them out and found that it isn't consistently choosing the first certificate or anything trivial like that.
So exactly what criteria does it use? Initially I thought that localhost was special but then the third example baffled me completely.
I take it that this is somehow decided by the KeyManagerFactory, which is SunX509 in my case.