Skip to content

Commit c7f41d7

Browse files
elias-playfindereliasfernandez
authored andcommitted
[FrameworkBundle][Mailer] Add the process to sign the message on a listener (Fix 53941)
1 parent 6f4f04b commit c7f41d7

File tree

14 files changed

+567
-0
lines changed

14 files changed

+567
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

+75
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,81 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
22362236
->end()
22372237
->end()
22382238
->end()
2239+
->arrayNode('dkim_signer')
2240+
->addDefaultsIfNotSet()
2241+
->fixXmlConfig('option')
2242+
->canBeEnabled()
2243+
->info('DKIM signer configuration')
2244+
->children()
2245+
->scalarNode('key')
2246+
->info('Key content, or path to key (in PEM format with the `file://` prefix)')
2247+
->defaultValue('')
2248+
->cannotBeEmpty()
2249+
->end()
2250+
->scalarNode('domain')->defaultValue('')->end()
2251+
->scalarNode('select')->defaultValue('')->end()
2252+
->scalarNode('passphrase')
2253+
->info('A passphrase of the private key if any')
2254+
->defaultValue('')
2255+
->end()
2256+
->arrayNode('options')
2257+
->performNoDeepMerging()
2258+
->normalizeKeys(false)
2259+
->useAttributeAsKey('name')
2260+
->prototype('variable')->end()
2261+
->end()
2262+
->end()
2263+
->end()
2264+
->arrayNode('smime_signer')
2265+
->addDefaultsIfNotSet()
2266+
->canBeEnabled()
2267+
->info('SMIME signer configuration')
2268+
->children()
2269+
->scalarNode('key')
2270+
->info('Path to key (in PEM format)')
2271+
->defaultValue('')
2272+
->cannotBeEmpty()
2273+
->end()
2274+
->scalarNode('certificate')
2275+
->info('Path to certificate (in PEM format without the `file://` prefix)')
2276+
->defaultValue('')
2277+
->cannotBeEmpty()
2278+
->end()
2279+
->scalarNode('passphrase')
2280+
->info('A passphrase of the private key if any')
2281+
->defaultNull()
2282+
->end()
2283+
->scalarNode('extra_certificates')->defaultNull()->end()
2284+
->integerNode('sign_options')->defaultNull()->end()
2285+
->end()
2286+
->end()
2287+
->arrayNode('smime_encrypter')
2288+
->addDefaultsIfNotSet()
2289+
->canBeEnabled()
2290+
->info('SMIME encrypter configuration')
2291+
->children()
2292+
->scalarNode('certificate')
2293+
->info('Path to certificate (in PEM format without the `file://` prefix)')
2294+
->defaultValue('')
2295+
->cannotBeEmpty()
2296+
->end()
2297+
->integerNode('cipher')
2298+
->info('A set of algorithms used to encrypt the message')
2299+
->defaultNull()
2300+
->beforeNormalization()
2301+
->always(function (?string $v): ?int {
2302+
return null !== $v && defined('OPENSSL_CIPHER_' . $v) ? constant('OPENSSL_CIPHER_' . $v) : null;
2303+
})
2304+
->end()
2305+
->validate()
2306+
->ifTrue(function (?string $v) {
2307+
return \extension_loaded('openssl') && null !== $v && !defined('OPENSSL_CIPHER_' . $v);
2308+
})
2309+
->thenInvalid('You must provide a valid cipher.')
2310+
->end()
2311+
->end()
2312+
->end()
2313+
->end()
22392314
->end()
22402315
->end()
22412316
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+45
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@
112112
use Symfony\Component\Lock\Store\StoreFactory;
113113
use Symfony\Component\Mailer\Bridge as MailerBridge;
114114
use Symfony\Component\Mailer\Command\MailerTestCommand;
115+
use Symfony\Component\Mailer\EventListener\DkimSignedMessageListener;
115116
use Symfony\Component\Mailer\EventListener\MessengerTransportListener;
117+
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
118+
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
116119
use Symfony\Component\Mailer\Mailer;
117120
use Symfony\Component\Mercure\HubRegistry;
118121
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
@@ -2837,6 +2840,48 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
28372840
$container->removeDefinition('mailer.messenger_transport_listener');
28382841
}
28392842

