8000 Add experimental MimePgp component · symfony/symfony@53db3ba · GitHub
[go: up one dir, main page]

Skip to content

Commit 53db3ba

Browse files
committed
Add experimental MimePgp component
Introduce the new MimePgp component for encrypting MIME messages using OpenPGP. The component is marked as experimental and includes initial setup files, exceptions, tests, and a changelog.
1 parent b28e597 commit 53db3ba

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2143
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"symfony/mailer": "self.version",
9090
"symfony/messenger": "self.version",
9191
"symfony/mime": "self.version",
92+
"symfony/mime-pgp": "self.version",
9293
"symfony/monolog-bridge": "self.version",
9394
"symfony/notifier": "self.version",
9495
"symfony/options-resolver": "self.version",

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,6 +2343,54 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
23432343
->end()
23442344
->end()
23452345
->end()
2346+
->arrayNode('pgp_signer')
2347+
->addDefaultsIfNotSet()
2348+
->canBeEnabled()
2349+
->info('PGP/MIME signer configuration')
2350+
->children()
2351+
->scalarNode('secret_key')
2352+
->info('Path to the secret key (ASCII armored format without the `file://` prefix)')
2353+
->defaultValue('')
2354+
->cannotBeEmpty()
2355+
->end()
2356+
->scalarNode('public_key')
2357+
->info('Path to the public key (ASCII armored format without the `file://` prefix)')
2358+
->defaultNull('')
2359+
->end()
2360+
->scalarNode('passphrase')
2361+
->info('The secret key passphrase')
2362+
->defaultNull()
2363+
->end()
2364+
->scalarNode('binary')
2365+
->info('Path to the GnuPG binary')
2366+
->defaultValue('gpg')
2367+
->end()
2368+
->scalarNode('digest_algorithm')
2369+
->info('The digest algorithm')
2370+
->defaultValue('SHA512')
2371+
->end()
2372+
->end()
2373+
->end()
2374+
->arrayNode('pgp_encrypter')
2375+
->addDefaultsIfNotSet()
2376+
->canBeEnabled()
2377+
->info('S/MIME encrypter configuration')
2378+
->children()
2379+
->scalarNode('repository')
2380+
->info('Path to the S/MIME certificate repository. Shall implement the `Symfony\Component\Mailer\EventListener\PgpPublicKeyRepositoryInterface`.')
2381+
->defaultValue('')
2382+
->cannotBeEmpty()
2383+
->end()
2384+
->scalarNode('binary')
2385+
->info('Path to the GnuPG binary')
2386+
->defaultValue('gpg')
2387+
->end()
2388+
->integerNode('cipher_algorithm')
2389+
->info('The cipher algorithm used to encrypt the message')
2390+
->defaultValue('AES256')
2391+
->end()
2392+
->end()
2393+
->end()
23462394
->end()
23472395
->end()
23482396
->end()

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@
113113
use Symfony\Component\Mailer\Command\MailerTestCommand;
114114
use Symfony\Component\Mailer\EventListener\DkimSignedMessageListener;
115115
use Symfony\Component\Mailer\EventListener\MessengerTransportListener;
116+
use Symfony\Component\Mailer\EventListener\PgpMimeEncryptedMessageListener;
117+
use Symfony\Component\Mailer\EventListener\PgpMimeSignedMessageListener;
116118
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
117119
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
118120
use Symfony\Component\Mailer\Mailer;
@@ -2901,6 +2903,34 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
29012903
$container->removeDefinition('mailer.smime_encrypter.listener');
29022904
}
29032905

