有時候需要在C#的程式裡面發出request和內部或者外部的服務溝通,如果內部或者外部的服務只允許https連線,而且的ssl憑證並沒有經過認證(有可能是用self signed certifcate),那麼C#會直接出錯:
system.security.authentication.authenticationexception the remote certificate is invalid according to the validation procedure
System.Net.Http.HttpRequestException: 傳送要求時發生錯誤。 ---> System.Net.WebException: 基礎連接已關閉: 無法為 SSL/TLS 安全通道建立信任關係。
---> System.Security.Authentication.AuthenticationException: 根據驗證程序,遠端憑證是無效的。
一般來說要解決這個問題有兩個做法:
- 把self sign的certificate裝到程式的機器上面並且信任那個憑證
- 在送出request的時候做一些特殊處理
這篇將會對於第二個做法,調整程式讓發出request遇到這種問題的時候能夠處理這種問題。
問題發生原因
基本上合法的ssl憑證一定會是由一個可信任的機構發出,不過有時候有些服務只是要把通訊的管道加密,為了節省成本(或者其他什麼原因)而使用了self sign certificate (自我簽章的憑證)。
理論上任何會對外被呼叫到的https網站都不應該用self sign certificate,因此,從C#發出的https request(不管是用WebClient
還是HttpClient
)都會檢查ssl憑證是否合法。
也就是因為這個檢查導致出現錯誤。
解決方式
取決於你的target platform是什麼,有兩種解決方式:
- .Net Framework裡面的處理方式
- .Net Core裡面的處理方式
.Net Framework裡面的處理方式
基本上有個全域的事件叫做ServicePointManager.ServerCertificateValidationCallback
會在ssl憑證驗證的時候被觸發,因此假設不管檢查結果是如何,都要通過的情況下,可以再系統啟動的時候呼叫:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
不過這個有點太大,因此建議還是針對性的去通過比較好,舉例來說,假設有ssl憑證的host是:problem.com
,那麼可以只當host是problem.com
的情況下有驗證錯誤在過,範例如下:
ServicePointManager .ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => { if (sslPolicyErrors == SslPolicyErrors.None) { return true; } var request = sender as HttpWebRequest; if (request != null) { var result = request.RequestUri.Host == "problem.com"; return result; } return false; };
程式應該還蠻好理解:
- 如果ssl驗證沒有錯誤,直接回傳過(true)
- 判斷request的host是不是符合我們已知有問題憑證的host - 如果是就回傳過(true)
- 要不然都不過(false)
.Net Core裡面的處理方式
在.Net Core裡面會發現沒有ServicePointManager
,只有在HTTPClient 4.1
的版本以上有支援可以再request裡面設定(其實.Net Framework如果用的也是HttpClient 4.1 以上也可以用這種方式):
var handler = new HttpClientHandler(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => { return true; }; var client = new HttpClient(handler);
結語
基本上這個問題透過google就能夠找到,不過比較少有提到判斷host的部分(都是直接return true),因此這邊做一個記錄方便以後找到。
沒有留言 :
張貼留言