我正在用 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
您可以存储每个服务器证书并使用自定义 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();