on('connection', function (React\Socket\ConnectionInterface $connection) { * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; * * $connection->write('hello there!' . PHP_EOL); * … * }); * ``` * * Whenever a client fails to perform a successful TLS handshake, it will emit an * `error` event and then close the underlying TCP/IP connection: * * ```php * $server->on('error', function (Exception $e) { * echo 'Error' . $e->getMessage() . PHP_EOL; * }); * ``` * * See also the `ServerInterface` for more details. * * Note that the `SecureServer` class is a concrete implementation for TLS sockets. * If you want to typehint in your higher-level protocol implementation, you SHOULD * use the generic `ServerInterface` instead. * * @see ServerInterface * @see ConnectionInterface */ final class SecureServer extends EventEmitter implements ServerInterface { private $tcp; private $encryption; private $context; /** * Creates a secure TLS server and starts waiting for incoming connections * * It does so by wrapping a `TcpServer` instance which waits for plaintext * TCP/IP connections and then performs a TLS handshake for each connection. * It thus requires valid [TLS context options], * which in its most basic form may look something like this if you're using a * PEM encoded certificate file: * * ```php * $server = new React\Socket\TcpServer(8000); * $server = new React\Socket\SecureServer($server, null, array( * 'local_cert' => 'server.pem' * )); * ``` * * Note that the certificate file will not be loaded on instantiation but when an * incoming connection initializes its TLS context. * This implies that any invalid certificate file paths or contents will only cause * an `error` event at a later time. * * If your private key is encrypted with a passphrase, you have to specify it * like this: * * ```php * $server = new React\Socket\TcpServer(8000); * $server = new React\Socket\SecureServer($server, null, array( * 'local_cert' => 'server.pem', * 'passphrase' => 'secret' * )); * ``` * * Note that available [TLS context options], * their defaults and effects of changing these may vary depending on your system * and/or PHP version. * Passing unknown context options has no effect. * * This class takes an optional `LoopInterface|null $loop` parameter that can be used to * pass the event loop instance to use for this object. You can use a `null` value * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). * This value SHOULD NOT be given unless you're sure you want to explicitly use a * given event loop instance. * * Advanced usage: Despite allowing any `ServerInterface` as first parameter, * you SHOULD pass a `TcpServer` instance as first parameter, unless you * know what you're doing. * Internally, the `SecureServer` has to set the required TLS context options on * the underlying stream resources. * These resources are not exposed through any of the interfaces defined in this * package, but only through the internal `Connection` class. * The `TcpServer` class is guaranteed to emit connections that implement * the `ConnectionInterface` and uses the internal `Connection` class in order to * expose these underlying resources. * If you use a custom `ServerInterface` and its `connection` event does not * meet this requirement, the `SecureServer` will emit an `error` event and * then close the underlying connection. * * @param ServerInterface|TcpServer $tcp * @param ?LoopInterface $loop * @param array $context * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support * @see TcpServer * @link https://www.php.net/manual/en/context.ssl.php for TLS context options */ public function __construct(ServerInterface $tcp, LoopInterface $loop = null, array $context = array()) { if (!\function_exists('stream_socket_enable_crypto')) { throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore } // default to empty passphrase to suppress blocking passphrase prompt $context += array( 'passphrase' => '' ); $this->tcp = $tcp; $this->encryption = new StreamEncryption($loop ?: Loop::get()); $this->context = $context; $that = $this; $this->tcp->on('connection', function ($connection) use ($that) { $that->handleConnection($connection); }); $this->tcp->on('error', function ($error) use ($that) { $that->emit('error', array($error)); }); } public function getAddress() { $address = $this->tcp->getAddress(); if ($address === null) { return null; } return \str_replace('tcp://' , 'tls://', $address); } public function pause() { $this->tcp->pause(); } public function resume() { $this->tcp->resume(); } public function close() { return $this->tcp->close(); } /** @internal */ public function handleConnection(ConnectionInterface $connection) { if (!$connection instanceof Connection) { $this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource'))); $connection->close(); return; } foreach ($this->context as $name => $value) { \stream_context_set_option($connection->stream, 'ssl', $name, $value); } // get remote address before starting TLS handshake in case connection closes during handshake $remote = $connection->getRemoteAddress(); $that = $this; $this->encryption->enable($connection)->then( function ($conn) use ($that) { $that->emit('connection', array($conn)); }, function ($error) use ($that, $connection, $remote) { $error = new \RuntimeException( 'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(), $error->getCode() ); $that->emit('error', array($error)); $connection->close(); } ); } }