Skip to content

[MimePgp] Add the component #59372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"symfony/mailer": "self.version",
"symfony/messenger": "self.version",
"symfony/mime": "self.version",
"symfony/mime-pgp": "self.version",
"symfony/monolog-bridge": "self.version",
"symfony/notifier": "self.version",
"symfony/options-resolver": "self.version",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2343,6 +2343,54 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
->end()
->end()
->end()
->arrayNode('pgp_signer')
->addDefaultsIfNotSet()
->canBeEnabled()
->info('PGP/MIME signer configuration')
->children()
->scalarNode('secret_key')
->info('Path to the secret key (ASCII armored format without the `file://` prefix)')
->defaultValue('')
->cannotBeEmpty()
->end()
->scalarNode('public_key')
->info('Path to the public key (ASCII armored format without the `file://` prefix)')
->defaultNull('')
->end()
->scalarNode('passphrase')
->info('The secret key passphrase')
->defaultNull()
->end()
->scalarNode('binary')
->info('Path to the GnuPG binary')
->defaultValue('gpg')
->end()
->scalarNode('digest_algorithm')
->info('The digest algorithm')
->defaultValue('SHA512')
->end()
->end()
->end()
->arrayNode('pgp_encrypter')
->addDefaultsIfNotSet()
->canBeEnabled()
->info('S/MIME encrypter configuration')
->children()
->scalarNode('repository')
->info('Path to the S/MIME certificate repository. Shall implement the `Symfony\Component\Mailer\EventListener\PgpPublicKeyRepositoryInterface`.')
->defaultValue('')
->cannotBeEmpty()
->end()
->scalarNode('binary')
->info('Path to the GnuPG binary')
->defaultValue('gpg')
->end()
->integerNode('cipher_algorithm')
->info('The cipher algorithm used to encrypt the message')
->defaultValue('AES256')
->end()
->end()
->end()
->end()
->end()
->end()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@
use Symfony\Component\Mailer\Command\MailerTestCommand;
use Symfony\Component\Mailer\EventListener\DkimSignedMessageListener;
use Symfony\Component\Mailer\EventListener\MessengerTransportListener;
use Symfony\Component\Mailer\EventListener\PgpMimeEncryptedMessageListener;
use Symfony\Component\Mailer\EventListener\PgpMimeSignedMessageListener;
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
use Symfony\Component\Mailer\Mailer;
Expand Down Expand Up @@ -2901,6 +2903,34 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
$container->removeDefinition('mailer.smime_encrypter.listener');
}

if ($config['pgp_signer']['enabled']) {
if (!class_exists(PgpMimeSignedMessageListener::class)) {
throw new LogicException('PGP/MIME signed messages support cannot be enabled as this version of the Mailer component does not support it.');
}
$smimeSigner = $container->getDefinition('mailer.pgp_signer');
$smimeSigner->setArgument(0, $config['pgp_signer']['secret_key']);
$smimeSigner->setArgument(1, $config['pgp_signer']['public_key']);
$smimeSigner->setArgument(2, $config['pgp_signer']['passphrase']);
$smimeSigner->setArgument(3, [
'binary' => $config['pgp_signer']['binary'],
'digest_algorithm' => $config['pgp_signer']['digest_algorithm'],
]);
} else {
$container->removeDefinition('mailer.pgp_signer');
$container->removeDefinition('mailer.pgp_signer.listener');
}

if ($config['pgp_encrypter']['enabled']) {
if (!class_exists(PgpMimeEncryptedMessageListener::class)) {
throw new LogicException('PGP/MIME encrypted messages support cannot be enabled as this version of the Mailer component does not support it.');
}
$container->setAlias('mailer.pgp_encrypter.repository', $config['pgp_encrypter']['repository']);
$container->setParameter('mailer.pgp_encrypter.binary', $config['pgp_encrypter']['binary']);
$container->setParameter('mailer.pgp_encrypter.cipher_algorithm', $config['pgp_encrypter']['cipher_algorithm']);
} else {
$container->removeDefinition('mailer.pgp_encrypter.listener');
}

if ($webhookEnabled) {
$loader->load('mailer_webhook.php');
}
Expand Down
25 changes: 25 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Symfony\Component\Mailer\EventListener\MessageListener;
use Symfony\Component\Mailer\EventListener\MessageLoggerListener;
use Symfony\Component\Mailer\EventListener\MessengerTransportListener;
use Symfony\Component\Mailer\EventListener\PgpMimeEncryptedMessageListener;
use Symfony\Component\Mailer\EventListener\PgpMimeSignedMessageListener;
use Symfony\Component\Mailer\EventListener\SmimeEncryptedMessageListener;
use Symfony\Component\Mailer\EventListener\SmimeSignedMessageListener;
use Symfony\Component\Mailer\Mailer;
Expand All @@ -28,6 +30,7 @@
use Symfony\Component\Mime\Crypto\DkimSigner;
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
use Symfony\Component\Mime\Crypto\SMimeSigner;
use Symfony\Component\MimePgp\PgpSigner;

return static function (ContainerConfigurator $container) {
$container->services()
Expand Down Expand Up @@ -126,6 +129,28 @@
])
->tag('kernel.event_subscriber')

->set('mailer.pgp_signer', PgpSigner::class)
->args([
abstract_arg('secret_key'),
abstract_arg('public_key'),
abstract_arg('passphrase'),
abstract_arg('options'),
])

->set('mailer.pgp_signer.listener', PgpMimeSignedMessageListener::class)
->args([
service('mailer.pgp_signer'),
])
->tag('kernel.event_subscriber')

