天天看點

SLB配置自己生成的SSL證書--雙向認證

SLB配置自己生成的SSL證書--雙向認證
           

因為公司業務的伺服器隻提供接口給移動端使用,不需要浏覽器通路,而且要求使用雙向認證,移動端要預埋證書,這就涉及到證書過期之後必須更新才能通路伺服器的問題。使用第三方機構的伺服器證書,有效期就1-2年,過期不得不更換,而移動端又不能確定使用者及時更新,安全性什麼的要求也高。總之,業務的需求決定了最終沒有購買第三方機構的伺服器證書,而是使用了openssl自己生成的證書。

之前是把證書直接配置在後端伺服器上,但考慮到後續業務的擴充以及高可用的要求,還是應該要做負載均衡,是以就嘗試在阿裡雲的SLB服務上做配置。

1.生成證書

首先生成證書,這裡我是寫了一個build_cert.sh腳本和配置來生成CA憑證,以及服務端和用戶端證書的,具體編寫可以參考這篇文章:https://blog.csdn.net/ustccw/article/details/76691248

我這裡生成測試證書使用的域名是:slb.test.mosiliang.top

接着在linux系統終端執行腳本build_cert.sh,就生成了ca.cert,ca.key,client.cert,client.key,server.cert,server.key等後續需要的檔案。

2.上傳證書

這些證書檔案生成之後還不能直接使用,需要做一些格式轉換。我這裡使用openssl到腳本所在路徑下執行如下指令:

openssl pkcs12 -export -in rsa/server.cert -inkey rsa/server.key -out server.pkcs12 -name server -CAfile rsa/ca.cert -caname root -chain
           

輸入密碼(随意寫,但要記住,因為後續還要用到,我這裡是:1523480205018),得到server.pkcs12伺服器證書。

但是SLB裡要求的證書格式是pem格式,是以要做PKCS#12 到 PEM 的轉換:

openssl pkcs12 -in server.pkcs12 -nokeys -out server.pem
           

接下來上傳證書到SLB做配置,先是上傳伺服器證書,這裡要選擇第三方簽發證書,如下圖:

SLB配置自己生成的SSL證書--雙向認證

接着下一步選擇檔案上傳,就是server.pem檔案,但是這裡生成的檔案直接上傳會提示格式錯誤,按照裡面的樣例格式說明(如下圖),

SLB配置自己生成的SSL證書--雙向認證

打開server.pem檔案發現這裡生成時多了一些額外的資訊,我們隻要把BEGIN CERTIFICATE和END CERTIFICATE的兩部分單獨拷貝到上傳證書的輸入框那裡就可以了。

接着是服務端私鑰的上傳,也就是前面生成的server.key檔案,這裡直接上傳也會提示格式不對,對比格式樣例,我這裡是收尾行少了個"RSA",即按照格式,隻要把BEGIN PRIVATE KEY改成BEGIN RSA PRIVATE KEY,END PRIVATE KEY改成END RSA PRIVATE KEY就可以了。

上傳伺服器證書之後就可以選擇證書了。另外,因為要實作雙向認證,是以還要啟用雙向認證并上傳CA根證書,如下圖:

SLB配置自己生成的SSL證書--雙向認證

這裡直接上傳ca.cert檔案就可以了。

3.測試驗證

驗證之前不要忘了在域名解析裡把前面生成證書的域名slb.test.mosiliang.top解析到自己SLB伺服器的VIP上。
           

上傳好證書,配置SLB的後端伺服器,建立虛拟伺服器組,并指定通路後端伺服器的端口,一般是80,我這裡是8080。伺服器組的選擇,看官網說明:

SLB配置自己生成的SSL證書--雙向認證

至于後端裡跑的測試服務環境,那就要自己去搭建了,我這裡之前就有測試的背景服務環境在,是以這裡隻要確定背景服務正常能夠通路到就行。

接着是用戶端的驗證,這裡我們的用戶端其實是移動端,但也可以先驗證一下浏覽器的通路。

浏覽器驗證

先http請求,驗證下後端伺服器通路情況是否正常:http://www.mosiliang.top:8080/xxxx

接着直接通過https通路浏覽器:https://slb.test.mosiliang.top/xxxx

這是會報錯的,因為我們開啟了雙向認證,要在電腦上安裝對應用戶端證書,之後才能通路。

用戶端證書就是使用前面生成的client.cert和client.key來生成,同樣是在腳本檔案所在路徑下執行指令:

openssl pkcs12 -export -in rsa/client.cert -inkey rsa/client.key -out client.p12 -name client -CAfile rsa/ca.cert -caname root -chain
           

輸入密碼(同樣是随意寫,這裡是:2563481407018),之後得到client.p12。

把client.p12安裝到win10上(安裝過程要輸入對應的密碼2563481407018),然後再打開浏覽器通路,大概因為是自己生成的證書(沒有第三方認證),浏覽器會彈框詢問,選擇對應證書确認就可以通路了,如下圖:

SLB配置自己生成的SSL證書--雙向認證

如果在SLB上關閉雙向認證,那麼這裡浏覽器不用安裝用戶端證書就可以通路。

移動端驗證

最後來驗證移動端,這裡使用的是Android端。

先生成用戶端信任的伺服器端證書,即把CA根證書導入,執行如下指令:

