1: <?php
2:
3: namespace React\Socket;
4:
5: use Evenement\EventEmitter;
6: use React\EventLoop\LoopInterface;
7: use React\Socket\Server;
8: use React\Socket\ConnectionInterface;
9: use React\Stream\Stream;
10:
11: /**
12: * The `SecureServer` class implements the `ServerInterface` and is responsible
13: * for providing a secure TLS (formerly known as SSL) server.
14: *
15: * It does so by wrapping a `Server` instance which waits for plaintext
16: * TCP/IP connections and then performs a TLS handshake for each connection.
17: *
18: * ```php
19: * $server = new Server(8000, $loop);
20: * $server = new SecureServer($server, $loop, array(
21: * // tls context options here…
22: * ));
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: * });
35: * ```
36: *
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:
39: *
40: * ```php
41: * $server->on('error', function (Exception $e) {
42: * echo 'Error' . $e->getMessage() . PHP_EOL;
43: * });
44: * ```
45: *
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
54: */
55: final class SecureServer extends EventEmitter implements ServerInterface
56: {
57: private $tcp;
58: private $encryption;
59: private $context;
60:
61: /**
62: * Creates a secure TLS server and starts waiting for incoming connections
63: *
64: * It does so by wrapping a `Server` instance which waits for plaintext
65: * TCP/IP connections and then performs a TLS handshake for each connection.
66: * It thus requires valid [TLS context options],
67: * which in its most basic form may look something like this if you're using a
68: * PEM encoded certificate file:
69: *
70: * ```php
71: * $server = new Server(8000, $loop);
72: * $server = new SecureServer($server, $loop, array(
73: * 'local_cert' => 'server.pem'
74: * ));
75: * ```
76: *
77: * Note that the certificate file will not be loaded on instantiation but when an
78: * incoming connection initializes its TLS context.
79: * This implies that any invalid certificate file paths or contents will only cause
80: * an `error` event at a later time.
81: *
82: * If your private key is encrypted with a passphrase, you have to specify it
83: * like this:
84: *
85: * ```php
86: * $server = new Server(8000, $loop);
87: * $server = new SecureServer($server, $loop, array(
88: * 'local_cert' => 'server.pem',
89: * 'passphrase' => 'secret'
90: * ));
91: * ```
92: *
93: * Note that available [TLS context options],
94: * their defaults and effects of changing these may vary depending on your system
95: * and/or PHP version.
96: * Passing unknown context options has no effect.
97: *
98: * Advanced usage: Despite allowing any `ServerInterface` as first parameter,
99: * you SHOULD pass a `Server` instance as first parameter, unless you
100: * know what you're doing.
101: * Internally, the `SecureServer` has to set the required TLS context options on
102: * the underlying stream resources.
103: * These resources are not exposed through any of the interfaces defined in this
104: * package, but only through the `React\Stream\Stream` class.
105: * The `Server` class is guaranteed to emit connections that implement
106: * the `ConnectionInterface` and also extend the `Stream` class in order to
107: * expose these underlying resources.
108: * If you use a custom `ServerInterface` and its `connection` event does not
109: * meet this requirement, the `SecureServer` will emit an `error` event and
110: * then close the underlying connection.
111: *
112: * @param ServerInterface|Server $tcp
113: * @param LoopInterface $loop
114: * @param array $context
115: * @throws \BadMethodCallException for legacy HHVM < 3.8 due to lack of support
116: * @see Server
117: * @link http://php.net/manual/en/context.ssl.php for TLS context options
118: */
119: public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context)
120: {
121: if (!function_exists('stream_socket_enable_crypto')) {
122: throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
123: }
124:
125: // default to empty passphrase to surpress blocking passphrase prompt
126: $context += array(
127: 'passphrase' => ''
128: );
129:
130: $this->tcp = $tcp;
131: $this->encryption = new StreamEncryption($loop);
132: $this->context = $context;
133:
134: $that = $this;
135: $this->tcp->on('connection', function ($connection) use ($that) {
136: $that->handleConnection($connection);
137: });
138: $this->tcp->on('error', function ($error) use ($that) {
139: $that->emit('error', array($error));
140: });
141: }
142:
143: public function getAddress()
144: {
145: return $this->tcp->getAddress();
146: }
147:
148: public function pause()
149: {
150: $this->tcp->pause();
151: }
152:
153: public function resume()
154: {
155: $this->tcp->resume();
156: }
157:
158: public function close()
159: {
160: return $this->tcp->close();
161: }
162:
163: /** @internal */
164: public function handleConnection(ConnectionInterface $connection)
165: {
166: if (!$connection instanceof Stream) {
167: $this->emit('error', array(new \UnexpectedValueException('Connection event MUST emit an instance extending Stream in order to access underlying stream resource')));
168: $connection->end();
169: return;
170: }
171:
172: foreach ($this->context as $name => $value) {
173: stream_context_set_option($connection->stream, 'ssl', $name, $value);
174: }
175:
176: $that = $this;
177:
178: $this->encryption->enable($connection)->then(
179: function ($conn) use ($that) {
180: $that->emit('connection', array($conn));
181: },
182: function ($error) use ($that, $connection) {
183: $that->emit('error', array($error));
184: $connection->end();
185: }
186: );
187: }
188: }
189: