File 0001-Enigma-WOAT-Support-8626.patch of Package roundcubemail-selfcontained

From 4f25da4f006bbf5bec15a88a08dca68df2bebdbe Mon Sep 17 00:00:00 2001
From: Christian Mollekopf <cmollekopf@gmail.com>
Date: Mon, 1 Aug 2022 11:51:32 +0200
Subject: [PATCH 1/6] Enigma: WOAT Support (#8626)

* Enigma: WOAT Support
* Fixed public key extraction from dns record

Co-authored-by: Aleksander Machniak <alec@alec.pl>
Co-authored-by: Christian Mollekopf <mollekopf@apheleia-it.ch>
---
 plugins/enigma/config.inc.php.dist      |  5 ++
 plugins/enigma/lib/enigma_engine.php    | 93 +++++++++++++++++++++++--
 plugins/enigma/lib/enigma_subkey.php    | 12 +++-
 program/lib/Roundcube/rcube_message.php | 10 +--
 4 files changed, 108 insertions(+), 12 deletions(-)

diff --git a/plugins/enigma/config.inc.php.dist b/plugins/enigma/config.inc.php.dist
index a5a5233c2..d654c764d 100644
--- a/plugins/enigma/config.inc.php.dist
+++ b/plugins/enigma/config.inc.php.dist
@@ -78,3 +78,8 @@ $config['enigma_passwordless'] = false;
 //     - enigma_options_lock = ['sign']
 //     - dont_override       = ['enigma_sign_all']
 $config['enigma_options_lock'] = [];
+
+// Enable Kolab's Web Of Anti-Trust feature
+// Fetches public keys from DNS. Default: false
+// To enable set it to True or an array of domain names.
+$config['enigma_woat'] = false;
diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php
index 113030db1..1717d41da 100644
--- a/plugins/enigma/lib/enigma_engine.php
+++ b/plugins/enigma/lib/enigma_engine.php
@@ -28,6 +28,7 @@ class enigma_engine
     private $pgp_driver;
     private $smime_driver;
     private $password_time;
+    private $sender;
     private $cache = [];
 
     public $decryptions     = [];
@@ -272,6 +273,9 @@ class enigma_engine
 
         $recipients = array_unique($recipients);
 
+        // Fetch keys from external sources, if configured
+        $this->sync_keys($recipients);
+
         // find recipient public keys
         foreach ((array) $recipients as $email) {
             if ($email == $from && $sign_key) {
@@ -380,6 +384,17 @@ class enigma_engine
             return;
         }
 
+        // Get the message/part sender
+        if (!empty($p['object']->sender) && !empty($p['object']->sender['mailto'])) {
+            $this->sender = $p['object']->sender['mailto'];
+        }
+        if (!empty($p['structure']->headers) && !empty($p['structure']->headers['from'])) {
+            $from = rcube_mime::decode_address_list($p['structure']->headers['from'], 1, false);
+            if (($from = current($from)) && !empty($from['mailto'])) {
+                $this->sender = $from['mailto'];
+            }
+        }
+
         // Don't be tempted to support encryption in text/html parts
         // Because of EFAIL vulnerability we should never support this (#6289)
 
@@ -881,6 +896,11 @@ class enigma_engine
     {
         // @TODO: Handle big bodies using (temp) files
 
+        // Import sender's key from external sources, if configured
+        if ($this->sender) {
+            $this->sync_keys([$this->sender]);
+        }
+
         // Get rid of possible non-ascii characters (#5962)
         $sig_body = preg_replace('/[^\x00-\x7F]/', '', $sig_body);
 
@@ -905,6 +925,11 @@ class enigma_engine
     {
         // @TODO: Handle big bodies using (temp) files
 
+        // Import sender's key from external sources, if configured
+        if ($this->sender) {
+            $this->sync_keys([$this->sender]);
+        }
+
         // Get rid of possible non-ascii characters (#5962)
         $msg_body = preg_replace('/[^\x00-\x7F]/', '', $msg_body);
 
@@ -1020,19 +1045,25 @@ class enigma_engine
             return;
         }
 
-        $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT;
-        $ret  = null;
+        $mode  = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT;
+        $found = [];
 
         // check key validity and type
         foreach ($result as $key) {
             if (($subkey = $key->find_subkey($email, $mode))
                 && (!$can_sign || $key->get_type() == enigma_key::TYPE_KEYPAIR)
             ) {
-                $ret = $key;
-                break;
+                $found[$subkey->get_creation_date(true)] = $key;
             }
         }
 
+        // Use the most recent one
+        if (count($found) > 1) {
+            ksort($found, SORT_NUMERIC);
+        }
+
+        $ret = count($found) > 0 ? array_pop($found) : null;
+
         // cache private key info for better performance
         // we can skip one list_keys() call when signing and attaching a key
         if ($can_sign) {
@@ -1442,4 +1473,58 @@ class enigma_engine
             );
         }
     }
+
+    /**
+     * Import public keys from DNS according to Kolab Web-Of-Anti-Trust
+     *
+     * @param array $recipients List of email addresses
+     */
+    protected function sync_keys($recipients)
+    {
+        $import = [];
+        $woat = $this->rc->config->get('enigma_woat');
+
+        if (empty($woat)) {
+            return;
+        }
+
+        foreach ($recipients as $recipient) {
+            if (!strpos($recipient, '@')) {
+                continue;
+            }
+
+            list($local, $domain) = explode('@', $recipient);
+
+            // Do this for configured domains only
+            if (is_array($woat) && !in_array_nocase($domain, $woat)) {
+                continue;
+            }
+
+            // remove parts behind a recipient delimiter ("jeroen+Trash" => "jeroen")
+            $local = preg_replace('/\+.*$/', '', $local);
+
+            $fqdn = sha1($local) . '._woat.' . $domain;
+
+            // Fetch the TXT record(s)
+            if (($records = dns_get_record($fqdn, DNS_TXT)) === false) {
+                continue;
+            }
+
+            foreach ($records as $record) {
+                if (strpos($record['txt'], 'v=woat1,') === 0) {
+                    $entry = explode('public_key=', $record['txt']);
+                    if (count($entry) == 2) {
+                        $import[] = $entry[1];
+                        // For now we support only one key
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Import the fetched keys
+        if (!empty($import)) {
+            $this->import_key(implode("\n", $import));
+        }
+    }
 }
diff --git a/plugins/enigma/lib/enigma_subkey.php b/plugins/enigma/lib/enigma_subkey.php
index ffdb3e8ce..014ab7de3 100644
--- a/plugins/enigma/lib/enigma_subkey.php
+++ b/plugins/enigma/lib/enigma_subkey.php
@@ -93,12 +93,18 @@ class enigma_subkey
     /**
      * Returns subkey creation date-time string
      *
-     * @return string|null
+     * @param bool $asInt Return the date as an integer
+     *
+     * @return string|null|int
      */
-    function get_creation_date()
+    function get_creation_date($asInt = false)
     {
         if (empty($this->created)) {
-            return null;
+            return $asInt ? 0 : null;
+        }
+
+        if ($asInt) {
+            return (int) $this->created->format('U');
         }
 
         $date_format = rcube::get_instance()->config->get('date_format', 'Y-m-d');
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index e81df1b2f..7a2f7d96a 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -126,6 +126,11 @@ class rcube_message
             )
         ];
 
+        $this->mime    = new rcube_mime($this->headers->charset);
+        $this->subject = str_replace("\n", '', $this->headers->get('subject'));
+        $from          = $this->mime->decode_address_list($this->headers->from, 1);
+        $this->sender  = current($from);
+
         if (!empty($this->headers->structure)) {
             $this->get_mime_numbers($this->headers->structure);
             $this->parse_structure($this->headers->structure);
@@ -134,11 +139,6 @@ class rcube_message
             $this->body = $this->storage->get_body($uid);
         }
 
-        $this->mime    = new rcube_mime($this->headers->charset);
-        $this->subject = str_replace("\n", '', $this->headers->get('subject'));
-        $from          = $this->mime->decode_address_list($this->headers->from, 1);
-        $this->sender  = current($from);
-
         // notify plugins and let them analyze this structured message object
         $this->app->plugins->exec_hook('message_load', ['object' => $this]);
     }
-- 
2.37.1