ldapsaisie/src/includes/class/class.LSattr_ldap_password.php

393 lines
13 KiB
PHP

<?php
/*******************************************************************************
* Copyright (C) 2007 Easter-eggs
* https://ldapsaisie.org
*
* Author: See AUTHORS file in top-level directory.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
******************************************************************************/
/**
* Ldap attribute type password
*
*/
class LSattr_ldap_password extends LSattr_ldap {
/**
* Cache of the new password in raw format (=clear, not hashed)
* @see LSattr_ldap_password::getUpdateData()
* @see LSattr_ldap_password::getClearPassword()
* @var string
*/
private $clearPassword = '';
/**
* Return the display value of this attribute
*
* @param mixed $data The value of this attribute
*
* @return mixed The display value of this attribute
*/
public function getDisplayValue($data) {
if ($this -> getConfig('ldap_options.displayClearValue', false, 'bool')) {
$ret=array();
$wildcardPassword = $this -> getConfig('ldap_options.wildcardPassword');
$encodedWildcardPassword = $this -> getConfig('ldap_options.encodedWildcardPassword');
foreach(ensureIsArray($data) as $p) {
if ($p == $wildcardPassword || $p == $encodedWildcardPassword)
continue;
$ret[] = $p;
}
return $ret;
}
return array('********');
}
/**
* Return the value of this attribute to be stocked
*
* Note : Password encoding was strongly inspired of the project phpLdapAdmin.
* URL : http://phpldapadmin.sourceforge.net/
*
* @param mixed $data The attribute value
*
* @return mixed The value of this attribute to be stocked
*/
public function getUpdateData($data) {
$ret = array();
foreach(ensureIsArray($data) as $val) {
$this -> clearPassword = $val;
$ret[] = $this -> encodePassword($val);
}
// Wildcard Password
foreach(ensureIsArray($this -> getConfig('ldap_options.wildcardPassword')) as $pwd)
$ret[] = $this -> encodePassword($pwd);
// Wildcard Password already encoded
foreach(ensureIsArray($this -> getConfig('ldap_options.encodedWildcardPassword')) as $pwd)
$ret[] = $pwd;
return $ret;
}
/**
* Encode the password
*
* Note : Password encoding was strongly inspired of the project phpLdapAdmin.
* URL : http://phpldapadmin.sourceforge.net/
*
* @param string $clearPassword The clear password
* @param string|null $encode The encoding type
* @param callable $encode_function The encoding callable
*
* @return string The encode password
*/
public function encodePassword($clearPassword, $encode=null, $encode_function=null, $salt=null) {
if (is_null($encode))
$encode = $this -> getConfig('ldap_options.encode', 'md5crypt', 'string');
if (is_null($encode_function))
$encode_function = $this -> getConfig('ldap_options.encode_function');
if ($encode_function || $encode == 'function') {
if ( (!$encode_function) || (!is_callable($encode_function)) ) {
$encode = 'clear';
$encode_function = null;
LSerror :: addErrorCode('LSattr_ldap_password_02', ($encode_function?$encode_function:__('undefined')));
}
else {
$encode = 'function';
}
}
switch($encode) {
case 'crypt':
if ($this -> getConfig('ldap_options.no_random_crypt_salt')) {
return '{CRYPT}' . crypt($clearPassword,substr($clearPassword,0,2));
}
else {
if (is_null($salt))
$salt = $this -> getSalt(2);
return '{CRYPT}' . crypt($clearPassword, $salt);
}
break;
case 'ext_des':
if ( ! defined( 'CRYPT_EXT_DES' ) || CRYPT_EXT_DES == 0 ) {
LSerror :: addErrorCode('LSattr_ldap_password_01','ext_des');
}
else {
if (is_null($salt))
$salt = $this -> getSalt(8);
return '{CRYPT}' . crypt( $clearPassword, '_' . $salt );
}
break;
case 'blowfish':
if( ! defined( 'CRYPT_BLOWFISH' ) || CRYPT_BLOWFISH == 0 ) {
LSerror :: addErrorCode('LSattr_ldap_password_01','blowfish');
}
else {
if (is_null($salt))
$salt = '$2y$12$' . $this -> getSalt(22);
return '{CRYPT}' . crypt( $clearPassword, $salt );
}
break;
case 'sha':
if( function_exists('sha1') ) {
return '{SHA}' . base64_encode( pack( 'H*' , sha1( $clearPassword ) ) );
}
elseif( function_exists( 'mhash' ) ) {
return '{SHA}' . base64_encode( mhash( MHASH_SHA1, $clearPassword ) );
} else {
LSerror :: addErrorCode('LSattr_ldap_password_01','sha');
}
break;
case 'sha256':
case 'sha512':
$mhash_type = null;
switch($encode) {
case 'sha256':
$mhash_type = MHASH_SHA256;
break;
case 'sha512':
$mhash_type = MHASH_SHA512;
break;
}
if( function_exists( 'mhash' ) ) {
return '{'.strtoupper($encode).'}' . base64_encode( mhash( $mhash_type, $clearPassword ) );
} else {
LSerror :: addErrorCode('LSattr_ldap_password_01', $encode);
}
break;
case 'ssha':
case 'ssha256':
case 'ssha512':
$mhash_type = null;
switch($encode) {
case 'ssha':
$mhash_type = MHASH_SHA1;
break;
case 'ssha256':
$mhash_type = MHASH_SHA256;
break;
case 'ssha512':
$mhash_type = MHASH_SHA512;
break;
}
if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
mt_srand( (double) microtime() * 1000000 );
if (is_null($salt))
$salt = mhash_keygen_s2k( $mhash_type, $clearPassword, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
return "{".strtoupper($encode)."}".base64_encode( mhash( $mhash_type, $clearPassword.$salt ).$salt );
}
else {
LSerror :: addErrorCode('LSattr_ldap_password_01', $encode);
}
break;
case 'smd5':
if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
mt_srand( (double) microtime() * 1000000 );
if (is_null($salt))
$salt = mhash_keygen_s2k( MHASH_MD5, $clearPassword, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
return "{SMD5}".base64_encode( mhash( MHASH_MD5, $clearPassword.$salt ).$salt );
}
else {
LSerror :: addErrorCode('LSattr_ldap_password_01','smd5');
}
break;
case 'md5':
return '{MD5}' . base64_encode( pack( 'H*' , md5( $clearPassword ) ) );
break;
case 'md5crypt':
if( ! defined( 'CRYPT_MD5' ) || CRYPT_MD5 == 0 ) {
LSerror :: addErrorCode('LSattr_ldap_password_01','md5crypt');
}
else {
if (is_null($salt))
$salt = $this -> getSalt();
return '{CRYPT}'.crypt($clearPassword,'$1$'.$salt.'$');
}
break;
case 'argon2':
case 'argon2i':
if( ! defined( 'PASSWORD_ARGON2I' ) ) {
LSerror :: addErrorCode('LSattr_ldap_password_01', 'argon2');
}
else {
return '{ARGON2}'.password_hash($clearPassword, PASSWORD_ARGON2I);
}
break;
case 'argon2id':
if( ! defined( 'PASSWORD_ARGON2ID' ) ) {
LSerror :: addErrorCode('LSattr_ldap_password_01', 'argon2id');
}
else {
return '{ARGON2}'.password_hash($clearPassword, PASSWORD_ARGON2ID);
}
break;
case 'clear':
return $clearPassword;
break;
case 'function':
return call_user_func_array($encode_function, array(&$this -> attribute -> ldapObject, $clearPassword));
break;
}
LSerror :: addErrorCode('LSattr_ldap_password_01', $encode);
return $clearPassword;
}
/**
* Verify if the clear specified password match with specified hashed password(s)
* @param string $clearPassword The clear password to check
* @param string|array|null $hashedPassword The hashed password(s) (optional, default: check againt attibute values)
* @return bool True if clear password matched, False otherwise
*/
function verify($clearPassword, $hashedPassword=null) {
// If $hashedPassword is not provided, use attribute values
if (is_null($hashedPassword))
$hashedPassword = $this -> attribute -> getValue();
// If $hashedPassword is array, iter to find valid password
if (is_array($hashedPassword)) {
foreach($hashedPassword as $pwd)
if ($this -> verify($clearPassword, $pwd))
return true;
return false;
}
// Verify $hashedPassword is a string
elseif (!is_string($hashedPassword))
return false;
// Custom verify function configured ? If yes, use it
$verifyFunction = $this -> getConfig('ldap_options.verify_function', null);
if (!is_null($verifyFunction) && is_callable($verifyFunction))
return call_user_func_array($verifyFunction, array(&$this -> attribute -> ldapObject, $clearPassword, $hashedPassword));
// Custom encode function configured ? If yes, use it
$encodeFunction = $this -> getConfig('ldap_options.encode_function', null);
if (!is_null($encodeFunction) && is_callable($encodeFunction))
return (strcasecmp(call_user_func_array($encodeFunction, array(&$this -> attribute -> ldapObject, $clearPassword)), $hashedPassword) == 0);
// Extract cipher
$hashedPasswordData = $cypher = null;
if (preg_match('/{([^}]+)}(.*)/',$hashedPassword,$matches)) {
$hashedPasswordData = $matches[2];
$cypher = strtolower($matches[1]);
}
// Verify password according on cypher
switch($cypher) {
# SSHA crypted passwords
case 'ssha':
case 'ssha256':
case 'ssha512':
case 'smd5':
$data = base64_decode($hashedPasswordData);
# Salt = last 4 bytes for SSHA / SMD5 and last 8 bytes for SSH256 / SSHA512
if ($cypher == 'ssha' || $cypher == 'smd5')
$salt_size = 4;
else
$salt_size = 8;
$salt = substr($data, -$salt_size);
$new_hash = $this -> encodePassword($clearPassword, $cypher, null, $salt);
return (strcmp($hashedPassword,$new_hash) == 0);
break;
# Non-salted cyphers
case 'sha':
case 'sha256':
case 'sha512':
case 'md5':
$new_hash = $this -> encodePassword($clearPassword, $cypher);
return (strcasecmp($new_hash, $hashedPassword) == 0);
break;
# Crypt passwords
case 'crypt':
# Check if it's blowfish crypt
if (preg_match('/^\\$2+/',$hashedPasswordData)) {
list($dummy, $version, $rounds, $salt_hash) = explode('$',$hashedPasswordData);
$salt = '$'.$version.'$'.$rounds.'$'.substr($salt_hash, 0, 22);
$new_hash = $this -> encodePassword($clearPassword, 'blowfish', null, $salt);
return (strcasecmp($new_hash, $hashedPassword) == 0);
}
# Check if it's an md5crypt
elseif (strstr($hashedPasswordData,'$1$')) {
list($dummy,$type,$salt,$hash) = explode('$',$hashedPasswordData);
$new_hash = $this -> encodePassword($clearPassword, 'md5crypt', null, $salt);
return (strcasecmp($new_hash, $hashedPassword) == 0);
}
# Check if it's ext_des crypt
elseif (strstr($hashedPasswordData,'_')) {
return (crypt($clearPassword,$hashedPasswordData) == $hashedPasswordData);
}
# Password is plain crypt
else {
return (crypt($clearPassword,$hashedPasswordData) == $hashedPasswordData);
}
break;
# Argon2 passwords
case 'argon2':
return password_verify($clearPassword, $hashedPasswordData);
# No crypt is given
default:
# Assume is a plaintext password
return (strcasecmp($clearPassword, $hashedPassword) == 0);
}
// It's supposed to never append, but just in case, return false
return false;
}
/**
* Return salt (random string)
*
* @param integer $length Number of caracters in this salt
*
* @return string A salt
*/
public static function getSalt($length=8) {
$pattern = "1234567890abcdefghijklmnopqrstuvwxyz";
$key = $pattern{rand(0,35)};
for($i=1;$i<$length;$i++)
{
$key .= $pattern{rand(0,35)};
}
return $key;
}
/**
* Return the password in clear text
*
* @return string The password in clear text
*/
public function getClearPassword() {
return $this -> clearPassword;
}
}
/**
* Error Codes
**/
LSerror :: defineError('LSattr_ldap_password_01',
___("LSattr_ldap_password : Encoding type %{type} is not supported. This password will be stored in clear text.")
);
LSerror :: defineError('LSattr_ldap_password_02',
___("LSattr_ldap_password : Encoding function %{function} is not callable. This password will be stored in clear text.")
);