|
1 <?php |
|
2 // $Id: openid.inc,v 1.8 2008/01/30 22:11:22 goba Exp $ |
|
3 |
|
4 /** |
|
5 * @file |
|
6 * OpenID utility functions. |
|
7 */ |
|
8 |
|
9 // Diffie-Hellman Key Exchange Default Value. |
|
10 define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801'. |
|
11 '966915404479707795314057629378541917580651227423698188993727816152646631'. |
|
12 '438561595825688188889951272158842675419950341258706556549803580104870537'. |
|
13 '681476726513255747040765857479291291572334510643245094715007229621094194'. |
|
14 '349783925984760375594985848253359305585439638443'); |
|
15 |
|
16 // Constants for Diffie-Hellman key exchange computations. |
|
17 define('OPENID_DH_DEFAULT_GEN', '2'); |
|
18 define('OPENID_SHA1_BLOCKSIZE', 64); |
|
19 define('OPENID_RAND_SOURCE', '/dev/urandom'); |
|
20 |
|
21 // OpenID namespace URLs |
|
22 define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0'); |
|
23 define('OPENID_NS_1_1', 'http://openid.net/signon/1.1'); |
|
24 define('OPENID_NS_1_0', 'http://openid.net/signon/1.0'); |
|
25 |
|
26 /** |
|
27 * Performs an HTTP 302 redirect (for the 1.x protocol). |
|
28 */ |
|
29 function openid_redirect_http($url, $message) { |
|
30 $query = array(); |
|
31 foreach ($message as $key => $val) { |
|
32 $query[] = $key .'='. urlencode($val); |
|
33 } |
|
34 |
|
35 $sep = (strpos($url, '?') === FALSE) ? '?' : '&'; |
|
36 header('Location: '. $url . $sep . implode('&', $query), TRUE, 302); |
|
37 exit; |
|
38 } |
|
39 |
|
40 /** |
|
41 * Creates a js auto-submit redirect for (for the 2.x protocol) |
|
42 */ |
|
43 function openid_redirect($url, $message) { |
|
44 $output = '<html><head><title>'. t('OpenID redirect') ."</title></head>\n<body>"; |
|
45 $output .= drupal_get_form('openid_redirect_form', $url, $message); |
|
46 $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>'; |
|
47 $output .= "</body></html>\n"; |
|
48 print $output; |
|
49 exit; |
|
50 } |
|
51 |
|
52 function openid_redirect_form(&$form_state, $url, $message) { |
|
53 $form = array(); |
|
54 $form['#action'] = $url; |
|
55 $form['#method'] = "post"; |
|
56 foreach ($message as $key => $value) { |
|
57 $form[$key] = array( |
|
58 '#type' => 'hidden', |
|
59 '#name' => $key, |
|
60 '#value' => $value, |
|
61 ); |
|
62 } |
|
63 $form['submit'] = array( |
|
64 '#type' => 'submit', |
|
65 '#prefix' => '<noscript>', |
|
66 '#suffix' => '</noscript>', |
|
67 '#value' => t('Send'), |
|
68 ); |
|
69 |
|
70 return $form; |
|
71 } |
|
72 |
|
73 /** |
|
74 * Determine if the given identifier is an XRI ID. |
|
75 */ |
|
76 function _openid_is_xri($identifier) { |
|
77 $firstchar = substr($identifier, 0, 1); |
|
78 if ($firstchar == "@" || $firstchar == "=") |
|
79 return TRUE; |
|
80 |
|
81 if (stristr($identifier, 'xri://') !== FALSE) { |
|
82 return TRUE; |
|
83 } |
|
84 |
|
85 return FALSE; |
|
86 } |
|
87 |
|
88 /** |
|
89 * Normalize the given identifier as per spec. |
|
90 */ |
|
91 function _openid_normalize($identifier) { |
|
92 if (_openid_is_xri($identifier)) { |
|
93 return _openid_normalize_xri($identifier); |
|
94 } |
|
95 else { |
|
96 return _openid_normalize_url($identifier); |
|
97 } |
|
98 } |
|
99 |
|
100 function _openid_normalize_xri($xri) { |
|
101 $normalized_xri = $xri; |
|
102 if (stristr($xri, 'xri://') !== FALSE) { |
|
103 $normalized_xri = substr($xri, 6); |
|
104 } |
|
105 return $normalized_xri; |
|
106 } |
|
107 |
|
108 function _openid_normalize_url($url) { |
|
109 $normalized_url = $url; |
|
110 |
|
111 if (stristr($url, '://') === FALSE) { |
|
112 $normalized_url = 'http://'. $url; |
|
113 } |
|
114 |
|
115 if (substr_count($normalized_url, '/') < 3) { |
|
116 $normalized_url .= '/'; |
|
117 } |
|
118 |
|
119 return $normalized_url; |
|
120 } |
|
121 |
|
122 /** |
|
123 * Create a serialized message packet as per spec: $key:$value\n . |
|
124 */ |
|
125 function _openid_create_message($data) { |
|
126 $serialized = ''; |
|
127 |
|
128 foreach ($data as $key => $value) { |
|
129 if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) { |
|
130 return null; |
|
131 } |
|
132 $serialized .= "$key:$value\n"; |
|
133 } |
|
134 return $serialized; |
|
135 } |
|
136 |
|
137 /** |
|
138 * Encode a message from _openid_create_message for HTTP Post |
|
139 */ |
|
140 function _openid_encode_message($message) { |
|
141 $encoded_message = ''; |
|
142 |
|
143 $items = explode("\n", $message); |
|
144 foreach ($items as $item) { |
|
145 $parts = explode(':', $item, 2); |
|
146 |
|
147 if (count($parts) == 2) { |
|
148 if ($encoded_message != '') { |
|
149 $encoded_message .= '&'; |
|
150 } |
|
151 $encoded_message .= rawurlencode(trim($parts[0])) .'='. rawurlencode(trim($parts[1])); |
|
152 } |
|
153 } |
|
154 |
|
155 return $encoded_message; |
|
156 } |
|
157 |
|
158 /** |
|
159 * Convert a direct communication message |
|
160 * into an associative array. |
|
161 */ |
|
162 function _openid_parse_message($message) { |
|
163 $parsed_message = array(); |
|
164 |
|
165 $items = explode("\n", $message); |
|
166 foreach ($items as $item) { |
|
167 $parts = explode(':', $item, 2); |
|
168 |
|
169 if (count($parts) == 2) { |
|
170 $parsed_message[$parts[0]] = $parts[1]; |
|
171 } |
|
172 } |
|
173 |
|
174 return $parsed_message; |
|
175 } |
|
176 |
|
177 /** |
|
178 * Return a nonce value - formatted per OpenID spec. |
|
179 */ |
|
180 function _openid_nonce() { |
|
181 // YYYY-MM-DDThh:mm:ssTZD UTC, plus some optional extra unique chars |
|
182 return gmstrftime('%Y-%m-%dT%H:%M:%S%Z') . |
|
183 chr(mt_rand(0, 25) + 65) . |
|
184 chr(mt_rand(0, 25) + 65) . |
|
185 chr(mt_rand(0, 25) + 65) . |
|
186 chr(mt_rand(0, 25) + 65); |
|
187 } |
|
188 |
|
189 /** |
|
190 * Pull the href attribute out of an html link element. |
|
191 */ |
|
192 function _openid_link_href($rel, $html) { |
|
193 $rel = preg_quote($rel); |
|
194 preg_match('|<link\s+rel=["\'](.*)'. $rel .'(.*)["\'](.*)/?>|iUs', $html, $matches); |
|
195 if (isset($matches[3])) { |
|
196 preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href); |
|
197 return trim($href[1]); |
|
198 } |
|
199 return FALSE; |
|
200 } |
|
201 |
|
202 /** |
|
203 * Pull the http-equiv attribute out of an html meta element |
|
204 */ |
|
205 function _openid_meta_httpequiv($equiv, $html) { |
|
206 preg_match('|<meta\s+http-equiv=["\']'. $equiv .'["\'](.*)/?>|iUs', $html, $matches); |
|
207 if (isset($matches[1])) { |
|
208 preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content); |
|
209 if (isset($content[1])) { |
|
210 return $content[1]; |
|
211 } |
|
212 } |
|
213 return FALSE; |
|
214 } |
|
215 |
|
216 /** |
|
217 * Sign certain keys in a message |
|
218 * @param $association - object loaded from openid_association or openid_server_association table |
|
219 * - important fields are ->assoc_type and ->mac_key |
|
220 * @param $message_array - array of entire message about to be sent |
|
221 * @param $keys_to_sign - keys in the message to include in signature (without |
|
222 * 'openid.' appended) |
|
223 */ |
|
224 function _openid_signature($association, $message_array, $keys_to_sign) { |
|
225 $signature = ''; |
|
226 $sign_data = array(); |
|
227 |
|
228 foreach ($keys_to_sign as $key) { |
|
229 if (isset($message_array['openid.'. $key])) { |
|
230 $sign_data[$key] = $message_array['openid.'. $key]; |
|
231 } |
|
232 } |
|
233 |
|
234 $message = _openid_create_message($sign_data); |
|
235 $secret = base64_decode($association->mac_key); |
|
236 $signature = _openid_hmac($secret, $message); |
|
237 |
|
238 return base64_encode($signature); |
|
239 } |
|
240 |
|
241 function _openid_hmac($key, $text) { |
|
242 if (strlen($key) > OPENID_SHA1_BLOCKSIZE) { |
|
243 $key = _openid_sha1($key, true); |
|
244 } |
|
245 |
|
246 $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00)); |
|
247 $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE); |
|
248 $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE); |
|
249 $hash1 = _openid_sha1(($key ^ $ipad) . $text, true); |
|
250 $hmac = _openid_sha1(($key ^ $opad) . $hash1, true); |
|
251 |
|
252 return $hmac; |
|
253 } |
|
254 |
|
255 function _openid_sha1($text) { |
|
256 $hex = sha1($text); |
|
257 $raw = ''; |
|
258 for ($i = 0; $i < 40; $i += 2) { |
|
259 $hexcode = substr($hex, $i, 2); |
|
260 $charcode = (int)base_convert($hexcode, 16, 10); |
|
261 $raw .= chr($charcode); |
|
262 } |
|
263 return $raw; |
|
264 } |
|
265 |
|
266 function _openid_dh_base64_to_long($str) { |
|
267 $b64 = base64_decode($str); |
|
268 |
|
269 return _openid_dh_binary_to_long($b64); |
|
270 } |
|
271 |
|
272 function _openid_dh_long_to_base64($str) { |
|
273 return base64_encode(_openid_dh_long_to_binary($str)); |
|
274 } |
|
275 |
|
276 function _openid_dh_binary_to_long($str) { |
|
277 $bytes = array_merge(unpack('C*', $str)); |
|
278 |
|
279 $n = 0; |
|
280 foreach ($bytes as $byte) { |
|
281 $n = bcmul($n, pow(2, 8)); |
|
282 $n = bcadd($n, $byte); |
|
283 } |
|
284 |
|
285 return $n; |
|
286 } |
|
287 |
|
288 function _openid_dh_long_to_binary($long) { |
|
289 $cmp = bccomp($long, 0); |
|
290 if ($cmp < 0) { |
|
291 return FALSE; |
|
292 } |
|
293 |
|
294 if ($cmp == 0) { |
|
295 return "\x00"; |
|
296 } |
|
297 |
|
298 $bytes = array(); |
|
299 |
|
300 while (bccomp($long, 0) > 0) { |
|
301 array_unshift($bytes, bcmod($long, 256)); |
|
302 $long = bcdiv($long, pow(2, 8)); |
|
303 } |
|
304 |
|
305 if ($bytes && ($bytes[0] > 127)) { |
|
306 array_unshift($bytes, 0); |
|
307 } |
|
308 |
|
309 $string = ''; |
|
310 foreach ($bytes as $byte) { |
|
311 $string .= pack('C', $byte); |
|
312 } |
|
313 |
|
314 return $string; |
|
315 } |
|
316 |
|
317 function _openid_dh_xorsecret($shared, $secret) { |
|
318 $dh_shared_str = _openid_dh_long_to_binary($shared); |
|
319 $sha1_dh_shared = _openid_sha1($dh_shared_str); |
|
320 $xsecret = ""; |
|
321 for ($i = 0; $i < strlen($secret); $i++) { |
|
322 $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i])); |
|
323 } |
|
324 |
|
325 return $xsecret; |
|
326 } |
|
327 |
|
328 function _openid_dh_rand($stop) { |
|
329 static $duplicate_cache = array(); |
|
330 |
|
331 // Used as the key for the duplicate cache |
|
332 $rbytes = _openid_dh_long_to_binary($stop); |
|
333 |
|
334 if (array_key_exists($rbytes, $duplicate_cache)) { |
|
335 list($duplicate, $nbytes) = $duplicate_cache[$rbytes]; |
|
336 } |
|
337 else { |
|
338 if ($rbytes[0] == "\x00") { |
|
339 $nbytes = strlen($rbytes) - 1; |
|
340 } |
|
341 else { |
|
342 $nbytes = strlen($rbytes); |
|
343 } |
|
344 |
|
345 $mxrand = bcpow(256, $nbytes); |
|
346 |
|
347 // If we get a number less than this, then it is in the |
|
348 // duplicated range. |
|
349 $duplicate = bcmod($mxrand, $stop); |
|
350 |
|
351 if (count($duplicate_cache) > 10) { |
|
352 $duplicate_cache = array(); |
|
353 } |
|
354 |
|
355 $duplicate_cache[$rbytes] = array($duplicate, $nbytes); |
|
356 } |
|
357 |
|
358 do { |
|
359 $bytes = "\x00". _openid_get_bytes($nbytes); |
|
360 $n = _openid_dh_binary_to_long($bytes); |
|
361 // Keep looping if this value is in the low duplicated range. |
|
362 } while (bccomp($n, $duplicate) < 0); |
|
363 |
|
364 return bcmod($n, $stop); |
|
365 } |
|
366 |
|
367 function _openid_get_bytes($num_bytes) { |
|
368 static $f = null; |
|
369 $bytes = ''; |
|
370 if (!isset($f)) { |
|
371 $f = @fopen(OPENID_RAND_SOURCE, "r"); |
|
372 } |
|
373 if (!$f) { |
|
374 // pseudorandom used |
|
375 $bytes = ''; |
|
376 for ($i = 0; $i < $num_bytes; $i += 4) { |
|
377 $bytes .= pack('L', mt_rand()); |
|
378 } |
|
379 $bytes = substr($bytes, 0, $num_bytes); |
|
380 } |
|
381 else { |
|
382 $bytes = fread($f, $num_bytes); |
|
383 } |
|
384 return $bytes; |
|
385 } |
|
386 |
|
387 function _openid_response($str = NULL) { |
|
388 $data = array(); |
|
389 |
|
390 if (isset($_SERVER['REQUEST_METHOD'])) { |
|
391 $data = _openid_get_params($_SERVER['QUERY_STRING']); |
|
392 |
|
393 if ($_SERVER['REQUEST_METHOD'] == 'POST') { |
|
394 $str = file_get_contents('php://input'); |
|
395 |
|
396 $post = array(); |
|
397 if ($str !== false) { |
|
398 $post = _openid_get_params($str); |
|
399 } |
|
400 |
|
401 $data = array_merge($data, $post); |
|
402 } |
|
403 } |
|
404 |
|
405 return $data; |
|
406 } |
|
407 |
|
408 function _openid_get_params($str) { |
|
409 $chunks = explode("&", $str); |
|
410 |
|
411 $data = array(); |
|
412 foreach ($chunks as $chunk) { |
|
413 $parts = explode("=", $chunk, 2); |
|
414 |
|
415 if (count($parts) == 2) { |
|
416 list($k, $v) = $parts; |
|
417 $data[$k] = urldecode($v); |
|
418 } |
|
419 } |
|
420 return $data; |
|
421 } |
|
422 |
|
423 /** |
|
424 * Provide bcpowmod support for PHP4. |
|
425 */ |
|
426 if (!function_exists('bcpowmod')) { |
|
427 function bcpowmod($base, $exp, $mod) { |
|
428 $square = bcmod($base, $mod); |
|
429 $result = 1; |
|
430 while (bccomp($exp, 0) > 0) { |
|
431 if (bcmod($exp, 2)) { |
|
432 $result = bcmod(bcmul($result, $square), $mod); |
|
433 } |
|
434 $square = bcmod(bcmul($square, $square), $mod); |
|
435 $exp = bcdiv($exp, 2); |
|
436 } |
|
437 return $result; |
|
438 } |
|
439 } |