W.I.P Smime:

- Adapt mail ui to resolve smime messages
- Set signature and encryption indicators
This commit is contained in:
Hadi Nategh 2017-07-19 18:16:22 +02:00
parent b30c712e19
commit 0499f50943
4 changed files with 90 additions and 275 deletions

View File

@ -2038,7 +2038,7 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
{ {
if (Mail\Smime::isSmime($attch['type'])) if (Mail\Smime::isSmime($attch['type']))
{ {
$data['smimeSigUrl'] = $attch['mime_url']; $data['smime'] = Mail\Smime::isSmimeSignatureOnly($attch['type']) ? 'smimeSignature' : 'smimeEncryption';
} }
} }
} }
@ -2159,46 +2159,12 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
// if we are in HTML so its likely that we should show the embedded images; as a result // if we are in HTML so its likely that we should show the embedded images; as a result
// we do NOT want to see those, that are embedded in the list of attachments // we do NOT want to see those, that are embedded in the list of attachments
if ($htmlOptions !='always_display') $fetchEmbeddedImages = true; if ($htmlOptions !='always_display') $fetchEmbeddedImages = true;
$attachments = $this->mail_bo->getMessageAttachments($uid, $partID, null, $fetchEmbeddedImages,true,true,$mailbox); try{
$attachments = $this->mail_bo->getMessageAttachments($uid, $partID, null, $fetchEmbeddedImages,true,true,$mailbox);
foreach ($attachments as &$attachment)
{
if (Mail\Smime::isSmime($attachment['mimeType']))
{
$smimeData = $this->resolveSmimeMessage (
$this->mail_bo->getMessageRawBody($uid, $partID, $mailbox),
array(
'mimeType' => $attachment['mimeType'],
'uid' => $uid,
'partID' => $partID,
'rowID' => $rowID,
'mailbox' => $mailbox,
'certAttachedPartID' => $attachment['partID'],
)
);
}
} }
catch(Mail\Smime\PassphraseMissing $e)
if (is_array($smimeData))
{ {
$error_msg[] = $smimeData['msg']; //continue
if ($smimeData['signed'])
{
$linkData = array
(
'menuaction' => 'mail.mail_ui.getAttachment',
'id' => $rowID,
'part' => $smimeData['partID'],
'is_winmail' => false,
'mailbox' => base64_encode($mailbox)
);
$content['smimeSigUrl'] = Egw::link('/index.php',$linkData);
}
if ($smimeData['password_required'])
{
$response = Api\Json\Response::get();
$response->call('app.mail.smimeRequestPassphrase');
}
} }
//error_log(__METHOD__.__LINE__.array2string($attachments)); //error_log(__METHOD__.__LINE__.array2string($attachments));
@ -2270,152 +2236,6 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
$etpl->exec('mail.mail_ui.displayMessage',$content,$sel_options,$readonlys,$preserv,2); $etpl->exec('mail.mail_ui.displayMessage',$content,$sel_options,$readonlys,$preserv,2);
} }
/**
* Parse SMIME signed message
*
* @param string $_message signed message
* @param type $_mailbox mailbox
* @return array
*/
function parseSmimeSignedMessage ($_message, $_mailbox)
{
$structure = Horde_Mime_Part::parseMessage($_message, array('forcemime'=>true));
return array (
'attachments' => $this->mail_bo->getMessageAttachments('',null,$structure,true, false,true,$_mailbox),
'body' => $this->mail_bo->getMessageBody('', '', null, $structure, false, $_mailbox, $calendar_part = null)
);
}
/**
* decrypt given smime encrypted message
*
* @param string $_message
* @param string $_passphrase
* @return array|string return
*/
function decryptSmimeBody ($_message, $_passphrase = '')
{
$AB_bo = new addressbook_bo();
$credents = Mail\Credentials::read($this->mail_bo->profileID, Mail\Credentials::SMIME, $GLOBALS['egw_info']['user']['account_id']);
$certkey = $AB_bo->get_smime_keys($GLOBALS['egw_info']['user']['account_email']);
if (!$this->smime->verifyPassphrase($credents['acc_smime_password'], $_passphrase))
{
return array (
'password_required' => true,
'msg' => 'Authentication failure!'
);
}
$params = array (
'type' => 'message',
'pubkey' => $certkey[$GLOBALS['egw_info']['user']['account_email']],
'privkey' => $credents['acc_smime_password'],
'passphrase'=> $_passphrase
);
return $this->smime->decrypt($_message, $params);
}
/**
* Resolve certificate and encrypted message from smime attachment
*
* @param string $_message
* @param array $_params
* params = array (
* mimeType => (string) // message mime type
* uid => (string) // message uid
* partID => (int) // message part id
* rowID => (string) // row id (mail nm)
* mailbox => (string) // the mailbox where message is stored
* passphrase => (string) // smime private key passphrase
* certAttachedPartID => (int) // partID of attached smime certificate
* fetchAttachmentWithPartID => (int) // PartID of requested attachment to be fetched
* )
*
* @return array
* @throws Exception
*/
function resolveSmimeMessage ($_message, $_params)
{
// default params
$params = array_merge(array(
'partID' => 0,
'passphrase' => '',
'certAttachedPartID' => 0
), $_params);
if (!$_message || empty($_params['uid']) || empty($_params['mailbox']))
{
throw new Exception(__METHOD__.__LINE__.' (): Seems crucial parameter(s) is missing!');
}
$this->smime = new Mail\Smime;
if (!Mail\Smime::isSmimeSignatureOnly($params['mimeType']))
{
try {
$this->mail_bo->restoreSessionData();
$_message = $this->decryptSmimeBody($_message, $params['passphrase'] !='' ?
$params['passphrase'] : $this->mail_bo->sessionData['smime_passphrase']);
} catch (Exception $ex) {
return array('msg', $ex->getMessage());
}
}
try {
$cert = $this->smime->verify($_message);
} catch (Exception $ex) {
// passphrase is required to decrypt the message
if (isset($_message['password_required']))
{
return $_message;
}
}
if ($cert) // signed message, it might be encrypted too
{
$message_parts = $this->smime->extractSignedContents($_message);
$cert_data = array (
'verify' => $cert->verify,
'cert' => $cert->cert,
'msg' => $cert->msg,
'certHtml' => $this->smime->certToHTML($cert->cert),
'partID' => $params['certAttachedPartID'],
'signed' => true,
);
}
else // only encrypted message
{
$message_parts = Horde_Mime_Part::parseMessage($_message, array('forcemime' => true));
}
if (!Mail\Smime::isSmimeSignatureOnly($params['mimeType']))
{
$dec_attachments = $this->mail_bo->getMessageAttachments($params['uid'],$params['partID'],$message_parts,true,false,true,$params['mailbox']);
// mark attachments as smime encrypted
array_walk ($dec_attachments,function (&$_attachment, $_index, $_mimeType){
$_attachment['smime_type'] = $_mimeType;
},$params['mimeType']);
// fetch requested attachment's content
if (!empty($params['fetchAttachmentWithPartID']) && is_array($dec_attachments))
{
return array(
'type' => $message_parts[$params['fetchAttachmentWithPartID']]->getType(),
'charset' => $message_parts[$params['fetchAttachmentWithPartID']]->getContentTypeParameter('charset'),
'filename' => $message_parts[$params['fetchAttachmentWithPartID']]->getName(),
'attachment' => $message_parts[$params['fetchAttachmentWithPartID']]->getContents()
);
}
}
$result = array(
'signed' => false,
'attachments' => is_array($dec_attachments) ? $this->createAttachmentBlock($dec_attachments,$params['rowID'],$params['uid'],$params['mailbox']) : '',
'message' => $this->mail_bo->getMessageBody($params['uid'], '', null, $message_parts)
);
return is_array($cert_data) ? array_merge($result, $cert_data): $result;
}
/** /**
* This is a helper function to trigger Push method * This is a helper function to trigger Push method
* faster than normal 60 sec cycle. * faster than normal 60 sec cycle.
@ -2802,7 +2622,6 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
$uid = $hA['msgUID']; $uid = $hA['msgUID'];
$mailbox = $hA['folder']; $mailbox = $hA['folder'];
$icServerID = $hA['profileID']; $icServerID = $hA['profileID'];
$smime_type = $_GET['smime_type'];
$rememberServerID = $this->mail_bo->profileID; $rememberServerID = $this->mail_bo->profileID;
if ($icServerID && $icServerID != $this->mail_bo->profileID) if ($icServerID && $icServerID != $this->mail_bo->profileID)
{ {
@ -2813,24 +2632,7 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
$is_winmail = $_GET['is_winmail'] ? $_GET['is_winmail'] : 0; $is_winmail = $_GET['is_winmail'] ? $_GET['is_winmail'] : 0;
$this->mail_bo->reopen($mailbox); $this->mail_bo->reopen($mailbox);
if ($smime_type) $attachment = $this->mail_bo->getAttachment($uid,$part,$is_winmail,false);
{
$attachment = $this->resolveSmimeMessage(
$this->mail_bo->getMessageRawBody($uid, null, $mailbox),
array(
'mimeType' => $smime_type,
'uid' => $uid,
'partID' => 0,
'mailbox' => $mailbox,
'fetchAttachmentWithPartID' => $part
)
);
}
else
{
$attachment = $this->mail_bo->getAttachment($uid,$part,$is_winmail,false);
}
$this->mail_bo->closeConnection(); $this->mail_bo->closeConnection();
if ($rememberServerID != $this->mail_bo->profileID) if ($rememberServerID != $this->mail_bo->profileID)
{ {
@ -2901,22 +2703,6 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
} }
//Import failed, download content anyway //Import failed, download content anyway
} }
// Display smime certificate
if (Mail\Smime::isSmime($attachment['type']))
{
$smime = $this->resolveSmimeMessage(
$this->mail_bo->getMessageRawBody($uid, null, $mailbox),
array(
'mimeType' => $attachment['type'],
'uid' => $uid,
'partID' => 0,
'mailbox' => $mailbox
)
);
Api\Header\Content::safe($smime['certHtml'], '', $attachment['type'], $size=0, false, true);
echo $smime['certHtml'];
exit();
}
} }
//error_log(__METHOD__.__LINE__.'->'.array2string($attachment)); //error_log(__METHOD__.__LINE__.'->'.array2string($attachment));
$filename = ($attachment['name']?$attachment['name']:($attachment['filename']?$attachment['filename']:$mailbox.'_uid'.$uid.'_part'.$part)); $filename = ($attachment['name']?$attachment['name']:($attachment['filename']?$attachment['filename']:$mailbox.'_uid'.$uid.'_part'.$part));
@ -3280,56 +3066,42 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
$bufferHtmlOptions = $this->mail_bo->htmlOptions; $bufferHtmlOptions = $this->mail_bo->htmlOptions;
if (empty($htmlOptions)) $htmlOptions = $this->mail_bo->htmlOptions; if (empty($htmlOptions)) $htmlOptions = $this->mail_bo->htmlOptions;
// fetching structure now, to supply it to getMessageBody and getMessageAttachment, so it does not get fetched twice // fetching structure now, to supply it to getMessageBody and getMessageAttachment, so it does not get fetched twice
$structure = $this->mail_bo->getStructure($uid, $partID, $mailbox, false); try
{
if ($smimePassphrase)
{
Api\Cache::setSession('mail', 'smime_passphrase', $smimePassphrase);
}
$structure = $this->mail_bo->getStructure($uid, $partID, $mailbox, false);
if (($smime = $structure->getMetadata('X-EGroupware-Smime')))
{
$attachments = $this->mail_bo->getMessageAttachments($uid, $partID, $structure,true,false,true, $mailbox);
$push = new Api\Json\Push();
if (is_array($attachments))
{
$push->call('app.mail.set_smimeAttachments', $this->createAttachmentBlock($attachments, $_GET['_messageID'], $uid, $mailbox));
}
$push->call('app.mail.set_smimeFlags', $smime);
}
}
catch(Mail\Smime\PassphraseMissing $e)
{
// do NOT include any default CSS
$smimeHtml = $this->get_email_header().
'<div class="smime-message">'.lang("This message is smime encrypted and password protected.").'</div>'.
'<form id="smimePasswordRequest" method="post">'.
'<div class="bg-style"></div>'.
'<div>'.
'<input type="password" placeholder="'.lang("Please enter password").'" name="smime_passphrase"/>'.
'<input type="submit" value="'.lang("submit").'"/>'.
'</div>'.
'</form>';
return $smimeHtml;
}
$calendar_part = null; $calendar_part = null;
$bodyParts = $this->mail_bo->getMessageBody($uid, ($htmlOptions?$htmlOptions:''), $partID, $structure, false, $mailbox, $calendar_part); $bodyParts = $this->mail_bo->getMessageBody($uid, ($htmlOptions?$htmlOptions:''), $partID, $structure, false, $mailbox, $calendar_part);
$mimeType = $structure->getType(); if (!$smime)
if ($mimeType == 'multipart/signed')
{
$param = $structure->getAllContentTypeParameters();
$mimeType = $param['protocol'];
}
if (Mail\Smime::isSmime($mimeType))
{
$smime = $this->resolveSmimeMessage(
$this->mail_bo->getMessageRawBody($uid, $partID, $mailbox),
array(
'mimeType' => $mimeType,
'uid' => $uid,
'partID' => $partID,
'rowID' => $_GET['_messageID'],
'mailbox' => $mailbox,
'passphrase'=> $smimePassphrase
)
);
if ($smime['message'] || $smime['attachments'])
{
if ($smimePassphrase)
{
$this->mail_bo->sessionData['smime_passphrase'] = $smimePassphrase;
$this->mail_bo->saveSessionData();
}
$bodyParts = $smime['message'];
$push = new Api\Json\Push();
if (!empty($smime['attachments'])) $push->call('app.mail.set_smimeAttachments', $smime['attachments']);
}
else if ($smime['password_required'])
{
// do NOT include any default CSS
$smimeHtml = $this->get_email_header().
'<div class="smime-message">'.lang("This message is smime encrypted and password protected.").'</div>'.
'<form id="smimePasswordRequest" method="post">'.
'<div class="bg-style"></div>'.
'<div>'.
'<input type="password" placeholder="'.lang("Please enter password").'" name="smime_passphrase"/>'.
'<input type="submit" value="'.lang("submit").'"/>'.
'</div>'.
'</form>';
return $smimeHtml;
}
}
else
{ {
Api\Session::cache_control(true); Api\Session::cache_control(true);
} }

View File

@ -982,7 +982,21 @@ app.classes.mail = AppJS.extend(
if(widget == null) continue; if(widget == null) continue;
widget.set_value(dataElem.data[data_widgets[id]] || ""); widget.set_value(dataElem.data[data_widgets[id]] || "");
} }
['smime_signature', 'smime_encryption'].forEach(id =>
{
var widget = this.et2.getWidgetById(id);
switch (id)
{
case 'smime_signature':
widget.set_disabled(!(dataElem.data.smime == 'smimeSignature'));
break;
case 'smime_encryption':
widget.set_disabled(!(dataElem.data.smime == 'smimeEncryption'));
break;
default:
widget.set_disabled(true);
}
});
// Blank first, so we don't show previous email while loading // Blank first, so we don't show previous email while loading
var IframeHandle = this.et2.getWidgetById('messageIFRAME'); var IframeHandle = this.et2.getWidgetById('messageIFRAME');
IframeHandle.set_src('about:blank'); IframeHandle.set_src('about:blank');
@ -1018,7 +1032,7 @@ app.classes.mail = AppJS.extend(
jQuery(IframeHandle.getDOMNode()).on('load', function(e){ jQuery(IframeHandle.getDOMNode()).on('load', function(e){
self.resolveExternalImages (this.contentWindow.document); self.resolveExternalImages (this.contentWindow.document);
}); });
if (dataElem.data['smimeSigUrl']) this.smimeAttachmentsCheckerInterval(); if (dataElem.data['smime']) this.smimeAttachmentsCheckerInterval();
} }
var messages = {}; var messages = {};
@ -5753,6 +5767,7 @@ app.classes.mail = AppJS.extend(
{ {
content.data[attachmentArea.id] = _attachments; content.data[attachmentArea.id] = _attachments;
this.et2.setArrayMgr('contnet', content); this.et2.setArrayMgr('contnet', content);
attachmentArea.getDOMNode().classList.remove('loading');
attachmentArea.set_value({content:_attachments}); attachmentArea.set_value({content:_attachments});
if (attachmentArea.id == 'previewAttachmentArea') if (attachmentArea.id == 'previewAttachmentArea')
{ {
@ -5773,6 +5788,8 @@ app.classes.mail = AppJS.extend(
smimeAttachmentsCheckerInterval:function () smimeAttachmentsCheckerInterval:function ()
{ {
var self = this; var self = this;
var attachmentArea = this.et2.getWidgetById('previewAttachmentArea');
if (attachmentArea) attachmentArea.getDOMNode().classList.add('loading');
var interval = window.setInterval(function(){ var interval = window.setInterval(function(){
self.egw.json('mail.mail_ui.ajax_smimeAttachmentsChecker',null,function(_stop){ self.egw.json('mail.mail_ui.ajax_smimeAttachmentsChecker',null,function(_stop){
if (_stop) if (_stop)
@ -5781,5 +5798,26 @@ app.classes.mail = AppJS.extend(
} }
}).sendRequest(true); }).sendRequest(true);
},1000); },1000);
},
/**
*
* @param {object} _data smime resolved certificate data
* @returns {undefined}
*/
set_smimeFlags: function (_data)
{
if (!_data) return;
var attachmentArea = this.et2.getWidgetById('previewAttachmentArea');
if (attachmentArea) attachmentArea.getDOMNode().classList.remove('loading');
var smime_signature = this.et2.getWidgetById('smime_signature');
var smime_encryption = this.et2.getWidgetById('smime_encryption');
smime_signature.set_disabled(!_data.signed);
smime_encryption.set_disabled(!_data.encrypted);
if (!_data.verify)
{
smime_signature.set_statustext(_data.msg);
}
} }
}); });

