傳輸層安全訪問是通過身份驗證和加密傳輸的過程。JDK的JSSE提供了傳輸層安全訪問的實現。本文旨在通過一個完整的TLS通訊實例,辨析一個普遍的誤導。

網絡上停留在理論的簡單實例通常存在一個誤導。在雙向信任的情況下,雙方都需要信任對方的證書。這樣的例子很多,普遍拷貝并簡述過程。

其實,在實踐中發現,如果雙方都信任一個權威的CA,并持有該CA根證書和該CA簽發的證書,即可以信任對方,實現通訊,不需要對方證書。
這樣做的好處是,連接方發生變動后,如果新的接入方也是CA簽發認證的,即可認為可信。還有一個好處是,減少一個文件的部署(如果該通訊被重用在其他項目,或許這不是小事)。

〇 Scenario
ICM和UCGW是雙向信任的兩方,通過TLS通訊。CA是內部公信簽證機構(注:如果你的case不具備公信的CA,需要對CA的權威做認證,這超出了本文范圍)。

一 Certificate
簽證流程:
0.CA自簽證書作為其他設備的根證書
1.ICM和UCGW(兩方流程一致,以下簡稱ICM)自簽證書
2.向CA發送簽發請求
這一步可以是發送一個CSR將公鑰信息傳遞給CA
本例是將證書直接發給CA
3.CA為請求者簽發證書
4.CA發送根證書和CA簽發的證書給請求者
5.ICM將CA根證書導入信任列表
6.ICM將CA簽發的證書替換自己簽發的證書

實現代碼:
0.CA自簽證書作為其他設備的根證書
CertInfo certInfo = new CertInfo();
SelfSign selfs 
= new SelfSign();
certInfo.setKeystore(CA_KEYSTORE);
certInfo.setAlias(CA_ALIAS);
certInfo.setCommonName(
"mars_ca");
selfs.sign(certInfo, CA_CER);

keytool -genkey -dname "CN=mars_ca, OU=rv, O=rcd, L=ZB, ST=bj, C=China" -alias root -keyalg RSA -keystore ca--ca.keystore -keypass 111111 -storepass 111111 -validity 60
keytool -export -alias root -keystore ca--ca.keystore -storepass 111111 -rfc -file ca--ca.cer
Certificate stored in file <ca--ca.cer>

keytool -list -keystore ca--ca.keystore -storepass 111111

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

root, May 24, 2011, PrivateKeyEntry,
Certificate fingerprint (MD5): 24:48:A3:4D:9F:EE:39:DE:8C:E7:51:60:7E:94:7A:76

1.ICM和UCGW(兩方流程一致,以下簡稱ICM)自簽證書
// CertInfo certInfo = new CertInfo();
// SelfSign selfs = new SelfSign();
certInfo.setKeystore(ICM_KEYSTORE);
certInfo.setAlias(ICM_ALIAS);
certInfo.setCommonName(
"mars_icm");
selfs.sign(certInfo, ICM_CER);

// SelfSign selfs = new SelfSign();
// CertInfo certInfo = new CertInfo();
certInfo.setKeystore(UCGW_KEYSTORE);
certInfo.setAlias(UCGW_ALIAS);
certInfo.setCommonName(
"mars_UCGW");
selfs.sign(certInfo, UCGW_CER);

keytool -genkey -dname "CN=mars_icm, OU=rv, O=rcd, L=ZB, ST=bj, C=China" -alias icm -keyalg RSA -keystore iview.keystore -keypass 111111 -storepass 111111 -validity 60
keytool -export -alias icm -keystore iview.keystore -storepass 111111 -rfc -file icm--icm.cer
Certificate stored in file <icm--icm.cer>

keytool -genkey -dname "CN=mars_UCGW, OU=rv, O=rcd, L=ZB, ST=bj, C=China" -alias ucgw -keyalg RSA -keystore ucgw.keystore -keypass 111111 -storepass 111111 -validity 60
keytool -export -alias ucgw -keystore ucgw.keystore -storepass 111111 -rfc -file ucgw--ucgw.cer
Certificate stored in file <ucgw--ucgw.cer>

keytool -export -alias ca_signed -keystore ca--ca_sign.keystore -storepass 111111 -rfc -file ca--icm.signed.cer
Certificate stored in file <ca--icm.signed.cer>

keytool -export -alias ca_signed -keystore ca--ca_sign.keystore -storepass 111111 -rfc -file ca--ucgw.signed.cer
Certificate stored in file <ca--ucgw.signed.cer>

keytool -list -keystore iview.keystore -storepass 111111

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 
1 entry

icm, May 242011, PrivateKeyEntry,
Certificate fingerprint (MD5): 
78:5C:AA:1B:27:9D:FB:3E:BE:1A:BD:6E:C5:A5:25:BD

keytool 
-list -keystore ucgw.keystore -storepass 111111

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 
1 entry

ucgw, May 242011, PrivateKeyEntry,
Certificate fingerprint (MD5): 7B:
88:44:27:88:66:2D:6B:64:E3:D5:34:4A:03:DA:8F

