问题
I am trying to use libcurl with public-key pinning in order to verify a server's authenticity when downloading a file.
Curl is compiled so that it doesn't use any certificates on the system, but only relies on certificates it receives from the user:
./configure --without-ca-bundle --without-ca-path --without-ca-fallback && make
First I obtain the sha256 sum of the server certificate's public key, as explained 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=
Then I set the public key's hash and other related options in libcurl:
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));
}
The download fails with an error:
* SSL certificate problem: unable to get local issuer certificate
...
Peer certificate cannot be authenticated with given CA certificates
Well, it seems curl is looking for some certificates, so I recompile it in order for it to include the default certificates:
./configure && make
Now, the download will work:
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: none
...
* SSL certificate verify ok.
* public key hash: sha256//xmvvalwaPni4IBbhPzFPPMX6JbHlKqua257FmJsWWto=
...
In the CURLOPT_PINNEDPUBLICKEY documentation, it is explained:
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.
So my impression was that curl only needs the public key from the user, in order to compare it with the public key extracted from the server's certificate.
What am I missing here?
回答1:
The problem is that CURLOPT_SSL_VERIFYPEER
being set to 1 enables CA pinning. Curl accepts setting both CA pinning and public-key pinning at the same time, and because CA pinning is tried before public key pinning, the CA pinning fails and it never gets to do the public key pinning.
The solution is to explicitly disable CA pinning when doing public key pinning:
curl_easy_setopt(conn, CURLOPT_SSL_VERIFYPEER, 0);
This needs to be done explicitly because the default value for CURLOPT_SSL_VERIFYPEER
is 1.
NOTE: setting CURLOPT_SSL_VERIFYPEER
to 0 should generally be avoided, but in this case it is safe because public-key pinning is being done.
For more details also see this curl issue.
来源:https://stackoverflow.com/questions/52114653/public-key-pinning-in-curl-does-not-work-without-using-certificates-from-the-sys