-
-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Opportunistic TLS implementation
This commit introduces the functionality required to build opportunistic TLS clients and servers with ReactPHP. It does so by introducing a prefix to `tls://`, namely `opportunistic`, to create `opportunistic+tls://example.com:5432` for example as the full URL. This will create an `OpportunisticTlsConnectionInterface` (instead of a `ConnectionInterface`) that extends the `ConnectionInterface` and exposes the `enableEncryption` method to enable TLS encryption at the desired moment. Inside this PR is an example of a server and client negotiating when to enable TLS and enable it when ready. Opportunistic Security described in RFC7435: https://www.rfc-editor.org/rfc/rfc7435 External PR using the proposed changes in this commit: voryx/PgAsync#52
- Loading branch information
1 parent
936546b
commit 5216b9e
Showing
10 changed files
with
534 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
// Opportunistic TLS example showing a basic negotiation before enabling the encryption. It starts out as an | ||
// unencrypted TCP connection. After both parties agreed to encrypt the connection they both enable the encryption. | ||
// After which any communication over the line is encrypted. | ||
// | ||
// This example is design to show both sides in one go, as such the server stops listening for new connection after | ||
// the first, this makes sure the loop shuts down after the example connection has closed. | ||
// | ||
// $ php examples/31-opportunistic-tls.php | ||
|
||
use React\EventLoop\Loop; | ||
use React\Socket\ConnectionInterface; | ||
use React\Socket\Connector; | ||
use React\Socket\OpportunisticTlsConnectionInterface; | ||
use React\Socket\SocketServer; | ||
|
||
require __DIR__ . '/../vendor/autoload.php'; | ||
|
||
$server = new SocketServer('opportunistic+tls://127.0.0.1:0', array( | ||
'tls' => array( | ||
'local_cert' => __DIR__ . '/localhost.pem', | ||
) | ||
)); | ||
$server->on('connection', static function (OpportunisticTlsConnectionInterface $connection) use ($server) { | ||
$server->close(); | ||
|
||
$connection->on('data', function ($data) { | ||
echo 'From Client: ', $data, PHP_EOL; | ||
}); | ||
React\Promise\Stream\first($connection)->then(function ($data) use ($connection) { | ||
if ($data === 'Let\'s encrypt?') { | ||
$connection->write('yes'); | ||
return $connection->enableEncryption(); | ||
} | ||
|
||
return $connection; | ||
})->then(static function (ConnectionInterface $connection) { | ||
$connection->write('Encryption enabled!'); | ||
})->done(); | ||
}); | ||
|
||
$client = new Connector(array( | ||
'tls' => array( | ||
'verify_peer' => false, | ||
'verify_peer_name' => false, | ||
'allow_self_signed' => true, | ||
), | ||
)); | ||
$client->connect($server->getAddress())->then(static function (OpportunisticTlsConnectionInterface $connection) { | ||
$connection->on('data', function ($data) { | ||
echo 'From Server: ', $data, PHP_EOL; | ||
}); | ||
$connection->write('Let\'s encrypt?'); | ||
|
||
return React\Promise\Stream\first($connection)->then(function ($data) use ($connection) { | ||
if ($data === 'yes') { | ||
return $connection->enableEncryption(); | ||
} | ||
|
||
return $connection; | ||
}); | ||
})->then(function (ConnectionInterface $connection) { | ||
$connection->write('Encryption enabled!'); | ||
Loop::addTimer(1, static function () use ($connection) { | ||
$connection->end('Cool! Bye!'); | ||
}); | ||
})->done(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
|
||
namespace React\Socket; | ||
|
||
use Evenement\EventEmitter; | ||
use React\EventLoop\LoopInterface; | ||
use React\Promise\PromiseInterface; | ||
use React\Stream\DuplexResourceStream; | ||
use React\Stream\Util; | ||
use React\Stream\WritableResourceStream; | ||
use React\Stream\WritableStreamInterface; | ||
|
||
/** | ||
* The actual connection implementation for StartTlsConnectionInterface | ||
* | ||
* This class should only be used internally, see StartTlsConnectionInterface instead. | ||
* | ||
* @see OpportunisticTlsConnectionInterface | ||
* @internal | ||
*/ | ||
class OpportunisticTlsConnection extends EventEmitter implements OpportunisticTlsConnectionInterface | ||
{ | ||
/** @var Connection */ | ||
private $connection; | ||
|
||
/** @var StreamEncryption */ | ||
private $streamEncryption; | ||
|
||
/** @var string */ | ||
private $uri; | ||
|
||
public function __construct(Connection $connection, StreamEncryption $streamEncryption, $uri) | ||
{ | ||
$this->connection = $connection; | ||
$this->streamEncryption = $streamEncryption; | ||
$this->uri = $uri; | ||
|
||
Util::forwardEvents($connection, $this, array('data', 'end', 'error', 'close')); | ||
} | ||
|
||
public function getRemoteAddress() | ||
{ | ||
return $this->connection->getRemoteAddress(); | ||
} | ||
|
||
public function getLocalAddress() | ||
{ | ||
return $this->connection->getLocalAddress(); | ||
} | ||
|
||
public function isReadable() | ||
{ | ||
return $this->connection->isReadable(); | ||
} | ||
|
||
public function pause() | ||
{ | ||
$this->connection->pause(); | ||
} | ||
|
||
public function resume() | ||
{ | ||
$this->connection->resume(); | ||
} | ||
|
||
public function pipe(WritableStreamInterface $dest, array $options = array()) | ||
{ | ||
return $this->connection->pipe($dest, $options); | ||
} | ||
|
||
public function close() | ||
{ | ||
$this->connection->close(); | ||
} | ||
|
||
public function enableEncryption() | ||
{ | ||
$that = $this; | ||
$connection = $this->connection; | ||
$uri = $this->uri; | ||
|
||
return $this->streamEncryption->enable($connection)->then(function () use ($that) { | ||
return $that; | ||
}, function ($error) use ($connection, $uri) { | ||
// establishing encryption failed => close invalid connection and return error | ||
$connection->close(); | ||
|
||
throw new \RuntimeException( | ||
'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(), | ||
$error->getCode() | ||
); | ||
}); | ||
} | ||
|
||
public function isWritable() | ||
{ | ||
return $this->connection->isWritable(); | ||
} | ||
|
||
public function write($data) | ||
{ | ||
return $this->connection->write($data); | ||
} | ||
|
||
public function end($data = null) | ||
{ | ||
$this->connection->end($data); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
namespace React\Socket; | ||
|
||
use React\Promise\PromiseInterface; | ||
|
||
/** | ||
* @see DuplexStreamInterface | ||
* @see ServerInterface | ||
* @see ConnectionInterface | ||
*/ | ||
interface OpportunisticTlsConnectionInterface extends ConnectionInterface | ||
{ | ||
/** | ||
* @return PromiseInterface<OpportunisticTlsConnectionInterface> | ||
*/ | ||
public function enableEncryption(); | ||
} |
Oops, something went wrong.