8000 Refactor S/MIME encrypter to use certificate repository · symfony/symfony@7c76c54 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7c76c54

Browse files
committed
Refactor S/MIME encrypter to use certificate repository
Replaces direct certificate path usage with a repository interface for managing S/MIME certificates. This improves flexibility by allowing custom certificate retrieval logic through `SmimeCertificateRepositoryInterface`. Adjusted related tests, configuration, and event listener implementation accordingly.
1 parent b28e597 commit 7c76c54

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
@@ -2314,8 +2314,8 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
23142314
->canBeEnabled()
23152315
->info('S/MIME encrypter configuration')
23162316
->children()
2317-
->scalarNode('certificate')
2318-
->info('Path to certificate (in PEM format without the `file://` prefix)')
2317+
->scalarNode('repository')
2318+
->info('Path to the S/MIME certificate repository. Shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`.')
23192319
->defaultValue('')
23202320
->cannotBeEmpty()
23212321
->end()

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2893,11 +2893,9 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
28932893
if (!class_exists(SmimeEncryptedMessageListener::class)) {
28942894
throw new LogicException('S/MIME encrypted messages support cannot be enabled as this version of the Mailer component does not support it.');
28952895
}
2896-
$smimeDecrypter = $container->getDefinition('mailer.smime_encrypter');
2897-
$smimeDecrypter->setArgument(0, $config['smime_encrypter']['certificate']);
2898-
$smimeDecrypter->setArgument(1, $config['smime_encrypter']['cipher']);
2896+
$container->setAlias('mailer.smime_encrypter.repository', $config['smime_encrypter']['repository']);
2897+
$container->setParameter('mailer.smime_encrypter.cipher', $config['smime_encrypter']['cipher']);
28992898
} else {
2900-
$container->removeDefinition('mailer.smime_encrypter');
29012899
$container->removeDefinition('mailer.smime_encrypter.listener');
29022900
}
29032901

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) {
@@ -102,12 +101,6 @@
102101
abstract_arg('signOptions'),
103102
])
104103

105-
->set('mailer.smime_encrypter', SMimeEncrypter::class)
106-
->args([
107-
abstract_arg('certificate'),
108-
abstract_arg('cipher'),
109-
])
110-
111104
->set('mailer.dkim_signer.listener', DkimSignedMessageListener::class)
112105
->args([
113106
service(DkimSigner::class),
@@ -122,7 +115,8 @@
122115

123116
->set('mailer.smime_encrypter.listener', SmimeEncryptedMessageListener::class)
124117
->args([
125-
service('mailer.smime_encrypter'),
118+
service('mailer.smime_encrypter.repository'),
119+
param('mailer.smime_encrypter.cipher'),
126120
])
127121
->tag('kernel.event_subscriber')
128122

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
@@ -836,7 +836,7 @@
836836
</xsd:complexType>
837837

838838
<xsd:complexType name="mailer_smime_encrypter">
839-
<xsd:attribute name="certificate" type="xsd:string"/>
839+
<xsd:attribute name="repository" type="xsd:string" />
840840
<xsd:attribute name="cipher" type="xsd:integer" />
841841
</xsd:complexType>
842842

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
941941
],
942942
'smime_encrypter' => [
943943
'enabled' => false,
944-
'certificate' => '',
944+
'repository' => '',
945945
'cipher' => null,
946946
],
947947
],
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 <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

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('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