How to set the list of trusted certificate authorities as a socket client in PHP?
In the context of IHE Connectathon, I want to make a raw socket server that responds to an ATNA profile, which requires a TLS socket with certificates on both ends.
If I summarize my problem in this message : https://groups.google.com/d/msg/eu_connectathon/O-VGI_3cltw/ARsElA65ZkkJ
EDIT : Sorry, Google Groups is private, here is the message:
Hi Florian,
What is the exact meaning of the error message "The server requested a certificate, but the issuer list does not contain a valid certificate authority". Means the implementation of the TLS tool client has changed over the years, or am I using the wrong certificate?
This message means that the server has sent a certificate request message to the client without any value in the certificate_authorities field.
I ran into this issue last year and had a discussion with the developers of the TLS tool. He claims that if the server doesn't include this field, assuming you'll be connecting to multiple top-level affinity domains (each with its own CA), the client won't know what certificate to return.
It seems that OpenSSL can be instructed to return this value by calling SSL_CTX_set_client_CA_list, for example in DcmTLSTransportLayer::addTrustedCertificateFile. I haven't tested this with TLS tools, but I'd like this to be done before the connectathon starts.
But my implementation in PHP is different from theirs. It seems that PHP lacks the "SSL CTX set client CA list" possibility to tell the client which certificate authority should be used.
$context = stream_context_create();
if ($certificate) {
// Server certificate + private key
stream_context_set_option($context, 'ssl', 'local_cert', "/path/to/server.pem");
stream_context_set_option($context, 'ssl', 'passphrase', $passphrase);
// Client public certificates
stream_context_set_option($context, 'ssl', 'cafile', "/path/to/ca.pem");
stream_context_set_option($context, 'ssl', 'allow_self_signed', false);
stream_context_set_option($context, 'ssl', 'verify_peer', true);
stream_context_set_option($context, 'ssl', 'peer_name', "TlsTools2");
stream_context_set_option($context, 'ssl', 'capture_peer_cert', true);
stream_context_set_option($context, 'ssl', 'capture_peer_cert_chain', true);
}
$this->__socket = @stream_socket_server("tcp://$address:$port", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
The IHE Gazelle TLS client tells me "The server requested a certificate, but there is no valid certificate authority in the issuer list."
The message between client and server passes, but the test is not correct because the prompt message is "not secure enough".
Do you see the problem, are there more options for PHP that I don't see?
thank you for your help.
Edit : Following @ rdlowrey 's suggestion, I just created a bug report: https://bugs.php.net/bug.php?id=69215
As I mentioned in my original comment:
PHP's stream server implementation never actually uses SSL_CTX_set_client_CA_list() for encrypted streams.
This bug has been corrected upstream as described in the related bug report:
https://bugs.php.net/bug.php?id=69215
This change will manifest once the PHP 5.6.8 binaries are released (or you can manually build PHP against the current sources prior to that release).
implement
With the updated binaries, the OP's example code works as expected without modification. Simple example of encryption context used in server:
<?php
$serverCtx = stream_context_create(['ssl' => [
'local_cert' => '/path/to/my-server-cert.pem',
'passphrase' => 'elephpant',
'cafile' => '/path/to/my-ca-certs.pem',
'verify_peer' => true
]]);
In the example above, PHP automatically used the name from the certificate found in the file referenced above my-ca-certs.pem
when sending the client CA list as part of the TLS handshake .
notes
Peer name verification is not automatically enabled when peer verification is enabled in an encrypted server stream via "verify_peer" => true
PHP . Unless you only want to allow a single certificate holder (with a specific known name) to access the server, this is exactly what you want. This default behavior supports the more common use case of allowing any client whose certificate is signed by a trusted CA to establish a connection to the server. However, if you wish to enforce name verification in the encrypted server, modify the above context example as follows:
<?php
$serverCtx = stream_context_create(['ssl' => [
'local_cert' => '/path/to/my-server-cert.pem',
'passphrase' => 'elephpant',
'cafile' => '/path/to/my-ca-certs.pem',
'verify_peer' => true,
'verify_peer_name' => true, // verify the name on the cert
'peer_name' => 'zanzibar' // ensure the cert's name matches this
]]);