Search code examples
phpsslsmtpphpmailerphp-openssl

Domain Verification in PHP 5.6 issue with PHPMailer for sending mails via TLS


I am trying to send emails using PHPMailer with Let's Encrypt certificates but after shifting to PHP 5.6 it doesn't work due to failed Domain verification.

Connection failed. Error #2: stream_socket_enable_crypto(): SSL operation >failed with code 1. OpenSSL Error messages:error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed

The obvious workaround for this is to disable domain verification, but I am not willing to do the same as i want to know how this works

The Output of the following

php -r "var_dump(openssl_get_cert_locations());"

array(8) {
  ["default_cert_file"]=>
  string(36) "/usr/local/apps/etc/openssl/cert.pem"
  ["default_cert_file_env"]=>
  string(13) "SSL_CERT_FILE"
  ["default_cert_dir"]=>
  string(33) "/usr/local/apps/etc/openssl/certs"
  ["default_cert_dir_env"]=>
  string(12) "SSL_CERT_DIR"
  ["default_private_dir"]=>
  string(35) "/usr/local/apps/etc/openssl/private"
  ["default_default_cert_area"]=>
  string(27) "/usr/local/apps/etc/openssl"
  ["ini_cafile"]=>
  string(0) ""
  ["ini_capath"]=>
  string(0) ""
}

I have the cert.pem in the above mentioned location, but my certs and private keys are present in /etc/ssl, i even tried to create a Sysmlink to those folder but still the verification fails.

So can anyone explain how the verification works as i am unable to figure out same even after days of googling the problem as most of the solution point to disabling domain verification.

I am i missing a something, as I have all the proper files related to the certificate and it's valid certificate as i am using it on my website.

Here is the code for PHPMailer

require 'PHPMailer-master/PHPMailerAutoload.php';
$mail = new PHPMailer;
$mail->isSMTP();
$mail->SMTPDebug = 5;
$mail->Debugoutput = 'html';
$mail->Host = 'mydomin.com';
$mail->Port = 587;
$mail->SMTPSecure = 'tls';
$mail->SMTPAuth = true;
$mail->Username = "[email protected]";
$mail->Password = "test";
$mail->setFrom('[email protected]', 'Hii');
$mail->addAddress('[email protected]', 'John Doe');
$mail->Subject = 'PHPMailer GMail SMTP test';
$mail->msgHTML("<h2>Hello World</h2>");
$mail->AltBody = 'This is a plain-text message body';

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

if (!$mail->send()) {
    echo "Mailer Error: " . $mail->ErrorInfo;
} else {
    echo "Message sent!";
}

Solution

  • Your server is not providing the letsencrypt X3 intermediate certificate in its response, only the leaf cert. This isn't enough because most CA stores don't contain the letsencrypt CA certs, only the root certs they are signed with, so you need the intermediate to bridge the two. Get the intermediate certificate from here and append it to your certificate file.

    Here's how you can see it working from the client end:

    $ wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt
    $ openssl s_client -CAfile lets-encrypt-x3-cross-signed.pem.txt -connect example.com:465
    depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
    verify return:1
    depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
    verify return:1
    depth=0 CN = example.com
    verify return:1
    ---
    Certificate chain
     0 s:/CN=example.com
       i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
    ...
    Verify return code: 0 (ok)
    

    If you bundle that cert at the server end, it should work in all up to date clients without a local intermediate cert.