2.向CA發送簽發請求
3.CA為請求者簽發證書
CASign cas = new CASign();
cas.sign(ICM_CER, ICM_SIGN_CER, CASIGN_KEYSTORE);
cas.sign(UCGW_CER, UCGW_SIGN_CER, CASIGN_KEYSTORE);

5.ICM將CA根證書導入信任列表
6.ICM將CA簽發的證書替換自己簽發的證書
CertImport im = new CertImport();
im.importCA(ICM_KEYSTORE);
im.importSign(ICM_ALIAS, ICM_SIGN_CER, ICM_KEYSTORE);

im.importCA(UCGW_KEYSTORE);
im.importSign(UCGW_ALIAS, UCGW_SIGN_CER, UCGW_KEYSTORE);

keytool -importcert -noprompt -alias root -file ca--ca.cer -keystore iview.keystore -storepass 111111
Certificate was added to keystore

keytool -importcert -noprompt -trustcacerts -alias icm -file ca--icm.signed.cer -keystore iview.keystore -storepass 111111 -keypass 111111
Certificate reply was installed in keystore

keytool -importcert -noprompt -alias root -file ca--ca.cer -keystore ucgw.keystore -storepass 111111
Certificate was added to keystore

keytool -importcert -noprompt -trustcacerts -alias ucgw -file ca--ucgw.signed.cer -keystore ucgw.keystore -storepass 111111 -keypass 111111
Certificate reply was installed in keystore

keytool -list -keystore iview.keystore -storepass 111111

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 
2 entries

root, May 242011, trustedCertEntry,
Certificate fingerprint (MD5): 
24:48:A3:4D:9F:EE:39:DE:8C:E7:51:60:7E:94:7A:76
icm, May 242011, PrivateKeyEntry, 
Certificate fingerprint (MD5): 
18:D9:40:BD:65:6C:4D:B9:F3:87:2B:09:63:CD:F0:7A

keytool 
-list -keystore ucgw.keystore -storepass 111111

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 
2 entries

root, May 242011, trustedCertEntry,
Certificate fingerprint (MD5): 
24:48:A3:4D:9F:EE:39:DE:8C:E7:51:60:7E:94:7A:76
ucgw, May 242011, PrivateKeyEntry, 
Certificate fingerprint (MD5): 2A:3D:3F:A6:E3:2F:
36:B9:71:CD:AB:1D:9F:19:8A:49

