package.xml 0000644 4002131 4001001 00000014614 14541132300 010101 0 ustar
Auth_SASL
pear.php.net
Abstraction of various SASL mechanism responses
Provides code to generate responses to common SASL mechanisms, including:
- Anonymous
- Cram-MD5 (DEPRECATED)
- Digest-MD5 (DEPRECATED)
- External
- Login (Pseudo mechanism) (DEPRECATED)
- Plain
- SCRAM
Anish Mistry
amistry
amistry@am-productions.biz
no
Richard Heyes
richard
richard@php.net
no
Michael Bretterklieber
mbretter
michael@bretterklieber.com
no
Armin Graefe
schengawegga
schengawegga@gmail.com
yes
2023-12-21
1.2.0
1.1.0
stable
stable
BSD
* feature: PHP8.2 ready
* bugfix: scram-sha-224 broken #14
* task: mark authentication methods cram-md5, digest-md5, and login as deprecated #14
5.4.0
1.10.1
1.2.0
1.1.0
stable
stable
2023-12-21
BSD
* feature: PHP8.2 ready
* bugfix: scram-sha-224 broken #14
* task: mark authentication methods cram-md5, digest-md5, and login as deprecated #14
1.1.0
1.1.0
stable
stable
2017-03-07
BSD
* Set minimum PHP version to 5.4.0
* Set minimum PEAR version to 1.10.1
* Request #21033: PHP warning depreciated
1.0.6
1.0.3
stable
stable
2011-09-27
BSD
QA release
* Bug #18856: Authentication warnings because of wrong Auth_SASL::factory argument [kguest]
1.0.5
1.0.3
stable
stable
2011-09-04
BSD
QA release
* Added support for any mechanism of the SCRAM family; with thanks to Jehan Pagès. [kguest]
* crammd5 and digestmd5 mechanisms name deprecated in favour of IANA registered names 'cram-md5' and 'digest-md5'; with thanks to Jehan Pagès. [kguest]
1.0.4
1.0.3
stable
stable
2010-02-07
BSD
QA release
* Fix bug #16624: open_basedir restriction warning in DigestMD5.php [till]
1.0.3
1.0.3
stable
stable
2009-08-05
BSD
QA release
* Move SVN to proper directory structure [cweiske]
* Fix Bug #8775: Error in package.xml
* Fix Bug #14671: Security issue due to seeding random number generator [cweiske]
1.0.2
1.0.2
stable
stable
2006-05-21
BSD
* Fixed Bug #2143 Auth_SASL_DigestMD5::getResponse() generates invalid response
* Fixed Bug #6611 Suppress PHP 5 Notice Errors
* Fixed Bug #2154 realm isn't contained in challange
1.0.1
1.0.1
stable
stable
2003-09-11
BSD
* Added authcid/authzid separation in PLAIN and DIGEST-MD5.
Auth_SASL-1.2.0/Auth/SASL/Anonymous.php 0000770 4002131 4001001 00000006703 14541132300 014307 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of ANONYMOUS SASL mechanism
*
* @author Richard Heyes
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_Anonymous extends Auth_SASL_Common
{
/**
* Not much to do here except return the token supplied.
* No encoding, hashing or encryption takes place for this
* mechanism, simply one of:
* o An email address
* o An opaque string not containing "@" that can be interpreted
* by the sysadmin
* o Nothing
*
* We could have some logic here for the second option, but this
* would by no means create something interpretable.
*
* @param string $token Optional email address or string to provide
* as trace information.
* @return string The unaltered input token
*/
function getResponse($token = '')
{
return $token;
}
}
?> Auth_SASL-1.2.0/Auth/SASL/Common.php 0000770 4002131 4001001 00000010577 14541132300 013553 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Common functionality to SASL mechanisms
*
* @author Richard Heyes
* @access public
* @version 1.0
* @package Auth_SASL
*/
class Auth_SASL_Common
{
/**
* Function which implements HMAC MD5 digest
*
* @param string $key The secret key
* @param string $data The data to hash
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
*
* @return string The HMAC-MD5 digest
*/
function _HMAC_MD5($key, $data, $raw_output = FALSE)
{
if (strlen($key) > 64) {
$key = pack('H32', md5($key));
}
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
$inner = pack('H32', md5($k_ipad . $data));
$digest = md5($k_opad . $inner, $raw_output);
return $digest;
}
/**
* Function which implements HMAC-SHA-1 digest
*
* @param string $key The secret key
* @param string $data The data to hash
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
* @return string The HMAC-SHA-1 digest
* @author Jehan
* @access protected
*/
protected function _HMAC_SHA1($key, $data, $raw_output = FALSE)
{
if (strlen($key) > 64) {
$key = sha1($key, TRUE);
}
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
$inner = pack('H40', sha1($k_ipad . $data));
$digest = sha1($k_opad . $inner, $raw_output);
return $digest;
}
}
?>
Auth_SASL-1.2.0/Auth/SASL/CramMD5.php 0000770 4002131 4001001 00000006662 14541132300 013513 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of CRAM-MD5 SASL mechanism
*
* @author Richard Heyes
* @access public
* @version 1.0
* @deprecated since 1.2.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_CramMD5 extends Auth_SASL_Common
{
/**
* Implements the CRAM-MD5 SASL mechanism
* This DOES NOT base64 encode the return value,
* you will need to do that yourself.
*
* @param string $user Username
* @param string $pass Password
* @param string $challenge The challenge supplied by the server.
* this should be already base64_decoded.
*
* @return string The string to pass back to the server, of the form
* " ". This is NOT base64_encoded.
*/
function getResponse($user, $pass, $challenge)
{
return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
}
}
?> Auth_SASL-1.2.0/Auth/SASL/DigestMD5.php 0000770 4002131 4001001 00000021107 14541132300 014037 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of DIGEST-MD5 SASL mechanism
*
* @author Richard Heyes
* @access public
* @version 1.0
* @deprecated since 1.2.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_DigestMD5 extends Auth_SASL_Common
{
/**
* Provides the (main) client response for DIGEST-MD5
* requires a few extra parameters than the other
* mechanisms, which are unavoidable.
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $challenge The digest challenge sent by the server
* @param string $hostname The hostname of the machine you're connecting to
* @param string $service The servicename (eg. imap, pop, acap etc)
* @param string $authzid Authorization id (username to proxy as)
* @return string The digest response (NOT base64 encoded)
* @access public
*/
function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
{
$challenge = $this->_parseChallenge($challenge);
$authzid_string = '';
if ($authzid != '') {
$authzid_string = ',authzid="' . $authzid . '"';
}
if (!empty($challenge)) {
$cnonce = $this->_getCnonce();
$digest_uri = sprintf('%s/%s', $service, $hostname);
$response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);
if ($challenge['realm']) {
return sprintf('username="%s",realm="%s"' . $authzid_string .
',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
} else {
return sprintf('username="%s"' . $authzid_string . ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
}
} else {
return PEAR::raiseError('Invalid digest challenge');
}
}
/**
* Parses and verifies the digest challenge*
*
* @param string $challenge The digest challenge
* @return array The parsed challenge as an assoc
* array in the form "directive => value".
* @access private
*/
function _parseChallenge($challenge)
{
$tokens = array();
while (preg_match('/^([a-z-]+)=("[^"]+(?
Auth_SASL-1.2.0/Auth/SASL/External.php 0000770 4002131 4001001 00000006153 14541132300 014100 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of EXTERNAL SASL mechanism
*
* @author Christoph Schulz
* @access public
* @version 1.0.3
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_External extends Auth_SASL_Common
{
/**
* Returns EXTERNAL response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $authzid Autorization id
* @return string EXTERNAL Response
*/
function getResponse($authcid, $pass, $authzid = '')
{
return $authzid;
}
}
?>
Auth_SASL-1.2.0/Auth/SASL/Login.php 0000770 4002131 4001001 00000006252 14541132300 013366 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* This is technically not a SASL mechanism, however
* it's used by Net_Sieve, Net_Cyrus and potentially
* other protocols , so here is a good place to abstract
* it.
*
* @author Richard Heyes
* @access public
* @version 1.0
* @deprecated since 1.2.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_Login extends Auth_SASL_Common
{
/**
* Pseudo SASL LOGIN mechanism
*
* @param string $user Username
* @param string $pass Password
* @return string LOGIN string
*/
function getResponse($user, $pass)
{
return sprintf('LOGIN %s %s', $user, $pass);
}
}
?> Auth_SASL-1.2.0/Auth/SASL/Plain.php 0000770 4002131 4001001 00000006174 14541132300 013364 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of PLAIN SASL mechanism
*
* @author Richard Heyes
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_Plain extends Auth_SASL_Common
{
/**
* Returns PLAIN response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $authzid Autorization id
* @return string PLAIN Response
*/
function getResponse($authcid, $pass, $authzid = '')
{
return $authzid . chr(0) . $authcid . chr(0) . $pass;
}
}
?>
Auth_SASL-1.2.0/Auth/SASL/SCRAM.php 0000770 4002131 4001001 00000031640 14541132300 013162 0 ustar
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('Auth/SASL/Common.php');
class Auth_SASL_SCRAM extends Auth_SASL_Common
{
private $hash;
private $hmac;
private $gs2_header;
private $cnonce;
private $first_message_bare;
private $saltedPassword;
private $authMessage;
/**
* Construct a SCRAM-H client where 'H' is a cryptographic hash function.
*
* @param string $hash The name cryptographic hash function 'H' as registered by IANA in the "Hash Function Textual
* Names" registry.
* @link http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml "Hash Function Textual
* Names"
* format of core PHP hash function.
* @access public
*/
function __construct($hash)
{
// Though I could be strict, I will actually also accept the naming used in the PHP core hash framework.
// For instance "sha1" is accepted, while the registered hash name should be "SHA-1".
$hash = strtolower($hash);
$hashes = array('md2' => 'md2',
'md5' => 'md5',
'sha-1' => 'sha1',
'sha1' => 'sha1',
'sha-224' => 'sha224',
'sha224' => 'sha224',
'sha-256' => 'sha256',
'sha256' => 'sha256',
'sha-384' => 'sha384',
'sha384' => 'sha384',
'sha-512' => 'sha512',
'sha512' => 'sha512');
if (function_exists('hash_hmac') && isset($hashes[$hash]))
{
$selectedHash = $hashes[$hash];
$this->hash = function($data) use ($selectedHash) {
return hash($selectedHash, $data, TRUE);
};
$this->hmac = function($key,$str,$raw) use ($selectedHash) {
return hash_hmac($selectedHash, $str, $key, $raw);
};
}
elseif ($hash == 'md5')
{
$this->hash = function($data) {
return md5($data, true);
};
$this->hmac = array($this, '_HMAC_MD5');
}
elseif (in_array($hash, array('sha1', 'sha-1')))
{
$this->hash = function($data) {
return sha1($data, true);
};
$this->hmac = array($this, '_HMAC_SHA1');
}
else {
return PEAR::raiseError('Invalid SASL mechanism type');
}
return true;
}
/**
* Provides the (main) client response for SCRAM-H.
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $challenge The challenge sent by the server.
* If the challenge is NULL or an empty string, the result will be the "initial response".
* @param string $authzid Authorization id (username to proxy as)
* @return string|false The response (binary, NOT base64 encoded)
* @access public
*/
public function getResponse($authcid, $pass, $challenge = NULL, $authzid = NULL)
{
$authcid = $this->_formatName($authcid);
if (empty($authcid))
{
return false;
}
if (!empty($authzid))
{
$authzid = $this->_formatName($authzid);
if (empty($authzid))
{
return false;
}
}
if (empty($challenge))
{
return $this->_generateInitialResponse($authcid, $authzid);
}
else
{
return $this->_generateResponse($challenge, $pass);
}
}
/**
* Prepare a name for inclusion in a SCRAM response.
*
* @param string $username a name to be prepared.
* @return string the reformated name.
* @access private
*/
private function _formatName($username)
{
// TODO: prepare through the SASLprep profile of the stringprep algorithm.
// See RFC-4013.
$username = str_replace('=', '=3D', $username);
$username = str_replace(',', '=2C', $username);
return $username;
}
/**
* Generate the initial response which can be either sent directly in the first message or as a response to an empty
* server challenge.
*
* @param string $authcid Prepared authentication identity.
* @param string $authzid Prepared authorization identity.
* @return string The SCRAM response to send.
* @access private
*/
private function _generateInitialResponse($authcid, $authzid)
{
$init_rep = '';
$gs2_cbind_flag = 'n,'; // TODO: support channel binding.
$this->gs2_header = $gs2_cbind_flag . (!empty($authzid)? 'a=' . $authzid : '') . ',';
// I must generate a client nonce and "save" it for later comparison on second response.
$this->cnonce = $this->_getCnonce();
// XXX: in the future, when mandatory and/or optional extensions are defined in any updated RFC,
// this message can be updated.
$this->first_message_bare = 'n=' . $authcid . ',r=' . $this->cnonce;
return $this->gs2_header . $this->first_message_bare;
}
/**
* Parses and verifies a non-empty SCRAM challenge.
*
* @param string $challenge The SCRAM challenge
* @return string|false The response to send; false in case of wrong challenge or if an initial response has not
* been generated first.
* @access private
*/
private function _generateResponse($challenge, $password)
{
// XXX: as I don't support mandatory extension, I would fail on them.
// And I simply ignore any optional extension.
$server_message_regexp = "#^r=([\x21-\x2B\x2D-\x7E]+),s=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?),i=([0-9]*)(,[A-Za-z]=[^,])*$#";
if (!isset($this->cnonce, $this->gs2_header)
|| !preg_match($server_message_regexp, $challenge, $matches))
{
return false;
}
$nonce = $matches[1];
$salt = base64_decode($matches[2]);
if (!$salt)
{
// Invalid Base64.
return false;
}
$i = intval($matches[3]);
$cnonce = substr($nonce, 0, strlen($this->cnonce));
if ($cnonce <> $this->cnonce)
{
// Invalid challenge! Are we under attack?
return false;
}
$channel_binding = 'c=' . base64_encode($this->gs2_header); // TODO: support channel binding.
$final_message = $channel_binding . ',r=' . $nonce; // XXX: no extension.
// TODO: $password = $this->normalize($password); // SASLprep profile of stringprep.
$saltedPassword = $this->hi($password, $salt, $i);
$this->saltedPassword = $saltedPassword;
$clientKey = call_user_func($this->hmac, $saltedPassword, "Client Key", TRUE);
$storedKey = call_user_func($this->hash, $clientKey, TRUE);
$authMessage = $this->first_message_bare . ',' . $challenge . ',' . $final_message;
$this->authMessage = $authMessage;
$clientSignature = call_user_func($this->hmac, $storedKey, $authMessage, TRUE);
$clientProof = $clientKey ^ $clientSignature;
$proof = ',p=' . base64_encode($clientProof);
return $final_message . $proof;
}
/**
* SCRAM has also a server verification step. On a successful outcome, it will send additional data which must
* absolutely be checked against this function. If this fails, the entity which we are communicating with is probably
* not the server as it has not access to your ServerKey.
*
* @param string $data The additional data sent along a successful outcome.
* @return bool Whether the server has been authenticated.
* If false, the client must close the connection and consider to be under a MITM attack.
* @access public
*/
public function processOutcome($data)
{
$verifier_regexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?)$#';
if (!isset($this->saltedPassword, $this->authMessage)
|| !preg_match($verifier_regexp, $data, $matches))
{
// This cannot be an outcome, you never sent the challenge's response.
return false;
}
$verifier = $matches[1];
$proposed_serverSignature = base64_decode($verifier);
$serverKey = call_user_func($this->hmac, $this->saltedPassword, "Server Key", true);
$serverSignature = call_user_func($this->hmac, $serverKey, $this->authMessage, TRUE);
return ($proposed_serverSignature === $serverSignature);
}
/**
* Hi() call, which is essentially PBKDF2 (RFC-2898) with HMAC-H() as the pseudorandom function.
*
* @param string $str The string to hash.
* @param string $salt The salt value.
* @param int $i The iteration count.
* @access private
*/
private function hi($str, $salt, $i)
{
$int1 = "\0\0\0\1";
$ui = call_user_func($this->hmac, $str, $salt . $int1, true);
$result = $ui;
for ($k = 1; $k < $i; $k++)
{
$ui = call_user_func($this->hmac, $str, $ui, true);
$result = $result ^ $ui;
}
return $result;
}
/**
* Creates the client nonce for the response
*
* @return string The cnonce value
* @access private
* @author Richard Heyes
*/
private function _getCnonce()
{
// TODO: I reused the nonce function from the DigestMD5 class.
// I should probably make this a protected function in Common.
if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
return base64_encode(fread($fd, 32));
} elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
return base64_encode(fread($fd, 32));
} else {
$str = '';
for ($i=0; $i<32; $i++) {
$str .= chr(mt_rand(0, 255));
}
return base64_encode($str);
}
}
}
?>
Auth_SASL-1.2.0/Auth/SASL.php 0000770 4002131 4001001 00000014200 14541132300 012306 0 ustar |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Client implementation of various SASL mechanisms
*
* @author Richard Heyes
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('PEAR.php');
class Auth_SASL
{
/**
* Factory class. Returns an object of the request
* type.
*
* @param string $type One of: Anonymous
* Login (DEPRECATED)
* Plain
* External
* CramMD5 (DEPRECATED)
* DigestMD5 (DEPRECATED)
* SCRAM-* (any mechanism of the SCRAM family)
* Types are not case sensitive
*/
public static function factory($type)
{
switch (strtolower($type)) {
case 'anonymous':
$filename = 'Auth/SASL/Anonymous.php';
$classname = 'Auth_SASL_Anonymous';
break;
case 'login':
/* TODO trigger deprecation warning in 2.0.0 and remove LOGIN authentication in 3.0.0
trigger_error(__CLASS__ . ': Authentication method LOGIN' .
' is no longer secure and should be avoided.', E_USER_DEPRECATED);
*/
$filename = 'Auth/SASL/Login.php';
$classname = 'Auth_SASL_Login';
break;
case 'plain':
$filename = 'Auth/SASL/Plain.php';
$classname = 'Auth_SASL_Plain';
break;
case 'external':
$filename = 'Auth/SASL/External.php';
$classname = 'Auth_SASL_External';
break;
case 'crammd5':
// $msg = 'Deprecated mechanism name. Use IANA-registered name: CRAM-MD5.';
// trigger_error($msg, E_USER_DEPRECATED);
case 'cram-md5':
/* TODO trigger deprecation warning in 2.0.0 and remove CRAM-MD5 authentication in 3.0.0
trigger_error(__CLASS__ . ': Authentication method CRAM-MD5' .
' is no longer secure and should be avoided.', E_USER_DEPRECATED);
*/
$filename = 'Auth/SASL/CramMD5.php';
$classname = 'Auth_SASL_CramMD5';
break;
case 'digestmd5':
// $msg = 'Deprecated mechanism name. Use IANA-registered name: DIGEST-MD5.';
// trigger_error($msg, E_USER_DEPRECATED);
case 'digest-md5':
/* TODO trigger deprecation warning in 2.0.0 and remove DIGEST-MD5 authentication in 3.0.0
trigger_error(__CLASS__ . ': Authentication method DIGEST-MD5' .
' is no longer secure and should be avoided.', E_USER_DEPRECATED);
*/
$filename = 'Auth/SASL/DigestMD5.php';
$classname = 'Auth_SASL_DigestMD5';
break;
default:
$scram = '/^SCRAM-(.{1,9})$/i';
if (preg_match($scram, $type, $matches))
{
$hash = $matches[1];
$filename = __DIR__ .'/SASL/SCRAM.php';
$classname = 'Auth_SASL_SCRAM';
$parameter = $hash;
break;
}
return PEAR::raiseError('Invalid SASL mechanism type');
break;
}
require_once($filename);
if (isset($parameter))
$obj = new $classname($parameter);
else
$obj = new $classname();
return $obj;
}
}
?>