2017年12月10日 星期日

[faq]解決C#呼叫有ssl憑證問題的網站出現遠端憑證是無效的錯誤問題

image
圖片來源:https://pixabay.com/en/despair-alone-being-alone-archetype-513528/

有時候需要在C#的程式裡面發出request和內部或者外部的服務溝通,如果內部或者外部的服務只允許https連線,而且的ssl憑證並沒有經過認證(有可能是用self signed certifcate),那麼C#會直接出錯:

image
錯誤訊息範例畫面

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: 根據驗證程序,遠端憑證是無效的。

一般來說要解決這個問題有兩個做法:

  1. 把self sign的certificate裝到程式的機器上面並且信任那個憑證
  2. 在送出request的時候做一些特殊處理

這篇將會對於第二個做法,調整程式讓發出request遇到這種問題的時候能夠處理這種問題。

問題發生原因

基本上合法的ssl憑證一定會是由一個可信任的機構發出,不過有時候有些服務只是要把通訊的管道加密,為了節省成本(或者其他什麼原因)而使用了self sign certificate (自我簽章的憑證)。

理論上任何會對外被呼叫到的https網站都不應該用self sign certificate,因此,從C#發出的https request(不管是用WebClient還是HttpClient)都會檢查ssl憑證是否合法

也就是因為這個檢查導致出現錯誤。

解決方式

取決於你的target platform是什麼,有兩種解決方式:

  1. .Net Framework裡面的處理方式
  2. .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)
或許你會說幹嘛這麼麻煩,何不都過就好?這個其實是為了明確定義那些是特例可以通過,那些不可以。 要記得,這個修改是全域都有作用,換句話說所有送出的request,包含像是Xml Web Service這種reference都會吃,因此明確一點比較好。

.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);
因為這個設定是和某一個client有關,因此直接全部return true比較不會有問題 - 不像上面是一個全域的事件。

結語

基本上這個問題透過google就能夠找到,不過比較少有提到判斷host的部分(都是直接return true),因此這邊做一個記錄方便以後找到。

標籤: ,,

沒有留言 :

張貼留言