Reputation: 61388
Trying to make CURL in PHP work with a self signed certificate. I've made a copy of the cert file available to the client code, and I specify the path to the cert file both in CURLOPT_CAINFO
and CURLOPT_CAPATH
. Still, I'm getting error 60: SSL certificate problem: unable to get local issuer certificate
.
Here are the repro steps. All on Linux (Debian Stretch in my case). Replace example.com with a relevant hostname.
First, I'd generate a private key:
openssl genrsa -out key.pem 2048
Compose a config file:
[req]
prompt=no
distinguished_name=dn
req_extensions=ext
x509_extensions=ext
[dn]
[email protected]
CN=example.com
O=Seva Alekseyev
L=Chicago
ST=IL
C=US
[ext]
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=@alt
[alt]
DNS=example.com
Save as req.txt, generate a self signed cert:
openssl req -x509 -new -config req.txt -days 3650 -key key.pem -out example.cer
Install example.cer
and key.pem
in Apache under hostname example.com
. Browse to make sure the basic setup works (modulo the scary security message).
Now, the client. Placed a copy of example.cer
under $path
. The PHP code goes:
$cu = curl_init("https://example.com/");
curl_setopt_array($cu, array(
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CAINFO => "$path/example.cer",
CURLOPT_CAPATH => "$path/example.cer"
));
$r = curl_exec($cu);
$c = curl_errno($cu);
$s = curl_error($cu);
curl_close($cu);
echo "$c $s";
Then the error message.
What am I missing here? Some guides suggest the value of CURLOPT_CAINFO/CAPATH should a folder instead, with serial-based symlinks pointing to cert files. Tried that too, same error. The document at https://curl.haxx.se/docs/sslcerts.html says:
Get a CA certificate that can verify the remote server and use the proper option to point out this CA cert for verification when connecting.
But there's no CA there, no cert chain. The signing cert is itself. Should I somehow transform the cert so that CURL sees it as a CA one? Should I generate a fake CA cert first, and sign the SSL cert with that one?
Command line curl, as in curl --cacert example.cer https://example.com/
, pops the same message.
Related question here, but I'd rather not mess with systemwide settings.
Upvotes: 0
Views: 3050
Reputation: 61388
The keyUsage
line under [ext]
must include keyCertSign
, like this:
keyUsage=digitalSignature,keyEncipherment,keyCertSign
Otherwise, it's not a CA cert as far as OpenSSL is concerned.
OBTW, the basicConstraints=CA:true
line under [ext]
, suggested by Steffen, is not necessary, I've checked. At least with CURL 7.52.1 and OpenSSL 1.0.2r it's not.
In the client code, CURLOPT_CAPATH
is not necessary, either. CURL supports two alternative ways of specifying the root CA cert bundle. CURLOPT_CAINFO
makes CURL read and parse a single file, potentially with multiple certificates in it. CURLOPT_CAPATH
makes CURL scan a directory with certificate files identified by their serial numbers - or symlinks to those, as generated by c_rehash
. Since in my scenario the effective root CA cert bundle has exactly one cert, the one file approach is sufficient.
Doesn't work under Windows, at least with command line CURL 7.55.1. The Windows version of CURL uses the built-in Schannel library for its SSL implementation, and ignores the --cacert
option, instead relying on Windows' built-in trusted CA store. See here.
It might be possible to rebuild CURL for Windows against a different SSL implementation, but the trouble is hardly worth it. Windows comes with its own fleet of HTTP(S) clients.
Upvotes: 1
Reputation: 123531
The certificates pointed to by CURLOPT_CAINFO/CAPATH are expected to be CA certificates - at least when OpenSSL is used. This means that your self-signed certificate need also to be a CA certificate, i.e. it should not only be for serverAuth
but also have basic constraints CA:true
.
Upvotes: 0