TYPO3  7.6
SMimeSigner.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 
19 {
20  protected $signCertificate;
21  protected $signPrivateKey;
22  protected $encryptCert;
23  protected $signThenEncrypt = true;
24  protected $signLevel;
25  protected $encryptLevel;
26  protected $signOptions;
27  protected $encryptOptions;
28  protected $encryptCipher;
29  protected $extraCerts = null;
30 
35 
39  protected $headerFactory;
40 
48  public function __construct($signCertificate = null, $signPrivateKey = null, $encryptCertificate = null)
49  {
50  if (null !== $signPrivateKey) {
52  }
53 
54  if (null !== $encryptCertificate) {
55  $this->setEncryptCertificate($encryptCertificate);
56  }
57 
58  $this->replacementFactory = Swift_DependencyContainer::getInstance()
59  ->lookup('transport.replacementfactory');
60 
61  $this->signOptions = PKCS7_DETACHED;
62 
63  // Supported since php5.4
64  if (defined('OPENSSL_CIPHER_AES_128_CBC')) {
65  $this->encryptCipher = OPENSSL_CIPHER_AES_128_CBC;
66  } else {
67  $this->encryptCipher = OPENSSL_CIPHER_RC2_128;
68  }
69  }
70 
79  public static function newInstance($certificate = null, $privateKey = null)
80  {
81  return new self($certificate, $privateKey);
82  }
83 
96  public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED, $extraCerts = null)
97  {
98  $this->signCertificate = 'file://'.str_replace('\\', '/', realpath($certificate));
99 
100  if (null !== $privateKey) {
101  if (is_array($privateKey)) {
102  $this->signPrivateKey = $privateKey;
103  $this->signPrivateKey[0] = 'file://'.str_replace('\\', '/', realpath($privateKey[0]));
104  } else {
105  $this->signPrivateKey = 'file://'.str_replace('\\', '/', realpath($privateKey));
106  }
107  }
108 
109  $this->signOptions = $signOptions;
110  if (null !== $extraCerts) {
111  $this->extraCerts = str_replace('\\', '/', realpath($extraCerts));
112  }
113 
114  return $this;
115  }
116 
128  public function setEncryptCertificate($recipientCerts, $cipher = null)
129  {
130  if (is_array($recipientCerts)) {
131  $this->encryptCert = array();
132 
133  foreach ($recipientCerts as $cert) {
134  $this->encryptCert[] = 'file://'.str_replace('\\', '/', realpath($cert));
135  }
136  } else {
137  $this->encryptCert = 'file://'.str_replace('\\', '/', realpath($recipientCerts));
138  }
139 
140  if (null !== $cipher) {
141  $this->encryptCipher = $cipher;
142  }
143 
144  return $this;
145  }
146 
150  public function getSignCertificate()
151  {
152  return $this->signCertificate;
153  }
154 
158  public function getSignPrivateKey()
159  {
160  return $this->signPrivateKey;
161  }
162 
174  public function setSignThenEncrypt($signThenEncrypt = true)
175  {
176  $this->signThenEncrypt = $signThenEncrypt;
177 
178  return $this;
179  }
180 
184  public function isSignThenEncrypt()
185  {
186  return $this->signThenEncrypt;
187  }
188 
194  public function reset()
195  {
196  return $this;
197  }
198 
206  public function signMessage(Swift_Message $message)
207  {
208  if (null === $this->signCertificate && null === $this->encryptCert) {
209  return $this;
210  }
211 
212  // Store the message using ByteStream to a file{1}
213  // Remove all Children
214  // Sign file{1}, parse the new MIME headers and set them on the primary MimeEntity
215  // Set the singed-body as the new body (without boundary)
216 
217  $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
218  $this->toSMimeByteStream($messageStream, $message);
219  $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder'));
220 
221  $message->setChildren(array());
222  $this->streamToMime($messageStream, $message);
223  }
224 
230  public function getAlteredHeaders()
231  {
232  return array('Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition');
233  }
234 
239  protected function toSMimeByteStream(Swift_InputByteStream $inputStream, Swift_Message $message)
240  {
241  $mimeEntity = $this->createMessage($message);
242  $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
243 
244  $mimeEntity->toByteStream($messageStream);
245  $messageStream->commit();
246 
247  if (null !== $this->signCertificate && null !== $this->encryptCert) {
248  $temporaryStream = new Swift_ByteStream_TemporaryFileByteStream();
249 
250  if ($this->signThenEncrypt) {
251  $this->messageStreamToSignedByteStream($messageStream, $temporaryStream);
252  $this->messageStreamToEncryptedByteStream($temporaryStream, $inputStream);
253  } else {
254  $this->messageStreamToEncryptedByteStream($messageStream, $temporaryStream);
255  $this->messageStreamToSignedByteStream($temporaryStream, $inputStream);
256  }
257  } elseif ($this->signCertificate !== null) {
258  $this->messageStreamToSignedByteStream($messageStream, $inputStream);
259  } else {
260  $this->messageStreamToEncryptedByteStream($messageStream, $inputStream);
261  }
262  }
263 
269  protected function createMessage(Swift_Message $message)
270  {
271  $mimeEntity = new Swift_Message('', $message->getBody(), $message->getContentType(), $message->getCharset());
272  $mimeEntity->setChildren($message->getChildren());
273 
274  $messageHeaders = $mimeEntity->getHeaders();
275  $messageHeaders->remove('Message-ID');
276  $messageHeaders->remove('Date');
277  $messageHeaders->remove('Subject');
278  $messageHeaders->remove('MIME-Version');
279  $messageHeaders->remove('To');
280  $messageHeaders->remove('From');
281 
282  return $mimeEntity;
283  }
284 
291  protected function messageStreamToSignedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $inputStream)
292  {
293  $signedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
294 
295  $args = array($outputStream->getPath(), $signedMessageStream->getPath(), $this->signCertificate, $this->signPrivateKey, array(), $this->signOptions);
296  if (null !== $this->extraCerts) {
297  $args[] = $this->extraCerts;
298  }
299 
300  if (!call_user_func_array('openssl_pkcs7_sign', $args)) {
301  throw new Swift_IoException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string()));
302  }
303 
304  $this->copyFromOpenSSLOutput($signedMessageStream, $inputStream);
305  }
306 
314  {
315  $encryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();
316 
317  if (!openssl_pkcs7_encrypt($outputStream->getPath(), $encryptedMessageStream->getPath(), $this->encryptCert, array(), 0, $this->encryptCipher)) {
318  throw new Swift_IoException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string()));
319  }
320 
321  $this->copyFromOpenSSLOutput($encryptedMessageStream, $is);
322  }
323 
328  protected function copyFromOpenSSLOutput(Swift_OutputByteStream $fromStream, Swift_InputByteStream $toStream)
329  {
330  $bufferLength = 4096;
331  $filteredStream = new Swift_ByteStream_TemporaryFileByteStream();
332  $filteredStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
333  $filteredStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
334 
335  while (false !== ($buffer = $fromStream->read($bufferLength))) {
336  $filteredStream->write($buffer);
337  }
338 
339  $filteredStream->flushBuffers();
340 
341  while (false !== ($buffer = $filteredStream->read($bufferLength))) {
342  $toStream->write($buffer);
343  }
344 
345  $toStream->commit();
346  }
347 
354  protected function streamToMime(Swift_OutputByteStream $fromStream, Swift_Message $message)
355  {
356  $bufferLength = 78;
357  $headerData = '';
358 
359  $fromStream->setReadPointer(0);
360 
361  while (($buffer = $fromStream->read($bufferLength)) !== false) {
362  $headerData .= $buffer;
363 
364  if (false !== strpos($buffer, "\r\n\r\n")) {
365  break;
366  }
367  }
368 
369  $headersPosEnd = strpos($headerData, "\r\n\r\n");
370  $headerData = trim($headerData);
371  $headerData = substr($headerData, 0, $headersPosEnd);
372  $headerLines = explode("\r\n", $headerData);
373  unset($headerData);
374 
375  $headers = array();
376  $currentHeaderName = '';
377 
378  foreach ($headerLines as $headerLine) {
379  // Line separated
380  if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) {
381  $headers[$currentHeaderName] .= ' '.trim($headerLine);
382  continue;
383  }
384 
385  $header = explode(':', $headerLine, 2);
386  $currentHeaderName = strtolower($header[0]);
387  $headers[$currentHeaderName] = trim($header[1]);
388  }
389 
390  $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
391  $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
392  $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');
393 
394  $messageHeaders = $message->getHeaders();
395 
396  // No need to check for 'application/pkcs7-mime', as this is always base64
397  if ('multipart/signed;' === substr($headers['content-type'], 0, 17)) {
398  if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $headers['content-type'], $contentTypeData)) {
399  throw new Swift_SwiftException('Failed to find Boundary parameter');
400  }
401 
402  $boundary = trim($contentTypeData['1'], '"');
403  $boundaryLen = strlen($boundary);
404 
405  // Skip the header and CRLF CRLF
406  $fromStream->setReadPointer($headersPosEnd + 4);
407 
408  while (false !== ($buffer = $fromStream->read($bufferLength))) {
409  $messageStream->write($buffer);
410  }
411 
412  $messageStream->commit();
413 
414  $messageHeaders->remove('Content-Transfer-Encoding');
415  $message->setContentType($headers['content-type']);
416  $message->setBoundary($boundary);
417  $message->setBody($messageStream);
418  } else {
419  $fromStream->setReadPointer($headersPosEnd + 4);
420 
421  if (null === $this->headerFactory) {
422  $this->headerFactory = Swift_DependencyContainer::getInstance()->lookup('mime.headerfactory');
423  }
424 
425  $message->setContentType($headers['content-type']);
426  $messageHeaders->set($this->headerFactory->createTextHeader('Content-Transfer-Encoding', $headers['content-transfer-encoding']));
427  $messageHeaders->set($this->headerFactory->createTextHeader('Content-Disposition', $headers['content-disposition']));
428 
429  while (false !== ($buffer = $fromStream->read($bufferLength))) {
430  $messageStream->write($buffer);
431  }
432 
433  $messageStream->commit();
434  $message->setBody($messageStream);
435  }
436  }
437 }