/** * Create (or reuse existing) association between OpenID consumer and * OpenID server based on Diffie-Hellman key agreement. Returns true * on success and false on failure. * * @param string $url OpenID server url * @param float $version OpenID protocol version * @param string $priv_key for testing only * @return bool */ protected function _associate($url, $version, $priv_key = null) { /* Check if we already have association in chace or storage */ if ($this->_getAssociation($url, $handle, $macFunc, $secret, $expires)) { return true; } if ($this->_dumbMode) { /* Use dumb mode */ return true; } $params = array(); if ($version >= 2.0) { $params = array('openid.ns' => OpenId\OpenId::NS_2_0, 'openid.mode' => 'associate', 'openid.assoc_type' => 'HMAC-SHA256', 'openid.session_type' => 'DH-SHA256'); } else { $params = array('openid.mode' => 'associate', 'openid.assoc_type' => 'HMAC-SHA1', 'openid.session_type' => 'DH-SHA1'); } $dh = OpenId\OpenId::createDhKey(pack('H*', OpenId\OpenId::DH_P), pack('H*', OpenId\OpenId::DH_G), $priv_key); $dh_details = OpenId\OpenId::getDhKeyDetails($dh); $params['openid.dh_modulus'] = base64_encode(OpenId\OpenId::btwoc($dh_details['p'])); $params['openid.dh_gen'] = base64_encode(OpenId\OpenId::btwoc($dh_details['g'])); $params['openid.dh_consumer_public'] = base64_encode(OpenId\OpenId::btwoc($dh_details['pub_key'])); while (1) { $ret = $this->_httpRequest($url, 'POST', $params, $status); if ($ret === false) { $this->_setError("HTTP request failed"); return false; } $r = array(); $bad_response = false; foreach (explode("\n", $ret) as $line) { $line = trim($line); if (!empty($line)) { $x = explode(':', $line, 2); if (is_array($x) && count($x) == 2) { list($key, $value) = $x; $r[trim($key)] = trim($value); } else { $bad_response = true; } } } if ($bad_response && strpos($ret, 'Unknown session type') !== false) { $r['error_code'] = 'unsupported-type'; } $ret = $r; if (isset($ret['error_code']) && $ret['error_code'] == 'unsupported-type') { if ($params['openid.session_type'] == 'DH-SHA256') { $params['openid.session_type'] = 'DH-SHA1'; $params['openid.assoc_type'] = 'HMAC-SHA1'; } else { if ($params['openid.session_type'] == 'DH-SHA1') { $params['openid.session_type'] = 'no-encryption'; } else { $this->_setError("The OpenID service responded with: " . $ret['error_code']); return false; } } } else { break; } } if ($status != 200) { $this->_setError("The server responded with status code: " . $status); return false; } if ($version >= 2.0 && isset($ret['ns']) && $ret['ns'] != OpenId\OpenId::NS_2_0) { $this->_setError("Wrong namespace definition in the server response"); return false; } if (!isset($ret['assoc_handle']) || !isset($ret['expires_in']) || !isset($ret['assoc_type']) || $params['openid.assoc_type'] != $ret['assoc_type']) { if ($params['openid.assoc_type'] != $ret['assoc_type']) { $this->_setError("The returned assoc_type differed from the supplied openid.assoc_type"); } else { $this->_setError("Missing required data from provider (assoc_handle, expires_in, assoc_type are required)"); } return false; } $handle = $ret['assoc_handle']; $expiresIn = $ret['expires_in']; if ($ret['assoc_type'] == 'HMAC-SHA1') { $macFunc = 'sha1'; } else { if ($ret['assoc_type'] == 'HMAC-SHA256' && $version >= 2.0) { $macFunc = 'sha256'; } else { $this->_setError("Unsupported assoc_type"); return false; } } if ((empty($ret['session_type']) || $version >= 2.0 && $ret['session_type'] == 'no-encryption') && isset($ret['mac_key'])) { $secret = base64_decode($ret['mac_key']); } else { if (isset($ret['session_type']) && $ret['session_type'] == 'DH-SHA1' && !empty($ret['dh_server_public']) && !empty($ret['enc_mac_key'])) { $dhFunc = 'sha1'; } else { if (isset($ret['session_type']) && $ret['session_type'] == 'DH-SHA256' && $version >= 2.0 && !empty($ret['dh_server_public']) && !empty($ret['enc_mac_key'])) { $dhFunc = 'sha256'; } else { $this->_setError("Unsupported session_type"); return false; } } } if (isset($dhFunc)) { $serverPub = base64_decode($ret['dh_server_public']); $dhSec = OpenId\OpenId::computeDhSecret($serverPub, $dh); if ($dhSec === false) { $this->_setError("DH secret comutation failed"); return false; } $sec = OpenId\OpenId::digest($dhFunc, $dhSec); if ($sec === false) { $this->_setError("Could not create digest"); return false; } $secret = $sec ^ base64_decode($ret['enc_mac_key']); } if ($macFunc == 'sha1') { if (OpenId\OpenId::strlen($secret) != 20) { $this->_setError("The length of the sha1 secret must be 20"); return false; } } else { if ($macFunc == 'sha256') { if (OpenId\OpenId::strlen($secret) != 32) { $this->_setError("The length of the sha256 secret must be 32"); return false; } } } $this->_addAssociation($url, $handle, $macFunc, $secret, time() + $expiresIn); return true; }