->set('mailer.pgp_encrypter.listener', PgpMimeEncryptedMessageListener::class)
->args([
service('mailer.pgp_encrypter.repository'),
param('mailer.pgp_encrypter.binary'),
param('mailer.pgp_encrypter.cipher_algorithm'),
])
->tag('kernel.event_subscriber')

->set('console.command.mailer_test', MailerTestCommand::class)
->args([
service('mailer.transports'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,8 @@
<xsd:element name="dkim-signer" type="mailer_dkim_signer" minOccurs="0" />
<xsd:element name="smime-signer" type="mailer_smime_signer" minOccurs="0" />
<xsd:element name="smime-encrypter" type="mailer_smime_encrypter" minOccurs="0" />
<xsd:element name="pgp-signer" type="mailer_pgp_signer" minOccurs="0" />
<xsd:element name="pgp-encrypter" type="mailer_pgp_encrypter" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="dsn" type="xsd:string" />
Expand Down Expand Up @@ -840,6 +842,24 @@
<xsd:attribute name="cipher" type="xsd:integer" />
</xsd:complexType>

<xsd:complexType name="mailer_pgp_signer">
<xsd:sequence>
<xsd:element name="secret_key" type="xsd:string" />
<xsd:element name="public_key" type="xsd:string" minOccurs="0" />
<xsd:element name="passphrase" type="xsd:string" minOccurs="0" />
<xsd:element name="binary" type="xsd:string" minOccurs="0" />
<xsd:element name="digest_algorithm" type="xsd:string" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="mailer_pgp_encrypter">
<xsd:sequence>
<xsd:element name="repository" type="xsd:string"/>
<xsd:element name="binary" type="xsd:string" minOccurs="0" />
<xsd:element name="cipher_algorithm" type="xsd:string" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="http_cache">
<xsd:sequence>
<xsd:element name="private-header" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,20 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
'certificate' => '',
'cipher' => null,
],
'pgp_signer' => [
'enabled' => false,
'secret_key' => '',
'public_key' => null,
'passphrase' => null,
'binary' => 'gpg',
'digest_algorithm' => 'SHA512',
],
'pgp_encrypter' => [
'enabled' => false,
'repository' => '',
'binary' => 'gpg',
'cipher_algorithm' => 'AES256',
],
],
'notifier' => [
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"symfony/mailer": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
"symfony/mime": "^6.4|^7.0",
"symfony/mime-pgp": "^7.3",
"symfony/notifier": "^6.4|^7.0",
"symfony/process": "^6.4|^7.0",
"symfony/rate-limiter": "^6.4|^7.0",
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Mailer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CHANGELOG
* Add DSN param `source_ip` to allow binding to a (specific) IPv4 or IPv6 address.
* Add DSN param `require_tls` to enforce use of TLS/STARTTLS
* Add `DkimSignedMessageListener`, `SmimeEncryptedMessageListener`, and `SmimeSignedMessageListener`
* Add `PgpMimeSignedMessageListener`

7.2
---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Mailer\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mime\Message;
use Symfony\Component\MimePgp\PgpEncrypter;

/**
* Encrypts messages using S/MIME.
*
* @author Florent Morselli <florent.morselli@spomky-labs.com>
*/
final class PgpMimeEncryptedMessageListener implements EventSubscriberInterface
{
public function __construct(
private readonly PgpPublicKeyRepositoryInterface $pgpRepository,
private readonly string $binary = 'gpg',
private readonly string $cipherAlgorithm = 'AES256',
) {
}

public function onMessage(MessageEvent $event): void
{
$message = $event->getMessage();
if (!$message instanceof Message) {
return;
}
if (!$message->getHeaders()->has('X-Pgp-Encrypt')) {
return;
}
$message->getHeaders()->remove('X-Pgp-Encrypt');
$publicKeys = [];
foreach ($event->getEnvelope()->getRecipients() as $recipient) {
$certificatePath = $this->pgpRepository->findPublicKeyPathFor($recipient->getAddress());
if (null === $certificatePath) {
return;
}
$publicKeys[$recipient->getAddress()] = $certificatePath;
}
if (0 === \count($publicKeys)) {
return;
}

$encrypter = new PgpEncrypter($publicKeys, ['binary' => $this->binary, 'cipher_algorithm' => $this->cipherAlgorithm]);

$event->setMessage($encrypter->encrypt($message));
}

public static function getSubscribedEvents(): array
{
return [
MessageEvent::class => ['onMessage', -128],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Mailer\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mime\Message;
use Symfony\Component\MimePgp\PgpSigner;

/**
* Signs messages using PGP/MIME.
*
* @author Florent Morselli
*/
class PgpMimeSignedMessageListener implements EventSubscriberInterface
{
public function __construct(
private readonly PgpSigner $signer,
) {
}

public function onMessage(MessageEvent $event): void
{
$message = $event->getMessage();
if (!$message instanceof Message) {
return;
}

$event->setMessage($this->signer->sign($message));
}

public static function getSubscribedEvents(): array
{
return [
MessageEvent::class => ['onMessage', -128],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Mailer\EventListener;

/**
* Encrypts messages using S/MIME.
*
* @author Florent Morselli <florent.morselli@spomky-labs.com>
*/
interface PgpPublicKeyRepositoryInterface
{
/**
* @return ?string The path to the PGP public key. null if not found
*/
public function findPublicKeyPathFor(string $email): ?string;
}
Loading
Loading