【问题标题】:fwrite(): SSL operation failed with code 1. OpenSSL Error messages:error:1420C0CF:SSL routines:ssl_write_internal:protocol is shutdownfwrite():SSL 操作失败,代码为 1。OpenSSL 错误消息:错误:1420C0CF:SSL 例程:ssl_write_internal:协议已关闭
【发布时间】:2020-05-09 11:48:54
【问题描述】:

完整标题应为“PHP - 发送电子邮件:fwrite(): SSL 操作失败,代码为 1。OpenSSL 错误消息:错误:1420C0CF:SSL 例程:ssl_write_internal:protocol is shutdown - 分析 stream_socket_enable_crypto 返回 false”。但这对于标题的 150 个字符来说太长了。

我们正在尝试在 PHP 脚本中发送电子邮件,但我们收到了

Warning: fwrite(): SSL operation failed with code 1. OpenSSL Error messages:error:1420C0CF:SSL routines:ssl_write_internal:protocol is shutdown in <...>/vendor/recolize/zendframework1/library/Zend/Mail/Protocol/Abstract.php on line 324 

如上所示,我们正在使用早已过时的 zendframework1 的改编版本,但这不是当前的问题,因为这适用于其他两个主机。

由于问题是不确定的,我们现在无法重现它。

但是,我们有一个允许进行一些分析的测试脚本,但我需要帮助了解可能是什么问题。

事不宜迟:

我们正在使用来自这个 stackoverflow 答案的脚本:How do I verify a TLS SMTP certificate is valid in PHP? - Answer 1

重要的是:

// Establish the connection
$smtp = fsockopen( "tcp://$server", 25, $errno, $errstr );
fread( $smtp, 512 );

// Here you can check the usual banner from $server (or in general,
// check whether it contains $server's domain name, or whether the
// domain it advertises has $server among its MX's.
// But yet again, Google fails both these tests.

fwrite($smtp,"HELO $myself\r\n");
fread($smtp, 512);

// Switch to TLS
fwrite($smtp,"STARTTLS\r\n");
fread($smtp, 512);
stream_set_blocking($smtp, true);
stream_context_set_option($smtp, 'ssl', 'verify_peer', true);
stream_context_set_option($smtp, 'ssl', 'allow_self_signed', true);
stream_context_set_option($smtp, 'ssl', 'capture_peer_cert', true);
stream_context_set_option($smtp, 'ssl', 'cafile', $cabundle);

// Necessary block to circumvent https://www.php.net/manual/en/function.stream-socket-enable-crypto.php#119122
$crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
    $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
    $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
}

$secure = stream_socket_enable_crypto($smtp, true, $crypto_method);

// $secure = stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT );
var_dump($secure);
die;

已建立一个套接字,设置为阻塞,设置 SSL 选项并尝试使用 PHP 的 stream_socket_enable_crypto 开始加密。

stream_socket_enable_crypto 然而返回 bool(false) 并且没有任何失败原因的信息。

目前我们怀疑邮件服务器的证书验证失败,但无法分析。

我们如何获得更多关于为什么 stream_socket_enable_crypto 返回 false 的信息?

edit1:

修改了上面的代码块以反映STREAM_CRYPTO_METHOD_TLS_CLIENT's specialty since PHP 5.6.7,如下面的@Dilek 所述。谢谢你的回答! 遗憾的是,这并没有改变整体结果。 $secure 保持 bool(false)。

edit2:

回应@Dilek 的更新:

$ php ssl_test.php
Array
(
    [ssl] => Array
        (
            [verify_host] => 1
            [verify_peer] => 1
            [allow_self_signed] =>
            [cafile] => /etc/ssl/cert.pem
        )

)
failed to connect securely

我确定cafile存在:

$ ll /etc/ssl/cert.pem
0 lrwxrwxrwx  1 root  wheel  38 Dec 19 11:09 /etc/ssl/cert.pem -> /usr/local/share/certs/ca-root.crt

并快速检查了它看起来不错的内容:

Certificate:
    Data:
        Version: ...
        Serial Number:
            ...
        Signature Algorithm: ...
        Issuer: ...
        ...
    Signature Algorithm: ...
-----BEGIN CERTIFICATE-----
MI...6EULg==
-----END CERTIFICATE-----

Certificate:
    Data:
        Version: ...
        Serial Number:
            ...

etc.

【问题讨论】:

  • 只是出于好奇:以简单的方式发送邮件已经非常困难(这就是为什么有多个库可以让您免于麻烦)。现在,您首先通过简单的 SMTP 增加了另一层困难 - 这是什么原因?
  • 我正试图找出问题的根源,或者至少找到一些关于发生错误的提示。我在上面提供的 stackoverflow 链接中找到了该脚本,目前最好的办法是获取其他信息。因此,由于缺乏替代想法,我真的只是想谈论普通的 SMTP。产生上述错误的原始电子邮件问题(警告:fwrite()...)来自框架(zendframework1),但我们无法确定地重现它。当前的脚本确实是我们现在唯一的想法。我很乐意尝试任何替代方法来解决这个问题。
  • 要提供替代想法,需要了解该脚本的需求。你想达到什么目标?使用 PHPMailer、Swiftmailer 甚至使用普通的 mail 函数,发送邮件更加容易。
  • 我将使用 PHPMailer 生成一个脚本并更新我的答案。
  • 我们终于找到了答案。我贴出来了。