二 TLS
這里以ICM作為服務器端,UCGW作為客戶端。
方式一:加載keystore到環境變量,啟用默認工廠SSL-Server-Socket-Factory
public class SSLServer {
    
public static void main(String args[]) throws Exception {
        System.setProperty(
"javax.net.ssl.keyStore", TLSParameter.ICM_KEYSTORE);
        System.setProperty(
"javax.net.ssl.keyStorePassword", TLSParameter.S_KEY_PASS);
        
        SSLServerSocketFactory ssf 
= (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        ServerSocket ss 
= ssf.createServerSocket(TLSParameter.SSLPORT);
        System.out.println(
"SSL Server is started.");
        
while (true) {
            Socket s 
= ss.accept();
            PrintStream out 
= new PrintStream(s.getOutputStream());
            out.println(
"ICM say Hello to UCGW!");
            out.close();
            s.close();
        }
    }
}

public class SSLClient {

    
public static void main(String args[]) throws Exception {
        System.setProperty(
"javax.net.ssl.trustStore", TLSParameter.UCGW_KEYSTORE);
        SSLSocketFactory ssf 
= (SSLSocketFactory) SSLSocketFactory.getDefault();
        Socket s 
= ssf.createSocket(TLSParameter.IP, TLSParameter.SSLPORT);
        BufferedReader in 
= new BufferedReader(new InputStreamReader(s.getInputStream()));
        String x 
= in.readLine();
        System.out.println(x);
        in.close();
    }
}

方式二:設置SSLContext,通過上下文實例啟用SSL-Server-Socket-Factory
public class SSLServer1 {
    
public static void main(String args[]) throws Exception {
        SSLContext sslContext 
= SSLContext.getInstance(PROTOCOL);
        KeyManager[] km 
= TLSSocket.createKeyManagers(keyStoreType, ICM_KEYSTORE);
        TrustManager[] tm 
= TLSSocket.createTrustManagers(keyStoreType, ICM_KEYSTORE);
        SecureRandom random 
= SecureRandom.getInstance(SHA1PRNG);
        sslContext.init(km, tm, random);
        
        SSLServerSocketFactory factory 
= sslContext.getServerSocketFactory();        
        ServerSocket ss 
= (SSLServerSocket) factory.createServerSocket(SSLPORT);
        System.out.println(
"SSL Server is started.");
        
while (true) {
            Socket s 
= ss.accept();
            PrintStream out 
= new PrintStream(s.getOutputStream());
            out.println(
"ICM say Hello to UCGW!");
            out.close();
            s.close();
        }
    }
}

public class SSLClient1 {

    
public static void main(String args[]) throws Exception {
        SSLContext sslContext 
= SSLContext.getInstance(PROTOCOL);
        KeyManager[] km 
= TLSSocket.createKeyManagers(keyStoreType, UCGW_KEYSTORE);
        TrustManager[] tm 
= TLSSocket.createTrustManagers(keyStoreType, UCGW_KEYSTORE);
        SecureRandom random 
= SecureRandom.getInstance(SHA1PRNG);
        sslContext.init(km, tm, random);
        SSLSocketFactory factory 
= sslContext.getSocketFactory();

        System.out.println(
"TLS Client, Connecting to server " + IP + "" + SSLPORT);
        SSLSocket socket 
= (SSLSocket) factory.createSocket(IP, SSLPORT);
        socket.setUseClientMode(
true);

        BufferedReader in 
= new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String x 
= in.readLine();
        System.out.println(x);
        in.close();
    }
}


三 Summary
ICM和UCGW共同信任CA,各自持有CA根證書和CA簽發的自身證書,通過TLS協議實現傳輸層的安全通訊。
雙向信任的兩方無需持有對方證書。

四 EasterEgg
運行結果:
服務器端:
SSL Server is started.
client端:
ICM say Hello to UCGW!

網絡包分析(工具:Wireshark)

1.抓取本地通信:route add 192.168.225.166 mask 255.255.255.255 192.168.225.254 metric 1
Connection-specific DNS Suffix  . :
IP Address. . . . . . . . . . . . : 192.168.225.166
Subnet Mask . . . . . . . . . . . : 255.255.255.0
IP Address. . . . . . . . . . . . : fe80::225:64ff:feb3:88dc%4
Default Gateway . . . . . . . . . : 192.168.225.254

2.設置過濾器
Filter:
ip.addr==192.168.225.166

Decode As:
transport = SSL

Filter:
ip.addr==192.168.225.166&&ssl

3.結果和分析

測試完畢 請刪除本地IP:(reference: http://hi.baidu.com/btb368/blog/item/098d36acfbc837014b36d6cd.html)
route delete 192.168.225.166
Export File:
No.     Time        Source                Destination           Protocol Info
    
217 12.022322   192.168.225.166       192.168.225.166       SSLv2    Client Hello
Transmission Control Protocol, 
Src Port: prp (2091), Dst Port: 9527 (9527), Seq: 1, Ack: 1, Len: 103
Secure Socket Layer
    SSLv2 Record Layer: Client Hello

No.     Time        Source                Destination           Protocol Info
    
219 12.024335   192.168.225.166       192.168.225.166       TLSv1    Server Hello, Certificate, Server Hello Done
Transmission Control Protocol, 
Src Port: 9527 (9527), Dst Port: prp (2091), Seq: 1, Ack: 104, Len: 1210
Secure Socket Layer
    TLSv1 Record Layer: Handshake Protocol: Multiple Handshake Messages

No.     Time        Source                Destination           Protocol Info
    
221 12.036842   192.168.225.166       192.168.225.166       TLSv1    Client Key Exchange
Transmission Control Protocol, 
Src Port: prp (2091), Dst Port: 9527 (9527), Seq: 104, Ack: 1211, Len: 139
Secure Socket Layer
    TLSv1 Record Layer: Handshake Protocol: Client Key Exchange
 
No.     Time        Source                Destination           Protocol Info
    
225 12.161312   192.168.225.166       192.168.225.166       TLSv1    Change Cipher Spec, Encrypted Handshake Message
Transmission Control Protocol, 
Src Port: prp (2091), Dst Port: 9527 (9527), Seq: 243, Ack: 1211, Len: 43
Secure Socket Layer
    TLSv1 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
    TLSv1 Record Layer: Handshake Protocol: Encrypted Handshake Message

No.     Time        Source                Destination           Protocol Info
    
227 12.165380   192.168.225.166       192.168.225.166       TLSv1    Change Cipher Spec
Transmission Control Protocol, 
Src Port: 9527 (9527), Dst Port: prp (2091), Seq: 1211, Ack: 286, Len: 6
Secure Socket Layer
    TLSv1 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec

No.     Time        Source                Destination           Protocol Info
    
229 12.165965   192.168.225.166       192.168.225.166       TLSv1    Encrypted Handshake Message, Application Data, Application Data, Encrypted Alert
Transmission Control Protocol, 
Src Port: 9527 (9527), Dst Port: prp (2091), Seq: 1217, Ack: 286, Len: 126
Secure Socket Layer
    TLSv1 Record Layer: Handshake Protocol: Encrypted Handshake Message
    TLSv1 Record Layer: Application Data Protocol: Application Data
    TLSv1 Record Layer: Application Data Protocol: Application Data
    TLSv1 Record Layer: Encrypted Alert

No.     Time        Source                Destination           Protocol Info
    
233 12.166930   192.168.225.166       192.168.225.166       TLSv1    Encrypted Alert
Transmission Control Protocol, 
Src Port: prp (2091), Dst Port: 9527 (9527), Seq: 286, Ack: 1344, Len: 23
Secure Socket Layer
    TLSv1 Record Layer: Encrypted Alert