# HttpsURLConnection - SSL驗CA憑證 ###### tags: `Tag(Android 技術相關)` [TOC] ## 什麼是 SSL 背後原理 ### SL: Secure Sockets Layer 當我們瀏覽網頁時,我們都有個好習慣:先檢查網址列的開頭是不是有個 🔒鎖的圖案。 ![](https://i.imgur.com/YIwfN6q.png) 就是這個鎖代表你現在連線到的網站是安全可信任的,是使用安全的。 HTTPS (HTTP Secure) 連線,而不是使用不安全的 HTTP protocol。連線後,只有瀏覽器本人跟網站能夠解讀雙方之間傳遞的資料,就算其他人想要從中攔截(比如說你的使用者帳號密碼),也沒辦法。 HTTPS 背後的 protocol,就是 SSL。 --- ### SSL 加密步驟 SSL 讓瀏覽器(所謂的 ==client==)要連到一個遠端網站(所謂的 ==server==)之前,先要求這個網站提供身分認證,跟這個網站約定暗號(交換鑰匙),打好交情(建立加密的 session),才會心甘情願地跟這個網站連線。可以分成六個步驟: 1. 瀏覽器對想要連線的網站送出==連線請求==,同時要求網站驗證自己。 2. 網站將自己的 SSL 數位憑證 (SSL certificate) 回傳給 client,裡面包含了網站的 public key。 3. 瀏覽器驗證網站回傳的的 root certificate,透過 chain of trust 機制(憑證鍊)確認這個證明文件是否可以被信認,同時也確認這個憑證是否過期。 4. 當認證通過,瀏覽器會用網站的 public key 建立一個 symmetric session key。 5. 網站用自己的 private key 解讀 session key,並且回傳一個確認訊息,開始一個被 SSL 保護的 session。 6. 這個 session key 會被用來加密所有之後瀏覽器與網站之間傳送的資料。 --- ## SSL 相關名詞 ### TLS (Transport Layer Security Protocol) 由於 SSL 已經不再安全(POODLE 與 DROWN 是兩個曾經發生過的著名攻擊),所以現在已經被 TLS protocol 取代。慣性使然,當我們說 SSL(比如說 SSL 憑證)時,大部分情況其實是在說 TLS。 TLS說明:https://jennycodes.me/posts/security-ssh#sshvsssl ### CA (Certificate Authority) 數位憑證認證機構,簡稱 CA,是負責發放與管理數位憑證 (certificate)的單位。前面提到進行 SSL 連線的前置作業是 server 要提供數位憑證讓瀏覽器驗證,但瀏覽器要怎麼驗證?它會去看這個憑證是不是被一個它相信的 CA 簽署的。如果是,那瀏覽器就相信這個 server 可以信任,如果不是,那瀏覽器就再看這個 CA 有沒有它自己的 certificate,如果有,而這個 certificate 是被一個瀏覽器信任的 CA 簽的,那就放行,如果沒有,就再往下找。如果一路找下去,找不到可以信任的 CA,就失敗。 #### Chain of Trust 憑證鍊 假設今天世界上有甲乙丙三家 CA。 CA 丙簽了 CA 乙的憑證 CA 乙簽了 CA 甲的憑證 瀏覽器想連線到網站 A,而瀏覽器只知道 CA 丙。 連線前,網站 A 傳了它的 certificate 們給瀏覽器 瀏覽器先看第一張,眉頭一皺,發現 A 的憑證是不認識的 CA 甲簽的,往下翻,看到 CA 乙簽了甲的憑證,但瀏覽器也不認識乙,所以繼續往下翻,下一張是乙的憑證,是 CA 丙簽署的 — bingo! 於是網站 A 順利與小明建立連線。 所以 CA 其實很像保證人的角色,它向瀏覽器保證一個網域的合法性,讓想要連線的那一方確保自己的連線對象是安全的。 這樣一層一層檢查 certificate,直到找到信任的 CA 的機制,叫做 chain of trust。你可能會疑惑,為什麼不直接讓所有 server 都帶著 CA 丙簽的 certificate 就好了?為什麼還需要經過這麼多層?主要原因是安全。如果今天壞人 B 找到一個破解 CA 甲的方法,可以偽造甲的簽名,這個漏洞一旦被發現,所有被甲簽過的憑證就都沒有意義了,那些網域必須要重新找 CA 來擔保自己的合法性。 使用 chain of trust 的好處是,它可以降低 root CA(chain of trust 的源頭 CA,就是例子中的 CA 丙)被暴露的風險。不過這也代表,要成為一個 root CA,安全防護必須要做到謹慎再謹慎,不然如果 root CA 的私鑰被攻破了,後果不堪設想。 ## 程式在何處驗憑證 說明: 1.憑證預先放入Assets檔案中,然後使用它創建 KeyStore 2.創建 KeyStore,然後再使用該示例創建和初始化TrustManager。一個TrustManager是系統用來驗證證書 從服務器和通過創建一個從什麼KeyStore與一個或多個CA,這些將是唯一的CA的信任通過TrustManager。 3.給定新的名稱TrustManager,該示例將初始化一個新的名稱SSLContext,該名稱提供了一個SSLSocketFactory您可以用來覆蓋的默認值 SSLSocketFactory的 HttpsURLConnection。這樣,連接將使用您的CA進行證書驗證。 ``` URL url = new URL(key.sConnUrl); if (key.sConnUrl.startsWith("https")) { urlHttpsConnection = (HttpsURLConnection) url.openConnection(); InputStream input = context.getAssets().open("Security_sample.cer"); //取得已存放在本地端的證書 urlHttpsConnection.setSSLSocketFactory(new TLSSocketFactory(input)); urlHttpsConnection.setConnectTimeout(baseTimeout * 1000); urlHttpsConnection.setReadTimeout(baseTimeout * 1000); ``` 憑證預先放入Assets檔案中 ==InputStream input = context.getAssets().open("Security_sample.cer");== 在setSSLSocketFactory方法中定義驗憑證 ==urlHttpsConnection.setSSLSocketFactory(new TLSSocketFactory(input));== TrustManagerFactory tmf = null; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); Certificate ca = cf.generateCertificate(caInput); caInput.close(); // 創建包含我們受信任的CA的密鑰庫 String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); keyStore.setCertificateEntry("ca", ca); // 創建一個信任我們的KeyStore中的CA的TrustManager String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); tmf = TrustManagerFactory.getInstance(tmfAlgorithm); tmf.init(keyStore); } catch (CertificateException | IOException | KeyStoreException e) { e.printStackTrace(); } SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf != null ? tmf.getTrustManagers() : new TrustManager[0], null); sslSocketFactory = sslContext.getSocketFactory(); 設定自定義驗CA憑證 sslContext.init(null, ==tmf != null ? tmf.getTrustManagers() :new TrustManager[0]==, null); ## 參考文獻 官方參考 example: https://developer.android.com/training/articles/security-ssl#java security的一些案權知識和有關SSH說明 https://jennycodes.me/posts/security-ssh#sshvsssl 三種綁憑證方式 https://www.netguru.com/codestories/3-ways-how-to-implement-certificate-pinning-on-android