使用 curl 進行 ssl 認證


SSL 認證

可以將 SSL 服務器與客戶端之間的通信配置為使用單向或雙向 SSL 認證。

單向 SSL 認證一般是客戶端利用服務器傳過來的信息驗證服務器的合法性,服務器的合法性包括:證書是否過期,發行服務器證書的 CA 是否可靠,發行者證書的公鑰能否正確解開服務器證書的“發行者的數字簽名”,服務器證書上的域名是否和服務器的實際域名相匹配。

雙向 SSL 認證則除了需要對服務器的合法性進行認證,還需要按照單向 SSL 認證方法對客戶端的合法性進行認證。

在金融支付過程中,對安全要求級別比較高的接口,不僅要驗證簽名,還要進行雙向驗證 SSL 證書,因此有些就需要安裝在服務開通之后第三方給我們發送的安全證書了。

為了便於更好的認識和理解 SSL 協議,這里着重介紹 SSL 協議的握手協議。SSL 協議既用到了公鑰加密技術又用到了對稱加密技術,對稱加密技術雖然比公鑰加密技術的速度快,可是公鑰加密技術提供了更好的身份認證技術。SSL 的握手協議非常有效的讓客戶和服務器之間完成相互之間的身份認證,其主要過程如下:
  ① 客戶端的瀏覽器向服務器傳送客戶端 SSL 協議的版本號,加密算法的種類,產生的隨機數,以及其他服務器和客戶端之間通訊所需要的各種信息。
  ② 服務器向客戶端傳送 SSL 協議的版本號,加密算法的種類,隨機數以及其他相關信息,同時服務器還將向客戶端傳送自己的證書。
  ③ 客戶利用服務器傳過來的信息驗證服務器的合法性,服務器的合法性包括:證書是否過期,發行服務器證書的 CA 是否可靠,發行者證書的公鑰能否正確解開服務器證書的“發行者的數字簽名”,服務器證書上的域名是否和服務器的實際域名相匹配。如果合法性驗證沒有通過,通訊將斷開;如果合法性驗證通過,將繼續進行第四步。
  ④ 用戶端隨機產生一個用於后面通訊的“對稱密碼”,然后用服務器的公鑰(服務器的公鑰從步驟②中的服務器的證書中獲得)對其加密,然后將加密后的“預主密碼”傳給服務器。
  ⑤ 如果服務器要求客戶的身份認證(在握手過程中為可選),用戶可以建立一個隨機數然后對其進行數據簽名,將這個含有簽名的隨機數和客戶自己的證書以及加密過的“預主密碼”一起傳給服務器。
  ⑥ 如果服務器要求客戶的身份認證,服務器必須檢驗客戶證書和簽名隨機數的合法性,具體的合法性驗證過程包括:客戶的證書使用日期是否有效,為客戶提供證書的 CA 是否可靠,發行 CA 的公鑰能否正確解開客戶證書的發行 CA 的數字簽名,檢查客戶的證書是否在證書廢止列表(CRL)中。檢驗如果沒有通過,通訊立刻中斷;如果驗證通過,服務器將用自己的私鑰解開加密的“預主密碼”,然后執行一系列步驟來產生主通訊密碼(客戶端也將通過同樣的方法產生相同的主通訊密碼)。
  ⑦ 服務器和客戶端用相同的主密碼即“通話密碼”,一個對稱密鑰用於 SSL 協議的安全數據通訊的加解密通訊。同時在 SSL 通訊過程中還要完成數據通訊的完整性,防止數據通訊中的任何變化。
  ⑧ 客戶端向服務器端發出信息,指明后面的數據通訊將使用的步驟⑦中的主密碼為對稱密鑰,同時通知服務器客戶端的握手過程結束。
  ⑨ 服務器向客戶端發出信息,指明后面的數據通訊將使用的步驟⑦中的主密碼為對稱密鑰,同時通知客戶端服務器端的握手過程結束。
  ⑩ SSL 的握手部分結束,SSL 安全通道的數據通訊開始,客戶和服務器開始使用相同的對稱密鑰進行數據通訊,同時進行通訊完整性的檢驗。

  雙向認證 SSL 協議的具體過程
  ① 瀏覽器發送一個連接請求給安全服務器。
  ② 服務器將自己的證書,以及同證書相關的信息發送給客戶瀏覽器。
  ③ 客戶瀏覽器檢查服務器送過來的證書是否是由自己信賴的 CA 中心所簽發的。如果是,就繼續執行協議;如果不是,客戶瀏覽器就給客戶一個警告消息:警告客戶這個證書不是可以信賴的,詢問客戶是否需要繼續。
  ④ 接着客戶瀏覽器比較證書里的消息,例如域名和公鑰,與服務器剛剛發送的相關消息是否一致,如果是一致的,客戶瀏覽器認可這個服務器的合法身份。
  ⑤ 服務器要求客戶發送客戶自己的證書。收到后,服務器驗證客戶的證書,如果沒有通過驗證,拒絕連接;如果通過驗證,服務器獲得用戶的公鑰。
  ⑥ 客戶瀏覽器告訴服務器自己所能夠支持的通訊對稱密碼方案。
  ⑦ 服務器從客戶發送過來的密碼方案中,選擇一種加密程度最高的密碼方案,用客戶的公鑰加過密后通知瀏覽器。
  ⑧ 瀏覽器針對這個密碼方案,選擇一個通話密鑰,接着用服務器的公鑰加過密后發送給服務器。
  ⑨ 服務器接收到瀏覽器送過來的消息,用自己的私鑰解密,獲得通話密鑰。
  ⑩ 服務器、瀏覽器接下來的通訊都是用對稱密碼方案,對稱密鑰是加過密的。
  上面所述的是雙向認證 SSL 協議的具體通訊過程,這種情況要求服務器和用戶雙方都有證書。單向認證 SSL 協議不需要客戶擁有 CA 證書,具體的過程相對於上面的步驟,只需將服務器端驗證客戶證書的過程去掉,以及在協商對稱密碼方案,對稱通話密鑰時,服務器發送給客戶的是沒有加過密的(這並不影響 SSL 過程的安全性)密碼方案。 這樣,雙方具體的通訊內容,就是加過密的數據,如果有第三方攻擊,獲得的只是加密的數據,第三方要獲得有用的信息,就需要對加密的數據進行解密,這時候的安全就依賴於密碼方案的安全。而幸運的是,目前所用的密碼方案,只要通訊密鑰長度足夠的長,就足夠的安全。這也是我們強調要求使用 128 位加密通訊的原因。

