Skip to content

Commit 7b6d3a2

Browse files
committed
feature #58501 [Mailer] Add configuration for dkim and smime signers (elias-playfinder, eliasfernandez)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [Mailer] Add configuration for dkim and smime signers | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix #53941 | License | MIT DKIM is becoming a must have for emails but the current workflow to sign messages on symfony requires to do it in the controller or the application and it causes failures when the email is signed before some other listener needs to get it. This PR adds the needed configuration as part of the mailer configuration and sign all the messages if configured at the mailer level. By instance, we can add these two new blocks under mailer.yml: ```yml framework: mailer: dsn: '%env(MAILER_DSN)%' dkim_signer: key: 'file://private.key' domain: 'symfony.com' select: 's1' passphrase: '' options: ``` or ```yml framework: mailer: dsn: '%env(MAILER_DSN)%' smime_signer: key: '/path/to/certificate-private-key.key' certificate: '/path/to/certificate.crt' passphrase: '' extra_certificates: '/path/to/certificate.crt' sign_options: 1 ``` Commits ------- 244cbf1aad4 [Mailer] Add configuration for dkim and smime signers
2 parents 889f88f + 6ca2456 commit 7b6d3a2

File tree

5 files changed

+223
-0
lines changed

5 files changed

+223
-0
lines changed

DependencyInjection/Configuration.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2239,6 +2239,88 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
22392239
->end()
22402240
->end()
22412241
->end()
2242+
->arrayNode('dkim_signer')
2243+
->addDefaultsIfNotSet()
2244+
->fixXmlConfig('option')
2245+
->canBeEnabled()
2246+
->info('DKIM signer configuration')
2247+
->children()
2248+
->scalarNode('key')
2249+
->info('Key content, or path to key (in PEM format with the `file://` prefix)')
2250+
->defaultValue('')
2251+
->cannotBeEmpty()
2252+
->end()
2253+
->scalarNode('domain')->defaultValue('')->end()
2254+
->scalarNode('select')->defaultValue('')->end()
2255+
->scalarNode('passphrase')
2256+
->info('The private key passphrase')
2257+
->defaultValue('')
2258+
->end()
2259+
->arrayNode('options')
2260+
->performNoDeepMerging()
2261+
->normalizeKeys(false)
2262+
->useAttributeAsKey('name')
2263+
->prototype('variable')->end()
2264+
->end()
2265+
->end()
2266+
->end()
2267+
->arrayNode('smime_signer')
2268+
->addDefaultsIfNotSet()
2269+
->canBeEnabled()
2270+
->info('S/MIME signer configuration')
2271+
->children()
2272+
->scalarNode('key')
2273+
->info('Path to key (in PEM format)')
2274+
->defaultValue('')
2275+
->cannotBeEmpty()
2276+
->end()
2277+
->scalarNode('certificate')
2278+
->info('Path to certificate (in PEM format without the `file://` prefix)')
2279+
->defaultValue('')
2280+
->cannotBeEmpty()
2281+
->end()
2282+
->scalarNode('passphrase')
2283+
->info('The private key passphrase')
2284+
->defaultNull()
2285+
->end()
2286+
->scalarNode('extra_certificates')->defaultNull()->end()
2287+
->integerNode('sign_options')->defaultNull()->end()
2288+
->end()
2289+
->end()
2290+
->arrayNode('smime_encrypter')
2291+
->addDefaultsIfNotSet()
2292+
->canBeEnabled()
2293+
->info('S/MIME encrypter configuration')
2294+
->children()
2295+
->scalarNode('certificate')
2296+
->info('Path to certificate (in PEM format without the `file://` prefix)')
2297+
->defaultValue('')
2298+
->cannotBeEmpty()
2299+
->end()
2300+
->integerNode('cipher')
2301+
->info('A set of algorithms used to encrypt the message')
2302+
->defaultNull()
2303+
->beforeNormalization()
2304+
->always(function ($v): ?int {
2305+
if (null === $v) {
2306+
return null;
2307+
}
2308+
if (\defined('OPENSSL_CIPHER_'.$v)) {
2309+
return \constant('OPENSSL_CIPHER_'.$v);
2310+
}
2311+
2312+
throw new \InvalidArgumentException(\sprintf('"%s" is not a valid OPENSSL cipher.', $v));
2313+
})
2314+
->end()
2315+
->validate()
2316+
->ifTrue(function ($v) {
2317+
return \extension_loaded('openssl') && null !== $v && !\defined('OPENSSL_CIPHER_'.$v);
2318+
})
2319+
->thenInvalid('You must provide a valid cipher.')
2320+
->end()
2321+
->end()
2322+
->end()
2323+
->end()
22422324
->end()
22432325
->end()
22442326
->end()

