From 58c2798fae7749cf7b4aee471a696aed389d0941 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 7 Jun 2015 17:54:01 +0200 Subject: [PATCH] Implemented password prompt when entering compose page of encrypted message --- plugins/enigma/README | 3 - plugins/enigma/enigma.js | 39 +++++----- plugins/enigma/enigma.php | 26 +++++-- plugins/enigma/lib/enigma_driver.php | 2 +- plugins/enigma/lib/enigma_driver_gnupg.php | 3 +- plugins/enigma/lib/enigma_driver_phpssl.php | 3 +- plugins/enigma/lib/enigma_engine.php | 80 +++++++++++--------- plugins/enigma/lib/enigma_error.php | 3 +- plugins/enigma/lib/enigma_key.php | 3 +- plugins/enigma/lib/enigma_mime_message.php | 3 +- plugins/enigma/lib/enigma_signature.php | 3 +- plugins/enigma/lib/enigma_subkey.php | 3 +- plugins/enigma/lib/enigma_ui.php | 83 ++++++++++++++++----- plugins/enigma/lib/enigma_userid.php | 3 +- 14 files changed, 168 insertions(+), 89 deletions(-) diff --git a/plugins/enigma/README b/plugins/enigma/README index 0566069ff..2ccd0474f 100644 --- a/plugins/enigma/README +++ b/plugins/enigma/README @@ -24,9 +24,6 @@ Implemented features: TODO (must have): ----------------- - Keys export to file -- Disable Reply/Forward options when viewing encrypted messages - until they are decrypted successfully -- Handling of replying/forwarding of encrypted/signed messages - Client-side keys generation (with OpenPGP.js?) TODO (later): diff --git a/plugins/enigma/enigma.js b/plugins/enigma/enigma.js index 4048d8d85..21551e38e 100644 --- a/plugins/enigma/enigma.js +++ b/plugins/enigma/enigma.js @@ -41,10 +41,9 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { e.stopPropagation(); }); } - else if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') { - if (rcmail.env.enigma_password_request) { - rcmail.enigma_password_request(rcmail.env.enigma_password_request); - } + + if (rcmail.env.enigma_password_request) { + rcmail.enigma_password_request(rcmail.env.enigma_password_request); } } }); @@ -324,15 +323,16 @@ rcube_webmail.prototype.enigma_password_request = function(data) click: function(e) { e.stopPropagation(); - var jq = ref.is_framed() ? window.parent.$ : $, - pass = myprompt_input.val(); + var jq = ref.is_framed() ? window.parent.$ : $; - if (!pass) { + data.password = myprompt_input.val(); + + if (!data.password) { myprompt_input.focus(); return; } - ref.enigma_password_submit(data.key, pass); + ref.enigma_password_submit(data); jq(this).remove(); } }, @@ -352,34 +352,37 @@ rcube_webmail.prototype.enigma_password_request = function(data) } // submit entered password -rcube_webmail.prototype.enigma_password_submit = function(keyid, password) +rcube_webmail.prototype.enigma_password_submit = function(data) { - if (this.env.action == 'compose') { - return this.enigma_password_compose_submit(keyid, password); + if (this.env.action == 'compose' && !data['compose-init']) { + return this.enigma_password_compose_submit(data); } + var lock = this.set_busy(true, 'loading'); + // message preview var form = $('
').attr({method: 'post', action: location.href, style: 'display:none'}) - .append($('').attr({type: 'hidden', name: '_keyid', value: keyid})) - .append($('').attr({type: 'hidden', name: '_passwd', value: password})) + .append($('').attr({type: 'hidden', name: '_keyid', value: data.key})) + .append($('').attr({type: 'hidden', name: '_passwd', value: data.password})) .append($('').attr({type: 'hidden', name: '_token', value: this.env.request_token})) + .append($('').attr({type: 'hidden', name: '_unlock', value: lock})) .appendTo(document.body); form.submit(); } // submit entered password - in mail compose page -rcube_webmail.prototype.enigma_password_compose_submit = function(keyid, password) +rcube_webmail.prototype.enigma_password_compose_submit = function(data) { var form = this.gui_objects.messageform; if (!$('input[name="_keyid"]', form).length) { - $(form).append($('').attr({type: 'hidden', name: '_keyid', value: keyid})) - .append($('').attr({type: 'hidden', name: '_passwd', value: password})); + $(form).append($('').attr({type: 'hidden', name: '_keyid', value: data.key})) + .append($('').attr({type: 'hidden', name: '_passwd', value: data.password})); } else { - $('input[name="_keyid"]', form).val(keyid); - $('input[name="_passwd"]', form).val(password); + $('input[name="_keyid"]', form).val(data.key); + $('input[name="_passwd"]', form).val(data.password); } this.submit_messageform(this.env.last_action == 'savedraft'); diff --git a/plugins/enigma/enigma.php b/plugins/enigma/enigma.php index 3b9aa0bb9..1ac619c62 100644 --- a/plugins/enigma/enigma.php +++ b/plugins/enigma/enigma.php @@ -1,5 +1,6 @@ rc->action == 'compose') { + $this->add_hook('message_compose_body', array($this, 'message_compose')); + $this->load_ui(); $this->ui->init(); } @@ -437,6 +439,16 @@ class enigma extends rcube_plugin return $this->ui->message_ready($p); } + /** + * Handle message_compose_body hook + */ + function message_compose($p) + { + $this->load_ui(); + + return $this->ui->message_compose($p); + } + /** * Handler for refresh hook. */ diff --git a/plugins/enigma/lib/enigma_driver.php b/plugins/enigma/lib/enigma_driver.php index 49208b39d..4c8340e08 100644 --- a/plugins/enigma/lib/enigma_driver.php +++ b/plugins/enigma/lib/enigma_driver.php @@ -1,6 +1,6 @@ rc = rcmail::get_instance(); $this->enigma = $enigma; - $this->password_time = $this->rc->config->get('enigma_password_time'); + $this->password_time = $this->rc->config->get('enigma_password_time') * 60; // this will remove passwords from session after some time if ($this->password_time) { @@ -485,7 +487,7 @@ class enigma_engine // Store signature data for display if (!empty($sig)) { $this->signed_parts[$part->mime_id] = $part->mime_id; - $this->signatures[$part->mime_id] = $sig; + $this->signatures[$part->mime_id] = $sig; } fclose($fh); @@ -495,7 +497,7 @@ class enigma_engine * Handler for PGP/MIME signed message. * Verifies signature. * - * @param array Reference to hook's parameters + * @param array Reference to hook's parameters */ private function parse_pgp_signed(&$p) { @@ -503,34 +505,35 @@ class enigma_engine return; } - // Verify signature - if ($this->rc->action == 'show' || $this->rc->action == 'preview') { - $this->load_pgp_driver(); - $struct = $p['structure']; + if ($this->rc->action != 'show' && $this->rc->action != 'preview') { + return; + } - $msg_part = $struct->parts[0]; - $sig_part = $struct->parts[1]; + $this->load_pgp_driver(); + $struct = $p['structure']; - // Get bodies - // Note: The first part body need to be full part body with headers - // it also cannot be decoded - $msg_body = $this->get_part_body($p['object'], $msg_part->mime_id, true); - $sig_body = $this->get_part_body($p['object'], $sig_part->mime_id); + $msg_part = $struct->parts[0]; + $sig_part = $struct->parts[1]; - // Verify - $sig = $this->pgp_verify($msg_body, $sig_body); + // Get bodies + // Note: The first part body need to be full part body with headers + // it also cannot be decoded + $msg_body = $this->get_part_body($p['object'], $msg_part->mime_id, true); + $sig_body = $this->get_part_body($p['object'], $sig_part->mime_id); - // Store signature data for display - $this->signatures[$struct->mime_id] = $sig; + // Verify + $sig = $this->pgp_verify($msg_body, $sig_body); - // Message can be multipart (assign signature to each subpart) - if (!empty($msg_part->parts)) { - foreach ($msg_part->parts as $part) - $this->signed_parts[$part->mime_id] = $struct->mime_id; - } - else { - $this->signed_parts[$msg_part->mime_id] = $struct->mime_id; - } + // Store signature data for display + $this->signatures[$struct->mime_id] = $sig; + + // Message can be multipart (assign signature to each subpart) + if (!empty($msg_part->parts)) { + foreach ($msg_part->parts as $part) + $this->signed_parts[$part->mime_id] = $struct->mime_id; + } + else { + $this->signed_parts[$msg_part->mime_id] = $struct->mime_id; } } @@ -976,6 +979,9 @@ class enigma_engine $this->rc->output->send(); } + /** + * Registers password for specified key/cert sent by the password prompt. + */ function password_handler() { $keyid = rcube_utils::get_input_value('_keyid', rcube_utils::INPUT_POST); @@ -986,6 +992,9 @@ class enigma_engine } } + /** + * Saves key/cert password in user session + */ function save_password($keyid, $password) { // we store passwords in session for specified time @@ -999,6 +1008,9 @@ class enigma_engine $_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config)); } + /** + * Returns currently stored passwords + */ function get_passwords() { if ($config = $_SESSION['enigma_pass']) { @@ -1011,7 +1023,7 @@ class enigma_engine // delete expired passwords foreach ((array) $config as $key => $value) { - if ($pass_time && $value[1] < $threshold) { + if ($threshold && $value[1] < $threshold) { unset($config[$key]); $modified = true; } diff --git a/plugins/enigma/lib/enigma_error.php b/plugins/enigma/lib/enigma_error.php index 1717a7c46..91c281f46 100644 --- a/plugins/enigma/lib/enigma_error.php +++ b/plugins/enigma/lib/enigma_error.php @@ -1,5 +1,6 @@ getData('missing'); @@ -143,6 +145,10 @@ class enigma_ui $data = array('keyid' => key($data), 'user' => $data[key($data)]); + if (!empty($params)) { + $data = array_merge($params, $data); + } + if ($this->rc->action == 'send') { $this->rc->output->command('enigma_password_request', $data); } @@ -337,23 +343,28 @@ class enigma_ui */ function tpl_key_data($attrib) { - $out = ''; + $out = ''; $table = new html_table(array('cols' => 2)); // Key user ID $table->add('title', $this->enigma->gettext('keyuserid')); $table->add(null, rcube::Q($this->data->name)); + // Key ID $table->add('title', $this->enigma->gettext('keyid')); $table->add(null, $this->data->subkeys[0]->get_short_id()); + // Key type $keytype = $this->data->get_type(); - if ($keytype == enigma_key::TYPE_KEYPAIR) + if ($keytype == enigma_key::TYPE_KEYPAIR) { $type = $this->enigma->gettext('typekeypair'); - else if ($keytype == enigma_key::TYPE_PUBLIC) + } + else if ($keytype == enigma_key::TYPE_PUBLIC) { $type = $this->enigma->gettext('typepublickey'); + } $table->add('title', $this->enigma->gettext('keytype')); $table->add(null, $type); + // Key fingerprint $table->add('title', $this->enigma->gettext('fingerprint')); $table->add(null, $this->data->subkeys[0]->get_fingerprint()); @@ -476,6 +487,9 @@ class enigma_ui $this->rc->output->send(); } + /** + * Init compose UI (add task button and the menu) + */ private function compose_ui() { $this->add_css(); @@ -493,12 +507,6 @@ class enigma_ui 'height' => 32 ), 'toolbar'); - // Options menu contents - $this->enigma->add_hook('render_page', array($this, 'compose_menu')); - } - - function compose_menu($p) - { $menu = new html_table(array('cols' => 2)); $chbox = new html_checkbox(array('value' => 1)); @@ -512,12 +520,10 @@ class enigma_ui $menu->add(null, $chbox->show($this->rc->config->get('enigma_encrypt_all') ? 1 : 0, array('name' => '_enigma_encrypt', 'id' => 'enigmaencryptopt'))); - $menu = html::div(array('id' => 'enigmamenu', 'class' => 'popupmenu'), - $menu->show()); + $menu = html::div(array('id' => 'enigmamenu', 'class' => 'popupmenu'), $menu->show()); - $p['content'] .= $menu; - - return $p; + // Options menu contents + $this->rc->output->add_footer($menu); } /** @@ -646,7 +652,7 @@ class enigma_ui { $engine = $this->enigma->load_engine(); - // handle attachments vcard attachments + // handle keys/certs in attachments foreach ((array) $p['object']->attachments as $attachment) { if ($engine->is_keys_part($attachment)) { $this->keys_parts[] = $attachment->mime_id; @@ -746,4 +752,45 @@ class enigma_ui return $p; } + /** + * Handler for message_compose_body hook + * Display error when the message cannot be encrypted + * and provide a way to try again with a password. + */ + function message_compose($p) + { + $engine = $this->enigma->load_engine(); + + // skip: message has no signed/encoded content + if (!$this->enigma->engine) { + return $p; + } + + $engine = $this->enigma->engine; + + // Decryption status + foreach ($engine->decryptions as $status) { + if ($status instanceof enigma_error) { + $code = $status->getCode(); + + if ($code == enigma_error::E_KEYNOTFOUND) { + $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')), + $this->enigma->gettext('decryptnokey'))); + } + else if ($code == enigma_error::E_BADPASS) { + $this->password_prompt($status, array('compose-init' => true)); + return $p; + } + else { + $msg = rcube::Q($this->enigma->gettext('decrypterror')); + } + } + } + + if ($msg) { + $this->rc->output->show_message($msg, 'error'); + } + + return $p; + } } diff --git a/plugins/enigma/lib/enigma_userid.php b/plugins/enigma/lib/enigma_userid.php index da0358445..11baef4db 100644 --- a/plugins/enigma/lib/enigma_userid.php +++ b/plugins/enigma/lib/enigma_userid.php @@ -1,5 +1,6 @@