phpldapopenldapmtls

implementation of mtls with php


I want to achieve mTLS on dummy php webpage from a guest to my Openldap Server. I have issue and it seems my php never sends client certificate to my server even then I specified the options accordingly. I have php 8.2 on my client guest and slapd version for my openldap server is in version 2.5.13.

I enabled TLS and enforced mutual TLS (mTLS) on my server:

olcTLSCACertificateFile: /etc/ldap/certs/issuing_ca.crt
olcTLSCertificateFile: /etc/ldap/certs/ldap.test.local.crt
olcTLSCertificateKeyFile: /etc/ldap/certs/ldap.test.local.key
olcTLSVerifyClient: demand

I can connect successfully with ldapsearch in csh with my client certificate from my client guest, so they are correct and valid:

setenv LDAPTLS_CACERT /var/run/certs/f03dd1db.crt
setenv LDAPTLS_CERT /var/run/certs/229c13f4.crt
setenv LDAPTLS_KEY /var/run/certs/229c13f4.key
ldapsearch -H ldaps://ldap.domain.local -b "dc=domain,dc=local" -D "uid=ldap_srv,ou=users,dc=domain,dc=local" -W -d 256 -x -LLL

However I can't make it work with my php :

function simple_test(){
    $ldapUri = 'ldaps://ldap.domain.local:636';
    $caFile = '/var/run/certs/f03dd1db.crt';
    $certFile = '/var/run/certs/229c13f4.crt';
    $keyFile = '/var/run/certs/229c13f4.key';
    $ldapbindun = 'uid=ldap_srv,ou=users,dc=domain,dc=local';
    $ldapbindpw = 'password';

    $ldapOptions = [
        LDAP_OPT_X_TLS_CACERTFILE => $caFile,
        LDAP_OPT_X_TLS_CERTFILE => $certFile,
        LDAP_OPT_X_TLS_KEYFILE => $keyFile,
        LDAP_OPT_PROTOCOL_VERSION => 3,
        LDAP_OPT_REFERRALS => 0,
    ];
    //set global options
    foreach($ldapOptions as $k => $v) {
        ldap_set_option(null, $k, $v);
    }
    
    $error = false;
    // initial connection seems to work and return success:
    if (!($ldapResource = ldap_connect($ldapserver))) {
        $error = true;
    }
    // Also tried to set options here especially for $ldap without luck also :
    //ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, "/var/run/certs/229c13f4.crt");
    //ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, "/var/run/certs/229c13f4.key");
    //try to bind and check for errors (that I have):
    //Same error if I switch to ldap_bind($ldapResource)
    if (!($res = ldap_bind($ldapResource, $ldapbindun, $ldapbindpw))) {
        $error = true;
    }

    if ($error == true) {
        ldap_get_option($ldapResource, LDAP_OPT_DIAGNOSTIC_MESSAGE, $errMsg);
        log_error(sprintf(gettext("%s (%d): %s\n"),ldap_error($ldapResource), ldap_errno($ldapResource),$errMsg));
        @ldap_close($ldapResource);
    }
}

Client logs shows me:

LDAP bind failed: Can't contact LDAP server (-1):

Server logs shows me :

 slap_listener_activate(8):
 >>> slap_listener(ldaps:///)
 conn=1022 fd=14 ACCEPT from IP=<MY_CLIENT_IP>:32896 (IP=0.0.0.0:636)
 connection_get(14): got connid=1022
 connection_read(14): checking for input on id=1022
 connection_get(14): got connid=1022
 connection_read(14): checking for input on id=1022
 TLS: can't accept: Certificate is required..
 connection_read(14): TLS accept failure error=-1 id=1022, closing
 connection_close: conn=1022 sd=14
 conn=1022 fd=14 closed (TLS negotiation failure)

However when I'm printing my $ldap options just before bind it seems everything is correct:

Option LDAP_OPT_DEREF: 'LDAP_DEREF_SEARCHING (Searching)'
Option LDAP_OPT_SIZELIMIT: 0
Option LDAP_OPT_TIMELIMIT: 25
Option LDAP_OPT_NETWORK_TIMEOUT: 25
Option LDAP_OPT_PROTOCOL_VERSION: '3 (LDAPv3)'
Option LDAP_OPT_ERROR_NUMBER: -1
Option LDAP_OPT_ERROR_STRING: Unable to retrieve value
Option LDAP_OPT_REFERRALS: '0 (Disable)'
Option LDAP_OPT_RESTART: '0 (Disable)'
Option LDAP_OPT_HOST_NAME: 'ldap.domain.local:636'
Option LDAP_OPT_MATCHED_DN: Unable to retrieve value
Option LDAP_OPT_SERVER_CONTROLS: Unable to retrieve value
Option LDAP_OPT_CLIENT_CONTROLS: Unable to retrieve value
Option LDAP_OPT_X_KEEPALIVE_IDLE: 0
Option LDAP_OPT_X_KEEPALIVE_PROBES: 0
Option LDAP_OPT_X_KEEPALIVE_INTERVAL: 0
Option LDAP_OPT_X_TLS_CACERTDIR: '/var/run/certs'
Option LDAP_OPT_X_TLS_CACERTFILE: '/var/run/certs/f03dd1db.crt'
Option LDAP_OPT_X_TLS_CERTFILE: '/var/run/certs/229c13f4.crt'
Option LDAP_OPT_X_TLS_CIPHER_SUITE: Unable to retrieve value
Option LDAP_OPT_X_TLS_CRLCHECK: 'LDAP_OPT_X_TLS_CRL_NONE (No CRL Checking)'
Option LDAP_OPT_X_TLS_CRLFILE: Unable to retrieve value
Option LDAP_OPT_X_TLS_DHFILE: Unable to retrieve value
Option LDAP_OPT_X_TLS_KEYFILE: '/var/run/certs/229c13f4.key'
Option LDAP_OPT_X_TLS_PACKAGE: 'OpenSSL'
Option LDAP_OPT_X_TLS_PROTOCOL_MIN: 'LDAP_OPT_X_TLS_PROTOCOL_SSL2 (SSLv2)'
Option LDAP_OPT_X_TLS_REQUIRE_CERT: 'LDAP_OPT_X_TLS_HARD (Hard)'
Option LDAP_OPT_X_TLS_RANDOM_FILE: Unable to retrieve value

But somehow, my php obviously do something wrong. If I believe this user comment, from php manual, or this comment from github php project it should work. I can't see what's the issue here.

EDIT

As my tests are going on, it's getting super weird.

By calling the exact same test.php file as root and from cli as env -i php -c /usr/local/etc/php.ini -d scan.dir=/usr/local/etc/php /usr/local/www/test.php it works and certificates are sent, bind is successful.

By reaching this exact same file, from my webserver (nginx/1.24.0) it fails. My logs are clear :

All those three files in both case can be correctly read (and wrote to my debug.log from my test.php)

The ldap_connect seems to work in both scenarios, but then ldap_bind works only when called in cli env -i php -c /usr/local/etc/php.ini -d scan.dir=/usr/local/etc/php /usr/local/www/test.php, but doesn't when reached from my nginx webserver.

To be as close as my nginx env in cli (Got info by calling phpinfo() from test.php and check values when reaching from nginx):

Still, work in cli, not from my webserver.

EDIT2

For information I use Nginx + PHP-FPM. Here is my very simple nginx conf:

#
# nginx configuration file

user  root wheel;
error_log /var/log/nginx/error.log;

events {
    worker_connections  1024;
}

http {
        include       /usr/local/etc/nginx/mime.types;
        default_type  application/octet-stream;
        access_log /var/log/nginx/access.log;

        server {
                listen 80;
                listen [::]:80;
                root "/usr/local/www/";

                location ~ \.php$ {
                        fastcgi_pass   unix:/var/run/php-fpm.socket;
                        fastcgi_index  index.php;
                        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                        fastcgi_param  HTTP_PROXY  "";
                        fastcgi_read_timeout 180;
                        include        /usr/local/etc/nginx/fastcgi_params_dummy;
                }

        }
}

And my fastcgi_params_dummy to be like this:

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

I tried to hardcode values here by adding those lines, it didn't change the outcome.

fastcgi_param  LDAPTLS_CERT       /var/run/certs/229c13f4.crt;
fastcgi_param  LDAPTLS_KEY        /var/run/certs/229c13f4.key;
fastcgi_param  LDAPTLS_CACERT     /var/run/certs/f03dd1db.0;

Solution

  • For the ones running Nginx + PHP-FPM : Solution was to restart php-fpm to make it work : /bin/pkill -F /var/run/php-fpm.pid followed by /usr/local/sbin/php-fpm -c /usr/local/etc/php.ini -y /usr/local/lib/php-fpm.conf -RD 2>&1 >/dev/null