Skip to the content.

在本文撰写的时候,这个问题并没有彻底解决,因此本文中所阐述的内容可能不全面。

问题描述

最开始这个问题发生在PHP进行邮件发送的时候,当使用SMTP协议时产生报错:

2021-01-11 17:39:38 Error: [Exception]task-5ffb92aacf61ce7c7d8b4567: send_heart_beat:13311539325@qq.com(stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:
error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
stream_socket_client(): Failed to enable crypto
stream_socket_client(): unable to connect to ssl://smtpdm.aliyun.com:465 (Unknown error))

通过报错内容来看,这里是证书校验时发生了错误。同时,在其他位置获取HTTPS协议的文件时,也出现了类似的报错:

Warning (2): file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages:
error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed [APP/Vendor/khanamiryan/qrcode-detector-decoder/lib/QrReader.php, line 34]
Warning (2): file_get_contents() [function.file-get-contents]: Failed to enable crypto [APP/Vendor/khanamiryan/qrcode-detector-decoder/lib/QrReader.php, line 34]
Warning (2): file_get_contents(https://devpic.mimixiche.cn/insurances/5ff6af2fcf61ce6d008b4567/pay_code20210111142906143.jpg)

服务器端处理方案

发生这个问题的原因是,在PHP5.6版本开始,OpenSSL默认将校验CA证书。比较普遍的做法是:

  1. 下载CA证书,可以在cURL官方文档里找到下载地址;
  2. 在php.ini中配置cafile的地址
    wget -O /usr/local/openssl/cacerts/cacert.pem https://curl.se/ca/cacert.pem
    vi /etc/php/5.6/php.ini
    

    找到openssl.cafile一行,修改为:

    openssl.cafile=/usr/local/openssl/certs/cacert.pem
    

问题没有完全解决

进行配置后,我们发现邮件发送的问题已经解决了,上述解决方案生效。然而,file_get_contents的问题仍在。 我们配置的证书并未对file_get_contents生效。 实际上,不论是发送邮件的插件,还是file_get_contents函数,都使用了stream_context_create方法创建的资源流。因此,问题可能出在这里。

配置资源流

file_get_contents方法的第三个参数是资源流,默认情况下,使用默认资源流。我们可以创建一个属于我们自己的资源流,来避免校验CA证书,方法如下:

$co = [
  'ssl' => [
    'verify_peer' => false,
    'verify_peer_name' => false,
  ],
];
$img = "https://devpic.mimixiche.cn/insurances/5ff6af2fcf61ce6d008b4567/pay_code20210111142906143.jpg";
$rt = file_get_contents($img, false, stream_context_create($co));

上述代码在使用file_get_contents时,传入了利用$co参数创建资源流,避免了对CA证书的校验。

设置默认资源流

虽然通过配置可以解决,但是对于历史代码较多的情况,这种方式的修改难度较大。这种情况下,可以设置好,默认的资源流参数,方法如下:

$defaultOpts = [
  'ssl' => [
    'verify_peer' => false,
    'verify_peer_name' => false,
  ]
];
stream_context_set_default($defaultOpts);

伺候再使用默认参数调用file_get_contents也不会产生报错。 对于CakePHP框架而言,可以考虑将上述默认参数配置,加到Config/bootstrap.php中。

进一步处理

既然CA证书已经配置,并在SMTP服务中已经生效,那么从原理上,服务器端的处理方案是没问题的。问题可能发生在CA证书本身,如何使用正确的CA证书进行校验,而避免在代码中规避校验,是下一步的处理方向。