From 00683b3ea761acae5ff0c0e767796dca98c88ef8 Mon Sep 17 00:00:00 2001 From: Scott Shambarger Date: Mon, 4 Nov 2019 18:02:13 +0000 Subject: [PATCH] Added TLS client certificate support Adds configuration for TLS client certificates to secure TLS connection (requires PHP 7.1+ to use). Updates use of ldap_set_option to report errors if settings fail. Modifies connection logic to fail if connection preparation fails (eg. to avoid connections over insecure links if requested TLS fails). --- config/config.php.example | 16 ++++++ lib/ds_ldap.php | 116 ++++++++++++++++++++++++++++++++------ 2 files changed, 115 insertions(+), 17 deletions(-) diff --git a/config/config.php.example b/config/config.php.example index daf9497..3f96407 100644 --- a/config/config.php.example +++ b/config/config.php.example @@ -339,6 +339,22 @@ $servers->setValue('server','name','My LDAP Server'); /* Use TLS (Transport Layer Security) to connect to the LDAP server. */ // $servers->setValue('server','tls',false); +/* TLS Certificate Authority file (overrides ldap.conf, PHP 7.1+) */ +// $servers->setValue('server','tls_cacert',null); +# $servers->setValue('server','tls_cacert','/etc/openldap/certs/ca.crt'); + +/* TLS Certificate Authority hashed directory (overrides ldap.conf, PHP 7.1+) */ +// $servers->setValue('server','tls_cacertdir',null); +# $servers->setValue('server','tls_cacertdir','/etc/openldap/certs'); + +/* TLS Client Certificate file (PHP 7.1+) */ +// $servers->setValue('server','tls_cert',null); +# $servers->setValue('server','tls_cert','/etc/pki/tls/certs/ldap_user.crt'); + +/* TLS Client Certificate Key file (PHP 7.1+) */ +// $servers->setValue('server','tls_key',null); +# $servers->setValue('server','tls_key','/etc/pki/tls/private/ldap_user.key'); + /************************************ * SASL Authentication * ************************************/ diff --git a/lib/ds_ldap.php b/lib/ds_ldap.php index 0c83170..a477efc 100644 --- a/lib/ds_ldap.php +++ b/lib/ds_ldap.php @@ -54,6 +54,22 @@ class ldap extends DS { 'desc'=>'Connect using TLS', 'default'=>false); + $this->default->server['tls_cacert'] = array( + 'desc'=>'TLS Certificate Authority', + 'default'=>null); + + $this->default->server['tls_cacertdir'] = array( + 'desc'=>'TLS Certificate Authority Directory', + 'default'=>null); + + $this->default->server['tls_cert'] = array( + 'desc'=>'TLS Client Certificate', + 'default'=>null); + + $this->default->server['tls_key'] = array( + 'desc'=>'TLS Client Certificate Key', + 'default'=>null); + # Login Details $this->default->login['attr'] = array( 'desc'=>'Attribute to use to find the users DN', @@ -110,6 +126,35 @@ class ldap extends DS { 'default'=>null); } + /** + * Set LDAP option with error checking... + * + * @param resource Connection resource + * @param string Name of option to set + * @param mixed Option value + * @return boolean false if error + */ + private function setLdapOption($resource, $option, $value) { + if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) + debug_log('Entered (%%)',16,0,__FILE__,__LINE__,__METHOD__,$fargs); + + if (! defined($option)) { + system_message(array( + 'title'=>sprintf('%s',_('Undefined LDAP option')), + 'body'=>sprintf('%s: %s %s',_('Error'),_('Required LDAP option not defined'),$option), + 'type'=>'error')); + return false; + } + if (! @ldap_set_option($resource,constant($option),$value)) { + system_message(array( + 'title'=>sprintf('%s',_('Failed to set LDAP option')), + 'body'=>sprintf('%s: %s %s',_('Error'),_('Failed to set LDAP option'),$option), + 'type'=>'error')); + return false; + } + return true; + } + /** * Required ABSTRACT functions */ @@ -164,6 +209,7 @@ class ldap extends DS { else $resource = ldap_connect($this->getValue('server','host')); + $this->noconnect = false; $CACHE[$this->index][$method] = $resource; if (DEBUG_ENABLED) @@ -174,12 +220,14 @@ class ldap extends DS { debug_dump_backtrace('UNHANDLED, $resource is not a resource',1); # Go with LDAP version 3 if possible (needed for renaming and Novell schema fetching) - ldap_set_option($resource,LDAP_OPT_PROTOCOL_VERSION,3); + if (! $this->setLdapOption($resource,'LDAP_OPT_PROTOCOL_VERSION',3)) + $this->noconnect = true; /* Disabling this makes it possible to browse the tree for Active Directory, and seems * to not affect other LDAP servers (tested with OpenLDAP) as phpLDAPadmin explicitly * specifies deref behavior for each ldap_search operation. */ - ldap_set_option($resource,LDAP_OPT_REFERRALS,0); + elseif (! $this->setLdapOption($resource,'LDAP_OPT_REFERRALS',0)) + $this->noconnect = true; /* Enabling manageDsaIt to be able to browse through glued entries * 2.16.840.1.113730.3.4.2 : "ManageDsaIT Control" "RFC 3296" "The client may provide @@ -187,14 +235,18 @@ class ldap extends DS { * to manage objects within the DSA (server) Information Tree. The control causes * Directory-specific entries (DSEs), regardless of type, to be treated as normal entries * allowing clients to interrogate and update these entries using LDAP operations." */ - ldap_set_option($resource,LDAP_OPT_SERVER_CONTROLS,array(array('oid'=>'2.16.840.1.113730.3.4.2'))); + elseif (! $this->setLdapOption($resource,'LDAP_OPT_SERVER_CONTROLS',array(array('oid'=>'2.16.840.1.113730.3.4.2')))) + $this->noconnect = true; # Try to fire up TLS is specified in the config - if ($this->isTLSEnabled()) - $this->startTLS($resource); + if ($this->isTLSEnabled() && !$this->noconnect) + if(! $this->startTLS($resource)) + $this->noconnect = true; # If SASL has been configured for binding, then start it now. - if ($this->isSASLEnabled()) + if ($this->noconnect) + $bind['result'] = false; + elseif ($this->isSASLEnabled()) $bind['result'] = $this->startSASL($resource,$method,$bind['id'],$bind['pass']); # Normal bind... @@ -211,17 +263,16 @@ class ldap extends DS { if (DEBUG_ENABLED) debug_log('Leaving with FALSE, bind FAILed',16,0,__FILE__,__LINE__,__METHOD__); - $this->noconnect = true; - - system_message(array( - 'title'=>sprintf('%s %s',_('Unable to connect to LDAP server'),$this->getName()), - 'body'=>sprintf('%s: %s (%s) for %s',_('Error'),$this->getErrorMessage($method),$this->getErrorNum($method),$method), - 'type'=>'error')); - + if (! $this->noconnect) { + $this->noconnect = true; + system_message(array( + 'title'=>sprintf('%s %s',_('Unable to connect to LDAP server'),$this->getName()), + 'body'=>sprintf('%s: %s (%s) for %s',_('Error'),$this->getErrorMessage($method),$this->getErrorNum($method),$method), + 'type'=>'error')); + } $CACHE[$this->index][$method] = null; } else { - $this->noconnect = false; # If this is a proxy session, we need to switch to the proxy user if ($this->isProxyEnabled() && $bind['id'] && $method != 'anon') @@ -570,10 +621,41 @@ class ldap extends DS { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs); - if (! $this->getValue('server','tls') || (function_exists('ldap_start_tls') && ! @ldap_start_tls($resource))) { + // LDAP_OPT_X_TLS_ options must be set globally ($res = null) + // until LDAP_OPT_X_TLS_NEWCTX is exported, + // NOTE: new values will require php-fpm or other stateful + // php servers to be restarted, and are global for all php + // users in the process pool! + $val = $this->getValue('server','tls_cacert'); + if (! empty($val)) + if (! $this->setLdapOption(null, 'LDAP_OPT_X_TLS_CACERTFILE', $val)) + return false; + + $val = $this->getValue('server','tls_cacertdir'); + if (! empty($val)) + if (! $this->setLdapOption(null, 'LDAP_OPT_X_TLS_CACERTDIR', $val)) + return false; + + $val = $this->getValue('server','tls_cert'); + if (! empty($val)) + if (! $this->setLdapOption(null, 'LDAP_OPT_X_TLS_CERTFILE', $val)) + return false; + + $val = $this->getValue('server','tls_key'); + if (! empty($val)) + if (! $this->setLdapOption(null, 'LDAP_OPT_X_TLS_KEYFILE', $val)) + return false; + + if (! @ldap_start_tls($resource)) { + $diag_error=''; + ldap_get_option($resource, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diag_error); + if (! empty($diag_error)) { + $diag_error = '
'.$diag_error; + } + $error = ldap_error($resource); system_message(array( 'title'=>sprintf('%s (%s)',_('Could not start TLS.'),$this->getName()), - 'body'=>sprintf('%s: %s',_('Error'),_('Could not start TLS. Please check your LDAP server configuration.')), + 'body'=>sprintf('%s: %s %s%s',_('Error'),_('Could not start TLS.'),$error,$diag_error), 'type'=>'error')); return false; @@ -792,7 +874,7 @@ class ldap extends DS { 'value'=>sprintf('dn:%s',$dn), 'iscritical' => true); - if (! ldap_set_option($resource,LDAP_OPT_SERVER_CONTROLS,array($ctrl))) { + if (! @ldap_set_option($resource,LDAP_OPT_SERVER_CONTROLS,array($ctrl))) { system_message(array( 'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()), 'body'=>sprintf('%s: %s (%s) for %s',_('Error'),$this->getErrorMessage($method),$this->getErrorNum($method),$method),