19

我们的系统中有一些代码可以自动将自签名证书生成到密钥库中,然后由 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。

4

1 回答 1

16

这确实最终由KeyManager(通常从 a 获得KeyManagerFactory)决定。

一个密钥库可以有多个以不同别名存储的证书。certAlias如果在 Jetty 配置中没有明确配置别名,则SunX509实现将选择它找到的第一个别名,其中有一个私钥和一个正确类型的密钥,用于所选密码套件(通常是 RSA,但在你的情况下可能是 DSA这里)。如果您查看Sun 提供程序的实现,选择逻辑还有更多内容,但您不应该真正依赖一般的顺序,而只依赖别名。

你当然可以给Jetty你自己的SSLContext,用你自己的X509KeyManager来选择别名。您必须实施:

 chooseServerAlias(String keyType, Principal[] issuers, Socket socket)

不幸的是,除了keyTypeand之外issuers,您做出决定的只是它socket本身。充其量,您在那里获得的有用信息是本地 IP 地址和远程 IP 地址。

除非您的服务器在同一端口上侦听多个 IP 地址,否则您将始终获得相同的本地 IP 地址。(在这里,显然,您至少有两个:127.0.0.1192.168.222.100,但我怀疑您对 localhost 并不真正感兴趣,除了您自己的测试。)您需要服务器端的服务器名称指示(SNI)支持才能使基于请求的主机名的决定(由支持它的客户端)。不幸的是,SNI 仅在 Java 7 中引入,但仅在客户端

您将在这里面临的另一个问题是Java 客户端会抱怨主题 DN 的 CN 中的 IP 地址。一些浏览器会容忍这种情况,但这不符合 HTTPS 规范 (RFC 2818)。IP 地址必须是 IP 地址类型的主题备用名称条目。

于 2012-05-03T08:53:41.277 回答