How to Force a Certain Tls Version in a PHP Stream Context for the Ssl:// Transport

How to force a certain TLS version in a PHP stream context for the ssl:// transport?

PHP 5.6+ Users

This is a new feature as documented on the PHP 5.6 OpenSSL Changes page.

At time of writing this, PHP5.6 is in Beta1 and thus this isn't overly useful. People of the future - lucky you!

The future is upon us. PHP 5.6 is a thing and its use should be encouraged. Be aware that it deprecates some fairly widely used things like mysql_* functions so care should be taken when upgrading.

Everyone else

@toubsen is correct in his answer - this isn't directly possible. To elaborate on his suggested workarounds... when working around a problem where a supplier's API server wasn't correctly negotiating TLSv1.2 down to its supported TLSv1.0, sending a small subset of ciphers seemed to allow negotiation to complete correctly. Stream context code is:

$context = stream_context_create(
[
'ssl' => [
'ciphers' => 'DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-SHA:KRB5-DES-CBC3-MD5:KRB5-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DES-CBC3-SHA:DES-CBC3-MD5:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:AES128-SHA:RC2-CBC-MD5:KRB5-RC4-MD5:KRB5-RC4-SHA:RC4-SHA:RC4-MD5:RC4-MD5:KRB5-DES-CBC-MD5:KRB5-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DES-CBC-SHA:DES-CBC-MD5:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-KRB5-RC4-MD5:EXP-KRB5-RC4-SHA:EXP-RC4-MD5:EXP-RC4-MD5',
],
]
);

SOAP Users

PHP's SOAP client doesn't use curl, nor does it seem to use the default context set with stream_context_set_default. As such, the created context needs to be passed to the SOAPClient constructor in the 2nd parameter as such:

$soap_client = new SOAPClient('http://webservices.site.com/wsdlfile.wsdl', array('stream_context' => $context));

Why those Ciphers?

Running the command openssl ciphers on the server gives you a list of supported ciphers in the above format. Running openssl ciphers -v tells you those that are TLSv1.2 specific. The above list was compiled from all of the non-TLSv1.2 ciphers reported by OpenSSL.

openssl ciphers -v | grep -v 'TLSv1.2' | cut -d ' ' -f 1 | tr "\n" ':'

How to force TLS 1.2 with SoapClient?

As it turns out, the above code is 100% correct.

In my case, the error lay somewhere in the certificate. For still unknown reasons, our self-generated SSL client certificate did not cope well with TLS 1.2. After we generated a new certificate, everything worked like magic.

Things I've tried / verified

  • Make sure your have a supported openssl verion (>= 1.0.1)
  • Consider manually specifying the ciphers
  • Debug the connection using Wireshark to verify the protocol

Also, take a look at How to force a certain TLS version in a PHP stream context for the ssl:// transport?

PHP Sockets with SSL / TLS - no suitable shared cipher could be used

It turns out that the issue was related to OpenSSL and the transport streams available. The issue was resolved by:

  • Updating OpenSSL to the latest version (1.0.2k, to support TLS v1.2)
  • Recompiling PHP to enable tlsv1.2 transport stream
  • Updating the code to use tlsv1.2:// as the protocol on both the server and the client, e.g.

    $server = stream_socket_server('tlsv1.2://'.$address.':'.$port, $errno, 
    $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);

    and

    $socket = stream_socket_client( 'tlsv1.2://'.$host.':'.$port, $errno,
    $errstr, 30, STREAM_CLIENT_CONNECT, $context)

Once the transport stream was updated to TLSv1.2, there was no need for the cipher option, and the scripts successfully negotiated a TLS connection.

How to deal with self-signed TLS certificates in Laravel's SMTP driver?

Well in that link you provided the solution is straight-forward.

The correct solution is to fix your SSL config - it's not PHP's fault!

how to fix it? in config/mail.php ,'driver' => env('MAIL_DRIVER', 'smtp'), should be 'driver' => env('MAIL_DRIVER', 'mail'), (credits: Danyal Sandeelo)

How do I get the PHP SOAP client to communicate with a service running over SSL with an invalid certificate

This was two years ago but I think it deserves an answer.

The SoapClient class in PHP uses PHP streams to communicate over HTTP. For various reasons SSL over PHP streams is insecure1, but in this case your problem is that it's too secure.

The first step towards a solution is to use the stream_context option when constructing your SoapClient2. This will allow you to specify more advanced SSL settings3:

// Taken from note 1 below.
$contextOptions = array(
'ssl' => array(
'verify_peer' => true,
'cafile' => '/etc/ssl/certs/ca-certificates.crt',
'verify_depth' => 5,
'CN_match' => 'api.twitter.com',
'disable_compression' => true,
'SNI_enabled' => true,
'ciphers' => 'ALL!EXPORT!EXPORT40!EXPORT56!aNULL!LOW!RC4'
)
);
$sslContext = stream_context_create($contextOptions);
// Then use this context when creating your SoapClient.
$soap = new SoapClient('https://domain.com/webservice.asmx?wsdl', array('stream_context' => $sslContext));

The ideal solution to your problem is to create your own CA certificate, use that to sign the SSL certificate, and add your CA certificate to the cafile. Even better might be to only have that certificate in the cafile, to avoid some rogue CA signing someone else's certificate for your domain, but that won't always be practical.

If what you are doing does not need to be secure (such as during testing), you can also use SSL settings in a stream context to reduce the security of your connection. The option allow_self_signed will allow self-signed certificates:

$contextOptions = array(
'ssl' => array(
'allow_self_signed' => true,
)
);
$sslContext = stream_context_create($contextOptions);
$soap = new SoapClient('https://domain.com/webservice.asmx?wsdl', array('stream_context' => $sslContext));

Links:

  1. Survive the Deep End: PHP Security - Transport Layer Security (HTTPS, SSL, and TLS) - PHP Streams
  2. PHP: SoapClient::SoapClient
  3. PHP: SSL Context Options

how to fix stream_socket_enable_crypto(): SSL operation failed with code 1

Try changing the app/config/email.php

smtp to mail

How to make TLS connection from PHP in web server, and safely

TLS is the proper name, however most people still call it SSL. In PHP you can make this connection using CURL.

With a TLS/SSL client you only need the public key to verify a remote host. This public key is just that public, it doesn't matter if it gets leaked to an attacker. On the server side Apache has access both the public and private key. These keys are protected using normal file access privileges. Under *nix systems these keys are commonly stored somewhere in /etc/, owned by the Apache process and chmod 400 is best.

The easiest authentication method for clients would be a simple username/password. You can use SSL to authenticate both the client and server. This would require you to store the private key somewhere where your PHP app has access, ideally outside of the webroot with a chmod 400, but you could easily rename it to .php or put it in a folder with a .htaccess that contains deny from all. On the server side you can verify the clients certificate using these environmental variables.

If you just want a TLS connection and not an HTTPs. Then you can use stream_context_set_option by setting the Context Options:

<?php 
$context = stream_context_create();
$result = stream_context_set_option($context, 'ssl', 'local_cert', '/path/to/keys.pem');
// This line is needed if your cert has a passphrase
$result = stream_context_set_option($context, 'ssl', 'passphrase', 'pass_to_access_keys');

$socket = stream_socket_client('tls://'.$host.':443', $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
?>


Related Topics



Leave a reply



Submit