2843+
if ($config['dkim_signer']['enabled']) {
2844+
if (!class_exists(DkimSignedMessageListener::class)) {
2845+
throw new LogicException('DKIM signed messages support cannot be enabled as the component is not up to date. Update the symfony/mailer.');
2846+
}
2847+
$dkimSigner = $container->getDefinition('mailer.dkim_signer');
2848+
$dkimSigner->setArgument(0, $config['dkim_signer']['key']);
2849+
$dkimSigner->setArgument(1, $config['dkim_signer']['domain']);
2850+
$dkimSigner->setArgument(2, $config['dkim_signer']['select']);
2851+
$dkimSigner->setArgument(3, $config['dkim_signer']['options']);
2852+
$dkimSigner->setArgument(4, $config['dkim_signer']['passphrase']);
2853+
} else {
2854+
$container->removeDefinition('mailer.dkim_signer');
2855+
$container->removeDefinition('mailer.dkim_signer.listener');
2856+
}
2857+
2858+
if ($config['smime_signer']['enabled']) {
2859+
if (!class_exists(SmimeSignedMessageListener::class)) {
2860+
throw new LogicException('SMIME signed messages support cannot be enabled as the component is not up to date. Update the symfony/mailer.');
2861+
}
2862+
$smimeSigner = $container->getDefinition('mailer.smime_signer');
2863+
$smimeSigner->setArgument(0, $config['smime_signer']['key']);
2864+
$smimeSigner->setArgument(1, $config['smime_signer']['certificate']);
2865+
$smimeSigner->setArgument(2, $config['smime_signer']['passphrase']);
2866+
$smimeSigner->setArgument(3, $config['smime_signer']['extra_certificates']);
2867+
$smimeSigner->setArgument(4, $config['smime_signer']['sign_options']);
2868+
} else {
2869+
$container->removeDefinition('mailer.smime_signer');
2870+
$container->removeDefinition('mailer.smime_signer.listener');
2871+
}
2872+
2873+
if ($config['smime_encrypter']['enabled']) {
2874+
if (!class_exists(SmimeEncryptedMessageListener::class)) {
2875+
throw new LogicException('SMIME encrypted messages support cannot be enabled as the component is not up to date. Update the symfony/mailer.');
2876+
}
2877+
$smimeDecrypter = $container->getDefinition('mailer.smime_encrypter');
2878+
$smimeDecrypter->setArgument(0, $config['smime_encrypter']['certificate']);
2879+
$smimeDecrypter->setArgument(1, $config['smime_encrypter']['cipher']);
2880+
} else {
2881+
$container->removeDefinition('mailer.smime_encrypter');
2882+
$container->removeDefinition('mailer.smime_encrypter.listener');
2883+
}
2884+
28402885
if ($webhookEnabled) {
28412886
$loader->load('mailer_webhook.php');
28422887
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php

+48
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\Component\Mailer\Command\MailerTestCommand;
15+
use Symfony\Component\Mailer\EventListener\DkimSignedMessageListener;
1516
use Symfony\Component\Mailer\EventListener\EnvelopeListener;
1617
use Symfony\Component\Mailer\EventListener\MessageListener;
1718
use Symfony\Component\Mailer\EventListener\MessageLoggerListener;
1819
use Symfony\Component\Mailer\EventListener\MessengerTransportListener;
20+
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
21+
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
1922
use Symfony\Component\Mailer\Mailer;
2023
use Symfony\Component\Mailer\MailerInterface;
2124
use Symfony\Component\Mailer\Messenger\MessageHandler;
2225
use Symfony\Component\Mailer\Transport;
2326
use Symfony\Component\Mailer\Transport\TransportInterface;
2427
use Symfony\Component\Mailer\Transport\Transports;
28+
use Symfony\Component\Mime\Crypto\DkimSigner;
29+
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
30+
use Symfony\Component\Mime\Crypto\SMimeSigner;
2531

2632
return static function (ContainerConfigurator $container) {
2733
$container->services()
@@ -78,6 +84,48 @@
7884
->set('mailer.messenger_transport_listener', MessengerTransportListener::class)
7985
->tag('kernel.event_subscriber')
8086

87+
->set('mailer.dkim_signer', DkimSigner::class)
88+
->args([
89+
abstract_arg('key'),
90+
abstract_arg('domain'),
91+
abstract_arg('select'),
92+
abstract_arg('options'),
93+
abstract_arg('passphrase'),
94+
])
95+
96+
->set('mailer.smime_signer', SMimeSigner::class)
97+
->args([
98+
abstract_arg('key'),
99+
abstract_arg('certificate'),
100+
abstract_arg('passphrase'),
101+
abstract_arg('extraCertificates'),
102+
abstract_arg('signOptions'),
103+
])
104+
105+
->set('mailer.smime_encrypter', SMimeEncrypter::class)
106+
->args([
107+
abstract_arg('certificate'),
108+
abstract_arg('cipher'),
109+
])
110+
111+
->set('mailer.dkim_signer.listener', DkimSignedMessageListener::class)
112+
->args([
113+
service(DkimSigner::class),
114+
])
115+
->tag('kernel.event_subscriber')
116+
117+
->set('mailer.smime_signer.listener', SmimeSignedMessageListener::class)
118+
->args([
119+
service('mailer.smime_signer'),
120+
])
121+
->tag('kernel.event_subscriber')
122+
123+
->set('mailer.smime_encrypter.listener', SmimeEncryptedMessageListener::class)
124+
->args([
125+
service('mailer.smime_encrypter'),
126+
])
127+
->tag('kernel.event_subscriber')
128+
81129
->set('console.command.mailer_test', MailerTestCommand::class)
82130
->args([
83131
service('mailer.transports'),

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

+27
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,9 @@
789789
<xsd:element name="transport" type="mailer_transport" minOccurs="0" maxOccurs="unbounded" />
790790
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
791791
<xsd:element name="header" type="header" minOccurs="0" maxOccurs="unbounded" />
792+
<xsd:element name="dkim-signer" type="mailer_dkim_signer" minOccurs="0" />
793+
<xsd:element name="smime-signer" type="mailer_smime_signer" minOccurs="0" />
794+
<xsd:element name="smime-encrypter" type="mailer_smime_encrypter" minOccurs="0" />
792795
</xsd:sequence>
793796
<xsd:attribute name="enabled" type="xsd:boolean" />
794797
<xsd:attribute name="dsn" type="xsd:string" />
@@ -811,6 +814,30 @@
811814
</xsd:sequence>
812815
</xsd:complexType>
813816

817+
818+
<xsd:complexType name="mailer_dkim_signer">
819+
<xsd:sequence>
820+
<xsd:element name="option" type="metadata" minOccurs="0" maxOccurs="unbounded" />
821+
</xsd:sequence>
822+
<xsd:attribute name="key" type="xsd:string"/>
823+
<xsd:attribute name="domain" type="xsd:string"/>
824+
<xsd:attribute name="select" type="xsd:string"/>
825+
<xsd:attribute name="passphrase" type="xsd:string" />
826+
</xsd:complexType>
827+
828+
<xsd:complexType name="mailer_smime_signer">
829+
<xsd:attribute name="key" type="xsd:string"/>
830+
<xsd:attribute name="certificate" type="xsd:string"/>
831+
<xsd:attribute name="passphrase" type="xsd:string" />
832+
<xsd:attribute name="extraCertificates" type="xsd:string" />
833+
<xsd:attribute name="signOptions" type="xsd:integer" />
834+
</xsd:complexType>
835+
836+
<xsd:complexType name="mailer_smime_encrypter">
837+
<xsd:attribute name="certificate" type="xsd:string"/>
838+
<xsd:attribute name="cipher" type="xsd:integer" />
839+
</xsd:complexType>
840+
814841
<xsd:complexType name="http_cache">
815842
<xsd:sequence>
816843
<xsd:element name="private-header" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

+21
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,27 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
922922
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
923923
'message_bus' => null,
924924
'headers' => [],
925+
'dkim_signer' => [
926+
'enabled' => false,
927+
'options' => [],
928+
'key' => '',
929+
'domain' => '',
930+
'select' => '',
931+
'passphrase' => '',
932+
],
933+
'smime_signer' => [
934+
'enabled' => false,
935+
'key' => '',
936+
'certificate' => '',
937+
'passphrase' => null,
938+
'extra_certificates' => null,
939+
'sign_options' => null,
940+
],
941+
'smime_encrypter' => [
942+
'enabled' => false,
943+
'certificate' => '',
944+
'cipher' => null,
945+
],
925946
],
926947
'notifier' => [
927948
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),

src/Symfony/Component/Mailer/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add DSN param `retry_period` to override default email transport retry period
88
* Add `Dsn::getBooleanOption()`
99
* Add DSN param `source_ip` to allow binding to a (specific) IPv4 or IPv6 address.
10+
* Enable the mailer to configure DKIM or SMIME signer
1011

1112
7.2
1213
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Mailer\Event\MessageEvent;
16+
use Symfony\Component\Mime\Crypto\DkimSigner;
17+
use Symfony\Component\Mime\Message;
18+
19+
/**
20+
* Signs the message.
21+
*
22+
* @author Elías Fernández
23+
*/
24+
class DkimSignedMessageListener implements EventSubscriberInterface
25+
{
26+
public function __construct(
27+
private DkimSigner $signer,
28+
) {
29+
}
30+
31+
public function onMessage(MessageEvent $event): void
32+
{
33+
$message = $event->getMessage();
34+
if (!$message instanceof Message) {
35+
return;
36+
}
37+
$event->setMessage($this->signer->sign($message));
38+
}
39+
40+
public static function getSubscribedEvents(): array
41+
{
42+
return [
43+
MessageEvent::class => ['onMessage', -128],
44+
];
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Mailer\Event\MessageEvent;
16+
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
17+
use Symfony\Component\Mime\Message;
18+
19+
/**
20+
* Encrypts the message.
21+
*
22+
* @author Elías Fernández
23+
*/
24+
class SmimeEncryptedMessageListener implements EventSubscriberInterface
25+
{
26+
public function __construct(
27+
private SMimeEncrypter $encrypter,
28+
) {
29+
}
30+
31+
public function onMessage(MessageEvent $event): void
32+
{
33+
$message = $event->getMessage();
34+
if (!$message instanceof Message) {
35+
return;
36+
}
37+
38+
$event->setMessage($this->encrypter->encrypt($message));
39+
}
40+
41+
public static function getSubscribedEvents(): array
42+
{
43+
return [
44+
MessageEvent::class => ['onMessage', -128],
45+
];
46+
}
47+
}

0 commit comments

Comments
 (0)