View File

@ -17,7 +17,6 @@
<hbox class="mailDisplayHeaders" align="right" width="30%"> <hbox class="mailDisplayHeaders" align="right" width="30%">
<description value="Date"/> <description value="Date"/>
<date-time id="mail_displaydate" readonly="true"/> <date-time id="mail_displaydate" readonly="true"/>
<buttononly id="smimeSigBtn" image="smimeSignature" statustext="This message is signed" onclick="app.mail.smimeSigBtn" disabled="!@smimeSigUrl"/>
</hbox> </hbox>
</hbox> </hbox>
@ -47,6 +46,10 @@
<description value="Subject"/> <description value="Subject"/>
<description align="left" id="mail_displaysubject" no_lang="1" readonly="true"/> <description align="left" id="mail_displaysubject" no_lang="1" readonly="true"/>
</hbox> </hbox>
<hbox>
<image id="smime_signature" src="smimeSignature" statustext="Smime signed message" disabled="true" align="right" width="24"/>
<image id="smime_encryption" src="smimeEncryption" statustext="Smime encrypted message" disabled="true" align="right" width="24"/>
</hbox>
</vbox> </vbox>
<vbox class="mailDisplay"> <vbox class="mailDisplay">
<box class="$cont[mailDisplayContainerClass]"> <box class="$cont[mailDisplayContainerClass]">

