[Mime] Add DKIM support
This commit is contained in:
parent
c0831f91e8
commit
6dc533821c
@ -4,6 +4,7 @@ CHANGELOG
|
|||||||
5.2.0
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* Add support for DKIM
|
||||||
* Deprecated `Address::fromString()`, use `Address::create()` instead
|
* Deprecated `Address::fromString()`, use `Address::create()` instead
|
||||||
|
|
||||||
4.4.0
|
4.4.0
|
||||||
|
97
src/Symfony/Component/Mime/Crypto/DkimOptions.php
Normal file
97
src/Symfony/Component/Mime/Crypto/DkimOptions.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?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\Mime\Crypto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper providing autocompletion for available DkimSigner options.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
final class DkimOptions
|
||||||
|
{
|
||||||
|
private $options = [];
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return $this->options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function algorithm(int $algo): self
|
||||||
|
{
|
||||||
|
$this->options['algorithm'] = $algo;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function signatureExpirationDelay(int $show): self
|
||||||
|
{
|
||||||
|
$this->options['signature_expiration_delay'] = $show;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function bodyMaxLength(int $max): self
|
||||||
|
{
|
||||||
|
$this->options['body_max_length'] = $max;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function bodyShowLength(bool $show): self
|
||||||
|
{
|
||||||
|
$this->options['body_show_length'] = $show;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function headerCanon(string $canon): self
|
||||||
|
{
|
||||||
|
$this->options['header_canon'] = $canon;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function bodyCanon(string $canon): self
|
||||||
|
{
|
||||||
|
$this->options['body_canon'] = $canon;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function headersToIgnore(array $headers): self
|
||||||
|
{
|
||||||
|
$this->options['headers_to_ignore'] = $headers;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
213
src/Symfony/Component/Mime/Crypto/DkimSigner.php
Normal file
213
src/Symfony/Component/Mime/Crypto/DkimSigner.php
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
<?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\Mime\Crypto;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Mime\Header\UnstructuredHeader;
|
||||||
|
use Symfony\Component\Mime\Message;
|
||||||
|
use Symfony\Component\Mime\Part\AbstractPart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* RFC 6376 and 8301
|
||||||
|
*/
|
||||||
|
final class DkimSigner
|
||||||
|
{
|
||||||
|
public const CANON_SIMPLE = 'simple';
|
||||||
|
public const CANON_RELAXED = 'relaxed';
|
||||||
|
|
||||||
|
public const ALGO_SHA256 = 'rsa-sha256';
|
||||||
|
public const ALGO_ED25519 = 'ed25519-sha256'; // RFC 8463
|
||||||
|
|
||||||
|
private $key;
|
||||||
|
private $domainName;
|
||||||
|
private $selector;
|
||||||
|
private $defaultOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $pk The private key as a string or the path to the file containing the private key, should be prefixed with file:// (in PEM format)
|
||||||
|
* @param string $passphrase A passphrase of the private key (if any)
|
||||||
|
*/
|
||||||
|
public function __construct(string $pk, string $domainName, string $selector, array $defaultOptions = [], string $passphrase = '')
|
||||||
|
{
|
||||||
|
if (!\extension_loaded('openssl')) {
|
||||||
|
throw new \LogicException('PHP extension "openssl" is required to use DKIM.');
|
||||||
|
}
|
||||||
|
if (!$this->key = openssl_pkey_get_private($pk, $passphrase)) {
|
||||||
|
throw new InvalidArgumentException('Unable to load DKIM private key: '.openssl_error_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->domainName = $domainName;
|
||||||
|
$this->selector = $selector;
|
||||||
|
$this->defaultOptions = $defaultOptions + [
|
||||||
|
'algorithm' => self::ALGO_SHA256,
|
||||||
|
'signature_expiration_delay' => 0,
|
||||||
|
'body_max_length' => PHP_INT_MAX,
|
||||||
|
'body_show_length' => false,
|
||||||
|
'header_canon' => self::CANON_RELAXED,
|
||||||
|
'body_canon' => self::CANON_RELAXED,
|
||||||
|
'headers_to_ignore' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sign(Message $message, array $options = []): Message
|
||||||
|
{
|
||||||
|
$options += $this->defaultOptions;
|
||||||
|
if (!\in_array($options['algorithm'], [self::ALGO_SHA256, self::ALGO_ED25519], true)) {
|
||||||
|
throw new InvalidArgumentException('Invalid DKIM signing algorithm "%s".', $options['algorithm']);
|
||||||
|
}
|
||||||
|
$headersToIgnore['return-path'] = true;
|
||||||
|
foreach ($options['headers_to_ignore'] as $name) {
|
||||||
|
$headersToIgnore[strtolower($name)] = true;
|
||||||
|
}
|
||||||
|
unset($headersToIgnore['from']);
|
||||||
|
$signedHeaderNames = [];
|
||||||
|
$headerCanonData = '';
|
||||||
|
$headers = $message->getPreparedHeaders();
|
||||||
|
foreach ($headers->getNames() as $name) {
|
||||||
|
foreach ($headers->all($name) as $header) {
|
||||||
|
if (isset($headersToIgnore[strtolower($header->getName())])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $header->getBodyAsString()) {
|
||||||
|
$headerCanonData .= $this->canonicalizeHeader($header->toString(), $options['header_canon']);
|
||||||
|
$signedHeaderNames[] = $header->getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$bodyHash, $bodyLength] = $this->hashBody($message->getBody(), $options['body_canon'], $options['body_max_length']);
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'v' => '1',
|
||||||
|
'q' => 'dns/txt',
|
||||||
|
'a' => $options['algorithm'],
|
||||||
|
'bh' => base64_encode($bodyHash),
|
||||||
|
'd' => $this->domainName,
|
||||||
|
'h' => implode(': ', $signedHeaderNames),
|
||||||
|
'i' => '@'.$this->domainName,
|
||||||
|
's' => $this->selector,
|
||||||
|
't' => time(),
|
||||||
|
'c' => $options['header_canon'].'/'.$options['body_canon'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($options['body_show_length']) {
|
||||||
|
$params['l'] = $bodyLength;
|
||||||
|
}
|
||||||
|
if ($options['signature_expiration_delay']) {
|
||||||
|
$params['x'] = $params['t'] + $options['signature_expiration_delay'];
|
||||||
|
}
|
||||||
|
$value = '';
|
||||||
|
foreach ($params as $k => $v) {
|
||||||
|
$value .= $k.'='.$v.'; ';
|
||||||
|
}
|
||||||
|
$value = trim($value);
|
||||||
|
$header = new UnstructuredHeader('DKIM-Signature', $value);
|
||||||
|
$headerCanonData .= rtrim($this->canonicalizeHeader($header->toString()."\r\n b=", $options['header_canon']));
|
||||||
|
if (self::ALGO_SHA256 === $options['algorithm']) {
|
||||||
|
if (!openssl_sign($headerCanonData, $signature, $this->key, OPENSSL_ALGO_SHA256)) {
|
||||||
|
throw new RuntimeException('Unable to sign DKIM hash: '.openssl_error_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException(sprintf('The "%s" DKIM signing algorithm is not supported yet.', self::ALGO_ED25519));
|
||||||
|
}
|
||||||
|
$header->setValue($value.' b='.trim(chunk_split(base64_encode($signature), 73, ' ')));
|
||||||
|
$headers->add($header);
|
||||||
|
|
||||||
|
return new Message($headers, $message->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canonicalizeHeader(string $header, string $headerCanon): string
|
||||||
|
{
|
||||||
|
if (self::CANON_RELAXED !== $headerCanon) {
|
||||||
|
return $header."\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$exploded = explode(':', $header, 2);
|
||||||
|
$name = strtolower(trim($exploded[0]));
|
||||||
|
$value = str_replace("\r\n", '', $exploded[1]);
|
||||||
|
$value = trim(preg_replace("/[ \t][ \t]+/", ' ', $value));
|
||||||
|
|
||||||
|
return $name.':'.$value."\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hashBody(AbstractPart $body, string $bodyCanon, int $maxLength): array
|
||||||
|
{
|
||||||
|
$hash = hash_init('sha256');
|
||||||
|
$relaxed = self::CANON_RELAXED === $bodyCanon;
|
||||||
|
$currentLine = '';
|
||||||
|
$emptyCounter = 0;
|
||||||
|
$isSpaceSequence = false;
|
||||||
|
$length = 0;
|
||||||
|
foreach ($body->bodyToIterable() as $chunk) {
|
||||||
|
$canon = '';
|
||||||
|
for ($i = 0, $len = \strlen($chunk); $i < $len; ++$i) {
|
||||||
|
switch ($chunk[$i]) {
|
||||||
|
case "\r":
|
||||||
|
break;
|
||||||
|
case "\n":
|
||||||
|
// previous char is always \r
|
||||||
|
if ($relaxed) {
|
||||||
|
$isSpaceSequence = false;
|
||||||
|
}
|
||||||
|
if ('' === $currentLine) {
|
||||||
|
++$emptyCounter;
|
||||||
|
} else {
|
||||||
|
$currentLine = '';
|
||||||
|
$canon .= "\r\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case "\t":
|
||||||
|
if ($relaxed) {
|
||||||
|
$isSpaceSequence = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// no break
|
||||||
|
default:
|
||||||
|
if ($emptyCounter > 0) {
|
||||||
|
$canon .= str_repeat("\r\n", $emptyCounter);
|
||||||
|
$emptyCounter = 0;
|
||||||
|
}
|
||||||
|
if ($isSpaceSequence) {
|
||||||
|
$currentLine .= ' ';
|
||||||
|
$canon .= ' ';
|
||||||
|
$isSpaceSequence = false;
|
||||||
|
}
|
||||||
|
$currentLine .= $chunk[$i];
|
||||||
|
$canon .= $chunk[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($length + \strlen($canon) >= $maxLength) {
|
||||||
|
$canon = substr($canon, 0, $maxLength - $length);
|
||||||
|
$length += \strlen($canon);
|
||||||
|
hash_update($hash, $canon);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$length += \strlen($canon);
|
||||||
|
hash_update($hash, $canon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $length) {
|
||||||
|
hash_update($hash, "\r\n");
|
||||||
|
$length = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [hash_final($hash, true), $length];
|
||||||
|
}
|
||||||
|
}
|
@ -80,7 +80,9 @@ class Message extends RawMessage
|
|||||||
$headers->addMailboxListHeader('From', [$headers->get('Sender')->getAddress()]);
|
$headers->addMailboxListHeader('From', [$headers->get('Sender')->getAddress()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$headers->has('MIME-Version')) {
|
||||||
$headers->addTextHeader('MIME-Version', '1.0');
|
$headers->addTextHeader('MIME-Version', '1.0');
|
||||||
|
}
|
||||||
|
|
||||||
if (!$headers->has('Date')) {
|
if (!$headers->has('Date')) {
|
||||||
$headers->addDateHeader('Date', new \DateTimeImmutable());
|
$headers->addDateHeader('Date', new \DateTimeImmutable());
|
||||||
|
164
src/Symfony/Component/Mime/Tests/Crypto/DkimSignerTest.php
Normal file
164
src/Symfony/Component/Mime/Tests/Crypto/DkimSignerTest.php
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<?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\Mime\Tests\Crypto;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bridge\PhpUnit\ClockMock;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\Mime\Crypto\DkimSigner;
|
||||||
|
use Symfony\Component\Mime\Email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group time-sensitive
|
||||||
|
* @requires extension openssl
|
||||||
|
*/
|
||||||
|
class DkimSignerTest extends TestCase
|
||||||
|
{
|
||||||
|
private static $pk = <<<EOF
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQC6lQYNOMaboSOE/c2KNl8Rwk61zoMXrEmXC926an3/jHrtj9wB
|
||||||
|
ndP2DY2nUyz0vpmJlcDOjDwTGs8U/C7zn7PDdZ8EuuxlAa7oNo/38YYV+5Oki93m
|
||||||
|
io6rGV8zLMGLLygAB1sJaJVP5W9wm0RLY776YFL4V/nekA5ZTnA4+KaIYwIDAQAB
|
||||||
|
AoGAJLhjgoKkA8kI1omkxAjDWRlmqD1Ga4hKy2FYd/GxbnPVVZ+0atUG/Cvarw2d
|
||||||
|
kWVZjkxcr8nFoPTrwHOJQgUyOXWLuIuirznoTtDKzC+4JlDsZJd8hkVohqwKfdPA
|
||||||
|
v4iYceN6V0YRQpsLVwKJinr5k6oHpCGs3sNffpHQzrXc24ECQQDb0JLiMm5OZoYZ
|
||||||
|
G3739DsYVycUmYmYJtXuUBHTIwBAaOyo0yEmeQ8Li4H5dSSWqeOO0XrfP7cQ3TOm
|
||||||
|
6LuSrIXDAkEA2Uv2PuteQXGSzOEuQbDbYeR0Le0drDUFJkXBM4oS3XB3wx2+umD+
|
||||||
|
WqpfLEIXWV3/hkuottTmlsQuuAP3Xv+o4QJAf5FyTRfbcGCLnnKYoyn4Sc36fjgE
|
||||||
|
5GpVaXLKhXAgq0C5Z9jvujYzhw21pqJXU6DQ0Ye8+WcuxPi7Czix8xNwpQJBAMm1
|
||||||
|
vexCSMivSPpuvaW1KrEAhOhtB/JndVRFxEa3kTOFx2aUIgyZJQO8y4QmBc6rdxuO
|
||||||
|
+BpgH30st8GRzPuej4ECQAsLon/QgsyhkfquBMLDC1uhO027K59C/aYRlufPyHkq
|
||||||
|
HIyrMg2pQ46h2ybEuB50Cs+xF19KwBuGafBtRjkvXdU=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
EOF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getSignData
|
||||||
|
*/
|
||||||
|
public function testSign(int $time, string $bodyCanon, string $headerCanon, string $header)
|
||||||
|
{
|
||||||
|
ClockMock::withClockMock($time);
|
||||||
|
|
||||||
|
$message = (new Email())
|
||||||
|
->from(new Address('fabien@testdkim.symfony.net', 'Fabién'))
|
||||||
|
->to('fabien.potencier@gmail.com')
|
||||||
|
->subject('Tést')
|
||||||
|
->text("Some body \n \n This \r\n\r\n is really interesting and at the same time very long line to see if everything works as expected, does it?\r\n\r\n\r\n\r\n")
|
||||||
|
->date(new \DateTimeImmutable('2005-10-15', new \DateTimeZone('Europe/Paris')));
|
||||||
|
|
||||||
|
$signer = new DkimSigner(self::$pk, 'testdkim.symfony.net', 'sf');
|
||||||
|
$signedMessage = $signer->sign($message, [
|
||||||
|
'header_canon' => $headerCanon,
|
||||||
|
'body_canon' => $bodyCanon,
|
||||||
|
'headers_to_ignore' => ['Message-ID'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame($message->getBody()->toString(), $signedMessage->getBody()->toString());
|
||||||
|
$this->assertTrue($signedMessage->getHeaders()->has('DKIM-Signature'));
|
||||||
|
$this->assertEquals($header, $signedMessage->getHeaders()->get('DKIM-Signature')->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignData()
|
||||||
|
{
|
||||||
|
yield 'simple/simple' => [
|
||||||
|
1591597074, DkimSigner::CANON_SIMPLE, DkimSigner::CANON_SIMPLE,
|
||||||
|
'v=1; q=dns/txt; a=rsa-sha256; bh=JC6qmm3afMaxL3Rm1YHxrzIpqiUuB7aAarWMcZfuca4=; d=testdkim.symfony.net; h=From: To: Subject: Date: MIME-Version; i=@testdkim.symfony.net; s=sf; t=1591597074; c=simple/simple; b=Z+KvV7QwQ7gdTy49sOzT1c+UDZbT8nFUClbiW8cCKtj4HVuIxGUgWMSN46CX8GoYd0rIsoutF +Cgc4rcp/AU9tgLswliYh66Gk5gR6tA0h13FBVFuWeWz7PiMK5s8nLymMmiKDM0GNjshy4cdD VnQdREINJOD7yycmRDPT0Q828=',
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'relaxed/simple' => [
|
||||||
|
1591597424, DkimSigner::CANON_RELAXED, DkimSigner::CANON_SIMPLE,
|
||||||
|
'v=1; q=dns/txt; a=rsa-sha256; bh=JC6qmm3afMaxL3Rm1YHxrzIpqiUuB7aAarWMcZfuca4=; d=testdkim.symfony.net; h=From: To: Subject: Date: MIME-Version; i=@testdkim.symfony.net; s=sf; t=1591597424; c=simple/relaxed; b=F52zm1Pg6VKb0g6ySZ6KcFxC2jlnUVkXb2OjptChUXsJBM83n1Gk48D2ipbP2L+UkKXvKl6YI BdMxkde0Tpw0hTxDJdM5xekacqWZbyC0y8wE5Ks635aDagdV+WfJ3m6l3grb+Ng+qqetEWZpP 3vRRBd8qDn9IUgoPxDJ6MpIMs=',
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'relaxed/relaxed' => [
|
||||||
|
1591597493, DkimSigner::CANON_RELAXED, DkimSigner::CANON_RELAXED,
|
||||||
|
'v=1; q=dns/txt; a=rsa-sha256; bh=JC6qmm3afMaxL3Rm1YHxrzIpqiUuB7aAarWMcZfuca4=; d=testdkim.symfony.net; h=From: To: Subject: Date: MIME-Version; i=@testdkim.symfony.net; s=sf; t=1591597493; c=relaxed/relaxed; b=sINllavShGfnMXymubjBflrAlRlv3zGTP/ZbI2XlFqu5G7Bvb0jFReKkgUo/Swezt50w4WqxP 3zNv4W1uilomtgqjihf4WJRi/wMnVjCt8KZ8z3AXrDK+udcXln6OCLw63CrV4FpdOfYyQUQBq NaizUh+k7y1dvqxMJTaAp2POY=',
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'simple/relaxed' => [
|
||||||
|
1591597612, DkimSigner::CANON_SIMPLE, DkimSigner::CANON_RELAXED,
|
||||||
|
'v=1; q=dns/txt; a=rsa-sha256; bh=JC6qmm3afMaxL3Rm1YHxrzIpqiUuB7aAarWMcZfuca4=; d=testdkim.symfony.net; h=From: To: Subject: Date: MIME-Version; i=@testdkim.symfony.net; s=sf; t=1591597612; c=relaxed/simple; b=E+BszWWfYJfrWXk5uggwZJmLlh+4IeVScnJhqAj0G4h0dhqRZ0Qs1XNPSS0IZtPSTUgNxAeTi mc8jjVCnrROPnYnaomvgTdkxwRU5ZcA4felmGjcXODrdy9GUAokES6qjy4bVwBvaHxMgr00eP J3sJqBBwcg/HsO52ppJma/1HM=',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getCanonicalizeHeaderData
|
||||||
|
*/
|
||||||
|
public function testCanonicalizeHeader(string $bodyCanon, string $canonBody, string $body, int $maxLength)
|
||||||
|
{
|
||||||
|
$message = (new Email())
|
||||||
|
->from(new Address('fabien@testdkim.symfony.net', 'Fabién'))
|
||||||
|
->to('fabien.potencier@gmail.com')
|
||||||
|
->subject('Tést')
|
||||||
|
->text($body)
|
||||||
|
;
|
||||||
|
|
||||||
|
$signer = new DkimSigner(self::$pk, 'testdkim.symfony.net', 'sf');
|
||||||
|
$signedMessage = $signer->sign($message, [
|
||||||
|
'body_canon' => $bodyCanon,
|
||||||
|
'body_max_length' => $maxLength,
|
||||||
|
'body_show_length' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
preg_match('{bh=([^;]+).+l=([^;]+)}', $signedMessage->getHeaders()->get('DKIM-Signature')->getBody(), $matches);
|
||||||
|
$bh = $matches[1];
|
||||||
|
$l = $matches[2];
|
||||||
|
$this->assertEquals(base64_encode(hash('sha256', $canonBody, true)), $bh);
|
||||||
|
$this->assertEquals(\strlen($canonBody), $l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCanonicalizeHeaderData()
|
||||||
|
{
|
||||||
|
yield 'simple_empty' => [
|
||||||
|
DkimSigner::CANON_SIMPLE, "\r\n", '', PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
yield 'relaxed_empty' => [
|
||||||
|
DkimSigner::CANON_RELAXED, "\r\n", '', PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'simple_empty_single_ending_CLRF' => [
|
||||||
|
DkimSigner::CANON_SIMPLE, "\r\n", "\r\n", PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
yield 'relaxed_empty_single_ending_CLRF' => [
|
||||||
|
DkimSigner::CANON_RELAXED, "\r\n", "\r\n", PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'simple_multiple_ending_CLRF' => [
|
||||||
|
DkimSigner::CANON_SIMPLE, "Some body\r\n", "Some body\r\n\r\n\r\n\r\n\r\n\r\n", PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
yield 'relaxed_multiple_ending_CLRF' => [
|
||||||
|
DkimSigner::CANON_RELAXED, "Some body\r\n", "Some body\r\n\r\n\r\n\r\n\r\n\r\n", PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'simple_basic' => [
|
||||||
|
DkimSigner::CANON_SIMPLE, "Some body\r\n", "Some body\r\n", PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
yield 'relaxed_basic' => [
|
||||||
|
DkimSigner::CANON_RELAXED, "Some body\r\n", "Some body\r\n", PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
|
||||||
|
$body = "Some body with whitespaces\r\n";
|
||||||
|
yield 'simple_with_many_inline_whitespaces' => [
|
||||||
|
DkimSigner::CANON_SIMPLE, $body, $body, PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
yield 'relaxed_with_many_inline_whitespaces' => [
|
||||||
|
DkimSigner::CANON_RELAXED, "Some body with whitespaces\r\n", $body, PHP_INT_MAX,
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'simple_basic_with_length' => [
|
||||||
|
DkimSigner::CANON_SIMPLE, 'Some b', "Some body\r\n", 6,
|
||||||
|
];
|
||||||
|
yield 'relaxed_basic_with_length' => [
|
||||||
|
DkimSigner::CANON_RELAXED, 'Some b', "Some body\r\n", 6,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user