rfc:tls-peer-verification

TLS Peer Verification

Introduction

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 Current State of Things

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:

  • That PHP developers will have a functioning knowlege of transport layer security.

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.

Proposal

  • All encrypted client streams enable peer verification by default.
  • Peer names are automatically detected from the URI at connection time (eliminating the need for any configuration on the part of the user) with the option to manually override this detection with a custom value stored in the CN_match context option.
  • Global CA defaults may be specified via new openssl.cafile and openssl.capath php.ini directives.
  • Per-stream CA paths may still be specified at call time by passing the existing cafile or capath ssl options in the stream context (the new php.ini directives exist as a fallback).

NEW ADDITIONS:

  • If none of the above methods are used to specify the necessary CA file/path info PHP will fall back to the defaults built into OpenSSL at compile time. This means that those using a distro-supplied PHP version can expect existing code to “just work” for most cases.
  • Only if the OpenSSL defaults cannot be loaded and no manual user assignments exist via the .ini directives or stream context options is an E_WARNING triggered due to insufficient CA settings. Manually disabling peer verification at call time can (as in the original proposal) prevent such failures.

Differences From the Original Proposal

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 does NOT include any CA files or hashed directories as part of its distribution.
  • Distros use their own values when compiling OpenSSL to manually specify defaults corresponding to their CA file/dir locations. As a result userland PHP code is able to use OS-managed CA files for peer verification. This is the best of all possible worlds because it prevents any fussing about with CA files on our end. Also, the OS is in charge of updating these files as needed which prevents any cert maintenance associated with managing our own CA file.
  • Because the defaults are specified when the OpenSSL lib is compiled users building ext/openssl against manually-compiled OpenSSL libs will need to either: specify the CA defaults when compiling the lib, manually place PEM-formatted versions of the necessary files in the appropriate directory or specify a location via the new .ini directives to enjoy the benefits of distro-managed CA files.
  • Users may check the default CA file location of their OpenSSL binary using the openssl version -d command

Usage Examples

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://74.125.224.72/';
$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);

Backward Incompatible Changes

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.

Proposed PHP Version

This RFC is proposed for implementation in PHP 5.6.

Impact to Existing Extensions

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.

php.ini Defaults

This patch proposes two new php.ini directives as part of the openssl module:

  • openssl.cafile

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

The 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.”

Unaffected PHP Functionality

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.

Open Issues

None.

Proposed Voting Choices

  • Should secure-by-default client peer verification be implemented for 5.6?

Implementation

References

Vote

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!

Should PHP verify client peers by default in PHP 5.6?
Real name Yes No
aharvey (aharvey)  
ajf (ajf)  
bwoebi (bwoebi)  
chobieeee (chobieeee)  
datibbaw (datibbaw)  
daverandom (daverandom)  
derick (derick)  
dm (dm)  
krakjoe (krakjoe)  
levim (levim)  
mbeccati (mbeccati)  
mike (mike)  
mj (mj)  
nikic (nikic)  
pajoye (pajoye)  
peehaa (peehaa)  
pollita (pollita)  
ralphschindler (ralphschindler)  
rasmus (rasmus)  
rdlowrey (rdlowrey)  
remi (remi)  
seld (seld)  
stas (stas)  
treffynnon (treffynnon)  
tyrael (tyrael)  
Final result: 25 0
This poll has been closed.

Rejected Features

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.

rfc/tls-peer-verification.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1