From 2d8b8fc5dcf1245a4e48d65ce23e6859df02bc2d Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Thu, 26 Jan 2017 17:45:07 +0100 Subject: [PATCH] WIP of SMIME support: First attempt to decrypt a smime encrypted message --- api/src/Mail/Smime.php | 40 ++++++++++--- mail/inc/class.mail_ui.inc.php | 105 +++++++++++++++++++++++++++------ 2 files changed, 118 insertions(+), 27 deletions(-) diff --git a/api/src/Mail/Smime.php b/api/src/Mail/Smime.php index 7aeaa5673f..9bf8ef5f94 100644 --- a/api/src/Mail/Smime.php +++ b/api/src/Mail/Smime.php @@ -35,25 +35,35 @@ class Smime extends Horde_Crypt_Smime 'application/pkcs7-signature', ); + /** + * SMIME signature only types + * @var type + */ + static $SMIME_SIGNATURE_ONLY_TYPES = array ( + 'application/x-pkcs7-signature', + 'application/pkcs7-signature', + 'multipart/signed' + ); + /* * SMIME public key regular expresion */ - static public $pubkey_regexp = '/-----BEGIN PUBLIC KEY-----.*-----END PUBLIC KEY-----\r?\n/s/'; + static public $pubkey_regexp = '/-----BEGIN PUBLIC KEY-----.*-----END PUBLIC KEY-----\r?\n/s'; /* * SMIME encrypted private key regular expresion */ - static public $privkey_encrypted_regexp = '/-----BEGIN ENCRYPTED PRIVATE KEY-----.*-----END ENCRYPTED PRIVATE KEY-----\r?\n/s/'; + static public $privkey_encrypted_regexp = '/-----BEGIN ENCRYPTED PRIVATE KEY-----.*-----END ENCRYPTED PRIVATE KEY-----\r?\n/s'; /* * SMIME private key regular expresion */ - static public $privkey_regexp = '/-----BEGIN PRIVATE KEY-----.*-----END PRIVATE KEY-----\r?\n/s/'; + static public $privkey_regexp = '/-----BEGIN PRIVATE KEY-----.*-----END PRIVATE KEY-----\r?\n/s'; /* * SMIME certificate regular expresion */ - static public $certificate_regexp = '/-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----\r?\n/s/'; + static public $certificate_regexp = '/-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----\r?\n/s'; /** * Constructor. @@ -77,6 +87,18 @@ class Smime extends Horde_Crypt_Smime return in_array($_mime, self::$SMIME_TYPES); } + /** + * Check if a given mime type is smime type of signature only + * + * @param string $_mime mimetype + * + * @return type + */ + public static function isSmimeSignatureOnly ($_mime) + { + return in_array($_mime, self::$SMIME_SIGNATURE_ONLY_TYPES); + } + /** * Check if the openssl is supported * @@ -96,7 +118,8 @@ class Smime extends Horde_Crypt_Smime /** * Extract public key from certificate * - * @param type $cert + * @param string $cert content of certificate in PEM format + * * @return string returns public key */ public function get_publickey ($cert) @@ -109,11 +132,12 @@ class Smime extends Horde_Crypt_Smime /** * Extract certificates info from a p12 file * - * @param string $pkcs12 - * @param string $passphrase + * @param string $pkcs12 content of p12 file in string + * @param string $passphrase = '', passphrase to unlock the p12 file + * * @return boolean|array returns array of certs info or false if not successful */ - public function extractCertPKCS12 ($pkcs12, $passphrase) + public function extractCertPKCS12 ($pkcs12, $passphrase = '') { $certs = array (); if (openssl_pkcs12_read($pkcs12, $certs, $passphrase)) diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index f387dcee0a..bceec3a1ae 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -2011,15 +2011,23 @@ $filter['before']= date("d-M-Y", $cutoffdate2); if (is_array($smimeData)) { $error_msg[] = $smimeData['msg']; - $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['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['required_password']) + { + $response = Api\Json\Response::get(); + $response->call('app.mail.smimeRequestPassphrase'); + } } //error_log(__METHOD__.__LINE__.array2string($attachments)); @@ -2091,6 +2099,35 @@ $filter['before']= date("d-M-Y", $cutoffdate2); $etpl->exec('mail.mail_ui.displayMessage',$content,$sel_options,$readonlys,$preserv,2); } + /** + * 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 from smime attachment * @@ -2102,21 +2139,40 @@ $filter['before']= date("d-M-Y", $cutoffdate2); */ function resolveSmimeAttachment (&$attachments, $_uid, $_partID, $_mailbox) { - $smime = new Mail\Smime; + $this->smime = new Mail\Smime; foreach ($attachments as $key => $attachment) { if (Mail\Smime::isSmime($attachment['mimeType'])) { - $message = $this->mail_bo->getMessageRawBody($_uid,$_partID,$_mailbox); - $cert = $smime->verify($message); - $data = array ( - 'verify' => $cert->verify, - 'cert' => $cert->cert, - 'msg' => $cert->msg, - 'certHtml' => $smime->certToHTML($cert->cert), - 'partID' => $attachment['partID'] - ); + $message = $this->mail_bo->getMessageRawBody($_uid, $_partID, $_mailbox); + + if (!Mail\Smime::isSmimeSignatureOnly($attachment['mimeType'])) + { + try { + $message = $this->decryptSmimeBody($message); + } catch (Exception $ex) { + return array('msg', $ex->getMessage()); + } + } + try { + $cert = $this->smime->verify($message); + $data = array ( + 'verify' => $cert->verify, + 'cert' => $cert->cert, + 'msg' => $cert->msg, + 'certHtml' => $this->smime->certToHTML($cert->cert), + 'partID' => $attachment['partID'], + 'signed' => true, + 'message' => $message + ); + + } catch (Exception $ex) { + $data = array ( + 'signed' => false, + 'message' => $message + ); + } unset ($attachments[$key]); } } @@ -2930,7 +2986,18 @@ $filter['before']= date("d-M-Y", $cutoffdate2); $structure = $this->mail_bo->getStructure($uid, $partID, $mailbox, false); $calendar_part = null; $bodyParts = $this->mail_bo->getMessageBody($uid, ($htmlOptions?$htmlOptions:''), $partID, $structure, false, $mailbox, $calendar_part); + $mimeType = $structure->getType(); + if (Mail\Smime::isSmime( $mimeType) && !Mail\Smime::isSmimeSignatureOnly($mimeType)) + { + $smimeAttachments = array ( array('mimeType' => $mimeType)); + $smime = $this->resolveSmimeAttachment($smimeAttachments, $uid, 0, $mailbox); + if ($smime['message']) + { + $bodyParts[0]['body'] = $smime['message']; + } + + } // for meeting requests (multipart alternative with text/calendar part) let calendar render it if ($calendar_part && isset($GLOBALS['egw_info']['user']['apps']['calendar'])) {