标签: php email ssl


【解决方案1】:

最终的答案是:在谈 SMTP 时使用EHLO statt HELO

原因:HELO 不支持 STARTTLS

最终脚本:

$smtp = fsockopen( "tcp://mail.noris.net", 25, $errno, $errstr );
fread( $smtp, 512 );
fwrite($smtp,"EHLO test.de\r\n");
fread($smtp, 512);
fwrite($smtp,"STARTTLS\r\n");
fread($smtp, 512);
stream_set_blocking($smtp, true);
stream_context_set_option($smtp, 'ssl', 'verify_host', false);
stream_context_set_option($smtp, 'ssl', 'verify_peer', false);
stream_context_set_option($smtp, 'ssl', 'allow_self_signed', false);

//Self signed certificates are blocked by googl and most servers

stream_context_set_option($smtp, 'ssl', 'cafile', '/etc/ssl/cert.pem');
//you need to add correct and full path of CA file

//This is second solution in php documents in the link in question and in my answer
$secure = stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
stream_set_blocking($smtp, false);
print_r(stream_context_get_options($smtp));
if( ! $secure) {
    print("failed to connect securely\n");
}
else {
    print "Success!\n";
}

var_dump($errno);
var_dump($errstr);
die;

输出:

$ php ssl_test.php
Array
(
    [ssl] => Array
        (
            [verify_host] =>
            [verify_peer] =>
            [allow_self_signed] =>
            [cafile] => /etc/ssl/cert.pem
        )

)
Success!
int(0)
string(0) ""

【讨论】:

    【解决方案2】:

    PHP 错误:https://bugs.php.net/bug.php?id=69195

    提交:https://github.com/php/php-src/commit/10bc5fd4c4c8e1dd57bd911b086e9872a56300a0

    STREAM_CRYPTO_METHOD_SSLv23_CLIENT 使用不安全,因为在 php 5.6.7 之前,它表示 sslv2 或 sslv3。所以,你应该这样做:

    $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
    
    if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
        $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
        $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
    }
    
    stream_socket_enable_crypto($socket, true, $crypto_method);
    

    您应该阅读相关链接页面的底部。

    文档:https://www.php.net/manual/en/function.stream-socket-enable-crypto.php

    更新:您的代码应如下所示

                $smtp = fsockopen( "tcp://mail.domain.com", 25, $errno, $errstr );
                fread( $smtp, 512 );
                fwrite($smtp,"HELO worp\r\n");
                fread($smtp, 512);
                fwrite($smtp,"STARTTLS\r\n");
                fread($smtp, 512);
                stream_set_blocking($smtp, true);
                stream_context_set_option($smtp, 'ssl', 'verify_host', true);
                stream_context_set_option($smtp, 'ssl', 'verify_peer', true);
                stream_context_set_option($smtp, 'ssl', 'allow_self_signed', false);
    
    //Self signed certificates are blocked by googl and most servers
    
                stream_context_set_option($smtp, 'ssl', 'cafile', '/etc/ssl/cacert.pem');
        //you need to add correct and full path of CA file 
    
        //This is second solution in php documents in the link in question and in my answer
                $secure = stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
                stream_set_blocking($smtp, false);
                print_r(stream_context_get_options($smtp));
                if( ! $secure)
                        die("failed to connect securely\n");
                print "Success!\n";
    

    希望对您有所帮助。

    【讨论】:

    • 我已经编辑了上面的代码,很好的提及。我已经阅读并手动尝试了各种常量,但我实际上并没有使用整个块。我现在确实尝试使用您的 php.net 链接中的完整块,但 stream_socket_enable_crypto 继续返回 false。
    • stream_socket_enable_crypto 继续返回 false 经过多次调查,我发现我本地机器上的 PHP 没有正确设置来验证受信任的 CA。请看这里:stackoverflow.com/questions/53437771/…我会给出这个答案,但有些人会尊重我的答案:)
    • 我会假设您是正确的并且答案是正确的,但我不知道它以什么方式设置错误。我曾希望 stream_socket_enable_crypto 能给我一个提示,为什么它无法建立加密以获取错误配置可能是什么的指针。我们已经建立了证书链,我们已经允许自签名(参见上面的代码),我尝试过使用和不使用 verify_peer。但是没有任何信息会产生证书的实际问题或一般建立 SSL 的信息。这就是我正在寻找的提示。
    • @Worp 请您试试这个,而不是我的答案中的代码:stream_set_blocking ($smtp, true); stream_socket_enable_crypto ($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);stream_set_blocking ($smtp, false); 并确保您的 CA 路径是否正确在此处查看详细信息:curl.haxx.se/docs/caextract.html请查看此答案以获取完整答案 stackoverflow.com/a/13445297/12232340 告诉我。
    • 用你的 UPDATE 更新了我的答案(参见上面的“edit2”)。我还对cafile的位置和内容提供了一些保证。但是此时我想知道: cafile 的内容可能不正确吗? (这是由主机提供的)
    猜你喜欢
    • 2019-02-13
    • 1970-01-01
    • 1970-01-01
    • 2015-11-27
    • 2019-03-02
    • 1970-01-01
    • 2022-06-12
    • 2020-05-12
    • 2022-01-11
    相关资源
    最近更新 更多