處理一枚 TLS 1.2 茶包,藉此釐清了一些 .NET Framework 版本觀念,筆記備忘。

故事是有個 ASP.NET MVC / .NET Framework 4.5.2 網站,WebClient 連某個 HTTPS 網址時出現典型的 基礎連接已關閉: 接收時發生未預期的錯誤 / 客戶端與伺服器無法溝通,因為它們沒有公用的演算法 (The client and server cannot communicate, because they do not possess a common algorithm) 錯誤。很明顯是網站只支援 TLS 1.2,而 .NET 4.6+ 才預設使用 TLS 1.2 連線,解法有三種:

  1. 升級到 .NET Framework 4.6 以上版本 參考
  2. 在程式碼加上 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
  3. 設定 SystemDefaultTlsVersions、SchUseStrongCrypto Registry 參考

我選擇第一種做法,將 ASP.NET MVC 網站升級到 4.8,重跑程式,卻照樣彈出「沒有公用的演算法」錯誤... 登楞! 莫非「.NET 4.6+ 預設改用 TLS 1.2」 的說法有誤,我還錯信了八年?

開了個全新 .NET 4.5.2 Console Application 測試以下程式,成功重現 TLS 連線錯誤;將版本改成 4.8 重新編譯執行都可成功,印證先前的認知並沒有錯!

static void Main(string[] args)
{
    var attr = (TargetFrameworkAttribute)Attribute.GetCustomAttribute(
                typeof(Program).Assembly, typeof(TargetFrameworkAttribute));
    Console.WriteLine($"Target Framework: {attr?.FrameworkName ?? "Unknown"}");
    try
    {
        var wc = new WebClient();
        var resp = wc.DownloadString("https://owasp.org/");
        Console.WriteLine("HTTPS Request Succeeded.");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
}

那為什麼 ASP.NET MVC 已升級 4.8,卻沒有如預期預設改用 TLS 1.2?

首先,複習幾個觀念:(參考資料:.NET 4.6 專案可以參照及使用 .NET 4.8 編譯的程式庫嗎?)

  • .NET 4.X 採「就地更新」(In-Place Update)政策,在安裝 .NET 4.8 的環境, 以 .NET 4.0、4.5、4.6、4.7、4.8 為目標編譯的程式都是在 .NET 4.8 版 Runtime 執行。
  • .NET 4.5 安裝時會覆寫換掉 \Windows.NET Framework\V4.0.30319 的 .NET 4 舊版 dll,沿用相同目錄同樣檔名,置換成新版本。因此,安裝 .NET 4.5 後,即使是以 .NET 4 為目標編譯的程式,使用的也會是 .NET 4.5 版 Runtime 及系統組件。而 .NET 4.5.X、4.6.X、4.7.X、4.8 的升級也是依循相同模式。

所以,不管我是編譯成 .NET 4.8 或 4.5.2,其實使用的都是 4.8 的 Runtime 與系統套件。而我們在 Visual Studio 選取不同的 Target Framework,它會修改 .csproj 的 <TargetFrameworkVersion> 以及 app.config 的 supportRuntime sku Attribute:

Visual Studio 在編譯時會依據 app.config 在執行檔加上 [assembly: TargetFramework(".NETFramework,Version=v4.X", FrameworkDisplayName = ".NET Framework 4.X")],程式在執行時便會向下相容,模擬舊版本的行為。

至於 ASP.NET 沒有 supportRuntime 可參照,所以會依據 web.config 的 compilation 及 httpRuntime 而定,問題主機的二者呈現衝突狀態,compilation 寫 4.8、但 httpRuntime 寫 4.5.2:

<system.web>
  <compilation targetFramework="4.8" />
  <httpRuntime targetFramework="4.5.2" />
</system.web>

compilation 的 targetFramework 決定 ASP.NET 動態編譯時的 .NET Framework 版本依據,設定 4.0,遇到 4.5+ 才支援的語法將會出錯。

httpRuntime 的 targetFramework 則決定執行時期的 .NET 版本行為 (CLR 的 Quirks Mode 行為),設定成 4.5.2 便會恢復 ServicePointManager.SecurityProtocol 預設舊版 SSL (非 TLS 1.2) 的行為,導致雖然 DLL 已升級成 4.8,在 ASP.NET 執行卻無法連上 TLS 1.2 網站的錯誤,結案!

關於這部分的詳細介紹,推薦 .NET 部落格這篇文章:All about httpRuntime targetFramework by levibroderick / .NET Blog

.NET Framework TLS 1.2 defaults depend on both runtime and httpRuntime targetFramework. Upgrading DLLs isn’t enough—httpRuntime must also target 4.6+ for default TLS 1.2 behavior.


Comments

# by johnny

> 至於 ASP.NET Core 沒有 supportRuntime 可參照 Core 是不是筆誤?

# by Jeffrey

to johnny, Yes, 應該是 ASP.NET,不限 ASP.NET Core。謝謝指正。

Post a comment