PHP 5.6 verifies TLS peers by default, and old code notices

PHP’s encrypted stream wrappers did not verify certificates. file_get_contents('https://...') on 5.5 opened the connection, negotiated TLS and never checked who was on the other end, which made every one of those calls trivially interceptable. 5.6 turns verification on by default, and the first thing that happens on an upgraded server is that half the outbound integrations stop working.

// 5.5: no verification at all
$rates = file_get_contents('https://rates.example.net/latest.json');

// 5.6: the same line, unless a CA bundle can be found
// Warning: SSL operation failed ... unable to get local issuer certificate
// Warning: file_get_contents(): Failed to enable crypto

// php.ini:  openssl.cafile=/etc/ssl/certs/ca-certificates.crt

// or per call, for a peer using a private CA
$context = stream_context_create(array('ssl' => array(
    'cafile'           => '/etc/pki/internal-ca.pem',
    'verify_peer'      => true,
    'verify_peer_name' => true,
)));

$rates = file_get_contents('https://rates.internal/latest.json', false, $context);

The failure is a warning and a false return, so code that never checked now behaves as though the remote end returned nothing — an empty feed rather than an error, which is the slowest possible way to find out. The fix that circulates is 'verify_peer' => false, which restores both the old behaviour and the old vulnerability; the actual fix is one openssl.cafile line in php.ini pointing at the distribution’s bundle. Note that cURL was already verifying by default, which is why the same server can fetch a URL with one API and not the other, and why the problem tends to surface in exactly the oldest code. verify_peer_name being separate is genuinely useful: a hostname mismatch and an untrusted chain are different problems and now report differently.