import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
public class SSLClient {
private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";
public static void main(String[] args) throws Exception {
// Set the key store to use for validating the server cert.
System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
System.setProperty("javax.net.debug", "ssl,handshake");
SSLClient client = new SSLClient();
Socket s = client.clientWithoutCert();
PrintWriter writer = new PrintWriter(s.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(s
.getInputStream()));
writer.println("hello");
writer.flush();
System.out.println(reader.readLine());
s.close();
}
private Socket clientWithoutCert() throws Exception {
SocketFactory sf = SSLSocketFactory.getDefault();
Socket s = sf.createSocket("localhost", 8443);
return s;
}
}
可以看到,除了把一些类变成SSL通信类以外,客户端也多出了使用信任证书仓库的代码。以上,我们便完成了SSL单向握手通信。即:客户端验证服务端的证书,服务端不认证客户端的证书。
以上便是Java环境下SSL单向握手的全过程。因为我们在客户端设置了日志输出级别为DEBUG:
Java代码
1.System.setProperty("javax.net.debug", "ssl,handshake");
System.setProperty("javax.net.debug", "ssl,handshake");
因此我们可以看到SSL通信的全过程,这些日志可以帮助我们更具体地了解通过SSL协议建立网络连接时的全过程。
结合日志,我们来看一下SSL双向认证的全过程:
第一步: 客户端发送ClientHello消息,发起SSL连接请求,告诉服务器自己支持的SSL选项(加密方式等)。
Bash代码
1.*** ClientHello, TLSv1
*** ClientHello, TLSv1
第二步: 服务器响应请求,回复ServerHello消息,和客户端确认SSL加密方式:
Bash代码
1.*** ServerHello, TLSv1
*** ServerHello, TLSv1
第三步: 服务端向客户端发布自己的公钥。
第四步: 客户端与服务端的协通沟通完毕,服务端发送ServerHelloDone消息:
Bash代码
1.*** ServerHelloDone
*** ServerHelloDone
第五步: 客户端使用服务端给予的公钥,创建会话用密钥(SSL证书认证完成后,为了提高性能,所有的信息交互就可能会使用对称加密算法),并通过ClientKeyExchange消息发给服务器:
Bash代码
1.*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
第六步: 客户端通知服务器改变加密算法,通过ChangeCipherSpec消息发给服务端:
Bash代码
1.main, WRITE: TLSv1 Change Cipher Spec, length = 1
main, WRITE: TLSv1 Change Cipher Spec, length = 1
第七步: 客户端发送Finished消息,告知服务器请检查加密算法的变更请求:
Bash代码
1.*** Finished
*** Finished
第八步:服务端确认算法变更,返回ChangeCipherSpec消息
Bash代码
1.main, READ: TLSv1 Change Cipher Spec, length = 1
main, READ: TLSv1 Change Cipher Spec, length = 1
第九步:服务端发送Finished消息,加密算法生效:
Bash代码
1.*** Finished
*** Finished
那么如何让服务端也认证客户端的身份,即双向握手呢?其实很简单,在服务端代码中,把这一行:
Java代码
1.((SSLServerSocket) _socket).setNeedClientAuth(false);
((SSLServerSocket) _socket).setNeedClientAuth(false);
改成:
Java代码
1.((SSLServerSocket) _socket).setNeedClientAuth(true);
((SSLServerSocket) _socket).setNeedClientAuth(true);
就可以了。但是,同样的道理,现在服务端并没有信任客户端的证书,因为客户端的证书也是自己生成的。所以,对于服务端,需要做同样的工作:把客户端的证书导出来,并导入到服务端的证书仓库:
Bash代码
1.keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer
2.Enter keystore password: client
3.Certificate stored in file
keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer
Enter keystore password: client
Certificate stored in file
Bash代码
1.keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks
2.Enter keystore password: server
3.Owner