keytool -import -keystore client.truststore -keypass 1523480205018 -storepass 1523480205018 -alias ca -trustcacerts -file rsa/ca.cert
           

其中1523480205018是密碼,可以随意的寫,然後得到client.truststore檔案。

而Android端使用的一個證書格式要求是bks,是以這裡還要把client.truststore轉成bks格式的。這裡就要借助工具portecle.jar來轉換,工具下載下傳:https://sourceforge.net/projects/portecle/

大概步驟如下:

運作:java -jar portecle.jar
打開client.truststore檔案,如下圖:
SLB配置自己生成的SSL證書--雙向認證
輸入前面設定的對應密碼,之後轉換格式,如下圖:
SLB配置自己生成的SSL證書--雙向認證
再儲存檔案,如下圖:最終得到client_slb.bks檔案。
SLB配置自己生成的SSL證書--雙向認證
SLB配置自己生成的SSL證書--雙向認證

其中p12轉換bks時可能會報錯:

java.security.invalidKeyException:lllegal key size

原因是限制了密鑰長度,參考這裡:

https://blog.csdn.net/ainiyiwan123/article/details/796225

解決:

下載下傳JCE:https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

得到jce_policy-8.zip,解壓,把local_policy.jar和US_export_policy.jar複制到本地JDK對應安裝目錄下,我這裡是/usr/lib/jvm/jdk1.8.0_25/jre/lib/security,替換原來的檔案。

再重新開機portecle正常。

接下來,使用client_slb.bks和client…p12來做Android端驗證。

我們寫個demo來驗證,先把client_slb.bks和client…p12(重命名為client_slb.p12)放到assets目錄下;

接着是代碼處理,這裡連接配接用的是OkHttp,MainActivity.java的部分代碼如下:

public void btnParamTest(View view) throws Exception {
       OkHttpClient mOkHttpClient = SSLHelper.getOkHttpClient(this);
       Request request = new Request.Builder()
             .url("https://slb.test.mosiliang.top/xxx")
             .build();
       mOkHttpClient.newCall(request).enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {
               Log.i("lyl","onFailure------222--------" + e.toString());
               e.printStackTrace();
          }
           @Override
           public void onResponse(Call call, Response response) throws IOException {
               Log.i("lyl","onResponse------222--------" + response.body().string());
           }
       });
   } 
           

SSLHelper裡的方法getOkHttpClient:

public static OkHttpClient getOkHttpClient(Context context) {
       OkHttpClient client = null;
       try {
       // 伺服器端需要驗證的用戶端證書
       KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
       // 用戶端信任的伺服器端證書
       KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);

       InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
       InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
       try {
           keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
           trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           try {
               ksIn.close();
           } catch (Exception e) {
               e.printStackTrace();
           }
           try {
               tsIn.close();
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
       TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
       trustManagerFactory.init((trustStore));

       TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
       if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
           throw new IllegalStateException("Unexpected default trust managers:"
                   + Arrays.toString(trustManagers));
       }
       X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

       //密鑰管理器
       KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
       keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());

       SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] { trustManager }, null);
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

       client = new OkHttpClient.Builder()
               .connectTimeout(300*1000,TimeUnit.MILLISECONDS)
               .readTimeout(300*1000,TimeUnit.MILLISECONDS)
               .writeTimeout(300*1000,TimeUnit.MILLISECONDS)
               .sslSocketFactory(sslSocketFactory, trustManager)
               .hostnameVerifier(new UnSafeHostnameVerifier())
               .build();

       } catch (Exception e) {
           e.printStackTrace();
       }
       return client;
   }
           

相關配置參數:

private static final String KEY_STORE_TYPE_BKS = "bks";
private static final String KEY_STORE_TYPE_P12 = "PKCS12";
public static final String KEY_STORE_CLIENT_PATH = "client_slb.p12";//P12檔案
private static final String KEY_STORE_TRUST_PATH = "client_slb.bks";//truststore檔案
public static final String KEY_STORE_PASSWORD = "2563481407018";//P12檔案密碼
private static final String KEY_STORE_TRUST_PASSWORD = "1523480205018";//truststore檔案密碼
           

找個終端機器(如Android手機)來跑demo,運作通路,可以正常通路。

再驗證下雙向認證是否有效,把用戶端證書的配置注釋掉,如下圖,初始化sslContext時設定km為null。

SLB配置自己生成的SSL證書--雙向認證

這時候再請求通路就會報錯,傳回log資訊如下圖:

SLB配置自己生成的SSL證書--雙向認證

而把SLB伺服器上配置的”啟用雙向認證按鈕”關掉,再去請求,驗證能夠正常通路。說明這裡的雙向認證配置OK。

總結:在SLB負載均衡服務上可以配置openssl生成的證書,但一般伺服器證書都會購買第三方機構的,具體看使用場景吧。不過具體的配置差不多,不過是不用自己生成證書而已。

最後,分享一些阿裡雲服務活動:

雲産品通用紅包:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=wbq4iya3

建站優惠:https://www.aliyun.com/jianzhan/?userCode=wbq4iya3

高性能雲伺服器特惠:https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=wbq4iya3

商标服務:https://tm.aliyun.com/?userCode=wbq4iya3

新使用者專享:https://promotion.aliyun.com/ntms/act/shoppingcart.html?userCode=wbq4iya3