2906+
if ($config['pgp_signer']['enabled']) {
2907+
if (!class_exists(PgpMimeSignedMessageListener::class)) {
2908+
throw new LogicException('PGP/MIME signed messages support cannot be enabled as this version of the Mailer component does not support it.');
2909+
}
2910+
$smimeSigner = $container->getDefinition('mailer.pgp_signer');
2911+
$smimeSigner->setArgument(0, $config['pgp_signer']['secret_key']);
2912+
$smimeSigner->setArgument(1, $config['pgp_signer']['public_key']);
2913+
$smimeSigner->setArgument(2, $config['pgp_signer']['passphrase']);
2914+
$smimeSigner->setArgument(3, [
2915+
'binary' => $config['pgp_signer']['binary'],
2916+
'digest_algorithm' => $config['pgp_signer']['digest_algorithm'],
2917+
]);
2918+
} else {
2919+
$container->removeDefinition('mailer.pgp_signer');
2920+
$container->removeDefinition('mailer.pgp_signer.listener');
2921+
}
2922+
2923+
if ($config['pgp_encrypter']['enabled']) {
2924+
if (!class_exists(PgpMimeEncryptedMessageListener::class)) {
2925+
throw new LogicException('PGP/MIME encrypted messages support cannot be enabled as this version of the Mailer component does not support it.');
2926+
}
2927+
$container->setAlias('mailer.pgp_encrypter.repository', $config['pgp_encrypter']['repository']);
2928+
$container->setParameter('mailer.pgp_encrypter.binary', $config['pgp_encrypter']['binary']);
2929+
$container->setParameter('mailer.pgp_encrypter.cipher_algorithm', $config['pgp_encrypter']['cipher_algorithm']);
2930+
} else {
2931+
$container->removeDefinition('mailer.pgp_encrypter.listener');
2932+
}
2933+
29042934
if ($webhookEnabled) {
29052935
$loader->load('mailer_webhook.php');
29062936
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Symfony\Component\Mailer\EventListener\MessageListener;
1818
use Symfony\Component\Mailer\EventListener\MessageLoggerListener;
1919
use Symfony\Component\Mailer\EventListener\MessengerTransportListener;
20+
use Symfony\Component\Mailer\EventListener\PgpMimeEncryptedMessageListener;
21+
use Symfony\Component\Mailer\EventListener\PgpMimeSignedMessageListener;
2022
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
2123
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
2224
use Symfony\Component\Mailer\Mailer;
@@ -28,6 +30,7 @@
2830
use Symfony\Component\Mime\Crypto\DkimSigner;
2931
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
3032
use Symfony\Component\Mime\Crypto\SMimeSigner;
33+
use Symfony\Component\MimePgp\PgpSigner;
3134

3235
return static function (ContainerConfigurator $container) {
3336
$container->services()
@@ -126,6 +129,28 @@
126129
])
127130
->tag('kernel.event_subscriber')
128131

