SSL连接分为双向认证和单向认证。其中双向认证表示服务器和客户端都需要分别校验对方的身份。单向认证则只需要客户端校验服务器的身份。
SSL的双向认证的流程如下图:
从以上流程可见,要完成双向认证,服务器端和客户端都需要验证对方的证书,然后再进行加密的协商。这里基于JAVA来实现一个服务器端和客户端的程序,可以实现双向认证。
首先需要准备服务器和客户端的相关证书:
1. 创建自签名的根密钥
openssl genrsa -out rootkey.pem 2048
2. 生成根证书
openssl req -x509 -new -key rootkey.pem -out root.crt -subj="/C=CN/ST=GD/L=GZ/O=RootCA/OU=RootCA/CN=RootCA"
3. 生成客户端密钥
openssl genrsa -out clientkey.pem 2048
4. 生成客户端证书请求文件,使用根证书进行签发
openssl req -new -key clientkey.pem -out client.csr -subj="/C=CN/ST=GD/L=GZ/O=BMW/OU=Vehicle/CN=Vehicle1"
5. 用根证书来签发客户端请求文件,生成客户端证书client.crt
openssl x509 -req -in client.csr -CA root.crt -CAkey rootkey.pem -CAcreateserial -days 3650 -out client.crt
6. 打包客户端资料为pkcs12格式(client.pkcs12)
openssl pkcs12 -export -in client.crt -inkey clientkey.pem -out client.pkcs12
7. 生成服务器端的密匙
openssl genrsa -out serverkey.pem 2048
8. 生成服务器端证书的请求文件。请求根证书来签发
openssl req -new -key serverkey.pem -out server.csr -subj="/C=CN/ST=GD/L=GZ/O=BMW/OU=IT/CN=Broker"
9. 用根证书来签发服务器端请求文件,生成服务器端证书server.crt
openssl x509 -req -in server.csr -CA root.crt -CAkey rootkey.pem -CAcreateserial -days 3650 -out server.crt
10. 打包服务器端资料为pkcs12格式(server.pkcs12 )
openssl pkcs12 -export -in server.crt -inkey serverkey.pem -out server.pkcs12
11. 生成信任客户端的keystore,把根证书以及需要信任的客户端的证书添加到这个keystore
keytool -importcert -alias ca -file root.crt -keystore clienttrust.jks
keytool -importcert -alias clientcert -file client.crt -keystore clienttrust.jks
12. 生成信任服务器端的keystore,把根证书以及需要信任的服务端的证书添加到这个keystore
keytool -importcert -alias ca -file root.crt -keystore servertrust.jks
keytool -importcert -alias servercert -file server.crt -keystore servertrust.jks
服务器程序
以下是服务器的程序
public class SSLServer
{
private SSLServerSocket sslServerSocket;
public static void main( String[] args ) throws Exception
{
SSLServer server = new SSLserver();
server.init();
System.out.println( "Server initialted!" );
server.process();
}
public void init() throws Exception {
int port = 9999;
String keystorePath = "/home/roy/projects/cert/server.pkcs12";
String keystorePass = "123456";
SSLContext context = SSLContext.getInstance("TLSv1.2");
//加载服务器的证书和Private key
KeyStore serverKeyStore = KeyStore.getInstance("pkcs12");
FileInputStream keystoreFis = new FileInputStream(keystorePath);
serverKeyStore.load(keystoreFis, keystorePass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(serverKeyStore, keystorePass.toCharArray());
//加载要信任的客户端证书的keystore
String trustClientKeystorePath = "/home/roy/projects/cert/clienttrust.jks";
KeyStore trustKeyStore = KeyStore.getInstance("jks");
FileInputStream trustKeystoreFis = new FileInputStream(trustClientKeystorePath);
trustKeyStore.load(trustKeystoreFis, keystorePass.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
tmf.init(trustKeyStore);
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
sslServerSocket = (SSLServerSocket)context.getServerSocketFactory().createServerSocket(port);
sslServerSocket.setNeedClientAuth(true);
}
public void process() throws Exception {
String bye = "Bye!";
byte[] buffer = new byte[50];
while(true) {
Socket socket = sslServerSocket.accept();
InputStream in = socket.getInputStream();
in.read(buffer);
System.out.println("Received: " + new String(buffer));
OutputStream out = socket.getOutputStream();
out.write(bye.getBytes());
out.flush();
}
}
}
客户端程序
如以下代码:
public class SSLClient
{
private SSLSocket sslSocket;
public static void main( String[] args ) throws Exception
{
SSLClient client = new SSLClient();
client.init();
System.out.println( "Client initiated." );
client.process();
}
public void init() throws Exception {
String host = "127.0.0.1";
int port = 9999;
String keystorePath = "/home/roy/projects/cert/client.pkcs12";
String keystorePass = "123456";
SSLContext context = SSLContext.getInstance("TLSv1.2");
//加载客户端的证书和private key
KeyStore clientKeyStore = KeyStore.getInstance("pkcs12");
FileInputStream keystoreFis = new FileInputStream(keystorePath);
clientKeyStore.load(keystoreFis, keystorePass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(clientKeyStore, keystorePass.toCharArray());
//加载信任的服务器证书的keystore
String trustServerKeystorePath = "/home/roy/projects/cert/servertrust.jks";
KeyStore trustKeyStore = KeyStore.getInstance("jks");
FileInputStream trustKeystoreFis = new FileInputStream(trustServerKeystorePath);
trustKeyStore.load(trustKeystoreFis, keystorePass.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
tmf.init(trustKeyStore);
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
sslSocket = (SSLSocket)context.getSocketFactory().createSocket(host, port);
}
public void process() throws Exception {
String hello = "Hello World";
OutputStream out = sslSocket.getOutputStream();
out.write(hello.getBytes(), 0, hello.getBytes().length);
out.flush();
Thread.sleep(1000);
InputStream in = sslSocket.getInputStream();
byte [] buffer = new byte[50];
in.read(buffer);
System.out.println(new String(buffer));
}
}
之后分别运行服务器和客户端程序,可以见到SSL的双向认证通过,建立连接并成功收发信息。
如果要实现单向认证,那么客户端的代码不需要改动,服务器端的代码改动如下:
public class SSLServer
{
private SSLServerSocket sslServerSocket;
public static void main( String[] args ) throws Exception
{
SSLServer server = new SSLserver();
server.init();
System.out.println( "Server initialted!" );
server.process();
}
public void init() throws Exception {
int port = 9999;
String keystorePath = "/home/roy/projects/cert/server.pkcs12";
String keystorePass = "123456";
SSLContext context = SSLContext.getInstance("TLSv1.2");
//加载服务器的证书和Private key
KeyStore serverKeyStore = KeyStore.getInstance("pkcs12");
FileInputStream keystoreFis = new FileInputStream(keystorePath);
serverKeyStore.load(keystoreFis, keystorePass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(serverKeyStore, keystorePass.toCharArray());
context.init(kmf.getKeyManagers(), null, null);
sslServerSocket = (SSLServerSocket)context.getServerSocketFactory().createServerSocket(port);
sslServerSocket.setNeedClientAuth(false);
}
public void process() throws Exception {
String bye = "Bye!";
byte[] buffer = new byte[50];
while(true) {
Socket socket = sslServerSocket.accept();
InputStream in = socket.getInputStream();
in.read(buffer);
System.out.println("Received: " + new String(buffer));
OutputStream out = socket.getOutputStream();
out.write(bye.getBytes());
out.flush();
}
}
}