Add simpleid-1.0.5
diff --git a/simpleid/www/openid.inc.php b/simpleid/www/openid.inc.php
new file mode 100644
index 0000000..8507804
--- /dev/null
+++ b/simpleid/www/openid.inc.php
@@ -0,0 +1,1054 @@
+<?php
+/*
+ * SimpleID
+ *
+ * Copyright (C) Kelvin Mo 2007-10
+ *
+ * Includes code Drupal OpenID module (http://drupal.org/project/openid)
+ * Rowan Kerr <rowan@standardinteractive.com>
+ * James Walker <james@bryght.com>
+ *
+ * Copyright (C) Rowan Kerr and James Walker
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 
+ * $Id$
+ */
+
+/**
+ * OpenID related functions.
+ *
+ * @package simpleid
+ * @filesource
+ */
+ 
+include_once "bignum.inc.php";
+include_once "random.inc.php";
+ 
+/**
+ * OpenID default modulus for Diffie-Hellman key exchange.
+ *
+ * @link http://openid.net/specs/openid-authentication-1_1.html#pvalue, http://openid.net/specs/openid-authentication-2_0.html#pvalue
+ */
+define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801'.
+       '966915404479707795314057629378541917580651227423698188993727816152646631'.
+       '438561595825688188889951272158842675419950341258706556549803580104870537'.
+       '681476726513255747040765857479291291572334510643245094715007229621094194'.
+       '349783925984760375594985848253359305585439638443');
+
+/**
+ * OpenID default generator for Diffie-Hellman key exchange.
+ */
+define('OPENID_DH_DEFAULT_GEN', '2');
+
+/** Constant for the global variable {@link $version} */
+define('OPENID_VERSION_2', 2);
+/** Constant for the global variable {@link $version} */
+define('OPENID_VERSION_1_1', 1);
+
+/** Constant for OpenID namespace */
+define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
+/** Constant for OpenID namespace */
+define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
+/** Constant for OpenID namespace */
+define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
+
+/**
+ * Constant for the OP-local identifier which indicates that SimpleID should choose an identifier
+ *
+ * @link http://openid.net/specs/openid-authentication-2_0.html#anchor27
+ */
+define('OPENID_IDENTIFIER_SELECT', 'http://specs.openid.net/auth/2.0/identifier_select');
+/** Constant for the XRDS service type for return_to verification */
+define('OPENID_RETURN_TO', 'http://specs.openid.net/auth/2.0/return_to');
+
+/** Parameter for {@link openid_indirect_response_url()} */
+define('OPENID_RESPONSE_QUERY', 0);
+/** Parameter for {@link openid_indirect_response_url()} */
+define('OPENID_RESPONSE_FRAGMENT', 1);
+
+/**
+ * A mapping of Type URIs of OpenID extnesions to aliases provided in an OpenID
+ * request.
+ *
+ * @global array $openid_ns_to_alias
+ */
+$openid_ns_to_alias = array("http://openid.net/extensions/sreg/1.1" => "sreg"); // For sreg 1.0 compatibility
+
+
+/**
+ * Detects the OpenID version of the current request
+ *
+ * @param mixed $request the OpenID request
+ * @param string $key the key to look for to determine the OpenID
+ * version
+ * @return float either OPENID_VERSION_2 or OPENID_VERSION_1_1
+ * @see $version
+ *
+ */
+function openid_get_version($request, $key = 'openid.ns') {
+    if (!isset($request[$key])) return OPENID_VERSION_1_1;
+    if ($request[$key] != OPENID_NS_2_0) return OPENID_VERSION_1_1;
+    return OPENID_VERSION_2;
+}
+
+/**
+ * Creates a OpenID message for direct response.
+ *
+ * The response will be encoded using Key-Value Form Encoding.
+ *
+ * @param array $data the data in the response
+ * @param float $version the message version
+ * @return string the message in key-value form encoding
+ * @link http://openid.net/specs/openid-authentication-1_1.html#anchor32, http://openid.net/specs/openid-authentication-2_0.html#kvform
+ */
+function openid_direct_message($data, $version = OPENID_VERSION_2) {
+    $message = '';
+    $ns = '';
+    
+    // Add namespace for OpenID 2
+    if ($version == OPENID_VERSION_2) $ns = OPENID_NS_2_0;
+    if (($ns != '') && !isset($data['ns'])) $data['ns'] = $ns;
+    
+    foreach ($data as $key => $value) {
+        // Filter out invalid characters
+        if (strpos($key, ':') !== false) return null;
+        if (strpos($key, "\n") !== false) return null;
+        if (strpos($value, "\n") !== false) return null;
+        
+        $message .= "$key:$value\n";
+    }
+    return $message;
+}
+
+/**
+ * Sends a direct response.
+ *
+ * @param string $message an OpenID message encoded using Key-Value Form
+ * @param string $status the HTTP status to send
+ */
+function openid_direct_response($message, $status = '200 OK') {
+    if (substr(PHP_SAPI, 0, 3) === 'cgi') {
+        header("Status: $status");
+    } else {
+        header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status);
+    }
+    
+    header("Content-Type: text/plain");
+    print $message;
+}
+
+/**
+ * Creates a OpenID message for indirect response.
+ *
+ * The response will be encoded using HTTP Encoding.
+ *
+ * @param array $data the data in the response
+ * @param float $version the message version
+ * @return array the message
+ * @link http://openid.net/specs/openid-authentication-2_0.html#indirect_comm
+ */
+function openid_indirect_message($data, $version = OPENID_VERSION_2) {
+    $ns = '';
+    
+    // Add namespace for OpenID 2
+    if ($version == OPENID_VERSION_2) $ns = OPENID_NS_2_0;
+    if (($ns != '') && !isset($data['openid.ns'])) $data['openid.ns'] = $ns;
+    
+    return $data;
+}
+
+/**
+ * Sends an indirect response to a URL.
+ *
+ * The indirect message is encoded in the URL and returned to the user agent using
+ * a HTTP redirect response.  The message can be encoded in either the query component
+ * or the fragment component of the URL.
+ *
+ * @param string $url the URL to which the response is to be sent
+ * @param array|string $message an OpenID message, which can either be an array of keys
+ * and values, or a URL-encoded query string
+ * @param int $component the component of the URL in which the indirect message is
+ * encoded, either OPENID_RESPONSE_QUERY or OPENID_RESPONSE_FRAGMENT
+ */ 
+function openid_indirect_response($url, $message, $component = OPENID_RESPONSE_QUERY) {
+    if (substr(PHP_SAPI, 0,3) === 'cgi') {
+        header('Status: 303 See Other');
+    } else {
+        header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other');
+    }
+
+    header('Location: ' . openid_indirect_response_url($url, $message, $component));
+    exit;
+}
+
+/**
+ * Encodes an indirect message into a URL
+ *
+ * @param string $url the URL to which the response is to be sent
+ * @param array|string $message an OpenID message, which can either be an array of keys
+ * and values, or a URL-encoded query string
+ * @param int $component the component of the URL in which the indirect message is
+ * encoded, either OPENID_RESPONSE_QUERY or OPENID_RESPONSE_FRAGMENT
+ * @return string the URL to which the response is to be sent, with the
+ * encoded message
+ */
+function openid_indirect_response_url($url, $message, $component = OPENID_RESPONSE_QUERY) {
+    // 1. Firstly, get the query string
+    $query = '';
+    
+    if (is_array($message)) {
+        $query = openid_urlencode_message($message);
+    } else {
+        $query = $message;
+    }
+    
+    // 2. If there is no query string, then we just return the URL
+    if (!$query) return $url;
+    
+    // 3. The URL may already have a query and a fragment.  If this is so, we
+    //    need to slot in the new query string properly.  We disassemble and
+    //    reconstruct the URL.
+    $parts = parse_url($url);
+    
+    $url = $parts['scheme'] . '://';
+    if (isset($parts['user'])) {
+        $url .= $parts['user'];
+        if (isset($parts['pass'])) $url .= ':' . $parts['pass'];
+        $url .= '@';
+    }
+    $url .= $parts['host'];
+    if (isset($parts['port'])) $url .= ':' . $parts['port'];
+    if (isset($parts['path'])) $url .= $parts['path'];
+    
+    if (($component == OPENID_RESPONSE_QUERY) || (strpos($url, '#') === FALSE)) {
+        $url .= '?' . ((isset($parts['query'])) ? $parts['query'] . '&' : '') . $query;
+        if (isset($parts['fragment'])) $url .= '#' . $parts['fragment'];
+    } elseif ($component == OPENID_RESPONSE_FRAGMENT) {
+        // In theory $parts['fragment'] should be an empty string, but the
+        // current draft specification does not prohibit putting other things
+        // in the fragment.
+        
+        if (isset($parts['query'])) {
+            $url .= '?' . $parts['query'] . '#' . $parts['fragment'] . '&' . $query;
+        } else {
+            $url .= '#' . $parts['fragment'] . '?' . $query;
+        }
+    }
+    return $url;
+}
+
+/**
+ * Encodes a message in application/x-www-form-urlencoded format.
+ *
+ * @param array $message the OpenID message to encode
+ * @return string the encoded message
+ * @since 0.8
+ */
+function openid_urlencode_message($message) {
+    $pairs = array();
+    
+    foreach ($message as $key => $value) {
+        $pairs[] = $key . '=' . rfc3986_urlencode($value);
+    }
+        
+    return implode('&', $pairs);
+}
+
+/**
+ * Sends a direct message indicating an error.  This is a convenience function
+ * for {@link openid_direct_response()}.
+ *
+ * @param string $error the error message
+ * @param array $additional any additional data to be sent with the error
+ * message
+ * @param float $version the message version
+ */
+function openid_direct_error($error, $additional = array(), $version = OPENID_VERSION_2) {
+    $message = openid_direct_message(array_merge(array('error' => $error), $additional), $version);
+    openid_direct_response($message, '400 Bad Request');
+}
+
+/**
+ * Sends an indirect message indicating an error.  This is a convenience function
+ * for {@link openid_indirect_response()}.
+ *
+ * @param string $url the URL to which the error message is to be sent
+ * @param string $error the error message
+ * @param array $additional any additional data to be sent with the error
+ * message
+ * @param float $version the message version
+ * @param int $component the component of the URL in which the indirect message is
+ * encoded, either OPENID_RESPONSE_QUERY or OPENID_RESPONSE_FRAGMENT
+ */
+function openid_indirect_error($url, $error, $additional = array(), $version = OPENID_VERSION_2, $component = OPENID_RESPONSE_QUERY) {
+    $message = openid_indirect_message(array_merge(array('openid.mode'=> 'error', 'openid.error' => $error), $additional), $version);
+    openid_indirect_response($url, $message, $component);
+}
+
+/**
+ * Gets the realm from the OpenID request.  This is specified differently
+ * depending on the OpenID version.
+ *
+ * @param mixed $request the OpenID request
+ * @param float $version the OpenID version for the message
+ * @return string the realm URI
+ */
+function openid_get_realm($request, $version) {
+    if ($version == OPENID_VERSION_1_1) {
+        $realm = $request['openid.trust_root'];
+    }
+
+    if ($version >= OPENID_VERSION_2) {
+        $realm = $request['openid.realm'];
+    }
+    
+    if (!$realm) {
+        $realm = $request['openid.return_to'];
+    }
+    
+    return $realm;
+}
+
+/**
+ * Parses a direct message.
+ *
+ * @param string $message the direct message to parse
+ * @return array an array containing the parsed key-value pairs
+ *
+ * @since 0.7
+ */
+function openid_parse_direct_message($message) {
+    $data = array();
+
+    $items = explode("\n", $message);
+    foreach ($items as $item) {
+        list ($key, $value) = explode(':', $item, 2);
+        $data[$key] = $value;
+    }
+
+    return $data;
+}
+
+/**
+ * Parses a query string.
+ *
+ * Query strings can be used to receive OpenID indirect messages.
+ *
+ * @param string $query the query string to parse
+ * @return array an array containing the parsed key-value pairs
+ *
+ * @since 0.7
+ */
+function openid_parse_query($query) {
+    $data = array();
+    
+    if ($query === NULL) return array();
+    if ($query === '') return array();
+    
+    $pairs = explode('&', $query);
+    
+    foreach ($pairs as $pair) {
+        list ($key, $value) = explode('=', $pair, 2);
+        $data[$key] = urldecode($value);
+    }
+
+    return $data;
+}
+
+/**
+ * Parses the OpenID request to extract namespace information.
+ *
+ * This function builds a map between namespace aliases and their Type URIs.
+ *
+ * @param array $request the OpenID request
+ */
+function openid_parse_request($request) {
+    global $openid_ns_to_alias;
+    
+    foreach ($request as $key => $value) {
+        if (strpos($key, 'openid.ns.') === 0) {
+            $alias = substr($key, 10);
+            $openid_ns_to_alias[$value] = $alias;
+        }
+    }
+}
+
+/**
+ * Determines whether a URL matches a realm.
+ *
+ * A URL matches a realm if:
+ *
+ * 1. The URL scheme and port of the URL are identical to those in the realm.
+ *    See RFC 3986, section 3.1 for rules about URI matching.
+ * 2. The URL's path is equal to or a sub-directory of the realm's path.
+ * 3. Either:
+ *    (a) The realm's domain contains the wild-card characters "*.", and the
+ *        trailing part of the URL's domain is identical to the part of the
+ *        realm following the "*." wildcard, or
+ *    (b) The URL's domain is identical to the realm's domain
+ *
+ * @param string $url to URL to test
+ * @param string $realm the realm
+ * @return bool true if the URL matches the realm
+ * @since 0.6
+ */
+function openid_url_matches_realm($url, $realm) {
+    $url = parse_url($url);
+    $realm = parse_url($realm);
+    
+    foreach(array('user', 'pass', 'fragment') as $key) {
+        if (array_key_exists($key, $url) || array_key_exists($key, $realm))
+            return false;
+    }
+    
+    if ($url['scheme'] != $realm['scheme']) return false;
+    
+    if (!isset($url['port']))
+        $url['port'] = '';
+    if (!isset($realm['port']))
+        $realm['port'] = '';
+    if (($url['port'] != $realm['port']))
+        return false;
+    
+    if (substr($realm['host'], 0, 2) == '*.') {
+        $realm_re = '/^([^.]+\.)?' . preg_quote(substr($realm['host'], 2)) . '$/i';
+    } else {
+        $realm_re = '/^' . preg_quote($realm['host']) . '$/i';
+    }
+    
+    if (!preg_match($realm_re, $url['host'])) return false;
+    
+    if (!isset($url['path']))
+        $url['path'] = '';
+    if (!isset($realm['path']))
+        $realm['path'] = '';
+    if (substr($realm['path'], -1) == '/') $realm['path'] = substr($realm['path'], 0, -1);
+    if (($url['path'] != $realm['path']) && !preg_match('#^' . preg_quote($realm['path']) . '/.*$#', $url['path'])) return false;
+    
+    return true;
+}
+
+/**
+ * Returns the URL of a relying party endpoint for a specified realm.  This URL
+ * is used to discover services associated with the realm.
+ *
+ * If the realm's domain contains the wild-card characters "*.", this is substituted
+ * with "www.".
+ *
+ * @param string $realm the realm
+ * @url string the URL
+ *
+ * @since 0.7
+ */
+function openid_realm_discovery_url($realm) {
+    $parts = parse_url($realm);
+    $host = strtr($parts['host'], array('*.' => 'www.'));;
+    
+    $url = $parts['scheme'] . '://';
+    if (isset($parts['user'])) {
+        $url .= $parts['user'];
+        if (isset($parts['pass'])) $url .= ':' . $parts['pass'];
+        $url .= '@';
+    }
+    $url .= $host;
+    if (isset($parts['port'])) $url .= ':' . $parts['port'];
+    if (isset($parts['path'])) $url .= $parts['path'];
+    if (isset($parts['query'])) $url .= '?' . $parts['query'];
+    if (isset($parts['fragment'])) $url .= '#' . $parts['fragment'];
+    return $url;
+}
+
+/**
+ * Verifies a return_to URL against the actual URL of the HTTP request.
+ *
+ * The return_to URL matches if:
+ *
+ * - The URL scheme, authority, and path are the same; and
+ * - Any query parameters that are present in the return_to URL are also present
+ *   with the same values in the actual request.
+ *
+ * @param string $return_to the URL specified in the openid.return_to parameter
+ * @param string $actual_url the actual URL requested
+ * @return bool true if the URLs match
+ *
+ * @since 0.7
+ */
+function openid_verify_return_to($return_to, $actual_url) {
+    $expected = parse_url($return_to);
+    $actual = parse_url($actual_url);
+    
+    // Schemes are case insensitive
+    if (strtoupper($expected['scheme']) != strtoupper($actual['scheme'])) return false;
+    
+    // Hosts are case insensitive
+    if (strtoupper($expected['host']) != strtoupper($actual['host'])) return false;
+    
+    if (!isset($expected['port']))
+        $expected['port'] = '';
+    if (!isset($actual['port']))
+        $actual['port'] = '';
+    if ($expected['port'] != $actual['port']) return false;
+    
+    if (!isset($expected['path']))
+        $expected['path'] = '';
+    if (!isset($actual['path']))
+        $actual['path'] = '';
+    if ($expected['path'] != $actual['path']) return false;
+    
+    if ($expected['query']) {
+        $expected_query = openid_parse_query($expected['query']);
+        $actual_query = openid_parse_query($actual['query']);
+        
+        foreach ($expected_query as $key => $value) {            
+            if (!array_key_exists($key, $actual_query)) return false;
+            if ($value != $actual_query[$key]) return false;
+        }
+    }
+    
+    return true;
+}
+
+/**
+ * Filters an OpenID request to find keys specific to an extension, as specified
+ * by the Type URI.
+ *
+ * For exmaple, if the extension has the Type URI http://example.com/ and the
+ * alias example, this function will return an array of all the keys in the
+ * OpenID request which starts with openid.example
+ *
+ * @param string $ns the Type URI of the extension
+ * @param array $request the OpenID request
+ * @return array the filtered request, with the prefix (in the example above,
+ * openid.example.) stripped in the keys.
+ */
+function openid_extension_filter_request($ns, $request) {
+    global $openid_ns_to_alias;
+    
+    if (!isset($openid_ns_to_alias[$ns])) return array();
+    
+    $alias = $openid_ns_to_alias[$ns];
+    $return = array();
+    
+    if (is_array($request)) {
+        foreach ($request as $key => $value) {
+            if ($key == 'openid.' . $alias) {
+                $return['#default'] = $value;
+            }
+            if (strpos($key, 'openid.' . $alias . '.') === 0) {
+                $return[substr($key, strlen('openid.' . $alias . '.'))] = $value;
+            }
+        }
+    }
+    
+    return $return;
+}
+
+/**
+ * Determines whether an extension is present in an OpenID request.
+ *
+ * @param string $ns the Type URI of the extension
+ * @param array $request the OpenID request
+ * @return bool true if the extension is present in the request
+ */
+function openid_extension_requested($ns, $request) {
+    global $openid_ns_to_alias;
+    
+    if (!isset($openid_ns_to_alias[$ns])) return false;
+    $alias = $openid_ns_to_alias[$ns];
+    
+    if (is_array($request)) {
+        foreach ($request as $key => $value) {
+            if ((strpos($key, 'openid.' . $alias . '.') === 0) || (strpos($key, 'openid.' . $alias . '=') === 0)) {
+                return true;
+            }
+        }
+    }
+    
+    return false;
+}
+
+/**
+ * Returns the OpenID alias for an extension, given a Type URI, based on the
+ * alias definitions in the current OpenID request.
+ *
+ * @param string $ns the Type URI
+ * @param bool|string $create whether to create an alias if the Type URI does not already
+ * have an alias in the current OpenID request.  If this parameter is a string,
+ * then the string specified is the preferred alias to be created, unless a collision
+ * occurs
+ * @return string the alias, or NULL if the Type URI does not already
+ * have an alias in the current OpenID request <i>and</i> $create is false
+ */
+function openid_extension_alias($ns, $create = FALSE) {
+    global $openid_ns_to_alias;
+    static $e = 1;
+    
+    if (isset($openid_ns_to_alias[$ns])) return $openid_ns_to_alias[$ns];
+    if ($create !== FALSE) {
+        if ($create === TRUE) {
+            $alias = 'e' . $e;
+            $e++;
+        } elseif (is_string($create)) {
+            $used_aliases = array_values($openid_ns_to_alias);
+        
+            $alias = $create;
+            $i = 0;
+        
+            while (in_array($alias, $used_aliases)) {
+                $i++;
+                $alias = $create . $i;
+            }
+        }
+        $openid_ns_to_alias[$ns] = $alias;
+        return $alias;
+    }
+    return NULL;
+}
+
+
+/* ------- OpenID nonce functions -------------------------------------------- */
+/**
+ * Generates a nonce for use in OpenID responses
+ *
+ * @return string an OpenID nonce
+ * @link http://openid.net/specs/openid-authentication-2_0.html#positive_assertions
+ */
+function openid_nonce() {
+    return gmstrftime('%Y-%m-%dT%H:%M:%SZ') . bin2hex(random_bytes(4));
+}
+
+/* ------- Diffie-Hellman Key Exchange functions ----------------------------- */
+
+/**
+ * Returns the association types supported by this server.
+ *
+ * @return array an array containing the association types supported by this server as keys
+ * and an array containing the key size (mac_size) and HMAC function (hmac_func) as
+ * values
+ */
+function openid_association_types() {
+    $association_types = array('HMAC-SHA1' => array('mac_size' => 20, 'hmac_func' => '_openid_hmac_sha1'));
+    if (OPENID_SHA256_SUPPORTED) $association_types['HMAC-SHA256'] = array('mac_size' => 32, 'hmac_func' => '_openid_hmac_sha256');
+    return $association_types;
+}
+
+/**
+ * Returns the association types supported by this server and the version of
+ * OpenID.
+ *
+ * OpenID version 1 supports an empty string as the session type.  OpenID version 2
+ * reqires a session type to be sent.
+ *
+ * @param bool $is_https whether the transport layer encryption is used for the current
+ * connection
+ * @param float $version the OpenID version, either OPENID_VERSION_1_1 and OPENID_VERSION_2
+ * @return array an array containing the session types supported by this server as keys
+ * and an array containing the hash function (hash_func) as
+ * values
+ */
+function openid_session_types($is_https = FALSE, $version = OPENID_VERSION_2) {
+    $session_types = array(
+        'DH-SHA1' => array('hash_func' => '_openid_sha1'),
+    );
+    if (OPENID_SHA256_SUPPORTED) $session_types['DH-SHA256'] = array('hash_func' => '_openid_sha256');
+    if (($version >= OPENID_VERSION_2) && ($is_https == TRUE)) {
+        // Under OpenID 2.0 no-encryption is only allowed if TLS is used
+        $session_types['no-encryption'] = array();
+    }
+    if ($version == OPENID_VERSION_1_1) $session_types[''] = array();
+    return $session_types;
+}
+
+/**
+ * Generates the cryptographic values required for responding to association
+ * requests
+ *
+ * This involves generating a key pair for the OpenID provider, then calculating
+ * the shared secret.  The shared secret is then used to encrypt the MAC key.
+ *
+ * @param string $mac_key the MAC key, in binary representation
+ * @param string $dh_consumer_public the consumer's public key, in Base64 representation
+ * @param string $dh_modulus modulus - a large prime number
+ * @param string $dh_gen generator - a primitive root modulo
+ * @param string $hash_func the hash function
+ * @return array an array containing (a) dh_server_public - the server's public key (in Base64), and (b)
+ * enc_mac_key encrypted MAC key (in Base64), encrypted using the Diffie-Hellman shared secret
+ */
+function openid_dh_server_assoc($mac_key, $dh_consumer_public, $dh_modulus = NULL, $dh_gen = NULL, $hash_func = '_openid_sha1') {
+    
+    // Generate a key pair for the server
+    $key_pair = openid_dh_generate_key_pair($dh_modulus, $dh_gen);
+    
+    // Generate the shared secret
+    $ZZ = openid_dh_shared_secret($dh_consumer_public, $key_pair['private'], $dh_modulus);
+
+    return array(
+        'dh_server_public' => $key_pair['public'],
+        'enc_mac_key' => openid_encrypt_mac_key($ZZ, $mac_key, $hash_func)
+    );
+}
+
+/**
+ * Complete association by obtaining the session MAC key from the key obtained
+ * from the Diffie-Hellman key exchange
+ *
+ * @param string $enc_mac_key the encrypted session MAC key, in Base64 represnetation
+ * @param string $dh_server_public the server's public key, in Base64 representation
+ * @param string $dh_consumer_private the consumer's private key, in Base64 representation
+ * @param string $dh_modulus modulus, in Base64 representation
+ * @param string $hash_func the hash function
+ * @return string the decrypted session MAC key, in Base64 representation
+ */
+function openid_dh_consumer_assoc($enc_mac_key, $dh_server_public, $dh_consumer_private, $dh_modulus = NULL, $hash_func = '_openid_sha1') {
+    // Retrieve the shared secret
+    $ZZ = openid_dh_shared_secret($dh_server_public, $dh_consumer_private, $dh_modulus);
+    
+    // Decode the encrypted MAC key
+    $encrypted_mac_key = base64_decode($enc_mac_key);
+    
+    return openid_encrypt_mac_key($ZZ, $encrypted_mac_key, $hash_func);
+}
+
+/**
+ * Calculates the shared secret for Diffie-Hellman key exchange.
+ *
+ * This is the second step in the Diffle-Hellman key exchange process.  The other
+ * party (in OpenID 1.0 terms, the consumer) has already generated the public
+ * key ($dh_consumer_public) and sent it to this party (the server).  The Diffie-Hellman
+ * modulus ($dh_modulus) and generator ($dh_gen) have either been sent or previously agreed.
+ *
+ * @param string $their_public the other party's public key, in Base64 representation
+ * @param string $my_private this party's private key, in Base64 representation
+ * @param string $dh_modulus modulus, in Base64 representation
+ * @return resource the shared secret (as a bignum)
+ *
+ * @see openid_dh_generate_key_pair()
+ * @link http://www.ietf.org/rfc/rfc2631.txt RFC 2631
+ */
+function openid_dh_shared_secret($their_public, $my_private, $dh_modulus = NULL) {
+    // Decode the keys
+    $y = _openid_base64_to_bignum($their_public);
+    $x = _openid_base64_to_bignum($my_private);
+    
+    if ($dh_modulus != NULL) {
+        $p = _openid_base64_to_bignum($dh_modulus);
+    } else {
+        $p = bignum_new(OPENID_DH_DEFAULT_MOD);
+    }
+
+    // Generate the shared secret = their public ^ my private mod p = my public ^ their private mod p
+    $ZZ = bignum_powmod($y, $x, $p);
+
+    return $ZZ;
+}
+
+/**
+ * Generates a key pair for Diffie-Hellman key exchange.
+ *
+ * @param string $dh_modulus modulus, in Base64 representation
+ * @param string $dh_gen generator, in Base64 representation
+ * @return array an array containing: (a) private - the private key, in Base64
+ * and (b) public - the public key, in Base64
+ */
+function openid_dh_generate_key_pair($dh_modulus = NULL, $dh_gen = NULL) {
+    if ($dh_modulus != NULL) {
+        $p = _openid_base64_to_bignum($dh_modulus);
+    } else {
+        $p = bignum_new(OPENID_DH_DEFAULT_MOD);
+    }
+
+    if ($dh_gen != NULL) {
+        $g = _openid_base64_to_bignum($dh_gen);
+    } else {
+        $g = bignum_new(OPENID_DH_DEFAULT_GEN);
+    }
+
+    // Generate the private key - a random number which is less than p
+    $rand = _openid_dh_rand($p);
+    $x = bignum_add($rand, 1);
+    
+    // Calculate the public key is g ^ private mod p
+    $y = bignum_powmod($g, $x, $p);
+    
+    return array('private' => _openid_bignum_to_base64($x), 'public' => _openid_bignum_to_base64($y));
+}
+
+
+/**
+ * Encrypts/decrypts and encodes the MAC key.
+ *
+ * @param resource $ZZ the Diffie-Hellman key exchange shared secret as a bignum
+ * @param string $mac_key a byte stream containing the MAC key
+ * @param string $hash_func the hash function
+ * @return string the encrypted MAC key in Base64 representation
+ */
+function openid_encrypt_mac_key($ZZ, $mac_key, $hash_func = '_openid_sha1') {
+    // Encrypt/decrypt the MAC key using the shared secret and the hash function
+    $encrypted_mac_key = _openid_xor($ZZ, $mac_key, $hash_func);
+    
+    // Encode the encrypted/decrypted MAC key
+    $enc_mac_key = base64_encode($encrypted_mac_key);
+    
+    return $enc_mac_key;
+}
+
+/**
+ * Encrypts/decrypts using XOR.
+ *
+ * @param string $key the encryption key as a bignum.  This is usually
+ * the shared secret (ZZ) calculated from the Diffie-Hellman key exchange
+ * @param string $plain_cipher the plaintext or ciphertext
+ * @param string $hash_func the hash function
+ * @return string the ciphertext or plaintext
+ */
+function _openid_xor($key, $plain_cipher, $hash_func = '_openid_sha1') {
+    $decoded_key = bignum_val($key, 256);
+    $hashed_key = call_user_func($hash_func, $decoded_key);
+    
+    $cipher_plain = "";
+    for ($i = 0; $i < strlen($plain_cipher); $i++) {
+        $cipher_plain .= chr(ord($plain_cipher[$i]) ^ ord($hashed_key[$i]));
+    }
+  
+    return $cipher_plain;
+}
+
+/**
+ * Generates a random integer, which will be used to derive a private key
+ * for Diffie-Hellman key exchange.  The integer must be less than $stop
+ *
+ * @param resource $stop a prime number as a bignum
+ * @return resource the random integer as a bignum
+ */
+function _openid_dh_rand($stop) {
+    static $duplicate_cache = array();
+  
+    // Used as the key for the duplicate cache
+    $rbytes = bignum_val($stop, 256);
+  
+    if (array_key_exists($rbytes, $duplicate_cache)) {
+        list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
+    } else {
+        if ($rbytes[0] == "\x00") {
+            $nbytes = strlen($rbytes) - 1;
+        } else {
+            $nbytes = strlen($rbytes);
+        }
+    
+        $mxrand = bignum_pow(bignum_new(256), $nbytes);
+
+        // If we get a number less than this, then it is in the
+        // duplicated range.
+        $duplicate = bignum_mod($mxrand, $stop);
+
+        if (count($duplicate_cache) > 10) {
+            $duplicate_cache = array();
+        }
+    
+        $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
+    }
+  
+    do {
+        $bytes = "\x00" . random_bytes($nbytes);
+        $n = bignum_new($bytes, 256);
+        // Keep looping if this value is in the low duplicated range
+    } while (bignum_cmp($n, $duplicate) < 0);
+
+    return bignum_mod($n, $stop);
+}
+
+/* ------- Arbitary precision arithmetic and conversion functions ------------ */
+/**
+ * Converts an arbitary precision integer, encoded in Base64, to a bignum
+ *
+ * @param string $str arbitary precision integer, encoded in Base64
+ * @return resource the string representation
+ */
+function _openid_base64_to_bignum($str) {
+    return bignum_new(base64_decode($str), 256);
+}
+
+/**
+ * Converts a string representation of an integer to an arbitary precision
+ * integer, then converts it to Base64 encoding.
+ *
+ * @param string $str the string representation
+ * @return string the Base64 encoded arbitary precision integer
+ */
+function _openid_bignum_to_base64($str) {
+    return base64_encode(bignum_val($str, 256));
+}
+
+/**
+ * Encode an integer as big-endian signed two's complement binary string.
+ *
+ * @param string $num the binary integer
+ * @return string the signed two's complement binary string
+ * @link http://openid.net/specs/openid-authentication-2_0.html#btwoc
+ */
+function _openid_btwoc($num) {
+    return pack('H*', $num);
+}
+
+/* ------- Hash and HMAC functions ------------------------------------------- */
+/**
+ * Calculates a signature of an OpenID message
+ *
+ * @param array $data the data in the message
+ * @param array $keys a list of keys in the message to be signed (without the
+ * 'openid.' prefix)
+ * @param string $mac_key the MAC key used to sign the message, in Base64 representation
+ * @param string $hmac_func the HMAC function used in the signing process
+ * @param float $version the OpenID version
+ * @return string the signature encoded in Base64
+ */
+function openid_sign($data, $keys, $mac_key, $hmac_func = '_openid_hmac_sha1', $version = OPENID_VERSION_2) {
+    $signature = '';
+    $sign_data = array();
+
+    foreach ($keys as $key) {
+        if (array_key_exists('openid.' . $key, $data)) {
+            $sign_data[$key] = $data['openid.' . $key];
+        }
+    }
+    
+    $signature_base_string = _openid_signature_base_string($sign_data, $version);
+    $secret = base64_decode($mac_key);
+    $signature = call_user_func($hmac_func, $secret, $signature_base_string);
+
+    return base64_encode($signature);
+}
+
+/**
+ * Calculates the base string from which an OpenID signature is generated.
+ *
+ * OpenID versions 1 and 2 specify that messages are to be encoded using Key-Value
+ * Encoding when generating signatures.  However, future OpenID version may
+ * specify different ways of encoding the message, such as OAuth.
+ * 
+ * @param array $data the data to sign
+ * @param float $version the OpenID version
+ * @return string the signature base string
+ * @link http://openid.net/specs/openid-authentication-2_0.html#anchor11
+ */
+function _openid_signature_base_string($data, $version) {
+    switch ($version) {
+        case OPENID_VERSION_1_1:
+        case OPENID_VERSION_2:
+            // We set OPENID_VERSION_1_1 because we don't want to sign the namespace header
+            $signature_base_string = openid_direct_message($data, OPENID_VERSION_1_1);
+            break;
+        default:
+            // We set OPENID_VERSION_1_1 because we don't want to sign the namespace header
+            $signature_base_string = openid_direct_message($data, OPENID_VERSION_1_1);
+    }
+    return $signature_base_string;
+}
+
+/**
+ * Obtains the SHA1 hash of a string in binary representation.
+ *
+ * @param string $text the text to be hashed
+ * @return string the hash in binary representation
+ */
+function _openid_sha1($text) {
+    return sha1($text, true);
+}
+
+/**
+ * Obtains the keyed hash value using the HMAC method and the SHA1 algorithm
+ *
+ * @param string $key the key in binary representation
+ * @param string $text the text to be hashed
+ * @return string the hash in binary representation
+ */
+function _openid_hmac_sha1($key, $text) {
+    if (function_exists('hash_hmac') && function_exists('hash_algos') && (in_array('sha1', hash_algos()))) {
+        return hash_hmac('sha1', $text, $key, true);
+    } else {
+        if (!defined('OPENID_SHA1_BLOCKSIZE')) define('OPENID_SHA1_BLOCKSIZE', 64);
+        
+        if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
+            $key = _openid_sha1($key);
+        }
+    
+        $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
+        $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
+        $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
+        $hash1 = _openid_sha1(($key ^ $ipad) . $text);
+        $hmac = _openid_sha1(($key ^ $opad) . $hash1);
+        return $hmac;
+    }
+}
+
+// Check if SHA-256 support is available
+if (function_exists('hash_hmac') && function_exists('hash_algos') && (in_array('sha256', hash_algos()))) {
+    
+    /**
+     * Whether the current installation of PHP supports SHA256.  SHA256 is supported
+     * if the hash module is properly compiled and loaded into PHP.
+     */
+    define('OPENID_SHA256_SUPPORTED', true);
+    
+    /**
+     * Obtains the SHA256 hash of a string in binary representation.
+     *
+     * @param string $text the text to be hashed
+     * @return string $hash the hash in binary representation
+     */
+    function _openid_sha256($text) {
+        return hash('sha256', $text, true);
+    }
+    
+    /**
+     * Obtains the keyed hash value using the HMAC method and the SHA256 algorithm
+     *
+     * @param string $key the key in binary representation
+     * @param string $text the text to be hashed
+     * @return string the hash in binary representation
+     */
+    function _openid_hmac_sha256($key, $text) {
+        return hash_hmac('sha256', $text, $key, true);
+    }
+} else {
+    /** @ignore */
+    define('OPENID_SHA256_SUPPORTED', false);
+}
+
+if (!function_exists('rfc3986_urlencode')) {
+    /**
+     * Encodes a URL using RFC 3986.
+     *
+     * PHP's rfc3986_urlencode function encodes a URL using RFC 1738 for PHP versions
+     * prior to 5.3.  RFC 1738 has been
+     * updated by RFC 3986, which change the list of characters which needs to be
+     * encoded.
+     *
+     * Strictly correct encoding is required for various purposes, such as OAuth
+     * signature base strings.
+     *
+     * @param string $s the URL to encode
+     * @return string the encoded URL
+     */
+    function rfc3986_urlencode($s) {
+        if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+            return rawurlencode($s);
+        } else {
+            return str_replace('%7E', '~', rawurlencode($s));
+        }
+    }
+}
+?>
\ No newline at end of file