From 58c2798fae7749cf7b4aee471a696aed389d0941 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Sun, 07 Jun 2015 11:54:01 -0400 Subject: [PATCH] Implemented password prompt when entering compose page of encrypted message --- plugins/enigma/enigma.php | 26 +++- plugins/enigma/lib/enigma_mime_message.php | 3 plugins/enigma/lib/enigma_driver_gnupg.php | 3 plugins/enigma/lib/enigma_key.php | 3 plugins/enigma/README | 3 plugins/enigma/lib/enigma_driver.php | 2 plugins/enigma/enigma.js | 39 ++++--- plugins/enigma/lib/enigma_engine.php | 80 +++++++++------ plugins/enigma/lib/enigma_subkey.php | 3 plugins/enigma/lib/enigma_error.php | 3 plugins/enigma/lib/enigma_userid.php | 3 plugins/enigma/lib/enigma_ui.php | 83 +++++++++++++--- plugins/enigma/lib/enigma_signature.php | 3 plugins/enigma/lib/enigma_driver_phpssl.php | 3 14 files changed, 168 insertions(+), 89 deletions(-) diff --git a/plugins/enigma/README b/plugins/enigma/README index 0566069..2ccd047 100644 --- a/plugins/enigma/README +++ b/plugins/enigma/README @@ -24,9 +24,6 @@ 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 4048d8d..21551e3 100644 --- a/plugins/enigma/enigma.js +++ b/plugins/enigma/enigma.js @@ -41,10 +41,9 @@ 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 @@ 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 @@ } // 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 = $('<form>').attr({method: 'post', action: location.href, style: 'display:none'}) - .append($('<input>').attr({type: 'hidden', name: '_keyid', value: keyid})) - .append($('<input>').attr({type: 'hidden', name: '_passwd', value: password})) + .append($('<input>').attr({type: 'hidden', name: '_keyid', value: data.key})) + .append($('<input>').attr({type: 'hidden', name: '_passwd', value: data.password})) .append($('<input>').attr({type: 'hidden', name: '_token', value: this.env.request_token})) + .append($('<input>').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($('<input>').attr({type: 'hidden', name: '_keyid', value: keyid})) - .append($('<input>').attr({type: 'hidden', name: '_passwd', value: password})); + $(form).append($('<input>').attr({type: 'hidden', name: '_keyid', value: data.key})) + .append($('<input>').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 3b9aa0b..1ac619c 100644 --- a/plugins/enigma/enigma.php +++ b/plugins/enigma/enigma.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | Enigma Plugin for Roundcube | | | @@ -14,11 +15,10 @@ +-------------------------------------------------------------------------+ */ -/* - This class contains only hooks and action handlers. - Most plugin logic is placed in enigma_engine and enigma_ui classes. -*/ - +/** + * This class contains only hooks and action handlers. + * Most plugin logic is placed in enigma_engine and enigma_ui classes. + */ class enigma extends rcube_plugin { public $task = 'mail|settings'; @@ -26,7 +26,7 @@ public $engine; public $ui; - private $env_loaded = false; + private $env_loaded = false; /** @@ -51,6 +51,8 @@ } // message composing else if ($this->rc->action == 'compose') { + $this->add_hook('message_compose_body', array($this, 'message_compose')); + $this->load_ui(); $this->ui->init(); } @@ -438,6 +440,16 @@ } /** + * Handle message_compose_body hook + */ + function message_compose($p) + { + $this->load_ui(); + + return $this->ui->message_compose($p); + } + + /** * Handler for refresh hook. */ function refresh($p) diff --git a/plugins/enigma/lib/enigma_driver.php b/plugins/enigma/lib/enigma_driver.php index 49208b3..4c8340e 100644 --- a/plugins/enigma/lib/enigma_driver.php +++ b/plugins/enigma/lib/enigma_driver.php @@ -1,6 +1,6 @@ <?php -/* +/** +-------------------------------------------------------------------------+ | Abstract driver for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php index 52a0ad6..d0b854c 100644 --- a/plugins/enigma/lib/enigma_driver_gnupg.php +++ b/plugins/enigma/lib/enigma_driver_gnupg.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | GnuPG (PGP) driver for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_driver_phpssl.php b/plugins/enigma/lib/enigma_driver_phpssl.php index 0250893..a2d73f4 100644 --- a/plugins/enigma/lib/enigma_driver_phpssl.php +++ b/plugins/enigma/lib/enigma_driver_phpssl.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | S/MIME driver for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php index 0111d93..85c2882 100644 --- a/plugins/enigma/lib/enigma_engine.php +++ b/plugins/enigma/lib/enigma_engine.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | Engine of the Enigma Plugin | | | @@ -14,12 +15,13 @@ +-------------------------------------------------------------------------+ */ -/* - RFC2440: OpenPGP Message Format - RFC3156: MIME Security with OpenPGP - RFC3851: S/MIME -*/ - +/** + * Enigma plugin engine. + * + * RFC2440: OpenPGP Message Format + * RFC3156: MIME Security with OpenPGP + * RFC3851: S/MIME + */ class enigma_engine { private $rc; @@ -49,7 +51,7 @@ $this->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 @@ // 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 @@ * 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 @@ 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 @@ $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 @@ } } + /** + * Saves key/cert password in user session + */ function save_password($keyid, $password) { // we store passwords in session for specified time @@ -999,6 +1008,9 @@ $_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config)); } + /** + * Returns currently stored passwords + */ function get_passwords() { if ($config = $_SESSION['enigma_pass']) { @@ -1011,7 +1023,7 @@ // 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 1717a7c..91c281f 100644 --- a/plugins/enigma/lib/enigma_error.php +++ b/plugins/enigma/lib/enigma_error.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | Error class for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_key.php b/plugins/enigma/lib/enigma_key.php index 8c61cbd..734cbb8 100644 --- a/plugins/enigma/lib/enigma_key.php +++ b/plugins/enigma/lib/enigma_key.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | Key class for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_mime_message.php b/plugins/enigma/lib/enigma_mime_message.php index feed78e..eae8cb7 100644 --- a/plugins/enigma/lib/enigma_mime_message.php +++ b/plugins/enigma/lib/enigma_mime_message.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | Mail_mime wrapper for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_signature.php b/plugins/enigma/lib/enigma_signature.php index 2e63a80..4207435 100644 --- a/plugins/enigma/lib/enigma_signature.php +++ b/plugins/enigma/lib/enigma_signature.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | Signature class for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_subkey.php b/plugins/enigma/lib/enigma_subkey.php index cd57611..7604473 100644 --- a/plugins/enigma/lib/enigma_subkey.php +++ b/plugins/enigma/lib/enigma_subkey.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | SubKey class for the Enigma Plugin | | | diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php index e866ba3..c76583e 100644 --- a/plugins/enigma/lib/enigma_ui.php +++ b/plugins/enigma/lib/enigma_ui.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | User Interface for the Enigma Plugin | | | @@ -131,9 +132,10 @@ /** * Initializes key password prompt * - * @param enigma_error Error object with key info + * @param enigma_error $status Error object with key info + * @param array $params Optional prompt parameters */ - function password_prompt($status) + function password_prompt($status, $params = array()) { $data = $status->getData('missing'); @@ -142,6 +144,10 @@ } $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 @@ */ 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 @@ $this->rc->output->send(); } + /** + * Init compose UI (add task button and the menu) + */ private function compose_ui() { $this->add_css(); @@ -493,12 +507,6 @@ '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 @@ $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 @@ { $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 @@ 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 da03584..11baef4 100644 --- a/plugins/enigma/lib/enigma_userid.php +++ b/plugins/enigma/lib/enigma_userid.php @@ -1,5 +1,6 @@ <?php -/* + +/** +-------------------------------------------------------------------------+ | User ID class for the Enigma Plugin | | | -- Gitblit v1.9.1