Certificate in Envelope

So you'd like to configure Joomla CMS to use a SMTP relay to send email. Unfortunately, presenting a client certificate is not a standard option and Joomla only allows authentication via SMTP Auth. You could allow access by your public IP but many have dynamic IPs and where is the fun in that? I did some research, but I could not find any good documentation on how to do it. After some trial and error, I was successful by editing the built-in PHP Mailer plugin. It was a fairly simple process and I will share the steps I took in this guide. You will need to have full root access to the Joomla host server.


Combine your Certificate

First things first, you need to have a trusted client certificate added to your postfix relay_clientcerts. Postfix also needs to trust the root and intermediate CA that signed your certificate. See more about this here. If you are not using Postfix, you will need to configure the equivalent on whatever software is running your SMTP relay.

  • Navigate to the directory that your private .key file and certificate file are located.
$ cd /etc/ssl/certs/tictactech.net/
  • From here, you need to combine the .key file along with the .cer into a .pem 
$ cat '*.tictactech.net.key' fullchain.cer > tictactech.net.pem
  • Verify the contents of the file look correct
$ cat tictactech.net.pem

It should look similar to the image below. Make sure the Private Key portion is first

cert cat

  • Copy the new .pem file to a location that your Joomla instance has read access to.
$ cp -r tictactech.net_ecc /volume2/docker/joomla/certs

 

  • Change the owner of the .pem file to the webserver user. In my case it is http
$ sudo chown http /volume2/docker/joomla/certs/tictactech.net.pem
  • Great! Your certificate should be ready!

Edit Joomla Mail PHP plugin

So here I had to do some digging around. I found that Joomla uses a class called jmailer but it's really an extension of the PHPMailer application. The github for which can be found here. The relevant Joomla files are located in webroot/libraries/src/Mail

  1. Navigate to the webroot/libraries/src/Mail folder

  2. Make a copy of the Mail.php file and name it Mail.php.bak . This will be helpful if you need to restore it for any reason.

  3. Open the Mail.php file in your favorite text editor

Around line 550 starts the /**Use SMTP for sending the email**/ comments. This is where you will need to start editing. I've outlined the needed changes below

/**
     * Use SMTP for sending the email
     *
     * @param   string   $auth    SMTP Authentication [optional]
     * @param   string   $host    SMTP Host [optional]
     * @param   string   $user    SMTP Username [optional]
     * @param   string   $pass    SMTP Password [optional]
     * @param   string   $secure  Use secure methods
     * @param   integer  $port    The SMTP port
     *
     * @return  boolean  True on success
     *
     * @since   1.7.0
     */
    public function useSmtp($auth = null, $host = null, $user = null, $pass = null, $secure = null, $port = 25)
    {
        $this->SMTPAuth = $auth;
        $this->Host     = $host;
        $this->Username = $user;
        $this->Password = $pass;
        $this->Port     = $port;
		
        if ($secure === 'ssl' || $secure === 'tls') {
            $this->SMTPSecure = $secure;
        }

        if (
            ($this->SMTPAuth !== null && $this->Host !== null && $this->Username !== null && $this->Password !== null)
            || ($this->SMTPAuth === null && $this->Host !== null)
        ) {
            $this->isSMTP();

            return true;
        }

        $this->isMail();

        return false;
    }
    • Line 15 - Add $SMTPOptions = null to the end of the useSmtp() public function

    • Line 24 - Add the following after $this->SMTPSecure = $secure; within the IF statement

$this->SMTPOptions = array(
			'ssl' => [
				'verify_peer' => true,
				'local_cert' => '/volume2/docker/joomla/certs/tictactech.net.pem',
				],
				);
    • The final result should look like below...

public function useSmtp($auth = null, $host = null, $user = null, $pass = null, $secure = null, $port = 25, $SMTPOptions = null)
    {
        $this->SMTPAuth = $auth;
        $this->Host     = $host;
        $this->Username = $user;
        $this->Password = $pass;
        $this->Port     = $port;
			
        if ($secure === 'ssl' || $secure === 'tls') {
            $this->SMTPSecure = $secure;
            $this->SMTPOptions = array(
			'ssl' => [
				'verify_peer' => true,
				'local_cert' => '/volume2/docker/joomla/certs/tictactech.net.pem',
				],
				);
        }

        if (
            ($this->SMTPAuth !== null && $this->Host !== null && $this->Username !== null && $this->Password !== null)
            || ($this->SMTPAuth === null && $this->Host !== null)
        ) {
            $this->isSMTP();

            return true;
        }

        $this->isMail();

        return false;
    }
    • Save the file and close it.

That's it! Your Joomla instance should now present your client certificate when sending email.


Testing and troubleshooting

  1. Assuming you added the certificate's fingerprint to your Postfix relay_clientcerts,  open up your Joomla admin portal and head to System > Global Configuration

  2. Click the server tab at the top and scroll down to the email section.

  3. Fill in the relevant settings including your relay's fully qualified domain name. You can use port 587 (Assuming you configured postfix to listen on that port) or port 25.

  4. Make sure to set the security to Starttls . SMTP Auth is not needed because we are presenting a client certificate and that's how the relay is authenticating us. Send the email and hopefully its successful!

