TYPO3  7.6
DKIMSigner.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of SwiftMailer.
5  * (c) 2004-2009 Chris Corbyn
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10 
17 {
23  protected $_privateKey;
24 
30  protected $_domainName;
31 
37  protected $_selector;
38 
44  protected $_hashAlgorithm = 'rsa-sha1';
45 
51  protected $_bodyCanon = 'simple';
52 
58  protected $_headerCanon = 'simple';
59 
65  protected $_ignoredHeaders = array();
66 
72  protected $_signerIdentity;
73 
79  protected $_bodyLen = 0;
80 
86  protected $_maxLen = PHP_INT_MAX;
87 
93  protected $_showLen = false;
94 
100  protected $_signatureTimestamp = true;
101 
108  protected $_signatureExpiration = false;
109 
115  protected $_debugHeaders = false;
116 
117  // work variables
123  protected $_signedHeaders = array();
124 
130  private $_debugHeadersData = '';
131 
137  private $_bodyHash = '';
138 
144  protected $_dkimHeader;
145 
152 
154 
155  private $_headerHash;
156 
157  private $_headerCanonData = '';
158 
160 
162 
163  private $_bodyCanonSpace = false;
164 
165  private $_bodyCanonLastChar = null;
166 
167  private $_bodyCanonLine = '';
168 
169  private $_bound = array();
170 
178  public function __construct($privateKey, $domainName, $selector)
179  {
180  $this->_privateKey = $privateKey;
181  $this->_domainName = $domainName;
182  $this->_signerIdentity = '@'.$domainName;
183  $this->_selector = $selector;
184  }
185 
195  public static function newInstance($privateKey, $domainName, $selector)
196  {
197  return new static($privateKey, $domainName, $selector);
198  }
199 
205  public function reset()
206  {
207  $this->_headerHash = null;
208  $this->_signedHeaders = array();
209  $this->_headerHashHandler = null;
210  $this->_bodyHash = null;
211  $this->_bodyHashHandler = null;
212  $this->_bodyCanonIgnoreStart = 2;
213  $this->_bodyCanonEmptyCounter = 0;
214  $this->_bodyCanonLastChar = null;
215  $this->_bodyCanonSpace = false;
216  }
217 
234  public function write($bytes)
235  {
236  $this->_canonicalizeBody($bytes);
237  foreach ($this->_bound as $is) {
238  $is->write($bytes);
239  }
240  }
241 
248  public function commit()
249  {
250  // Nothing to do
251  return;
252  }
253 
261  public function bind(Swift_InputByteStream $is)
262  {
263  // Don't have to mirror anything
264  $this->_bound[] = $is;
265 
266  return;
267  }
268 
277  public function unbind(Swift_InputByteStream $is)
278  {
279  // Don't have to mirror anything
280  foreach ($this->_bound as $k => $stream) {
281  if ($stream === $is) {
282  unset($this->_bound[$k]);
283 
284  return;
285  }
286  }
287 
288  return;
289  }
290 
297  public function flushBuffers()
298  {
299  $this->reset();
300  }
301 
309  public function setHashAlgorithm($hash)
310  {
311  // Unable to sign with rsa-sha256
312  if ($hash == 'rsa-sha1') {
313  $this->_hashAlgorithm = 'rsa-sha1';
314  } else {
315  $this->_hashAlgorithm = 'rsa-sha256';
316  }
317 
318  return $this;
319  }
320 
328  public function setBodyCanon($canon)
329  {
330  if ($canon == 'relaxed') {
331  $this->_bodyCanon = 'relaxed';
332  } else {
333  $this->_bodyCanon = 'simple';
334  }
335 
336  return $this;
337  }
338 
346  public function setHeaderCanon($canon)
347  {
348  if ($canon == 'relaxed') {
349  $this->_headerCanon = 'relaxed';
350  } else {
351  $this->_headerCanon = 'simple';
352  }
353 
354  return $this;
355  }
356 
364  public function setSignerIdentity($identity)
365  {
366  $this->_signerIdentity = $identity;
367 
368  return $this;
369  }
370 
378  public function setBodySignedLen($len)
379  {
380  if ($len === true) {
381  $this->_showLen = true;
382  $this->_maxLen = PHP_INT_MAX;
383  } elseif ($len === false) {
384  $this->showLen = false;
385  $this->_maxLen = PHP_INT_MAX;
386  } else {
387  $this->_showLen = true;
388  $this->_maxLen = (int) $len;
389  }
390 
391  return $this;
392  }
393 
401  public function setSignatureTimestamp($time)
402  {
403  $this->_signatureTimestamp = $time;
404 
405  return $this;
406  }
407 
415  public function setSignatureExpiration($time)
416  {
417  $this->_signatureExpiration = $time;
418 
419  return $this;
420  }
421 
429  public function setDebugHeaders($debug)
430  {
431  $this->_debugHeaders = (bool) $debug;
432 
433  return $this;
434  }
435 
439  public function startBody()
440  {
441  // Init
442  switch ($this->_hashAlgorithm) {
443  case 'rsa-sha256' :
444  $this->_bodyHashHandler = hash_init('sha256');
445  break;
446  case 'rsa-sha1' :
447  $this->_bodyHashHandler = hash_init('sha1');
448  break;
449  }
450  $this->_bodyCanonLine = '';
451  }
452 
456  public function endBody()
457  {
458  $this->_endOfBody();
459  }
460 
466  public function getAlteredHeaders()
467  {
468  if ($this->_debugHeaders) {
469  return array('DKIM-Signature', 'X-DebugHash');
470  } else {
471  return array('DKIM-Signature');
472  }
473  }
474 
482  public function ignoreHeader($header_name)
483  {
484  $this->_ignoredHeaders[strtolower($header_name)] = true;
485 
486  return $this;
487  }
488 
496  public function setHeaders(Swift_Mime_HeaderSet $headers)
497  {
498  $this->_headerCanonData = '';
499  // Loop through Headers
500  $listHeaders = $headers->listAll();
501  foreach ($listHeaders as $hName) {
502  // Check if we need to ignore Header
503  if (!isset($this->_ignoredHeaders[strtolower($hName)])) {
504  if ($headers->has($hName)) {
505  $tmp = $headers->getAll($hName);
506  foreach ($tmp as $header) {
507  if ($header->getFieldBody() != '') {
508  $this->_addHeader($header->toString());
509  $this->_signedHeaders[] = $header->getFieldName();
510  }
511  }
512  }
513  }
514  }
515 
516  return $this;
517  }
518 
526  public function addSignature(Swift_Mime_HeaderSet $headers)
527  {
528  // Prepare the DKIM-Signature
529  $params = array('v' => '1', 'a' => $this->_hashAlgorithm, 'bh' => base64_encode($this->_bodyHash), 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'i' => $this->_signerIdentity, 's' => $this->_selector);
530  if ($this->_bodyCanon != 'simple') {
531  $params['c'] = $this->_headerCanon.'/'.$this->_bodyCanon;
532  } elseif ($this->_headerCanon != 'simple') {
533  $params['c'] = $this->_headerCanon;
534  }
535  if ($this->_showLen) {
536  $params['l'] = $this->_bodyLen;
537  }
538  if ($this->_signatureTimestamp === true) {
539  $params['t'] = time();
540  if ($this->_signatureExpiration !== false) {
541  $params['x'] = $params['t'] + $this->_signatureExpiration;
542  }
543  } else {
544  if ($this->_signatureTimestamp !== false) {
545  $params['t'] = $this->_signatureTimestamp;
546  }
547  if ($this->_signatureExpiration !== false) {
548  $params['x'] = $this->_signatureExpiration;
549  }
550  }
551  if ($this->_debugHeaders) {
552  $params['z'] = implode('|', $this->_debugHeadersData);
553  }
554  $string = '';
555  foreach ($params as $k => $v) {
556  $string .= $k.'='.$v.'; ';
557  }
558  $string = trim($string);
559  $headers->addTextHeader('DKIM-Signature', $string);
560  // Add the last DKIM-Signature
561  $tmp = $headers->getAll('DKIM-Signature');
562  $this->_dkimHeader = end($tmp);
563  $this->_addHeader(trim($this->_dkimHeader->toString())."\r\n b=", true);
564  $this->_endOfHeaders();
565  if ($this->_debugHeaders) {
566  $headers->addTextHeader('X-DebugHash', base64_encode($this->_headerHash));
567  }
568  $this->_dkimHeader->setValue($string.' b='.trim(chunk_split(base64_encode($this->_getEncryptedHash()), 73, ' ')));
569 
570  return $this;
571  }
572 
573  /* Private helpers */
574 
575  protected function _addHeader($header, $is_sig = false)
576  {
577  switch ($this->_headerCanon) {
578  case 'relaxed' :
579  // Prepare Header and cascade
580  $exploded = explode(':', $header, 2);
581  $name = strtolower(trim($exploded[0]));
582  $value = str_replace("\r\n", '', $exploded[1]);
583  $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
584  $header = $name.':'.trim($value).($is_sig ? '' : "\r\n");
585  case 'simple' :
586  // Nothing to do
587  }
588  $this->_addToHeaderHash($header);
589  }
590 
591  protected function _endOfHeaders()
592  {
593  //$this->_headerHash=hash_final($this->_headerHashHandler, true);
594  }
595 
596  protected function _canonicalizeBody($string)
597  {
598  $len = strlen($string);
599  $canon = '';
600  $method = ($this->_bodyCanon == 'relaxed');
601  for ($i = 0; $i < $len; ++$i) {
602  if ($this->_bodyCanonIgnoreStart > 0) {
604  continue;
605  }
606  switch ($string[$i]) {
607  case "\r" :
608  $this->_bodyCanonLastChar = "\r";
609  break;
610  case "\n" :
611  if ($this->_bodyCanonLastChar == "\r") {
612  if ($method) {
613  $this->_bodyCanonSpace = false;
614  }
615  if ($this->_bodyCanonLine == '') {
617  } else {
618  $this->_bodyCanonLine = '';
619  $canon .= "\r\n";
620  }
621  } else {
622  // Wooops Error
623  // todo handle it but should never happen
624  }
625  break;
626  case ' ' :
627  case "\t" :
628  if ($method) {
629  $this->_bodyCanonSpace = true;
630  break;
631  }
632  default :
633  if ($this->_bodyCanonEmptyCounter > 0) {
634  $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter);
635  $this->_bodyCanonEmptyCounter = 0;
636  }
637  if ($this->_bodyCanonSpace) {
638  $this->_bodyCanonLine .= ' ';
639  $canon .= ' ';
640  $this->_bodyCanonSpace = false;
641  }
642  $this->_bodyCanonLine .= $string[$i];
643  $canon .= $string[$i];
644  }
645  }
646  $this->_addToBodyHash($canon);
647  }
648 
649  protected function _endOfBody()
650  {
651  // Add trailing Line return if last line is non empty
652  if (strlen($this->_bodyCanonLine) > 0) {
653  $this->_addToBodyHash("\r\n");
654  }
655  $this->_bodyHash = hash_final($this->_bodyHashHandler, true);
656  }
657 
658  private function _addToBodyHash($string)
659  {
660  $len = strlen($string);
661  if ($len > ($new_len = ($this->_maxLen - $this->_bodyLen))) {
662  $string = substr($string, 0, $new_len);
663  $len = $new_len;
664  }
665  hash_update($this->_bodyHashHandler, $string);
666  $this->_bodyLen += $len;
667  }
668 
669  private function _addToHeaderHash($header)
670  {
671  if ($this->_debugHeaders) {
672  $this->_debugHeadersData[] = trim($header);
673  }
674  $this->_headerCanonData .= $header;
675  }
676 
682  private function _getEncryptedHash()
683  {
684  $signature = '';
685  switch ($this->_hashAlgorithm) {
686  case 'rsa-sha1':
687  $algorithm = OPENSSL_ALGO_SHA1;
688  break;
689  case 'rsa-sha256':
690  $algorithm = OPENSSL_ALGO_SHA256;
691  break;
692  }
693  $pkeyId = openssl_get_privatekey($this->_privateKey);
694  if (!$pkeyId) {
695  throw new Swift_SwiftException('Unable to load DKIM Private Key ['.openssl_error_string().']');
696  }
697  if (openssl_sign($this->_headerCanonData, $signature, $pkeyId, $algorithm)) {
698  return $signature;
699  }
700  throw new Swift_SwiftException('Unable to sign DKIM Hash ['.openssl_error_string().']');
701  }
702 }