phpldapstarttls

php_ldap - Try StartTLS but keep going when it fails


Because of various customers environments running the same code, I'm trying to build an LDAP authentification system with PHP that may work in 3 modes :

  1. Do not use StartTLS at all (for when the LDAP server prefers LDAPS instead)
  2. Try StartTLS but keep going unsecured if the LDAP server refuses TLS
  3. Try StartTLS and abort authentication if the LDAP server refuses TLS

Here is a simplified version of the code :

// ldap_connect is successful and returns $ldap

ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); // I tried ALLOW and TRY as well and got the same results

// $configStartTls contains 1, 2 or 3 (see options above)
if ($configStartTls > 1) {
    $tlsOk = ldap_start_tls($ldap) || ($configStartTls === 2);
} else {
    $tlsOk = true;
}
if ($tlsOk) {
    // ldap_bind here
}

Here are the results with a LDAP server that does not support TLS (ldap_start_tls always returns false), for each value of $configStartTls

  1. ldap_bind is successful - OK
  2. ldap_bind fails - KO
  3. ldap_bind is not attempted - OK

In case 2 :

I don't understand why ldap_bind fails in case 2. It's as if using ldap_start_tls made the use of TLS mandatory. I expected it would be possible to keep communicating with the LDAP server after a failure, in a non-secured way.

Do I have to cancel the use of StartTLS, after a failure, to implement option 2 ? How would I do this ?


Solution

  • I simply needed to do ldap_connect again to start over without using StartTLS. I had tried that and failed, but only because I forgot to re-apply the LDAP options between ldap_connect and ldap_bind, which means I was probably using LDAP v2 on the second attempt, and that's why the server refused it.

    Here is full working code. It includes URI, BindDN and password of a free public LDAP server with no TLS support, so anyone who would face the same problem can try the code themselves and tinker with it. Just change the value of $startTlsMode to any of the constants to try other modes.

    const TLS_NO = 1;
    const TLS_OPTIONAL = 2;
    const TLS_MANDATORY = 3;
    
    $startTlsMode = TLS_OPTIONAL;
    
    $ldap = connectAndSetOptions();
    if ($startTlsMode === TLS_OPTIONAL || $startTlsMode === TLS_MANDATORY) {
        $tlsOk = ldap_start_tls($ldap);
    } else {
        $tlsOk = true;
    }
    
    // TLS optional and StartTLS failed, start over without using StartTLS
    if ($startTlsMode === TLS_OPTIONAL && !$tlsOk) {
        $ldap = connectAndSetOptions();
        $tlsOk = true;
    }
    
    // TLS successful or not necessary, proceed with binding
    if ($tlsOk) {
        $bindOK = ldap_bind($ldap, 'cn=read-only-admin,dc=example,dc=com', 'password');
        echo $bindOK ? 'Bind successful' : 'Bind failed';
    } else {
        echo 'No bind attempt';
    }
    
    function connectAndSetOptions() {
        $ldap = ldap_connect('ldap://ldap.forumsys.com:389');
        ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_TRY);
        return $ldap;
    }