132+
->set('mailer.pgp_signer', PgpSigner::class)
133+
->args([
134+
abstract_arg('secret_key'),
135+
abstract_arg('public_key'),
136+
abstract_arg('passphrase'),
137+
abstract_arg('options'),
138+
])
139+
140+
->set('mailer.pgp_signer.listener', PgpMimeSignedMessageListener::class)
141+
->args([
142+
service('mailer.pgp_signer'),
143+
])
144+
->tag('kernel.event_subscriber')
145+
146+
->set('mailer.pgp_encrypter.listener', PgpMimeEncryptedMessageListener::class)
147+
->args([
148+
service('mailer.pgp_encrypter.repository'),
149+
param('mailer.pgp_encrypter.binary'),
150+
param('mailer.pgp_encrypter.cipher_algorithm'),
151+
])
152+
->tag('kernel.event_subscriber')
153+
129154
->set('console.command.mailer_test', MailerTestCommand::class)
130155
->args([
131156
service('mailer.transports'),

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,8 @@
794794
<xsd:element name="dkim-signer" type="mailer_dkim_signer" minOccurs="0" />
795795
<xsd:element name="smime-signer" type="mailer_smime_signer" minOccurs="0" />
796796
<xsd:element name="smime-encrypter" type="mailer_smime_encrypter" minOccurs="0" />
797+
<xsd:element name="pgp-signer" type="mailer_pgp_signer" minOccurs="0" />
798+
<xsd:element name="pgp-encrypter" type="mailer_pgp_encrypter" minOccurs="0" />
797799
</xsd:sequence>
798800
<xsd:attribute name="enabled" type="xsd:boolean" />
799801
<xsd:attribute name="dsn" type="xsd:string" />
@@ -840,6 +842,24 @@
840842
<xsd:attribute name="cipher" type="xsd:integer" />
841843
</xsd:complexType>
842844

845+
<xsd:complexType name="mailer_pgp_signer">
846+
<xsd:sequence>
847+
<xsd:element name="secret_key" type="xsd:string" />
848+
<xsd:element name="public_key" type="xsd:string" minOccurs="0" />
849+
<xsd:element name="passphrase" type="xsd:string" minOccurs="0" />
850+
<xsd:element name="binary" type="xsd:string" minOccurs="0" />
851+
<xsd:element name="digest_algorithm" type="xsd:string" minOccurs="0" />
852+
</xsd:sequence>
853+
</xsd:complexType>
854+
855+
<xsd:complexType name="mailer_pgp_encrypter">
856+
<xsd:sequence>
857+
<xsd:element name="repository" type="xsd:string"/>
858+
<xsd:element name="binary" type="xsd:string" minOccurs="0" />
859+
<xsd:element name="cipher_algorithm" type="xsd:string" minOccurs="0" />
860+
</xsd:sequence>
861+
</xsd:complexType>
862+
843863
<xsd:complexType name="http_cache">
844864
<xsd:sequence>
845865
<xsd:element name="private-header" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,20 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
944944
'certificate' => '',
945945
'cipher' => null,
946946
],
947+
'pgp_signer' => [
948+
'enabled' => false,
949+
'secret_key' => '',
950+
'public_key' => null,
951+
'passphrase' => null,
952+
'binary' => 'gpg',
953+
'digest_algorithm' => 'SHA512',
954+
],
955+
'pgp_encrypter' => [
956+
'enabled' => false,
957+
'repository' => '',
958+
'binary' => 'gpg',
959+
'cipher_algorithm' => 'SHA512',
960+
],
947961
],
948962
'notifier' => [
949963
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"symfony/mailer": "^6.4|^7.0",
5454
"symfony/messenger": "^6.4|^7.0",
5555
"symfony/mime": "^6.4|^7.0",
56+
"symfony/mime-pgp": "^7.3",
5657
"symfony/notifier": "^6.4|^7.0",
5758
"symfony/process": "^6.4|^7.0",
5859
"symfony/rate-limiter": "^6.4|^7.0",

src/Symfony/Component/Mailer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add DSN param `source_ip` to allow binding to a (specific) IPv4 or IPv6 address.
1010
* Add DSN param `require_tls` to enforce use of TLS/STARTTLS
1111
* Add `DkimSignedMessageListener`, `SmimeEncryptedMessageListener`, and `SmimeSignedMessageListener`
12+
* Add `PgpMimeSignedMessageListener`
1213

1314
7.2
1415
---
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
use Symfony\Component\MimePgp\PgpEncrypter;
19+
20+
/**
21+
* Encrypts messages using S/MIME.
22+
*
23+
* @author Florent Morselli <florent.morselli@spomky-labs.com>
24+
*/
25+
final class PgpMimeEncryptedMessageListener implements EventSubscriberInterface
26+
{
27+
public function __construct(
28+
private readonly PgpPublicKeyRepositoryInterface $pgpRepository,
29+
private readonly string $binary = 'gpg',
30+
private readonly string $cipherAlgorithm = 'AES256',
31+
) {
32+
}
33+
34+
public function onMessage(MessageEvent $event): void
35+
{
36+
$message = $event->getMessage();
37+
if (!$message instanceof Message) {
38+
return;
39+
}
40+
if (!$message->getHeaders()->has('X-Pgp-Encrypt')) {
41+
return;
42+
}
43+
$message->getHeaders()->remove('X-Pgp-Encrypt');
44+
$publicKeys = [];
45+
foreach ($event->getEnvelope()->getRecipients() as $recipient) {
46+
$certificatePath = $this->pgpRepository->findPublicKeyPathFor($recipient->getAddress());
47+
if (null === $certificatePath) {
48+
return;
49+
}
50+
$publicKeys[$recipient->getAddress()] = $certificatePath;
51+
}
52+
if (0 === \count($publicKeys)) {
53+
return;
54+
}
55+
56+
$encrypter = new PgpEncrypter($publicKeys, ['binary' => $this->binary, 'cipher_algorithm' => $this->cipherAlgorithm]);
57+
58+
$event->setMessage($encrypter->encrypt($message));
59+
}
60+
61+
public static function getSubscribedEvents(): array
62+
{
63+
return [
64+
MessageEvent::class => ['onMessage', -128],
65+
];
66+
}
67+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\Address;
17+
use Symfony\Component\Mime\Message;
18+
use Symfony\Component\MimePgp\PgpSigner;
19+
20+
/**
21+
* Signs messages using PGP/MIME.
22+
*
23+
* @author Florent Morselli
24+
*/
25+
class PgpMimeSignedMessageListener implements EventSubscriberInterface
26+
{
27+
public function __construct(
28+
private readonly PgpSigner $signer,
29+
) {
30+
}
31+
32+
public function onMessage(MessageEvent $event): void
33+
{
34+
$message = $event->getMessage();
35+
if (!$message instanceof Message) {
36+
return;
37+
}
38+
39+
$event->setMessage($this->signer->sign($message));
40+
}
41+
42+
public static function getSubscribedEvents(): array
43+
{
44+
return [
45+
MessageEvent::class => ['onMessage', -128],
46+
];
47+
}
48+
}
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 PgpPublicKeyRepositoryInterface
20+
{
21+
/**
22+
* @return ?string The path to the PGP public key. null if not found
23+
*/
24+
public function findPublicKeyPathFor(string $email): ?string;
25+
}

0 commit comments

Comments
 (0)
0