CURL error: [77] error setting certificate verify locations:

My domain is: app.ccgopvt.org

I ran this command: PHP procedure...

$sslCertPath = "/etc/letsencrypt/live/app.ccgopvt.org/fullchain.pem";
$ch = curl_init( $url );
// Assign CAINFO to the specific SSL Cert ONLY IF it is self-authorized.
curl_setopt( $ch, CURLOPT_CAINFO, $sslCertPath);
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt( $ch, CURLOPT_HEADER, 1);
curl_setopt( $ch, CURLOPT_CERTINFO, 1);
curl_setopt( $ch, CURLOPT_VERBOSE, 1);

$response = curl_exec($ch);
if ( ! ($response)) {
	$errno = curl_errno($ch);
	$errstr = curl_error($ch);
	$response = "postToURL - CURL error: [$errno] $errstr.";
}
curl_close($ch);

It produced this output:
postToURL - CURL error: [77] error setting certificate verify locations: CAfile: /etc/letsencrypt/live/app.ccgopvt.org/fullchain.pem CApath: /etc/ssl/certs.

My web server is (include version):
Server version: Apache/2.4.25 (Debian)
Server built: 2019-10-13T15:43:54

The operating system my web server runs on is (include version):
Debian GNU/Linux 9 \n \l

My hosting provider, if applicable, is: myself

I can login to a root shell on my machine (yes or no, or I don't know): yes

I'm using a control panel to manage my site (no, or provide the name and version of the control panel): no

The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot):
certbot 0.28.0

I can run curl directly from the command line for the same URL and I get a valid response. e.g...

$curl https://app.ccgopvt.org


  


You have reached the CCGOPVT Admin Demo Pages...

