60

After upgrading to PHP 5.6 I get an error when trying to connect to a server via fsockopen()..

The certificate on the server (host) is self-signed

PHP Warning: fsockopen(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

code

if($fp = fsockopen($host, $port, $errno, $errstr, 20)){
    $this->request = 'POST '.substr($this->url, strlen($this->host)).' HTTP/1.1'.$crlf
        .'Host: '.$this->host.$crlf
        .'Content-Length: '.$content_length.$crlf
        .'Connection: Close'.$crlf.$crlf
        .$body;
    fwrite($fp, $this->request);

    while($line = fgets($fp)){
        if($line !== false){
            $this->response .= $line;
        }
    }

    fclose($fp);
}

Have tried

# cd /etc/ssl/certs/
# wget http://curl.haxx.se/ca/cacert.pem

php.ini

openssl.cafile = "/etc/ssl/certs/cacert.pem"

But the script still fails to work

update

This works

echo file_get_contents("/etc/ssl/certs/cacert.pem");

update 2

$contextOptions = array(
    'ssl' => array(
        'verify_peer' => true, // You could skip all of the trouble by changing this to false, but it's WAY uncool for security reasons.
        'cafile' => '/etc/ssl/certs/cacert.pem',
        //'CN_match' => 'example.com', // Change this to your certificates Common Name (or just comment this line out if not needed)
        'ciphers' => 'HIGH:!SSLv2:!SSLv3',
        'disable_compression' => true,
    )
);

$context = stream_context_create($contextOptions);

$fp = stream_socket_client("{$host}:{$port}", $errno, $errstr, 20, STREAM_CLIENT_CONNECT, $context);

error

PHP Warning: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

Machavity
  • 30,841
  • 27
  • 92
  • 100
clarkk
  • 27,151
  • 72
  • 200
  • 340
  • http://forum.directadmin.com/showthread.php?t=50885 – Mihai Aug 25 '15 at 18:36
  • Already have tried that.. I can't `locate` the file – clarkk Aug 25 '15 at 18:44
  • You have to look at the details. There are a variety of possible causes but not using the same domain as appears in the certificate is a common one. Can you access it without an error using `curl -v`? Also note that unless those versions of openssl have had all the security patches backported you may want to upgrade. – user650881 Aug 25 '15 at 18:47
  • The issue is not the server.. It works. Its the client-side which causes the problem – clarkk Aug 25 '15 at 20:59
  • Which OS are you using? – Swarf Sep 02 '15 at 00:45
  • Since PHP 5.6.0 (https://secure.php.net/ChangeLog-5.php#5.6.0) "Stream wrappers verify peer certificates and host names by default in encrypted client streams". Since the certificate that the server you're attempting to connect to is self signed (not signed by an authority like thawte.com), it will fail this check. You need to either get a new certificate for the remote server that has been signed or you need to disable peer verification (as aecend mentioned in his answer). – Tom Sep 09 '15 at 14:43

10 Answers10

36

The file that you downloaded (http://curl.haxx.se/ca/cacert.pem) is a bundle of the root certificates from the major trusted certificate authorities. You said that the remote host has a self-signed SSL certificate, so it didn't use a trusted certificate. The openssl.cafile setting needs to point to the CA certificate that was used to sign the SSL certificate on the remote host. PHP 5.6 has been improved over previous versions of PHP to now verify peer certificates and host names by default (http://php.net/manual/en/migration56.openssl.php)

You'll need to locate the CA certificate that was generated on the server that signed the SSL certificate and copy it to this server. If you're using self-signed certificates, you'll need to add the CA cert that was used to sign the remote host's SSL certificate to the trusted store on the server you're connecting from OR use stream contexts to use that certificate for each individual request. Adding it to the trusted certificates is the simplest solution. Just add the contents of the remote host's CA cert to the end of the cacert.pem file you downloaded.

Previous:

fsockopen doesn't support stream contexts, so use stream_socket_client instead. It returns a resource that can be used with all the commands that fsockopen resources can.

This should be a drop in replacement for the snippet you have in your question:

<?php

$contextOptions = array(
    'ssl' => array(
        'verify_peer' => true, // You could skip all of the trouble by changing this to false, but it's WAY uncool for security reasons.
        'cafile' => '/etc/ssl/certs/cacert.pem',
        'CN_match' => 'example.com', // Change this to your certificates Common Name (or just comment this line out if not needed)
        'ciphers' => 'HIGH:!SSLv2:!SSLv3',
        'disable_compression' => true,
    )
);

$context = stream_context_create($contextOptions);

$fp = stream_socket_client("tcp://{$host}:{$port}", $errno, $errstr, 20, STREAM_CLIENT_CONNECT, $context);

if (!$fp) {

    echo "$errstr ({$errno})<br />\n";

}else{
    
    $this->request = 'POST '.substr($this->url, strlen($this->host)).' HTTP/1.1'.$crlf
        .'Host: '.$this->host.$crlf
        .'Content-Length: '.$content_length.$crlf
        .'Connection: Close'.$crlf.$crlf
        .$body;

    fwrite($fp, $this->request);

    while (!feof($fp)) {
        $this->response .= fgets($fp);
    }

    fclose($fp);

}
miken32
  • 42,008
  • 16
  • 111
  • 154
aecend
  • 2,432
  • 14
  • 16
  • It should work with `fsockopen` alone.. This is much too complicated – clarkk Sep 03 '15 at 08:23
  • I'm not sure what distribution of linux you're using, but try running `sudo service apache2 restart`. It may be that you need to restart apache after adding the openssl.cafile line to php.ini – aecend Sep 03 '15 at 15:33
  • @clarkk The host you're connecting to, is the SSL certificate for that host a self-signed certificate that you have the CA certificate for? PHP 5.6 now validates SSL certificates when using fsockopen, so the CA certificate used to sign the remote host's SSL certificate needs to be trusted on your system. My answer above is just another way to use a specific certificate for the request. – aecend Sep 03 '15 at 15:41
  • I always restart apache after config change.. But the cert on the server (host) is self-signed – clarkk Sep 03 '15 at 16:16
  • @clarkk Could you try the code that I posted to see if it does actually work? Because if it does then we can address trusting the CA certificate on your host so fsockopen will work. If it doesn't work at all, then there's something else wrong. – aecend Sep 03 '15 at 20:34
  • so, the best solution would be to drop the self-signed cert and use a validated cert? :) – clarkk Sep 08 '15 at 21:27
  • have added a validated SSL cert to the server.. this is much too complicated if the client must handle multiple certs.. – clarkk Sep 09 '15 at 15:49
  • I'm not sure what you mean by multiple certs. fsockopen simply verifies the validity of the certificate and the peer it belongs to. As long as you're using validated certs, or have installed the self signed CA as a trusted root, everything works exactly the same as before PHP 5.6 – aecend Sep 09 '15 at 16:11
18

I faced a similar issue during work with Ubuntu 16.04 by using Docker. In my case that was a problem with Composer, but error message (and thus the problem) was the same.

Because of minimalist Docker-oriented base image I had missing ca-certificates package and simple apt-get install ca-certificates helped me.

Vladimir Posvistelik
  • 3,843
  • 24
  • 28
4

Add

$mail->SMTPOptions = array(
'ssl' => array(
    'verify_peer' => false,
    'verify_peer_name' => false,
    'allow_self_signed' => true
));

before

mail->send()

and replace

require "mailer/class.phpmailer.php";

with

require "mailer/PHPMailerAutoload.php";
4

The problem is in new PHP Version in macOS Sierra

Please add

stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
  • It worked for me in Production environment, but in sandbox it is working. Can you please put some light on it? – Janmenjaya Jun 19 '17 at 13:39
  • 4
    Note that this is not secure as it tells PHP to ignore the fact that the peer is not a valid match. – Alexis Wilke Jul 26 '17 at 18:59
  • 2
    @AlexisWilke : thank you, i know that, but i cant find other solutions for this issue. if you have any good idea, please tell me – Nguyễn Quang Tuấn Dec 28 '17 at 08:01
  • 1
    First of all, I think your answer you say that there is a security risk involved in turning off the peer verification, hence my comment. As for a solution, it could be that the server still uses SSL3 which has been deprecated. So you should not access that server anymore until it gets upgraded... – Alexis Wilke Dec 28 '17 at 08:23
  • add where exactly sir ?? – aspirinemaga Dec 08 '20 at 14:31
3

In my case, I was on CentOS 7 and my php installation was pointing to a certificate that was being generated through update-ca-trust. The symlink was /etc/pki/tls/cert.pem pointing to /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem. This was just a test server and I wanted my self signed cert to work properly. So in my case...

# My root ca-trust folder was here. I coped the .crt file to this location
# and renamed it to a .pem
/etc/pki/ca-trust/source/anchors/self-signed-cert.pem

# Then run this command and it will regenerate the certs for you and
# include your self signed cert file.
update-ca-trust

Then some of my api calls started working as my cert was now trusted. Also if your ca-trust gets updated through yum or something, this will rebuild your root certificates and still include your self signed cert. Run man update-ca-trust for more info on what to do and how to do it. :)

n0nag0n
  • 1,575
  • 1
  • 17
  • 25
0

Firstable, make sure that you Antivirus software doesn't block SSL2.
Because I could not solve a problem for a long time and only disabling the antivirus helped me

Igor Shumichenko
  • 394
  • 3
  • 12
0

I used the following script to check the issue

<?php
$url = "mail.example.com";// your host which has issue
$orignal_parse = parse_url($url, PHP_URL_HOST);
$get = stream_context_create(array("ssl" => array("capture_peer_cert" => TRUE)));
$read = stream_socket_client("ssl://".$url.":993", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $get);

if (!$read) {
// ssl connection failed for some reason
// could be a certificate error or failure to connect on port 443
echo "Failed to connect to site.  Error {$errno}: {$errstr}\n";
} else {
   $cert = stream_context_get_params($read);
   $certinfo = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);
   var_dump($certinfo);
}
?>