If you get an error similar to...
"SMTP Error: Could not connect to SMTP host. Connection failed. stream_socket_enable_crypto(): Unable to set local cert chain file `/volume2/docker/joomla/certs/tictactech.net.pem'; Check that your cafile/capath settings include details of your certificate and its issuer" 
It means the PHP Mailer cannot access the cert file. This is usually a permission issue so make sure you did not overlook changing permissions on the cert.

Another way to troubleshoot is turn on TLS logging in postfix and tail the log. Add the option for tls logging and reload the postfix config...

$ sudo postconf -e 'smtpd_tls_loglevel = 2'
$ sudo postfix reload

Now tail the log and try sending another test email

$ tail -f /var/log/mail.log
Nov 11 14:28:24 relay postfix/postscreen[25292]: CONNECT from [XXXXXXXX]:42304 to [XXXXXX]:25
Nov 11 14:28:24 relay postfix/postscreen[25292]: ALLOWLISTED [XXXXXXXX]:42304
Nov 11 14:28:24 relay postfix/smtpd[25296]: initializing the server-side TLS engine
Nov 11 14:28:24 relay postfix/smtpd[25296]: connect from cpe-XXXXXXXX.nycap.res.rr.com[XXXXXX]
Nov 11 14:28:24 relay postfix/smtpd[25296]: setting up TLS connection from cpe-XXXXXXXX.nycap.res.rr.com[XXXXXXX]
Nov 11 14:28:24 relay postfix/smtpd[25296]: cpe-XXXXXX.nycap.res.rr.com[XXXXXX]: TLS cipher list "aNULL:-aNULL:HIGH:MEDIUM:+RC4:@STRENGTH:!aNULL"
Nov 11 14:28:24 relay postfix/smtpd[25296]: SSL_accept:before SSL initialization
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:before SSL initialization
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS read client hello
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS write server hello
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS write change cipher spec
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:TLSv1.3 write encrypted extensions
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS write certificate request
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS write certificate
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:TLSv1.3 write server certificate verify
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS write finished
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:TLSv1.3 early data
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:TLSv1.3 early data
Nov 11 14:28:25 relay postfix/smtpd[25296]: cpe-xxxxxxxxxxxxxx.nycap.res.rr.com[XXXXXXX]: depth=0 verify=0 subject=/CN=*.tictactech.net
Nov 11 14:28:25 relay postfix/smtpd[25296]: cpe-xxxxxxxxxxxxxx.nycap.res.rr.com[XXXXXXXXX]: depth=0 verify=0 subject=/CN=*.tictactech.net
Nov 11 14:28:25 relay postfix/smtpd[25296]: cpe-xxxxxxxxxxxxxx.nycap.res.rr.com[XXXXXXXXX]: depth=0 verify=1 subject=/CN=*.tictactech.net
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS read client certificate
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS read certificate verify
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS read finished
Nov 11 14:28:25 relay postfix/smtpd[25296]: cpe-xxxxxxxxxxxxxx.nycap.res.rr.com[XXXXXXX]: Issuing session ticket, key expiration: 1699736167
Nov 11 14:28:25 relay postfix/smtpd[25296]: SSL_accept:SSLv3/TLS write session ticket
Nov 11 14:28:25 relay postfix/smtpd[25296]: subject=/CN=*.tictactech.net
Nov 11 14:28:25 relay postfix/smtpd[25296]: issuer=/C=US/O=Let's Encrypt/CN=R3
Nov 11 14:28:25 relay postfix/smtpd[25296]: cpe-XXXXXXXXXX.nycap.res.rr.com[XXXXXXX]: subject_CN=*.tictactech.net, issuer=R3, fingerprint=13:0B:CD:62:6C:8C:CC:32:BF:F6:45:2B:E9:0B:84:81:EF:8B:E7:4A:9F:53:EC:96:5C:84:B4:23:A0:C4:EE:6D, pkey_fingerprint=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Nov 11 14:28:25 relay postfix/smtpd[25296]: certificate verification failed for cpe-XXXXXXX.nycap.res.rr.com[XXXXXXXX]: untrusted issuer /C=US/O=Let's Encrypt/CN=R3
Nov 11 14:28:25 relay postfix/smtpd[25296]: Untrusted TLS connection established from cpe-XXXXXXXXXX.nycap.res.rr.com[XXXXXXX]: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256 client-signature ECDSA (prime256v1) client-digest SHA256
Nov 11 14:28:25 relay postfix/smtpd[25296]: NOQUEUE: abort: TLS from cpe-XXXXXXXXX.nycap.res.rr.com[XXXXXXXXX]: Client certificate not trusted

Here the problem was the certificate I was using was not trusted. This issue can occur if you are using a self signed certificate or if your postfix cert store does not trust the root or intermediate CA that signed your cert. You can manually add the root and/or CA cert to your postfix config by adding it to the cert store in /etc/ssl/certs . This store is governed by the smtpd_tls_CApath parameter in main.cf

Note: "The client certificate not trusted" error has nothing to do with the relay_clientcerts file where you enter the fingerprint of the cert. That simply governs if you are allowed to use the relay.


Conclusion

By default, Joomla does not allow for presenting client certificates to a remote SMTP server. It requires editing the PHPMailer plugin in order to accomplish this. Fortunately, its a simple edit and it seems to work great once configured. Don't forget to add your relay's IP to your sending domain's SPF record. It would also be a good idea to configure opendkim on your relay to improve email deliverability. Stay tuned for an addition to this guide on how to do that!

If you update Joomla, these changes will likely get overwritten and you will need to re-add them.

No comments