The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何讓 cURL 可以連接自簽憑證提供的 TLS/SSL 加密連線網站

當我在 Linux 環境下開發或測試 ASP.NET Core 應用程式時,經常會需要用 curl 命令快速測試或取得網站內容,這時如果你連上 https:// 加密連線網址,就會立刻遇到 curl: (60) SSL certificate problem: unable to get local issuer certificate 的問題,關於這個問題我打算用這篇文章徹底搞定它。

建立測試環境

請先安裝 .NET Core SDK 到你的主機,然後執行以下命令建立並啟動 ASP.NET Core 網站:

dotnet new mvc -n mvc1
cd mvc1
dotnet run

上述三個命令就會快速啟動 ASP.NET Core 內建的 Kestrel 伺服器,並監聽 Port 5001 為加密連線:

$ dotnet run
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/will/build/mvc1

建立測試憑證

.NET Core SDK 有內建一組 .NET CLI 工具,透過 dotnet dev-certs https 命令就可以快速產生一組以 localhost 為主的自簽憑證,但是在不同作業系統有不同的行為,需要特別注意!

  • Windows

    dotnet dev-certs https --trust
    

    如果想要將憑證註冊到「受信任的根憑證授權單位」(Trusted Root CA),讓整台電腦預設信任這張自簽憑證,只要加上 --trust-t 參數即可。如果沒有加上 --trust 參數,一樣也可以產生憑證,但只會註冊到「個人」憑證儲存區。

  • macOS

    dotnet dev-certs https --trust
    

    新版 .NET Core 3.1 SDK 也開始支援 --trust 參數,可以讓整台電腦預設信任這張自簽憑證。

  • Linux

    dotnet dev-certs https
    

    由於 .NET Core 3.1 SDK 在 Linux 並沒有支援 --trust 參數,所以這個命令只會幫你產生憑證到 ~/.dotnet/corefx/cryptography/x509stores/my 目錄下,並不會自動幫你註冊到系統中。

    $ ls ~/.dotnet/corefx/cryptography/x509stores/my/
    6F30C86687D3A9B2503A17DDA64D66E79EE539C5.pfx
    

無論你在哪個作業系統下使用 dotnet dev-certs https 命令,都可以透過以下命令,將 PFX 憑證儲存到指定路徑:

dotnet dev-certs https -ep localhost.pfx -p 123123

由於透過 dotnet dev-certs https 所建立的憑證都是以 PKCS#12 的格式儲存的 (*.pfx),一般在 Linux 都是用 PEM 格式為主,所以需要做一次格式轉換。透過以下命令可以將憑證的 公開金鑰 (Public Key) 改用 PEM 格式儲存成 localhost.crt 檔案:

openssl pkcs12 -in localhost.pfx -out localhost.crt -nokeys -nodes

注意:由於所有 PKCS#12 格式的 PFX 憑證,都要求一定要輸入密碼,當你看到 Enter Import Password: 的時候,只要直接按下 Enter 即可!

使用 cURL 連接由自簽憑證加密的 TLS/SSL 加密連線

你只要用 curl 連接 Kestrel 提供的加密連線網站,就會立刻發現以下錯誤:

$ curl https://localhost:5001/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

這個錯誤主要是告訴你,它找不到 local issuer certificate (本地發行者憑證),所以無法「驗證」遠端加密連線的安全性,這是一種「信任」問題,並不是真的連不上,最簡單的解法,就是忽略「驗證」步驟,直接加上 --insecure 參數即可:

curl https://localhost:5001/ --insecure

無論你想用 curl 連接哪個 TLS/SSL 網站,當這個「信任」問題發生時,最好的解決方法還是用「信任」的方式來解決較為妥當!

如果你已經拿到 localhost.crt 憑證檔,就可以透過 curl 的 --cacert 參數直接指定該憑證,這個就可以用來驗證伺服器所使用的 TLS/SSL 加密連線:

curl https://localhost:5001/ --cacert localhost.crt

如果你已經拿到該網站的 根授權憑證 (Root CA) 的 公開金鑰 (Public Key) 檔案,也可以直接加入到 curl 正在使用的 憑證授權單位(CA) 的憑證清單中,如此一來,未來所有從 curl 發出的加密連線請求,就會預設通過「信任」驗證,以下是不同作業系統平台可以使用的方法:

  • Linux

    由於 Linux 作業系統的 Distro 版本繁多,特定檔案的儲存路徑雖然大同小異,但是遇到不同的時候,也可能會讓你找很久。

    我個人是利用 strace 工具,分析 curl 執行時到底使用了哪一個 ca-certificates.crt 檔案,找出確切的檔案路徑,然後手動將新的憑證加入即可!

    如下範例,這台電腦的 CA 憑證清單就放在 /etc/ssl/certs/ca-certificates.crt

    $ strace curl https://localhost:5001 2>&1 | grep ca-certificates.crt
    openat(AT_FDCWD, "/etc/ssl/certs/ca-certificates.crt", O_RDONLY) = 6
    

    除了用 strace 之外,也可以在執行 curl 的時候,隨便指定一個不存在的 CA 檔案,讓 curl 出現錯誤,並顯示預設的 CApath 路徑,在該路徑下,就會有預設的 ca-certificates.crt 檔案:

    $ curl --cacert xxxxxx https://localhost:5001/
    curl: (77) error setting certificate verify locations:
      CAfile: xxxxxx
      CApath: /etc/ssl/certs
    

    不過,在 Ubuntu 20.04 LTS 作業系統中,更新系統管理的 CA 憑證清單有標準作法,建議透過以下命令進行更新:

    sudo cp localhost.crt /usr/local/share/ca-certificates/
    sudo update-ca-certificates
    
  • Windows (使用 PowerShell 執行)

    由於 Windows 10 已經內建 curl.exe 命令列工具,這個工具是 Microsoft 微調過的版本,大部分參數都完全一樣,唯獨驗證憑證的方式跟其他平台不同。Windows 的 curl 不會有自己的 ca-certificates.crt 檔案,而是會自動讀取 Windows 作業系統中的註冊的憑證資訊。因此,只要把你拿到的憑證匯入到 Cert:\CurrentUser\Root\ 即可:

    Import-Certificate -FilePath .\localhost.crt -CertStoreLocation Cert:\CurrentUser\Root\
    

    上述命令不需要使用系統管理者權限執行!

  • macOS

    sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain localhost.crt
    

使用 OpenSSL 快速取得遠端網站的憑證

有時候我們想連接的網站,並不是我們自己建立的憑證,當該網站的憑證不受 curl 信任的時候,就需要把對方的憑證抓回來,加入到 curl 正在使用的 憑證授權單位(CA) 的憑證清單中,以下我示範幾個可以方便用來抓取憑證的命令:

  • 顯示該網站正在使用的所有憑證鏈完整資訊

    echo QUIT | openssl s_client -connect example.com:443 -servername example.com -showcerts
    
  • 僅取得憑證內容

    使用 sed 取得憑證內容

    openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | sed -n '/^-----BEGIN CERT/,/^-----END CERT/p'
    

    使用 awk 取得憑證內容

    openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | awk '/^-----BEGIN CERT/,/^-----END CERT/'
    
  • 將憑證儲存到檔案中

    openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | sed -n '/^-----BEGIN CERT/,/^-----END CERT/p' > example.crt
    

相關連結

留言評論