Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7100c7b

Browse 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

File tree

8 files changed

+115
-24
lines changed

8 files changed

+115
-24
lines changed

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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 4 deletions
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

Lines changed: 2 additions & 8 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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
],
Lines changed: 25 additions & 0 deletions
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 <[email protected]>
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 <[email protected]>
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

Lines changed: 20 additions & 3 deletions
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

Lines changed: 62 additions & 5 deletions
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('[email protected]')])
39+
new MailboxListHeader('From', [new Address('[email protected]')]),
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('[email protected]')]),
65+
new UnstructuredHeader('X-SMime-Encrypt', 'true'),
66+
),
67+
new TextPart('hello')
68+
);
69+
$envelope = new Envelope(new Address('[email protected]'), [
70+
new Address('[email protected]'),
71+
new Address('[email protected]'),
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('[email protected]')]),
92+
),
93+
new TextPart('hello')
94+
);
95+
$envelope = new Envelope(new Address('[email protected]'), [
96+
new Address('[email protected]'),
97+
new Address('[email protected]'),
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)