Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7100c7b

Browse filesBrowse files
committed
feature #59831 [Mailer][Mime] Refactor S/MIME encryption handling in SMimeEncryptionListener (Spomky)
This PR was merged into the 7.3 branch. Discussion ---------- [Mailer][Mime] Refactor S/MIME encryption handling in `SMimeEncryptionListener` | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | yes | New feature? | yes | Deprecations? | no | Issues | | License | MIT It appears that the smime_encrypter introduced in #58501 is incorrect, as the email is encrypted only for the sender instead of being encrypted per recipient. This PR introduces a new `SmimeCertificateRepositoryInterface`, responsible for retrieving recipient certificates. An email is encrypted under the following conditions: * A certificate is found for all recipients. * The custom header `X-SMime-Encrypt` is present. If either of these conditions is not met, the email is sent unencrypted. Commits ------- 7c76c54 Refactor S/MIME encrypter to use certificate repository
2 parents 452ad95 + 7c76c54 commit 7100c7b
Copy full SHA for 7100c7b

File tree

8 files changed

+115
-24
lines changed
Filter options

8 files changed

+115
-24
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+2-2
Original file line numberDiff line numberDiff line change
@@ -2348,8 +2348,8 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
23482348
->canBeEnabled()
23492349
->info('S/MIME encrypter configuration')
23502350
->children()
2351-
->scalarNode('certificate')
2352-
->info('Path to certificate (in PEM format without the `file://` prefix)')
2351+
->scalarNode('repository')
2352+
->info('Path to the S/MIME certificate repository. Shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`.')
23532353
->defaultValue('')
23542354
->cannotBeEmpty()
23552355
->end()

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+2-4
Original file line numberDiff line numberDiff line change
@@ -2946,11 +2946,9 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
29462946
if (!class_exists(SmimeEncryptedMessageListener::class)) {
29472947
throw new LogicException('S/MIME encrypted messages support cannot be enabled as this version of the Mailer component does not support it.');
29482948
}
2949-
$smimeDecrypter = $container->getDefinition('mailer.smime_encrypter');
2950-
$smimeDecrypter->setArgument(0, $config['smime_encrypter']['certificate']);
2951-
$smimeDecrypter->setArgument(1, $config['smime_encrypter']['cipher']);
2949+
$container->setAlias('mailer.smime_encrypter.repository', $config['smime_encrypter']['repository']);
2950+
$container->setParameter('mailer.smime_encrypter.cipher', $config['smime_encrypter']['cipher']);
29522951
} else {
2953-
$container->removeDefinition('mailer.smime_encrypter');
29542952
$container->removeDefinition('mailer.smime_encrypter.listener');
29552953
}
29562954

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php
+2-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
use Symfony\Component\Mailer\Transport\TransportInterface;
2727
use Symfony\Component\Mailer\Transport\Transports;
2828
use Symfony\Component\Mime\Crypto\DkimSigner;
29-
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
3029
use Symfony\Component\Mime\Crypto\SMimeSigner;
3130

3231
return static function (ContainerConfigurator $container) {
@@ -99,12 +98,6 @@
9998
abstract_arg('signOptions'),
10099
])
101100

