Skip to content

Navigation Menu

Sign in
Appearance settings

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 8ba20b0

Browse filesBrowse 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 8ba20b0
Copy full SHA for 8ba20b0

Some content is hidden

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

50 files changed

+2141
-0
lines changed

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+48Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+30Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php
+25Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+20Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+14Lines 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' => 'AES256',
960+
],
947961
],
948962
'notifier' => [
949963
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),

‎src/Symfony/Bundle/FrameworkBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/composer.json
+1Lines 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

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