TYPO3 API  SVNRelease
Association.php
Go to the documentation of this file.
00001 <?php
00002 
00003 /**
00004  * This module contains code for dealing with associations between
00005  * consumers and servers.
00006  *
00007  * PHP versions 4 and 5
00008  *
00009  * LICENSE: See the COPYING file included in this distribution.
00010  *
00011  * @package OpenID
00012  * @author JanRain, Inc. <openid@janrain.com>
00013  * @copyright 2005-2008 Janrain, Inc.
00014  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
00015  */
00016 
00017 /**
00018  * @access private
00019  */
00020 require_once 'Auth/OpenID/CryptUtil.php';
00021 
00022 /**
00023  * @access private
00024  */
00025 require_once 'Auth/OpenID/KVForm.php';
00026 
00027 /**
00028  * @access private
00029  */
00030 require_once 'Auth/OpenID/HMAC.php';
00031 
00032 /**
00033  * This class represents an association between a server and a
00034  * consumer.  In general, users of this library will never see
00035  * instances of this object.  The only exception is if you implement a
00036  * custom {@link Auth_OpenID_OpenIDStore}.
00037  *
00038  * If you do implement such a store, it will need to store the values
00039  * of the handle, secret, issued, lifetime, and assoc_type instance
00040  * variables.
00041  *
00042  * @package OpenID
00043  */
00044 class Auth_OpenID_Association {
00045 
00046     /**
00047      * This is a HMAC-SHA1 specific value.
00048      *
00049      * @access private
00050      */
00051     var $SIG_LENGTH = 20;
00052 
00053     /**
00054      * The ordering and name of keys as stored by serialize.
00055      *
00056      * @access private
00057      */
00058     var $assoc_keys = array(
00059                             'version',
00060                             'handle',
00061                             'secret',
00062                             'issued',
00063                             'lifetime',
00064                             'assoc_type'
00065                             );
00066 
00067     var $_macs = array(
00068                        'HMAC-SHA1' => 'Auth_OpenID_HMACSHA1',
00069                        'HMAC-SHA256' => 'Auth_OpenID_HMACSHA256'
00070                        );
00071 
00072     /**
00073      * This is an alternate constructor (factory method) used by the
00074      * OpenID consumer library to create associations.  OpenID store
00075      * implementations shouldn't use this constructor.
00076      *
00077      * @access private
00078      *
00079      * @param integer $expires_in This is the amount of time this
00080      * association is good for, measured in seconds since the
00081      * association was issued.
00082      *
00083      * @param string $handle This is the handle the server gave this
00084      * association.
00085      *
00086      * @param string secret This is the shared secret the server
00087      * generated for this association.
00088      *
00089      * @param assoc_type This is the type of association this
00090      * instance represents.  The only valid values of this field at
00091      * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
00092      * be defined in the future.
00093      *
00094      * @return association An {@link Auth_OpenID_Association}
00095      * instance.
00096      */
00097     function fromExpiresIn($expires_in, $handle, $secret, $assoc_type)
00098     {
00099         $issued = time();
00100         $lifetime = $expires_in;
00101         return new Auth_OpenID_Association($handle, $secret,
00102                                            $issued, $lifetime, $assoc_type);
00103     }
00104 
00105     /**
00106      * This is the standard constructor for creating an association.
00107      * The library should create all of the necessary associations, so
00108      * this constructor is not part of the external API.
00109      *
00110      * @access private
00111      *
00112      * @param string $handle This is the handle the server gave this
00113      * association.
00114      *
00115      * @param string $secret This is the shared secret the server
00116      * generated for this association.
00117      *
00118      * @param integer $issued This is the time this association was
00119      * issued, in seconds since 00:00 GMT, January 1, 1970.  (ie, a
00120      * unix timestamp)
00121      *
00122      * @param integer $lifetime This is the amount of time this
00123      * association is good for, measured in seconds since the
00124      * association was issued.
00125      *
00126      * @param string $assoc_type This is the type of association this
00127      * instance represents.  The only valid values of this field at
00128      * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
00129      * be defined in the future.
00130      */
00131     function Auth_OpenID_Association(
00132         $handle, $secret, $issued, $lifetime, $assoc_type)
00133     {
00134         if (!in_array($assoc_type,
00135                       Auth_OpenID_getSupportedAssociationTypes())) {
00136             $fmt = 'Unsupported association type (%s)';
00137             trigger_error(sprintf($fmt, $assoc_type), E_USER_ERROR);
00138         }
00139 
00140         $this->handle = $handle;
00141         $this->secret = $secret;
00142         $this->issued = $issued;
00143         $this->lifetime = $lifetime;
00144         $this->assoc_type = $assoc_type;
00145     }
00146 
00147     /**
00148      * This returns the number of seconds this association is still
00149      * valid for, or 0 if the association is no longer valid.
00150      *
00151      * @return integer $seconds The number of seconds this association
00152      * is still valid for, or 0 if the association is no longer valid.
00153      */
00154     function getExpiresIn($now = null)
00155     {
00156         if ($now == null) {
00157             $now = time();
00158         }
00159 
00160         return max(0, $this->issued + $this->lifetime - $now);
00161     }
00162 
00163     /**
00164      * This checks to see if two {@link Auth_OpenID_Association}
00165      * instances represent the same association.
00166      *
00167      * @return bool $result true if the two instances represent the
00168      * same association, false otherwise.
00169      */
00170     function equal($other)
00171     {
00172         return ((gettype($this) == gettype($other))
00173                 && ($this->handle == $other->handle)
00174                 && ($this->secret == $other->secret)
00175                 && ($this->issued == $other->issued)
00176                 && ($this->lifetime == $other->lifetime)
00177                 && ($this->assoc_type == $other->assoc_type));
00178     }
00179 
00180     /**
00181      * Convert an association to KV form.
00182      *
00183      * @return string $result String in KV form suitable for
00184      * deserialization by deserialize.
00185      */
00186     function serialize()
00187     {
00188         $data = array(
00189                      'version' => '2',
00190                      'handle' => $this->handle,
00191                      'secret' => base64_encode($this->secret),
00192                      'issued' => strval(intval($this->issued)),
00193                      'lifetime' => strval(intval($this->lifetime)),
00194                      'assoc_type' => $this->assoc_type
00195                      );
00196 
00197         assert(array_keys($data) == $this->assoc_keys);
00198 
00199         return Auth_OpenID_KVForm::fromArray($data, $strict = true);
00200     }
00201 
00202     /**
00203      * Parse an association as stored by serialize().  This is the
00204      * inverse of serialize.
00205      *
00206      * @param string $assoc_s Association as serialized by serialize()
00207      * @return Auth_OpenID_Association $result instance of this class
00208      */
00209     function deserialize($class_name, $assoc_s)
00210     {
00211         $pairs = Auth_OpenID_KVForm::toArray($assoc_s, $strict = true);
00212         $keys = array();
00213         $values = array();
00214         foreach ($pairs as $key => $value) {
00215             if (is_array($value)) {
00216                 list($key, $value) = $value;
00217             }
00218             $keys[] = $key;
00219             $values[] = $value;
00220         }
00221 
00222         $class_vars = get_class_vars($class_name);
00223         $class_assoc_keys = $class_vars['assoc_keys'];
00224 
00225         sort($keys);
00226         sort($class_assoc_keys);
00227 
00228         if ($keys != $class_assoc_keys) {
00229             trigger_error('Unexpected key values: ' . var_export($keys, true),
00230                           E_USER_WARNING);
00231             return null;
00232         }
00233 
00234         $version = $pairs['version'];
00235         $handle = $pairs['handle'];
00236         $secret = $pairs['secret'];
00237         $issued = $pairs['issued'];
00238         $lifetime = $pairs['lifetime'];
00239         $assoc_type = $pairs['assoc_type'];
00240 
00241         if ($version != '2') {
00242             trigger_error('Unknown version: ' . $version, E_USER_WARNING);
00243             return null;
00244         }
00245 
00246         $issued = intval($issued);
00247         $lifetime = intval($lifetime);
00248         $secret = base64_decode($secret);
00249 
00250         return new $class_name(
00251             $handle, $secret, $issued, $lifetime, $assoc_type);
00252     }
00253 
00254     /**
00255      * Generate a signature for a sequence of (key, value) pairs
00256      *
00257      * @access private
00258      * @param array $pairs The pairs to sign, in order.  This is an
00259      * array of two-tuples.
00260      * @return string $signature The binary signature of this sequence
00261      * of pairs
00262      */
00263     function sign($pairs)
00264     {
00265         $kv = Auth_OpenID_KVForm::fromArray($pairs);
00266 
00267         /* Invalid association types should be caught at constructor */
00268         $callback = $this->_macs[$this->assoc_type];
00269 
00270         return call_user_func_array($callback, array($this->secret, $kv));
00271     }
00272 
00273     /**
00274      * Generate a signature for some fields in a dictionary
00275      *
00276      * @access private
00277      * @param array $fields The fields to sign, in order; this is an
00278      * array of strings.
00279      * @param array $data Dictionary of values to sign (an array of
00280      * string => string pairs).
00281      * @return string $signature The signature, base64 encoded
00282      */
00283     function signMessage($message)
00284     {
00285         if ($message->hasKey(Auth_OpenID_OPENID_NS, 'sig') ||
00286             $message->hasKey(Auth_OpenID_OPENID_NS, 'signed')) {
00287             // Already has a sig
00288             return null;
00289         }
00290 
00291         $extant_handle = $message->getArg(Auth_OpenID_OPENID_NS,
00292                                           'assoc_handle');
00293 
00294         if ($extant_handle && ($extant_handle != $this->handle)) {
00295             // raise ValueError("Message has a different association handle")
00296             return null;
00297         }
00298 
00299         $signed_message = $message;
00300         $signed_message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
00301                                 $this->handle);
00302 
00303         $message_keys = array_keys($signed_message->toPostArgs());
00304         $signed_list = array();
00305         $signed_prefix = 'openid.';
00306 
00307         foreach ($message_keys as $k) {
00308             if (strpos($k, $signed_prefix) === 0) {
00309                 $signed_list[] = substr($k, strlen($signed_prefix));
00310             }
00311         }
00312 
00313         $signed_list[] = 'signed';
00314         sort($signed_list);
00315 
00316         $signed_message->setArg(Auth_OpenID_OPENID_NS, 'signed',
00317                                 implode(',', $signed_list));
00318         $sig = $this->getMessageSignature($signed_message);
00319         $signed_message->setArg(Auth_OpenID_OPENID_NS, 'sig', $sig);
00320         return $signed_message;
00321     }
00322 
00323     /**
00324      * Given a {@link Auth_OpenID_Message}, return the key/value pairs
00325      * to be signed according to the signed list in the message.  If
00326      * the message lacks a signed list, return null.
00327      *
00328      * @access private
00329      */
00330     function _makePairs($message)
00331     {
00332         $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
00333         if (!$signed || Auth_OpenID::isFailure($signed)) {
00334             // raise ValueError('Message has no signed list: %s' % (message,))
00335             return null;
00336         }
00337 
00338         $signed_list = explode(',', $signed);
00339         $pairs = array();
00340         $data = $message->toPostArgs();
00341         foreach ($signed_list as $field) {
00342             $pairs[] = array($field, Auth_OpenID::arrayGet($data,
00343                                                            'openid.' .
00344                                                            $field, ''));
00345         }
00346         return $pairs;
00347     }
00348 
00349     /**
00350      * Given an {@link Auth_OpenID_Message}, return the signature for
00351      * the signed list in the message.
00352      *
00353      * @access private
00354      */
00355     function getMessageSignature($message)
00356     {
00357         $pairs = $this->_makePairs($message);
00358         return base64_encode($this->sign($pairs));
00359     }
00360 
00361     /**
00362      * Confirm that the signature of these fields matches the
00363      * signature contained in the data.
00364      *
00365      * @access private
00366      */
00367     function checkMessageSignature($message)
00368     {
00369         $sig = $message->getArg(Auth_OpenID_OPENID_NS,
00370                                 'sig');
00371 
00372         if (!$sig || Auth_OpenID::isFailure($sig)) {
00373             return false;
00374         }
00375 
00376         $calculated_sig = $this->getMessageSignature($message);
00377         return $calculated_sig == $sig;
00378     }
00379 }
00380 
00381 function Auth_OpenID_getSecretSize($assoc_type)
00382 {
00383     if ($assoc_type == 'HMAC-SHA1') {
00384         return 20;
00385     } else if ($assoc_type == 'HMAC-SHA256') {
00386         return 32;
00387     } else {
00388         return null;
00389     }
00390 }
00391 
00392 function Auth_OpenID_getAllAssociationTypes()
00393 {
00394     return array('HMAC-SHA1', 'HMAC-SHA256');
00395 }
00396 
00397 function Auth_OpenID_getSupportedAssociationTypes()
00398 {
00399     $a = array('HMAC-SHA1');
00400 
00401     if (Auth_OpenID_HMACSHA256_SUPPORTED) {
00402         $a[] = 'HMAC-SHA256';
00403     }
00404 
00405     return $a;
00406 }
00407 
00408 function Auth_OpenID_getSessionTypes($assoc_type)
00409 {
00410     $assoc_to_session = array(
00411        'HMAC-SHA1' => array('DH-SHA1', 'no-encryption'));
00412 
00413     if (Auth_OpenID_HMACSHA256_SUPPORTED) {
00414         $assoc_to_session['HMAC-SHA256'] =
00415             array('DH-SHA256', 'no-encryption');
00416     }
00417 
00418     return Auth_OpenID::arrayGet($assoc_to_session, $assoc_type, array());
00419 }
00420 
00421 function Auth_OpenID_checkSessionType($assoc_type, $session_type)
00422 {
00423     if (!in_array($session_type,
00424                   Auth_OpenID_getSessionTypes($assoc_type))) {
00425         return false;
00426     }
00427 
00428     return true;
00429 }
00430 
00431 function Auth_OpenID_getDefaultAssociationOrder()
00432 {
00433     $order = array();
00434 
00435     if (!Auth_OpenID_noMathSupport()) {
00436         $order[] = array('HMAC-SHA1', 'DH-SHA1');
00437 
00438         if (Auth_OpenID_HMACSHA256_SUPPORTED) {
00439             $order[] = array('HMAC-SHA256', 'DH-SHA256');
00440         }
00441     }
00442 
00443     $order[] = array('HMAC-SHA1', 'no-encryption');
00444 
00445     if (Auth_OpenID_HMACSHA256_SUPPORTED) {
00446         $order[] = array('HMAC-SHA256', 'no-encryption');
00447     }
00448 
00449     return $order;
00450 }
00451 
00452 function Auth_OpenID_getOnlyEncryptedOrder()
00453 {
00454     $result = array();
00455 
00456     foreach (Auth_OpenID_getDefaultAssociationOrder() as $pair) {
00457         list($assoc, $session) = $pair;
00458 
00459         if ($session != 'no-encryption') {
00460             if (Auth_OpenID_HMACSHA256_SUPPORTED &&
00461                 ($assoc == 'HMAC-SHA256')) {
00462                 $result[] = $pair;
00463             } else if ($assoc != 'HMAC-SHA256') {
00464                 $result[] = $pair;
00465             }
00466         }
00467     }
00468 
00469     return $result;
00470 }
00471 
00472 function &Auth_OpenID_getDefaultNegotiator()
00473 {
00474     $x = new Auth_OpenID_SessionNegotiator(
00475                  Auth_OpenID_getDefaultAssociationOrder());
00476     return $x;
00477 }
00478 
00479 function &Auth_OpenID_getEncryptedNegotiator()
00480 {
00481     $x = new Auth_OpenID_SessionNegotiator(
00482                  Auth_OpenID_getOnlyEncryptedOrder());
00483     return $x;
00484 }
00485 
00486 /**
00487  * A session negotiator controls the allowed and preferred association
00488  * types and association session types. Both the {@link
00489  * Auth_OpenID_Consumer} and {@link Auth_OpenID_Server} use
00490  * negotiators when creating associations.
00491  *
00492  * You can create and use negotiators if you:
00493 
00494  * - Do not want to do Diffie-Hellman key exchange because you use
00495  * transport-layer encryption (e.g. SSL)
00496  *
00497  * - Want to use only SHA-256 associations
00498  *
00499  * - Do not want to support plain-text associations over a non-secure
00500  * channel
00501  *
00502  * It is up to you to set a policy for what kinds of associations to
00503  * accept. By default, the library will make any kind of association
00504  * that is allowed in the OpenID 2.0 specification.
00505  *
00506  * Use of negotiators in the library
00507  * =================================
00508  *
00509  * When a consumer makes an association request, it calls {@link
00510  * getAllowedType} to get the preferred association type and
00511  * association session type.
00512  *
00513  * The server gets a request for a particular association/session type
00514  * and calls {@link isAllowed} to determine if it should create an
00515  * association. If it is supported, negotiation is complete. If it is
00516  * not, the server calls {@link getAllowedType} to get an allowed
00517  * association type to return to the consumer.
00518  *
00519  * If the consumer gets an error response indicating that the
00520  * requested association/session type is not supported by the server
00521  * that contains an assocation/session type to try, it calls {@link
00522  * isAllowed} to determine if it should try again with the given
00523  * combination of association/session type.
00524  *
00525  * @package OpenID
00526  */
00527 class Auth_OpenID_SessionNegotiator {
00528     function Auth_OpenID_SessionNegotiator($allowed_types)
00529     {
00530         $this->allowed_types = array();
00531         $this->setAllowedTypes($allowed_types);
00532     }
00533 
00534     /**
00535      * Set the allowed association types, checking to make sure each
00536      * combination is valid.
00537      *
00538      * @access private
00539      */
00540     function setAllowedTypes($allowed_types)
00541     {
00542         foreach ($allowed_types as $pair) {
00543             list($assoc_type, $session_type) = $pair;
00544             if (!Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
00545                 return false;
00546             }
00547         }
00548 
00549         $this->allowed_types = $allowed_types;
00550         return true;
00551     }
00552 
00553     /**
00554      * Add an association type and session type to the allowed types
00555      * list. The assocation/session pairs are tried in the order that
00556      * they are added.
00557      *
00558      * @access private
00559      */
00560     function addAllowedType($assoc_type, $session_type = null)
00561     {
00562         if ($this->allowed_types === null) {
00563             $this->allowed_types = array();
00564         }
00565 
00566         if ($session_type === null) {
00567             $available = Auth_OpenID_getSessionTypes($assoc_type);
00568 
00569             if (!$available) {
00570                 return false;
00571             }
00572 
00573             foreach ($available as $session_type) {
00574                 $this->addAllowedType($assoc_type, $session_type);
00575             }
00576         } else {
00577             if (Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
00578                 $this->allowed_types[] = array($assoc_type, $session_type);
00579             } else {
00580                 return false;
00581             }
00582         }
00583 
00584         return true;
00585     }
00586 
00587     // Is this combination of association type and session type allowed?
00588     function isAllowed($assoc_type, $session_type)
00589     {
00590         $assoc_good = in_array(array($assoc_type, $session_type),
00591                                $this->allowed_types);
00592 
00593         $matches = in_array($session_type,
00594                             Auth_OpenID_getSessionTypes($assoc_type));
00595 
00596         return ($assoc_good && $matches);
00597     }
00598 
00599     /**
00600      * Get a pair of assocation type and session type that are
00601      * supported.
00602      */
00603     function getAllowedType()
00604     {
00605         if (!$this->allowed_types) {
00606             return array(null, null);
00607         }
00608 
00609         return $this->allowed_types[0];
00610     }
00611 }
00612 
00613 ?>