Full title should be “PHP – Sending Emails: fwrite(): SSL operation failed with code 1. OpenSSL Error messages:error:1420C0CF:SSL routines:ssl_write_internal:protocol is shutdown – analyze stream_socket_enable_crypto returning false”. But this was too long for the title’s 150 characters.
We are trying to send an email in a PHP script and we’re getting
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
As seen above we’re using an adaption version of the long outdated zendframework1, but that is not the issue at hand as this works on two other hosters.
As the issue is non-deterministic we have no way to reproduce it right now.
However, we have a test script that allows some analysis, but I need help understanding what might be the problem.
Without further ado:
We’re using the script from this stackoverflow answer: How do I verify a TLS SMTP certificate is valid in PHP?
– Answer 1
The important part is this:
// 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 $myselfrn");
fread($smtp, 512);
// Switch to TLS
fwrite($smtp,"STARTTLSrn");
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;
A socket is established, set to blocking, SSL options are set and a try is made to start encryption using PHP’s stream_socket_enable_crypto.
stream_socket_enable_crypto however returns bool(false) and no information why it fails.
Currently we are suspecting that the verification of the mail server’s certificate fails, but we can’t analyse it.
How can we get more information why stream_socket_enable_crypto returns false?
edit1:
Adapted the code block above to reflect STREAM_CRYPTO_METHOD_TLS_CLIENT’s specialty since PHP 5.6.7 as mentioned by @Dilek below. Thank you for the answer!
Sadly this did not change the overall result. $secure stays bool(false).
edit2:
In response to @Dilek’s UPDATE:
$ 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
I made sure the cafile exists:
$ 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
and quick-checked its contents which seems fine:
Certificate:
Data:
Version: ...
Serial Number:
...
Signature Algorithm: ...
Issuer: ...
...
Signature Algorithm: ...
-----BEGIN CERTIFICATE-----
MI...6EULg==
-----END CERTIFICATE-----
Certificate:
Data:
Version: ...
Serial Number:
...
etc.
2
Answers
The final answer is: Use
EHLO
stattHELO
when talking SMTP.Reason:
HELO
does not support STARTTLS.The final script:
Output:
PHP bug : https://bugs.php.net/bug.php?id=69195
Commit : https://github.com/php/php-src/commit/10bc5fd4c4c8e1dd57bd911b086e9872a56300a0
STREAM_CRYPTO_METHOD_SSLv23_CLIENT
is not safe to use because before php 5.6.7, it means sslv2 or sslv3. So, you should do this:You should read bottom of the page you linked in question.
Documentation : https://www.php.net/manual/en/function.stream-socket-enable-crypto.php
UPDATE : your code should look like this
Hope this help you.