ssl證書生成及java secure provider踩坑記錄

生成android 需要的BKS證書

項目需要手機做http server 由于要用SSL加密,所以需要用到ssl證書和私鑰,用openssl生成的步驟如下

  1. 創(chuàng)建證書和私鑰

openssl req -newkey rsa:2048 -new -nodes -x509 -days 36500 -keyout key.pem -out cert.pem

輸入這行命令后會提示輸入證書的國家、組織等信息,這里請保證重要的信息不要跳過

  1. 轉(zhuǎn)換為p12格式
    因為android 默認只支持BKS格式的keystore,所以需要將上面生成的證書和私鑰封裝到一個bks文件中,這里可以通過openssl 命令先將證書和私鑰封裝到p12格式的keystore,再將p12 文件轉(zhuǎn)為bks 文件

openssl pkcs12 -export -in cert.pem -inkey key.pem -out android.p12

  1. 從p12轉(zhuǎn)換為BKS

keytool -importkeystore -srckeystore ./android.p12 -srcstoretype pkcs12 -destkeystore ./android.bks -deststoretype bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/bcprov-ext-jdk15on-158.jar
使用 KeyTool 轉(zhuǎn)換為 BKS 格式時,需要 bcprov-ext-jdk15on-158.jar,文件路徑直接帶在 -providerpath 參數(shù)后面即可。也可以放到如下路徑:jdk/jre/lib/ext。

通過三個命令 就成功的創(chuàng)建了證書和私鑰,并將其封裝到一個bks文件中,android中就可以正常的使用了。

坑一

項目的兩個模塊都需要用到ssl。另一個模塊的同事引入了com.madgag.spongycastle 的BouncyCastleProvider 的庫,并寫了如下代碼:

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
        Security.insertProviderAt(new BouncyCastleProvider(), 1);
        Security.removeProvider(BouncyCastleJsseProvider.PROVIDER_NAME);
        Security.insertProviderAt(new BouncyCastleJsseProvider(), 2);

如此設(shè)置后,BouncyCastleProvider及BouncyCastleJsseProvider就成了默認的密碼庫, 本來android就有BouncyCastleProvider,用這個新引入的想來也沒有問題。但結(jié)果就出了問題,報了個

java.io.IOException: initialization failed

debug 跟代碼,發(fā)現(xiàn)問題的原因是 org.spongycastle.jsse.provider.ProvTrustManagerFactorySpi#getTrustAnchors 為空, 導致org.spongycastle.jsse.provider.ProvX509TrustManager 初始化失敗,拋出了上面的ioexception
為什么失敗呢? 我發(fā)現(xiàn)keystore加載的bks證書只有一個type為sealed的entry ,而getTrustAnchors需要certificateentry 來加載trustAnchors。
我用portecle 軟件打開上文中生成的bks證書發(fā)現(xiàn)確實只有一個entry,嘗試用portecle將cert.pem和key.pem存到keystore的兩個entry中,再導出為bks文件,編譯、運行,發(fā)現(xiàn)終于解決了。
總結(jié): android集成的BouncyCastleProvider與com.madgag.spongycastle的BouncyCastleProvider實現(xiàn)有不同,為了保持兼容,最好把keystore的cert和key保存到不同的entry中,可以用軟件來操作,方便很多

坑二

解決了上面的問題后,又發(fā)現(xiàn)了新的問題

07-27 18:16:00.654 10114-10206/×××× W/ProvTlsClient: Client raised fatal(2) internal_error(80) alert: Failed to read record
                                                                              java.net.SocketTimeoutException: Read timed out
                                                                                  at java.net.SocketInputStream.socketRead0(Native Method)
                                                                                  at java.net.SocketInputStream.socketRead(SocketInputStream.java:119)
                                                                                  at java.net.SocketInputStream.read(SocketInputStream.java:176)
                                                                                  at java.net.SocketInputStream.read(SocketInputStream.java:144)
                                                                                  at org.spongycastle.util.io.Streams.readFully(Streams.java:92)
                                                                                  at org.spongycastle.util.io.Streams.readFully(Streams.java:73)
                                                                                  at org.spongycastle.tls.TlsUtils.readAllOrNothing(TlsUtils.java:586)
                                                                                  at org.spongycastle.tls.RecordStream.readRecord(RecordStream.java:182)
                                                                                  at org.spongycastle.tls.TlsProtocol.safeReadRecord(TlsProtocol.java:593)
                                                                                  at org.spongycastle.tls.TlsProtocol.readApplicationData(TlsProtocol.java:556)
                                                                                  at org.spongycastle.jsse.provider.ProvSSLSocketWrap$AppDataInput.read(ProvSSLSocketWrap.java:570)
                                                                                  at okio.Okio$2.read(Okio.java:140)

在做為client時,okhttp頻繁報上面的warn,最終會報:

   java.net.SocketException: Software caused connection abort
                                                                              at java.net.SocketInputStream.socketRead0(Native Method)
                                                                              at java.net.SocketInputStream.socketRead(SocketInputStream.java:119)
                                                                              at java.net.SocketInputStream.read(SocketInputStream.java:176)
                                                                              at java.net.SocketInputStream.read(SocketInputStream.java:144)
                                                                              at org.spongycastle.util.io.Streams.readFully(Streams.java:92)
                                                                              at org.spongycastle.util.io.Streams.readFully(Streams.java:73)
                                                                              at org.spongycastle.tls.TlsUtils.readFully(TlsUtils.java:606)
                                                                              at org.spongycastle.tls.RecordStream.decodeAndVerify(RecordStream.java:229)
                                                                              at org.spongycastle.tls.RecordStream.readRecord(RecordStream.java:221)
                                                                              at org.spongycastle.tls.TlsProtocol.safeReadRecord(TlsProtocol.java:593)
                                                                              at org.spongycastle.tls.TlsProtocol.readApplicationData(TlsProtocol.java:556)
                                                                              at org.spongycastle.jsse.provider.ProvSSLSocketWrap$AppDataInput.read(ProvSSLSocketWrap.java:570)
                                                                              at okio.Okio$2.read(Okio.java:140)

這個問題google、百度了許久還是沒找到原因,感覺是BouncyCastleJsseProvider 的原因,可也找不到適配BouncyCastleJsseProvider的方式。 最終才找到用回android默認provider的辦法 ,這樣就規(guī)避了這個問題。

替換secure provider的方式很簡單,在 SSLContext、KeyStore 等組件生成的getInstance方法中都可以指定provider,android 默認的兩個provider如下:

  1. 加載BKS證書的provider 類名: com.android.org.bouncycastle.jce.provider.BouncyCastleProvider , name:"BC",
  2. 負責ssl的provider,類名:com.android.org.conscrypt.OpenSSLProvider, name:AndroidOpenSSL

總結(jié): 接入第三方的secure provider 真的要慎用Security.insertProviderAt這種影響全局的方式,否則會碰到各種奇奇怪怪的問題 ,不過通過解決這個問題也讓自己對secure provider和sslcontext、keystore 有了更深的認識。

參考:

  1. https://www.cnblogs.com/sunsky303/p/11010155.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容