Aleksander Machniak
2015-06-07 58c2798fae7749cf7b4aee471a696aed389d0941
Implemented password prompt when entering compose page of encrypted message
14 files modified
257 ■■■■■ changed files
plugins/enigma/README 3 ●●●●● patch | view | raw | blame | history
plugins/enigma/enigma.js 39 ●●●● patch | view | raw | blame | history
plugins/enigma/enigma.php 26 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver.php 2 ●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver_gnupg.php 3 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver_phpssl.php 3 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_engine.php 80 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_error.php 3 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_key.php 3 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_mime_message.php 3 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_signature.php 3 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_subkey.php 3 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_ui.php 83 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_userid.php 3 ●●●● patch | view | raw | blame | history
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                                     |
 |                                                                         |