SSL 错误 SSL3_GET_SERVER_CERTIFICATE:certificate 验证失败

SSL error SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

升级到 PHP 5.6 后,我在尝试通过 fsockopen()..

连接到服务器时遇到错误

服务器(主机)上的证书是自签名的

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

代码

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);
}

尝试过

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

php.ini

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

但脚本仍然无法运行

更新

这个有效

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

更新 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);

错误

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

您尝试过使用 stream_context_set_option() 方法吗?

$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);

此外,尝试 file_get_contents() 获取 pem 文件,确保您有权访问它,并确保主机名与证书匹配。

您下载的文件 (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)

您需要找到在签署 SSL 证书的服务器上生成的 CA 证书并将其复制到此服务器。如果您使用的是自签名证书,则需要将用于签署远程主机的 SSL 证书的 CA 证书添加到您正在连接的服务器上的受信任存储区,或者使用流上下文将该证书用于每个单独的请求。将其添加到可信证书是最简单的解决方案。只需将远程主机的 CA 证书的内容添加到您下载的 cacert.pem 文件的末尾。

上一个:

fsockopen 不支持流上下文,因此请改用 stream_socket_client。它 returns 可以与 fsockopen 资源可以使用的所有命令一起使用的资源。

这应该是您问题中片段的替代品:

<?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);

}

你提到证书是(由你)自签名的?那么你有两个选择:

  • 将证书添加到您的信任库(从 cURL 网站获取 cacert.pem 不会执行任何操作,因为它是自签名的)
  • 不必费心验证证书:您相信自己,不是吗?

这是 PHP 中的 SSL 上下文选项列表: https://secure.php.net/manual/en/context.ssl.php

如果您将证书导入信任库,请设置 allow_self_signed,或将 verify_peer 设置为 false 以跳过验证。

我们信任特定证书的原因是因为我们信任它的颁发者。由于您的证书是自签名的,因此没有客户端会信任该证书,因为签名者(您)不受信任。如果您在签署证书时创建了自己的 CA,则可以将 CA 添加到您的信任库中。如果您的证书不包含任何 CA,那么您就不能指望任何人连接到您的服务器。

就我而言,我使用的是 CentOS 7,我的 php 安装指向通过 update-ca-trust 生成的证书。符号链接 /etc/pki/tls/cert.pem 指向 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem。这只是一个测试服务器,我希望我的自签名证书能够正常工作。所以就我而言...

# 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

然后我的一些 api 调用开始工作,因为我的证书现在是可信的。此外,如果您的 ca-trust 通过 yum 或其他方式更新,这将重建您的根证书并仍然包括您的自签名证书。 运行 man update-ca-trust 了解有关做什么和如何做的更多信息。 :)

我在使用 Docker 使用 Ubuntu 16.04 时遇到了类似的问题。在我的情况下,这是 Composer 的问题,但错误消息(以及问题)是相同的。

由于面向极简主义 Docker 的基本图像,我缺少 ca-certificates 包,而简单的 apt-get install ca-certificates 帮助了我。

添加

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

之前

mail->send()

并替换

require "mailer/class.phpmailer.php";

require "mailer/PHPMailerAutoload.php";

如果您使用的是 macOS sierra,PHP 版本有更新。您需要将 Entrust.net 证书颁发机构 (2048) 文件添加到 PHP 代码中。更多信息在此处检查已接受的答案

问题出在 macOS Sierra 的新 PHP 版本中

请加

stream_context_set_option($ctx, 'ssl', 'verify_peer', false);

首先,请确保您的杀毒软件没有阻止 SSL2。
因为我很长时间都无法解决问题,只有禁用杀毒软件才能帮助我