國產化之銀河麒麟.netcore3.1訪問https服務的兩個問題

波斯馬發表於2022-04-18

背景

某個專案需要實現基礎軟體全部國產化,其中作業系統指定銀河麒麟,資料庫使用達夢V8,CPU平臺的範圍包括x64、龍芯、飛騰、鯤鵬等。

考慮到這些基礎產品對.NETCore的支援,最終選擇了3.1版本。主要原因就是龍芯搞了自研CPU架構,使用者量不夠大,.NET官方並沒有專門針對龍芯的支援,而龍芯團隊只對.netcore3.1做了適配(目前.net6適配測試中),至於其它的國產CPU則是基於Arm64和x64的,.NET官方都有支援。

環境

  • 主機作業系統:Windows 10
  • 虛擬化工具:QEMU
  • 虛擬機器CPU:cortex-a53(ARMv8架構,支援Arm64)
  • 虛擬機器作業系統:銀河麒麟 v4 (未安裝桌面)

問題

問題一:無法驗證xxx的由yyy頒發的證書

這個錯誤是在開發環境出現的,通過wget請求https服務時丟擲異常:

e6c9d24ely1h1di5e7jvoj20rl04h3zq

具體錯誤提示如圖所示,使用HttpClient發起請求一樣會報錯,這是因為安裝的作業系統沒有自帶根CA證書。安裝 ca-certificates 就可以解決:

sudo apt-get install -y ca-certificates

至於錯誤資訊中的建議:使用"--no-check-certificate"。使用https就是為了安全,直接無視就好了。

問題二:The SSL connnection could not be established

這個錯誤是在生產環境出現的,已經排除第一個問題。

img

錯誤的關鍵詞還有:AuthenticationException: The remote cert is invalid according to the validation procedure. 簡單點說就是:被訪問服務的證書無效。

這裡先貼出解決方案,一會再說原因。增加一個環境變數:

export DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER = 0

建議還是把它寫到 /etc/profile 中,然後用 source /etc/profile 生效。

下面來分析下這個問題產生的原因

在微軟的官方文件中可以找到關於這個配置的說明:

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.socketshttphandler?view=net-6.0

https://docs.microsoft.com/zh-cn/dotnet/core/runtime-config/networking

大概就是說.NET Core 2.1之後,HttpClient內部預設使用新寫的SocketsHttpHandler,但是也允許切換到之前的舊Handler,在Linux上之前使用的是CurlHandler,從這個名字上看應該是使用了libcurl這個庫,這個庫使用C語言寫的,.NET呼叫的時候會有一點效能損失,所以後來.NET拋棄了libcurl,自己實現了Http網路棧的處理,也就是SocketsHttpHandler。通過設定環境變數,將Http網路棧的處理回退到了CurlHandler。

那麼為什麼CurlHandler可以,SocketsHttpHandler卻不行呢?

在dotnet的github倉庫中有一些關於這個錯誤的issue:

https://github.com/dotnet/runtime/issues/26494
https://github.com/dotnet/runtime/issues/35880

我這裡總結核心問題就是SocketsHttpHandler驗證證書域名的問題,證書的域名中大小寫混合、包含下劃線、使用萬用字元等等會導致異常,有些問題可以通過修復SocketsHttpHandler解決,有些問題是因為依賴了OpenSSL,比如OpenSSL認為域名不應該包含下劃線,.NET這邊遇到此類的域名就會報錯,.NET也不會主動去解決。另外文中還提到舊版本的OpenSSL是沒問題的,新版本的OpenSSL才這樣,因為開發者認為新版本的做法更規範。.NETCore依賴的OpenSSL版本可以在這裡找到:https://docs.microsoft.com/zh-cn/dotnet/core/install/linux-ubuntu#dependenciesDocs

至於我遇到的是哪個問題,因為在本地環境沒有遇到這個問題,生產環境是別人去維護的,上線後也不方便去搞,所以暫時無法定位到具體原因。猜想可能有兩個原因:生產環境的OpenSSL相關庫版本比較新,而開發環境的OpenSSL庫版本比較舊;生產環境用到的證書域名不規範,測試環境用到的證書域名規範。

還有為什麼使用了CurlHandler就沒有問題呢?因為我這裡的curl沒有依賴openssl,在銀河麒麟v4中,curl依賴的是gnutls(SSL support is provided by GnuTLS.),如果已經安裝了curl可以使用這個命令看它依賴的包:apt-cache depends curl 我這裡顯示的是:

curl
  依賴: libc6
  依賴: libcurl3-gnutls
  依賴: zlib1g

如果沒有安裝curl,可以看看libcurl3-gnutls這個包是否存在:dpkg -s libcurl3-gnutls,如果存在這個包,則會顯示它的詳細資訊。

最後如果你想知道自己遇到了什麼錯誤,可以通過下邊的程式碼來獲取:

var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => {
    if (errors == SslPolicyErrors.None)
    {
        return true;
    }

    // todo: 在這裡輸出errors到日誌

    throw new AuthenticationException($"Ssl certificate validation failed when trying to connect to {message.RequestUri}, Error: {errors}.");
}

var client = new HttpClient(httpClientHandler);

以上就是本文的主要內容了,如有問題,歡迎反饋。

收穫更多架構知識,請關注微信公眾號 螢火架構。原創內容,轉載請註明出處。
掃描二維碼關注公眾號

相關文章