From 565a6556d380f236a4a35811f14ff0bb8483bdc4 Mon Sep 17 00:00:00 2001
From: m horde <m_horde@secure.mailbox.org>
Date: Fri, 13 Mar 2015 16:28:16 +0100
Subject: [PATCH] Implementation of peer verification in TLS connections

---
 .../Imap_Client/lib/Horde/Imap/Client/Base.php     |  1 +
 .../Imap_Client/lib/Horde/Imap/Client/Socket.php   |  5 +-
 .../Socket_Client/lib/Horde/Socket/Client.php      | 25 +++++++++
 imp/config/backends.php                            | 29 ++++++++++
 imp/lib/Imap.php                                   | 65 +++++++++++++++++++++-
 imp/lib/Imap/Config.php                            |  4 +-
 6 files changed, 124 insertions(+), 5 deletions(-)

diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php
index 09c9681..a3a8d1f 100644
--- a/framework/Imap_Client/lib/Horde/Imap/Client/Base.php
+++ b/framework/Imap_Client/lib/Horde/Imap/Client/Base.php
@@ -198,6 +198,7 @@ implements Serializable, SplObserver
      *               DEFAULT: Use the server default
      * - context: (array) Any context parameters passed to
      *            stream_create_context(). @since 2.27.0
+     * - fingerprints: (array) fingerprints of server certificate
      * - debug: (string) If set, will output debug information to the stream
      *          provided. The value can be any PHP supported wrapper that can
      *          be opened via PHP's fopen() function.
diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
index 5c6ada3..4097bc3 100644
--- a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
+++ b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
@@ -572,7 +572,10 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
                 $this->getParam('context'),
                 array(
                     'debug' => $this->_debug,
-                    'debugliteral' => $this->getParam('debug_literal')
+                    'debugliteral' => $this->getParam('debug_literal'),
+                    'sha1' => $this->getParam('fingerprints')['sha1'],
+                    'sha256' => $this->getParam('fingerprints')['sha256'],
+                    'md5' => $this->getParam('fingerprints')['md5']
                 )
             );
         } catch (Horde\Socket\Client\Exception $e) {
diff --git a/framework/Socket_Client/lib/Horde/Socket/Client.php b/framework/Socket_Client/lib/Horde/Socket/Client.php
index f69b74f..a4e877f 100644
--- a/framework/Socket_Client/lib/Horde/Socket/Client.php
+++ b/framework/Socket_Client/lib/Horde/Socket/Client.php
@@ -76,6 +76,9 @@ class Client
      * @param array $context    Any context parameters passed to
      *                          stream_create_context().
      * @param array $params     Additional options.
+     *   - 'md5': md5 hash of server certificate
+     *   - 'sha1': sha1 hash of server certificate
+     *   - 'sha256': sha256 hash of server certificate
      *
      * @throws Horde\Socket\Client\Exception
      */
@@ -145,6 +148,28 @@ class Client
         if ($this->connected &&
             !$this->secure &&
             (@stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) === true)) {
+            if (function_exists(openssl_x509_fingerprint)) {
+                /**openssl_x509_fingerprint exist since PHP 5.6*/
+                openssl_x509_export(stream_context_get_params($this->_stream)["options"]["ssl"]["peer_certificate"], $pem_cert);
+                if (!empty($this->_params['sha1'])) {
+                    $cert_fp = openssl_x509_fingerprint($pem_cert, 'sha1', false);
+                    if ($cert_fp != $this->_params['sha1']) {
+                        return false;
+                    }
+                }
+                if (!empty($this->_params['md5'])) {
+                    $cert_fp = openssl_x509_fingerprint($pem_cert, 'md5', false);
+                    if ($cert_fp != $this->_params['md5']) {
+                        return false;
+                    }
+                }
+                if (!empty($this->_params['sha256'])) {
+                    $cert_fp = openssl_x509_fingerprint($pem_cert, 'sha256', false);
+                    if ($cert_fp != $this->_params['sha256']) {
+                        return false;
+                    }
+                }
+            }
             $this->_secure = true;
             return true;
         }
diff --git a/imp/config/backends.php b/imp/config/backends.php
index 0eda72a..5990a3c 100644
--- a/imp/config/backends.php
+++ b/imp/config/backends.php
@@ -219,6 +219,26 @@
  *   - true:  Enable ACLs. (Not all IMAP servers support this feature).
  *   - false:  [DEFAULT] Disable ACLs.
  *
+ * tls_params: (array) TLS parameter to ensure a secure conncection
+ *   to a remote IMAP server.
+ *  
+ *   The following parameters are available:
+ *     - cafile: (string) The certificate authority bundle; turns peer
+ *               verification on if given.
+ *     - name: (string) The peer name; turns peer name verification on
+ *             if given.
+ *     - verify_name: (boolean) Turns peer name verification on; guess
+ *                    peer name if not given by 'name'.
+ *     - ciphers: (string) The cipher string to pass unto openssl.
+ *     - depth: (integer) Verification depth, only applicable if
+ *              cafile is set.
+ *     - sha1: (string) sha1 fingerprint of server certificate; only
+ *             applicable if cafile is given; all lowercase.
+ *     - md5: (string) md5 fingerprint of server certificate; only
+ *            applicable if cafile is given; all lowercase.
+ *     - sha256: (string) sha256 fingerprint of server certificate;
+ *               only applicable if cafile is given; all lowercase.
+ *
  * admin: (array) Use this if you want to enable mailbox management for
  *   administrators via Horde's user administration interface. The mailbox
  *   management gets enabled if you let IMP handle the Horde authentication
