SSLSocket實現服務端和客戶端雙向認證的例子

Franson發表於2016-06-03

首先建立伺服器端私有金鑰和公共金鑰
1, keytool -genkey -alias serverkey -keystore kserver.ks
    密碼: serverpass
2, keytool -export -alias serverkey -keystore kserver.ks -file server.crt
3, keytool -import -alias serverkey -file server.crt -keystore tclient.ks
    密碼: clientpublicpass

下面建立客戶器端私有金鑰和公共金鑰
1, keytool -genkey -alias clientkey -keystore kclient.ks
    密碼: clientpass
2, keytool -export -alias clientkey -keystore kclient.ks -file client.crt
3, keytool -import -alias clientkey -file client.crt -keystore tserver.ks
    密碼: serverpublicpass

把伺服器端產生的公共金鑰放到客戶端, 同樣把客戶端建立的公共金鑰放到伺服器端.

下面是伺服器端程式碼:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.TrustManagerFactory;

public class SSLTestServer {

    public static void main(String[] args) throws Exception {
        SSLContext ctx = SSLContext.getInstance("SSL");

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

        KeyStore ks = KeyStore.getInstance("JKS");
        KeyStore tks = KeyStore.getInstance("JKS");

        ks.load(new FileInputStream("cert/kserver.ks"), "serverpass".toCharArray());
        tks.load(new FileInputStream("cert/tserver.ks"), "serverpublicpass".toCharArray());

        kmf.init(ks, "serverpass".toCharArray());
        tmf.init(tks);

        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        SSLServerSocket serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(8443);
        serverSocket.setNeedClientAuth(true);

        while (true) {
            try {
                Socket s = serverSocket.accept();
                InputStream input = s.getInputStream();
                OutputStream utput = s.getOutputStream();

                BufferedInputStream bis = new BufferedInputStream(input);
                BufferedOutputStream bos = new BufferedOutputStream(output);

                byte[] buffer = new byte[20];
                int length = bis.read(buffer);
                System.out.println("Receive: " + new String(buffer, 0, length).toString());

                bos.write("Hello".getBytes());
                bos.flush();

                s.close();
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }
}

下面是客戶端程式碼:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;

public class SSLTestClient {

    public static void main(String[] args) throws Exception {
        SSLContext ctx = SSLContext.getInstance("SSL");

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

        KeyStore ks = KeyStore.getInstance("JKS");
        KeyStore tks = KeyStore.getInstance("JKS");

        ks.load(new FileInputStream("cert/kclient.ks"), "clientpass".toCharArray());
        tks.load(new FileInputStream("cert/tclient.ks"), "clientpublicpass".toCharArray());

        kmf.init(ks, "clientpass".toCharArray());
        tmf.init(tks);

        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        SSLSocket sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket("localhost", 8443);
        InputStream input = sslSocket.getInputStream();
        OutputStream utput = sslSocket.getOutputStream();

        BufferedInputStream bis = new BufferedInputStream(input);
        BufferedOutputStream bos = new BufferedOutputStream(output);

        bos.write("Hello".getBytes());
        bos.flush();

        byte[] buffer = new byte[20];
        int length = bis.read(buffer);
        System.out.println(new String(buffer, 0, length));

        sslSocket.close();   
    }

}

常見的HTTPS傳輸, 不需要進行客戶端認證, 也就是單向認證. 這時也就不需要建立客戶端的私鑰和公鑰. 伺服器端也只要配置一下伺服器端的私鑰即可, 當客戶端瀏覽器訪問時會生成一個證照檔案,類似於上面建立的crt檔案. 如果需要程式訪問,可以通過這個crt檔案生成一個keystore. 然後是用這個keystore作為trust keystore即可.

 

目前我用keytool.exe生成的keystore和證照用於雙向認證是沒有問題的,但是使用ejbca系統生成的由某CA機構頒發的證照以及生成的keystore用來做雙向認證就會報錯。

javax.net.ssl.SSLHandshakeException: null cert chain

這個問題還需要進一步跟蹤解決。

相關文章