| 1 |
<?php
|
| 2 |
// $Id: openid_provider.inc,v 1.2 2008/05/26 18:20:30 walkah Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Create an association with an RP
|
| 6 |
*
|
| 7 |
* @param array $request
|
| 8 |
*/
|
| 9 |
function openid_provider_association_response($request) {
|
| 10 |
module_load_include('inc', 'openid');
|
| 11 |
|
| 12 |
$session_type = $request['openid.session_type'];
|
| 13 |
$assoc_type = $request['openid.assoc_type'];
|
| 14 |
$dh_modulus = $request['openid.dh_modulus'];
|
| 15 |
$dh_gen = $request['openid.dh_gen'];
|
| 16 |
$dh_consumer_public = $request['openid.dh_consumer_public'];
|
| 17 |
|
| 18 |
$assoc_handle = _openid_provider_nonce();
|
| 19 |
$expires_in = variable_get('openid_provider_assoc_expires_in', '3600');
|
| 20 |
|
| 21 |
// CLEAR STALE ASSOCIATIONS
|
| 22 |
db_query("DELETE FROM {openid_provider_association} WHERE created + expires_in < %d", time());
|
| 23 |
|
| 24 |
$response = array(
|
| 25 |
'ns' => OPENID_NS_2_0,
|
| 26 |
'session_type' => $session_type,
|
| 27 |
'assoc_handle' => $assoc_handle,
|
| 28 |
'assoc_type' => $assoc_type,
|
| 29 |
'expires_in' => $expires_in
|
| 30 |
);
|
| 31 |
|
| 32 |
if ($session_type == 'DH-SHA1'
|
| 33 |
|| (($session_type == '' || $session_type == 'no-encryption')
|
| 34 |
&& $assoc_type == 'HMAC-SHA1')) {
|
| 35 |
$num_bytes = 20;
|
| 36 |
$algo = 'sha1';
|
| 37 |
}
|
| 38 |
elseif ($session_type == 'DH-SHA256'
|
| 39 |
|| (($session_type == '' || $session_type == 'no-encryption')
|
| 40 |
&& $assoc_type == 'HMAC-SHA256')) {
|
| 41 |
$num_bytes = 32;
|
| 42 |
$algo = 'sha256';
|
| 43 |
}
|
| 44 |
$secret = _openid_get_bytes($num_bytes);
|
| 45 |
if ($session_type == '' || $session_type == 'no-encryption') {
|
| 46 |
$mac_key = hash_hmac($algo, $response['assoc_handle'], $secret, true);
|
| 47 |
$response['mac_key'] = $mac_key;
|
| 48 |
}
|
| 49 |
else {
|
| 50 |
$dh_assoc = openid_provider_dh_assoc($request, $secret, $algo);
|
| 51 |
$mac_key = base64_encode($secret);
|
| 52 |
$response['dh_server_public'] = $dh_assoc['dh_server_public'];
|
| 53 |
$response['enc_mac_key'] = $dh_assoc['enc_mac_key'];
|
| 54 |
}
|
| 55 |
// Save the association for reference when dealing
|
| 56 |
// with future requests from the same RP.
|
| 57 |
db_query("INSERT INTO {openid_provider_association} (assoc_handle, assoc_type, session_type, mac_key, created, expires_in) VALUES ('%s', '%s', '%s', '%s', %d, %d)",
|
| 58 |
$assoc_handle, $assoc_type, $session_type, $mac_key, time(), $expires_in);
|
| 59 |
|
| 60 |
$message = _openid_create_message($response);
|
| 61 |
|
| 62 |
drupal_set_header('HTTP/1.1 200 OK');
|
| 63 |
drupal_set_header("Content-Type: text/plain");
|
| 64 |
print $message;
|
| 65 |
}
|
| 66 |
|
| 67 |
/**
|
| 68 |
* Generate an association error response
|
| 69 |
*/
|
| 70 |
function openid_provider_association_error() {
|
| 71 |
return array(
|
| 72 |
'error' => '', // optional
|
| 73 |
'error_code' => 'unsupported-type',
|
| 74 |
'session_type' => '', // optional
|
| 75 |
'assoc_type' => '' // optional
|
| 76 |
);
|
| 77 |
}
|
| 78 |
|
| 79 |
/**
|
| 80 |
* Generate an authentication response
|
| 81 |
*
|
| 82 |
* @param
|
| 83 |
*/
|
| 84 |
function openid_provider_authentication_response($request) {
|
| 85 |
global $user;
|
| 86 |
|
| 87 |
// If the user is not yet logged in, redirect to the login page before continuing.
|
| 88 |
if (!$user->uid) {
|
| 89 |
$_SESSION['openid_provider']['request'] = $request;
|
| 90 |
drupal_goto('user/login', 'destination=openid/provider/continue');
|
| 91 |
}
|
| 92 |
|
| 93 |
// Determine the realm (openid.trust_root in 1.x)
|
| 94 |
$realm = (empty($request['openid.realm'])) ? $request['openid.trust_root'] : $request['openid.realm'];
|
| 95 |
|
| 96 |
// Check for a directed identity request.
|
| 97 |
if ($request['openid.identity'] == 'http://specs.openid.net/auth/2.0/identifier_select') {
|
| 98 |
$identity = url('user/' . $user->uid, array('absolute' => TRUE));
|
| 99 |
}
|
| 100 |
else {
|
| 101 |
$identity = $request['openid.identity'];
|
| 102 |
if ($identity != url('user/'. $user->uid, array('absolute' => TRUE))) {
|
| 103 |
$response = openid_provider_authentication_error($request['openid.mode']);
|
| 104 |
openid_redirect($request['openid.return_to'], $response);
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
| 108 |
$response = array(
|
| 109 |
'openid.ns' => OPENID_NS_2_0,
|
| 110 |
'openid.mode' => 'id_res',
|
| 111 |
'openid.op_endpoint' => url('openid/provider', array('absolute' => TRUE)),
|
| 112 |
'openid.identity' => $identity,
|
| 113 |
'openid.claimed_id' => $identity,
|
| 114 |
'openid.return_to' => $request['openid.return_to'],
|
| 115 |
'openid.response_nonce' => _openid_provider_nonce(),
|
| 116 |
'openid.assoc_handle' => $request['openid.assoc_handle']
|
| 117 |
);
|
| 118 |
|
| 119 |
// Is the RP requesting Immediate or Indirect mode?
|
| 120 |
if ($request['openid.mode'] == 'checkid_immediate') {
|
| 121 |
// TODO
|
| 122 |
}
|
| 123 |
|
| 124 |
$parts = parse_url($request['openid.return_to']);
|
| 125 |
if (isset($parts['query'])) {
|
| 126 |
$query = $parts['query'];
|
| 127 |
$q = _openid_get_params($query);
|
| 128 |
foreach ($q as $key => $val) {
|
| 129 |
$response[$key] = $val;
|
| 130 |
}
|
| 131 |
}
|
| 132 |
|
| 133 |
$rp = _openid_provider_rp_load($user->uid, $realm);
|
| 134 |
if ($rp->auto_release) {
|
| 135 |
$response = _openid_provider_sign($response);
|
| 136 |
_openid_provider_rp_save($user->uid, $realm, TRUE);
|
| 137 |
return openid_redirect_http($response['openid.return_to'], $response);
|
| 138 |
}
|
| 139 |
else {
|
| 140 |
return drupal_get_form('openid_provider_form', $response, $realm);
|
| 141 |
}
|
| 142 |
}
|
| 143 |
|
| 144 |
/**
|
| 145 |
* Negative assertion
|
| 146 |
*/
|
| 147 |
function openid_provider_authentication_error($mode) {
|
| 148 |
if ($mode == 'checkid_immediate') {
|
| 149 |
return array(
|
| 150 |
'openid.mode' => 'id_res',
|
| 151 |
'openid.user_setup_url' => url('user/login', NULL, NULL, TRUE)
|
| 152 |
);
|
| 153 |
}
|
| 154 |
else { // checkid_setup
|
| 155 |
return array(
|
| 156 |
'openid.mode' => 'cancel'
|
| 157 |
);
|
| 158 |
}
|
| 159 |
}
|
| 160 |
|
| 161 |
|
| 162 |
|
| 163 |
function openid_provider_dh_assoc($request, $secret, $algo = 'sha1') {
|
| 164 |
if (empty($request['openid.dh_consumer_public'])) {
|
| 165 |
return FALSE;
|
| 166 |
}
|
| 167 |
|
| 168 |
if (isset($request['openid.dh_modulus'])) {
|
| 169 |
$mod = _openid_dh_base64_to_long($request['openid.dh_modulus']);
|
| 170 |
}
|
| 171 |
else {
|
| 172 |
$mod = OPENID_DH_DEFAULT_MOD;
|
| 173 |
}
|
| 174 |
|
| 175 |
if (isset($request['openid.dh_gen'])) {
|
| 176 |
$gen = _openid_dh_base64_to_long($request['openid.dh_gen']);
|
| 177 |
}
|
| 178 |
else {
|
| 179 |
$gen = OPENID_DH_DEFAULT_GEN;
|
| 180 |
}
|
| 181 |
|
| 182 |
$r = _openid_dh_rand($mod);
|
| 183 |
$private = _openid_provider_add($r, 1);
|
| 184 |
$public = _openid_provider_powmod($gen, $private, $mod);
|
| 185 |
|
| 186 |
$cpub = _openid_dh_base64_to_long($request['openid.dh_consumer_public']);
|
| 187 |
$shared = _openid_provider_powmod($cpub, $private, $mod);
|
| 188 |
$mac_key = _openid_provider_dh_xorsecret($shared, $secret, $algo);
|
| 189 |
$enc_mac_key = base64_encode($mac_key);
|
| 190 |
$spub64 = _openid_dh_long_to_base64($public);
|
| 191 |
return array(
|
| 192 |
'dh_server_public' => $spub64,
|
| 193 |
'enc_mac_key' => $enc_mac_key
|
| 194 |
);
|
| 195 |
}
|
| 196 |
|
| 197 |
/**
|
| 198 |
* Is copy of _opend_dh_xorsecret() but uses PHP5 hash() function. Should be merged back into openid client
|
| 199 |
* for D7.
|
| 200 |
*
|
| 201 |
* @param long $shared
|
| 202 |
* @param string $secret
|
| 203 |
* @param string $algo
|
| 204 |
* @return binary string
|
| 205 |
*/
|
| 206 |
function _openid_provider_dh_xorsecret($shared, $secret, $algo = 'sha1') {
|
| 207 |
$dh_shared_str = _openid_dh_long_to_binary($shared);
|
| 208 |
$sha1_dh_shared = hash($algo, $dh_shared_str, true);
|
| 209 |
$xsecret = "";
|
| 210 |
for ($i = 0; $i < strlen($secret); $i++) {
|
| 211 |
$xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
|
| 212 |
}
|
| 213 |
return $xsecret;
|
| 214 |
}
|
| 215 |
|
| 216 |
// 9.2.2.2. Verifying Directly with the Identity Provider
|
| 217 |
// 9.2.2.2.2. Response Parameters
|
| 218 |
// Request is: Exact copies of all fields from the authentication response
|
| 219 |
function openid_provider_verification_response($request) {
|
| 220 |
$is_valid = TRUE;
|
| 221 |
|
| 222 |
// Use the request openid.assoc_handle to look up
|
| 223 |
// how this message should be signed, based on
|
| 224 |
// a previously-created association.
|
| 225 |
$assoc = db_fetch_object(db_query("SELECT * FROM {openid_provider_association} WHERE assoc_handle = '%s'",
|
| 226 |
$request['openid.assoc_handle']));
|
| 227 |
|
| 228 |
$signed_keys = explode(',', $request['openid.signed']);
|
| 229 |
$signature = _openid_provider_signature($assoc, $request, $signed_keys);
|
| 230 |
|
| 231 |
if ($signature != $request['openid.sig']) {
|
| 232 |
$is_valid = FALSE;
|
| 233 |
}
|
| 234 |
|
| 235 |
if ($is_valid) {
|
| 236 |
$response = array('is_valid' => 'true');
|
| 237 |
}
|
| 238 |
else {
|
| 239 |
$response = array(
|
| 240 |
'is_valid' => 'false',
|
| 241 |
'invalidate_handle' => $request['openid.assoc_handle'] // optional, An association handle sent in the request
|
| 242 |
);
|
| 243 |
}
|
| 244 |
|
| 245 |
$message = _openid_create_message($response);
|
| 246 |
header("Content-Type: text/plain");
|
| 247 |
print $message;
|
| 248 |
}
|
| 249 |
|
| 250 |
function openid_provider_cancel_authentication_response($mode = 'checkid_immediate') {
|
| 251 |
$response = array();
|
| 252 |
if ($mode == 'checkid_immediate') {
|
| 253 |
$response = array(
|
| 254 |
'openid.ns' => OPENID_NS_2_0,
|
| 255 |
'openid.mode' => 'id_res',
|
| 256 |
'openid.user_setup_url' => url('user', NULL, NULL, TRUE)
|
| 257 |
);
|
| 258 |
}
|
| 259 |
else {
|
| 260 |
$response = array('openid.module' => OPENID_NS_2_0, 'openid.mode' => 'cancel');
|
| 261 |
}
|
| 262 |
return $response;
|
| 263 |
}
|
| 264 |
|
| 265 |
function _openid_provider_rp_load($uid, $realm = NULL) {
|
| 266 |
if ($realm) {
|
| 267 |
return db_fetch_object(db_query("SELECT * FROM {openid_provider_relying_party} WHERE uid=%d AND realm='%s'", $uid, $realm));
|
| 268 |
}
|
| 269 |
else {
|
| 270 |
$rps = array();
|
| 271 |
$result = db_query("SELECT * FROM {openid_provider_relying_party} WHERE uid=%d ORDER BY last_time DESC", $uid);
|
| 272 |
while ($rp = db_fetch_object($result)){
|
| 273 |
$rps[] = $rp;
|
| 274 |
}
|
| 275 |
return $rps;
|
| 276 |
}
|
| 277 |
}
|
| 278 |
|
| 279 |
function _openid_provider_rp_save($uid, $realm, $auto_release = FALSE) {
|
| 280 |
$rpid = db_result(db_query("SELECT rpid FROM {openid_provider_relying_party} WHERE uid=%d AND realm='%s'", $uid, $realm));
|
| 281 |
if ($rpid) {
|
| 282 |
db_query("UPDATE {openid_provider_relying_party} SET auto_release=%d, last_time=%d WHERE rpid=%d", $auto_release, time(), $rpid);
|
| 283 |
}
|
| 284 |
else {
|
| 285 |
db_query("INSERT INTO {openid_provider_relying_party} (uid, realm, first_time, last_time, auto_release) VALUES (%d, '%s', %d, %d, %d)", $uid, $realm, time(), time(), $auto_release);
|
| 286 |
}
|
| 287 |
}
|
| 288 |
function _openid_provider_nonce() {
|
| 289 |
// YYYY-MM-DDThh:mm:ssTZD UTC, plus some optional extra unique chars
|
| 290 |
return gmstrftime('%Y-%m-%dT%H:%M:%SZ') .
|
| 291 |
chr(mt_rand(0, 25) + 65) .
|
| 292 |
chr(mt_rand(0, 25) + 65) .
|
| 293 |
chr(mt_rand(0, 25) + 65) .
|
| 294 |
chr(mt_rand(0, 25) + 65);
|
| 295 |
}
|
| 296 |
|
| 297 |
function _openid_provider_sign($response) {
|
| 298 |
module_load_include('inc', 'openid');
|
| 299 |
|
| 300 |
$also_sign = array();
|
| 301 |
$parts = parse_url($response['openid.return_to']);
|
| 302 |
if (isset($parts['query'])) {
|
| 303 |
$query = $parts['query'];
|
| 304 |
$q = _openid_get_params($query);
|
| 305 |
foreach ($q as $key => $val) {
|
| 306 |
$also_sign[] = $key;
|
| 307 |
$response[$key] = $val;
|
| 308 |
}
|
| 309 |
}
|
| 310 |
|
| 311 |
$signed_keys = array('op_endpoint', 'return_to', 'response_nonce', 'assoc_handle', 'identity', 'claimed_id');
|
| 312 |
$signed_keys = array_merge($signed_keys, module_invoke_all('openid', 'signed', $response));
|
| 313 |
$response['openid.signed'] = implode(',', $signed_keys);
|
| 314 |
|
| 315 |
// Use the request openid.assoc_handle to look up
|
| 316 |
// how this message should be signed, based on
|
| 317 |
// a previously-created association.
|
| 318 |
$assoc = db_fetch_object(db_query("SELECT * FROM {openid_provider_association} WHERE assoc_handle = '%s'",
|
| 319 |
$response['openid.assoc_handle']));
|
| 320 |
|
| 321 |
// Generate signature for this message
|
| 322 |
$response['openid.sig'] = _openid_provider_signature($assoc, $response, $signed_keys);
|
| 323 |
return $response;
|
| 324 |
}
|
| 325 |
|
| 326 |
/**
|
| 327 |
* Is copy from openid client but uses PHP5 only hash_hmac() function.
|
| 328 |
*
|
| 329 |
* @param object $association
|
| 330 |
* @param array $message_array
|
| 331 |
* @param array $keys_to_sign
|
| 332 |
* @return string
|
| 333 |
*/
|
| 334 |
function _openid_provider_signature($association, $message_array, $keys_to_sign) {
|
| 335 |
$signature = '';
|
| 336 |
$sign_data = array();
|
| 337 |
foreach ($keys_to_sign as $key) {
|
| 338 |
if (isset($message_array['openid.'. $key])) {
|
| 339 |
$sign_data[$key] = $message_array['openid.'. $key];
|
| 340 |
}
|
| 341 |
}
|
| 342 |
$message = _openid_create_message($sign_data);
|
| 343 |
$secret = base64_decode($association->mac_key);
|
| 344 |
$signature = hash_hmac($association->assoc_type == 'HMAC-SHA256' ? 'sha256' : 'sha1', $message, $secret, true);
|
| 345 |
return base64_encode($signature);
|
| 346 |
}
|
| 347 |
|
| 348 |
function _openid_provider_add($a, $b) {
|
| 349 |
if (function_exists('gmp_add')) {
|
| 350 |
return gmp_add($a, $b);
|
| 351 |
}
|
| 352 |
else if (function_exists('bcadd')) {
|
| 353 |
bcadd($a, $b);
|
| 354 |
}
|
| 355 |
}
|
| 356 |
|
| 357 |
function _openid_provider_powmod($base, $exp, $mod) {
|
| 358 |
if (function_exists('gmp_powm')) {
|
| 359 |
return gmp_powm($base, $exp, $mod);
|
| 360 |
}
|
| 361 |
else if (function_exists('bcpowmod')) {
|
| 362 |
return bcpowmod($base, $exp, $mod);
|
| 363 |
}
|
| 364 |
}
|