guzzle/guzzle — pull request #800 by mtdowling (2014-09-02 02:34:40+00:00)
Title: Using the system CA bundle by default when possible

This commit updates Guzzle to use the CA bundle packaged on the
system by default when possible. cURL will use the system CA
bundle without issue, however, PHP 5.5 and lower will still
default to the bundled CA cert. You can now pass "bundled" to
use the cacert file bundled with Guzzle.

@klausi / @catch56: I'd love feedback on this if you have time.

I've gotten feedback from some sysadmins at work, and they'd prefer if Guzzle defaulted to the system's CA bundle. That seemed easy enough when Guzzle just used cURL, but now that Guzzle also uses PHP's stream wrapper, it gets a bit complicated. PHP version < 5.6 do not know where the system's default bundles are located, so there's two options: find it on disk or use a Guzzle bundled cert by default.

I could scan the filesystem for common locations, but my biggest problem here is that this there's no default cert on OS X, which makes Guzzle not work out of the box for mac users.

Something like this would work (note: this is not what I'm currently doing):

```php
/**
* Returns the default cacert bundle for the current system.
*
* The common locations for bundles found on Red Hat, CentOS, Fedora,
* Ubuntu, Debian, and FreeBSD are checked. If any of these file locations
* are found on disk, they will be utilized.
*
* @return string
* @throws \RuntimeException if no bundle can be found.
*/
public static function getDefaultCabundle()
{
static $cafiles = [
// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
'/etc/pki/tls/certs/ca-bundle.crt',
// Ubuntu, Debian (provided by the ca-certificates package)
'/etc/ssl/certs/ca-certificates.crt',
// FreeBSD (provided by the ca_root_nss package)
'/usr/local/share/certs/ca-root-nss.crt',
// OS X provided by homebrew (using the default path)
'/usr/local/etc/openssl/cert.pem',
// openSUSE/SLE
'/etc/ssl/ca-bundle.pem',
// Windows?
'C:\\windows\\system32\\curl-ca-bundle.crt'
];

foreach ($cafiles as $filename) {
if (file_exists($filename)) {
return $filename;
}
}

throw new \RuntimeException(sprintf(
'No system CA bundle could be found in any of the the following '
. 'common locations: %s. PHP versions earlier than 5.6 are not '
. 'properly configured to use the system\'s OpenSSL certificates '
. 'by default. In order to verify peer certificates, you will '
. 'need to supply the path on disk to a certificate bundle to the '
. '"verify" request option: %s. If you do not need a specific '
. 'certificate bundle, then Mozilla provides a commonly used '
. 'CA bundle which can be downloaded here (provided by cURL): %s. '
. 'Once you have a CA bundle available on disk, you can set the '
. '"openssl.cafile" PHP ini setting to point to the path to the '
. 'file, allowing you to omit the "verify" request option.',
implode(', ', $cafiles),
'http://docs.guzzlephp.org/en/latest/clients.html#verify',
'https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt'
));
}
```

My biggest problem with this approach for me is that it doesn't "just work" for everyone.

To work around this, I've updated Guzzle 4 to have a "bundled" cacert setting that will use the cacert packaged with Guzzle. When running Guzzle through a phar, we will extract the cacert to one of several locations (see the updated docs in this PR for specifics).

The only reason I'm bundling a cacert is because PHP's stream wrapper doesn't use the system's default cert when running < 5.6. I'd rather not bundle this, but I want it to work for users out of the box on all platforms. Does that make sense, or is there a better way of achieving this?

What do you think about this approach?