Android SDK 在進行 https 連線時,對於自簽署的憑證是會拒絕連線的,會得到 Not trusted server certificate 的例外。如果使用 HttpsURLConnection 來連線,網路上可以找到一些破解方法,在此不多談。使用 apache httpclient 其實執行效率比較差一點,但是他最大的好處就是有內建的機制儲存cookie,並且也可以跟隨 server 作自動轉址。網路上資料比較多的是 httpclient 3.x版,Android 使用 httpclient 4 (而且還有些實作被拿掉) 唯一找到比較可信的來源是 apache httpclient 官方的 example。節錄重點段落如下:
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream = new FileInputStream(new File("my.keystore"));
try {
trustStore.load(instream, "nopassword".toCharArray());
} finally {
instream.close();
}
SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore);
Scheme sch = new Scheme("https", socketFactory, 443);
httpclient.getConnectionManager().getSchemeRegistry().register(sch);
直接把這段拿去用當然只有一個死字。本來看討論以為是要製造一個假的空憑證騙過 httpclient,從 Android 檔案系統有點微妙開始改來改去,一連串不同的例外或直接crash就不多談了。解決了檔案路徑,到底有沒有建立等等方面的問題之後才終於發現,假憑證是不行的…
1. 所以首先,開啟你PC或Mac上的瀏覽器連上目標網站,從憑證管理的地方把該網站的憑證匯出。每個瀏覽器做法都不同,請各位發揮正常工程師的水準做完這件事。以Firefox為例,找到site名後選匯出,多半是會得到一個檔名為 your_site_name.crt 的 X509(PEM) 憑證檔。為了之後使用方便,先把這檔案複製一份並把檔名改為 your_site_name.pem。
2. 將這個憑證格式從 PEM 轉為 BKS 格式。 這是關鍵性的一步啊。
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
這行中的 getDefaultType 到底會 get到什麼 type 呢?答案:在 Android 中是 BKS。有 J2ME 經驗的人會想說,我看多半是跟 J2ME 一樣的 Sun JKS 格式吧,而且,我不要用 getDefaultType 就好了,我可以自己指定為 PEM, JKS格式啊。是的,你可以。只是你會得到錯誤訊息說 KeyStore JKS implementation not found。測了半天,看起來 Android 的 httpclient 只吃 BKS 就對了,其他都沒實作。
3. 那格式要怎麼轉?根據網路上找到的資料,可以使用 KeyTool IUI 這個Java工具。打開後從介面選 create → KeyStore,格式選 BKS,自己命名一下,要不要設密碼都可。接著選 import → Keystore’s entry → Trusted certificate → Regular certificate。 Source 的部分選 PEM 並選到剛剛瀏覽器得到的那個檔,Target 當然選 BKS 和剛 create 出來的檔。按OK之後會跳出Entries視窗,下方提示你enter new alias,隨便取個名字後ok連打,順利的話你應該就會有一個 your_keystore.bks 之類的檔案了。
4. 要把憑證檔放在 Android app 能讀到的地方,做法有兩種,放在 SD card 中,或直接放在 resources 中。
[方案一] 放在 SD card 的情形:首先模擬器要掛上SD card,這在 Eclipse 用 AVD 工具產生 avd 時就可以順便指定SD了。如果習慣用指令的話,也可以用如下指令產生 image 並 mount 上:
$ mksdcard 512M my_sdcard.so $ emulator -sdcard ./my_sdcard.so
再來要把憑證檔 copy 到 SD card 中,目前只知道指令的做法:
$ adb push file_path/your_keystore.bks /sdcard
如果有顯示類似 ftp 傳輸速度之類的訊息應該就是成功了。
接著把 android 程式碼中檔案部分改一改
FileInputStream instream = new FileInputStream(new File("/sdcard/your_keystore.bks"));
...
trustStore.load(instream, null);
如果沒設密碼,可以把 keystore load 的第二個密碼參數改成 null,有設的話當然就把 “nopassword” 改成你的密碼。
基本上這樣應該就大功告成了。
[方案二] 再講講憑證放在 resources 的方法。首先把檔案複製到專案下的 res/raw/your_keystore.bks 。Eclipse 沒有錯誤的話就 ok,否則多半是你檔名不合規格,稍微改改。接著取用方法是把 android 程式碼改成:
InputStream instream = getResources().openRawResource(R.raw.your_keystore);
其它同SD card。
5. 還有一個地方要注意,就是 SSL port。
Scheme sch = new Scheme("https", socketFactory, 443);
如果你要連的網站不是用 port 443,這邊請記得改掉。
6. 另外如果要使用檔案放在 resources 的做法,getResources() 應該只能在 Activity class 中執行,如果另外包裝連線類別的話請不要直接服用上面的程式碼,要自行從 context 抓到資源再傳過去。
看起來不太複雜的事情我其實還鬼打牆了滿久才解決,希望對各位有點幫助。
