Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add socket and SSL/TLS context options to connectors #52

Merged
merged 2 commits into from
Mar 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ $tcpConnector->create('127.0.0.1', 80)->then(function (React\Stream\Stream $stre
$loop->run();
```

You can optionally pass additional
[socket context options](http://php.net/manual/en/context.socket.php)
to the constructor like this:

```php
$tcpConnector = new React\SocketClient\TcpConnector($loop, array(
'bindto' => '192.168.0.1:0'
));
```

Note that this class only allows you to connect to IP/port combinations.
If you want to connect to hostname/port combinations, see also the following chapter.

Expand Down Expand Up @@ -102,6 +112,17 @@ $secureConnector->create('www.google.com', 443)->then(function (React\Stream\Str
$loop->run();
```

You can optionally pass additional
[SSL context options](http://php.net/manual/en/context.ssl.php)
to the constructor like this:

```php
$secureConnector = new React\SocketClient\SecureConnector($dnsConnector, $loop, array(
'verify_peer' => false,
'verify_peer_name' => false
));
```

### Unix domain sockets

Similarly, the `UnixConnector` class can be used to connect to Unix domain socket (UDS)
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@
"branch-alias": {
"dev-master": "0.4-dev"
}
},
"require-dev": {
"clue/block-react": "~1.0"
}
}
19 changes: 13 additions & 6 deletions src/SecureConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ class SecureConnector implements ConnectorInterface
{
private $connector;
private $streamEncryption;
private $context;

public function __construct(ConnectorInterface $connector, LoopInterface $loop)
public function __construct(ConnectorInterface $connector, LoopInterface $loop, array $context = array())
{
$this->connector = $connector;
$this->streamEncryption = new StreamEncryption($loop);
$this->context = $context;
}

public function create($host, $port)
Expand All @@ -23,14 +25,19 @@ public function create($host, $port)
return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'));
}

return $this->connector->create($host, $port)->then(function (Stream $stream) use ($host) {
$context = $this->context + array(
'SNI_enabled' => true,
'SNI_server_name' => $host,
'peer_name' => $host
);

return $this->connector->create($host, $port)->then(function (Stream $stream) use ($context) {
// (unencrypted) TCP/IP connection succeeded

// set required SSL/TLS context options
$resource = $stream->stream;
stream_context_set_option($resource, 'ssl', 'SNI_enabled', true);
stream_context_set_option($resource, 'ssl', 'SNI_server_name', $host);
stream_context_set_option($resource, 'ssl', 'peer_name', $host);
foreach ($context as $name => $value) {
stream_context_set_option($stream->stream, 'ssl', $name, $value);
}

// try to enable encryption
return $this->streamEncryption->enable($stream)->then(null, function ($error) use ($stream) {
Expand Down
13 changes: 11 additions & 2 deletions src/TcpConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
class TcpConnector implements ConnectorInterface
{
private $loop;
private $context;

public function __construct(LoopInterface $loop)
public function __construct(LoopInterface $loop, array $context = array())
{
$this->loop = $loop;
$this->context = $context;
}

public function create($ip, $port)
Expand All @@ -25,7 +27,14 @@ public function create($ip, $port)

$url = $this->getSocketUrl($ip, $port);

$socket = @stream_socket_client($url, $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
$socket = @stream_socket_client(
$url,
$errno,
$errstr,
0,
STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT,
stream_context_create(array('socket' => $this->context))
);

if (false === $socket) {
return Promise\reject(new \RuntimeException(
Expand Down
50 changes: 50 additions & 0 deletions tests/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use React\SocketClient\Connector;
use React\SocketClient\SecureConnector;
use React\Stream\BufferedSink;
use Clue\React\Block;

class IntegrationTest extends TestCase
{
Expand Down Expand Up @@ -73,4 +74,53 @@ public function gettingEncryptedStuffFromGoogleShouldWork()
$this->assertTrue($connected);
$this->assertRegExp('#^HTTP/1\.0#', $response);
}

/** @test */
public function testSelfSignedRejectsIfVerificationIsEnabled()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is failing for me on OS X 10.11 with PHP 5.5.30

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is strange indeed…

Can you try running the following code to verify this is in line with what PHP's stream handlers do?

file_get_contents('https://self-signed.badssl.com/', false, stream_context_create(array('ssl'=>array('verify_peer'=>false))));
// all okay

file_get_contents('https://self-signed.badssl.com/', false, stream_context_create(array('ssl'=>array('verify_peer'=>true))));
// PHP warning:  file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages:
// error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed on line 1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. Those worked as you expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give some error output for the failing test?

Unfortunately I do not have a Mac to test against, so how else could I assist you tracking this down?

As an alternative, how about skipping this test on Mac for now (similar to how we skip certain tests for HHVM)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not just skip because it's failing. HHVM is skipped because the future is not supported in the language.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After talking to @cboden, we agree that this is probably a bug in certain (older) versions of PHP, which we have yet to triage further.

This test DOES work correctly on ALL PHP versions tested via Travis.

This test does NOT work on PHP 5.5.30 on Mac, but DOES work with PHP 7.? on Mac.

Note that this test failure is not related to this library at all, we're currently looking into providing a test script which highlights this issue in the underlying SSL stream wrapper.

Let's keep track of this in #59 instead and get this PR in :shipit:

{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('Not supported on HHVM');
}

$loop = new StreamSelectLoop();

$factory = new Factory();
$dns = $factory->create('8.8.8.8', $loop);


$secureConnector = new SecureConnector(
new Connector($loop, $dns),
$loop,
array(
'verify_peer' => true
)
);

$this->setExpectedException('RuntimeException');
Block\await($secureConnector->create('self-signed.badssl.com', 443), $loop);
}

/** @test */
public function testSelfSignedResolvesIfVerificationIsDisabled()
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('Not supported on HHVM');
}

$loop = new StreamSelectLoop();

$factory = new Factory();
$dns = $factory->create('8.8.8.8', $loop);

$secureConnector = new SecureConnector(
new Connector($loop, $dns),
$loop,
array(
'verify_peer' => false
)
);

$conn = Block\await($secureConnector->create('self-signed.badssl.com', 443), $loop);
$conn->close();
}
}