2

我正在通过 SSLSocket 连接到使用证书进行握手的远程主机。由于我们不使用所有证书颁发机构的默认 JVM 信任库,因此我需要将远程主机证书添加到我的信任库。

如何从 SSLSocket 获取我应该信任的证书?似乎确实要检索它们,我需要使用似乎需要握手的 SSLSession。为什么我们需要执行握手才能检索证书?

是否有任何工具可以提取所使用的远程主机证书?

4

2 回答 2

4

实际上,证书是在握手期间提供的,以便服务器可以识别自己,最终对客户端也是如此。

当你这样做时:

SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
[...]
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
try {
    socket.startHandshake();
    socket.close();
} catch (SSLException e) {
    e.printStackTrace(System.out);
}

如果您在 startHandshake() 上没有收到异常,则表示证书由于某种原因已经受信任(直接存在于密钥库中,由受信任的实体签名)。

是否发生异常,您可以访问下载的链:

X509Certificate[] chain = tm.chain;
if (chain == null) {
   // error in downloading certificate chain
   return;
}

// loop through chain
for (int i = 0; i < chain.length; i++) {
    X509Certificate cert = chain[i];
    [....]
}

使用 X509Certificate 对象实例,您实际上可以更新您的 k-ieth 密钥库:

X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
[...]
ks.setCertificateEntry(alias, cert);
OutputStream out = new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();

在此处查看完整示例。

或者,为您信任的服务器下载证书的另一种可能更安全的方法是使用 openssl 命令:

# openssl s_client -showcerts -connect $SERVER:$PORT  2>&1 | \
      sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >/tmp/$SERVERNAME.cert  

然后像往常一样使用keytool.

于 2012-09-24T11:13:29.417 回答
3

通常,您不应该从 中获取您应该信任的证书SSLSocket,相反,它应该是您独立获得的配置设置,作为您想要信任的参考。

您似乎想要做的是获取第一个连接的证书,希望该连接没有被拦截,然后将其用作后续连接的参考(类似于 SSH 通常做的事情,当你不必须在第一次连接时知道服务器密钥的指纹,但稍后检查是否得到相同的指纹)。

安全方面,这并不理想,因为初始连接可能会被 MITM 攻击者拦截(这会使所有后续连接易受攻击),但这无疑是一种降低风险的方法。理想情况下,您应该将该证书与您通过其他方式获得的已知参考进行比较。

您可以在握手期间使用自定义访问远程证书X509TrustManager(或者您可以使用它禁用信任验证并稍后获取证书),然后您可以使用它来初始化一个SSLContext,您可以从中获取您的SSLSocketFactory. 在信任管理器中禁用信任验证通常是一个坏主意(因为它打开了与 MITM 攻击的连接),但它可以用于此目的。您可能对该InstallCert实用程序感兴趣,它应该或多或少地完成您所追求的工作。

为什么我们需要在访问服务器证书之前进行握手?

这是在握手期间完成的,因为 SSL/TLS 套接字 API 的目的是为应用程序层提供一个它可以认为是安全的套接字,并且在该阶段或多或少地用作普通套接字。通常,对于 JSSE(或通常其他 SSL/TLS 堆栈)的大多数用途,作为使用该堆栈的应用程序开发人员,您不希望必须明确地进行验证。作为TLS 规范的一部分,还建议在握手期间检查证书:

收到服务器 hello done 消息后,如果需要,客户端应该验证服务器是否提供了有效的证书,并检查服务器 hello 参数是否可接受。

于 2012-09-24T10:58:04.967 回答