skip to Main Content

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


  1. Chosen as BEST ANSWER

    The final answer is: Use EHLO statt HELO when talking SMTP.

    Reason: HELO does not support STARTTLS.

    The final script:

    $smtp = fsockopen( "tcp://mail.noris.net", 25, $errno, $errstr );
    fread( $smtp, 512 );
    fwrite($smtp,"EHLO test.dern");
    fread($smtp, 512);
    fwrite($smtp,"STARTTLSrn");
    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 securelyn");
    }
    else {
        print "Success!n";
    }
    
    var_dump($errno);
    var_dump($errstr);
    die;
    

    Output:

    $ 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 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:

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

    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

                $smtp = fsockopen( "tcp://mail.domain.com", 25, $errno, $errstr );
                fread( $smtp, 512 );
                fwrite($smtp,"HELO worprn");
                fread($smtp, 512);
                fwrite($smtp,"STARTTLSrn");
                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 securelyn");
                print "Success!n";
    

    Hope this help you.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search