PHP client stream encryption is insecure by default. This RFC explores the problematic nature of the current implementation, proposes fixes for its vulnerabilities and suggests new features to improve security going forward. The end result is a more secure implementation that “just works” with a minimum level of user knowledge required. Meanwhile, full configurability is retained should userland code wish to override secure defaults and/or auto-detected values.
The most basic requirement for secure communication is to first establish that you are actually communicating directly with the intended party (without unwanted intermediaries). Absent this assurance all the encryption in the world won't afford adequate security. Regrettably, PHP's existing SSL/TLS stream wrappers fail this requirement. Although choosing not to verify peers makes it exceedingly simple to access encrypted resources, it also leaves many (most?) encrypted userland communications open to Man-in-the-Middle (MitM) attacks:
<?php // No peer verification: all your bank account are belong to us $uri = 'https://www.bankofamerica.com/'; $html = file_get_contents($uri);
Of course it is possible to solve this problem by passing an ssl stream context as demonstrated below:
<?php $uri = 'https://www.bankofamerica.com/'; $ctx = stream_context_create(['ssl' => [ 'verify_peer' => true, 'cafile' => '/hard/path/to/cacert.pem', 'CN_match' => 'www.bankofamerica.com' ]]); $html = file_get_contents($uri, FALSE, $ctx); // okay, we're good here
This method works but it relies on one extremely optimistic assumption:
Unfortunately, a large number of real-world implementations are naive to the need for additional peer verification context options. This is an untenable situation and by defaulting to insecure practices PHP is complicit in the compromise of user data.
capathssl options in the stream context (the new php.ini directives exist as a fallback).
E_WARNINGtriggered due to insufficient CA settings. Manually disabling peer verification at call time can (as in the original proposal) prevent such failures.
The first iteration of the RFC did not take advantage of the compiled OpenSSL lib's CA defaults. This change – though trivial in terms of LoC – is a significant improvement over the original patch. Users employing a distro-supplied PHP version will have all the benefits of peer verification without any changes to existing code in most scenarios.
Notes on this change:
openssl version -dcommand
Distro defaults prevent most BC-breakage:
<?php // Look 'ma, no BC-breaks! $html = file_get_contents('https://www.google.com/');
Specifying a global CA file via
ini_set() at runtime:
<?php $cafile = '/path/to/cacert.pem'; ini_set('openssl.cafile', $cafile); $html = file_get_contents('https://somesite.com/');
Specifying a CA file using a stream context option at call time:
<?php $uri = 'https://www.github.com/'; $cafile = '/path/to/cacert.pem'; $ctx = stream_context_create(['ssl' => [ 'cafile' => $cafile ]]); file_get_contents($uri, FALSE, $ctx);
Overriding automatic peer name detection:
<?php $uri = 'https://220.127.116.11/'; $cafile = '/path/to/cacert.pem'; $ctx = stream_context_create(['ssl' => [ 'cafile' => $cafile, 'CN_match' => 'google.com' ]]); file_get_contents($uri, FALSE, $ctx);
Disabling peer verification:
<?php $ctx = stream_context_create(['ssl' => [ 'verify_peer' => false ]]); $html = file_get_contents('https://somesite.com/', FALSE, $ctx);
Most preexisting code is expected work without any BC implications when using a distro-supplied PHP version. Manually compiled installations and those whose ext/openssl is built against a custom OpenSSL build will need to modify either the compiled OpenSSL settings or assign values to the new php.ini directives to enjoy the benefits of distro-managed CA files without updating their code. Existing code accessing encrypted resources which cannot be verified via the OS-managed CA store will fail with an
E_WARNING explaining the problem. Any code that fails as a result of this patch can pass a
“verify_peer” ⇒ false context option to regain the old (insecure) functionality.
This RFC is proposed for implementation in PHP 5.6.
This patch impacts
ext/openssl. The openssl module registers two new php.ini directives but module
initialization and shutdown routines are otherwise unaffected and no module globals are added.
This patch proposes two new php.ini directives as part of the openssl module:
The biggest impediment to secure peer verification is the lack of a CA file for name verification. By exposing a php.ini directive specifying a global CA file users/distros can eliminate the need for stream contexts to achieve secure peer verification. This global php.ini directive simplifies the process of specifying CA files in custom environments. This value should be left empty when using distros that supply a PHP version built against their own pre-compiled OpenSSL lib. Essentially, this directive is a convenience for power-users. If you are unsure of whether or not you need to specify a value for this directive then the answer is very likely, “No.”
openssl.capath directive should remain empty unless users wish to explicitly avoid specifying
their own custom hashed certificate directory path on each encrypted stream connection. The directive
exists solely as a convenience for these users and as such can safely be left empty or unspecified both
in development and production environments. Its use corresponds to the
“capath” ssl stream context
option and exists for power-users. If you are unsure of whether or not you need to specify a value for
this directive then the answer is very likely, “No.”
This proposal does not affect the default peer verification settings for server streams. Default verification at the language-level is only sensible for clients and steps have been taken to ensure server streams relying on the same underlying code remain unaffected.
The initial vote was halted to clarify voting options and improve the implementation. Please read the updated section titled “Differences From the Original Proposal” for information on the differences between the original proposal and what is now under consideration.
Voting closes Dec. 31 … happy holidays!
The original vote offered an option to maintain a CA file as part of the PHP distribution. This option was discarded with the introduction of distro-managed CA stores as part of the implementation.