我们的系统中有一些代码可以自动将自签名证书生成到密钥库中,然后由 Jetty 使用。如果给定主机的密钥已经存在,则什么也不会发生,但如果它不存在,我们会生成一个新密钥,如下所示:
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);
}
只要密钥库中只有一个密钥和证书,这一切都可以正常工作。一旦你有多个密钥,当你尝试连接时就会发生奇怪的事情:
java.io.IOException: HTTPS hostname wrong: should be <127.0.0.1>
这是一个相当神秘的错误,但我最终设法通过编写一个连接到服务器并断言证书上的 CN 与主机名匹配的单元测试来追踪它。我发现很有趣——Jetty 似乎可以任意选择向客户展示的证书,但方式一致。
例如:
- 如果“CN=localhost”和“CN=cheese.mydomain”在密钥库中,它总是选择“CN=cheese.mydomain”。
- 如果“CN=127.0.0.1”和“CN=cheese.mydomain”在密钥库中,它总是选择“CN=cheese.mydomain”。
- 如果“CN=192.168.222.100”(cheese.mydomain)和“CN=cheese.mydomain”在密钥库中,它总是选择“CN=192.168.222.100”。
我编写了一些代码,循环遍历商店中的证书以将它们打印出来,并发现它并没有始终如一地选择第一个证书或类似的任何微不足道的东西。
那么它究竟使用了什么标准呢?最初我认为 localhost 很特别,但是第三个例子完全让我感到困惑。
我认为这是由 KeyManagerFactory 决定的,在我的例子中是 SunX509。