NotionCommotion Posted May 16, 2017 Share Posted May 16, 2017 Trying to add tls sockets, but getting an error. I noticed that the client doesn't utilize a certificate. Will it need to? If not specified under the reactphp script, can it be a php.ini setting? Thanks Server $server = new \React\Socket\TcpServer($this->host['url'].':'.$this->host['port'], $loop); if($this->host['tls']) { $server = new \React\Socket\SecureServer($server, $loop, ['local_cert' => '../sslforfree/certificate.crt']); } $server->on('connection', function (\React\Socket\ConnectionInterface $conn) { }); Option 1 client $connector = new \React\Socket\TimeoutConnector(new \React\Socket\Connector($loop), $this->settings['httpWaitTime'], $loop); if($this->settings['socket']['tls']) { $connector = new \React\Socket\SecureConnector($connector, $loop, []); } $connector->connect($this->settings['socket']['url'].':'.$this->settings['socket']['port']) ->then(function (\React\Socket\ConnectionInterface $connection) use (&$response, $loop, $msg) { // ... })->otherwise(function(\RuntimeException $error) use(&$response, $loop){ // ... }); Option 2 client $connector=new \React\Socket\Connector($loop); $connector->connect(($this->settings['socket']['tls']?'tls://':'').$this->settings['socket']['url'].':'.$this->settings['socket']['port']) ->then(function (\React\Socket\ConnectionInterface $connection) use (&$response, $loop, $msg) { // ... })->otherwise(function(\RuntimeException $error) use(&$response, $loop){ // ... }); Error generated by client script May 16 10:41:38 devserver server: No connection to server: (No connection to server: (Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure)): (32003) May 16 10:41:38 devserver server: ERROR in /var/www/datalogger/src/classes/Helpers.php (199): No connection to server: (No connection to server: (Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure)): (32003) Quote Link to comment https://forums.phpfreaks.com/topic/303947-sockets-and-tls/ Share on other sites More sharing options...
kicken Posted May 16, 2017 Share Posted May 16, 2017 Your client needs to have a copy of the server's certificate which it can use to verify the server connection. You can specify the certificate with the cafile option. <?php $connector = new \React\Socket\TimeoutConnector(new \React\Socket\Connector($loop), $this->settings['httpWaitTime'], $loop); if($this->settings['socket']['tls']) { $connector = new \React\Socket\SecureConnector($connector, $loop, [ 'cafile' => 'server.crt' ]); } $connector->connect($this->settings['socket']['url'].':'.$this->settings['socket']['port']) ->then(function (\React\Socket\ConnectionInterface $connection) use (&$response, $loop, $msg) { // ... })->otherwise(function(\RuntimeException $error) use(&$response, $loop){ // ... }); Quote Link to comment https://forums.phpfreaks.com/topic/303947-sockets-and-tls/#findComment-1546564 Share on other sites More sharing options...
NotionCommotion Posted May 17, 2017 Author Share Posted May 17, 2017 (edited) Thank you kicken, I made the changes, but am still getting Unable to complete SSL/TLS handshake. I've verified that I am not making a bonehead error such as wrong paths to the certificates on both the client and server. The error originates in React\Socket\src\StreamEncryption.php in the following method. public function toggleCrypto($socket, Deferred $deferred, $toggle) { set_error_handler(array($this, 'handleError')); $result = stream_socket_enable_crypto($socket, $toggle, $this->method); //stream_socket_enable_crypto(15, true, 57) returns false $result = stream_socket_enable_crypto($socket, $toggle, $this->method); //.... } Before posting my original post, I thought that maybe the client would need some knowledge of the certificate, and searched for it in the documentation, but did not find anything. After getting your post, I searched for the text cafile in all of the reactphp scripts, but did not find it. I also searched for anything with the text cert in in, and found only the property local_cert. Odd how they give no description about giving the client the certificate, however, this is uncharted waters for me and everything is odd. Just for fun, I changed cafile to local_cert and now get Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): Unable to set private key file `/var/www/datalogger/sslforfree/certificate.crt'. Not a step in the right direction... Just to double confirm, both the server and client both include the same certificate, right? Shouldn't the server get the certificate from the client when requesting service? Where does the private key come in? Note that my certificate is call certificate.crt provided by slforfree, yet the example reactphp scripts use extention pem. Could this be related? Thanks again for all your help. EDIT. What versions of reactphp are you using? My composer.json file includes the following: "react/socket": "^0.8.0", "react/stream": "^0.7.0", "react/event-loop": "^0.4.2" Edited May 17, 2017 by NotionCommotion Quote Link to comment https://forums.phpfreaks.com/topic/303947-sockets-and-tls/#findComment-1546571 Share on other sites More sharing options...
Solution kicken Posted May 17, 2017 Solution Share Posted May 17, 2017 One side needs the certificate + key (typically the server) and the other side needs just the certificate for verification purposes (typically the client). The cafile option is part of PHP, not react. local_cert is as well. SecureServer and SecureConnector call stream_context_set_option with whatever parameters you pass in the constructor so you won't find them within the react source anywhere, you need to look at the PHP documentation for the SSL wrapper. If you add an error handler to your listening side you can get a more detailed message. $server->on('error', function($error) use ($loop){ echo "Some error occured!\n"; var_dump($error); $loop->stop(); }); When I tried using similar code to what you posted, this is the error that was returned: UnexpectedValueException: Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): Unable to set private key file `/home/kicken/secureTest/certificate.pem' in /home/kicken/secureTest/vendor/react/socket/src/StreamEncryption.php on line 117 There are two ways to resolve that error. Either combine your private key and certificate into a single file (just concatenate them together) or specify the location of the key file using the locak_pk option. You may need to specify the proper name on the certificate for verification to complete successfully. You can do that using the peer_name option. I had to do that since I just used a random certificate I had laying around rather than create a new one explicitly for this purpose. Server: <?php require 'vendor/autoload.php'; $loop = \React\EventLoop\Factory::create(); $server = new \React\Socket\TcpServer('127.0.0.1:12123', $loop); $server = new \React\Socket\SecureServer($server, $loop, [ 'local_cert' => 'combined.pem' ]); $server->on('connection', function (\React\Socket\ConnectionInterface $conn) use($loop) { echo "Received new connection!\n"; var_dump($conn); $loop->stop(); }); $server->on('error', function($error) use ($loop){ echo "Some error occured!\n"; var_dump($error); $loop->stop(); }); $loop->run(); Client: <?php require 'vendor/autoload.php'; $loop = \React\EventLoop\Factory::create(); $connector = new \React\Socket\TimeoutConnector(new \React\Socket\Connector($loop), 10, $loop); $connector = new \React\Socket\SecureConnector($connector, $loop, [ 'cafile' => 'certificate.pem' , 'peer_name' => 'Kickenscripts.us' ]); $connector->connect('127.0.0.1:12123') ->then(function (\React\Socket\ConnectionInterface $connection) use (&$response, $loop, $msg) { echo "Connected successfully!"; var_dump($connection); $loop->stop(); })->otherwise(function(\RuntimeException $error) use(&$response, $loop){ echo "Failed to connect!"; var_dump($error); $loop->stop(); }); $loop->run(); Note that my certificate is call certificate.crt provided by slforfree, yet the example reactphp scripts use extention pem. Could this be related? No, the extension doesn't make a difference. Some people use .crt to indicate a certificate, some use .pem because it's a PEM-encoded file format. Use whichever you prefer. Quote Link to comment https://forums.phpfreaks.com/topic/303947-sockets-and-tls/#findComment-1546572 Share on other sites More sharing options...
NotionCommotion Posted May 17, 2017 Author Share Posted May 17, 2017 (edited) Thank you kicken, Ah, of course cafile and local_cert is part of stream_context_set_option, and not reactphp. I should have known. Just to make sure, server and the client being on the same machine won't be an issue, right? Regarding the peer_name (i.e. Kickenscripts.us), is this the host name, or something included in the certificate? Currently I am setting verify_peer and verify_peer_name to false which is probably a bad idea. EDIT. When creating a csr, openssl asks for the FQDN, but allows for now entry for default. Should it always be included? Guess not relevant here, but if used for subdomain.example.com, should it be example.com or subdomain.example.com? I see now how for the server I can either use local_pk and local_cert or local_cert, and can even set the directory path (for both?) using capath. I seem to get better results with using the two files, but neither is working: Server when using cafile and local_pk Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): SSL_R_NO_SHARED_CIPHER: no suitable shared cipher could be used. This could be because the server is missing an SSL certificate (local_cert context option) Client Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure Server when using combined.pem (which was created using cat private.key certificate.crt > combined.pem and not openssl) Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): Unable to set local cert chain file `/var/www/datalogger/sslforfree/combined.pem'; Check that your cafile/capath settings include details of your certificate and its issuer Client Unable to complete SSL/TLS handshake: I am thinking it might have something to do with my certificate which was created using https://www.sslforfree.com/. Note the following. I am thinking I either need to CSR or maybe even don't use this at first and start off using self signed keys. Private Keys are generated in your browser and never transmitted. For modern browsers we generate a private key in your browser using the Web Cryptography API and the private key is never transmitted. The private key also gets deleted off your browser after the certificate is generated. If your browser does not support the Web Cryptography API then the keys will be generated on the server using the latest version of OpenSSL and outputted over SSL and never stored. For the best security you are recommended to use a supported browser for client generation. You can also provide your own CSR when using manual verification in which case the private key is handled completely on your end. Thanks again for all your help! Edited May 17, 2017 by NotionCommotion Quote Link to comment https://forums.phpfreaks.com/topic/303947-sockets-and-tls/#findComment-1546577 Share on other sites More sharing options...
NotionCommotion Posted May 17, 2017 Author Share Posted May 17, 2017 (edited) Eureka! I still would like confirmation whether I am doing the peer_name part correct. Is this the hostname for the server and the commonName for the certificate? Thanks for all your help! How I generated my keys. $certificateData = [ "countryName" => "US", "stateOrProvinceName" => "WA", "localityName" => "Seattle", "organizationName" => "TestingOrganizationName.com", "organizationalUnitName" => "TestingOrganizationalUnitName", "commonName" => "devserver.michaels.lan", "emailAddress" => "Testing@emailAddress.com" ]; $pem_passphrase = 'testing'; // Generate certificate $privateKey = openssl_pkey_new(); $certificate = openssl_csr_new($certificateData, $privateKey); $certificate = openssl_csr_sign($certificate, null, $privateKey, 365); // Generate PEM file $pem = []; openssl_x509_export($certificate, $pem[0]); file_put_contents('./sslforfree/certificate.pem', $pem[0]); openssl_pkey_export($privateKey, $pem[1], $pem_passphrase); //Remove $pem_passphrase for no $pem = implode($pem); file_put_contents('./sslforfree/private.pem', $pem); Server $server = new \React\Socket\TcpServer($this->host['url'].':'.$this->host['port'], $loop); if($this->host['tls']) { $server = new \React\Socket\SecureServer($server, $loop, [ 'local_cert' => realpath( __DIR__.'/../../sslforfree/private.pem'), 'passphrase' => 'testing' ]); } $server->on('connection', function (\React\Socket\ConnectionInterface $conn) {/* ... */}); $server->on('error', function($error) use ($loop){ syslog(LOG_INFO,'onServerError: '.$error->getMessage()); //$loop->stop(); }); Client if($this->settings['socket']['tls']) { $connector = new \React\Socket\SecureConnector($connector, $loop, [ 'cafile' => realpath( __DIR__.'/../../sslforfree/certificate.pem'), 'peer_name' => 'devserver.michaels.lan', 'allow_self_signed'=>true, ]); } //$this->settings['socket']['url'] is set to 127.0.0.1 $connector->connect($this->settings['socket']['url'].':'.$this->settings['socket']['port']) ->then(function (\React\Socket\ConnectionInterface $connection) use (&$response, $loop, $msg) {/*...*/}) ->otherwise(function(\RuntimeException $error) use(&$response, $loop){/*...*/}); Edited May 17, 2017 by NotionCommotion Quote Link to comment https://forums.phpfreaks.com/topic/303947-sockets-and-tls/#findComment-1546580 Share on other sites More sharing options...
kicken Posted May 17, 2017 Share Posted May 17, 2017 (edited) Just to make sure, server and the client being on the same machine won't be an issue, right?Same machine is fine, won't make a difference. My examples/tests were done by just running the server.php and client.php files in separate terminals and connecting to localhost. Regarding the peer_name (i.e. Kickenscripts.us), is this the host name, or something included in the certificate? Currently I am setting verify_peer and verify_peer_name to false which is probably a bad idea. EDIT. When creating a csr, openssl asks for the FQDN, but allows for now entry for default. Should it always be included? Guess not relevant here, but if used for subdomain.example.com, should it be example.com or subdomain.example.com?It's the common name of the certificate. For web certificates it should be the same as your domain name. You can use the command openssl x509 -in certificate.pem -noout -subject to see what your certificate is set to. What you need your peer_name set to is the CN= part. If you're using a URL that includes the domain name then you can skip setting the peer name, it'll automatically use the name in the URL. I am thinking it might have something to do with my certificate which was created using https://www.sslforfree.com/. Note the following. I am thinking I either need to CSR or maybe even don't use this at first and start off using self signed keys. It seems if you're using a certificate chain setup then things get slightly more complicated. I was initially using a self-signed certificate to test with. It took me a little while of playing around but what I eventually was able to get working is a setup like this: For server.php Create combined.pem by concatenating your certificate and any intermediate CA certificates: cat yours.pem intermediate1.pem intermediate2.pem ... > combined.pem Save your private key in key.pem Configure local_cert => combined.pem and local_pk => key.pem For client.phpObtain your CA's root certificate and save it in root.pem Configure cafile => root.pem If you're only need for the certificates is to secure the channel between your own two programs then you can just use your own self-signed certificates, there's no need to go through a CA. You only need to use a CA when you need a certificate that will be trusted by peoples browsers/other software by default. Edited May 17, 2017 by kicken Quote Link to comment https://forums.phpfreaks.com/topic/303947-sockets-and-tls/#findComment-1546581 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.