1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
34: class IMP_Mime_Viewer_Smime extends Horde_Mime_Viewer_Base
35: {
36: 37: 38: 39: 40:
41: protected $_capability = array(
42: 'full' => false,
43: 'info' => false,
44: 'inline' => true,
45: 'raw' => false
46: );
47:
48: 49: 50: 51: 52:
53: protected $_metadata = array(
54: 'compressed' => false,
55: 'embedded' => true,
56: 'forceinline' => true
57: );
58:
59: 60: 61: 62: 63:
64: protected $_impsmime = null;
65:
66: 67: 68:
69: protected function _initSmime()
70: {
71: if (is_null($this->_impsmime) &&
72: $GLOBALS['prefs']->getValue('use_smime')) {
73: try {
74: $this->_impsmime = $GLOBALS['injector']->getInstance('IMP_Crypt_Smime');
75: $this->_impsmime->checkForOpenSSL();
76: } catch (Horde_Exception $e) {
77: $this->_impsmime = null;
78: }
79: }
80: }
81:
82: 83: 84: 85: 86:
87: protected function _renderInline()
88: {
89:
90: $this->_initSmime();
91:
92: if ($GLOBALS['injector']->getInstance('Horde_Variables')->view_smime_key) {
93: return $this->_outputSmimeKey();
94: }
95:
96: $id = $this->_mimepart->getMimeId();
97:
98: switch ($this->_mimepart->getType()) {
99: case 'multipart/signed':
100: if (!in_array($this->_mimepart->getContentTypeParameter('protocol'), array('application/pkcs7-signature', 'application/x-pkcs7-signature'))) {
101: return array();
102: }
103: $this->_parseSignedData(true);
104:
105:
106: case 'application/pkcs7-mime':
107: case 'application/x-pkcs7-mime':
108: $cache = $this->getConfigParam('imp_contents')->getViewCache();
109:
110: if (isset($cache->smime[$id])) {
111: $ret = array(
112: $id => array(
113: 'data' => null,
114: 'status' => $cache->smime[$id]['status'],
115: 'type' => 'text/plain; charset=' . $this->getConfigParam('charset'),
116: 'wrap' => $cache->smime[$id]['wrap']
117: )
118: );
119: if (isset($cache->smime[$id]['sig'])) {
120: $ret[$cache->smime[$id]['sig']] = null;
121: }
122: return $ret;
123: }
124:
125:
126: default:
127: return array();
128: }
129: }
130:
131: 132: 133: 134: 135: 136: 137: 138:
139: protected function _getEmbeddedMimeParts()
140: {
141: if (!in_array($this->_mimepart->getType(), array('application/pkcs7-mime', 'application/x-pkcs7-mime'))) {
142: return null;
143: }
144:
145:
146: switch ($this->_mimepart->getContentTypeParameter('smime-type')) {
147: case 'signed-data':
148: return $this->_parseSignedData();
149:
150: case 'enveloped-data':
151: default:
152: 153: 154:
155: return $this->_parseEnvelopedData();
156: }
157: }
158:
159: 160: 161: 162: 163:
164: protected function _parseEnvelopedData()
165: {
166: $base_id = $this->_mimepart->getMimeId();
167:
168:
169: $status = new IMP_Mime_Status(_("The data in this part has been encrypted via S/MIME."));
170: $status->icon('mime/encryption.png', 'S/MIME');
171:
172: $cache = $this->getConfigParam('imp_contents')->getViewCache();
173: $cache->smime[$base_id] = array(
174: 'status' => $status,
175: 'wrap' => ''
176: );
177:
178:
179: $this->_initSmime();
180: if (empty($this->_impsmime)) {
181: $status->addText(_("S/MIME support is not currently enabled so the data is unable to be decrypted."));
182: return null;
183: }
184:
185: if (!$this->_impsmime->getPersonalPrivateKey()) {
186: $status->addText(_("No personal private key exists so the data is unable to be decrypted."));
187: return null;
188: }
189:
190:
191: $passphrase = $this->_impsmime->getPassphrase();
192: if ($passphrase === false) {
193: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_PassphraseDialog', array(
194: 'type' => 'smimePersonal'
195: ));
196: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getDomId())) . _("You must enter the passphrase for your S/MIME private key to view this data.") . '</a>');
197: return null;
198: }
199:
200: $raw_text = $this->_getPartStream($this->_mimepart->getMimeId());
201:
202: try {
203: $decrypted_data = $this->_impsmime->decryptMessage($this->_mimepart->replaceEOL($raw_text, Horde_Mime_Part::RFC_EOL));
204: } catch (Horde_Exception $e) {
205: $status->addText($e->getMessage());
206: return null;
207: }
208:
209: $cache->smime[$base_id]['wrap'] = 'mimePartWrapValid';
210:
211: $new_part = Horde_Mime_Part::parseMessage($decrypted_data, array(
212: 'forcemime' => true
213: ));
214:
215: switch ($new_part->getType()) {
216: case 'application/pkcs7-mime':
217: case 'application/x-pkcs7-mime':
218: $signed_data = (bool)$new_part->getContentTypeParameter('smime-type');
219: break;
220:
221: case 'multipart/signed':
222: $signed_data = true;
223: break;
224:
225: default:
226: $signed_data = false;
227: break;
228: }
229:
230: if ($signed_data) {
231: $hdrs = $this->getConfigParam('imp_contents')->getHeader();
232:
233: $data = new Horde_Stream_Temp();
234: $data->add(
235: 'From:' . $hdrs->getValue('from') . "\n" .
236: $decrypted_data
237: );
238:
239: $new_part->setMetadata('imp-smime-decrypt', $data);
240: $new_part->setContents($decrypted_data, array(
241: 'encoding' => 'binary'
242: ));
243: }
244:
245: return $new_part;
246: }
247:
248: 249: 250: 251: 252: 253: 254:
255: protected function _parseSignedData($sig_only = false)
256: {
257: $partlist = array_keys($this->_mimepart->contentTypeMap());
258: $base_id = reset($partlist);
259: $data_id = next($partlist);
260:
261: $id_ob = new Horde_Mime_Id($data_id);
262: $sig_id = $id_ob->idArithmetic($id_ob::ID_NEXT);
263:
264:
265: $status = new IMP_Mime_Status(_("The data in this part has been digitally signed via S/MIME."));
266: $status->icon('mime/encryption.png', 'S/MIME');
267:
268: $cache = $this->getConfigParam('imp_contents')->getViewCache();
269: $cache->smime[$base_id] = array(
270: 'sig' => $sig_id,
271: 'status' => $status,
272: 'wrap' => 'mimePartWrap'
273: );
274:
275: if (!$GLOBALS['prefs']->getValue('use_smime')) {
276: $status->addText(_("S/MIME support is not enabled so the digital signature is unable to be verified."));
277: return null;
278: }
279:
280:
281: if (!in_array($sig_id, $partlist)) {
282: $status->action(IMP_Mime_Status::ERROR);
283: $cache->smime[$base_id]['wrap'] = 'mimePartWrapInvalid';
284: $status->addText(_("Invalid S/MIME data."));
285:
286: $cache->smime[$base_id]['sig'] = $data_id;
287: return null;
288: }
289:
290: $imp_contents = $this->getConfigParam('imp_contents');
291: $stream = $imp_contents->isEmbedded($base_id)
292: ? $this->_mimepart->getMetadata('imp-smime-decrypt')->stream
293: : $this->_getPartStream($base_id);
294: $raw_text = $this->_mimepart->replaceEOL($stream, Horde_Mime_Part::RFC_EOL);
295:
296: $this->_initSmime();
297: $sig_result = null;
298:
299: if ($GLOBALS['prefs']->getValue('smime_verify') ||
300: $GLOBALS['injector']->getInstance('Horde_Variables')->smime_verify_msg) {
301: try {
302: $sig_result = $this->_impsmime->verifySignature($raw_text);
303: if ($sig_result->verify) {
304: $status->action(IMP_Mime_Status::SUCCESS);
305: } else {
306: $status->action(IMP_Mime_Status::WARNING);
307: }
308: $cache->smime[$base_id]['wrap'] = 'mimePartWrapValid';
309:
310: $email = is_array($sig_result->email)
311: ? implode(', ', $sig_result->email)
312: : $sig_result->email;
313:
314: $status->addText($sig_result->msg);
315:
316: if (!empty($sig_result->cert)) {
317: $cert = $this->_impsmime->parseCert($sig_result->cert);
318: if (isset($cert['certificate']['subject']['CommonName']) &&
319: (strcasecmp($email, $cert['certificate']['subject']['CommonName']) !== 0)) {
320: $email = $cert['certificate']['subject']['CommonName'] . ' (' . trim($email) . ')';
321: }
322: }
323:
324: if (!empty($sig_result->cert) &&
325: isset($sig_result->email) &&
326: $GLOBALS['registry']->hasMethod('contacts/addField') &&
327: $GLOBALS['prefs']->getValue('add_source')) {
328: $status->addText(sprintf(_("Sender: %s"), $imp_contents->linkViewJS($this->_mimepart, 'view_attach', htmlspecialchars(strlen($email) ? $email : $sig_result->email), array(
329: 'jstext' => _("View certificate details"),
330: 'params' => array(
331: 'mode' => IMP_Contents::RENDER_INLINE,
332: 'view_smime_key' => 1
333: )
334: ))));
335:
336: try {
337: $this->_impsmime->getPublicKey($sig_result->email);
338: } catch (Horde_Exception $e) {
339: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_ImportEncryptKey', array(
340: 'mime_id' => $base_id,
341: 'muid' => strval($imp_contents->getIndicesOb()),
342: 'type' => 'smime'
343: ));
344: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getDomId())) . _("Save the certificate to your Address Book.") . '</a>');
345: }
346: } elseif (strlen($email)) {
347: $status->addText(sprintf(_("Sender: %s"), htmlspecialchars($email)));
348: }
349: } catch (Horde_Exception $e) {
350: $status->action(IMP_Mime_Status::ERROR);
351: $cache->smime[$base_id]['wrap'] = 'mimePartWrapInvalid';
352: $status->addText($e->getMessage());
353: }
354: } else {
355: switch ($GLOBALS['registry']->getView()) {
356: case Horde_Registry::VIEW_BASIC:
357: $status->addText(Horde::link(Horde::selfUrlParams()->add('smime_verify_msg', 1)) . _("Click HERE to verify the data.") . '</a>');
358: break;
359:
360: case Horde_Registry::VIEW_DYNAMIC:
361: $status->addText(Horde::link('#', '', 'smimeVerifyMsg') . _("Click HERE to verify the data.") . '</a>');
362: break;
363: }
364: }
365:
366: if ($sig_only) {
367: return;
368: }
369:
370: $subpart = $imp_contents->getMIMEPart($sig_id);
371: if (empty($subpart)) {
372: try {
373: $msg_data = $this->_impsmime->extractSignedContents($raw_text);
374: $subpart = Horde_Mime_Part::parseMessage($msg_data, array('forcemime' => true));
375: } catch (Horde_Exception $e) {
376: $status->addText($e->getMessage());
377: return null;
378: }
379: }
380:
381: return $subpart;
382: }
383:
384: 385: 386: 387: 388:
389: protected function _outputSmimeKey()
390: {
391: if (empty($this->_impsmime)) {
392: return array();
393: }
394:
395: $raw_text = $this->_getPartStream($this->_mimepart->getMimeId());
396:
397: try {
398: $sig_result = $this->_impsmime->verifySignature($this->_mimepart->replaceEOL($raw_text, Horde_Mime_Part::RFC_EOL));
399: } catch (Horde_Exception $e) {
400: return array();
401: }
402:
403: return array(
404: $this->_mimepart->getMimeId() => array(
405: 'data' => $this->_impsmime->certToHTML($sig_result->cert),
406: 'type' => 'text/html; charset=' . $this->getConfigParam('charset')
407: )
408: );
409: }
410:
411: 412:
413: protected function _getPartStream($id)
414: {
415: return $id
416: ? $this->getConfigParam('imp_contents')->getBodyPart($id, array('mimeheaders' => true, 'stream' => true))->data
417: : $this->getConfigParam('imp_contents')->fullMessageText();
418: }
419:
420: }
421: