Enigma: Implement PGP encryption and signing in one go (#5302)

Enigma: Display signature verification status for encrypted+signed messages (#5302)
pull/5268/merge
Aleksander Machniak 8 years ago
parent 04aa29493c
commit f1c260b05f

@ -1,6 +1,8 @@
CHANGELOG Roundcube Webmail
===========================
- Enigma: Implement PGP encryption and signing in one go (#5302)
- Enigma: Display signature verification status for encrypted+signed messages (#5302)
- Display different attachment icon on encrypted messages
- Display different confirmation text when moving messages to Trash (#5220)
- Indicate that a collapsed thread has flagged children (#5013)

@ -32,22 +32,27 @@ abstract class enigma_driver
abstract function init();
/**
* Encryption.
* Encryption (and optional signing).
*
* @param string Message body
* @param array List of key-password mapping
* @param string Optional signing Key ID
* @param string Optional signing Key password
*
* @return mixed Encrypted message or enigma_error on failure
*/
abstract function encrypt($text, $keys);
abstract function encrypt($text, $keys, $sign_key = null, $sign_pass = null);
/**
* Decryption.
* Decryption (and sig verification if sig exists).
*
* @param string Encrypted message
* @param array List of key-password mapping
* @param string Encrypted message
* @param array List of key-password mapping
* @param enigma_signature Signature information (if available)
*
* @return mixed Decrypted message or enigma_error on failure
*/
abstract function decrypt($text, $keys = array());
abstract function decrypt($text, $keys = array(), &$signature = null);
/**
* Signing.

@ -95,20 +95,27 @@ class enigma_driver_gnupg extends enigma_driver
}
/**
* Encryption.
* Encryption (and optional signing).
*
* @param string Message body
* @param array List of key-password mapping
* @param string Optional signing Key ID
* @param string Optional signing Key password
*
* @return mixed Encrypted message or enigma_error on failure
*/
function encrypt($text, $keys)
function encrypt($text, $keys, $sign_key = null, $sign_pass = null)
{
try {
foreach ($keys as $key) {
$this->gpg->addEncryptKey($key);
}
if ($sign_key) {
$this->gpg->addSignKey($sign_key, $sign_pass);
return $this->gpg->encryptAndSign($text, true);
}
return $this->gpg->encrypt($text, true);
}
catch (Exception $e) {
@ -117,21 +124,28 @@ class enigma_driver_gnupg extends enigma_driver
}
/**
* Decrypt a message
* Decrypt a message (and verify if signature found)
*
* @param string Encrypted message
* @param array List of key-password mapping
* @param string Encrypted message
* @param array List of key-password mapping
* @param enigma_signature Signature information (if available)
*
* @return mixed Decrypted message or enigma_error on failure
*/
function decrypt($text, $keys = array())
function decrypt($text, $keys = array(), &$signature = null)
{
try {
foreach ($keys as $key => $password) {
$this->gpg->addDecryptKey($key, $password);
}
return $this->gpg->decrypt($text);
$result = $this->gpg->decryptAndVerify($text);
if (!empty($result['signatures'])) {
$signature = $this->parse_signature($result['signatures'][0]);
}
return $result['data'];
}
catch (Exception $e) {
return $this->get_error_from_exception($e);

@ -67,11 +67,11 @@ class enigma_driver_phpssl extends enigma_driver
}
function encrypt($text, $keys)
function encrypt($text, $keys, $sign_key = null, $sign_pass = null)
{
}
function decrypt($text, $keys = array())
function decrypt($text, $keys = array(), &$signature = null)
{
}
@ -142,14 +142,6 @@ class enigma_driver_phpssl extends enigma_driver
{
}
public function delete_privkey($keyid)
{
}
public function delete_pubkey($keyid)
{
}
/**
* Converts Crypt_GPG_Key object into Enigma's key object
*
@ -225,5 +217,4 @@ class enigma_driver_phpssl extends enigma_driver
return $data;
}
}

@ -38,10 +38,11 @@ class enigma_engine
const SIGN_MODE_BODY = 1;
const SIGN_MODE_SEPARATE = 2;
const SIGN_MODE_MIME = 3;
const SIGN_MODE_MIME = 4;
const ENCRYPT_MODE_BODY = 1;
const ENCRYPT_MODE_MIME = 2;
const ENCRYPT_MODE_SIGN = 4;
/**
@ -244,7 +245,28 @@ class enigma_engine
$mime = new enigma_mime_message($message, enigma_mime_message::PGP_ENCRYPTED);
// always use sender's key
$recipients = array($mime->getFromAddress());
$from = $mime->getFromAddress();
// check senders key for signing
if ($mode & self::ENCRYPT_MODE_SIGN) {
$sign_key = $this->find_key($from, true);
if (empty($sign_key)) {
return new enigma_error(enigma_error::KEYNOTFOUND);
}
// check if we have password for this key
$passwords = $this->get_passwords();
$sign_pass = $passwords[$sign_key->id];
if ($sign_pass === null) {
// ask for password
$error = array('missing' => array($sign_key->id => $sign_key->name));
return new enigma_error(enigma_error::BADPASS, '', $error);
}
}
$recipients = array($from);
// if it's not a draft we add all recipients' keys
if (!$is_draft) {
@ -271,16 +293,13 @@ class enigma_engine
}
// select mode
switch ($mode) {
case self::ENCRYPT_MODE_BODY:
if ($mode & self::ENCRYPT_MODE_BODY) {
$encrypt_mode = $mode;
break;
case self::ENCRYPT_MODE_MIME:
}
else if ($mode & self::ENCRYPT_MODE_MIME) {
$encrypt_mode = $mode;
break;
default:
}
else {
$encrypt_mode = $mime->isMultipart() ? self::ENCRYPT_MODE_MIME : self::ENCRYPT_MODE_BODY;
}
@ -296,9 +315,15 @@ class enigma_engine
}
// sign the body
$result = $this->pgp_encrypt($body, $keys);
$result = $this->pgp_encrypt($body, $keys, $sign_key ? $sign_key->id : null, $sign_pass);
if ($result !== true) {
if ($result->getCode() == enigma_error::BADPASS) {
// ask for password
$error = array('bad' => array($sign_key->id => $sign_key->name));
return new enigma_error(enigma_error::BADPASS, '', $error);
}
return $result;
}
@ -678,11 +703,16 @@ class enigma_engine
$part = $p['structure'];
// Decrypt
$result = $this->pgp_decrypt($body);
$result = $this->pgp_decrypt($body, $signature);
// Store decryption status
$this->decryptions[$part->mime_id] = $result;
// Store signature data for display
if ($signature) {
$this->signatures[$part->mime_id] = $signature;
}
// find parent part ID
if (strpos($part->mime_id, '.')) {
$items = explode('.', $part->mime_id);
@ -764,7 +794,7 @@ class enigma_engine
$body = $this->get_part_body($p['object'], $part);
// Decrypt
$result = $this->pgp_decrypt($body);
$result = $this->pgp_decrypt($body, $signature);
if ($result === true) {
// Parse decrypted message
@ -784,6 +814,9 @@ class enigma_engine
$this->decryptions[$struct->mime_id] = $result;
foreach ((array) $struct->parts as $sp) {
$this->decryptions[$sp->mime_id] = $result;
if ($signature) {
$this->signatures[$sp->mime_id] = $signature;
}
}
}
else {
@ -839,15 +872,16 @@ class enigma_engine
/**
* PGP message decryption.
*
* @param mixed Message body
* @param mixed &$msg_body Message body
* @param enigma_signature &$signature Signature verification result
*
* @return mixed True or enigma_error
*/
private function pgp_decrypt(&$msg_body)
private function pgp_decrypt(&$msg_body, &$signature = null)
{
// @TODO: Handle big bodies using (temp) files
$keys = $this->get_passwords();
$result = $this->pgp_driver->decrypt($msg_body, $keys);
$result = $this->pgp_driver->decrypt($msg_body, $keys, $signature);
if ($result instanceof enigma_error) {
$err_code = $result->getCode();
@ -899,15 +933,17 @@ class enigma_engine
/**
* PGP message encrypting
*
* @param mixed Message body
* @param array Keys
* @param mixed Message body
* @param array Keys
* @param string Optional signing Key ID
* @param string Optional signing Key password
*
* @return mixed True or enigma_error
*/
private function pgp_encrypt(&$msg_body, $keys)
private function pgp_encrypt(&$msg_body, $keys, $sign_key = null, $sign_pass = null)
{
// @TODO: Handle big bodies using (temp) files
$result = $this->pgp_driver->encrypt($msg_body, $keys);
$result = $this->pgp_driver->encrypt($msg_body, $keys, $sign_key, $sign_pass);
if ($result instanceof enigma_error) {
$err_code = $result->getCode();
@ -917,6 +953,7 @@ class enigma_engine
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
return $result;
}

@ -776,8 +776,9 @@ class enigma_ui
return $p;
}
$engine = $this->enigma->engine;
$part_id = $p['part']->mime_id;
$engine = $this->enigma->engine;
$part_id = $p['part']->mime_id;
$messages = array();
// Decryption status
if (($found = $this->find_part_id($part_id, $engine->decryptions)) !== null
@ -818,7 +819,8 @@ class enigma_ui
$msg = rcube::Q($this->enigma->gettext('decryptok'));
}
$p['prefix'] .= html::div($attrib, $msg);
$attrib['msg'] = $msg;
$messages[] = $attrib;
}
// Signature verification status
@ -869,7 +871,19 @@ class enigma_ui
// test
// $msg .= '<br /><pre>'.$sig->body.'</pre>';
$p['prefix'] .= html::div($attrib, $msg);
$attrib['msg'] = $msg;
$messages[] = $attrib;
}
if ($count = count($messages)) {
if ($count == 2 && $messages[0]['class'] == $messages[1]['class']) {
$p['prefix'] .= html::div($messages[0], $messages[0]['msg'] . ' ' . $messages[1]['msg']);
}
else {
foreach ($messages as $msg) {
$p['prefix'] .= html::div($msg, $msg['msg']);
}
}
}
if ($attach_scripts) {
@ -972,16 +986,16 @@ class enigma_ui
$engine->attach_public_key($p['message']);
}
if (!$savedraft && $sign_enable) {
if ($encrypt_enable) {
$engine = $this->enigma->load_engine();
$status = $engine->sign_message($p['message']);
$mode = 'sign';
$mode = !$savedraft && $sign_enable ? enigma_engine::ENCRYPT_MODE_SIGN : null;
$status = $engine->encrypt_message($p['message'], $mode, $savedraft);
$mode = 'encrypt';
}
if ((!$status instanceof enigma_error) && $encrypt_enable) {
else if (!$savedraft && $sign_enable) {
$engine = $this->enigma->load_engine();
$status = $engine->encrypt_message($p['message'], null, $savedraft);
$mode = 'encrypt';
$status = $engine->sign_message($p['message']);
$mode = 'sign';
}
if ($mode && ($status instanceof enigma_error)) {

Loading…
Cancel
Save