getMessage()); return; } $data = array(); foreach (explode("\n", $res) as $line) { if (preg_match('/^(.*?):(.*)$/', $line, $m)) { $data[$m[1]] = $m[2]; } } if (isset($data['error_code']) && $data['error_code'] == 'unsupported-type') { $cont = false; if (isset($data['session_type']) && $data['session_type'] != $sessionType) { // TODO: Check we support it before actually trying $sessionType = $data['session_type']; $cont = true; } if (isset($data['assoc_type']) && $data['assoc_type'] != $assocType) { $assocType = $data['assoc_type']; $cont = true; } if ($cont) { self::associate($server, $assocType, $sessionType); } return; } try { $data = self::decodeKey($server, $data); } catch (Exception $ex) { return; } $data['expires_at'] = time() + $data['expires_in']; self::$data[$server][$data['assoc_handle']] = $data; self::saveData(); } /** * Decodes the MAC key specified in the $data array. * * @param String $server The server which sent the data * @param Array $data Array of association data from the server * @return A copy of the $data array with the mac_key present */ private static function decodeKey($server, $data) { switch (strtolower($data['session_type'])) { case 'dh-sha1': $algo = 'sha1'; break; case 'dh-sha256': $algo = 'sha256'; break; case 'no-encryption': case 'blank': case '': $algo = false; break; default: throw new Exception('Unable to handle session type ' . $data['session_type']); } if ($algo !== false) { // The key is DH'd $mac = base64_decode($data['enc_mac_key']); $x = self::getDhPrivateKey($server); $temp = self::$bigmath->btwoc_undo(base64_decode($data['dh_server_public'])); $temp = self::$bigmath->powmod($temp, $x, self::DH_P); $temp = self::$bigmath->btwoc($temp); $temp = hash($algo, $temp, true); $mac = $mac ^ $temp; $data['mac_key'] = base64_encode($mac); unset($data['enc_mac_key'], $data['dh_server_public']); } return $data; } /** * Retrieves an active assoc_handle for the specified server. * * @param String $server The server whose handle we're looking for * @return An association handle for the server or null on failure */ public static function getHandle($server) { self::loadData(); if (!isset(self::$data[$server])) { Logger::log('No data found for %s', $server); return null; } foreach (self::$data[$server] as $handle => $data) { if ($handle == '__private') { continue; } if ($data['expires_at'] < time()) { Logger::log('Handle for %s expired at %s, unsetting', $server, $data['expires_at']); unset(self::$data[$server][$handle]); } else { return $handle; } } return null; } /** * Determines if the KeyManager has at least one assoc_handle for the * specified server. * * @param String $server The server to check for * @return True if the KeyManager has a handle, false otherwise */ public static function hasHandle($server) { return self::getHandle($server) !== null; } /** * Retrieves the association data array for the specified server and assoc * handle. * * @param String $server The server whose data is being requested * @param String $handle The current association handle for the server * @return Array of association data or null if none was found */ public static function getData($server, $handle) { self::loadData(); if (isset(self::$data[$server][$handle])) { if (self::$data[$server][$handle]['expires_at'] < time()) { self::revokeHandle($server, $handle); return null; } else { return self::$data[$server][$handle]; } } else { return null; } } /** * Attempts to authenticate that the specified arguments are a valid query * from the specified server. If smart authentication is not available * or an error is encountered, throws an exception. * * @param String $server The server that supposedly sent the request * @param Array $args The arguments included in the request * @return True if the message was authenticated, false if it's a fake */ public static function authenticate($server, $args) { Logger::log('Authenticating message from %s', $server); $data = self::getData($server, $args['openid_assoc_handle']); if ($data === null) { Logger::log('Can\'t authenticate, no key available'); throw new Exception('No key available for that server/handle'); } $contents = ''; foreach (explode(',', $args['openid_signed']) as $arg) { $argn = str_replace('.', '_', $arg); $contents .= $arg . ':' . $args['openid_' . $argn] . "\n"; } switch (strtolower($data['assoc_type'])) { case 'hmac-sha1': $algo = 'sha1'; break; case 'hmac-sha256': $algo = 'sha256'; break; default: Logger::log('Can\'t authenticate, unknown assoc type %s', $data['assoc_type']); throw new Exception('Unable to handle association type ' . $data['assoc_type']); } $sig = base64_encode(hash_hmac($algo, $contents, base64_decode($data['mac_key']), true)); Logger::log('Expected signature %s, received signature %s', $sig, $args['openid_sig']); // Manually compare characters to prevent timing attacks $res = strlen($sig) == strlen($args['openid_sig']); for ($i = 0; $i < strlen($sig); $i++) { $res &= $sig[$i] == $args['openid_sig'][$i]; } Logger::log('Authentication result: %s', $res ? 'good' : 'bad'); return $res; } /** * Validates the current request using dumb authentication (a POST to the * provider). * * @return True if the request has been authenticated, false otherwise. */ public static function dumbAuthenticate() { Logger::log('Performing dumb authentication'); $url = URLBuilder::buildAuth($_REQUEST, $_SESSION['openid']['version']); try { $data = Poster::post($_SESSION['openid']['endpointUrl'], $url); } catch (Exception $ex) { return false; } $valid = false; foreach (explode("\n", $data) as $line) { if (substr($line, 0, 9) == 'is_valid:') { $valid = (boolean) substr($line, 9); } } Logger::log('Authentication result: %s', $valid ? 'good' : 'bad'); return $valid; } /** * Removes the specified association handle from the specified server's * records. * * @param String $server The server which is revoking the handle * @param String $handle The handle which is being revoked */ public static function revokeHandle($server, $handle) { self::loadData(); unset(self::$data[$server][$handle]); self::saveData(); } /** * Determines if the keymanager is supported by the local environment or not. * * @return True if the keymanager can be used, false otherwise */ public static function isSupported() { return @is_writable(dirname(__FILE__) . '/keycache.php') && function_exists('hash_hmac'); } /** * Returns the base64-encoded representation of the dh_modulus parameter. * * @return Base64-encoded representation of dh_modulus */ public static function getDhModulus() { return base64_encode(self::$bigmath->btwoc(self::DH_P)); } /** * Returns the base64-encoded representation of the dh_gen parameter. * * @return Base64-encoded representation of dh_gen */ public static function getDhGen() { return base64_encode(self::$bigmath->btwoc(self::DH_G)); } /** * Retrieves our private key for the specified server. * * @param String $server The server which we're communicating with * @return Our private key for the specified server, or null if we don't have * one */ public static function getDhPrivateKey($server) { self::loadData(); if (isset(self::$data[$server])) { return self::$data[$server]['__private']; } else { return null; } } /** * Retrieves our public key for use with the specified server. * * @param String $server The server we wish to send the public key to * @return Base64-encoded public key for the specified server */ public static function getDhPublicKey($server) { self::loadData(); $key = self::createDhKey($server); self::saveData(); return base64_encode(self::$bigmath->btwoc(self::$bigmath->powmod(self::DH_G, $key, self::DH_P))); } /** * Creates a private key for use when exchanging keys with the specified * server. * * @param String $server The name of the server we're dealing with * @return The server's new private key */ private static function createDhKey($server) { return self::$data[$server]['__private'] = self::$bigmath->rand(self::DH_P); } /** * Determines whether the keymanager can support Diffie-Hellman key exchange. * * @return True if D-H exchange is supported, false otherwise. */ public static function supportsDH() { return self::$bigmath != null; } /** * Initialises the key manager. */ public static function init() { self::$bigmath = BigMath::getBigMath(); } } KeyManager::init(); ?>