Skip to content

Commit b67f00a

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 b67f00a

Some content is hidden

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

48 files changed

+1925
-0
lines changed

composer.json

+1
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

+28
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

+18
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

+16
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

+11
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

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

+1
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

+1
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
---
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+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
-----BEGIN PGP PUBLIC KEY BLOCK-----
2+
3+
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
50+
=w8wH
51+
-----END PGP PUBLIC KEY BLOCK-----

0 commit comments

Comments
 (0)