SMTP, STARTTLS and mail encryption in Laravel 10

In October CMS and Laravel, you can use the MAIL_ENCRYPTION environment variable to set the type of encryption for your email connections to use. This configuration is used to set the encryption option on the SMTP mail transport type.

You can also set the encryption and other configuration using the Mail Configuration screen in the Settings area using the SMTP encryption protocol field (MAIL_ENCRYPTION), which can be set to either No encryption (null) or TLS (tls). You may also set the port number using the SMTP port field (MAIL_PORT) as a number. Commonly this is set to port 465 or port 587, otherwise it can be left empty.

Research

In previous versions, October CMS v1 and v2, this encryption option is given verbatim to the Swift Mailer library used for SMTP emails, with $transport as a Swift_SmtpTransport instance. This works as expected and also included an ssl option for SSL encryption, which is now removed.

if (! empty($config['encryption'])) {
    $transport->setEncryption($config['encryption']);
}

Since Swift Mailer is no longer maintained, Laravel 9 and October CMS v3, switched to use the Symfony Mailer instead. As a result the use of the encryption setting behaves differently. See the code below for how the value is handled.

$transport = $factory->create(new Dsn(
    ! empty($config['encryption']) && $config['encryption'] === 'tls'
        ? (($config['port'] == 465) ? 'smtps' : 'smtp')
        : '',
    // ...
));

With $factory as an EsmtpTransportFactory instance, the encryption setting is no longer passed verbatim and instead used to set the scheme to one of three possible values. The logic makes a decision based on the port number and the encryption value. We should look at how the factory uses this scheme.

$tls = 'smtps' === $dsn->getScheme() ? true : null;
// ...
$transport = new EsmtpTransport(
    $host,
    $port,
    $tls,
    $this->dispatcher,
    $this->logger
);

With this information we can determine that the $tls variable is true when the port setting is 465 and the encrpytion is est to tls; otherwise $tls variable is set to null. However, before this value is used, it is modified further inside the EsmtpTransport constructor.

if (null === $tls) {
    if (465 === $port) {
        $tls = true;
    } else {
        $tls = \defined('OPENSSL_VERSION_NUMBER') &&
            0 === $port &&
            'localhost' !== $host;
    }
}

Now we see if the port setting is 465 but this $tls variable is set to null, it will be set to true anyway regardless of the encryption setting. Let’s look at how this $tls is used.

if (!$tls) {
    $stream->disableTls();
}

When $tls is true, implicit TLS remains enabled, and when $tls is null, implicit TLS is disabled instead. There is one final detail to cover: STARTTLS.

if (
    !$stream->isTLS() &&
    \defined('OPENSSL_VERSION_NUMBER') &&
    \array_key_exists('STARTTLS', $this->capabilities)
) {
    $this->executeCommand("STARTTLS\r\n", [220]);
    // ...
}

According to this code, when implicit TLS is not enabled and the server advertises STARTTLS, Symfony Mailer always tries to enable it. There is no possibility to enforce or disable this.

Conclusion

In summary, the port is used to determine if STARTTLS is used. When configuring your mail server to use SMTP:

  • When the MAIL_PORT environment variable is set to 465 then Symfony Mailer will end up using implicit TLS. Otherwise, Symfony Mailer will try to use STARTTLS if the server supports it.

  • When your MAIL_PORT is undefined or set to 0, Laravel will guess the port to use. When MAIL_HOST is set to localhost with port 25 STARTTLS is used, otherwise port 465 with implicit TLS is used.

There is no way for a developer to change this behaviour. The MAIL_ENCRYPTION environment variable ends up not affecting this process. This leads to the following problems.

  • You cannot enable implicit TLS for any non-standard port, nor can you disable it on the standard port.

  • You cannot disable STARTTLS even when it is optional. This prevents you from connecting to your mailserver through localhost.

  • You cannot enforce STARTTLS when you know a server supports it. This makes Symfony Mailer vulnerable to even trivial downgrade attacks.


Source: Wilco de Boer · Why you can't configure email encryption in Laravel 9

1 Like