PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/classes/Swift/Signers/DomainKeySigner.php

http://github.com/swiftmailer/swiftmailer
PHP | 504 lines | 241 code | 58 blank | 205 comment | 27 complexity | 71ff97eaafdba5450f3266823f202bc7 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1
  1. <?php
  2. /*
  3. * This file is part of SwiftMailer.
  4. * (c) 2004-2009 Chris Corbyn
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * DomainKey Signer used to apply DomainKeys Signature to a message.
  11. *
  12. * @author Xavier De Cock <xdecock@gmail.com>
  13. */
  14. class Swift_Signers_DomainKeySigner implements Swift_Signers_HeaderSigner
  15. {
  16. /**
  17. * PrivateKey.
  18. *
  19. * @var string
  20. */
  21. protected $privateKey;
  22. /**
  23. * DomainName.
  24. *
  25. * @var string
  26. */
  27. protected $domainName;
  28. /**
  29. * Selector.
  30. *
  31. * @var string
  32. */
  33. protected $selector;
  34. /**
  35. * Hash algorithm used.
  36. *
  37. * @var string
  38. */
  39. protected $hashAlgorithm = 'rsa-sha1';
  40. /**
  41. * Canonisation method.
  42. *
  43. * @var string
  44. */
  45. protected $canon = 'simple';
  46. /**
  47. * Headers not being signed.
  48. *
  49. * @var array
  50. */
  51. protected $ignoredHeaders = [];
  52. /**
  53. * Signer identity.
  54. *
  55. * @var string
  56. */
  57. protected $signerIdentity;
  58. /**
  59. * Must we embed signed headers?
  60. *
  61. * @var bool
  62. */
  63. protected $debugHeaders = false;
  64. // work variables
  65. /**
  66. * Headers used to generate hash.
  67. *
  68. * @var array
  69. */
  70. private $signedHeaders = [];
  71. /**
  72. * Stores the signature header.
  73. *
  74. * @var Swift_Mime_Headers_ParameterizedHeader
  75. */
  76. protected $domainKeyHeader;
  77. /**
  78. * Hash Handler.
  79. *
  80. * @var resource|null
  81. */
  82. private $hashHandler;
  83. private $canonData = '';
  84. private $bodyCanonEmptyCounter = 0;
  85. private $bodyCanonIgnoreStart = 2;
  86. private $bodyCanonSpace = false;
  87. private $bodyCanonLastChar = null;
  88. private $bodyCanonLine = '';
  89. private $bound = [];
  90. /**
  91. * Constructor.
  92. *
  93. * @param string $privateKey
  94. * @param string $domainName
  95. * @param string $selector
  96. */
  97. public function __construct($privateKey, $domainName, $selector)
  98. {
  99. $this->privateKey = $privateKey;
  100. $this->domainName = $domainName;
  101. $this->signerIdentity = '@'.$domainName;
  102. $this->selector = $selector;
  103. }
  104. /**
  105. * Resets internal states.
  106. *
  107. * @return $this
  108. */
  109. public function reset()
  110. {
  111. $this->hashHandler = null;
  112. $this->bodyCanonIgnoreStart = 2;
  113. $this->bodyCanonEmptyCounter = 0;
  114. $this->bodyCanonLastChar = null;
  115. $this->bodyCanonSpace = false;
  116. return $this;
  117. }
  118. /**
  119. * Writes $bytes to the end of the stream.
  120. *
  121. * Writing may not happen immediately if the stream chooses to buffer. If
  122. * you want to write these bytes with immediate effect, call {@link commit()}
  123. * after calling write().
  124. *
  125. * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
  126. * second, etc etc).
  127. *
  128. * @param string $bytes
  129. *
  130. * @return int
  131. *
  132. * @throws Swift_IoException
  133. *
  134. * @return $this
  135. */
  136. public function write($bytes)
  137. {
  138. $this->canonicalizeBody($bytes);
  139. foreach ($this->bound as $is) {
  140. $is->write($bytes);
  141. }
  142. return $this;
  143. }
  144. /**
  145. * For any bytes that are currently buffered inside the stream, force them
  146. * off the buffer.
  147. *
  148. * @throws Swift_IoException
  149. *
  150. * @return $this
  151. */
  152. public function commit()
  153. {
  154. // Nothing to do
  155. return $this;
  156. }
  157. /**
  158. * Attach $is to this stream.
  159. *
  160. * The stream acts as an observer, receiving all data that is written.
  161. * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
  162. *
  163. * @return $this
  164. */
  165. public function bind(Swift_InputByteStream $is)
  166. {
  167. // Don't have to mirror anything
  168. $this->bound[] = $is;
  169. return $this;
  170. }
  171. /**
  172. * Remove an already bound stream.
  173. *
  174. * If $is is not bound, no errors will be raised.
  175. * If the stream currently has any buffered data it will be written to $is
  176. * before unbinding occurs.
  177. *
  178. * @return $this
  179. */
  180. public function unbind(Swift_InputByteStream $is)
  181. {
  182. // Don't have to mirror anything
  183. foreach ($this->bound as $k => $stream) {
  184. if ($stream === $is) {
  185. unset($this->bound[$k]);
  186. break;
  187. }
  188. }
  189. return $this;
  190. }
  191. /**
  192. * Flush the contents of the stream (empty it) and set the internal pointer
  193. * to the beginning.
  194. *
  195. * @throws Swift_IoException
  196. *
  197. * @return $this
  198. */
  199. public function flushBuffers()
  200. {
  201. $this->reset();
  202. return $this;
  203. }
  204. /**
  205. * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256.
  206. *
  207. * @param string $hash
  208. *
  209. * @return $this
  210. */
  211. public function setHashAlgorithm($hash)
  212. {
  213. $this->hashAlgorithm = 'rsa-sha1';
  214. return $this;
  215. }
  216. /**
  217. * Set the canonicalization algorithm.
  218. *
  219. * @param string $canon simple | nofws defaults to simple
  220. *
  221. * @return $this
  222. */
  223. public function setCanon($canon)
  224. {
  225. if ('nofws' == $canon) {
  226. $this->canon = 'nofws';
  227. } else {
  228. $this->canon = 'simple';
  229. }
  230. return $this;
  231. }
  232. /**
  233. * Set the signer identity.
  234. *
  235. * @param string $identity
  236. *
  237. * @return $this
  238. */
  239. public function setSignerIdentity($identity)
  240. {
  241. $this->signerIdentity = $identity;
  242. return $this;
  243. }
  244. /**
  245. * Enable / disable the DebugHeaders.
  246. *
  247. * @param bool $debug
  248. *
  249. * @return $this
  250. */
  251. public function setDebugHeaders($debug)
  252. {
  253. $this->debugHeaders = (bool) $debug;
  254. return $this;
  255. }
  256. /**
  257. * Start Body.
  258. */
  259. public function startBody()
  260. {
  261. }
  262. /**
  263. * End Body.
  264. */
  265. public function endBody()
  266. {
  267. $this->endOfBody();
  268. }
  269. /**
  270. * Returns the list of Headers Tampered by this plugin.
  271. *
  272. * @return array
  273. */
  274. public function getAlteredHeaders()
  275. {
  276. if ($this->debugHeaders) {
  277. return ['DomainKey-Signature', 'X-DebugHash'];
  278. }
  279. return ['DomainKey-Signature'];
  280. }
  281. /**
  282. * Adds an ignored Header.
  283. *
  284. * @param string $header_name
  285. *
  286. * @return $this
  287. */
  288. public function ignoreHeader($header_name)
  289. {
  290. $this->ignoredHeaders[strtolower($header_name)] = true;
  291. return $this;
  292. }
  293. /**
  294. * Set the headers to sign.
  295. *
  296. * @return $this
  297. */
  298. public function setHeaders(Swift_Mime_SimpleHeaderSet $headers)
  299. {
  300. $this->startHash();
  301. $this->canonData = '';
  302. // Loop through Headers
  303. $listHeaders = $headers->listAll();
  304. foreach ($listHeaders as $hName) {
  305. // Check if we need to ignore Header
  306. if (!isset($this->ignoredHeaders[strtolower($hName)])) {
  307. if ($headers->has($hName)) {
  308. $tmp = $headers->getAll($hName);
  309. foreach ($tmp as $header) {
  310. if ('' != $header->getFieldBody()) {
  311. $this->addHeader($header->toString());
  312. $this->signedHeaders[] = $header->getFieldName();
  313. }
  314. }
  315. }
  316. }
  317. }
  318. $this->endOfHeaders();
  319. return $this;
  320. }
  321. /**
  322. * Add the signature to the given Headers.
  323. *
  324. * @return $this
  325. */
  326. public function addSignature(Swift_Mime_SimpleHeaderSet $headers)
  327. {
  328. // Prepare the DomainKey-Signature Header
  329. $params = ['a' => $this->hashAlgorithm, 'b' => chunk_split(base64_encode($this->getEncryptedHash()), 73, ' '), 'c' => $this->canon, 'd' => $this->domainName, 'h' => implode(': ', $this->signedHeaders), 'q' => 'dns', 's' => $this->selector];
  330. $string = '';
  331. foreach ($params as $k => $v) {
  332. $string .= $k.'='.$v.'; ';
  333. }
  334. $string = trim($string);
  335. $headers->addTextHeader('DomainKey-Signature', $string);
  336. return $this;
  337. }
  338. /* Private helpers */
  339. protected function addHeader($header)
  340. {
  341. switch ($this->canon) {
  342. case 'nofws':
  343. // Prepare Header and cascade
  344. $exploded = explode(':', $header, 2);
  345. $name = strtolower(trim($exploded[0]));
  346. $value = str_replace("\r\n", '', $exploded[1]);
  347. $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
  348. $header = $name.':'.trim($value)."\r\n";
  349. // no break
  350. case 'simple':
  351. // Nothing to do
  352. }
  353. $this->addToHash($header);
  354. }
  355. protected function endOfHeaders()
  356. {
  357. $this->bodyCanonEmptyCounter = 1;
  358. }
  359. protected function canonicalizeBody($string)
  360. {
  361. $len = strlen($string);
  362. $canon = '';
  363. $nofws = ('nofws' == $this->canon);
  364. for ($i = 0; $i < $len; ++$i) {
  365. if ($this->bodyCanonIgnoreStart > 0) {
  366. --$this->bodyCanonIgnoreStart;
  367. continue;
  368. }
  369. switch ($string[$i]) {
  370. case "\r":
  371. $this->bodyCanonLastChar = "\r";
  372. break;
  373. case "\n":
  374. if ("\r" == $this->bodyCanonLastChar) {
  375. if ($nofws) {
  376. $this->bodyCanonSpace = false;
  377. }
  378. if ('' == $this->bodyCanonLine) {
  379. ++$this->bodyCanonEmptyCounter;
  380. } else {
  381. $this->bodyCanonLine = '';
  382. $canon .= "\r\n";
  383. }
  384. } else {
  385. // Wooops Error
  386. throw new Swift_SwiftException('Invalid new line sequence in mail found \n without preceding \r');
  387. }
  388. break;
  389. case ' ':
  390. case "\t":
  391. case "\x09": //HTAB
  392. if ($nofws) {
  393. $this->bodyCanonSpace = true;
  394. break;
  395. }
  396. // no break
  397. default:
  398. if ($this->bodyCanonEmptyCounter > 0) {
  399. $canon .= str_repeat("\r\n", $this->bodyCanonEmptyCounter);
  400. $this->bodyCanonEmptyCounter = 0;
  401. }
  402. $this->bodyCanonLine .= $string[$i];
  403. $canon .= $string[$i];
  404. }
  405. }
  406. $this->addToHash($canon);
  407. }
  408. protected function endOfBody()
  409. {
  410. if (strlen($this->bodyCanonLine) > 0) {
  411. $this->addToHash("\r\n");
  412. }
  413. }
  414. private function addToHash($string)
  415. {
  416. $this->canonData .= $string;
  417. hash_update($this->hashHandler, $string);
  418. }
  419. private function startHash()
  420. {
  421. // Init
  422. switch ($this->hashAlgorithm) {
  423. case 'rsa-sha1':
  424. $this->hashHandler = hash_init('sha1');
  425. break;
  426. }
  427. $this->bodyCanonLine = '';
  428. }
  429. /**
  430. * @throws Swift_SwiftException
  431. *
  432. * @return string
  433. */
  434. private function getEncryptedHash()
  435. {
  436. $signature = '';
  437. $pkeyId = openssl_get_privatekey($this->privateKey);
  438. if (!$pkeyId) {
  439. throw new Swift_SwiftException('Unable to load DomainKey Private Key ['.openssl_error_string().']');
  440. }
  441. if (openssl_sign($this->canonData, $signature, $pkeyId, OPENSSL_ALGO_SHA1)) {
  442. return $signature;
  443. }
  444. throw new Swift_SwiftException('Unable to sign DomainKey Hash ['.openssl_error_string().']');
  445. }
  446. }