1: <?php
  2: 
  3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15: 
 16: 
 17:  18:  19: 
 20: require_once 'Auth/OpenID/CryptUtil.php';
 21: 
 22:  23:  24: 
 25: require_once 'Auth/OpenID/KVForm.php';
 26: 
 27:  28:  29: 
 30: require_once 'Auth/OpenID/HMAC.php';
 31: 
 32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43: 
 44: class Auth_OpenID_Association {
 45: 
 46:      47:  48:  49:  50: 
 51:     var $SIG_LENGTH = 20;
 52: 
 53:      54:  55:  56:  57: 
 58:     var $assoc_keys = array(
 59:                             'version',
 60:                             'handle',
 61:                             'secret',
 62:                             'issued',
 63:                             'lifetime',
 64:                             'assoc_type'
 65:                             );
 66: 
 67:     var $_macs = array(
 68:                        'HMAC-SHA1' => 'Auth_OpenID_HMACSHA1',
 69:                        'HMAC-SHA256' => 'Auth_OpenID_HMACSHA256'
 70:                        );
 71: 
 72:      73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96: 
 97:     static function fromExpiresIn($expires_in, $handle, $secret, $assoc_type)
 98:     {
 99:         $issued = time();
100:         $lifetime = $expires_in;
101:         return new Auth_OpenID_Association($handle, $secret,
102:                                            $issued, $lifetime, $assoc_type);
103:     }
104: 
105:     106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 
131:     function Auth_OpenID_Association(
132:         $handle, $secret, $issued, $lifetime, $assoc_type)
133:     {
134:         if (!in_array($assoc_type,
135:                       Auth_OpenID_getSupportedAssociationTypes(), true)) {
136:             $fmt = 'Unsupported association type (%s)';
137:             trigger_error(sprintf($fmt, $assoc_type), E_USER_ERROR);
138:         }
139: 
140:         $this->handle = $handle;
141:         $this->secret = $secret;
142:         $this->issued = $issued;
143:         $this->lifetime = $lifetime;
144:         $this->assoc_type = $assoc_type;
145:     }
146: 
147:     148: 149: 150: 151: 152: 153: 
154:     function getExpiresIn($now = null)
155:     {
156:         if ($now == null) {
157:             $now = time();
158:         }
159: 
160:         return max(0, $this->issued + $this->lifetime - $now);
161:     }
162: 
163:     164: 165: 166: 167: 168: 169: 
170:     function equal($other)
171:     {
172:         return ((gettype($this) == gettype($other))
173:                 && ($this->handle == $other->handle)
174:                 && ($this->secret == $other->secret)
175:                 && ($this->issued == $other->issued)
176:                 && ($this->lifetime == $other->lifetime)
177:                 && ($this->assoc_type == $other->assoc_type));
178:     }
179: 
180:     181: 182: 183: 184: 185: 
186:     function serialize()
187:     {
188:         $data = array(
189:                      'version' => '2',
190:                      'handle' => $this->handle,
191:                      'secret' => base64_encode($this->secret),
192:                      'issued' => strval(intval($this->issued)),
193:                      'lifetime' => strval(intval($this->lifetime)),
194:                      'assoc_type' => $this->assoc_type
195:                      );
196: 
197:         assert(array_keys($data) == $this->assoc_keys);
198: 
199:         return Auth_OpenID_KVForm::fromArray($data, $strict = true);
200:     }
201: 
202:     203: 204: 205: 206: 207: 208: 
209:     static function deserialize($class_name, $assoc_s)
210:     {
211:         $pairs = Auth_OpenID_KVForm::toArray($assoc_s, $strict = true);
212:         $keys = array();
213:         $values = array();
214:         foreach ($pairs as $key => $value) {
215:             if (is_array($value)) {
216:                 list($key, $value) = $value;
217:             }
218:             $keys[] = $key;
219:             $values[] = $value;
220:         }
221: 
222:         $class_vars = get_class_vars($class_name);
223:         $class_assoc_keys = $class_vars['assoc_keys'];
224: 
225:         sort($keys);
226:         sort($class_assoc_keys);
227: 
228:         if ($keys != $class_assoc_keys) {
229:             trigger_error('Unexpected key values: ' . var_export($keys, true),
230:                           E_USER_WARNING);
231:             return null;
232:         }
233: 
234:         $version = $pairs['version'];
235:         $handle = $pairs['handle'];
236:         $secret = $pairs['secret'];
237:         $issued = $pairs['issued'];
238:         $lifetime = $pairs['lifetime'];
239:         $assoc_type = $pairs['assoc_type'];
240: 
241:         if ($version != '2') {
242:             trigger_error('Unknown version: ' . $version, E_USER_WARNING);
243:             return null;
244:         }
245: 
246:         $issued = intval($issued);
247:         $lifetime = intval($lifetime);
248:         $secret = base64_decode($secret);
249: 
250:         return new $class_name(
251:             $handle, $secret, $issued, $lifetime, $assoc_type);
252:     }
253: 
254:     255: 256: 257: 258: 259: 260: 261: 262: 
263:     function sign($pairs)
264:     {
265:         $kv = Auth_OpenID_KVForm::fromArray($pairs);
266: 
267:         
268:         $callback = $this->_macs[$this->assoc_type];
269: 
270:         return call_user_func_array($callback, array($this->secret, $kv));
271:     }
272: 
273:     274: 275: 276: 277: 278: 279: 280: 281: 282: 
283:     function signMessage($message)
284:     {
285:         if ($message->hasKey(Auth_OpenID_OPENID_NS, 'sig') ||
286:             $message->hasKey(Auth_OpenID_OPENID_NS, 'signed')) {
287:             
288:             return null;
289:         }
290: 
291:         $extant_handle = $message->getArg(Auth_OpenID_OPENID_NS,
292:                                           'assoc_handle');
293: 
294:         if ($extant_handle && ($extant_handle != $this->handle)) {
295:             
296:             return null;
297:         }
298: 
299:         $signed_message = $message;
300:         $signed_message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
301:                                 $this->handle);
302: 
303:         $message_keys = array_keys($signed_message->toPostArgs());
304:         $signed_list = array();
305:         $signed_prefix = 'openid.';
306: 
307:         foreach ($message_keys as $k) {
308:             if (strpos($k, $signed_prefix) === 0) {
309:                 $signed_list[] = substr($k, strlen($signed_prefix));
310:             }
311:         }
312: 
313:         $signed_list[] = 'signed';
314:         sort($signed_list);
315: 
316:         $signed_message->setArg(Auth_OpenID_OPENID_NS, 'signed',
317:                                 implode(',', $signed_list));
318:         $sig = $this->getMessageSignature($signed_message);
319:         $signed_message->setArg(Auth_OpenID_OPENID_NS, 'sig', $sig);
320:         return $signed_message;
321:     }
322: 
323:     324: 325: 326: 327: 328: 329: 
330:     function _makePairs($message)
331:     {
332:         $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
333:         if (!$signed || Auth_OpenID::isFailure($signed)) {
334:             
335:             return null;
336:         }
337: 
338:         $signed_list = explode(',', $signed);
339:         $pairs = array();
340:         $data = $message->toPostArgs();
341:         foreach ($signed_list as $field) {
342:             $pairs[] = array($field, Auth_OpenID::arrayGet($data,
343:                                                            'openid.' .
344:                                                            $field, ''));
345:         }
346:         return $pairs;
347:     }
348: 
349:     350: 351: 352: 353: 354: 
355:     function getMessageSignature($message)
356:     {
357:         $pairs = $this->_makePairs($message);
358:         return base64_encode($this->sign($pairs));
359:     }
360: 
361:     362: 363: 364: 365: 366: 
367:     function checkMessageSignature($message)
368:     {
369:         $sig = $message->getArg(Auth_OpenID_OPENID_NS,
370:                                 'sig');
371: 
372:         if (!$sig || Auth_OpenID::isFailure($sig)) {
373:             return false;
374:         }
375: 
376:         $calculated_sig = $this->getMessageSignature($message);
377:         return Auth_OpenID_CryptUtil::constEq($calculated_sig, $sig);
378:     }
379: }
380: 
381: function Auth_OpenID_getSecretSize($assoc_type)
382: {
383:     if ($assoc_type == 'HMAC-SHA1') {
384:         return 20;
385:     } else if ($assoc_type == 'HMAC-SHA256') {
386:         return 32;
387:     } else {
388:         return null;
389:     }
390: }
391: 
392: function Auth_OpenID_getAllAssociationTypes()
393: {
394:     return array('HMAC-SHA1', 'HMAC-SHA256');
395: }
396: 
397: function Auth_OpenID_getSupportedAssociationTypes()
398: {
399:     $a = array('HMAC-SHA1');
400: 
401:     if (Auth_OpenID_HMACSHA256_SUPPORTED) {
402:         $a[] = 'HMAC-SHA256';
403:     }
404: 
405:     return $a;
406: }
407: 
408: function Auth_OpenID_getSessionTypes($assoc_type)
409: {
410:     $assoc_to_session = array(
411:        'HMAC-SHA1' => array('DH-SHA1', 'no-encryption'));
412: 
413:     if (Auth_OpenID_HMACSHA256_SUPPORTED) {
414:         $assoc_to_session['HMAC-SHA256'] =
415:             array('DH-SHA256', 'no-encryption');
416:     }
417: 
418:     return Auth_OpenID::arrayGet($assoc_to_session, $assoc_type, array());
419: }
420: 
421: function Auth_OpenID_checkSessionType($assoc_type, $session_type)
422: {
423:     if (!in_array($session_type,
424:                   Auth_OpenID_getSessionTypes($assoc_type))) {
425:         return false;
426:     }
427: 
428:     return true;
429: }
430: 
431: function Auth_OpenID_getDefaultAssociationOrder()
432: {
433:     $order = array();
434: 
435:     if (!Auth_OpenID_noMathSupport()) {
436:         $order[] = array('HMAC-SHA1', 'DH-SHA1');
437: 
438:         if (Auth_OpenID_HMACSHA256_SUPPORTED) {
439:             $order[] = array('HMAC-SHA256', 'DH-SHA256');
440:         }
441:     }
442: 
443:     $order[] = array('HMAC-SHA1', 'no-encryption');
444: 
445:     if (Auth_OpenID_HMACSHA256_SUPPORTED) {
446:         $order[] = array('HMAC-SHA256', 'no-encryption');
447:     }
448: 
449:     return $order;
450: }
451: 
452: function Auth_OpenID_getOnlyEncryptedOrder()
453: {
454:     $result = array();
455: 
456:     foreach (Auth_OpenID_getDefaultAssociationOrder() as $pair) {
457:         list($assoc, $session) = $pair;
458: 
459:         if ($session != 'no-encryption') {
460:             if (Auth_OpenID_HMACSHA256_SUPPORTED &&
461:                 ($assoc == 'HMAC-SHA256')) {
462:                 $result[] = $pair;
463:             } else if ($assoc != 'HMAC-SHA256') {
464:                 $result[] = $pair;
465:             }
466:         }
467:     }
468: 
469:     return $result;
470: }
471: 
472: function Auth_OpenID_getDefaultNegotiator()
473: {
474:     return new Auth_OpenID_SessionNegotiator(
475:                  Auth_OpenID_getDefaultAssociationOrder());
476: }
477: 
478: function Auth_OpenID_getEncryptedNegotiator()
479: {
480:     return new Auth_OpenID_SessionNegotiator(
481:                  Auth_OpenID_getOnlyEncryptedOrder());
482: }
483: 
484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 
525: class Auth_OpenID_SessionNegotiator {
526:     function Auth_OpenID_SessionNegotiator($allowed_types)
527:     {
528:         $this->allowed_types = array();
529:         $this->setAllowedTypes($allowed_types);
530:     }
531: 
532:     533: 534: 535: 536: 537: 
538:     function setAllowedTypes($allowed_types)
539:     {
540:         foreach ($allowed_types as $pair) {
541:             list($assoc_type, $session_type) = $pair;
542:             if (!Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
543:                 return false;
544:             }
545:         }
546: 
547:         $this->allowed_types = $allowed_types;
548:         return true;
549:     }
550: 
551:     552: 553: 554: 555: 556: 557: 
558:     function addAllowedType($assoc_type, $session_type = null)
559:     {
560:         if ($this->allowed_types === null) {
561:             $this->allowed_types = array();
562:         }
563: 
564:         if ($session_type === null) {
565:             $available = Auth_OpenID_getSessionTypes($assoc_type);
566: 
567:             if (!$available) {
568:                 return false;
569:             }
570: 
571:             foreach ($available as $session_type) {
572:                 $this->addAllowedType($assoc_type, $session_type);
573:             }
574:         } else {
575:             if (Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
576:                 $this->allowed_types[] = array($assoc_type, $session_type);
577:             } else {
578:                 return false;
579:             }
580:         }
581: 
582:         return true;
583:     }
584: 
585:     
586:     function isAllowed($assoc_type, $session_type)
587:     {
588:         $assoc_good = in_array(array($assoc_type, $session_type),
589:                                $this->allowed_types);
590: 
591:         $matches = in_array($session_type,
592:                             Auth_OpenID_getSessionTypes($assoc_type));
593: 
594:         return ($assoc_good && $matches);
595:     }
596: 
597:     598: 599: 600: 
601:     function getAllowedType()
602:     {
603:         if (!$this->allowed_types) {
604:             return array(null, null);
605:         }
606: 
607:         return $this->allowed_types[0];
608:     }
609: }
610: 
611: