5

C# 連線 HTTPS 網站發生驗證失敗導致基礎連接已關閉

 8 months ago
source link: https://blog.darkthread.net/blog/webclient-and-tls12/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

C# 連線 HTTPS 網站發生驗證失敗導致基礎連接已關閉-黑暗執行緒

某台透過 .NET WebClient 物件爬網頁抓資料排程忽然出現:

基礎連接已關閉: 傳送時發生未預期的錯誤。 ---> System.IO.IOException: 驗證失敗,因為遠端群體已經關閉傳輸資料流。
The underlying connection was closed: An unexpected error occurred on a send. ---> System.IO.IOException: Authentication failed because the remote party has closed the transport stream

有趣的是,上回同一排程就發生過類似狀況,原本早上還執行得好好的,近中午時開始出錯。二次出錯的時點幾乎相同,推測是該網站的固定換版上線時間。

對照測試,使用 IE 或 Chrome 開啟網頁正常,只有透過 WebClient 取回 HTML 內容時才出錯,在本機測試也發生相同錯誤。印出 Exception 內容如下:

ERROR=System.Net.WebException: 基礎連接已關閉: 傳送時發生未預期的錯誤。 ---> System.IO.IOException: 驗證失敗,因為遠端群體已經關閉傳輸資料流。
   於 System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   於 System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   於 System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   於 System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   於 System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
   於 System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
   於 System.Net.TlsStream.CallProcessAuthentication(Object state)
   於 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   於 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   於 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   於 System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result)
   於 System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size)
   於 System.Net.PooledStream.Write(Byte[] buffer, Int32 offset, Int32 size)
   於 System.Net.ConnectStream.WriteHeaders(Boolean async)
   --- 內部例外狀況堆疊追蹤的結尾 ---
   於 System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)
   於 System.Net.WebClient.DownloadString(Uri address)
   於 System.Net.WebClient.DownloadString(String address)

錯誤訊息明確指向 SSL,爬文後恍然大悟,原來跟上回研究過的 TLS 1.0 停用議題有關,研判對方網站調整系統停用了較不安全的 TLS 1.0,依上回心得,.NET 客戶端使用 WebClient、WCF 以 HTTPS 連線遠端主機,也會涉及 TLS 1.0/1.1/1.2 版本議題,不同版本 .NET 的處理方式不同:

  • .NET 4.6 內建支援且預設使用 TLS 1.2
  • .NET 4.5 內建支援,但需透過 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 設為預設協定
  • .NET 4 本身不支援,但安裝 .NET 4.5 後即可使用 TLS 1.2,指定 TLS 1.2 的寫法為 ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

我的程式是 .NET 4.5,在調整 ServicePointManager.SecurityProtocol 設定擴大 SSL/TLS 版本範圍後,問題排除:(若改程式不方便,亦有修改 Registry 的解法,請參考前文

排版顯示純文字
        static void Main(string[] args)
        {
            try
            {
                WebClient wc = new WebClient();
                //REF: https://stackoverflow.com/a/39534068/288936
                ServicePointManager.SecurityProtocol = 
                    SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | 
                    SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
                string res = wc.DownloadString("httqs://some-server/test");
                Console.WriteLine(res);
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR=" + ex.ToString());
            }
            Console.ReadLine();
        }

TLS 1.0 已被視為不安全,近期應會被各大網站陸續停用,使用 .NET 讀取網站資料遲早要對類似問題,宜多加留意。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK