Skip to content

Commit

Permalink
Add flag for using copiedHeaderFields and config for using extra head…
Browse files Browse the repository at this point in the history
…ers in DKIM signature (PHPMailer#1468)

* Changed in DKIM signature

- Add flag for using copiedHeaderFields
- config for using extra headers in DKIM signature

* Cleanups after Review

- Don't use masked ampersands in url
- Remove test related artifacts after run

* Complete docs
  • Loading branch information
gwi-mmuths authored and Synchro committed Jun 26, 2018
1 parent 46869d4 commit 5c9d3c5
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 13 deletions.
4 changes: 4 additions & 0 deletions examples/DKIM_sign.phps
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ $mail->DKIM_selector = 'phpmailer';
$mail->DKIM_passphrase = '';
//The identity you're signing as - usually your From address
$mail->DKIM_identity = $mail->From;
//Suppress listing signed header fields in signature, defaults to true for debugging purpose
$this->mailer->DKIM_copyHeaderFields = false;
//Optionally you can add extra headers for signing to meet special requirements
$this->mailer->DKIM_extraHeaders = ['List-Unsubscribe', 'List-Help'];

//When you send, the DKIM settings will be used to sign the message
if (!$mail->send()) {
Expand Down
72 changes: 59 additions & 13 deletions src/PHPMailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,22 @@ class PHPMailer
*/
public $DKIM_domain = '';

/**
* DKIM Copy header field values for diagnostic use.
*
* @var bool
*/
public $DKIM_copyHeaderFields = true;

/**
* DKIM Extra signing headers.
*
* @example ['List-Unsubscribe', 'List-Help']
*
* @var array
*/
public $DKIM_extraHeaders = [];

/**
* DKIM private key file path.
*
Expand Down Expand Up @@ -4266,6 +4282,11 @@ public function DKIM_Add($headers_line, $subject, $body)
$to_header = '';
$date_header = '';
$current = '';
$copiedHeaderFields = '';
$foundExtraHeaders = [];
$extraHeaderKeys = '';
$extraHeaderValues = '';
$extraCopyHeaderFields = '';
foreach ($headers as $header) {
if (strpos($header, 'From:') === 0) {
$from_header = $header;
Expand All @@ -4276,6 +4297,23 @@ public function DKIM_Add($headers_line, $subject, $body)
} elseif (strpos($header, 'Date:') === 0) {
$date_header = $header;
$current = 'date_header';
} elseif (!empty($this->DKIM_extraHeaders)) {
foreach ($this->DKIM_extraHeaders as $extraHeader) {
if (strpos($header, $extraHeader . ':') === 0) {
$headerValue = $header;
foreach ($this->CustomHeader as $customHeader) {
if ($customHeader[0] === $extraHeader) {
$headerValue = trim($customHeader[0]) .
': ' .
$this->encodeHeader(trim($customHeader[1]));
break;
}
}
$foundExtraHeaders[$extraHeader] = $headerValue;
$current = '';
break;
}
}
} else {
if (!empty($$current) and strpos($header, ' =?') === 0) {
$$current .= $header;
Expand All @@ -4284,14 +4322,24 @@ public function DKIM_Add($headers_line, $subject, $body)
}
}
}
$from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
$to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
$date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
$subject = str_replace(
'|',
'=7C',
$this->DKIM_QP($subject_header)
); // Copied header fields (dkim-quoted-printable)
foreach ($foundExtraHeaders as $key => $value) {
$extraHeaderKeys .= ':' . $key;
$extraHeaderValues .= $value . "\r\n";
if ($this->DKIM_copyHeaderFields) {
$extraCopyHeaderFields .= "\t|" . str_replace('|', '=7C', $this->DKIM_QP($value)) . ";\r\n";
}
}
if ($this->DKIM_copyHeaderFields) {
$from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
$to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
$date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
$subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header));
$copiedHeaderFields = "\tz=$from\r\n" .
"\t|$to\r\n" .
"\t|$date\r\n" .
"\t|$subject;\r\n" .
$extraCopyHeaderFields;
}
$body = $this->DKIM_BodyC($body);
$DKIMlen = strlen($body); // Length of body
$DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
Expand All @@ -4307,19 +4355,17 @@ public function DKIM_Add($headers_line, $subject, $body)
$this->DKIM_selector .
";\r\n" .
"\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
"\th=From:To:Date:Subject;\r\n" .
"\th=From:To:Date:Subject" . $extraHeaderKeys . ";\r\n" .
"\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
"\tz=$from\r\n" .
"\t|$to\r\n" .
"\t|$date\r\n" .
"\t|$subject;\r\n" .
$copiedHeaderFields .
"\tbh=" . $DKIMb64 . ";\r\n" .
"\tb=";
$toSign = $this->DKIM_HeaderC(
$from_header . "\r\n" .
$to_header . "\r\n" .
$date_header . "\r\n" .
$subject_header . "\r\n" .
$extraHeaderValues .
$dkimhdrs
);
$signed = $this->DKIM_Sign($toSign);
Expand Down
90 changes: 90 additions & 0 deletions test/PHPMailerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,96 @@ public function testDKIMHeaderCanonicalization()
);
}

/**
* DKIM copied header fields tests.
*
* @group dkim
*
* @see https://tools.ietf.org/html/rfc6376#section-3.5
*/
public function testDKIMOptionalHeaderFieldsCopy()
{
$privatekeyfile = 'dkim_private.pem';
$pk = openssl_pkey_new(
[
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]
);
openssl_pkey_export_to_file($pk, $privatekeyfile);
$this->Mail->DKIM_private = 'dkim_private.pem';

//Example from https://tools.ietf.org/html/rfc6376#section-3.5
$from = '[email protected]';
$to = '[email protected]';
$date = 'date';
$subject = 'example';

$headerLines = "From:$from\r\nTo:$to\r\nDate:$date\r\n";
$copyHeaderFields = "\tz=From:$from\r\n\t|To:$to\r\n\t|Date:$date\r\n\t|Subject:=20$subject;\r\n";

$this->Mail->DKIM_copyHeaderFields = true;
$this->assertContains(
$copyHeaderFields,
$this->Mail->DKIM_Add($headerLines, $subject, ''),
'DKIM header with copied header fields incorrect'
);

$this->Mail->DKIM_copyHeaderFields = false;
$this->assertNotContains(
$copyHeaderFields,
$this->Mail->DKIM_Add($headerLines, $subject, ''),
'DKIM header without copied header fields incorrect'
);

unlink($privatekeyfile);
}

/**
* DKIM signing extra headers tests.
*
* @group dkim
*/
public function testDKIMExtraHeaders()
{
$privatekeyfile = 'dkim_private.pem';
$pk = openssl_pkey_new(
[
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]
);
openssl_pkey_export_to_file($pk, $privatekeyfile);
$this->Mail->DKIM_private = 'dkim_private.pem';

//Example from https://tools.ietf.org/html/rfc6376#section-3.5
$from = '[email protected]';
$to = '[email protected]';
$date = 'date';
$subject = 'example';
$anyHeader = 'foo';
$unsubscribeUrl = '<https://www.example.com/unsubscribe/?newsletterId=anytoken&amp;actionToken=anyToken' .
'&otherParam=otherValue&anotherParam=anotherVeryVeryVeryLongValue>';

$this->Mail->addCustomHeader('X-AnyHeader', $anyHeader);
$this->Mail->addCustomHeader('Baz', 'bar');
$this->Mail->addCustomHeader('List-Unsubscribe', $unsubscribeUrl);

$this->Mail->DKIM_extraHeaders = ['Baz', 'List-Unsubscribe'];

$headerLines = "From:$from\r\nTo:$to\r\nDate:$date\r\n";
$headerLines .= "X-AnyHeader:$anyHeader\r\nBaz:bar\r\n";
$headerLines .= 'List-Unsubscribe:' . $this->Mail->encodeHeader($unsubscribeUrl) . "\r\n";

$headerFields = 'h=From:To:Date:Subject:Baz:List-Unsubscribe';

$result = $this->Mail->DKIM_Add($headerLines, $subject, '');

$this->assertContains($headerFields, $result, 'DKIM header with extra headers incorrect');

unlink($privatekeyfile);
}

/**
* DKIM Signing tests.
*
Expand Down

0 comments on commit 5c9d3c5

Please sign in to comment.