Skip to content

Commit 15774f1

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 68944f5 commit 15774f1

Some content is hidden

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

47 files changed

+1909
-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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,6 +2343,34 @@ 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()
23462374
->end()
23472375
->end()
23482376
->end()

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
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\PgpMimeSignedMessageListener;
116117
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
117118
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
118119
use Symfony\Component\Mailer\Mailer;
@@ -2901,6 +2902,23 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
29012902
$container->removeDefinition('mailer.smime_encrypter.listener');
29022903
}
29032904

2905+
if ($config['pgp_signer']['enabled']) {
2906+
if (!class_exists(PgpMimeSignedMessageListener::class)) {
2907+
throw new LogicException('PGP/MIME signed messages support cannot be enabled as this version of the Mailer component does not support it.');
2908+
}
2909+
$smimeSigner = $container->getDefinition('mailer.pgp_signer');
2910+
$smimeSigner->setArgument(0, $config['pgp_signer']['secret_key']);
2911+
$smimeSigner->setArgument(1, $config['pgp_signer']['public_key']);
2912+
$smimeSigner->setArgument(2, $config['pgp_signer']['passphrase']);
2913+
$smimeSigner->setArgument(3, [
2914+
'binary' => $config['pgp_signer']['binary'],
2915+
'digest_algorithm' => $config['pgp_signer']['digest_algorithm'],
2916+
]);
2917+
} else {
2918+
$container->removeDefinition('mailer.pgp_signer');
2919+
$container->removeDefinition('mailer.pgp_signer.listener');
2920+
}
2921+
29042922
if ($webhookEnabled) {
29052923
$loader->load('mailer_webhook.php');
29062924
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
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\PgpMimeSignedMessageListener;
2021
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
2122
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
2223
use Symfony\Component\Mailer\Mailer;
@@ -28,6 +29,7 @@
2829
use Symfony\Component\Mime\Crypto\DkimSigner;
2930
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
3031
use Symfony\Component\Mime\Crypto\SMimeSigner;
32+
use Symfony\Component\MimePgp\PgpSigner;
3133

3234
return static function (ContainerConfigurator $container) {
3335
$container->services()
@@ -126,6 +128,20 @@
126128
])
127129
->tag('kernel.event_subscriber')
128130

131+
->set('mailer.pgp_signer', PgpSigner::class)
132+
->args([
133+
abstract_arg('secret_key'),
134+
abstract_arg('public_key'),
135+
abstract_arg('passphrase'),
136+
abstract_arg('options'),
137+
])
138+
139+
->set('mailer.pgp_signer.listener', PgpMimeSignedMessageListener::class)
140+
->args([
141+
service('mailer.pgp_signer'),
142+
])
143+
->tag('kernel.event_subscriber')
144+
129145
->set('console.command.mailer_test', MailerTestCommand::class)
130146
->args([
131147
service('mailer.transports'),

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@
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" />
797798
</xsd:sequence>
798799
<xsd:attribute name="enabled" type="xsd:boolean" />
799800
<xsd:attribute name="dsn" type="xsd:string" />
@@ -840,6 +841,16 @@
840841
<xsd:attribute name="cipher" type="xsd:integer" />
841842
</xsd:complexType>
842843

844+
<xsd:complexType name="mailer_pgp_signer">
845+
<xsd:sequence>
846+
<xsd:element name="secret_key" type="xsd:string" />
847+
<xsd:element name="public_key" type="xsd:string" minOccurs="0" />
848+
<xsd:element name="passphrase" type="xsd:string" minOccurs="0" />
849+
<xsd:element name="binary" type="xsd:string" minOccurs="0" />
850+
<xsd:element name="digest_algorithm" type="xsd:string" minOccurs="0" />
851+
</xsd:sequence>
852+
</xsd:complexType>
853+
843854
<xsd:complexType name="http_cache">
844855
<xsd:sequence>
845856
<xsd:element name="private-header" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,14 @@ 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+
],
947955
],
948956
'notifier' => [
949957
'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: 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 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+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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\Tests\EventListener;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Mailer\Envelope;
16+
use Symfony\Component\Mailer\Event\MessageEvent;
17+
use Symfony\Component\Mailer\EventListener\PgpMimeSignedMessageListener;
18+
use Symfony\Component\Mime\Address;
19+
use Symfony\Component\Mime\Header\Headers;
20+
use Symfony\Component\Mime\Header\MailboxListHeader;
21+
use Symfony\Component\Mime\Message;
22+
use Symfony\Component\Mime\Part\TextPart;
23+
use Symfony\Component\MimePgp\Mime\Part\Multipart\PgpSignedPart;
24+
use Symfony\Component\MimePgp\PgpSigner;
25+
26+
class PgpMimeSignedMessageListenerTest extends TestCase
27+
{
28+
public function testPgpMimeMessageSigningProcess()
29+
{
30+
$signer = new PgpSigner(
31+
\dirname(__DIR__).'/Fixtures/pgp_secret_key.asc',
32+
\dirname(__DIR__).'/Fixtures/pgp_public_key.asc',
33+
'test1234'
34+
);
35+
$listener = new PgpMimeSignedMessageListener($signer);
36+
$message = new Message(
37+
new Headers(
38+
new MailboxListHeader('From', [new Address('sender@example.com')])
39+
),
40+
new TextPart('hello')
41+
);
42+
$envelope = new Envelope(new Address('sender@example.com'), [new Address('r1@example.com')]);
43+
$event = new MessageEvent($message, $envelope, 'default');
44+
45+
$listener->onMessage($event);
46+
$this->assertNotSame($message, $event->getMessage());
47+
$this->assertInstanceOf(TextPart::class, $message->getBody());
48+
$this->assertInstanceOf(PgpSignedPart::class, $event->getMessage()->getBody());
49+
}
50+
}

0 commit comments

Comments
 (0)