認證實現

與 SSL 單向認證相關的 curl_easy_setopt 選項有以下幾個:

  • CURLOPT_SSL_VERIFYPEER: cURL 是否驗證對等證書(peer's certificate),值為 1,則驗證,為 0 則不驗證。要驗證的交換證書可以在 CURLOPT_CAINFO 選項中設置,或在 CURLOPT_CAPATH中設置證書目錄。
  • CURLOPT_SSL_VERIFYHOST:值為1 : cURL 檢查服務器SSL證書中是否存在一個公用名(common name);值為2: cURL 會檢查公用名是否存在,並且是否與提供的主機名匹配;0 為不檢查名稱。這里的 common name 是在創建證書過程中指定,例如 subj 選項值中的 /CN 值; openssl req -subj "/C=CN/ST=IL/L=ShenZhen/O=Tencent/OU=Tencent/CN=luffichen_server.tencent.com/emailAddress=luffichen@www.tencent.com" ...
  • CURLOPT_CAINFO:一個保存着1個或多個用來讓服務端驗證的證書的文件名。這個參數僅僅在和CURLOPT_SSL_VERIFYPEER一起使用時才有意義。
  • CURLOPT_CAPATH:一個保存着多個CA證書的目錄。這個選項是和CURLOPT_SSL_VERIFYPEER一起使用的。

與 SSL 雙向認證相關的 curl_easy_setopt 選項有以下幾個:

  • CURLOPT_SSLCERT:客戶端證書路徑
  • CURLOPT_SSLCERTTYPE:證書的類型。支持的格式有"PEM" (默認值), "DER"和"ENG"。
  • CURLOPT_SSLKEY:客戶端私鑰的文件路徑
  • CURLOPT_SSLKEYTYPE:客戶端私鑰類型,支持的私鑰類型為"PEM"(默認值)、"DER"和"ENG"。
  • CURLOPT_KEYPASSWD:客戶端私鑰密碼,私鑰在創建時可以選擇加密。
if(!oneway_certification)
{
    // 驗證服務器證書有效性
    curl_easy_setopt(m_curl_handler, CURLOPT_SSL_VERIFYPEER, 1);
    // 檢驗證書中的主機名和你訪問的主機名一致
    curl_easy_setopt(m_curl_handler, CURLOPT_SSL_VERIFYHOST, 2);
    // 指定 CA 證書路徑
    curl_easy_setopt(m_curl_handler, CURLOPT_CAINFO, m_ca_cert_file.c_str());
}
else
{
    // 不驗證服務器證書
    curl_easy_setopt(m_curl_handler, CURLOPT_SSL_VERIFYPEER, 0);
    curl_easy_setopt(m_curl_handler, CURLOPT_SSL_VERIFYHOST, 0);
}

if(!client_cert_file.empty())
{
    // 客戶端證書,用於雙向認證
    curl_easy_setopt(m_curl_handler, CURLOPT_SSLCERT, client_cert_file.c_str());
}

if(!client_cert_type.empty())
{
    // 客戶端證書類型,用於雙向認證
    curl_easy_setopt(m_curl_handler, CURLOPT_SSLCERTTYPE, client_cert_type.c_str());
}

if(!private_key.empty())
{
    // 客戶端證書私鑰,用於雙向認證
    curl_easy_setopt(m_curl_handler, CURLOPT_SSLKEY, private_key.c_str());
}

if(!private_key_type.empty())
{
    // 客戶端證書私鑰類型,用於雙向認證
    curl_easy_setopt(m_curl_handler, CURLOPT_SSLKEYTYPE, private_key_type.c_str());
}

if(!private_key_passwd.empty())
{
    // 客戶端證書私鑰密碼
    curl_easy_setopt(m_curl_handler, CURLOPT_KEYPASSWD, private_key_passwd.c_str());
}

說明:設置 curl 選項時,這里對空值進行判斷,如果為空,則不進行雙向認證了。

問題解決

curl不支持 https

curl -V
curl 7.56.0-DEV (Linux) libcurl/7.56.0-DEV OpenSSL/1.0.1e zlib/1.2.3
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IPv6 Largefile NTLM SSL libz UnixSockets HTTPS-proxy 

如果沒有,Features 中沒有 ssl,則需要重新編譯支持 ssl 的 curl 版本(./configure --with-ssl)

在編譯時,可按照 curl 官網給出的方法進行:

./configure --with-ssl
If you have OpenSSL installed somewhere else (for example, /opt/OpenSSL) and you have pkg-config installed, set the pkg-config path first, like this:

env PKG_CONFIG_PATH=/opt/OpenSSL/lib/pkgconfig ./configure --with-ssl
Without pkg-config installed, use this:

./configure --with-ssl=/opt/OpenSSL

If you insist on forcing a build without SSL support, even though you may have OpenSSL installed in your system, you can run configure like this:

./configure --without-ssl

If you have OpenSSL installed, but with the libraries in one place and the header files somewhere else, you have to set the LDFLAGS and CPPFLAGS environment variables prior to running configure. Something like this should work:

CPPFLAGS="-I/path/to/ssl/include" LDFLAGS="-L/path/to/ssl/lib" ./configure
If you have shared SSL libs installed in a directory where your run-time linker doesn't find them (which usually causes configure failures), you can provide the -R option to ld on some operating systems to set a hard-coded path to the run-time linker:

LDFLAGS=-R/usr/local/ssl/lib ./configure --with-ssl

另外要注意 openssl 不同版本對 ssl 協議版本的支持。僅 openssl 1.0.2 及其以上版本目前支持 TLS 1.2 版本的。

在編譯 openssl 1.0.2h 時發現其生成的 libssl 文件為 libss.so.1.0.0/libcrypto.so.1.0.0 而不是 libssl.so.1.0.2/libcrypto.so.1.0.2,這里的 ssl 庫的版本和軟件版本的編號是不一致的,這么做的原因 Richard Levitte 做了解釋:

We recognised that our shared library version numbering was confusing, so from OpenSSL version 1.1.0 and up, the shared library version retains the two first digits of the OpenSSL version only, which reflects our intent that for any versions x.y.z where x.y stays the same, ABI backward compatibility will be maintained.

SSL certificate problem, verify that the CA cert is OK

CURLOPT_SSL_VERIFYPEER 為 1 時,表示啟用了驗證訪問的服務器合法性,且必須設置 CURLOPT_CAINFOCURLOPT_CAPATH 其中一個,而 CURLOPT_SSL_VERIFYHOST 為 2 時,表示驗證 CA 證書中的 common name 是否與訪問的服務器域名是否一致。在測試的時候,需要記得為客戶端側機器添加相應的 host 域名 IP 解析,如果直接使用 IP 訪問也會報 SSL certificate problem, verify that the CA cert is OK 錯誤。

curl: (60) SSL certificate : unable to get local issuer certificate

問題的原因有很多,這里只列舉一二。

在驗證服務器證書時,找不到CA證書,如果正確設置了 cainfo 或 capath 參數且 CA 證書已經是 rootCA,依然出錯,那么可能是證書生成的時候出錯,再重新生成一個;如果 CA 證書由一個中間證書簽發,rootCA 簽發中間證書,那么如果服務器沒有提供中間證書,在驗證過程中,openssl 在形成完整的證書鏈也會報這個錯誤,所以 cat intermediate.crt >> domain.crt 將所有中間證書與rootCA證書捆綁在一起。

參考鏈接

curl_easy_setopt - set options for a curl easy handle
curl_easy_setopt
ssl介紹以及雙向認證和單向認證原理
how to install curl and libcurl
OpenSSL 1.0.2h generates libss.so.1.0.0 instead of libssl.so.1.0.2
CURL使用SSL證書訪問HTTPS
HowTo: Create CSR using OpenSSL Without Prompt (Non-Interactive)
curl: (60) SSL certificate : unable to get local issuer certificate


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM