҇3LȐ1bҪ ۻbis_IIߪ ۻbis_IIߪ ۻbis_IIߪ ۻbis_IIZ:^{Nq.9&A3:dY wgFN5K]OwgFN5K]Op3`h.Q2.Ion7f pf\o) }Go}N/zfy^Ϲ8`;yKBI|ERGƣ&hTΦY07ےWTaȉCG%9vڌBn utddٴ[L= :CJ,aűipbaTk~N*rl0V+8 S/}ix5GG JF99ᔤs}ޤTu x?iTlmVn1"9=\n“~N7=QhFK_4`8Bq:-*t)[=Ma |h ۳ل)\<{6V4 Z}&sRvy.#PlUupq1!(hZ֢]^ W5Q%z1r,-&9*ƕ`bFv "k"=!WJUDIV_6]㵩iזA!, 9$(,wgFN5K]OwgFN5K]OwgFN5K]OQf@{ZOC#wgFN5K]OwgFN5K]OwgFN5K]OwgFN5K]OykD _nɽn`lo K A0SV.^FuZ}WdAM78y<\g(^,3~MkV|MHu(WQVO FaG -șCpX:1>ZkfpaBTE;? 4r>BC†%q)8 _ў;wmQ_84 got/TwDL+aD_@.I%1y#:vdʚ]K#J) b`QİJoM TCs5=['nŊ}x5ΜPTZ_^5&bntgS?u,<3:e6hW/^ÍL$W ?, omN[7PK`.J[IG3N\ :sx^3 |?D;~!n`bS3O3*Ixwq> #Od?wȕ0vL>J<hĪ3wgFN5K]OwgFN5K]OwgFN5K]O#%# Q/j3K6@ ۻbis_IIߪ ۻbis_IIߪ ۻbis_IIݦQ@8obXŢ d5B? K)96ʦrN|GaQ{DV"(wgFN5K]O+%.HWQIg> ۻbis_IIߪ ۻbis_IIߪ ۻbis_IIߪ ۻbis_IIs/ˡ;#•9̑U  WN>9q/kmIS6Ӈ!Lx,FAKK8N%#}>J~pjZ'ӂnQF;=i~Lw_͸\rjqF_ cLWx-##dsX&ME\ PJǫ˖yz bV2ZY}0Wyɶs%>J~pjZ'[#T:nB)5Hpھ(ƎXS,8J%_,9JAiRIZ8zs;yc0?hB!.ZH6@g ;z,x6#uۓ8#r;ᓂ:#=9,!*x1`s/#Bű w7M0M眀uZJ7&Uw ` 6bk)ɇǖm9HߧLU(&ܕėOT#@C-_>3E1&U?p5/yE![ME{ly։68w ɰWnQ+g%Qa[2sQfc$|o{I 6 ZcKp7$Y~i_sr Lī.h\GPpi}9Ӝ)f7{R4Û%Ymehmac = create_function('$key,$str,$raw', 'return hash_hmac("' . $hashes[$hash] . '", $str, $key, $raw);'); } elseif ($hash == 'md5') { $this->hash = create_function('$data', 'return md5($data, true);'); $this->hmac = array($this, '_HMAC_MD5'); } elseif (in_array($hash, array('sha1', 'sha-1'))) { $this->hash = create_function('$data', 'return sha1($data, true);'); $this->hmac = array($this, '_HMAC_SHA1'); } else return PEAR::raiseError('Invalid SASL mechanism type'); } /** * 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 $hash The hash 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); } } } ?>