I was getting below error

PHP Warning:  stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:
error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed 

Updated ca certificates and then ran reinstall that helped

yum reinstall ca-certificates
Mahesh Hegde
  • 1,131
  • 10
  • 12
-1

You mention the certificate is self-signed (by you)? Then you have two choices:

  • add the certificate to your trust store (fetching cacert.pem from cURL website won't do anything, since it's self-signed)
  • don't bother verifying the certificate: you trust yourself, don't you?

Here's a list of SSL context options in PHP: https://secure.php.net/manual/en/context.ssl.php

Set allow_self_signed if you import your certificate into your trust store, or set verify_peer to false to skip verification.

The reason why we trust a specific certificate is because we trust its issuer. Since your certificate is self-signed, no client will trust the certificate as the signer (you) is not trusted. If you created your own CA when signing the certificate, you can add the CA to your trust store. If your certificate doesn't contain any CA, then you can't expect anyone to connect to your server.

DavidS
  • 1,660
  • 1
  • 12
  • 26
  • The first solution of "do you trust yourself?" is the correct one: add yourself to the trust store. The second one is actively harmful: "do you trust yourself? Yeah, now you can trust anyone who says they're you!" – Piskvor left the building Apr 06 '18 at 14:57
-1

If you are using macOS sierra there is a update in PHP version. you need to have Entrust.net Certificate Authority (2048) file added to the PHP code. more info check accepted answer here Push Notification in PHP using PEM file

Community
  • 1
  • 1
Im Batman
  • 1,842
  • 1
  • 31
  • 48
-2

Have you tried using the stream_context_set_option() method ?

$context = stream_context_create();
$result = stream_context_set_option($context, 'ssl', 'local_cert', '/etc/ssl/certs/cacert.pem');
$fp = fsockopen($host, $port, $errno, $errstr, 20, $context);

In addition, try file_get_contents() for the pem file, to make sure you have permissions to access it, and make sure the host name matches the certificate.

uri2x
  • 3,162
  • 1
  • 11
  • 25