How to get peer cert chain from tls handshake phase 'try to enable encryption'?
flybyray opened this issue · comments
How to get SSL context option capture_peer_cert_chain
...
from a failing "try to enable encryption" - peer certs already presented by tls handshake initiation -
socket/src/SecureConnector.php
Lines 63 to 66 in f54040f
?
Context:
Assume we connect to a server which requires client certificates to establish a connection. We will get peer certificates but fail if the server closes the connection because of missing client certificates.
This issue has some relevance at Icinga/icingaweb2-module-x509#66 .
We can fix it by dirty hacking but I just want to ask what the architects of reactphp/socket have in mind how to resolve this with this library.
Thanks for clearance
@flybyray Thanks for reporting, this is an interesting feature request.
To recap the way I understand this: After specifying the capture_peer_cert_chain
context option, we should expose the resulting peer_certificate_chain
context option even if the connection fails. This is indeed not currently exposed and the underlying connection will be closed immediately before raising an Exception
as documented.
I would love to see some input (PRs?) to discuss/suggest some possible APIs 👍
@clue I came up with an approach to solve this issue in Icinga/icingaweb2-module-x509#76. Basically we intercept the connection in a custom connector and listen for the close event:
/**
* Connector that captures stream context options upon close of the underlying connection
*/
class StreamOptsCaptureConnector implements ConnectorInterface
{
/** @var array|null */
protected $capturedStreamOptions;
/** @var ConnectorInterface */
protected $connector;
public function __construct(ConnectorInterface $connector)
{
$this->connector = $connector;
}
/**
* @return array
*/
public function getCapturedStreamOptions()
{
return (array) $this->capturedStreamOptions;
}
/**
* @param array $capturedStreamOptions
*
* @return $this
*/
public function setCapturedStreamOptions($capturedStreamOptions)
{
$this->capturedStreamOptions = $capturedStreamOptions;
return $this;
}
public function connect($uri)
{
return $this->connector->connect($uri)->then(function (ConnectionInterface $conn) {
$conn->on('close', function () use ($conn) {
if (is_resource($conn->stream)) {
$this->setCapturedStreamOptions(stream_context_get_options($conn->stream));
}
});
return resolve($conn);
});
}
}
Example usage:
$connector = new Connector($loop);
$streamCaptureConnector = new StreamOptsCaptureConnector($connector);
$secureConnector = new SecureConnector($streamCaptureConnector, $loop, [
'verify_peer' => false,
'verify_peer_name' => false,
'capture_peer_cert_chain' => true,
'SNI_enabled' => true,
'peer_name' => $peerName
]);
$connector->connect($url)->then(
function (ConnectionInterface $conn) use ($streamCaptureConnector) {
// Close connection in order to capture stream context options
$conn->close();
$capturedStreamOptions = $streamCaptureConnector->getCapturedStreamOptions();
...
},
function (Exception $exception) use ($streamCaptureConnector) {
$capturedStreamOptions = $streamCaptureConnector->getCapturedStreamOptions();
if (isset($capturedStreamOptions['ssl']['peer_certificate_chain'])) {
// The scanned target presented its certificate chain despite throwing an error
// This is the case for targets which require client certificates for example
...
}
}
)->otherwise(function (Exception $e) {
echo $e->getMessage() . PHP_EOL;
echo $e->getTraceAsString() . PHP_EOL;
});
Do you see any caveats with this approach?
@lippserd Nice solution! Note that the Connection::$stream
property is marked as @internal
and should ideally not be relied upon. There's been some debate to eventually expose this in a more permanent way (#150 and others), but at the moment there are no plans to remove this property any time soon 👍
@clue I somehow solved it. I think i did something based on this: #221 (comment)
I think the #252 is something different. But I did not tested it.
@flybyray Glad to hear! Give this has been answered, I'm closing this for now. Please come back with more details if this problem persists and we can always reopen this 👍