Skip to content

Commit ca73095

Browse files
authored
Merge pull request #69 from clue-labs/secure-connection
Documentation and tests for exposing secure context options
2 parents 535f323 + f4d7314 commit ca73095

File tree

3 files changed

+138
-14
lines changed

3 files changed

+138
-14
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ $server = new SecureServer($server, $loop, array(
263263
));
264264
```
265265

266+
> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
267+
their defaults and effects of changing these may vary depending on your system
268+
and/or PHP version.
269+
Passing unknown context options has no effect.
270+
266271
Whenever a client completes the TLS handshake, it will emit a `connection` event
267272
with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
268273

@@ -286,6 +291,19 @@ $server->on('error', function (Exception $e) {
286291

287292
See also the [`ServerInterface`](#serverinterface) for more details.
288293

294+
Note that the `SecureServer` class is a concrete implementation for TLS sockets.
295+
If you want to typehint in your higher-level protocol implementation, you SHOULD
296+
use the generic [`ServerInterface`](#serverinterface) instead.
297+
298+
> Advanced usage: Internally, the `SecureServer` has to set the required
299+
context options on the underlying stream resources.
300+
It should therefor be used with an unmodified `Server` instance as first
301+
parameter so that it can allocate an empty context resource which this
302+
class uses to set required TLS context options.
303+
Failing to do so may result in some hard to trace race conditions,
304+
because all stream resources will use a single, shared default context
305+
resource otherwise.
306+
289307
### ConnectionInterface
290308

291309
The `ConnectionInterface` is used to represent any incoming connection.

src/SecureServer.php

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,110 @@
66
use React\EventLoop\LoopInterface;
77
use React\Socket\Server;
88
use React\Socket\ConnectionInterface;
9+
use React\Stream\Stream;
910

1011
/**
1112
* The `SecureServer` class implements the `ServerInterface` and is responsible
1213
* for providing a secure TLS (formerly known as SSL) server.
1314
*
1415
* It does so by wrapping a `Server` instance which waits for plaintext
1516
* TCP/IP connections and then performs a TLS handshake for each connection.
16-
* It thus requires valid [TLS context options],
17-
* which in its most basic form may look something like this if you're using a
18-
* PEM encoded certificate file:
1917
*
18+
* ```php
19+
* $server = new Server(8000, $loop);
20+
* $server = new SecureServer($server, $loop, array(
21+
* // tls context options here…
22+
* ));
2023
* ```
21-
* $context = array(
22-
* 'local_cert' => __DIR__ . '/localhost.pem'
23-
* );
24+
*
25+
* Whenever a client completes the TLS handshake, it will emit a `connection` event
26+
* with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
27+
*
28+
* ```php
29+
* $server->on('connection', function (ConnectionInterface $connection) {
30+
* echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
31+
*
32+
* $connection->write('hello there!' . PHP_EOL);
33+
* …
34+
* });
2435
* ```
2536
*
26-
* If your private key is encrypted with a passphrase, you have to specify it
27-
* like this:
37+
* Whenever a client fails to perform a successful TLS handshake, it will emit an
38+
* `error` event and then close the underlying TCP/IP connection:
2839
*
2940
* ```php
30-
* $context = array(
31-
* 'local_cert' => 'server.pem',
32-
* 'passphrase' => 'secret'
33-
* );
41+
* $server->on('error', function (Exception $e) {
42+
* echo 'Error' . $e->getMessage() . PHP_EOL;
43+
* });
3444
* ```
3545
*
36-
* @see Server
37-
* @link http://php.net/manual/en/context.ssl.php for TLS context options
46+
* See also the `ServerInterface` for more details.
47+
*
48+
* Note that the `SecureServer` class is a concrete implementation for TLS sockets.
49+
* If you want to typehint in your higher-level protocol implementation, you SHOULD
50+
* use the generic `ServerInterface` instead.
51+
*
52+
* @see ServerInterface
53+
* @see ConnectionInterface
3854
*/
3955
class SecureServer extends EventEmitter implements ServerInterface
4056
{
4157
private $tcp;
4258
private $encryption;
4359

60+
/**
61+
* Creates a secure TLS server and starts waiting for incoming connections
62+
*
63+
* It does so by wrapping a `Server` instance which waits for plaintext
64+
* TCP/IP connections and then performs a TLS handshake for each connection.
65+
* It thus requires valid [TLS context options],
66+
* which in its most basic form may look something like this if you're using a
67+
* PEM encoded certificate file:
68+
*
69+
* ```php
70+
* $server = new Server(8000, $loop);
71+
* $server = new SecureServer($server, $loop, array(
72+
* 'local_cert' => 'server.pem'
73+
* ));
74+
* ```
75+
*
76+
* Note that the certificate file will not be loaded on instantiation but when an
77+
* incoming connection initializes its TLS context.
78+
* This implies that any invalid certificate file paths or contents will only cause
79+
* an `error` event at a later time.
80+
*
81+
* If your private key is encrypted with a passphrase, you have to specify it
82+
* like this:
83+
*
84+
* ```php
85+
* $server = new Server(8000, $loop);
86+
* $server = new SecureServer($server, $loop, array(
87+
* 'local_cert' => 'server.pem',
88+
* 'passphrase' => 'secret'
89+
* ));
90+
* ```
91+
*
92+
* Note that available [TLS context options],
93+
* their defaults and effects of changing these may vary depending on your system
94+
* and/or PHP version.
95+
* Passing unknown context options has no effect.
96+
*
97+
* Advanced usage: Internally, the `SecureServer` has to set the required
98+
* context options on the underlying stream resources.
99+
* It should therefor be used with an unmodified `Server` instance as first
100+
* parameter so that it can allocate an empty context resource which this
101+
* class uses to set required TLS context options.
102+
* Failing to do so may result in some hard to trace race conditions,
103+
* because all stream resources will use a single, shared default context
104+
* resource otherwise.
105+
*
106+
* @param Server $tcp
107+
* @param LoopInterface $loop
108+
* @param array $context
109+
* @throws ConnectionException
110+
* @see Server
111+
* @link http://php.net/manual/en/context.ssl.php for TLS context options
112+
*/
44113
public function __construct(Server $tcp, LoopInterface $loop, array $context)
45114
{
46115
if (!is_resource($tcp->master)) {
@@ -81,6 +150,12 @@ public function close()
81150
/** @internal */
82151
public function handleConnection(ConnectionInterface $connection)
83152
{
153+
if (!$connection instanceof Stream) {
154+
$this->emit('error', array(new \UnexpectedValueException('Connection event MUST emit an instance extending Stream in order to access underlying stream resource')));
155+
$connection->end();
156+
return;
157+
}
158+
84159
$that = $this;
85160

86161
$this->encryption->enable($connection)->then(

tests/SecureServerTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,35 @@ public function testCloseWillBePassedThroughToTcpServer()
3838

3939
$server->close();
4040
}
41+
42+
public function testConnectionWillBeEndedWithErrorIfItIsNotAStream()
43+
{
44+
$tcp = $this->getMockBuilder('React\Socket\Server')->disableOriginalConstructor()->setMethods(null)->getMock();
45+
$tcp->master = stream_socket_server('tcp://localhost:0');
46+
47+
$loop = $this->getMock('React\EventLoop\LoopInterface');
48+
49+
$connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
50+
$connection->expects($this->once())->method('end');
51+
52+
$server = new SecureServer($tcp, $loop, array());
53+
54+
$server->on('error', $this->expectCallableOnce());
55+
56+
$tcp->emit('connection', array($connection));
57+
}
58+
59+
public function testSocketErrorWillBeForwarded()
60+
{
61+
$tcp = $this->getMockBuilder('React\Socket\Server')->disableOriginalConstructor()->setMethods(null)->getMock();
62+
$tcp->master = stream_socket_server('tcp://localhost:0');
63+
64+
$loop = $this->getMock('React\EventLoop\LoopInterface');
65+
66+
$server = new SecureServer($tcp, $loop, array());
67+
68+
$server->on('error', $this->expectCallableOnce());
69+
70+
$tcp->emit('error', array(new \RuntimeException('test')));
71+
}
4172
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy