diff --git a/CHANGELOG b/CHANGELOG
index ac87bffe3..cc1371698 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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)
diff --git a/plugins/enigma/lib/enigma_driver.php b/plugins/enigma/lib/enigma_driver.php
index 7fa334298..f3b117bdb 100644
--- a/plugins/enigma/lib/enigma_driver.php
+++ b/plugins/enigma/lib/enigma_driver.php
@@ -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.
diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php
index 2732f489d..9cd894546 100644
--- a/plugins/enigma/lib/enigma_driver_gnupg.php
+++ b/plugins/enigma/lib/enigma_driver_gnupg.php
@@ -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);
diff --git a/plugins/enigma/lib/enigma_driver_phpssl.php b/plugins/enigma/lib/enigma_driver_phpssl.php
index a5aa6de13..26be2cfaf 100644
--- a/plugins/enigma/lib/enigma_driver_phpssl.php
+++ b/plugins/enigma/lib/enigma_driver_phpssl.php
@@ -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;
}
-
}
diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php
index 413ec22e5..47d1d1df9 100644
--- a/plugins/enigma/lib/enigma_engine.php
+++ b/plugins/enigma/lib/enigma_engine.php
@@ -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;
}
diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php
index 3fa0f82e3..b8193eb33 100644
--- a/plugins/enigma/lib/enigma_ui.php
+++ b/plugins/enigma/lib/enigma_ui.php
@@ -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 .= '
'.$sig->body.''; - $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)) {