2

This is the script, working on my dev machine:

$certPath = SITE_ROOT.'/certs/GoDaddyRootCertificateAuthority-G2.crt';
$options = [
    CURLOPT_POST => 1,
    CURLOPT_URL => 'https://uat.dwolla.com/oauth/rest/offsitegateway/checkouts',
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_POSTFIELDS => json_encode(['name'=>'value']),
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_CAINFO => $certPath,
];
$ch = curl_init();

curl_setopt_array($ch, $options);
if( ! $result = curl_exec($ch)) $err = curl_error($ch);
curl_close($ch);

if(!$result) echo $err;
else print_r(json_decode($result,true));

echo '<br/><br/>';
readfile($certPath); //output cert on screen
echo '<br/><br/>';

No issues. Once I move it to my production environment, the cURL connection fails with this error:

SSL certificate problem: unable to get local issuer certificate

  • The same .crt contents are printed, so I know the path to the certificate is not the problem.
  • Both environments use PHP 5.6.23 on Apache 2.4
  • Dev machine is Win 7 x64, prod machine is Linux CentOS 7

I don't know where to start looking for the cause. Why is the script not working in production?

UPDATE: Thanks to @blackpen's great tip in comments, I learned about the CURLOPT_VERBOSE option used to generate a log of the connection. Here is the output in the broken production environment:

  • Hostname was NOT found in DNS cache
  • Trying 104.20.47.245...
  • Connected to uat.dwolla.com (104.20.47.245) port 443 (#0)
  • successfully set certificate verify locations:
  • CAfile: /path/to/GoDaddyRootCertificateAuthority-G2.crt CApath: none
  • SSL certificate problem: unable to get local issuer certificate
  • Closing connection 0

Here is the log from the same script, but from the working development environment:

  • Hostname in DNS cache was stale, zapped
  • Trying 104.20.48.245...
  • Connected to uat.dwolla.com (104.20.48.245) port 443 (#0)
  • Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
  • successfully set certificate verify locations:
  • CAfile: /path/to/GoDaddyRootCertificateAuthority-G2.crt
    CApath: none
  • NPN, negotiated HTTP1.1
  • SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
  • Server certificate:
  • ...(cert details)
  • SSL certificate verify ok.
  • ... (more POST details)
BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • Judging by the name of the cert in your code I think you have the right one, but if it's incorrect, than OpenSSL is probably falling back on your cert path and the dev machine has the proper certs installed but not the production one. Are there any permissions issues on production that might prevent the server from reading the crt file? – drew010 Aug 26 '16 at 23:08
  • @drew010 I think the cert is correct but maybe I'm wrong: if I remove the `CURLOPT_CAINFO` option (in dev), I get the same error that I see in prod. If I manually edit the cert (again in dev) sometimes the code still works (surprising), and other times (with different edits) I get the error `error setting certificate verify locations`. As for reading permissions, consider that the cert contents get properly output to screen by the `readfile()` command above – BeetleJuice Aug 26 '16 at 23:14
  • 1
    One of your servers is Windows, other being Linux. Right? Could there be path format problem? – blackpen Aug 26 '16 at 23:28
  • @blackpen I wondered the same thing, but after `readfile()` (near bottom of the script in the OP) correctly output the certificate contents, I dismissed that possibility. What do you think? – BeetleJuice Aug 26 '16 at 23:31
  • @BeetleJuice, Do you want to try to debug curl? .... _curl_setopt($ch, CURLOPT_VERBOSE, 1); curl_setopt($ch, CURLOPT_STDERR, fopen('file://tmp/php_curl.log', 'w'));_ – blackpen Aug 26 '16 at 23:49
  • @blackpen What a great tip! thanks. I updated the OP with the log from both the successful and failed attempts – BeetleJuice Aug 27 '16 at 00:08
  • @BeetleJuice In the dev machine, if you see it succeed when you supply a non-existent file to CURLOPT_CAINFO, that may be because of how the openssl works (I assume you are using openssl). ... Ref: Look at the bottom of the page at https://bugs.launchpad.net/ubuntu/+source/openssl/+bug/396818 ... _That's why we observe that "-CApath /nonsense" adds the default path. Additionally, loading an arbitrary CA file will work too: openssl s_client -quiet -CAfile /etc/ssl/certs/Visa_eCommerce_Root.pem -connect google.com:443_ ..... You could try to debug it with _openssl s_client_ – blackpen Aug 27 '16 at 00:28
  • If you have proper previleges, you could run the strace as shown in _https://bugs.launchpad.net/ubuntu/+source/openssl/+bug/396818_ .... and see which cafile is it using for success case and which cafile is it using when it fails. – blackpen Aug 27 '16 at 00:35
  • @blackpen When I use a non-existent file in the dev environment I get `error setting certificate verify locations: CAfile: /path/to/file.crt`. I got it to work (not exactly sure why) and posted an answer. Take a look – BeetleJuice Aug 27 '16 at 00:35

1 Answers1

2

I was able to get the script to work in both environments by replacing the .crt that worked only in the dev environments with a file named cacert.pem taken from here

I still don't know what exactly was going on but I suspect that it may have something to do with the formatting of the certificate. Perhaps PHP on Windows could deal with the .crt, but PHP on Linux couldn't. I got the idea from the highest rated answer on another question.

Community
  • 1
  • 1
BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • Yes. Other people who had problems seem to have done the same. Nice job! – blackpen Aug 27 '16 at 00:37
  • @blackpen thanks so much for taking the time to work with me through it. The thumbs belongs to the other answer - and it's just 1 away from +100 :-) Happy coding. – BeetleJuice Aug 27 '16 at 00:44