@@ -391,6 +411,15 @@ $servers['advanced'] = array(
     'protocol' => 'imap',
     'port' => 143,
     'secure' => 'tls',
+    'tls_params' => array(
+        'cafile' => '/path/to/cafile.pem',
+        'name' => 'your.domain.org',
+        'verify_name' => true,
+        'ciphers' => '!SSLv2:!MD5:!eNULL:!RC4:!RC2:!DES:!3DES:!kSRP:+SHA384:+SHA256:+SHA1:+DH:+ECDH:+ECDSA:+ADH:+AECDH:+ECDSA:+AES:+IDEA:+RSA:+kRSA:TLSv1:SSLv3',
+        'sha1' => 'a7467bc1ce43741cb3b1f015e678559a7c7071b9',
+        'md5' => '6b41eb055c4f5980cfa17122929e1b8a',
+        'sha256' => 'bb65981c105737bc7e5f59e65c4d518a997e1c5e0470c4ce642ffcf5e830cac0',
+        ),
     'maildomain' => '',
     'smtp' => array(
     //    'auth' => true,
diff --git a/imp/lib/Imap.php b/imp/lib/Imap.php
index e25c24e..1fbf577 100644
--- a/imp/lib/Imap.php
+++ b/imp/lib/Imap.php
@@ -255,7 +255,7 @@ class IMP_Imap implements Serializable
         $this->_config = $config;
 
         try {
-            return $this->createImapObject($imap_config, ($config->protocol == 'imap'));
+            return $this->createImapObject($imap_config, ($config->protocol == 'imap'), $config->tls_params);
         } catch (IMP_Imap_Exception $e) {
             unset($this->_config);
             throw $e;
@@ -263,6 +263,65 @@ class IMP_Imap implements Serializable
     }
 
     /**
+     * Creates the context parameters array to be passed to
+     * Socket_Client
+     *
+     * @param array $tls_config  tls_config form backends.php
+     *
+     * @return array  context parameters passed to stream_create_context()
+     */
+    protected function createContextArray($tls_config)
+    {
+        $context = array();
+        if (!empty($tls_config['cafile'])) {
+            $context['context']['ssl']['verify_peer'] = true;
+            $context['context']['ssl']['cafile'] = $tls_config['cafile'];
+        }
+        if (!empty($tls_config['name']) || $tls_config['verify_name'])
+        {
+            $context['context']['ssl']['verify_peer_name'] = true;
+        }
+        if (!empty($tls_config['name']))
+        {
+            $context['context']['ssl']['peer_name'] = $tls_config['name'];
+        }
+        if (!empty($tls_config['ciphers'])) {
+            $context['context']['ssl']['ciphers'] = $tls_config['ciphers'];
+        }
+        if (!empty($tls_config['depth'])) {
+            $context['context']['ssl']['depth'] = $tls_config['depth'];
+        }
+        if (!empty($tls_config['sha1']) || !empty($tls_config['md5']) ||
+            !empty($tls_config['sha256'])) {
+            $context['context']['ssl']['capture_peer_cert'] = true;
+        }
+        return $context;
+    }
+
+    /**
+     * Creates an array of fingerprints given in backends.php for
+     * the server certificate
+     *
+     * @param array $tls_config  tls_config form backend.php
+     *
+     * @return array  fingerprints of server certificate
+     */
+    protected function createFingerprintsArray($tls_config)
+    {
+        $fingerprints = array();
+        if (!empty($tls_config['sha1'])) {
+            $fingerprints['fingerprints']['sha1'] = $tls_config['sha1'];
+        }
+        if (!empty($tls_config['sha256'])) {
+            $fingerprints['fingerprints']['sha256'] = $tls_config['sha256'];
+        }
+        if (!empty($tls_config['md5'])) {
+            $fingerprints['fingerprints']['md5'] = $tls_config['md5'];
+        }
+        return $fingerprints;
+    }
+
+    /**
      * Create a Horde_Imap_Client object.
      *
      * @param array $config  The IMAP configuration.
@@ -271,7 +330,7 @@ class IMP_Imap implements Serializable
      * @return Horde_Imap_Client_Base  Client object.
      * @throws IMP_Imap_Exception
      */
-    public function createImapObject($config, $imap = true)
+    public function createImapObject($config, $imap = true, $tls_config = array())
     {
         if ($this->init) {
             return $this->_ob;
@@ -287,7 +346,7 @@ class IMP_Imap implements Serializable
             'lang' => $sconfig->lang,
             'timeout' => $sconfig->timeout,
             // 'imp:login' - Set in __call()
-        ), $config);
+        ), $config, $this->createContextArray($tls_config), $this->createFingerprintsArray($tls_config));
 
         try {
             $this->_ob = $imap
diff --git a/imp/lib/Imap/Config.php b/imp/lib/Imap/Config.php
index 7155636..200bf89 100644
--- a/imp/lib/Imap/Config.php
+++ b/imp/lib/Imap/Config.php
@@ -57,6 +57,7 @@
  * @property-read array $user_special_mboxes  List of user special mailboxes.
  * @property string $thread  The preferred thread sort algorithm.
  * @property string $timeout  The connection timeout (in seconds).
+ * @property array $tls_params  List of TLS parameters for peer verification.
  */
 class IMP_Imap_Config implements Serializable
 {
@@ -70,7 +71,8 @@ class IMP_Imap_Config implements Serializable
      */
     private $_aoptions = array(
         'admin', 'cache_params', 'capability_ignore', 'id', 'lang',
-        'namespace', 'preferred', 'quota', 'smtp', 'spam', 'special_mboxes'
+        'namespace', 'preferred', 'quota', 'smtp', 'spam', 'special_mboxes',
+        'tls_params'
     );
 
     /**
-- 
1.9.1