讀者 Edward 問了一個有趣問題,花了點時間追查,發現是 TLS 1.2 問題的變形茶包,累積了一些經驗值,簡單整理分享。

錯誤出現在使用 LineBotSDK 傳送 LINE 訊息的超簡單範例,甚至可簡化成一行就好:(參數都是亂給的,不可能傳送成功,正常情況應得到 HTTP 401,在特定環境則可重現錯誤)

isRock.LineBot.Utility.PushMessage("NoSuchUserId", "Never Sent", "InvalidToken");

我先試著在 .NET 6 Console 跑這行程式,可正確得到 HTTP 401 Authentication failed. Confirm that the access token in the authorization header is valid.

後來在 Visual Studio 2022 改用 .NET Framework Console 跑,我的電腦只裝了 .NET 4.7.2 跟 4.8,這兩個版本也都不會出錯。依 Edward 提供的情報,他的出錯環境是 .NET 4.5,莫非跟 .NET Framework 版本有關?

為了方便測試,我把程式搬進 Inline ASPX:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Net" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.ContentType = "text/plain";
        try
        {
            isRock.LineBot.Utility.PushMessage("NoSuchUserId", "Never Sent", "InvalidToken");
            Response.Write("WTF! You should never see this.");
        }
        catch (Exception ex)
        {
            Response.Write(ex);
        }
    }
</script> 

ASP.NET Runtime 指定 4.5,我成功重現了 NullReferenceException 錯誤:

懷疑跟呼叫 API 的部分有關,我另寫了一段程式成功重現錯誤,也找到真正原因:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Net" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
    Response.ContentType = "text/plain";
    string webErrMsg = string.Empty;
    try
    {
        try
        {
            var wc = new WebClient();
            wc.DownloadString("https://api.line.me/v2/bot/message/push");
            Response.Write("WTF! You should never see this.");
        }
        catch (System.Net.WebException wex)
        {
            webErrMsg = wex.Message;
            using (var sr = new StreamReader(wex.Response.GetResponseStream()))
            {
                Response.Write(sr.ReadToEnd());
            }
        }
    }
    catch (Exception ex)
    {
        Response.Write(webErrMsg + "\r\n");
        Response.Write(ex);
    }
}
</script>

要求已經中止: 無法建立 SSL/TLS 的安全通道。 是標準的 TLS 1.2 相容問題,LINE API 依循安全規範限定 TLS 1.2,而 .NET Framework 4.6+ 才預設使用 TLS 1.2,如果是 .NET 4.5 若沒加上 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 參考 或修改 Registry 參考,將無法建立 TLS 連線。而在 catch WebException 部分,程式未考量到完全無法連線時 WebException.Response 為 null 的狀況,未經檢查呼叫 Response.GetResponseStream() 是 NullReferenceException 的來源。(已提報 Issue 給 David 老師)

修改程式,指定使用 TLS 1.2,問題排除:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Net" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.ContentType = "text/plain";
        try
        {
            // 設定使用 TLS 1.2
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            isRock.LineBot.Utility.PushMessage("NoSuchUserId", "Never Sent", "InvalidToken");
            Response.Write("WTF! You should never see this.");
        }
        catch (Exception ex)
        {
            Response.Write(ex);
        }
    }
</script> 

試著縮減出最小可重現問題的程式片段,比對不同環境的結果,抽取可疑行為聚焦觀察,最終找出原因破案,這回也算一次標準的射茶包範例吧! 分享給大家參考。

Debugging a NullReferenceException issue in the LineBotSDK code to discover the root cause of a TLS 1.2 support issue.


Comments

# by Edward

感謝,黑大

Post a comment