本文需要讀者對mina和SSl原理有一定的了解,所以本文中對mina和SSL的原理,不做詳細的介紹。
TSL/SSL雙向認證連接:Server端和Client端通信,需要進行授權和身份的驗證,即Client只能接受Server的消息,Server只能接受Client的消息。這樣就可以在客戶機和服務器之間通過TCP/IP協(xié)議安全地傳輸數(shù)據(jù)。
在mina中實現(xiàn)TSL/SSL雙向認證連接,本人目前所知有三種方式:
1.Server端和Client端各自擁有自簽名的私有密鑰證書,重寫的 javax.net.ssl.X509TrustManager接口中的三個方法實現(xiàn)Server端和Client端信認證書。
2.Server端和Client端各自擁有自簽名的私有密鑰證書,并且互相交換公鑰,通過對方公鑰互相信認對方證書。
3.Server端和Client端各自擁有可信認的第三方認證機構(CA)簽名私有密鑰證書,通過CA互相信認對方證書。
以上三種方式,實現(xiàn)復雜度從低到高,靈活度安全性也從低到高。本系列文章將將會從簡到難分別介紹三種方式的TSL/SSL雙向認證連接。
下面我們介紹第一種實現(xiàn)方式:Server端和Client端各自擁有自簽名的私有密鑰證書,重寫的 javax.net.ssl.X509TrustManager接口中的三個方法實現(xiàn)Server端和Client端信認證書。
首先,創(chuàng)建Server端和Client端各自的私有密鑰證書,在這里使用keytool,關于keytool的使用在這里不作詳細介紹,請參考它處。
1.創(chuàng)建Server端KeyStore文件serverKeys.jks,包含一個用于服務器的證書 :
引用
keytool -genkey -alias server -keysize 1024 -validity 3650 -keyalg RSA -dname "CN=sundoctor.com, OU=Developer,O=Techstar, L=Beijing, S=Beijing, C=CH" -keypass 123456 -storepass 123456 -keystore serverKeys.jks
2.創(chuàng)建Client端KeyStore文件clientKeys.jks,分別包含用于虛構的通信者 Alice 和 Bob 的證書 :
引用
keytool -genkey -alias alice -keysize 1024 -validity 3650 -keyalg RSA -dname "CN=Aclie, OU=Developer,O=Techstar, L=Beijing, S=Beijing, C=CH" -keypass 123456 -storepass 123456 -keystore clientKeys.jks
keytool -genkey -alias bob -keysize 1024 -validity 3650 -keyalg RSA -dname "CN=Bob, OU=Developer,O=Techstar, L=Beijing, S=Beijing, C=CH" -keypass 123456 -storepass 123456 -keystore clientKeys.jks
其次重寫的 javax.net.ssl.X509TrustManager接口中的三個方法實現(xiàn)Server端和Client端信認證書,代碼中BogusTrustManagerFactory類有關X509TrustManager實現(xiàn)的片斷,具體參考源碼
- static final X509TrustManager X509 = new X509TrustManager() {
-
-
-
-
- public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
-
- if (x509Certificates == null || x509Certificates.length == 0)
- throw new IllegalArgumentException("null or zero-length certificate chain");
- if (s == null || s.length() == 0)
- throw new IllegalArgumentException("null or zero-length authentication type");
-
- boolean br = false;
- Principal principal = null;
- for (X509Certificate x509Certificate : x509Certificates) {
- principal = x509Certificate.getSubjectDN();
- if (principal != null && (StringUtils.contains(principal.getName(), "Alice") || StringUtils.contains(principal.getName(), "Bob"))) {
- br = true;
- return;
- }
- }
-
- if (!br) {
- throw new CertificateException("連接認證失??!");
- }
- }
-
-
-
-
- public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
- if (x509Certificates == null || x509Certificates.length == 0)
- throw new IllegalArgumentException("null or zero-length certificate chain");
- if (s == null || s.length() == 0)
- throw new IllegalArgumentException("null or zero-length authentication type");
-
- boolean br = false;
- Principal principal = null;
- for (X509Certificate x509Certificate : x509Certificates) {
- principal = x509Certificate.getSubjectDN();
- if (principal != null && (StringUtils.contains(principal.getName(), "sundoctor.com"))) {
- br = true;
- return;
- }
- }
-
- if (!br) {
- throw new CertificateException("連接認證失?。?);
- }
- }
-
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
- }
- };
static final X509TrustManager X509 = new X509TrustManager() {/*** 確認和信任將其用于基于身份驗證類型的客戶端 SSL 身份驗證*/public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {if (x509Certificates == null || x509Certificates.length == 0)throw new IllegalArgumentException("null or zero-length certificate chain");if (s == null || s.length() == 0)throw new IllegalArgumentException("null or zero-length authentication type");boolean br = false;Principal principal = null;for (X509Certificate x509Certificate : x509Certificates) {principal = x509Certificate.getSubjectDN();if (principal != null && (StringUtils.contains(principal.getName(), "Alice") || StringUtils.contains(principal.getName(), "Bob"))) {br = true;return;}}if (!br) {throw new CertificateException("連接認證失?。?);}}/*** 確認和信任將其用于基于身份驗證類型的服務器 SSL 身份驗證*/public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {if (x509Certificates == null || x509Certificates.length == 0)throw new IllegalArgumentException("null or zero-length certificate chain");if (s == null || s.length() == 0)throw new IllegalArgumentException("null or zero-length authentication type");boolean br = false;Principal principal = null;for (X509Certificate x509Certificate : x509Certificates) {principal = x509Certificate.getSubjectDN();if (principal != null && (StringUtils.contains(principal.getName(), "sundoctor.com"))) {br = true;return;}}if (!br) {throw new CertificateException("連接認證失??!");}}public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}};
在這里checkClientTrusted(X509Certificate[] x509Certificates, String s)驗證客戶端證書,在這里只是簡單的驗證一下客戶端證書CN是否為Alice或Bob, checkServerTrusted(X509Certificate[] x509Certificates, String s)驗證服務端證書,這里只是簡單的驗證一下服務端的CN是否為sundoctor.com。更復雜的驗證,大家可以自行實現(xiàn)。
最后是創(chuàng)建服務端和客戶端SSLContext工廠類,分別初始化服務端和客戶端的SSLContext
-
- SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
- sslContext.init(getKeyManagers(serverKeys, serverKeysPassword), BogusTrustManagerFactory.X509_MANAGERS, null);
// Initialize the SSLContext to work with our key managers.SSLContext sslContext = SSLContext.getInstance(PROTOCOL);sslContext.init(getKeyManagers(serverKeys, serverKeysPassword), BogusTrustManagerFactory.X509_MANAGERS, null);
初始化SSLContext需要KeyManagers和TrustManager,TrustManager參見BogusTrustManagerFactory,使用serverKeys.jks、clientKeys.jks分別構建服務端和客戶端KeyManagers
- private static KeyManager[] getKeyManagers(String keysfile, String password) throws GeneralSecurityException,
- IOException {
-
-
- KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
-
-
-
- KeyStore ks = KeyStore.getInstance("JKS");
- InputStream in = BogusSslContextFactory.class.getResourceAsStream(keysfile);
- ks.load(in, password.toCharArray());
- in.close();
-
-
- kmf.init(ks, password.toCharArray());
-
-
- return kmf.getKeyManagers();
- }
private static KeyManager[] getKeyManagers(String keysfile, String password) throws GeneralSecurityException,IOException {// First, get the default KeyManagerFactory.KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);// Next, set up the TrustStore to use. We need to load the file into// a KeyStore instance.KeyStore ks = KeyStore.getInstance("JKS");InputStream in = BogusSslContextFactory.class.getResourceAsStream(keysfile);ks.load(in, password.toCharArray());in.close();// Now we initialise the KeyManagerFactory with this KeyStorekmf.init(ks, password.toCharArray());// And now get the TrustManagersreturn kmf.getKeyManagers();}
服務端(TLSServer)和客戶端(TLSClient)測試代碼比較簡單,具體請參考源碼,在這里有一點需要注意的就是在服務端添加加密過濾器 SslFilter時必須設置為:sslFilter.setNeedClientAuth(true),需要驗證客戶端證書。