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