使用Java的SSLSocket
首先
让我们尝试使用Java的SSLSocket进行加密通信。顺便还可以验证客户端证书。
首先是服务器端。
准备证书
服务器端需要和用于Web服务器的证书相同的服务器证书、私钥和CA局证书。
将其打包成pkcs12格式。
如果使用客户端证书,则需要使用客户端证书的CA局证书。这个证书可以使用JKS格式而无需私钥。
尝试将证书放入pkcs12中似乎可以工作,但不知道为什么没有被识别。
读取服务器端证书
将服务器证书与私钥和CA证书一同保存在pkcs12文件中进行读取。
使用FileInputStream打开文件,然后使用java.security.KeyStore的load方法进行读取。
读取完毕后,将其指定给javax.net.ssl.KeyManagerFactory的init方法。
如果不使用客户端证书,则不需要TrustManager。
代码如下所示。
import java.net.Socket;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.FileInputStream;
public class App
{
private static void initSSL()
{
try (
FileInputStream p12_file = new FileInputStream("servercert.p12");
FileInputStream jks_file = new FileInputStream("clienttrust.jks");
) {
KeyManagerFactory kmf;
KeyStore ks;
ks = KeyStore.getInstance("pkcs12");
ks.load(p12_file, "passphrase".toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "passphrase".toCharArray());
// ここからはクライアント証明書のためのもの
TrustManagerFactory tmf;
KeyStore ts;
ts = KeyStore.getInstance("JKS");
ts.load(jks_file, "jkspass".toCharArray());
tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
}
catch (Exception e) {
System.out.println("initSSL exception: " + e.toString());
}
}
servercert.p12是一个包含服务器证书和私钥的pkcs12文件,其密码为passphrase,所以需要根据环境进行相应修改。
同样地,clienttrust.jks和jkspass也需要进行修改。
创建ServerSocket
使用上面生成的 kmf 和 tmf。(如果不使用客户端证书,则仅使用 kmf)
具体步骤是创建 java.net.ssl.SSLContext,并在初始化时指定从 kmf 和 tmf 中获取的密钥和证书信息。
然后使用 ctx.getServerSocketFactory() 获得 ServerSocketFactory,然后使用它创建 ServerSocket。
代码如下。
SSLContext ctx = SSLContext.getInstance("TLS");
// クライアント証明書を使用しない場合は tmf.getTrustManagers() のところをnullにする
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory ssf = ctx.getServerSocketFactory();
SSLServerSocket sslsvr = (SSLServerSocket)ssf.createServerSocket(443);
// クライアント証明書を使用しない場合は、以下不要
sslsvr.setNeedClientAuth(true);
除了使用常规的ServerSocket与客户端建立连接并获得Socket之外,其他步骤与常规相同。(可以将其转换为SSLSocket)
其中的443是端口号,如果要使用其他端口,需要进行更改。
如何创建PKCS12文件。
在使用openssl时可以创建服务器证书和客户端证书的pkcs12格式。(可能也可以使用keytool创建)
openssl pkcs12 -export -in <証明書ファイル名> -inkey <秘密鍵ファイル名> -certfile <CA局証明書ファイル名> -out <出力ファイル名>
执行这个操作时,需要设置密码,需要输入两次。
如何制作JKS文件
使用Java附带的keytool创建。
keytool import -file <クライアント証明書用CA局ファイル名> -alias clientca -keystore <出力ファイル名>
别名(alias)部分似乎是可选的,但当使用keytool时可能需要这个。
服务器程序的操作确认
在创建客户端之前,最好使用openssl进行操作验证。
按以下方式执行操作。
openssl s_client -connect <サーバホスト名>:<ポート> -cert <クライアント証明書ファイル名(PEM)> -key <秘密鍵ファイル名(PEM)>
另外,如果无法正常工作,您还可以在运行时的java选项中加上”-Djavax.net.debug=all”来输出多种信息,这可能会给您一些提示。在”all”的位置上,也可以指定为”ssl”,这样就可以指定ssl选项。
可指定的选项请参考此处。
关于TLS版本
在OpenJDK 17中,默认只启用TLS 1.2和1.3版本。可以使用SSLServerSocket.setEnabledProtocols进行指定。例如,如果按以下方式进行设置,则只接受TLS 1.3版本。
SSLServerSocket sslsvr = (SSLServerSocket)ssf.createServerSocket(443);
sslsvr.setEnabledProtocols(new String[] { "TLSv1.3" });
可以通过 getEnabledProtocols 方法来确认可用的协议。
使用 setEnabledCipherSuites 可以更改 Cipher Suites,使用 getEnabledCipherSuites 可以确认,可以先使用 getEnabledCipherSuites,然后从中删除不想使用的项,并将其指定给 setEnabledCipherSuites即可。