PageRenderTime 65ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Associates/SwiftMailer/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php

https://gitlab.com/fiesta-framework/Mail
PHP | 511 lines | 376 code | 116 blank | 19 comment | 22 complexity | f578a55a2dc2bc031824b010c107deb7 MD5 | raw file
  1. <?php
  2. class Swift_Signers_SMimeSignerTest extends \PHPUnit_Framework_TestCase
  3. {
  4. /**
  5. * @var Swift_StreamFilters_StringReplacementFilterFactory
  6. */
  7. protected $replacementFactory;
  8. protected $samplesDir;
  9. public function setUp()
  10. {
  11. $this->replacementFactory = Swift_DependencyContainer::getInstance()
  12. ->lookup('transport.replacementfactory');
  13. $this->samplesDir = str_replace('\\', '/', realpath(__DIR__.'/../../../_samples/')).'/';
  14. }
  15. public function testUnSingedMessage()
  16. {
  17. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  18. ->setFrom(array('john@doe.com' => 'John Doe'))
  19. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  20. ->setBody('Here is the message itself');
  21. $this->assertEquals('Here is the message itself', $message->getBody());
  22. }
  23. public function testSingedMessage()
  24. {
  25. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  26. ->setFrom(array('john@doe.com' => 'John Doe'))
  27. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  28. ->setBody('Here is the message itself');
  29. $signer = new Swift_Signers_SMimeSigner();
  30. $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
  31. $message->attachSigner($signer);
  32. $messageStream = $this->newFilteredStream();
  33. $message->toByteStream($messageStream);
  34. $messageStream->commit();
  35. $entityString = $messageStream->getContent();
  36. $headers = self::getHeadersOfMessage($entityString);
  37. if (!($boundary = $this->getBoundary($headers['content-type']))) {
  38. return false;
  39. }
  40. $expectedBody = <<<OEL
  41. This is an S/MIME signed message
  42. --$boundary
  43. Content-Type: text/plain; charset=utf-8
  44. Content-Transfer-Encoding: quoted-printable
  45. Here is the message itself
  46. --$boundary
  47. Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
  48. Content-Transfer-Encoding: base64
  49. Content-Disposition: attachment; filename="smime\.p7s"
  50. (?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
  51. --$boundary--
  52. OEL;
  53. $this->assertValidVerify($expectedBody, $messageStream);
  54. unset($messageStream);
  55. }
  56. public function testSingedMessageBinary()
  57. {
  58. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  59. ->setFrom(array('john@doe.com' => 'John Doe'))
  60. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  61. ->setBody('Here is the message itself');
  62. $signer = new Swift_Signers_SMimeSigner();
  63. $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key', PKCS7_BINARY);
  64. $message->attachSigner($signer);
  65. $messageStream = $this->newFilteredStream();
  66. $message->toByteStream($messageStream);
  67. $messageStream->commit();
  68. $entityString = $messageStream->getContent();
  69. $headers = self::getHeadersOfMessage($entityString);
  70. if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=signed\-data;#', $headers['content-type'])) {
  71. $this->fail('Content-type does not match.');
  72. return false;
  73. }
  74. $this->assertEquals($headers['content-transfer-encoding'], 'base64');
  75. $this->assertEquals($headers['content-disposition'], 'attachment; filename="smime.p7m"');
  76. $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
  77. $messageStreamClean = $this->newFilteredStream();
  78. $this->assertValidVerify($expectedBody, $messageStream);
  79. unset($messageStreamClean, $messageStream);
  80. }
  81. public function testSingedMessageWithAttachments()
  82. {
  83. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  84. ->setFrom(array('john@doe.com' => 'John Doe'))
  85. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  86. ->setBody('Here is the message itself');
  87. $message->attach(Swift_Attachment::fromPath($this->samplesDir.'/files/textfile.zip'));
  88. $signer = new Swift_Signers_SMimeSigner();
  89. $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
  90. $message->attachSigner($signer);
  91. $messageStream = $this->newFilteredStream();
  92. $message->toByteStream($messageStream);
  93. $messageStream->commit();
  94. $entityString = $messageStream->getContent();
  95. $headers = self::getHeadersOfMessage($entityString);
  96. if (!($boundary = $this->getBoundary($headers['content-type']))) {
  97. return false;
  98. }
  99. $expectedBody = <<<OEL
  100. This is an S/MIME signed message
  101. --$boundary
  102. Content-Type: multipart/mixed;
  103. boundary="([a-z0-9\\'\\(\\)\\+_\\-,\\.\\/:=\\?\\ ]{0,69}[a-z0-9\\'\\(\\)\\+_\\-,\\.\\/:=\\?])"
  104. --\\1
  105. Content-Type: text/plain; charset=utf-8
  106. Content-Transfer-Encoding: quoted-printable
  107. Here is the message itself
  108. --\\1
  109. Content-Type: application/zip; name=textfile\\.zip
  110. Content-Transfer-Encoding: base64
  111. Content-Disposition: attachment; filename=textfile\\.zip
  112. UEsDBAoAAgAAAMi6VjiOTiKwLgAAAC4AAAAMABUAdGV4dGZpbGUudHh0VVQJAAN3vr5Hd76\\+R1V4
  113. BAD1AfUBVGhpcyBpcyBwYXJ0IG9mIGEgU3dpZnQgTWFpbGVyIHY0IHNtb2tlIHRlc3QuClBLAQIX
  114. AwoAAgAAAMi6VjiOTiKwLgAAAC4AAAAMAA0AAAAAAAEAAACkgQAAAAB0ZXh0ZmlsZS50eHRVVAUA
  115. A3e\\+vkdVeAAAUEsFBgAAAAABAAEARwAAAG0AAAAAAA==
  116. --\\1--
  117. --$boundary
  118. Content-Type: application/(x\-)?pkcs7-signature; name="smime\\.p7s"
  119. Content-Transfer-Encoding: base64
  120. Content-Disposition: attachment; filename="smime\\.p7s"
  121. (?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
  122. --$boundary--
  123. OEL;
  124. $this->assertValidVerify($expectedBody, $messageStream);
  125. unset($messageStream);
  126. }
  127. public function testEncryptedMessage()
  128. {
  129. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  130. ->setFrom(array('john@doe.com' => 'John Doe'))
  131. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  132. ->setBody('Here is the message itself');
  133. $originalMessage = $this->cleanMessage($message->toString());
  134. $signer = new Swift_Signers_SMimeSigner();
  135. $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
  136. $message->attachSigner($signer);
  137. $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
  138. $message->toByteStream($messageStream);
  139. $messageStream->commit();
  140. $entityString = $messageStream->getContent();
  141. $headers = self::getHeadersOfMessage($entityString);
  142. if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
  143. $this->fail('Content-type does not match.');
  144. return false;
  145. }
  146. $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
  147. $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
  148. if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
  149. $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
  150. }
  151. $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
  152. unset($decryptedMessageStream, $messageStream);
  153. }
  154. public function testEncryptedMessageWithMultipleCerts()
  155. {
  156. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  157. ->setFrom(array('john@doe.com' => 'John Doe'))
  158. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  159. ->setBody('Here is the message itself');
  160. $originalMessage = $this->cleanMessage($message->toString());
  161. $signer = new Swift_Signers_SMimeSigner();
  162. $signer->setEncryptCertificate(array($this->samplesDir.'smime/encrypt.crt', $this->samplesDir.'smime/encrypt2.crt'));
  163. $message->attachSigner($signer);
  164. $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
  165. $message->toByteStream($messageStream);
  166. $messageStream->commit();
  167. $entityString = $messageStream->getContent();
  168. $headers = self::getHeadersOfMessage($entityString);
  169. if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
  170. $this->fail('Content-type does not match.');
  171. return false;
  172. }
  173. $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
  174. $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
  175. if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
  176. $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
  177. }
  178. $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
  179. unset($decryptedMessageStream);
  180. $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
  181. if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt2.crt', array('file://'.$this->samplesDir.'smime/encrypt2.key', 'swift'))) {
  182. $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
  183. }
  184. $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
  185. unset($decryptedMessageStream, $messageStream);
  186. }
  187. public function testSignThenEncryptedMessage()
  188. {
  189. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  190. ->setFrom(array('john@doe.com' => 'John Doe'))
  191. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  192. ->setBody('Here is the message itself');
  193. $signer = new Swift_Signers_SMimeSigner();
  194. $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
  195. $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
  196. $message->attachSigner($signer);
  197. $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
  198. $message->toByteStream($messageStream);
  199. $messageStream->commit();
  200. $entityString = $messageStream->getContent();
  201. $headers = self::getHeadersOfMessage($entityString);
  202. if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
  203. $this->fail('Content-type does not match.');
  204. return false;
  205. }
  206. $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';
  207. $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
  208. if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
  209. $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
  210. }
  211. $entityString = $decryptedMessageStream->getContent();
  212. $headers = self::getHeadersOfMessage($entityString);
  213. if (!($boundary = $this->getBoundary($headers['content-type']))) {
  214. return false;
  215. }
  216. $expectedBody = <<<OEL
  217. This is an S/MIME signed message
  218. --$boundary
  219. Content-Type: text/plain; charset=utf-8
  220. Content-Transfer-Encoding: quoted-printable
  221. Here is the message itself
  222. --$boundary
  223. Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
  224. Content-Transfer-Encoding: base64
  225. Content-Disposition: attachment; filename="smime\.p7s"
  226. (?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
  227. --$boundary--
  228. OEL;
  229. if (!$this->assertValidVerify($expectedBody, $decryptedMessageStream)) {
  230. return false;
  231. }
  232. unset($decryptedMessageStream, $messageStream);
  233. }
  234. public function testEncryptThenSignMessage()
  235. {
  236. $message = Swift_SignedMessage::newInstance('Wonderful Subject')
  237. ->setFrom(array('john@doe.com' => 'John Doe'))
  238. ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
  239. ->setBody('Here is the message itself');
  240. $originalMessage = $this->cleanMessage($message->toString());
  241. $signer = Swift_Signers_SMimeSigner::newInstance();
  242. $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
  243. $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
  244. $signer->setSignThenEncrypt(false);
  245. $message->attachSigner($signer);
  246. $messageStream = $this->newFilteredStream();
  247. $message->toByteStream($messageStream);
  248. $messageStream->commit();
  249. $entityString = $messageStream->getContent();
  250. $headers = self::getHeadersOfMessage($entityString);
  251. if (!($boundary = $this->getBoundary($headers['content-type']))) {
  252. return false;
  253. }
  254. $expectedBody = <<<OEL
  255. This is an S/MIME signed message
  256. --$boundary
  257. (?P<encrypted_message>MIME-Version: 1\.0
  258. Content-Disposition: attachment; filename="smime\.p7m"
  259. Content-Type: application/(x\-)?pkcs7-mime; smime-type=enveloped-data; name="smime\.p7m"
  260. Content-Transfer-Encoding: base64
  261. (?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
  262. )--$boundary
  263. Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
  264. Content-Transfer-Encoding: base64
  265. Content-Disposition: attachment; filename="smime\.p7s"
  266. (?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})
  267. --$boundary--
  268. OEL;
  269. if (!$this->assertValidVerify($expectedBody, $messageStream)) {
  270. return false;
  271. }
  272. $expectedBody = str_replace("\n", "\r\n", $expectedBody);
  273. if (!preg_match('%'.$expectedBody.'*%m', $entityString, $entities)) {
  274. $this->fail('Failed regex match.');
  275. return false;
  276. }
  277. $messageStreamClean = new Swift_ByteStream_TemporaryFileByteStream();
  278. $messageStreamClean->write($entities['encrypted_message']);
  279. $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
  280. if (!openssl_pkcs7_decrypt($messageStreamClean->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
  281. $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
  282. }
  283. $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
  284. unset($messageStreamClean, $messageStream, $decryptedMessageStream);
  285. }
  286. protected function assertValidVerify($expected, Swift_ByteStream_TemporaryFileByteStream $messageStream)
  287. {
  288. $actual = $messageStream->getContent();
  289. // File is UNIX encoded so convert them to correct line ending
  290. $expected = str_replace("\n", "\r\n", $expected);
  291. $actual = trim(self::getBodyOfMessage($actual));
  292. if (!$this->assertRegExp('%^'.$expected.'$\s*%m', $actual)) {
  293. return false;
  294. }
  295. $opensslOutput = new Swift_ByteStream_TemporaryFileByteStream();
  296. $verify = openssl_pkcs7_verify($messageStream->getPath(), null, $opensslOutput->getPath(), array($this->samplesDir.'smime/ca.crt'));
  297. if (false === $verify) {
  298. $this->fail('Verification of the message failed.');
  299. return false;
  300. } elseif (-1 === $verify) {
  301. $this->fail(sprintf('Verification of the message failed. Internal error "%s".', openssl_error_string()));
  302. return false;
  303. }
  304. return true;
  305. }
  306. protected function getBoundary($contentType)
  307. {
  308. if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $contentType, $contentTypeData)) {
  309. $this->fail('Failed to find Boundary parameter');
  310. return false;
  311. }
  312. return trim($contentTypeData[1], '"');
  313. }
  314. protected function newFilteredStream()
  315. {
  316. $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
  317. $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
  318. $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
  319. return $messageStream;
  320. }
  321. protected static function getBodyOfMessage($message)
  322. {
  323. return substr($message, strpos($message, "\r\n\r\n"));
  324. }
  325. /**
  326. * Strips of the sender headers and Mime-Version.
  327. *
  328. * @param Swift_ByteStream_TemporaryFileByteStream $messageStream
  329. * @param Swift_ByteStream_TemporaryFileByteStream $inputStream
  330. */
  331. protected function cleanMessage($content)
  332. {
  333. $newContent = '';
  334. $headers = self::getHeadersOfMessage($content);
  335. foreach ($headers as $headerName => $value) {
  336. if (!in_array($headerName, array('content-type', 'content-transfer-encoding', 'content-disposition'))) {
  337. continue;
  338. }
  339. $headerName = explode('-', $headerName);
  340. $headerName = array_map('ucfirst', $headerName);
  341. $headerName = implode('-', $headerName);
  342. if (strlen($value) > 62) {
  343. $value = wordwrap($value, 62, "\n ");
  344. }
  345. $newContent .= "$headerName: $value\r\n";
  346. }
  347. return $newContent."\r\n".ltrim(self::getBodyOfMessage($content));
  348. }
  349. /**
  350. * Returns the headers of the message.
  351. *
  352. * Header-names are lowercase.
  353. *
  354. * @param string $message
  355. *
  356. * @return array
  357. */
  358. protected static function getHeadersOfMessage($message)
  359. {
  360. $headersPosEnd = strpos($message, "\r\n\r\n");
  361. $headerData = substr($message, 0, $headersPosEnd);
  362. $headerLines = explode("\r\n", $headerData);
  363. if (empty($headerLines)) {
  364. return array();
  365. }
  366. $headers = array();
  367. foreach ($headerLines as $headerLine) {
  368. if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) {
  369. $headers[$currentHeaderName] .= ' '.trim($headerLine);
  370. continue;
  371. }
  372. $header = explode(':', $headerLine, 2);
  373. $currentHeaderName = strtolower($header[0]);
  374. $headers[$currentHeaderName] = trim($header[1]);
  375. }
  376. return $headers;
  377. }
  378. }