C# 連線 HTTPS 網站發生驗證失敗導致基礎連接已關閉
28 | 88,944 |
某台透過 .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 讀取網站資料遲早要對類似問題,宜多加留意。
Comments
# by Alex
有考慮改用這樣的方法嗎? ServicePointManager.SecurityProtocol = GetSecurityProtocol(); private static SecurityProtocolType GetSecurityProtocol() { var result = 0; foreach(var value in Enum.GetValues(typeof(SecurityProtocolType))) result += (int) value; return (SecurityProtocolType) result; }
# by Jeffrey
to Alex, 這方法挺好,但因為涉及資安,個人覺得正向表列想開放的項目更安全一些。
# by FalseApple
感謝大大, 今天也採到這個坑了
# by Roger
今天換我踩到這個坑,一改程式就解決了,感謝大大,黑暗執行緒這邊的文章幫我解決了不少怪問題!
# by 好奇
今天也遇到這個問題~謝謝大大提供解法
# by ivan
3Q 黑大
# by Link
最近真的是一直在踩TLS1.2的坑,上網一查就又是黑大的文章,真是太感謝了
# by Suming
果然也遇到了, 感謝黑大的分享, 受益良多
# by Yirung
Thanks!!
# by 小螺絲
黑大您好, 小弟我也遇到這個問題, 透過您的方式順利解決了 (開發環境: Win10). 但發現該api使用TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256這個Cipher suite, 僅Win10/Server 2016以後才有支援 在Server 2012 r2 上測試發現狀況依舊, ie也無法順利開啟. 但經測試, python所寫的測試code跟chrome在Server 2012 r2是能順利交握取得api資料的. 不知黑大是否有遇過類似的狀況?
# by Jeffrey
to 小螺絲,確認一下,你的狀況是「某 API Client 與 IE 在 Windows 2012 R2 上無法連上某 API Server,但使用 Python 及 Chrome 在該 2012R2 上連線同一台 API Server 卻沒問題?」 2012R2 的 IE 有升級到 IE11 嗎?API Client 限定只能用特定 Cipher 連 TLS 的案例我倒沒聽過(我以為會支援多種,視狀況選用其一),應該不是 .NET 程式吧?
# by 小螺絲
是的, 業主端與公司這邊的測試機都有升級到ie11. 用Postman測試, 也可以連得到並取得資料, 並得知應該是架在iis上的.NET... 因為只有撰寫的.net程式跟ie無法順利交握, 所以感到很神奇@@""
# by Jeffrey
to 小螺絲,聽起來像是 2012R2 的作業系統或 .NET 設定沒設好造成的,Chrome 跟 Postman、Python 用自己的 TLS 連線程式庫沒問題,IE 跟 .NET Client 都依賴 Windows 的設定因此壞掉。另一種可能是反過來,2012R2 只留了 TLS 1.2,而 API Server 沒設定好不支援 TLS 1.2,Chrome/Postman/Python 還 Support TLS 1.0/1.1 反而可以通。那個限定 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 Cipher 的資訊是從哪裡來的?
# by Bryan
to 小螺絲: 我也碰到相同問題,對方的網管指定只開tls1.2 + 特定的cipher,目前我的.NET程式也無法連至對方API,但可以連上同主機走同樣protocal的網站(用chrome跟IE都可以)
# by Jason Cheng
感謝這篇,今天也受用了!
# by Troy Chi
救了我一天,這篇到今天還是持續在救人^0^
# by OeOb
感謝!
# by Lance
請問黑大,我目前也遇到一樣的問題,原排程打API(使用HttpClient)都正常,但發布新版本之後(打API的code都沒異動過),打API就會出現"System.Net.WebException: 要求已經中止: 無法建立 SSL/TLS 的安全通道。",但是退版後就又正常,您覺得可能是什麼問題?
# by Jeffrey
to Lance,這種案例算單純,保證有解,全力比對新舊版差異找線索,查不出來還有大絕 - 一點一點把差異加進舊版,一定可破案。
# by 阿光
我在.Net Framework 4.8用XmlDocument.Load一個RSS網站也會有問題,後來用.Net core 6一樣的方法就過了,真是太坑了。
# by YU
您好~ .NET CLR 版本是4.0 改這個會影響嗎?
# by Jeffrey
to YU,接近文末有 .NET 4/4.5/4.6 的處理方式,不知你說的"改"指的是什麼?
# by max
您好,.NET CLR 版本是2.0 的話,有處理的方式嗎?
# by Jeffrey
to max, 我沒有處理過 2.0,但 3.5 的經驗是安裝最新版 .NET Framework Update + 設 Registry 搞定。https://blog.darkthread.net/blog/net35-tls12-issue/
# by Tim
黑大您好 不好意思打擾, 與您請教 我在一個只能用vs 2005開發的專案裡, 要去加入https web service參考, 會出現 The underlying connection was closed: An unexpected error occurred on a receive. The client and server cannot communicate, because they do not possess a common algorithm 之前在別的程式裡, 有用以下方式解掉 ervicePointManager.SecurityProtocol = (SecurityProtocolType)3072; 不過, 現在是加入web service時 (按下往右的綠箭頭, 移至後出現的) 不知該怎麼解
# by Tim
不好意思, 繼續上篇, 不知用此文是否可以 https://stackoverflow.com/questions/43240611/net-framework-3-5-and-tls-1-2
# by Jeffrey
to Tim,我在 .NET 3.5 專案有遇過類似問題,當時是設 Registry 解決,提供參考:https://blog.darkthread.net/blog/net35-tls12-issue/
# by Tim
TO 黑大 可以了, 真的非常非常非常感謝