PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/nova/modules/swiftmailer/classes/Swift/Signers/SMimeSigner.php

https://github.com/anodyne/nova
PHP | 437 lines | 237 code | 66 blank | 134 comment | 29 complexity | 093284dea7ab659f78ce0caacf18fff1 MD5 | raw file
  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. * MIME Message Signer used to apply S/MIME Signature/Encryption to a message.
  11. *
  12. *
  13. * @author Romain-Geissler
  14. * @author Sebastiaan Stok <s.stok@rollerscapes.net>
  15. */
  16. class Swift_Signers_SMimeSigner implements Swift_Signers_BodySigner
  17. {
  18. protected $signCertificate;
  19. protected $signPrivateKey;
  20. protected $encryptCert;
  21. protected $signThenEncrypt = true;
  22. protected $signLevel;
  23. protected $encryptLevel;
  24. protected $signOptions;
  25. protected $encryptOptions;
  26. protected $encryptCipher;
  27. protected $extraCerts = null;
  28. /**
  29. * @var Swift_StreamFilters_StringReplacementFilterFactory
  30. */
  31. protected $replacementFactory;
  32. /**
  33. * @var Swift_Mime_HeaderFactory
  34. */
  35. protected $headerFactory;
  36. /**
  37. * Constructor.
  38. *
  39. * @param string $certificate
  40. * @param string $privateKey
  41. * @param string $encryptCertificate
  42. */
  43. public function __construct($signCertificate = null, $signPrivateKey = null, $encryptCertificate = null)
  44. {
  45. if (null !== $signPrivateKey) {
  46. $this->setSignCertificate($signCertificate, $signPrivateKey);
  47. }
  48. if (null !== $encryptCertificate) {
  49. $this->setEncryptCertificate($encryptCertificate);
  50. }
  51. $this->replacementFactory = Swift_DependencyContainer::getInstance()
  52. ->lookup('transport.replacementfactory');
  53. $this->signOptions = PKCS7_DETACHED;
  54. // Supported since php5.4
  55. if (defined('OPENSSL_CIPHER_AES_128_CBC')) {
  56. $this->encryptCipher = OPENSSL_CIPHER_AES_128_CBC;
  57. } else {
  58. $this->encryptCipher = OPENSSL_CIPHER_RC2_128;
  59. }
  60. }
  61. /**
  62. * Returns an new Swift_Signers_SMimeSigner instance.
  63. *
  64. * @param string $certificate
  65. * @param string $privateKey
  66. *
  67. * @return Swift_Signers_SMimeSigner
  68. */
  69. public static function newInstance($certificate = null, $privateKey = null)
  70. {
  71. return new self($certificate, $privateKey);
  72. }
  73. /**
  74. * Set the certificate location to use for signing.
  75. *
  76. * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php
  77. *
  78. * @param string $certificate
  79. * @param string|array $privateKey If the key needs an passphrase use array('file-location', 'passphrase') instead
  80. * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign()
  81. * @param string $extraCerts A file containing intermediate certificates needed by the signing certificate
  82. *
  83. * @return Swift_Signers_SMimeSigner
  84. */
  85. public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED, $extraCerts = null)
  86. {
  87. $this->signCertificate = 'file://'.str_replace('\\', '/', realpath($certificate));
  88. if (null !== $privateKey) {
  89. if (is_array($privateKey)) {
  90. $this->signPrivateKey = $privateKey;
  91. $this->signPrivateKey[0] = 'file://'.str_replace('\\', '/', realpath($privateKey[0]));
  92. } else {
  93. $this->signPrivateKey = 'file://'.str_replace('\\', '/', realpath($privateKey));
  94. }
  95. }
  96. $this->signOptions = $signOptions;
  97. if (null !== $extraCerts) {
  98. $this->extraCerts = str_replace('\\', '/', realpath($extraCerts));
  99. }
  100. return $this;
  101. }
  102. /**
  103. * Set the certificate location to use for encryption.
  104. *
  105. * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php
  106. * @link http://nl3.php.net/manual/en/openssl.ciphers.php
  107. *
  108. * @param string|array $recipientCerts Either an single X.509 certificate, or an assoc array of X.509 certificates.
  109. * @param int $cipher
  110. *
  111. * @return Swift_Signers_SMimeSigner
  112. */
  113. public function setEncryptCertificate($recipientCerts, $cipher = null)
  114. {
  115. if (is_array($recipientCerts)) {
  116. $this->encryptCert = array();
  117. foreach ($recipientCerts as $cert) {
  118. $this->encryptCert[] = 'file://'.str_replace('\\', '/', realpath($cert));
  119. }
  120. } else {
  121. $this->encryptCert = 'file://'.str_replace('\\', '/', realpath($recipientCerts));
  122. }
  123. if (null !== $cipher) {
  124. $this->encryptCipher = $cipher;
  125. }
  126. return $this;
  127. }
  128. /**
  129. * @return string
  130. */
  131. public function getSignCertificate()
  132. {
  133. return $this->signCertificate;
  134. }
  135. /**
  136. * @return string
  137. */
  138. public function getSignPrivateKey()
  139. {
  140. return $this->signPrivateKey;
  141. }
  142. /**
  143. * Set perform signing before encryption.
  144. *
  145. * The default is to first sign the message and then encrypt.
  146. * But some older mail clients, namely Microsoft Outlook 2000 will work when the message first encrypted.
  147. * As this goes against the official specs, its recommended to only use 'encryption -> signing' when specifically targeting these 'broken' clients.
  148. *
  149. * @param string $signThenEncrypt
  150. *
  151. * @return Swift_Signers_SMimeSigner
  152. */
  153. public function setSignThenEncrypt($signThenEncrypt = true)
  154. {
  155. $this->signThenEncrypt = $signThenEncrypt;
  156. return $this;
  157. }
  158. /**
  159. * @return bool
  160. */
  161. public function isSignThenEncrypt()
  162. {
  163. return $this->signThenEncrypt;
  164. }
  165. /**
  166. * Resets internal states.
  167. *
  168. * @return Swift_Signers_SMimeSigner
  169. */
  170. public function reset()
  171. {
  172. return $this;
  173. }
  174. /**
  175. * Change the Swift_Message to apply the signing.
  176. *
  177. * @param Swift_Message $message
  178. *
  179. * @return Swift_Signers_SMimeSigner
  180. */
  181. public function signMessage(Swift_Message $message)
  182. {
  183. if (null === $this->signCertificate && null === $this->encryptCert) {
  184. return $this;
  185. }
  186. // Store the message using ByteStream to a file{1}
  187. // Remove all Children
  188. // Sign file{1}, parse the new MIME headers and set them on the primary MimeEntity
  189. // Set the singed-body as the new body (without boundary)
  190. $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
  191. $this->toSMimeByteStream($messageStream, $message);
  192. $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder'));
  193. $message->setChildren(array());
  194. $this->streamToMime($messageStream, $message);
  195. }
  196. /**
  197. * Return the list of header a signer might tamper.
  198. *
  199. * @return array
  200. */
  201. public function getAlteredHeaders()
  202. {
  203. return array('Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition');
  204. }
  205. /**
  206. * @param Swift_InputByteStream $inputStream
  207. * @param Swift_Message $mimeEntity
  208. */
  209. protected function toSMimeByteStream(Swift_InputByteStream $inputStream, Swift_Message $message)
  210. {
  211. $mimeEntity = $this->createMessage($message);
  212. $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
  213. $mimeEntity->toByteStream($messageStream);
  214. $messageStream->commit();
  215. if (null !== $this->signCertificate && null !== $this->encryptCert) {
  216. $temporaryStream = new Swift_ByteStream_TemporaryFileByteStream();
  217. if ($this->signThenEncrypt) {
  218. $this->messageStreamToSignedByteStream($messageStream, $temporaryStream);
  219. $this->messageStreamToEncryptedByteStream($temporaryStream, $inputStream);
  220. } else {
  221. $this->messageStreamToEncryptedByteStream($messageStream, $temporaryStream);
  222. $this->messageStreamToSignedByteStream($temporaryStream, $inputStream);
  223. }
  224. } elseif ($this->signCertificate !== null) {
  225. $this->messageStreamToSignedByteStream($messageStream, $inputStream);
  226. } else {
  227. $this->messageStreamToEncryptedByteStream($messageStream, $inputStream);
  228. }
  229. }
  230. /**
  231. * @param Swift_Message $message
  232. *
  233. * @return Swift_Message
  234. */
  235. protected function createMessage(Swift_Message $message)
  236. {
  237. $mimeEntity = new Swift_Message('', $message->getBody(), $message->getContentType(), $message->getCharset());
  238. $mimeEntity->setChildren($message->getChildren());
  239. $messageHeaders = $mimeEntity->getHeaders();
  240. $messageHeaders->remove('Message-ID');
  241. $messageHeaders->remove('Date');
  242. $messageHeaders->remove('Subject');
  243. $messageHeaders->remove('MIME-Version');
  244. $messageHeaders->remove('To');
  245. $messageHeaders->remove('From');
  246. return $mimeEntity;
  247. }
  248. /**
  249. * @param Swift_FileStream $outputStream
  250. * @param Swift_InputByteStream $inputStream
  251. *
  252. * @throws Swift_IoException
  253. */
  254. protected function messageStreamToSignedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $inputStream)
  255. {
  256. $signedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
  257. $args = array($outputStream->getPath(), $signedMessageStream->getPath(), $this->signCertificate, $this->signPrivateKey, array(), $this->signOptions);
  258. if (null !== $this->extraCerts) {
  259. $args[] = $this->extraCerts;
  260. }
  261. if (!call_user_func_array('openssl_pkcs7_sign', $args)) {
  262. throw new Swift_IoException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string()));
  263. }
  264. $this->copyFromOpenSSLOutput($signedMessageStream, $inputStream);
  265. }
  266. /**
  267. * @param Swift_FileStream $outputStream
  268. * @param Swift_InputByteStream $is
  269. *
  270. * @throws Swift_IoException
  271. */
  272. protected function messageStreamToEncryptedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $is)
  273. {
  274. $encryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
  275. if (!openssl_pkcs7_encrypt($outputStream->getPath(), $encryptedMessageStream->getPath(), $this->encryptCert, array(), 0, $this->encryptCipher)) {
  276. throw new Swift_IoException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string()));
  277. }
  278. $this->copyFromOpenSSLOutput($encryptedMessageStream, $is);
  279. }
  280. /**
  281. * @param Swift_OutputByteStream $fromStream
  282. * @param Swift_InputByteStream $toStream
  283. */
  284. protected function copyFromOpenSSLOutput(Swift_OutputByteStream $fromStream, Swift_InputByteStream $toStream)
  285. {
  286. $bufferLength = 4096;
  287. $filteredStream = new Swift_ByteStream_TemporaryFileByteStream();
  288. $filteredStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
  289. $filteredStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
  290. while (false !== ($buffer = $fromStream->read($bufferLength))) {
  291. $filteredStream->write($buffer);
  292. }
  293. $filteredStream->flushBuffers();
  294. while (false !== ($buffer = $filteredStream->read($bufferLength))) {
  295. $toStream->write($buffer);
  296. }
  297. $toStream->commit();
  298. }
  299. /**
  300. * Merges an OutputByteStream to Swift_Message.
  301. *
  302. * @param Swift_OutputByteStream $fromStream
  303. * @param Swift_Message $message
  304. */
  305. protected function streamToMime(Swift_OutputByteStream $fromStream, Swift_Message $message)
  306. {
  307. $bufferLength = 78;
  308. $headerData = '';
  309. $fromStream->setReadPointer(0);
  310. while (($buffer = $fromStream->read($bufferLength)) !== false) {
  311. $headerData .= $buffer;
  312. if (false !== strpos($buffer, "\r\n\r\n")) {
  313. break;
  314. }
  315. }
  316. $headersPosEnd = strpos($headerData, "\r\n\r\n");
  317. $headerData = trim($headerData);
  318. $headerData = substr($headerData, 0, $headersPosEnd);
  319. $headerLines = explode("\r\n", $headerData);
  320. unset($headerData);
  321. $headers = array();
  322. $currentHeaderName = '';
  323. foreach ($headerLines as $headerLine) {
  324. // Line separated
  325. if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) {
  326. $headers[$currentHeaderName] .= ' '.trim($headerLine);
  327. continue;
  328. }
  329. $header = explode(':', $headerLine, 2);
  330. $currentHeaderName = strtolower($header[0]);
  331. $headers[$currentHeaderName] = trim($header[1]);
  332. }
  333. $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
  334. $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
  335. $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
  336. $messageHeaders = $message->getHeaders();
  337. // No need to check for 'application/pkcs7-mime', as this is always base64
  338. if ('multipart/signed;' === substr($headers['content-type'], 0, 17)) {
  339. if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $headers['content-type'], $contentTypeData)) {
  340. throw new Swift_SwiftException('Failed to find Boundary parameter');
  341. }
  342. $boundary = trim($contentTypeData['1'], '"');
  343. $boundaryLen = strlen($boundary);
  344. // Skip the header and CRLF CRLF
  345. $fromStream->setReadPointer($headersPosEnd + 4);
  346. while (false !== ($buffer = $fromStream->read($bufferLength))) {
  347. $messageStream->write($buffer);
  348. }
  349. $messageStream->commit();
  350. $messageHeaders->remove('Content-Transfer-Encoding');
  351. $message->setContentType($headers['content-type']);
  352. $message->setBoundary($boundary);
  353. $message->setBody($messageStream);
  354. } else {
  355. $fromStream->setReadPointer($headersPosEnd + 4);
  356. if (null === $this->headerFactory) {
  357. $this->headerFactory = Swift_DependencyContainer::getInstance()->lookup('mime.headerfactory');
  358. }
  359. $message->setContentType($headers['content-type']);
  360. $messageHeaders->set($this->headerFactory->createTextHeader('Content-Transfer-Encoding', $headers['content-transfer-encoding']));
  361. $messageHeaders->set($this->headerFactory->createTextHeader('Content-Disposition', $headers['content-disposition']));
  362. while (false !== ($buffer = $fromStream->read($bufferLength))) {
  363. $messageStream->write($buffer);
  364. }
  365. $messageStream->commit();
  366. $message->setBody($messageStream);
  367. }
  368. }
  369. }