symfony: [Mailer] Symfony 5.2 DkimSigner not working (DKIM lookup failure)

Hello everyone,

I’ve just upgraded my Symfony projet from 5.0 to 5.2 because I needed to DKIM sign my emails (thanks to @fabpot).

I basically followed documentation by implementing my signature on my mail like that :

$email = (new TemplatedEmail())
         ->from(new Address('noreply@domain.com', 'Noreply'))
         ->to($userEmail)
         ->subject('Subject')
         ->text('My email text format')
         ->htmlTemplate('email.html.twig')
         ->context([
            ...
         ]);

$signer = new DkimSigner(
    'file://'.dirname(__DIR__).'/../'.$this->getParameter('private_key_filename'),
    $this->getParameter('domain_name'),
    $this->getParameter('selector')
);
$signedEmail = $signer->sign($email);

$mailer->send($signedEmail);

I also tried by getting content :

$signer = new DkimSigner(
    file_get_contents(dirname(__DIR__).'/../'.$this->getParameter('private_key_filename')),
    $this->getParameter('domain_name'),
    $this->getParameter('selector')
);

I still have “body has been altered” when using multiple DKIM lookup and the test fail.

I don’t think there is a problem in charging the private key file because I first had those kind of errors that I fixed by adding the “/…/” on the path.

I’ve set the public key on my DNS with TXT record and domain selector._domainkey.domain.com like that :

v=DKIM1;k=rsa;p=mypublickey

I don’t think this is a problem of key integrity because I used openssl to generate the private and public keys :

openssl genrsa -out private.key 1024
openssl rsa -in private.key -pubout -out public.key

And I can’t sign my email by using my SMTP server because I’m having a mutualized offer and I can’t change server configurations.

Did I miss something ?

Thank you for your time and I hope someone had the same problem !

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 22 (7 by maintainers)

Commits related to this issue

Most upvoted comments

textTemplate() and htmlTemplate() need to be resolved by the BodyRenderer (hooked into a listener). And signing needs to happen after the body rendering is done.

so I think that to make the DKIM signing compatible with TemplatedEmail, the signing should happen in a listener (at lower priority than the body rendering) instead of being done before calling Mailer::send. what do you think @fabpot ?

I suggested a PR to fix the issue: https://github.com/symfony/symfony/pull/46863.

@metaer can you share the code of your temporary solution? I seem to have a similar problem with the DkimSigner

@EriCreator for this temporary solution I used swift-mailer bundle: https://symfony.com/doc/current/email.html#installation

Here is my service defenition for custom swift mailer signer plugin for default mailer:

App\SwiftMailerPlugins\SwiftMailerSignerPlugin:
    tags:
        - { name: swiftmailer.default.plugin }

where default can be replaced with your alternative mailer if you use multiple swiftmailer services:

swiftmailer.mailer.default
swiftmailer.mailer.your_second_mailer

My custom signer plugin class code example:

<?php

namespace App\SwiftMailerPlugins;

use App\Service\ParametersProvider;
use Swift_Events_SendEvent;
use Swift_Events_SendListener;
use Swift_Message;
use Swift_Signers_DKIMSigner;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Contracts\Service\Attribute\Required;

class SwiftMailerSignerPlugin implements Swift_Events_SendListener
{
    #[Required]
    public ParametersProvider $parametersProvider;

    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
        /** @var Swift_Message $swiftMessage */
        $swiftMessage = $evt->getMessage();

        if (!$swiftMessage instanceof Swift_Message) {
            return;
        }

        $privateKeyFilePath = $this->parametersProvider->getParameter('kernel.project_dir').'/dkim/dkim_private_example.pem';
        $filesystem = new Filesystem();

        if (!$filesystem->exists($privateKeyFilePath)) {
            return;
        }

        $domainName = 'example.com';
        $selector = 'selector';
        $signer = new Swift_Signers_DKIMSigner("file://$privateKeyFilePath", $domainName, $selector);
        $swiftMessage->attachSigner($signer);
    }

    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
    }
}

Sending message example:

$message = (new \Swift_Message())
    ->setFrom($from)
    ->setTo($to)
    ->setSubject($subject)
    ->setBody($body, 'text/html')
;

$result = $defaultSwiftMailer->send($message);

Usefull links: https://symfony.com/doc/current/reference/dic_tags.html#swiftmailer-default-plugin https://swiftmailer.symfony.com/docs/plugins.html https://symfony.com/doc/current/reference/configuration/swiftmailer.html

Ok, so I was able to get it working in a hackish way, unforunetly it requires a 1 line change in the symfony code.

First I set up my own custom Listener: Here are the two important functions in the listener

    public static function getSubscribedEvents()
    {
        return [
            MessageEvent::class => [['onMessage', -40]],
        ];
    }

    public function onMessage(MessageEvent $event)
    {
        $eventMessage = $event->getMessage();
        $signer       = new DkimSigner('file://'.$this->dkimKeyFile, $this->dkimDomain, $this->dkimSelector);

        $message = new Message($eventMessage->getPreparedHeaders(), $eventMessage->getBody());
        $signed  = $signer->sign($message);
        $event->setMessage($signed);

    }

Priority must be in the negative so this listener runs after template rendering. 2nd I havent investigated why but you need to create a new message out of the existing message. If you try to use the existing message it fails dkim.

Now there needs to be a change in vendor/symfomy/mailer/Transport/AbstractTransport.php As you can see in previous code we set the message on the event, however the AbstractTransport doesnt use it, it uses the one that was passed into the function. So in the send function you need to change the line from $message = new SentMessage($message, $envelope); to

$message = new SentMessage($event->getMessage(), $envelope);

This now worked and passed DKIM verification. However this solution isnt really valid since it requires a modification to the vendor code. Hopefully someone can use this to hopefully come up with a PR to fix.