| | |
| | | Encryption/decryption is done server-side. So, this plugin |
| | | is for users that trust the server. |
| | | |
| | | WARNING! The plugin is in very early state. See below for a list |
| | | of missing features and known issues. |
| | | |
| | | |
| | | Implemented features: |
| | | --------------------- |
| | | + PGP: signatures verification |
| | | + PGP: messages decryption |
| | | + PGP: Sending of encrypted/signed messages |
| | | + PGP: keys management UI (key import, delete) |
| | | + PGP: keys management UI (key import, export, delete) |
| | | + PGP: key generation (client- or server-side) |
| | | + Handling of PGP keys attached to incoming messages |
| | | + User preferences to disable plugin features |
| | | |
| | | TODO (must have): |
| | | ----------------- |
| | | - Keys export to file |
| | | |
| | | TODO (later): |
| | | TODO: |
| | | ------------- |
| | | - Handling of big messages with temp files |
| | | - Key info in contact details page (optional) |
| | |
| | | - revoke, |
| | | - change expiration date, change passphrase, add photo, |
| | | - manage user IDs |
| | | - export private keys |
| | | - Generate revocation certs |
| | | - Search filter to see invalid/expired keys |
| | | - Key server(s) support (search, import, upload, refresh) |
| | |
| | | - S/MIME: Sending signed/encrypted messages |
| | | - S/MIME: Handling of certs attached to incoming messages |
| | | - S/MIME: Certificate info in Contacts details page (optional) |
| | | |
| | | Known issues: |
| | | ------------- |
| | | 1. There are Crypt_GPG issues when using gnupg >= 2.0 |
| | | - http://pear.php.net/bugs/bug.php?id=19914 |
| | | - http://pear.php.net/bugs/bug.php?id=20453 |
| | | - http://pear.php.net/bugs/bug.php?id=20527 |
| | |
| | | |
| | | if (rcmail.gui_objects.keyslist) { |
| | | rcmail.keys_list = new rcube_list_widget(rcmail.gui_objects.keyslist, |
| | | {multiselect:false, draggable:false, keyboard:false}); |
| | | {multiselect:true, draggable:false, keyboard:false}); |
| | | rcmail.keys_list |
| | | .addEventListener('select', function(o) { rcmail.enigma_keylist_select(o); }) |
| | | .addEventListener('keypress', function(o) { rcmail.enigma_keylist_keypress(o); }) |
| | |
| | | rcmail.register_command('search', function(props) {return rcmail.enigma_search(props); }, true); |
| | | rcmail.register_command('reset-search', function(props) {return rcmail.enigma_search_reset(props); }, true); |
| | | rcmail.register_command('plugin.enigma-import', function() { rcmail.enigma_import(); }, true); |
| | | // rcmail.register_command('plugin.enigma-export', function() { rcmail.enigma_key_export(); }, true); |
| | | rcmail.register_command('plugin.enigma-key-import', function() { rcmail.enigma_key_import() }, true); |
| | | rcmail.register_command('plugin.enigma-key-delete', function(props) { return rcmail.enigma_key_delete(); }); |
| | | rcmail.register_command('plugin.enigma-key-export', function() { rcmail.enigma_export(); }); |
| | | rcmail.register_command('plugin.enigma-key-export-selected', function() { rcmail.enigma_export(true); }); |
| | | rcmail.register_command('plugin.enigma-key-import', function() { rcmail.enigma_key_import(); }, true); |
| | | rcmail.register_command('plugin.enigma-key-delete', function(props) { return rcmail.enigma_delete(); }); |
| | | rcmail.register_command('plugin.enigma-key-create', function(props) { return rcmail.enigma_key_create(); }, true); |
| | | rcmail.register_command('plugin.enigma-key-save', function(props) { return rcmail.enigma_key_create_save(); }, true); |
| | | |
| | | rcmail.addEventListener('responseafterplugin.enigmakeys', function() { |
| | | rcmail.enable_command('plugin.enigma-key-export', rcmail.env.rowcount > 0); |
| | | }); |
| | | } |
| | | } |
| | | else if (rcmail.env.task == 'mail') { |
| | |
| | | }; |
| | | |
| | | // Delete key(s) |
| | | rcube_webmail.prototype.enigma_key_delete = function() |
| | | rcube_webmail.prototype.enigma_delete = function() |
| | | { |
| | | var keys = this.keys_list.get_selection(); |
| | | |
| | |
| | | |
| | | // send request to server |
| | | this.http_post('plugin.enigmakeys', post, lock); |
| | | }; |
| | | |
| | | // Export key(s) |
| | | rcube_webmail.prototype.enigma_export = function(selected) |
| | | { |
| | | var keys = selected ? this.keys_list.get_selection().join(',') : '*'; |
| | | |
| | | if (!keys.length) |
| | | return; |
| | | |
| | | this.goto_url('plugin.enigmakeys', {_a: 'export', _keys: keys}); |
| | | }; |
| | | |
| | | // Submit key(s) import form |
| | |
| | | // list row selection handler |
| | | rcube_webmail.prototype.enigma_keylist_select = function(list) |
| | | { |
| | | var id; |
| | | if (id = list.get_single_selection()) |
| | | this.enigma_loadframe('&_action=plugin.enigmakeys&_a=info&_id=' + id); |
| | | var id = list.get_single_selection(), url; |
| | | |
| | | this.enable_command('plugin.enigma-key-delete', list.selection.length > 0); |
| | | if (id) |
| | | url = '&_action=plugin.enigmakeys&_a=info&_id=' + id; |
| | | |
| | | this.enigma_loadframe(url); |
| | | this.enable_command('plugin.enigma-key-delete', 'plugin.enigma-key-export-selected', list.selection.length > 0); |
| | | }; |
| | | |
| | | rcube_webmail.prototype.enigma_keylist_keypress = function(list) |
| | |
| | | this.enigma_loadframe(); |
| | | if (this.keys_list) |
| | | this.keys_list.clear(true); |
| | | |
| | | this.enable_command('plugin.enigma-key-delete', 'plugin.enigma-key-delete-selected', false); |
| | | } |
| | | |
| | | // Adds a row to the list |
| | |
| | | abstract function import($content, $isfile = false); |
| | | |
| | | /** |
| | | * Key/Cert export. |
| | | * |
| | | * @param string Key ID |
| | | * |
| | | * @return mixed Key content or enigma_error |
| | | */ |
| | | abstract function export($key); |
| | | |
| | | /** |
| | | * Keys listing. |
| | | * |
| | | * @param string Optional pattern for key ID, user ID or fingerprint |
| | |
| | | } |
| | | } |
| | | |
| | | public function export($keyid) |
| | | { |
| | | try { |
| | | return $this->gpg->exportPublicKey($keyid, true); |
| | | } |
| | | catch (Exception $e) { |
| | | return $this->get_error_from_exception($e); |
| | | } |
| | | } |
| | | |
| | | public function list_keys($pattern='') |
| | | { |
| | | try { |
| | |
| | | } |
| | | |
| | | /** |
| | | * PGP keys/certs export.. |
| | | * |
| | | * @param string Key ID |
| | | * @param resource Optional output stream |
| | | * |
| | | * @return mixed Key content or enigma_error |
| | | */ |
| | | function export_key($key, $fp = null) |
| | | { |
| | | $this->load_pgp_driver(); |
| | | $result = $this->pgp_driver->export($key, $fp); |
| | | |
| | | if ($result instanceof enigma_error) { |
| | | rcube::raise_error(array( |
| | | 'code' => 600, 'type' => 'php', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => "Enigma plugin: " . $result->getMessage() |
| | | ), true, false); |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | if ($fp) { |
| | | fwrite($fp, $result); |
| | | } |
| | | else { |
| | | return $result; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Registers password for specified key/cert sent by the password prompt. |
| | | */ |
| | | function password_handler() |
| | |
| | | $this->key_import(); |
| | | break; |
| | | |
| | | case 'export': |
| | | $this->key_export(); |
| | | break; |
| | | |
| | | case 'generate': |
| | | $this->key_generate(); |
| | | break; |
| | |
| | | } |
| | | } |
| | | |
| | | $this->rc->output->set_env('rowcount', $size); |
| | | $this->rc->output->set_env('search_request', $search); |
| | | $this->rc->output->set_env('pagecount', ceil($listsize/$pagesize)); |
| | | $this->rc->output->set_env('current_page', $page); |
| | |
| | | $this->enigma->gettext('userids')) . $table->show($attrib)); |
| | | */ |
| | | return $out; |
| | | } |
| | | |
| | | /** |
| | | * Key(s) export handler |
| | | */ |
| | | private function key_export() |
| | | { |
| | | $keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_GPC); |
| | | $engine = $this->enigma->load_engine(); |
| | | $list = $keys == '*' ? $engine->list_keys() : explode(',', $keys); |
| | | |
| | | if (is_array($list)) { |
| | | $filename = 'export.pgp'; |
| | | if (count($list) == 1) { |
| | | $filename = (is_object($list[0]) ? $list[0]->id : $list[0]) . '.pgp'; |
| | | } |
| | | |
| | | // send downlaod headers |
| | | header('Content-Type: application/pgp-keys'); |
| | | header('Content-Disposition: attachment; filename="' . $filename . '"'); |
| | | |
| | | if ($fp = fopen('php://output', 'w')) { |
| | | foreach ($list as $key) { |
| | | $engine->export_key(is_object($key) ? $key->id : $key, $fp); |
| | | } |
| | | } |
| | | } |
| | | |
| | | exit; |
| | | } |
| | | |
| | | /** |
| | |
| | | private function key_delete() |
| | | { |
| | | $keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_POST); |
| | | |
| | | $this->enigma->load_engine(); |
| | | $engine = $this->enigma->load_engine(); |
| | | |
| | | foreach ((array)$keys as $key) { |
| | | $res = $this->enigma->engine->delete_key($key); |
| | | $res = $engine->delete_key($key); |
| | | |
| | | if ($res !== true) { |
| | | $this->rc->output->show_message('enigma.keyremoveerror', 'error'); |
| | |
| | | |
| | | $labels['enterkeypasstitle'] = 'Enter key passphrase'; |
| | | $labels['enterkeypass'] = 'A passphrase is needed to unlock the secret key ($keyid) for user: $user.'; |
| | | $labels['arialabelkeyexportoptions'] = 'Keys export options'; |
| | | |
| | | $messages = array(); |
| | | $messages['sigvalid'] = 'Verified signature from $sender.'; |
| | |
| | | width: 32px; |
| | | height: 32px; |
| | | padding: 0; |
| | | margin-right: 10px; |
| | | margin: 0 5px; |
| | | overflow: hidden; |
| | | background: url(keys_toolbar.png) 0 0 no-repeat transparent; |
| | | opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */ |
| | |
| | | <div id="keystoolbar"> |
| | | <roundcube:button command="plugin.enigma-key-create" type="link" class="buttonPas create" classAct="button create" classSel="button createSel" title="enigma.createkeys" content=" " /> |
| | | <roundcube:button command="plugin.enigma-key-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="enigma.importkeys" content=" " /> |
| | | <!-- |
| | | <span class="dropbutton"> |
| | | <roundcube:button command="plugin.enigma-key-export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="enigma.exportkeys" content=" " /> |
| | | --> |
| | | <span id="exportmenulink" onclick="rcmail_ui.show_popup('exportmenu');return false"></span> |
| | | </span> |
| | | <roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button keymenu" title="enigma.keyactions" onclick="rcmail_ui.show_popup('messagemenu');return false" content=" " /> |
| | | </div> |
| | | |
| | |
| | | <roundcube:object name="searchform" id="quicksearchbox" /> |
| | | <roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" /> |
| | | </div> |
| | | |
| | | |
| | | <div class="enigmascreen"> |
| | | |
| | |
| | | </ul> |
| | | </div> |
| | | |
| | | <div id="exportmenu" class="popupmenu"> |
| | | <ul> |
| | | <li><roundcube:button command="plugin.enigma-key-export" label="exportall" prop="sub" classAct="exportalllink active" class="exportalllink" /></li> |
| | | <li><roundcube:button command="plugin.enigma-key-export-selected" label="exportsel" prop="sub" classAct="exportsellink active" class="exportsellink" /></li> |
| | | </ul> |
| | | </div> |
| | | |
| | | <script type="text/javascript"> |
| | | rcube_init_mail_ui(); |
| | | </script> |
| | |
| | | } |
| | | |
| | | #keystoolbar a.export { |
| | | background-position: center 0; |
| | | background-position: center -40px; |
| | | } |
| | |
| | | <h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> |
| | | <div id="keystoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar"> |
| | | <roundcube:button command="plugin.enigma-key-import" type="link" class="button import disabled" classAct="button import" classSel="button import pressed" label="import" title="enigma.importkeys" /> |
| | | <!-- |
| | | <span class="dropbutton"> |
| | | <roundcube:button command="plugin.enigma-key-export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="export" title="enigma.exportkeys" /> |
| | | --> |
| | | <a href="#export" class="dropbuttontip" id="exportmenulink" onclick="return UI.toggle_popup('exportmenu',event)" aria-haspopup="true" aria-expanded="false" aria-owns="exportmenu-menu" tabindex="0"><roundcube:label name="enigma.arialabelkeyexportoptions" /></a> |
| | | </span> |
| | | </div> |
| | | |
| | | <div id="exportmenu" class="popupmenu" aria-hidden="true"> |
| | | <h3 id="aria-label-exportmenu" class="voice"><roundcube:label name="enigma.arialabelkeyexportoptions" /></h3> |
| | | <ul id="exportmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-exportmenu"> |
| | | <roundcube:button type="link-menuitem" command="plugin.enigma-key-export" label="exportall" prop="sub" class="exportalllink" classAct="exportalllink active" /> |
| | | <roundcube:button type="link-menuitem" command="plugin.enigma-key-export-selected" label="exportsel" prop="sub" class="exportsellink" classAct="exportsellink active" /> |
| | | </ul> |
| | | </div> |
| | | |
| | | <div id="quicksearchbar" class="searchbox" role="search" aria-labelledby="aria-label-searchform"> |