[ Index ]

MailPress 7.2

[ Index ]     [ Classes ]     [ Functions ]     [ Variables ]     [ Constants ]     [ Statistics ]    

title

Body

[close]

/mp-includes/composer/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/ -> DKIMSigner.php (source)

   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  
  11  /**
  12   * DKIM Signer used to apply DKIM Signature to a message.
  13   *
  14   * @author     Xavier De Cock <xdecock@gmail.com>
  15   */
  16  class Swift_Signers_DKIMSigner implements Swift_Signers_HeaderSigner
  17  {
  18      /**
  19       * PrivateKey.
  20       *
  21       * @var string
  22       */
  23      protected $privateKey;
  24  
  25      /**
  26       * DomainName.
  27       *
  28       * @var string
  29       */
  30      protected $domainName;
  31  
  32      /**
  33       * Selector.
  34       *
  35       * @var string
  36       */
  37      protected $selector;
  38  
  39      private $passphrase = '';
  40  
  41      /**
  42       * Hash algorithm used.
  43       *
  44       * @see RFC6376 3.3: Signers MUST implement and SHOULD sign using rsa-sha256.
  45       *
  46       * @var string
  47       */
  48      protected $hashAlgorithm = 'rsa-sha256';
  49  
  50      /**
  51       * Body canon method.
  52       *
  53       * @var string
  54       */
  55      protected $bodyCanon = 'simple';
  56  
  57      /**
  58       * Header canon method.
  59       *
  60       * @var string
  61       */
  62      protected $headerCanon = 'simple';
  63  
  64      /**
  65       * Headers not being signed.
  66       *
  67       * @var array
  68       */
  69      protected $ignoredHeaders = ['return-path' => true];
  70  
  71      /**
  72       * Signer identity.
  73       *
  74       * @var string
  75       */
  76      protected $signerIdentity;
  77  
  78      /**
  79       * BodyLength.
  80       *
  81       * @var int
  82       */
  83      protected $bodyLen = 0;
  84  
  85      /**
  86       * Maximum signedLen.
  87       *
  88       * @var int
  89       */
  90      protected $maxLen = PHP_INT_MAX;
  91  
  92      /**
  93       * Embbed bodyLen in signature.
  94       *
  95       * @var bool
  96       */
  97      protected $showLen = false;
  98  
  99      /**
 100       * When the signature has been applied (true means time()), false means not embedded.
 101       *
 102       * @var mixed
 103       */
 104      protected $signatureTimestamp = true;
 105  
 106      /**
 107       * When will the signature expires false means not embedded, if sigTimestamp is auto
 108       * Expiration is relative, otherwise it's absolute.
 109       *
 110       * @var int
 111       */
 112      protected $signatureExpiration = false;
 113  
 114      /**
 115       * Must we embed signed headers?
 116       *
 117       * @var bool
 118       */
 119      protected $debugHeaders = false;
 120  
 121      // work variables
 122      /**
 123       * Headers used to generate hash.
 124       *
 125       * @var array
 126       */
 127      protected $signedHeaders = [];
 128  
 129      /**
 130       * If debugHeaders is set store debugData here.
 131       *
 132       * @var array
 133       */
 134      private $debugHeadersData = [];
 135  
 136      /**
 137       * Stores the bodyHash.
 138       *
 139       * @var string
 140       */
 141      private $bodyHash = '';
 142  
 143      /**
 144       * Stores the signature header.
 145       *
 146       * @var Swift_Mime_Headers_ParameterizedHeader
 147       */
 148      protected $dkimHeader;
 149  
 150      private $bodyHashHandler;
 151  
 152      private $headerHash;
 153  
 154      private $headerCanonData = '';
 155  
 156      private $bodyCanonEmptyCounter = 0;
 157  
 158      private $bodyCanonIgnoreStart = 2;
 159  
 160      private $bodyCanonSpace = false;
 161  
 162      private $bodyCanonLastChar = null;
 163  
 164      private $bodyCanonLine = '';
 165  
 166      private $bound = [];
 167  
 168      /**
 169       * Constructor.
 170       *
 171       * @param string $privateKey
 172       * @param string $domainName
 173       * @param string $selector
 174       * @param string $passphrase
 175       */
 176      public function __construct($privateKey, $domainName, $selector, $passphrase = '')
 177      {
 178          $this->privateKey = $privateKey;
 179          $this->domainName = $domainName;
 180          $this->signerIdentity = '@'.$domainName;
 181          $this->selector = $selector;
 182          $this->passphrase = $passphrase;
 183      }
 184  
 185      /**
 186       * Reset the Signer.
 187       *
 188       * @see Swift_Signer::reset()
 189       */
 190      public function reset()
 191      {
 192          $this->headerHash = null;
 193          $this->signedHeaders = [];
 194          $this->bodyHash = null;
 195          $this->bodyHashHandler = null;
 196          $this->bodyCanonIgnoreStart = 2;
 197          $this->bodyCanonEmptyCounter = 0;
 198          $this->bodyCanonLastChar = null;
 199          $this->bodyCanonSpace = false;
 200      }
 201  
 202      /**
 203       * Writes $bytes to the end of the stream.
 204       *
 205       * Writing may not happen immediately if the stream chooses to buffer.  If
 206       * you want to write these bytes with immediate effect, call {@link commit()}
 207       * after calling write().
 208       *
 209       * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
 210       * second, etc etc).
 211       *
 212       * @param string $bytes
 213       *
 214       * @return int
 215       *
 216       * @throws Swift_IoException
 217       */
 218      // TODO fix return
 219      public function write($bytes)
 220      {
 221          $this->canonicalizeBody($bytes);
 222          foreach ($this->bound as $is) {
 223              $is->write($bytes);
 224          }
 225      }
 226  
 227      /**
 228       * For any bytes that are currently buffered inside the stream, force them
 229       * off the buffer.
 230       */
 231      public function commit()
 232      {
 233          // Nothing to do
 234          return;
 235      }
 236  
 237      /**
 238       * Attach $is to this stream.
 239       *
 240       * The stream acts as an observer, receiving all data that is written.
 241       * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
 242       */
 243      public function bind(Swift_InputByteStream $is)
 244      {
 245          // Don't have to mirror anything
 246          $this->bound[] = $is;
 247  
 248          return;
 249      }
 250  
 251      /**
 252       * Remove an already bound stream.
 253       *
 254       * If $is is not bound, no errors will be raised.
 255       * If the stream currently has any buffered data it will be written to $is
 256       * before unbinding occurs.
 257       */
 258      public function unbind(Swift_InputByteStream $is)
 259      {
 260          // Don't have to mirror anything
 261          foreach ($this->bound as $k => $stream) {
 262              if ($stream === $is) {
 263                  unset($this->bound[$k]);
 264  
 265                  return;
 266              }
 267          }
 268      }
 269  
 270      /**
 271       * Flush the contents of the stream (empty it) and set the internal pointer
 272       * to the beginning.
 273       *
 274       * @throws Swift_IoException
 275       */
 276      public function flushBuffers()
 277      {
 278          $this->reset();
 279      }
 280  
 281      /**
 282       * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1.
 283       *
 284       * @param string $hash 'rsa-sha1' or 'rsa-sha256'
 285       *
 286       * @throws Swift_SwiftException
 287       *
 288       * @return $this
 289       */
 290      public function setHashAlgorithm($hash)
 291      {
 292          switch ($hash) {
 293              case 'rsa-sha1':
 294                  $this->hashAlgorithm = 'rsa-sha1';
 295                  break;
 296              case 'rsa-sha256':
 297                  $this->hashAlgorithm = 'rsa-sha256';
 298                  if (!defined('OPENSSL_ALGO_SHA256')) {
 299                      throw new Swift_SwiftException('Unable to set sha256 as it is not supported by OpenSSL.');
 300                  }
 301                  break;
 302              default:
 303                  throw new Swift_SwiftException('Unable to set the hash algorithm, must be one of rsa-sha1 or rsa-sha256 (%s given).', $hash);
 304          }
 305  
 306          return $this;
 307      }
 308  
 309      /**
 310       * Set the body canonicalization algorithm.
 311       *
 312       * @param string $canon
 313       *
 314       * @return $this
 315       */
 316      public function setBodyCanon($canon)
 317      {
 318          if ('relaxed' == $canon) {
 319              $this->bodyCanon = 'relaxed';
 320          } else {
 321              $this->bodyCanon = 'simple';
 322          }
 323  
 324          return $this;
 325      }
 326  
 327      /**
 328       * Set the header canonicalization algorithm.
 329       *
 330       * @param string $canon
 331       *
 332       * @return $this
 333       */
 334      public function setHeaderCanon($canon)
 335      {
 336          if ('relaxed' == $canon) {
 337              $this->headerCanon = 'relaxed';
 338          } else {
 339              $this->headerCanon = 'simple';
 340          }
 341  
 342          return $this;
 343      }
 344  
 345      /**
 346       * Set the signer identity.
 347       *
 348       * @param string $identity
 349       *
 350       * @return $this
 351       */
 352      public function setSignerIdentity($identity)
 353      {
 354          $this->signerIdentity = $identity;
 355  
 356          return $this;
 357      }
 358  
 359      /**
 360       * Set the length of the body to sign.
 361       *
 362       * @param mixed $len (bool or int)
 363       *
 364       * @return $this
 365       */
 366      public function setBodySignedLen($len)
 367      {
 368          if (true === $len) {
 369              $this->showLen = true;
 370              $this->maxLen = PHP_INT_MAX;
 371          } elseif (false === $len) {
 372              $this->showLen = false;
 373              $this->maxLen = PHP_INT_MAX;
 374          } else {
 375              $this->showLen = true;
 376              $this->maxLen = (int) $len;
 377          }
 378  
 379          return $this;
 380      }
 381  
 382      /**
 383       * Set the signature timestamp.
 384       *
 385       * @param int $time A timestamp
 386       *
 387       * @return $this
 388       */
 389      public function setSignatureTimestamp($time)
 390      {
 391          $this->signatureTimestamp = $time;
 392  
 393          return $this;
 394      }
 395  
 396      /**
 397       * Set the signature expiration timestamp.
 398       *
 399       * @param int $time A timestamp
 400       *
 401       * @return $this
 402       */
 403      public function setSignatureExpiration($time)
 404      {
 405          $this->signatureExpiration = $time;
 406  
 407          return $this;
 408      }
 409  
 410      /**
 411       * Enable / disable the DebugHeaders.
 412       *
 413       * @param bool $debug
 414       *
 415       * @return Swift_Signers_DKIMSigner
 416       */
 417      public function setDebugHeaders($debug)
 418      {
 419          $this->debugHeaders = (bool) $debug;
 420  
 421          return $this;
 422      }
 423  
 424      /**
 425       * Start Body.
 426       */
 427      public function startBody()
 428      {
 429          // Init
 430          switch ($this->hashAlgorithm) {
 431              case 'rsa-sha256':
 432                  $this->bodyHashHandler = hash_init('sha256');
 433                  break;
 434              case 'rsa-sha1':
 435                  $this->bodyHashHandler = hash_init('sha1');
 436                  break;
 437          }
 438          $this->bodyCanonLine = '';
 439      }
 440  
 441      /**
 442       * End Body.
 443       */
 444      public function endBody()
 445      {
 446          $this->endOfBody();
 447      }
 448  
 449      /**
 450       * Returns the list of Headers Tampered by this plugin.
 451       *
 452       * @return array
 453       */
 454      public function getAlteredHeaders()
 455      {
 456          if ($this->debugHeaders) {
 457              return ['DKIM-Signature', 'X-DebugHash'];
 458          } else {
 459              return ['DKIM-Signature'];
 460          }
 461      }
 462  
 463      /**
 464       * Adds an ignored Header.
 465       *
 466       * @param string $header_name
 467       *
 468       * @return Swift_Signers_DKIMSigner
 469       */
 470      public function ignoreHeader($header_name)
 471      {
 472          $this->ignoredHeaders[strtolower($header_name)] = true;
 473  
 474          return $this;
 475      }
 476  
 477      /**
 478       * Set the headers to sign.
 479       *
 480       * @return Swift_Signers_DKIMSigner
 481       */
 482      public function setHeaders(Swift_Mime_SimpleHeaderSet $headers)
 483      {
 484          $this->headerCanonData = '';
 485          // Loop through Headers
 486          $listHeaders = $headers->listAll();
 487          foreach ($listHeaders as $hName) {
 488              // Check if we need to ignore Header
 489              if (!isset($this->ignoredHeaders[strtolower($hName)])) {
 490                  if ($headers->has($hName)) {
 491                      $tmp = $headers->getAll($hName);
 492                      foreach ($tmp as $header) {
 493                          if ('' != $header->getFieldBody()) {
 494                              $this->addHeader($header->toString());
 495                              $this->signedHeaders[] = $header->getFieldName();
 496                          }
 497                      }
 498                  }
 499              }
 500          }
 501  
 502          return $this;
 503      }
 504  
 505      /**
 506       * Add the signature to the given Headers.
 507       *
 508       * @return Swift_Signers_DKIMSigner
 509       */
 510      public function addSignature(Swift_Mime_SimpleHeaderSet $headers)
 511      {
 512          // Prepare the DKIM-Signature
 513          $params = ['v' => '1', 'a' => $this->hashAlgorithm, 'bh' => base64_encode($this->bodyHash), 'd' => $this->domainName, 'h' => implode(': ', $this->signedHeaders), 'i' => $this->signerIdentity, 's' => $this->selector];
 514          if ('simple' != $this->bodyCanon) {
 515              $params['c'] = $this->headerCanon.'/'.$this->bodyCanon;
 516          } elseif ('simple' != $this->headerCanon) {
 517              $params['c'] = $this->headerCanon;
 518          }
 519          if ($this->showLen) {
 520              $params['l'] = $this->bodyLen;
 521          }
 522          if (true === $this->signatureTimestamp) {
 523              $params['t'] = time();
 524              if (false !== $this->signatureExpiration) {
 525                  $params['x'] = $params['t'] + $this->signatureExpiration;
 526              }
 527          } else {
 528              if (false !== $this->signatureTimestamp) {
 529                  $params['t'] = $this->signatureTimestamp;
 530              }
 531              if (false !== $this->signatureExpiration) {
 532                  $params['x'] = $this->signatureExpiration;
 533              }
 534          }
 535          if ($this->debugHeaders) {
 536              $params['z'] = implode('|', $this->debugHeadersData);
 537          }
 538          $string = '';
 539          foreach ($params as $k => $v) {
 540              $string .= $k.'='.$v.'; ';
 541          }
 542          $string = trim($string);
 543          $headers->addTextHeader('DKIM-Signature', $string);
 544          // Add the last DKIM-Signature
 545          $tmp = $headers->getAll('DKIM-Signature');
 546          $this->dkimHeader = end($tmp);
 547          $this->addHeader(trim($this->dkimHeader->toString())."\r\n b=", true);
 548          if ($this->debugHeaders) {
 549              $headers->addTextHeader('X-DebugHash', base64_encode($this->headerHash));
 550          }
 551          $this->dkimHeader->setValue($string.' b='.trim(chunk_split(base64_encode($this->getEncryptedHash()), 73, ' ')));
 552  
 553          return $this;
 554      }
 555  
 556      /* Private helpers */
 557  
 558      protected function addHeader($header, $is_sig = false)
 559      {
 560          switch ($this->headerCanon) {
 561              case 'relaxed':
 562                  // Prepare Header and cascade
 563                  $exploded = explode(':', $header, 2);
 564                  $name = strtolower(trim($exploded[0]));
 565                  $value = str_replace("\r\n", '', $exploded[1]);
 566                  $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
 567                  $header = $name.':'.trim($value).($is_sig ? '' : "\r\n");
 568                  // no break
 569              case 'simple':
 570                  // Nothing to do
 571          }
 572          $this->addToHeaderHash($header);
 573      }
 574  
 575      protected function canonicalizeBody($string)
 576      {
 577          $len = strlen($string);
 578          $canon = '';
 579          $method = ('relaxed' == $this->bodyCanon);
 580          for ($i = 0; $i < $len; ++$i) {
 581              if ($this->bodyCanonIgnoreStart > 0) {
 582                  --$this->bodyCanonIgnoreStart;
 583                  continue;
 584              }
 585              switch ($string[$i]) {
 586                  case "\r":
 587                      $this->bodyCanonLastChar = "\r";
 588                      break;
 589                  case "\n":
 590                      if ("\r" == $this->bodyCanonLastChar) {
 591                          if ($method) {
 592                              $this->bodyCanonSpace = false;
 593                          }
 594                          if ('' == $this->bodyCanonLine) {
 595                              ++$this->bodyCanonEmptyCounter;
 596                          } else {
 597                              $this->bodyCanonLine = '';
 598                              $canon .= "\r\n";
 599                          }
 600                      } else {
 601                          // Wooops Error
 602                          // todo handle it but should never happen
 603                      }
 604                      break;
 605                  case ' ':
 606                  case "\t":
 607                      if ($method) {
 608                          $this->bodyCanonSpace = true;
 609                          break;
 610                      }
 611                      // no break
 612                  default:
 613                      if ($this->bodyCanonEmptyCounter > 0) {
 614                          $canon .= str_repeat("\r\n", $this->bodyCanonEmptyCounter);
 615                          $this->bodyCanonEmptyCounter = 0;
 616                      }
 617                      if ($this->bodyCanonSpace) {
 618                          $this->bodyCanonLine .= ' ';
 619                          $canon .= ' ';
 620                          $this->bodyCanonSpace = false;
 621                      }
 622                      $this->bodyCanonLine .= $string[$i];
 623                      $canon .= $string[$i];
 624              }
 625          }
 626          $this->addToBodyHash($canon);
 627      }
 628  
 629      protected function endOfBody()
 630      {
 631          // Add trailing Line return if last line is non empty
 632          if (strlen($this->bodyCanonLine) > 0) {
 633              $this->addToBodyHash("\r\n");
 634          }
 635          $this->bodyHash = hash_final($this->bodyHashHandler, true);
 636      }
 637  
 638      private function addToBodyHash($string)
 639      {
 640          $len = strlen($string);
 641          if ($len > ($new_len = ($this->maxLen - $this->bodyLen))) {
 642              $string = substr($string, 0, $new_len);
 643              $len = $new_len;
 644          }
 645          hash_update($this->bodyHashHandler, $string);
 646          $this->bodyLen += $len;
 647      }
 648  
 649      private function addToHeaderHash($header)
 650      {
 651          if ($this->debugHeaders) {
 652              $this->debugHeadersData[] = trim($header);
 653          }
 654          $this->headerCanonData .= $header;
 655      }
 656  
 657      /**
 658       * @throws Swift_SwiftException
 659       *
 660       * @return string
 661       */
 662      private function getEncryptedHash()
 663      {
 664          $signature = '';
 665          switch ($this->hashAlgorithm) {
 666              case 'rsa-sha1':
 667                  $algorithm = OPENSSL_ALGO_SHA1;
 668                  break;
 669              case 'rsa-sha256':
 670                  $algorithm = OPENSSL_ALGO_SHA256;
 671                  break;
 672          }
 673          $pkeyId = openssl_get_privatekey($this->privateKey, $this->passphrase);
 674          if (!$pkeyId) {
 675              throw new Swift_SwiftException('Unable to load DKIM Private Key ['.openssl_error_string().']');
 676          }
 677          if (openssl_sign($this->headerCanonData, $signature, $pkeyId, $algorithm)) {
 678              return $signature;
 679          }
 680          throw new Swift_SwiftException('Unable to sign DKIM Hash ['.openssl_error_string().']');
 681      }
 682  }


Generated: Tue May 19 15:55:14 2020 Cross-referenced by PHPXref 0.7.1