Skip to content

Commit d874bac

Browse files
committed
Improve TLS error messages during connection
1 parent 12d266a commit d874bac

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

src/SecureConnector.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function connect($uri)
4040
$context = $this->context;
4141

4242
$encryption = $this->streamEncryption;
43-
return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption) {
43+
return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri) {
4444
// (unencrypted) TCP/IP connection succeeded
4545

4646
if (!$connection instanceof Connection) {
@@ -54,10 +54,15 @@ public function connect($uri)
5454
}
5555

5656
// try to enable encryption
57-
return $encryption->enable($connection)->then(null, function ($error) use ($connection) {
57+
return $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) {
5858
// establishing encryption failed => close invalid connection and return error
5959
$connection->close();
60-
throw $error;
60+
61+
throw new \RuntimeException(
62+
'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(),
63+
0,
64+
$error
65+
);
6166
});
6267
});
6368
}

tests/SecureConnectorTest.php

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,26 @@ public function testConnectionToInvalidSchemeWillReject()
4949
$promise->then(null, $this->expectCallableOnce());
5050
}
5151

52-
public function testCancelDuringTcpConnectionCancelsTcpConnection()
52+
/**
53+
* @expectedException RuntimeException
54+
* @expectedExceptionMessage Connection cancelled
55+
*/
56+
public function testCancelDuringTcpConnectionCancelsTcpConnectionAndRejectsWithTcpRejection()
5357
{
54-
$pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
58+
$pending = new Promise\Promise(function () { }, function () { throw new \RuntimeException('Connection cancelled'); });
5559
$this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
5660

5761
$promise = $this->connector->connect('example.com:80');
5862
$promise->cancel();
5963

60-
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
64+
$this->throwRejection($promise);
6165
}
6266

63-
public function testConnectionWillBeClosedAndRejectedIfConnectioIsNoStream()
67+
/**
68+
* @expectedException UnexpectedValueException
69+
* @expectedExceptionMessage Base connector does not use internal Connection class exposing stream resource
70+
*/
71+
public function testConnectionWillBeClosedAndRejectedIfConnectionIsNoStream()
6472
{
6573
$connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
6674
$connection->expects($this->once())->method('close');
@@ -69,6 +77,57 @@ public function testConnectionWillBeClosedAndRejectedIfConnectioIsNoStream()
6977

7078
$promise = $this->connector->connect('example.com:80');
7179

72-
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
80+
$this->throwRejection($promise);
81+
}
82+
83+
public function testStreamEncryptionWillBeEnabledAfterConnecting()
84+
{
85+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
86+
87+
$encryption = $this->getMockBuilder('React\Socket\StreamEncryption')->disableOriginalConstructor()->getMock();
88+
$encryption->expects($this->once())->method('enable')->with($connection)->willReturn(new \React\Promise\Promise(function () { }));
89+
90+
$ref = new \ReflectionProperty($this->connector, 'streamEncryption');
91+
$ref->setAccessible(true);
92+
$ref->setValue($this->connector, $encryption);
93+
94+
$pending = new Promise\Promise(function () { }, function () { throw new \RuntimeException('Connection cancelled'); });
95+
$this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->willReturn(Promise\resolve($connection));
96+
97+
$promise = $this->connector->connect('example.com:80');
98+
}
99+
100+
/**
101+
* @expectedException RuntimeException
102+
* @expectedExceptionMessage Connection to example.com:80 failed during TLS handshake: TLS error
103+
*/
104+
public function testConnectionWillBeRejectedIfStreamEncryptionFailsAndClosesConnection()
105+
{
106+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
107+
$connection->expects($this->once())->method('close');
108+
109+
$encryption = $this->getMockBuilder('React\Socket\StreamEncryption')->disableOriginalConstructor()->getMock();
110+
$encryption->expects($this->once())->method('enable')->willReturn(Promise\reject(new \RuntimeException('TLS error')));
111+
112+
$ref = new \ReflectionProperty($this->connector, 'streamEncryption');
113+
$ref->setAccessible(true);
114+
$ref->setValue($this->connector, $encryption);
115+
116+
$pending = new Promise\Promise(function () { }, function () { throw new \RuntimeException('Connection cancelled'); });
117+
$this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->willReturn(Promise\resolve($connection));
118+
119+
$promise = $this->connector->connect('example.com:80');
120+
121+
$this->throwRejection($promise);
122+
}
123+
124+
private function throwRejection($promise)
125+
{
126+
$ex = null;
127+
$promise->then(null, function ($e) use (&$ex) {
128+
$ex = $e;
129+
});
130+
131+
throw $ex;
73132
}
74133
}

0 commit comments

Comments
 (0)