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): 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'); 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) plugins/enigma/lib/enigma_driver.php
@@ -1,6 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | Abstract driver for the Enigma Plugin | | | plugins/enigma/lib/enigma_driver_gnupg.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | GnuPG (PGP) driver for the Enigma Plugin | | | plugins/enigma/lib/enigma_driver_phpssl.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | S/MIME driver for the Enigma Plugin | | | 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; } plugins/enigma/lib/enigma_error.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | Error class for the Enigma Plugin | | | plugins/enigma/lib/enigma_key.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | Key class for the Enigma Plugin | | | plugins/enigma/lib/enigma_mime_message.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | Mail_mime wrapper for the Enigma Plugin | | | plugins/enigma/lib/enigma_signature.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | Signature class for the Enigma Plugin | | | plugins/enigma/lib/enigma_subkey.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | SubKey class for the Enigma Plugin | | | 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; } } plugins/enigma/lib/enigma_userid.php
@@ -1,5 +1,6 @@ <?php /* /** +-------------------------------------------------------------------------+ | User ID class for the Enigma Plugin | | |