/** * 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::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::createDhKey(pack('H*', OpenId::DH_P), pack('H*', OpenId::DH_G), $priv_key); $dh_details = OpenId::getDhKeyDetails($dh); $params['openid.dh_modulus'] = base64_encode(OpenId::btwoc($dh_details['p'])); $params['openid.dh_gen'] = base64_encode(OpenId::btwoc($dh_details['g'])); $params['openid.dh_consumer_public'] = base64_encode(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'; } elseif ($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::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'; } elseif ($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']); } elseif (isset($ret['session_type']) && $ret['session_type'] == 'DH-SHA1' && !empty($ret['dh_server_public']) && !empty($ret['enc_mac_key'])) { $dhFunc = 'sha1'; } elseif (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::computeDhSecret($serverPub, $dh); if ($dhSec === false) { $this->_setError("DH secret comutation failed"); return false; } $sec = 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 (strlen($secret) != 20) { $this->_setError("The length of the sha1 secret must be 20"); return false; } } elseif ($macFunc == 'sha256') { if (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; }
/** * Processes association request from OpenID consumerm generates secret * shared key and send it back using Diffie-Hellman encruption. * Returns array of variables to push back to consumer. * * @param float $version OpenID version * @param array $params GET or POST request variables * @return array */ protected function _associate($version, $params) { $ret = array(); if ($version >= 2.0) { $ret['ns'] = OpenId::NS_2_0; } if (isset($params['openid_assoc_type']) && $params['openid_assoc_type'] == 'HMAC-SHA1') { $macFunc = 'sha1'; } elseif (isset($params['openid_assoc_type']) && $params['openid_assoc_type'] == 'HMAC-SHA256' && $version >= 2.0) { $macFunc = 'sha256'; } else { $ret['error'] = 'Wrong "openid.assoc_type"'; $ret['error-code'] = 'unsupported-type'; return $ret; } $ret['assoc_type'] = $params['openid_assoc_type']; $secret = $this->_genSecret($macFunc); if (empty($params['openid_session_type']) || $params['openid_session_type'] == 'no-encryption') { $ret['mac_key'] = base64_encode($secret); } elseif (isset($params['openid_session_type']) && $params['openid_session_type'] == 'DH-SHA1') { $dhFunc = 'sha1'; } elseif (isset($params['openid_session_type']) && $params['openid_session_type'] == 'DH-SHA256' && $version >= 2.0) { $dhFunc = 'sha256'; } else { $ret['error'] = 'Wrong "openid.session_type"'; $ret['error-code'] = 'unsupported-type'; return $ret; } if (isset($params['openid_session_type'])) { $ret['session_type'] = $params['openid_session_type']; } if (isset($dhFunc)) { if (empty($params['openid_dh_consumer_public'])) { $ret['error'] = 'Wrong "openid.dh_consumer_public"'; return $ret; } if (empty($params['openid_dh_gen'])) { $g = pack('H*', OpenId::DH_G); } else { $g = base64_decode($params['openid_dh_gen']); } if (empty($params['openid_dh_modulus'])) { $p = pack('H*', OpenId::DH_P); } else { $p = base64_decode($params['openid_dh_modulus']); } $dh = OpenId::createDhKey($p, $g); $dh_details = OpenId::getDhKeyDetails($dh); $sec = OpenId::computeDhSecret(base64_decode($params['openid_dh_consumer_public']), $dh); if ($sec === false) { $ret['error'] = 'Wrong "openid.session_type"'; $ret['error-code'] = 'unsupported-type'; return $ret; } $sec = OpenId::digest($dhFunc, $sec); $ret['dh_server_public'] = base64_encode(OpenId::btwoc($dh_details['pub_key'])); $ret['enc_mac_key'] = base64_encode($secret ^ $sec); } $handle = uniqid(); $expiresIn = $this->_sessionTtl; $ret['assoc_handle'] = $handle; $ret['expires_in'] = $expiresIn; $this->_storage->addAssociation($handle, $macFunc, $secret, time() + $expiresIn); return $ret; }
/** * testing testDigest * */ public function testDigest() { $this->assertSame('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d', bin2hex(OpenId::digest('sha1', 'hello'))); $this->assertSame('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', bin2hex(OpenId::digest('sha256', 'hello'))); }