Client-Initiated Renegotiation DoS Vulnerability

#### Client-Initiated Renegotiation DoS Vulnerability

The idea of using SSL/TLS renegotiation as a DoS attack vector was first widely publicized in 2011 with the release of the [TLC-SSL-DOS][thc-ssl-dos] tool. The attack vector is plausible because establishing SSL connections requires more processing power for servers than what is required for clients ([Bernat][bernat]). This gap in resource consumption creates an easy target for nefarious clients looking to exhaust server resources through DoS attacks in which the TLS handshake is repeatedly renegotiated. There is generally no compelling reason to allow client-initiated renegotiation of the TLS handshake:

> There are some cases in which renegotiation needs to be initiated by the server, but there is no known need for clients to do so.

-- [*SSL/TLS Deployment Best Practices*][ssl-labs-report]

Encrypted PHP servers initialized via `stream_socket_server()` currently offer no mechanism to disable client-initiated TLS renegotiation and as a result afford no protection against the renegotiation DoS attack vector.

##### Proposed Solution

- Add a new `"max_handshake_rate"` context option limiting the number of client-initiated renegotiation attempts allowed per second.
- Clients exceeding the allowed rate are immediately shutdown (and have their stream's EOF flag set).
- Rate-limiting is not applied until *after* the initial handshake is negotiated.
- Servers wishing to differentiate between client sockets closed normally and those shutdown due to a renegotiation policy violation may check the stream's meta data after the fact:

```php
$wrapperMeta = stream_get_meta_data($clientSocket)['wrapper_data'];
if (!empty($wrapperMeta['handshake_limit_exceeded'])) {
    echo "Client closed due to renegotiation policy violation!\n";
}
```

##### Suggested Usage

Renegotiation rate-limiting is not enabled by default. Unless servers have a good reason for allowing client-initiated renegotiation they should disable this functionality as demonstrated in the example server script below:

```php
<?php
$bindTo = 'tls://127.0.0.1:12345';
$flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
$ctx = stream_context_create(['ssl' => [
    'local_cert' => '/path/to/cert.pem',
    'max_handshake_rate' => 0,
    'disable_compression' => TRUE
]]);
$server = stream_socket_server($bindTo, $errNo, $errStr, $flags, $ctx);
$clients = [];

while (1) {
    $r = array_merge([$server], $clients);
    $w = $e = [];
    stream_select($r, $w, $e, $timeout=1);

    foreach ($r as $sock) {
        if ($sock === $server
            && ($client = @stream_socket_accept($server, $timeout = 1))
        ) {
            $clientId = (int) $client;
            $clients[$clientId] = $client;
        } else {
            $clientId = (int) $client;
            $buffer = @fread($sock, 1024);
            
            if (strlen($buffer)) {
                echo "Client data received: ", $buffer, "\n";
            } elseif (feof($sock)) {
                $wrapperMeta = stream_get_meta_data($sock)['wrapper_data'];
                if (!empty($wrapperMeta['handshake_limit_exceeded'])) {
                    echo "Client closed due to renegotiation policy violation!\n";
                }
                fclose($sock);
                unset($clients[$clientId]);
                break 2; // end the demo script
            }
        }
    }
}
```

> **NOTE:** This patch has no BC implications and should be safe to merge with 5.4.x and 5.5.x branches

 [ssl-labs-report]: https://www.ssllabs.com/downloads/SSL_TLS_Deployment_Best_Practices_1.3.pdf
 [bernat]: http://vincent.bernat.im/en/blog/2011-ssl-dos-mitigation.html
 [thc-ssl-dos]: https://www.thc.org/thc-ssl-dos/