From 8ebb9d63fb328e4df8f20ad63b5f5cdb760d972b Mon Sep 17 00:00:00 2001
From: m_horde <m_horde@secure.mailbox.org>
Date: Mon, 1 Dec 2014 00:36:23 +0100
Subject: [PATCH] Implementation of peer verification in TLS connections

---
 .../Socket_Client/lib/Horde/Socket/Client.php      | 67 ++++++++++++++++++----
 horde/config/conf.xml                              | 10 ++++
 2 files changed, 66 insertions(+), 11 deletions(-)

diff --git a/framework/Socket_Client/lib/Horde/Socket/Client.php b/framework/Socket_Client/lib/Horde/Socket/Client.php
index 91a666a..1e2b09e 100644
--- a/framework/Socket_Client/lib/Horde/Socket/Client.php
+++ b/framework/Socket_Client/lib/Horde/Socket/Client.php
@@ -43,6 +43,13 @@ class Client
     protected $_params;
 
     /**
+     * TLS configuration parameters.
+     *
+     * @var array
+     */
+    protected $_tls_params;
+
+    /**
      * Is the connection secure?
      *
      * @var boolean
@@ -67,11 +74,18 @@ class Client
      *   - true (TLS if available/necessary)
      * </pre>
      * @param array $params     Additional options.
+     * @param array $tls_params TLS options.
+     * <pre>
+     *   - cafile (the certificate authority bundle)
+     *   - ciphers (the cipher string to pass unto openssl)
+     *   - depth (verification depth, only if cafile is set)
+     *   - source (horde: use Horde's $GLOBALS; overwrite: use $tls_params; [empty]: use both, but $tls_params overrules $GLOBALS)
+     * </pre>
      *
      * @throws Horde\Socket\Client\Exception
      */
     public function __construct(
-        $host, $port, $timeout = 30, $secure = false, array $params = array()
+        $host, $port, $timeout = 30, $secure = false, array $params = array(), array $tls_params = array()
     )
     {
         if ($secure && !extension_loaded('openssl')) {
@@ -83,6 +97,8 @@ class Client
 
         $this->_params = $params;
 
+        $this->_tls_params = $tls_params;
+
         $this->_connect($host, $port, $timeout, $secure);
     }
 
@@ -173,23 +189,52 @@ class Client
             $conn = 'tcp://';
             break;
         }
+        
+        $stream_context_array = array(
+            'ssl' => array(
+                'verify_peer' => false,
+                'verify_peer_name' => false,
+                'ciphers' => 'ALL',
+                'verify_depth' => 10,
+                )
+            );
 
+        if  ((!empty($GLOBALS['conf']['openssl']['cafile']) && $GLOBALS['conf']['openssl']['verify'] &&
+            ($this->_tls_params['source'] != 'overwrite')) ||
+            (!empty($this->_tls_params['cafile']) && ($this->_tls_params['source'] != 'horde'))) {
+            $stream_context_array['ssl']['verify_peer'] = true;
+            $stream_context_array['ssl']['verify_peer_name'] = true;
+        }
+        /* Be aware that the order of the following if statements is
+         * important. $_tls_params has to be checked after $GLOBALS,
+         * because it overrules $GLOBALS. */
+        if (!empty($GLOBALS['conf']['openssl']['cafile']) && ($this->_tls_params['source'] != 'overwrite')) {
+            $stream_context_array['ssl']['cafile'] = $GLOBALS['conf']['openssl']['cafile'];
+        }
+        if (!empty($GLOBALS['conf']['openssl']['ciphers']) && ($this->_tls_params['source'] != 'overwrite')) {
+            $stream_context_array['ssl']['ciphers'] = $GLOBALS['conf']['openssl']['ciphers'];
+        }
+        if (!empty($GLOBALS['conf']['openssl']['depth']) && ($this->_tls_params['source'] != 'overwrite')) {
+            $stream_context_array['ssl']['depth'] = $GLOBALS['conf']['openssl']['depth'];
+        }
+        if (!empty($this->_tls_params['cafile']) && ($this->_tls_params['source'] != 'horde')) {
+            $stream_context_array['ssl']['cafile'] = $this->_tls_params['cafile'];
+        }
+        if (!empty($this->_tls_params['ciphers']) && ($this->_tls_params['source'] != 'horde')) {
+            $stream_context_array['ssl']['ciphers'] = $this->_tls_params['ciphers'];
+        }
+        if (!empty($this->_tls_params['depth']) && ($this->_tls_params['source'] != 'horde')) {
+            $stream_context_array['ssl']['depth'] = $this->_tls_params['depth'];
+        }
+ 
         $this->_stream = @stream_socket_client(
             $conn . $host . ':' . $port,
             $error_number,
             $error_string,
             $timeout,
             STREAM_CLIENT_CONNECT,
-            /* @todo: As of PHP 5.6, TLS connections require valid certs.
-             * However, this is BC-breaking to this library. For now, keep
-             * pre-5.6 behavior. */
-            stream_context_create(array(
-                'ssl' => array(
-                    'verify_peer' => false,
-                    'verify_peer_name' => false
-                )
-            ))
-        );
+            stream_context_create($stream_context_array)
+            );
 
         if ($this->_stream === false) {
             /* From stream_socket_client() page: a function return of false,
diff --git a/horde/config/conf.xml b/horde/config/conf.xml
index 41a4cec..d9d1bfe 100644
--- a/horde/config/conf.xml
+++ b/horde/config/conf.xml
@@ -1555,6 +1555,16 @@
    certificates bundle, e.g. /etc/ssl/certs. See
    http://www.php.net/manual/en/openssl.cert.verification.php for
    details."/>
+   <configboolean name="verify" required="false" desc="Should we set the ssl
+   context to verify the peer certificate with the given certificate bundle.
+   This option has no effect when cafile is not set.
+   See https://php.net/manual/en/context.ssl.php for details."/>
+   <configstring name="depth" required="false" desc="The depth for
+   certificate verification. The default is 10."/>
+   <configstring name="ciphers" required="false" desc="Limit the used cipers
+   for secure connections. The default is to enable all ciphers except those
+   without encryption. See
+   https://www.openssl.org/docs/apps/ciphers.html for details."/>
    <configstring name="path" required="false" desc="The location of the OpenSSL
    binary on your system, e.g. /usr/bin/openssl. This program is REQUIRED to
    import personal S/MIME certificate information, as there is no native PHP
-- 
1.9.1