View File

@ -22,7 +22,6 @@
<hbox id="mailPreviewHeadersDate" class="mailPreviewHeaders"> <hbox id="mailPreviewHeadersDate" class="mailPreviewHeaders">
<description value="Date"/> <description value="Date"/>
<date-time align="left" id="previewDate" readonly="true"/> <date-time align="left" id="previewDate" readonly="true"/>
<button id="previewSmimeSigBtn" image="smimeSignature" statustext="This message is signed" onclick="app.mail.smimeSigBtn"/>
</hbox> </hbox>
<hbox width="100%" id="mailPreviewHeadersTo" class="mailPreviewHeaders"> <hbox width="100%" id="mailPreviewHeadersTo" class="mailPreviewHeaders">
<description value="To"/> <description value="To"/>
@ -67,7 +66,12 @@
</rows> </rows>
</grid> </grid>
<buttononly class="et2_button ui-button" label="Show all attachments" image="foldertree_nolines_plus" width="16px" height="16px" onclick="app.mail.showAllHeader"/> <buttononly class="et2_button ui-button" label="Show all attachments" image="foldertree_nolines_plus" width="16px" height="16px" onclick="app.mail.showAllHeader"/>
<hbox class="mailPreviewHeaders">
<image id="smime_signature" src="smimeSignature" statustext="Smime signed message" disabled="true" align="right" width="24"/>
<image id="smime_encryption" src="smimeEncryption" statustext="Smime encrypted message" disabled="true" align="right" width="24"/>
</hbox>
</hbox> </hbox>
<box id="mailPreviewContainer"> <box id="mailPreviewContainer">
<iframe frameborder="1" id="messageIFRAME" scrolling="auto"/> <iframe frameborder="1" id="messageIFRAME" scrolling="auto"/>
</box> </box>
@ -119,9 +123,7 @@
<url-email id="${row}[toaddress]" contact_plus = "true" readonly="true"/> <url-email id="${row}[toaddress]" contact_plus = "true" readonly="true"/>
<url-email id="${row}[fromaddress]" contact_plus = "true" readonly="true"/> <url-email id="${row}[fromaddress]" contact_plus = "true" readonly="true"/>
<vfs-size align="right" id="${row}[size]" no_lang="1" readonly="true"/> <vfs-size align="right" id="${row}[size]" no_lang="1" readonly="true"/>
<vbox> <image src="$row_cont[smime]" />
<buttononly id="${row}[smimeSigBtn]" image="smimeSignature" disabled="!@$row_cont[smimeSigUrl]"/>
</vbox>
</row> </row>
</rows> </rows>
</grid> </grid>