Public 在不使用系统证书的情况下,curl 中的键固定无法工作

Public key pinning in curl does not work without using certificates from the system

我正在尝试将 libcurl 与 public 键固定结合使用,以便在下载文件时验证服务器的真实性。

Curl 经过编译,因此它不使用系统上的任何证书,而仅依赖于它从用户那里收到的证书:

./configure --without-ca-bundle --without-ca-path --without-ca-fallback && make

首先,我获得了服务器证书的 public 密钥的 sha256 总和,如 here:

所述
$ openssl s_client -servername www.example.com -connect www.example.com:443 < /dev/null | sed -n "/-----BEGIN/,/-----END/p" > www.example.com.pem
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
verify return:1
depth=0 C = US, ST = California, L = Los Angeles, O = Internet Corporation for Assigned Names and Numbers, OU = Technology, CN = www.example.org
verify return:1
DONE
$ openssl x509 -in www.example.com.pem -pubkey -noout > www.example.com.pubkey.pem
$ openssl asn1parse -noout -inform pem -in www.example.com.pubkey.pem -out www.example.com.pubkey.der
$ openssl dgst -sha256 -binary www.example.com.pubkey.der | openssl base64
xmvvalwaPni4IBbhPzFPPMX6JbHlKqua257FmJsWWto=

然后我在 libcurl 中设置 public 密钥的哈希值和其他相关选项:

curl_easy_setopt(conn, CURLOPT_PINNEDPUBLICKEY, "sha256//xmvvalwaPni4IBbhPzFPPMX6JbHlKqua257FmJsWWto=");
curl_easy_setopt(conn, CURLOPT_SSL_VERIFYPEER, 1);
curl_easy_setopt(conn, CURLOPT_SSL_VERIFYHOST, 2);
curl_easy_setopt(conn, CURLOPT_URL, "https://example.com/index.html");
curl_easy_setopt(conn, CURLOPT_VERBOSE, 1);
curl_code = curl_easy_perform(conn);
if (curl_code != CURLE_OK)
{
    printf("%s\n", curl_easy_strerror(curl_code));
}

下载失败并出现错误:

* SSL certificate problem: unable to get local issuer certificate
...
Peer certificate cannot be authenticated with given CA certificates

嗯,似乎 curl 正在寻找一些证书,所以我重新编译它以使其包含默认证书:

./configure && make

现在,下载开始了:

* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
...
*  SSL certificate verify ok.
*    public key hash: sha256//xmvvalwaPni4IBbhPzFPPMX6JbHlKqua257FmJsWWto=
...

在CURLOPT_PINNEDPUBLICKEY文档中,是这样解释的:

When negotiating a TLS or SSL connection, the server sends a certificate
indicating its identity. A public key is extracted from this certificate
and if it does not exactly match the public key provided to this option,
curl will abort the connection before sending or receiving any data. 

所以我的印象是 curl 只需要来自用户的 public 密钥,以便将其与从服务器证书中提取的 public 密钥进行比较。

我在这里错过了什么?

问题是 CURLOPT_SSL_VERIFYPEER 设置为 1 启用 CA 固定。 Curl 接受同时设置 CA 固定和 public-键固定,并且因为在 public 键固定之前尝试了 CA 固定,所以 CA 固定失败并且它永远不会执行 public 键固定。

解决方案是显式在执行public键固定时禁用CA固定:

curl_easy_setopt(conn, CURLOPT_SSL_VERIFYPEER, 0);

这需要明确地完成,因为 CURLOPT_SSL_VERIFYPEER 的默认值为 1。

注意: 通常应避免将 CURLOPT_SSL_VERIFYPEER 设置为 0,但在这种情况下它是安全的,因为正在完成 public-key pinning。

有关详细信息,另请参阅 this curl issue