在 Java 客户端中打开 SSLSocket 时,如何在运行时获取服务器的证书?

发布时间:2021-03-07 18:02

我正在用 Java 编写一个 Gemini 客户端。 Gemini spec 的两部分在这里是相关的:

<块引用>

服务器必须使用 TLS...

<块引用>

...强烈推荐的方法是实施轻量级的“TOFU”证书固定系统,将自签名证书视为一等公民。

按照此答案 How do I accept a self-signed certificate with a Java using SSLSocket 我可以使用自签名证书成功连接到 Gemini 服务器。

但是要实现“TOFU”(首次使用时信任)要求,我应该获取服务器的证书以验证它在后续请求中没有更改。 如何获得证书?

我的代码是:

// On startup
try{
   m_sslContext = SSLContext.getInstance( "TLS" );
   m_sslContext.init(
      null,
      new TrustManager[]{
         new X509TrustManager(){
            public X509Certificate[] getAcceptedIssuers(){
               return new X509Certificate[] {};
            }

            public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException
            {}

            public void checkServerTrusted( X509Certificate[] chain, String authType ) throws CertificateException
            {}
         }
      },
      null
   );
}
catch( NoSuchAlgorithmException | KeyManagementException x )
{
   // error handling
}

// On fetching a Gemini URL:
Socket socket = m_sslContext.getSocketFactory().createSocket( host, port );
// ...read and write the socket
回答1

您可以存储每个服务器证书并使用自定义 TrustManager 获取它。

一个示例设置是:

public class MyTrustManager implements X509TrustManager {

    private final Map<Integer, List<X509Certificate>> serverCertificates = new HashMap<>();
    private int counter = 0;

    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        serverCertificates.put(counter++, Arrays.asList(x509Certificates));
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }

    public Map<Integer, List<X509Certificate>> getServerCertificates() {
        return Collections.unmodifiableMap(serverCertificates);
    }

}

使用

SSLContext sslContext = SSLContext.getInstance( "TLS" );

MyTrustManager myTrustManager = new MyTrustManager();
sslContext.init(null, new TrustManager[]{myTrustManager}, null);

// execute some https requests...
// and get the cached server certificates
Map<Integer, List<X509Certificate>> serverCertificates = myTrustManager.getServerCertificates();