This behavior is specific to my web applications. Things were working, and suddenly they weren't. (Yes, of course something changed. I'm just at a loss for what!) The initial setting of $sslCertPath above was NULL. (That's my default setting on other servers.) But that produced the error. The above tries to assign the LetsEncrypt certificate path, but the result is the same.

The certificate seems to be valid according to the browser. I'm sure I am missing something basic in my understanding of how CURL uses SSL certificates. Somehow, I think this problem has to do with the interaction of the two. I'm hoping someone here can help.

Thanks in advance,

Ron

Why would you set that?

I'm having a hard time determining what you're trying to actually achieve and what's failing here.

1 Like

I want my CURL calls to use SSL.
As I mentioned, things were working (though probably not quite as I thought they were), but now I'm getting the CURL error.

Truthfully, I don't quite understand what I'm doing here. I have the impression that in order for CURL to use SSL in making an HTTP request, it needs to know which certificate to use. That seems to be the point of the CURLOPT_CAINFO option.

I'm also under the impression that I should use the certificate from LetsEncrypt for this purpose. There are many postings that refer to installing cacert.pem. I have this certificate on the server, but shouldn't I be using the certificate from LetsEncrypt?

1 Like

One more bit of information:

The following setting for CAINFO seems to work!

curl_setopt( $ch, CURLOPT_CAINFO, "/etc/apache2/ssl/cacert.pem" );

Setting CAINFO to the LetsEncrypt certificate does not...

curl_setopt( $ch, CURLOPT_CAINFO, "/etc/letsencrypt/live/app.ccgopvt.org/fullchain.pem" );

This latter setting results in the CURL error.

But you're using cu as a client, right? Why bother with the Let's Encrypt bits in that case?

You might need to point curl in PHP to the correct root certificate store for server cert validation, but that's something else entirely.

That is probably what I am missing. In this case, the client and the server are the same--but they may not be. I'm using CURL to make a request to a REST API--which I hope to keep separated.

Still, my question is: Shouldn't I be using the LetsEncrypt certificate for CURL? And if my REST server was indeed on another server, how would I specify the certificate store on the client? (The client actually being a server for the web application.)
Do I download a local copy and refer to that?

(Obviously, still a bit fuzzy on how to handle SSL certificates here.)

Not for a client, no.

Osiris, Thanks for responding, but can you help me to understand why not?

1 Like

I think there's a misconception here on how certificates work in the TLS (HTTPS) enviroment.

Certificates are usually for servers*. Servers present a certificate to authenticate themselves. To make this certificate worth something, it's signed by a certificate authority (CA). Let's Encrypt is such a CA. CA's have a thing that we call root certificate.

Root certificates are installed on the client, so that the client can verify a certificate chain send by the server. As there is more than one certificate authority in the world, there's more than one root certificate. These root certificates are usually bundled together, which is then called a trust store.

When connecting to a server with curl via https, curl automatically receives the certificate + ca certificate from the chain (during the connection handshake), verifies the signatures on all certificates, and checks if some certificate in the chain is installed in it's local trust store and if so, the connection is good to go.

As such, curl only needs to know which root certificates to trust. The specific chain by the server (and especially the leaf certificate) must not be given, that is transferred during the handshake anyway. Curl can often auto detect the trust store installed on your system (from your output above, on your system it's probably /etc/ssl/certs) and in this case, no manual setting of any CA-parameter is required client-side.

[* There's a thing called TLS client authentication were the client also presents a certificate. This mode is very rarely used in HTTPS enviroments]

2 Likes

This is very good. Thank you for the explanation. I've not seen one so clear in years of dealing with SSL issues. In your explanation, however, I find that I still have questions about this particular problem.

Yes, the "trust store" is located at /etc/ssl/certs. One thing I noticed while investigating was that I couldn't find anything from LetsEncrypt in that folder; but some root certificates are clearly identifiable. Others are not.

In this particular server, it seems that I am REQUIRED to provide a path to cacert.pem in order for CURL to "verify the peer's certificate". (This phrase quoted from the PHP documentation on curl_setopt().) And that is unique to this installation.

Mind you, this installation is a cloned virtual machine from other servers I am running. On those other servers, as noted above, I have set the CURLOPT_CAINFO option to NULL. That setting is not working on this installation, and I can't figure out why. (Not having a clear understanding of the meaning of these options or the process is clearly part of the problem.) One thing that is unique to this server, of course, is the certificate that was generated for this domain.

So now, I understand that CURLOPT_CAINFO is a reference to the trust store--not the certificate itself. And is generally not necessary. (Thank you.) But, where is the root certificate for LetsEncrypt? And why would I need this setting on this particular server, and not on my other servers?

Finally, if I am asking stupid questions, if you can point me to a good reference document on this subject, it would be greatly appreciated.

Once again, thank you for your great, and concise, explanation.

I would usually say that curl can't find the trust store, but that doesn't fit with the other facts here. Sometimes servers are misconfigured and do not send the required intermediate certificates, but the server on https://app.ccgopvt.org:443 is sending the chain correctly (I just checked).

I think something weird is going on with the PHP-curl bridge. What's the error message you're getting when trying without CAINFO?

There's actually two root certificates for Let's Encrypt. Let's Encrypts parent organization, the Internet Security Research Group (ISRG) owns a root called "ISRG Root X1", which is hopefully in your trust store (not sure though, since your Debian version is rather old).

However, that root is relatively new (in stores since approx ~2016) and so Let's Encrypt is using a second root from a different CA, called "DST Root CA X3". This has ensured broad compatibility in the past.

The chain used is currently in the process of being changed, see Production Chain Changes.

Right now your website is still using the older pre-May 4 chain (no change here), but upon the next renewal that will change.

1 Like

Interesting... O.K. in my application, I use a configuration setting for $sslCertPath. (That's why it appears as an added line in my original code sample.) I found that setting that parameter to NULL allows a certificate from a trusted authority to verify.

If I'm using a self-generated certificate (as I often do in testing), I need to specify the path to the certificate I generated.

So, to answer your question, I first simply commented out the call to curl_setopt("CURLOPT_CAINFO"...) completely... and my test procedure worked!

I then tried to restore the configuration--setting $sslCertPath = NULL. I am once again getting the error message:

postToURL - CURL error: [77] error setting certificate verify locations: CAfile: CApath: /etc/ssl/certs.

I still have to wonder why that configuration setting is working on other servers, and not this one. At this point, however, I have a much clearer idea of what I am dealing with. (Thank you.)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.