目前根據項目的需要,整理了一下公司內部的安全通信規范,將其中涉及證書驗證的部分分享出來,供探討。
處於通信安全的考慮,很多通過互聯網進行的通信,
原則上均要求使用加密通信,即采用基於SSL或TLS的安全通信。對於Web應用及Web API來說,原則上應該一律使用HTTPS,禁用HTTP;對於Socket通信來說,原則上應采用SSL協議加密傳輸。
基於SSL協議,需要服務器證書。對於Web應用及Web API(統稱站點),原則上應使用由全球可信機構頒發的證書。但是考慮到證書費用、站點條件等一些因素,對於下列情形,可考慮使用自頒發證書:
1. 站點只在特定局域網、專網內部署使用;
2. 站點只有IP地址,沒有注冊域名;
3. 站點所有者不願意支付證書費用。
若考慮企業自己建個證書頒發機構(CA),那就涉及到客戶端對服務器證書的驗證問題。目前很多人提到的做法都是實現自定義驗證,但所謂的自定義驗證就是直接返回true。這樣處理,雖然能進行HTTPS加密通信,但由於實際上未對服務器證書做任何驗證,因此是存在中間人攻擊風險的。
本人研究了一下這個問題,發現針對.net開發,在中文網站上似乎沒有很好的實現建議,因此才想到整理這篇博文和大家分享我們的做法,歡迎各位大拿批評指正。
首先,要使用自頒發證書實現HTTPS通信,最好是要對企業自己的CA做一個統一管理,CA必須具有唯一性,並且CA一旦建立,原則上應保證永不更改,從而保證企業CA的權威性。CA的權威性,是通信過程中對服務器證書進行可信驗證、防止中間人攻擊的基礎。
其次,對自頒發證書的申請與獲取,也要遵循一定的原則,主要是證書的使用者要和實際站點IP地址或域名一致,另外根據需要考慮證書有效期問題。
最后,就是在客戶端對服務器證書的有效性驗證了。我們將這個問題分為兩部分:
(一)Web應用(網站)
對於Web應用來說,都是通過瀏覽器訪問,而瀏覽器對證書的驗證過程我們是無法干預的。對於自頒發證書,瀏覽器會提示證書無效。對於這個問題,我們的做法是參考鐵總12306網站的做法:
將我們的CA證書即根證書(公鑰)文件通過某種途徑,發送給所有用戶,要求用戶將根證書安裝到“受信任的根證書頒發機構”。從而防止中間人攻擊。(二)Web API
Web API均為某種客戶端程序調用,調用接口需做業務權限驗證。因此對於證書的驗證,是在自研程序中執行。
對於自頒發證書,各研發平台的默認驗證肯定是不通過的,有兩種方式:
1、 跟Web網站一樣,要求所有客戶端設備(電腦、終端設備)均將我們的根證書安裝到可信證書區域,從而可使用默認的驗證方式,代碼不用做任何額外處理。但這種方式,會有一定局限性,很多時候不大可行。
2、 將根證書集成到項目中,通過代碼實現自定義驗證。自定義驗證項目包括:
a) 構建證書鏈,看服務器證書是否能鏈接到我們的根證書;這里對於不同的開發語言,處理可能不同。在C#中,需要將我們的根證書放到鏈引擎可搜索到的地方,然后設置合適的鏈策略,再重新構建證書鏈。
b) 上述驗證通過后,驗證服務器證書的使用者是否就是當前請求Uri的主機地址(IP或域名),是則通過驗證;否則就是A地址的證書,用在了B地址的網站上。
服務器證書的有效期可以不用驗證。由於默認的有效期是1年,如果驗證有效期的話,則服務器證書每年都要更新,對於自頒發證書,沒有這個必要,就讓它永遠有效。
如果客戶端是C#,可以按如下方式進行驗證:
根據項目情況做一些初始化,比如初始化根證書等。
1 static String caFilePath = "D:\\…….cer"; //本地根證書路徑 2 static X509Certificate2 ca; //本地根證書對象 3 4 //驗證代碼一 5 WebRequestHandler wrh; 6 wrh = new WebRequestHandler(); 7 wrh.ServerCertificateValidationCallback += RemoteCertificateValidate; 8 //驗證代碼二 9 ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidate;//驗證服務器證書回調自動驗證 10 11 12 if (File.Exists(caFilePath)) 13 { 14 ca = new X509Certificate2(caFilePath); 15 }
使用該WebRequestHandler處理Https連接,以便實現自定義證書驗證
1 HttpClient client = new HttpClient(wrh); 2 ……
自定義驗證的實現:
1 private bool RemoteCertificateValidate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error) 2 { 3 if (null != ca) 4 { 5 /* 6 * 根證書未安裝到“受信任的根證書頒發機構”時,默認是無法形成可信證書鏈的。(chain中將只有服務器證書本身) 7 * 需更改鏈策略,然后重新構建證書鏈。 8 */ 9 // 將我們的根證書放到鏈引擎可搜索到的地方 10 chain.ChainPolicy.ExtraStore.Add(ca); 11 //不執行吊銷檢查 12 chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; 13 //忽略CA未知情況、不做時間檢查 14 chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority | X509VerificationFlags.IgnoreNotTimeNested | X509VerificationFlags.IgnoreNotTimeValid; 15 //重新構建可信證書鏈 16 bool isOk = chain.Build(cert as X509Certificate2); 17 if (isOk) 18 { 19 X509Certificate2 cacert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate; //獲取最前面的證書,認為是根證書 20 //與本地根證書比較 21 if (ca.GetPublicKeyString().Equals(cacert.GetPublicKeyString()) && ca.Thumbprint.Equals(cacert.Thumbprint)) 22 { 23 HttpWebRequest req = sender as HttpWebRequest; 24 if (null != req && cert.Subject.Contains("CN=" + req.Address.Host)) 25 { 26 return true; //根證書可信且服務器證書確實是指定服務器的,驗證通過 27 } 28 } 29 } 30 } 31 return false; 32 }
通過上述方式,可以靈活執行自定義的驗證,比如既驗證服務器證書確實是我們頒發給指定網站的,又可根據需要跳過證書有效性的驗證。
對於在java中做自定義驗證,可參考一下博文(本人沒仔細研究)
http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html
--------------------- 本文來自 Darlzan 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/notjusttech/article/details/72779904
