| 3 |
liveuser |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
namespace React\Socket;
|
|
|
4 |
|
|
|
5 |
use Evenement\EventEmitter;
|
|
|
6 |
use React\EventLoop\LoopInterface;
|
|
|
7 |
use BadMethodCallException;
|
|
|
8 |
use UnexpectedValueException;
|
|
|
9 |
|
|
|
10 |
/**
|
|
|
11 |
* The `SecureServer` class implements the `ServerInterface` and is responsible
|
|
|
12 |
* for providing a secure TLS (formerly known as SSL) server.
|
|
|
13 |
*
|
|
|
14 |
* It does so by wrapping a `TcpServer` instance which waits for plaintext
|
|
|
15 |
* TCP/IP connections and then performs a TLS handshake for each connection.
|
|
|
16 |
*
|
|
|
17 |
* ```php
|
|
|
18 |
* $server = new React\Socket\TcpServer(8000, $loop);
|
|
|
19 |
* $server = new React\Socket\SecureServer($server, $loop, array(
|
|
|
20 |
* // tls context options here…
|
|
|
21 |
* ));
|
|
|
22 |
* ```
|
|
|
23 |
*
|
|
|
24 |
* Whenever a client completes the TLS handshake, it will emit a `connection` event
|
|
|
25 |
* with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
|
|
|
26 |
*
|
|
|
27 |
* ```php
|
|
|
28 |
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
|
|
29 |
* echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
|
|
|
30 |
*
|
|
|
31 |
* $connection->write('hello there!' . PHP_EOL);
|
|
|
32 |
* …
|
|
|
33 |
* });
|
|
|
34 |
* ```
|
|
|
35 |
*
|
|
|
36 |
* Whenever a client fails to perform a successful TLS handshake, it will emit an
|
|
|
37 |
* `error` event and then close the underlying TCP/IP connection:
|
|
|
38 |
*
|
|
|
39 |
* ```php
|
|
|
40 |
* $server->on('error', function (Exception $e) {
|
|
|
41 |
* echo 'Error' . $e->getMessage() . PHP_EOL;
|
|
|
42 |
* });
|
|
|
43 |
* ```
|
|
|
44 |
*
|
|
|
45 |
* See also the `ServerInterface` for more details.
|
|
|
46 |
*
|
|
|
47 |
* Note that the `SecureServer` class is a concrete implementation for TLS sockets.
|
|
|
48 |
* If you want to typehint in your higher-level protocol implementation, you SHOULD
|
|
|
49 |
* use the generic `ServerInterface` instead.
|
|
|
50 |
*
|
|
|
51 |
* @see ServerInterface
|
|
|
52 |
* @see ConnectionInterface
|
|
|
53 |
*/
|
|
|
54 |
final class SecureServer extends EventEmitter implements ServerInterface
|
|
|
55 |
{
|
|
|
56 |
private $tcp;
|
|
|
57 |
private $encryption;
|
|
|
58 |
private $context;
|
|
|
59 |
|
|
|
60 |
/**
|
|
|
61 |
* Creates a secure TLS server and starts waiting for incoming connections
|
|
|
62 |
*
|
|
|
63 |
* It does so by wrapping a `TcpServer` 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 React\Socket\TcpServer(8000, $loop);
|
|
|
71 |
* $server = new React\Socket\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 React\Socket\TcpServer(8000, $loop);
|
|
|
86 |
* $server = new React\Socket\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: Despite allowing any `ServerInterface` as first parameter,
|
|
|
98 |
* you SHOULD pass a `TcpServer` instance as first parameter, unless you
|
|
|
99 |
* know what you're doing.
|
|
|
100 |
* Internally, the `SecureServer` has to set the required TLS context options on
|
|
|
101 |
* the underlying stream resources.
|
|
|
102 |
* These resources are not exposed through any of the interfaces defined in this
|
|
|
103 |
* package, but only through the internal `Connection` class.
|
|
|
104 |
* The `TcpServer` class is guaranteed to emit connections that implement
|
|
|
105 |
* the `ConnectionInterface` and uses the internal `Connection` class in order to
|
|
|
106 |
* expose these underlying resources.
|
|
|
107 |
* If you use a custom `ServerInterface` and its `connection` event does not
|
|
|
108 |
* meet this requirement, the `SecureServer` will emit an `error` event and
|
|
|
109 |
* then close the underlying connection.
|
|
|
110 |
*
|
|
|
111 |
* @param ServerInterface|TcpServer $tcp
|
|
|
112 |
* @param LoopInterface $loop
|
|
|
113 |
* @param array $context
|
|
|
114 |
* @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
|
|
|
115 |
* @see TcpServer
|
|
|
116 |
* @link https://www.php.net/manual/en/context.ssl.php for TLS context options
|
|
|
117 |
*/
|
|
|
118 |
public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context)
|
|
|
119 |
{
|
|
|
120 |
if (!\function_exists('stream_socket_enable_crypto')) {
|
|
|
121 |
throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
|
|
|
122 |
}
|
|
|
123 |
|
|
|
124 |
// default to empty passphrase to suppress blocking passphrase prompt
|
|
|
125 |
$context += array(
|
|
|
126 |
'passphrase' => ''
|
|
|
127 |
);
|
|
|
128 |
|
|
|
129 |
$this->tcp = $tcp;
|
|
|
130 |
$this->encryption = new StreamEncryption($loop);
|
|
|
131 |
$this->context = $context;
|
|
|
132 |
|
|
|
133 |
$that = $this;
|
|
|
134 |
$this->tcp->on('connection', function ($connection) use ($that) {
|
|
|
135 |
$that->handleConnection($connection);
|
|
|
136 |
});
|
|
|
137 |
$this->tcp->on('error', function ($error) use ($that) {
|
|
|
138 |
$that->emit('error', array($error));
|
|
|
139 |
});
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
public function getAddress()
|
|
|
143 |
{
|
|
|
144 |
$address = $this->tcp->getAddress();
|
|
|
145 |
if ($address === null) {
|
|
|
146 |
return null;
|
|
|
147 |
}
|
|
|
148 |
|
|
|
149 |
return \str_replace('tcp://' , 'tls://', $address);
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
public function pause()
|
|
|
153 |
{
|
|
|
154 |
$this->tcp->pause();
|
|
|
155 |
}
|
|
|
156 |
|
|
|
157 |
public function resume()
|
|
|
158 |
{
|
|
|
159 |
$this->tcp->resume();
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
public function close()
|
|
|
163 |
{
|
|
|
164 |
return $this->tcp->close();
|
|
|
165 |
}
|
|
|
166 |
|
|
|
167 |
/** @internal */
|
|
|
168 |
public function handleConnection(ConnectionInterface $connection)
|
|
|
169 |
{
|
|
|
170 |
if (!$connection instanceof Connection) {
|
|
|
171 |
$this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
|
|
|
172 |
$connection->close();
|
|
|
173 |
return;
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
foreach ($this->context as $name => $value) {
|
|
|
177 |
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
|
|
|
178 |
}
|
|
|
179 |
|
|
|
180 |
// get remote address before starting TLS handshake in case connection closes during handshake
|
|
|
181 |
$remote = $connection->getRemoteAddress();
|
|
|
182 |
$that = $this;
|
|
|
183 |
|
|
|
184 |
$this->encryption->enable($connection)->then(
|
|
|
185 |
function ($conn) use ($that) {
|
|
|
186 |
$that->emit('connection', array($conn));
|
|
|
187 |
},
|
|
|
188 |
function ($error) use ($that, $connection, $remote) {
|
|
|
189 |
$error = new \RuntimeException(
|
|
|
190 |
'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(),
|
|
|
191 |
$error->getCode()
|
|
|
192 |
);
|
|
|
193 |
|
|
|
194 |
$that->emit('error', array($error));
|
|
|
195 |
$connection->close();
|
|
|
196 |
}
|
|
|
197 |
);
|
|
|
198 |
}
|
|
|
199 |
}
|