生成android 需要的BKS證書
項目需要手機做http server 由于要用SSL加密,所以需要用到ssl證書和私鑰,用openssl生成的步驟如下
- 創(chuàng)建證書和私鑰
openssl req -newkey rsa:2048 -new -nodes -x509 -days 36500 -keyout key.pem -out cert.pem
輸入這行命令后會提示輸入證書的國家、組織等信息,這里請保證重要的信息不要跳過
- 轉(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
- 從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如下:
- 加載BKS證書的provider 類名: com.android.org.bouncycastle.jce.provider.BouncyCastleProvider , name:"BC",
- 負責ssl的provider,類名:com.android.org.conscrypt.OpenSSLProvider, name:AndroidOpenSSL
總結(jié): 接入第三方的secure provider 真的要慎用Security.insertProviderAt這種影響全局的方式,否則會碰到各種奇奇怪怪的問題 ,不過通過解決這個問題也讓自己對secure provider和sslcontext、keystore 有了更深的認識。
參考: