From 5f87cf93597a53bc8682267212ce98fa68ef9c26 Mon Sep 17 00:00:00 2001 From: Gabriel Correa de Oliveira Date: Thu, 2 Jul 2026 20:05:16 +1000 Subject: [PATCH] Add LDAP password obfuscation support --- .env.example | 2 + app/Classes/LDAP/SSSDPassword.php | 80 +++++++++++++++++++++++++++++++ config/ldap.php | 29 +++++++++-- tests/Unit/SSSDPasswordTest.php | 51 ++++++++++++++++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 app/Classes/LDAP/SSSDPassword.php create mode 100644 tests/Unit/SSSDPasswordTest.php diff --git a/.env.example b/.env.example index 18c92eaa..54c74f6f 100644 --- a/.env.example +++ b/.env.example @@ -13,5 +13,7 @@ SESSION_LIFETIME=120 LDAP_HOST= LDAP_USERNAME= LDAP_PASSWORD= +# Obfuscation scheme used for LDAP_PASSWORD: none (default) or sss (SSSD's sss_obfuscate(8) format) +LDAP_PASSWORD_OBFUSCATION=none LDAP_CACHE=false LDAP_ALERT_ROOTDN=true diff --git a/app/Classes/LDAP/SSSDPassword.php b/app/Classes/LDAP/SSSDPassword.php new file mode 100644 index 00000000..16e07fff --- /dev/null +++ b/app/Classes/LDAP/SSSDPassword.php @@ -0,0 +1,80 @@ +$method,'ctsize'=>$ctsize] = unpack('vmethod/vctsize',substr($buffer,0,4)); + + if ($method !== self::METHOD_AES_256) + throw new InvalidArgumentException(sprintf('Invalid obfuscated password: unsupported method [%d].',$method)); + + if (strlen($buffer) !== 4+self::KEY_LEN+self::IV_LEN+$ctsize+strlen(self::SENTINEL)) + throw new InvalidArgumentException('Invalid obfuscated password: unexpected buffer length.'); + + $p = 4; + $key = substr($buffer,$p,self::KEY_LEN); $p += self::KEY_LEN; + $iv = substr($buffer,$p,self::IV_LEN); $p += self::IV_LEN; + $ciphertext = substr($buffer,$p,$ctsize); $p += $ctsize; + + if (substr($buffer,$p,strlen(self::SENTINEL)) !== self::SENTINEL) + throw new InvalidArgumentException('Invalid obfuscated password: sentinel mismatch, buffer may be corrupt.'); + + $plaintext = openssl_decrypt($ciphertext,self::CIPHER,$key,OPENSSL_RAW_DATA,$iv); + + if ($plaintext === false) + throw new InvalidArgumentException('Invalid obfuscated password: unable to decrypt buffer.'); + + // The plaintext was encrypted with a trailing NUL terminator (C string). + $nul = strpos($plaintext,"\x00"); + + return $nul === false ? $plaintext : substr($plaintext,0,$nul); + } +} diff --git a/config/ldap.php b/config/ldap.php index d73a0d56..ea9823cf 100644 --- a/config/ldap.php +++ b/config/ldap.php @@ -1,5 +1,28 @@ $ldap_password, + 'sss' => $ldap_password !== '' ? SSSDPassword::deobfuscate($ldap_password) : $ldap_password, + default => throw new \InvalidArgumentException(sprintf('Unknown LDAP_PASSWORD_OBFUSCATION scheme [%s].',env('LDAP_PASSWORD_OBFUSCATION'))), +}; + return [ /* @@ -32,7 +55,7 @@ 'name' => env('LDAP_NAME','LDAP Server'), 'hosts' => [env('LDAP_HOST', '127.0.0.1')], 'username' => env('LDAP_USERNAME', ''), - 'password' => env('LDAP_PASSWORD', ''), + 'password' => $ldap_password, 'port' => env('LDAP_PORT', 389), 'timeout' => env('LDAP_TIMEOUT', 5), 'use_ssl' => env('LDAP_SSL', false), @@ -47,7 +70,7 @@ 'name' => env('LDAP_NAME','LDAPS Server'), 'hosts' => [env('LDAP_HOST', '127.0.0.1')], 'username' => env('LDAP_USERNAME', ''), - 'password' => env('LDAP_PASSWORD', ''), + 'password' => $ldap_password, 'port' => env('LDAP_PORT', 636), 'timeout' => env('LDAP_TIMEOUT', 5), 'use_ssl' => env('LDAP_SSL', true), @@ -62,7 +85,7 @@ 'name' => env('LDAP_NAME','LDAP-TLS Server'), 'hosts' => [env('LDAP_HOST', '127.0.0.1')], 'username' => env('LDAP_USERNAME', ''), - 'password' => env('LDAP_PASSWORD', ''), + 'password' => $ldap_password, 'port' => env('LDAP_PORT', 389), 'timeout' => env('LDAP_TIMEOUT', 5), 'use_ssl' => env('LDAP_SSL', false), diff --git a/tests/Unit/SSSDPasswordTest.php b/tests/Unit/SSSDPasswordTest.php new file mode 100644 index 00000000..4da3d635 --- /dev/null +++ b/tests/Unit/SSSDPasswordTest.php @@ -0,0 +1,51 @@ +assertEquals('Passw0rd',SSSDPassword::deobfuscate($token)); + } + + public function test_obfuscate_then_deobfuscate_round_trips(): void + { + $password = 'S0m3 \'Weird\' P@ssw0rd!'; + + $this->assertEquals($password,SSSDPassword::deobfuscate(SSSDPassword::obfuscate($password))); + } + + public function test_obfuscate_output_is_not_deterministic(): void + { + $password = 'Passw0rd'; + + $this->assertNotEquals(SSSDPassword::obfuscate($password),SSSDPassword::obfuscate($password)); + } + + public function test_deobfuscate_rejects_invalid_base64(): void + { + $this->expectException(\InvalidArgumentException::class); + + SSSDPassword::deobfuscate('not-valid-base64!!!'); + } + + public function test_deobfuscate_rejects_corrupt_buffer(): void + { + $buffer = base64_decode(SSSDPassword::obfuscate('Passw0rd')); + + $this->expectException(\InvalidArgumentException::class); + + SSSDPassword::deobfuscate(base64_encode($buffer.'x')); + } +}