summaryrefslogtreecommitdiffstats
path: root/Auth/OpenID
diff options
context:
space:
mode:
authortailor <cygnus@janrain.com>2007-01-15 19:06:01 +0000
committertailor <cygnus@janrain.com>2007-01-15 19:06:01 +0000
commit03634c658cb7451a8d14935e70a8fdeec8c0364c (patch)
tree281501e34770692cc7f2bae2f17388ce2d888206 /Auth/OpenID
parente7115c7b642f6916939fa5dfce40a16750e5366a (diff)
downloadphp-openid-03634c658cb7451a8d14935e70a8fdeec8c0364c.zip
php-openid-03634c658cb7451a8d14935e70a8fdeec8c0364c.tar.gz
php-openid-03634c658cb7451a8d14935e70a8fdeec8c0364c.tar.bz2
[project @ Refactor Consumer, add session negotiator, update session type classes]
Diffstat (limited to 'Auth/OpenID')
-rw-r--r--Auth/OpenID/Association.php118
-rw-r--r--Auth/OpenID/Consumer.php286
-rw-r--r--Auth/OpenID/DiffieHellman.php55
-rw-r--r--Auth/OpenID/Server.php16
4 files changed, 346 insertions, 129 deletions
diff --git a/Auth/OpenID/Association.php b/Auth/OpenID/Association.php
index ef7b734..9e7ac14 100644
--- a/Auth/OpenID/Association.php
+++ b/Auth/OpenID/Association.php
@@ -350,4 +350,122 @@ class Auth_OpenID_Association {
}
}
+function Auth_OpenID_getSessionTypes($assoc_type)
+{
+ $assoc_to_session = array(
+ 'HMAC-SHA1' => array('DH-SHA1', 'no-encryption'),
+ 'HMAC-SHA256' => array('DH-SHA256', 'no-encryption'));
+ return Auth_OpenID::arrayGet($assoc_to_session, $assoc_type, array());
+}
+
+function Auth_OpenID_checkSessionType($assoc_type, $session_type)
+{
+ if (!in_array($session_type,
+ Auth_OpenID_getSessionTypes($assoc_type))) {
+ return false;
+ }
+
+ return true;
+}
+
+function Auth_OpenID_getDefaultAssociationOrder()
+{
+ return array(
+ array('HMAC-SHA1', 'DH-SHA1'),
+ array('HMAC-SHA1', 'no-encryption'));
+}
+
+function Auth_OpenID_getOnlyEncryptedOrder()
+{
+ return array(
+ array('HMAC-SHA1', 'DH-SHA1'));
+}
+
+function Auth_OpenID_getDefaultNegotiator()
+{
+ return new Auth_OpenID_SessionNegotiator(
+ Auth_OpenID_getDefaultAssociationOrder());
+}
+
+function Auth_OpenID_getEncryptedNegotiator()
+{
+ return new Auth_OpenID_SessionNegotiator(
+ Auth_OpenID_getOnlyEncryptedOrder());
+}
+
+class Auth_OpenID_SessionNegotiator {
+ function Auth_OpenID_SessionNegotiator($allowed_types)
+ {
+ $this->allowed_types = $allowed_types;
+ }
+
+ // Set the allowed association types, checking to make sure each
+ // combination is valid.
+ function setAllowedTypes($allowed_types)
+ {
+ foreach ($allowed_types as $pair) {
+ list($assoc_type, $session_type) = $pair;
+ if (!Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
+ return false;
+ }
+ }
+
+ $this->allowed_types = $allowed_types;
+ return true;
+ }
+
+ // Add an association type and session type to the allowed types
+ // list. The assocation/session pairs are tried in the order that
+ // they are added.
+ function addAllowedType($assoc_type, $session_type = null)
+ {
+ if ($this->allowed_types === null) {
+ $this->allowed_types = array();
+ }
+
+ if ($session_type === null) {
+ $available = Auth_OpenID_getSessionTypes($assoc_type);
+
+ if (!$available) {
+ return false;
+ }
+
+ foreach ($available as $session_type) {
+ $this->addAllowedType($assoc_type, $session_type);
+ }
+ } else {
+ if (Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
+ $this->allowed_types[] = array($assoc_type, $session_type);
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Is this combination of association type and session type allowed?
+ function isAllowed($assoc_type, $session_type)
+ {
+ $assoc_good = in_array(array($assoc_type, $session_type),
+ $this->allowed_types);
+
+ $matches = in_array($session_type,
+ Auth_OpenID_getSessionTypes($assoc_type));
+
+ return ($assoc_good && $matches);
+ }
+
+ // Get a pair of assocation type and session type that are
+ // supported
+ function getAllowedType()
+ {
+ if (!$this->allowed_types) {
+ return array(null, null);
+ }
+
+ return $this->allowed_types[0];
+ }
+}
+
?> \ No newline at end of file
diff --git a/Auth/OpenID/Consumer.php b/Auth/OpenID/Consumer.php
index ba3ffc9..3c8ccb6 100644
--- a/Auth/OpenID/Consumer.php
+++ b/Auth/OpenID/Consumer.php
@@ -402,10 +402,13 @@ class Auth_OpenID_Consumer {
}
}
-class Auth_OpenID_DiffieHellmanConsumerSession {
+class Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
var $session_type = 'DH-SHA1';
+ var $hash_func = 'Auth_OpenID_SHA1';
+ var $secret_size = 20;
+ var $allowed_assoc_types = array('HMAC-SHA1');
- function Auth_OpenID_DiffieHellmanConsumerSession($dh = null)
+ function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null)
{
if ($dh === null) {
$dh = new Auth_OpenID_DiffieHellman();
@@ -420,14 +423,14 @@ class Auth_OpenID_DiffieHellmanConsumerSession {
$cpub = $math->longToBase64($this->dh->public);
- $args = array('openid.dh_consumer_public' => $cpub);
+ $args = array('dh_consumer_public' => $cpub);
if (!$this->dh->usingDefaultValues()) {
$args = array_merge($args, array(
- 'openid.dh_modulus' =>
+ 'dh_modulus' =>
$math->longToBase64($this->dh->mod),
- 'openid.dh_gen' =>
- $math->longToBase64($this->dh->gen)));
+ 'dh_gen' =>
+ $math->longToBase64($this->dh->gen)));
}
return $args;
@@ -446,17 +449,19 @@ class Auth_OpenID_DiffieHellmanConsumerSession {
}
$math =& Auth_OpenID_getMathLib();
+
$spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
'dh_server_public'));
$enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
'enc_mac_key'));
- return $this->dh->xorSecret($spub, $enc_mac_key);
+ return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func);
}
}
class Auth_OpenID_PlainTextConsumerSession {
- var $session_type = null;
+ var $session_type = 'no-encryption';
+ var $allowed_assoc_types = array('HMAC-SHA1');
function getRequest()
{
@@ -514,9 +519,16 @@ class Auth_OpenID_GenericConsumer {
function Auth_OpenID_GenericConsumer(&$store)
{
$this->store =& $store;
+ $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
$this->_use_assocs = !($this->store && $this->store->isDumb());
$this->fetcher = Services_Yadis_Yadis::getHTTPFetcher();
+
+ $this->session_types = array(
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
+ // 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession',
+ 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession'
+ );
}
function begin($service_endpoint)
@@ -813,109 +825,241 @@ class Auth_OpenID_GenericConsumer {
if (($assoc === null) ||
($assoc->getExpiresIn() <= 0)) {
- $parts = $this->_createAssociateRequest($endpoint->server_url);
+ $assoc = $this->_negotiateAssociation($endpoint);
+ if ($assoc !== null) {
+ $this->store->storeAssociation($endpoint->server_url,
+ $assoc);
+ }
+ }
+
+ return $assoc;
+ }
- if ($parts === null) {
+ function _negotiateAssociation($endpoint)
+ {
+ # Get our preferred session/association type from the negotiatior.
+ list($assoc_type, $session_type) = $this->negotiator->getAllowedType();
+
+ $assoc = $this->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (is_a($assoc, 'Auth_OpenID_ServerError')) {
+ // Any error message whose code is not 'unsupported-type'
+ // should be considered a total failure.
+ if (($why->error_code != 'unsupported-type') ||
+ ($why->message->isOpenID1())) {
+ // oidutil.log(
+ // 'Server error when requesting an association from %r: %s'
+ // % (endpoint.server_url, why.error_text))
return null;
}
- list($assoc_session, $message) = $parts;
+ // The server didn't like the association/session type
+ // that we sent, and it sent us back a message that
+ // might tell us how to handle it.
+ // oidutil.log(
+ // 'Unsupported association type %s: %s' % (assoc_type,
+ // why.error_text,))
- $response = $this->_makeKVPost($message, $endpoint->server_url);
+ // Extract the session_type and assoc_type from the
+ // error message
+ $assoc_type = $why->message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type');
- if ($response === null) {
- $assoc = null;
+ $session_type = $why->message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+
+ if (($assoc_type === null) || ($session_type === null)) {
+ // oidutil.log('Server responded with unsupported association '
+ // 'session but did not supply a fallback.')
+ return null;
+ } else if (!$self->negotiator->isAllowed($assoc_type,
+ $session_type)) {
+ // fmt = ('Server sent unsupported session/association type: '
+ // 'session_type=%s, assoc_type=%s')
+ // oidutil.log(fmt % (session_type, assoc_type))
+ return null;
} else {
- $assoc = $this->_parseAssociation($response, $assoc_session,
- $endpoint->server_url);
+ // Attempt to create an association from the assoc_type
+ // and session_type that the server told us it
+ // supported.
+ $assoc = $self->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (is_a($assoc, 'Auth_OpenID_ServerError')) {
+ // Do not keep trying, since it rejected the
+ // association type that it told us to use.
+ // oidutil.log('Server %s refused its suggested association
+ // 'type: session_type=%s, assoc_type=%s'
+ // % (endpoint.server_url, session_type,
+ // assoc_type))
+ return null;
+ } else {
+ return $assoc;
+ }
}
}
return $assoc;
}
- function _createAssociateRequest($server_url)
+ function _requestAssociation($endpoint, $assoc_type, $session_type)
{
- $parts = parse_url($server_url);
+ list($assoc_session, $args) = $this->_createAssociateRequest(
+ $endpoint, $assoc_type, $session_type);
- if ($parts === false) {
- return null;
- }
+ $response = $this->_makeKVPost($args, $endpoint->server_url);
- if (array_key_exists('scheme', $parts)) {
- $proto = $parts['scheme'];
- } else {
- $proto = 'http';
+ if ($response === null) {
+ // oidutil.log('openid.associate request failed: %s' % (why[0],))
+ return null;
}
- if ($proto == 'https' || defined('Auth_OpenID_NO_MATH_SUPPORT')) {
- $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
- } else {
- $assoc_session = new Auth_OpenID_DiffieHellmanConsumerSession();
- }
+ return $this->_extractAssociation($response, $assoc_session);
+ }
- $args = array(
- 'openid.mode' => 'associate',
- 'openid.assoc_type' => 'HMAC-SHA1');
+ function _extractAssociation($assoc_response, $assoc_session)
+ {
+ // Extract the common fields from the response, raising an
+ // exception if they are not found
+ $assoc_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_type',
+ Auth_OpenID_NO_DEFAULT);
+
+ $assoc_handle = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_handle',
+ Auth_OpenID_NO_DEFAULT);
+
+ // expires_in is a base-10 string. The Python parsing will
+ // accept literals that have whitespace around them and will
+ // accept negative values. Neither of these are really in-spec,
+ // but we think it's OK to accept them.
+ $expires_in_str = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'expires_in',
+ Auth_OpenID_NO_DEFAULT);
- if ($assoc_session->session_type !== null) {
- $args['openid.session_type'] = $assoc_session->session_type;
+ $expires_in = intval($expires_in_str);
+ if ($expires_in === false) {
+ // raise ProtocolError('Invalid expires_in field: %s' % (why[0],))
+ return null;
}
- $args = array_merge($args, $assoc_session->getRequest());
- $msg = Auth_OpenID_Message::fromPostArgs($args);
-
- return array($assoc_session, $msg);
- }
+ // OpenID 1 has funny association session behaviour.
+ if ($assoc_response->isOpenID1()) {
+ $session_type = $this->_getOpenID1SessionType($assoc_response);
+ } else {
+ $session_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID2_NS, 'session_type',
+ Auth_OpenID_NO_DEFAULT);
+ }
- /**
- * @access private
- */
- function _parseAssociation($results, $assoc_session, $server_url)
- {
- $required_keys = array('assoc_type', 'assoc_handle',
- 'expires_in');
+ // Session type mismatch
+ if ($assoc_session->session_type != $session_type) {
+ if ($assoc_response->isOpenID1() &&
+ ($session_type == 'no-encryption')) {
+ // In OpenID 1, any association request can result in
+ // a 'no-encryption' association response. Setting
+ // assoc_session to a new no-encryption session should
+ // make the rest of this function work properly for
+ // that case.
+ $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
+ } else {
+ // Any other mismatch, regardless of protocol version
+ // results in the failure of the association session
+ // altogether.
- foreach ($required_keys as $key) {
- if (!$results->hasKey(Auth_OpenID_OPENID_NS, $key)) {
+ // fmt = 'Session type mismatch. Expected %r, got %r'
+ // message = fmt % (assoc_session.session_type, session_type)
+ // raise ProtocolError(message)
return null;
}
}
- $assoc_type = $results->getArg(Auth_OpenID_OPENID_NS, 'assoc_type');
- $assoc_handle = $results->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
- $expires_in_str = $results->getArg(Auth_OpenID_OPENID_NS, 'expires_in');
-
- if ($assoc_type != 'HMAC-SHA1') {
+ // Make sure assoc_type is valid for session_type
+ if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) {
+ // fmt = 'Unsupported assoc_type for session %s returned: %s'
+ // raise ProtocolError(fmt % (assoc_session.session_type,
+ // assoc_type))
return null;
}
- $expires_in = intval($expires_in_str);
+ // Delegate to the association session to extract the secret
+ // from the response, however is appropriate for that session
+ // type.
+ $secret = $assoc_session->extractSecret($assoc_response);
- if ($expires_in <= 0) {
+ if ($secret === null) {
+ // fmt = 'Malformed response for %s session: %s'
+ // raise ProtocolError(fmt % (assoc_session.session_type, why[0]))
return null;
}
- $session_type = $results->getArg(Auth_OpenID_OPENID_NS, 'session_type');
- if ($session_type != $assoc_session->session_type) {
- if ($session_type === null) {
- $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
- } else {
- return null;
- }
- }
+ return Auth_OpenID_Association::fromExpiresIn(
+ $expires_in, $assoc_handle, $secret, $assoc_type);
+ }
- $secret = $assoc_session->extractSecret($results);
+ function _createAssociateRequest($endpoint, $assoc_type, $session_type)
+ {
+ $session_type_class = $this->session_types[$session_type];
+ $assoc_session = new $session_type_class();
- if (!$secret) {
- return null;
+ $args = array(
+ 'mode' => 'associate',
+ 'assoc_type' => $assoc_type);
+
+ if (!$endpoint->compatibilityMode()) {
+ $args['ns'] = Auth_OpenID_OPENID2_NS;
}
- $assoc = Auth_OpenID_Association::fromExpiresIn(
- $expires_in, $assoc_handle, $secret, $assoc_type);
- $this->store->storeAssociation($server_url, $assoc);
+ // Leave out the session type if we're in compatibility mode
+ // *and* it's no-encryption.
+ if ((!$endpoint->compatibilityMode()) ||
+ ($assoc_session->session_type != 'no-encryption')) {
+ $args['session_type'] = $assoc_session->session_type;
+ }
- return $assoc;
+ $args = array_merge($args, $assoc_session->getRequest());
+ $message = Auth_OpenID_Message::fromOpenIDArgs($args);
+ return array($assoc_session, $message);
+ }
+
+ // Given an association response message, extract the OpenID 1.X
+ // session type.
+ //
+ // This function mostly takes care of the 'no-encryption' default
+ // behavior in OpenID 1.
+ //
+ // If the association type is plain-text, this function will
+ // return 'no-encryption'
+ //
+ // @returns: The association type for this message
+ // @rtype: str
+ //
+ // @raises: KeyError, if the session_type field is absent.
+ function _getOpenID1SessionType($assoc_response)
+ {
+ // If it's an OpenID 1 message, allow session_type to default
+ // to None (which signifies "no-encryption")
+ $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_NS,
+ 'session_type');
+
+ // Handle the differences between no-encryption association
+ // respones in OpenID 1 and 2:
+
+ // no-encryption is not really a valid session type for OpenID
+ // 1, but we'll accept it anyway, while issuing a warning.
+ if ($session_type == 'no-encryption') {
+ // oidutil.log('WARNING: OpenID server sent "no-encryption"'
+ // 'for OpenID 1.X')
+ } else if (($session_type == '') || ($session_type === null)) {
+ // Missing or empty session type is the way to flag a
+ // 'no-encryption' response. Change the session type to
+ // 'no-encryption' so that it can be handled in the same
+ // way as OpenID 2 'no-encryption' respones.
+ $session_type = 'no-encryption';
+ }
+
+ return $session_type;
}
}
diff --git a/Auth/OpenID/DiffieHellman.php b/Auth/OpenID/DiffieHellman.php
index 2b845b7..0551390 100644
--- a/Auth/OpenID/DiffieHellman.php
+++ b/Auth/OpenID/DiffieHellman.php
@@ -116,64 +116,15 @@ class Auth_OpenID_DiffieHellman {
$this->gen == Auth_OpenID_getDefaultGen());
}
- /**
- * Perform the server side of the OpenID Diffie-Hellman association
- */
- function serverAssociate($consumer_args, $assoc_secret)
- {
- $lib =& Auth_OpenID_getMathLib();
-
- if (isset($consumer_args['openid.dh_modulus'])) {
- $mod = $lib->base64ToLong($consumer_args['openid.dh_modulus']);
- } else {
- $mod = null;
- }
-
- if (isset($consumer_args['openid.dh_gen'])) {
- $gen = $lib->base64ToLong($consumer_args['openid.dh_gen']);
- } else {
- $gen = null;
- }
-
- $cpub64 = @$consumer_args['openid.dh_consumer_public'];
- if (!isset($cpub64)) {
- return false;
- }
-
- $dh = new Auth_OpenID_DiffieHellman($mod, $gen);
- $cpub = $lib->base64ToLong($cpub64);
- $mac_key = $dh->xorSecret($cpub, $assoc_secret);
- $enc_mac_key = base64_encode($mac_key);
- $spub64 = $lib->longToBase64($dh->getPublicKey());
-
- $server_args = array(
- 'session_type' => 'DH-SHA1',
- 'dh_server_public' => $spub64,
- 'enc_mac_key' => $enc_mac_key
- );
-
- return $server_args;
- }
-
- function consumerFinish($reply)
- {
- $spub = $this->lib->base64ToLong($reply['dh_server_public']);
- if ($this->lib->cmp($spub, 0) <= 0) {
- return false;
- }
- $enc_mac_key = base64_decode($reply['enc_mac_key']);
- return $this->xorSecret($spub, $enc_mac_key);
- }
-
- function xorSecret($composite, $secret)
+ function xorSecret($composite, $secret, $hash_func)
{
$dh_shared = $this->getSharedSecret($composite);
$dh_shared_str = $this->lib->longToBinary($dh_shared);
- $sha1_dh_shared = Auth_OpenID_SHA1($dh_shared_str);
+ $hash_dh_shared = $hash_func($dh_shared_str);
$xsecret = "";
for ($i = 0; $i < strlen($secret); $i++) {
- $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
+ $xsecret .= chr(ord($secret[$i]) ^ ord($hash_dh_shared[$i]));
}
return $xsecret;
diff --git a/Auth/OpenID/Server.php b/Auth/OpenID/Server.php
index 130f541..eb6c688 100644
--- a/Auth/OpenID/Server.php
+++ b/Auth/OpenID/Server.php
@@ -363,6 +363,7 @@ class Auth_OpenID_PlainTextServerSession {
*/
var $session_type = 'plaintext';
var $needs_math = false;
+ var $allowed_assoc_types = array('HMAC-SHA1');
function fromMessage($unused_request)
{
@@ -375,7 +376,7 @@ class Auth_OpenID_PlainTextServerSession {
}
}
-class Auth_OpenID_DiffieHellmanServerSession {
+class Auth_OpenID_DiffieHellmanSHA1ServerSession {
/**
* An object that knows how to handle association requests with
* the Diffie-Hellman session type.
@@ -383,8 +384,10 @@ class Auth_OpenID_DiffieHellmanServerSession {
var $session_type = 'DH-SHA1';
var $needs_math = true;
+ var $allowed_assoc_types = array('HMAC-SHA1');
+ var $hash_func = 'Auth_OpenID_SHA1';
- function Auth_OpenID_DiffieHellmanServerSession($dh, $consumer_pubkey)
+ function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey)
{
$this->dh = $dh;
$this->consumer_pubkey = $consumer_pubkey;
@@ -441,14 +444,15 @@ class Auth_OpenID_DiffieHellmanServerSession {
"dh_consumer_public is not base64");
}
- return new Auth_OpenID_DiffieHellmanServerSession($dh,
- $consumer_pubkey);
+ return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh,
+ $consumer_pubkey);
}
function answer($secret)
{
$lib =& Auth_OpenID_getMathLib();
- $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret);
+ $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret,
+ $this->hash_func);
return array(
'dh_server_public' =>
$lib->longToBase64($this->dh->public),
@@ -475,7 +479,7 @@ class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
function fromMessage($message)
{
$session_classes = array(
- 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanServerSession',
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession',
null => 'Auth_OpenID_PlainTextServerSession');
$session_type = $message->getArg(Auth_OpenID_OPENID_NS,