phpopensslsmtpphpmailerstarttls

Why is PHP/OpenSSL rejecting this wildcard TLS certificate?


I'm trying to use PHPMailer to send emails via SMTP over TLS.

I've found that when PHPMailer attempts to connect to the SMTP server and STARTTLS is invoked, the connection immediately fails - unless I set the PHP SSL context variable verify_peer_name => false when opening the SMTP connection.

The SMTP server I'm connecting to is prefix.myredactedcompany.mailguard.com.au:2525. Looking at the SMTP logs, I see that when I ask to connect to prefix.myredactedcompany.mailguard.com.au:2525, I actually get connected to someotherhost.mailguard.com.au. I assume this is the result of a load balancer or other clustered setup.

When I follow the PHPMailer troubleshooting guide to test the OpenSSL connection outside of PHP by running the command echo QUIT | .\openssl.exe s_client -starttls smtp -crlf -connect prefix.myredactedcompany.mailguard.com.au:2525, I get the following results:

CONNECTED(000001E0)
---
Certificate chain
 0 s:/C=AU/postalCode=3006/ST=VIC/L=SOUTHBANK/street=198 NORMANBY RD/O=MailGuard Pty Ltd/OU=Netops/OU=PremiumSSL Wildcard/CN=*.mailguard.com.au
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
 2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIF0jCCBLqgAwIBAgIQI1raes2jQvcAbHxOBIAtqTANBgkqhkiG9w0BAQsFADCB
... snip ...
pYwh4eDZtcm4tZQfc71R1KhA9ci5A0G9ewPLmZUYoDlguNdlNlVf07aus54EV6XI
1wHfJ/xs
-----END CERTIFICATE-----
subject=/C=AU/postalCode=3006/ST=VIC/L=SOUTHBANK/street=198 NORMANBY RD/O=MailGuard Pty Ltd/OU=Netops/OU=PremiumSSL Wildcard/CN=*.mailguard.com.au
issuer=/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5395 bytes and written 468 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: redacted
    Session-ID-ctx: 
    Master-Key: redacted
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
    0000 - 18 ad e7 c2 c9 46 ab 96-5f 58 03 81 fc 48 3c 18   .....F.._X...H<.
    ... snip ...
    0090 - 41 47 9b f2 60 c4 41 5f-0d fc ea 2b 40 0c 25 3b   AG..`.A_...+@.%;

    Start Time: 1549441609
    Timeout   : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
---

The PHPMailer troubleshooting guide says that in this context

The verify error:num=20:unable to get local issuer certificate is not a problem.

This looks like a valid wildcard certificate for all subdomains of mailguard.com.au to me. Why is PHPMailer/OpenSSL rejecting it, unless I turn off peer name verification? Is it because the actual SMTP host name doesn't match the DNS name I used when establishing the outbound connection?

If I leave peer name verification turned off, what security risks does that expose me to?

Versions:


A commenter asked what the openssl connection results were like when connecting directly to one of the hosts in the pool. It's extremely similar:

CONNECTED(000001F8)
---
Certificate chain
 0 s:/C=AU/postalCode=3006/ST=VIC/L=SOUTHBANK/street=198 NORMANBY RD/O=MailGuard Pty Ltd/OU=Netops/OU=PremiumSSL Wildcard/CN=*.mailguard.com.au
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
 2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIF0jCCBLqgAwIBAgIQI1raes2jQvcAbHxOBIAtqTANBgkqhkiG9w0BAQsFADCB
...snip...
pYwh4eDZtcm4tZQfc71R1KhA9ci5A0G9ewPLmZUYoDlguNdlNlVf07aus54EV6XI
1wHfJ/xs
-----END CERTIFICATE-----
subject=/C=AU/postalCode=3006/ST=VIC/L=SOUTHBANK/street=198 NORMANBY RD/O=MailGuard Pty Ltd/OU=Netops/OU=PremiumSSL Wildcard/CN=*.mailguard.com.au
issuer=/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5399 bytes and written 468 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: redacted
    Session-ID-ctx: 
    Master-Key: redacted
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
    0000 - 6d bd 68 e7 44 29 72 ea-99 00 c8 84 a6 cc 45 76   m.h.D)r.......Ev
    ... snip ...
    0090 - 28 73 65 a4 a1 24 fd c6-18 ad fb 13 26 ec 6f b9   (se..$......&.o.

    Start Time: 1549519771
    Timeout   : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
---

Ok, so the comments in php.ini say that an empty openssl.cafile setting will cause PHP to use the OS-managed certificate store. This appears to be untrue on Windows, since OpenSSL doesn't have built-in support for the Windows certificate store. So, I followed the instructions in PHP - SSL certificate error: unable to get local issuer certificate to get a local root CA bundle set up. That seems to be working now. When I run the OpenSSL test command given above and append -CAfile C:\xampp\extras\cacert.pem, the final result is now Verify return code: 0 (ok) instead of Verify return code: 20 (unable to get local issuer certificate).

I've restarted Apache and checked the value of openssl.cafile in phpinfo() is good. But PHPMailer still won't successfully STARTTLS. Symptoms are the same as before - if I set the SSL context variable verify_peer_name to false, PHPMailer successfully connects. If I leave verify_peer_name turned on, the connection fails during STARTTLS.

I'm still interested in knowing:

  1. What are the security implications of disabling verify_peer_name? I'm assuming it would make it easy to MITM the connection.
  2. How I can further diagnose the exact cause of the STARTTLS failures, especially now that the certificate passes OpenSSL's validation?

Solution

  • It may be because wildcards only match at a single level, i.e. prefix.myredactedcompany.mailguard.com.au does not match *.mailguard.com.au. Using the reported name (seen when you connect) should help resolve that.

    Alternatively it could be down to the CA certificate bundle that PHP is using, meaning that the server is ok, but you are unable to verify its certs correctly - this is often hard to diagnose because it can be fiddly to tell where in your chain the verification is failing. For example if your chain is:

    host cert -> intermediate cert -> root (CA) cert
    

    A verification failure in any of those will result in a verification failure, but it may not be clear which one is wrong.

    You may need to fetch a copy of the latest CA certificate bundle and tell PHP to use it (as described in the PHPMailer troubleshooting guide), or use a package like Certainty to manage it from your app.

    It could also be that your intermediate certificates are in the wrong order.