102-
->set('mailer.smime_encrypter', SMimeEncrypter::class)
103-
->args([
104-
abstract_arg('certificate'),
105-
abstract_arg('cipher'),
106-
])
107-
108101
->set('mailer.dkim_signer.listener', DkimSignedMessageListener::class)
109102
->args([
110103
service('mailer.dkim_signer'),
@@ -119,7 +112,8 @@
119112

120113
->set('mailer.smime_encrypter.listener', SmimeEncryptedMessageListener::class)
121114
->args([
122-
service('mailer.smime_encrypter'),
115+
service('mailer.smime_encrypter.repository'),
116+
param('mailer.smime_encrypter.cipher'),
123117
])
124118
->tag('kernel.event_subscriber')
125119

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+1-1
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@
855855
</xsd:complexType>
856856

857857
<xsd:complexType name="mailer_smime_encrypter">
858-
<xsd:attribute name="certificate" type="xsd:string"/>
858+
<xsd:attribute name="repository" type="xsd:string" />
859859
<xsd:attribute name="cipher" type="xsd:integer" />
860860
</xsd:complexType>
861861

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+1-1
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
959959
],
960960
'smime_encrypter' => [
961961
'enabled' => false,
962-
'certificate' => '',
962+
'repository' => '',
963963
'cipher' => null,
964964
],
965965
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
/**
15+
* Encrypts messages using S/MIME.
16+
*
17+
* @author Florent Morselli <florent.morselli@spomky-labs.com>
18+
*/
19+
interface SmimeCertificateRepositoryInterface
20+
{
21+
/**
22+
* @return ?string The path to the certificate. null if not found
23+
*/
24+
public function findCertificatePathFor(string $email): ?string;
25+
}

‎src/Symfony/Component/Mailer/EventListener/SmimeEncryptedMessageListener.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Mailer/EventListener/SmimeEncryptedMessageListener.php
+20-3
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121
*
2222
* @author Elías Fernández
2323
*/
24-
class SmimeEncryptedMessageListener implements EventSubscriberInterface
24+
final class SmimeEncryptedMessageListener implements EventSubscriberInterface
2525
{
2626
public function __construct(
27-
private SMimeEncrypter $encrypter,
27+
private readonly SmimeCertificateRepositoryInterface $smimeRepository,
28+
private readonly ?int $cipher = null,
2829
) {
2930
}
3031

@@ -34,8 +35,24 @@ public function onMessage(MessageEvent $event): void
3435
if (!$message instanceof Message) {
3536
return;
3637
}
38+
if (!$message->getHeaders()->has('X-SMime-Encrypt')) {
39+
return;
40+
}
41+
$message->getHeaders()->remove('X-SMime-Encrypt');
42+
$certificatePaths = [];
43+
foreach ($event->getEnvelope()->getRecipients() as $recipient) {
44+
$certificatePath = $this->smimeRepository->findCertificatePathFor($recipient->getAddress());
45+
if (null === $certificatePath) {
46+
return;
47+
}
48+
$certificatePaths[] = $certificatePath;
49+
}
50+
if (0 === \count($certificatePaths)) {
51+
return;
52+
}
53+
$encrypter = new SMimeEncrypter($certificatePaths, $this->cipher);
3754

38-
$event->setMessage($this->encrypter->encrypt($message));
55+
$event->setMessage($encrypter->encrypt($message));
3956
}
4057

4158
public static function getSubscribedEvents(): array

‎src/Symfony/Component/Mailer/Tests/EventListener/SmimeEncryptedMessageListenerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Mailer/Tests/EventListener/SmimeEncryptedMessageListenerTest.php
+62-5
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Mailer\Envelope;
1616
use Symfony\Component\Mailer\Event\MessageEvent;
17+
use Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface;
1718
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
1819
use Symfony\Component\Mime\Address;
19-
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
2020
use Symfony\Component\Mime\Header\Headers;
2121
use Symfony\Component\Mime\Header\MailboxListHeader;
22+
use Symfony\Component\Mime\Header\UnstructuredHeader;
2223
use Symfony\Component\Mime\Message;
2324
use Symfony\Component\Mime\Part\SMimePart;
2425
use Symfony\Component\Mime\Part\TextPart;
@@ -28,13 +29,15 @@ class SmimeEncryptedMessageListenerTest extends TestCase
2829
/**
2930
* @requires extension openssl
3031
*/
31-
public function testSmimeMessageSigningProcess()
32+
public function testSmimeMessageEncryptionProcess()
3233
{
33-
$encrypter = new SMimeEncrypter(\dirname(__DIR__).'/Fixtures/sign.crt');
34-
$listener = new SmimeEncryptedMessageListener($encrypter);
34+
$repository = $this->createMock(SmimeCertificateRepositoryInterface::class);
35+
$repository->method('findCertificatePathFor')->willReturn(\dirname(__DIR__).'/Fixtures/sign.crt');
36+
$listener = new SmimeEncryptedMessageListener($repository);
3537
$message = new Message(
3638
new Headers(
37-
new MailboxListHeader('From', [new Address('sender@example.com')])
39+
new MailboxListHeader('From', [new Address('sender@example.com')]),
40+
new UnstructuredHeader('X-SMime-Encrypt', 'true'),
3841
),
3942
new TextPart('hello')
4043
);
@@ -45,5 +48,59 @@ public function testSmimeMessageSigningProcess()
4548
$this->assertNotSame($message, $event->getMessage());
4649
$this->assertInstanceOf(TextPart::class, $message->getBody());
4750
$this->assertInstanceOf(SMimePart::class, $event->getMessage()->getBody());
51+
$this->assertFalse($event->getMessage()->getHeaders()->has('X-SMime-Encrypt'));
52+
}
53+
54+
/**
55+
* @requires extension openssl
56+
*/
57+
public function testMessageNotEncryptedWhenOneRecipientCertificateIsMissing()
58+
{
59+
$repository = $this->createMock(SmimeCertificateRepositoryInterface::class);
60+
$repository->method('findCertificatePathFor')->willReturnOnConsecutiveCalls(\dirname(__DIR__).'/Fixtures/sign.crt', null);
61+
$listener = new SmimeEncryptedMessageListener($repository);
62+
$message = new Message(
63+
new Headers(
64+
new MailboxListHeader('From', [new Address('sender@example.com')]),
65+
new UnstructuredHeader('X-SMime-Encrypt', 'true'),
66+
),
67+
new TextPart('hello')
68+
);
69+
$envelope = new Envelope(new Address('sender@example.com'), [
70+
new Address('r1@example.com'),
71+
new Address('r2@example.com'),
72+
]);
73+
$event = new MessageEvent($message, $envelope, 'default');
74+
75+
$listener->onMessage($event);
76+
$this->assertSame($message, $event->getMessage());
77+
$this->assertInstanceOf(TextPart::class, $message->getBody());
78+
$this->assertInstanceOf(TextPart::class, $event->getMessage()->getBody());
79+
}
80+
81+
/**
82+
* @requires extension openssl
83+
*/
84+
public function testMessageNotExplicitlyAskedForNonEncryption()
85+
{
86+
$repository = $this->createMock(SmimeCertificateRepositoryInterface::class);
87+
$repository->method('findCertificatePathFor')->willReturn(\dirname(__DIR__).'/Fixtures/sign.crt');
88+
$listener = new SmimeEncryptedMessageListener($repository);
89+
$message = new Message(
90+
new Headers(
91+
new MailboxListHeader('From', [new Address('sender@example.com')]),
92+
),
93+
new TextPart('hello')
94+
);
95+
$envelope = new Envelope(new Address('sender@example.com'), [
96+
new Address('r1@example.com'),
97+
new Address('r2@example.com'),
98+
]);
99+
$event = new MessageEvent($message, $envelope, 'default');
100+
101+
$listener->onMessage($event);
102+
$this->assertSame($message, $event->getMessage());
103+
$this->assertInstanceOf(TextPart::class, $message->getBody());
104+
$this->assertInstanceOf(TextPart::class, $event->getMessage()->getBody());
48105
}
49106
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.