DependencyInjection/FrameworkExtension.php

Lines changed: 45 additions & 0 deletions
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;
@@ -2861,6 +2864,48 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
28612864
$container->removeDefinition('mailer.messenger_transport_listener');
28622865
}
28632866

2867+
if ($config['dkim_signer']['enabled']) {
2868+
if (!class_exists(DkimSignedMessageListener::class)) {
2869+
throw new LogicException('DKIM signed messages support cannot be enabled as this version of the Mailer component does not support it.');
2870+
}
2871+
$dkimSigner = $container->getDefinition('mailer.dkim_signer');
2872+
$dkimSigner->setArgument(0, $config['dkim_signer']['key']);
2873+
$dkimSigner->setArgument(1, $config['dkim_signer']['domain']);
2874+
$dkimSigner->setArgument(2, $config['dkim_signer']['select']);
2875+
$dkimSigner->setArgument(3, $config['dkim_signer']['options']);
2876+
$dkimSigner->setArgument(4, $config['dkim_signer']['passphrase']);
2877+
} else {
2878+
$container->removeDefinition('mailer.dkim_signer');
2879+
$container->removeDefinition('mailer.dkim_signer.listener');
2880+
}
2881+
2882+
if ($config['smime_signer']['enabled']) {
2883+
if (!class_exists(SmimeSignedMessageListener::class)) {
2884+
throw new LogicException('SMIME signed messages support cannot be enabled as this version of the Mailer component does not support it.');
2885+
}
2886+
$smimeSigner = $container->getDefinition('mailer.smime_signer');
2887+
$smimeSigner->setArgument(0, $config['smime_signer']['key']);
2888+
$smimeSigner->setArgument(1, $config['smime_signer']['certificate']);
2889+
$smimeSigner->setArgument(2, $config['smime_signer']['passphrase']);
2890+
$smimeSigner->setArgument(3, $config['smime_signer']['extra_certificates']);
2891+
$smimeSigner->setArgument(4, $config['smime_signer']['sign_options']);
2892+
} else {
2893+
$container->removeDefinition('mailer.smime_signer');
2894+
$container->removeDefinition('mailer.smime_signer.listener');
2895+
}
2896+
2897+
if ($config['smime_encrypter']['enabled']) {
2898+
if (!class_exists(SmimeEncryptedMessageListener::class)) {
2899+
throw new LogicException('S/MIME encrypted messages support cannot be enabled as this version of the Mailer component does not support it.');
2900+
}
2901+
$smimeDecrypter = $container->getDefinition('mailer.smime_encrypter');
2902+
$smimeDecrypter->setArgument(0, $config['smime_encrypter']['certificate']);
2903+
$smimeDecrypter->setArgument(1, $config['smime_encrypter']['cipher']);
2904+
} else {
2905+
$container->removeDefinition('mailer.smime_encrypter');
2906+
$container->removeDefinition('mailer.smime_encrypter.listener');
2907+
}
2908+
28642909
if ($webhookEnabled) {
28652910
$loader->load('mailer_webhook.php');
28662911
}

Resources/config/mailer.php

Lines changed: 48 additions & 0 deletions
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'),

Resources/config/schema/symfony-1.0.xsd

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,9 @@
790790
<xsd:element name="transport" type="mailer_transport" minOccurs="0" maxOccurs="unbounded" />
791791
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
792792
<xsd:element name="header" type="header" minOccurs="0" maxOccurs="unbounded" />
793+
<xsd:element name="dkim-signer" type="mailer_dkim_signer" minOccurs="0" />
794+
<xsd:element name="smime-signer" type="mailer_smime_signer" minOccurs="0" />
795+
<xsd:element name="smime-encrypter" type="mailer_smime_encrypter" minOccurs="0" />
793796
</xsd:sequence>
794797
<xsd:attribute name="enabled" type="xsd:boolean" />
795798
<xsd:attribute name="dsn" type="xsd:string" />
@@ -812,6 +815,30 @@
812815
</xsd:sequence>
813816
</xsd:complexType>
814817

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

Tests/DependencyInjection/ConfigurationTest.php

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

0 commit comments

Comments
 (0)