PHP OpenID consumer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

keymanager.inc.php 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?PHP
  2. /* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
  3. * Copyright (c) 2008 Chris Smith
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. * SOFTWARE.
  22. */
  23. require_once(dirname(__FILE__) . '/bigmath.inc.php');
  24. require_once(dirname(__FILE__) . '/poster.inc.php');
  25. require_once(dirname(__FILE__) . '/urlbuilder.inc.php');
  26. class KeyManager {
  27. /** Diffie-Hellman P value, defined by OpenID specification. */
  28. const DH_P = '155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443';
  29. /** Diffie-Hellman G value. */
  30. const DH_G = '2';
  31. private static $header = null;
  32. private static $data = null;
  33. private static $bigmath = null;
  34. /**
  35. * Loads the KeyManager's data array from disk.
  36. */
  37. private static function loadData() {
  38. if (self::$data == null) {
  39. $data = file(dirname(__FILE__) . '/keycache.php');
  40. self::$header = array_shift($data);
  41. self::$data = unserialize(implode("\n", $data));
  42. }
  43. }
  44. /**
  45. * Saves the KeyManager's data array to disk.
  46. */
  47. private static function saveData() {
  48. file_put_contents(dirname(__FILE__) . '/keycache.php', self::$header . serialize(self::$data));
  49. }
  50. /**
  51. * Attempts to associate with the specified server.
  52. *
  53. * @param String $server The server to associate with
  54. */
  55. public static function associate($server) {
  56. $data = URLBuilder::buildAssociate($server);
  57. try {
  58. $res = Poster::post($server, $data);
  59. } catch (Exception $ex) {
  60. return;
  61. }
  62. $data = array();
  63. foreach (explode("\n", $res) as $line) {
  64. if (preg_match('/^(.*?):(.*)$/', $line, $m)) {
  65. $data[$m[1]] = $m[2];
  66. }
  67. }
  68. try {
  69. $data = self::decodeKey($server, $data);
  70. } catch (Exception $ex) {
  71. return;
  72. }
  73. $data['expires_at'] = time() + $data['expires_in'];
  74. self::$data[$server][$data['assoc_handle']] = $data;
  75. self::saveData();
  76. }
  77. /**
  78. * Decodes the MAC key specified in the $data array.
  79. *
  80. * @param String $server The server which sent the data
  81. * @param Array $data Array of association data from the server
  82. * @return A copy of the $data array with the mac_key present
  83. */
  84. private static function decodeKey($server, $data) {
  85. switch (strtolower($data['session_type'])) {
  86. case 'dh-sha1':
  87. $algo = 'sha1';
  88. break;
  89. case 'dh-sha256':
  90. $algo = 'sha256';
  91. break;
  92. case 'no-encryption':
  93. case 'blank':
  94. case '':
  95. $algo = false;
  96. break;
  97. default:
  98. throw new Exception('Unable to handle session type ' . $data['session_type']);
  99. }
  100. if ($algo !== false) {
  101. // The key is DH'd
  102. $mac = base64_decode($data['enc_mac_key']);
  103. $x = self::getDhPrivateKey($server);
  104. $temp = self::$bigmath->btwoc_undo(base64_decode($data['dh_server_public']));
  105. $temp = self::$bigmath->powmod($temp, $x, self::DH_P);
  106. $temp = self::$bigmath->btwoc($temp);
  107. $temp = hash($algo, $temp, true);
  108. $mac = $mac ^ $temp;
  109. $data['mac_key'] = base64_encode($mac);
  110. unset($data['enc_mac_key'], $data['dh_server_public']);
  111. }
  112. return $data;
  113. }
  114. /**
  115. * Retrieves an active assoc_handle for the specified server.
  116. *
  117. * @param String $server The server whose handle we're looking for
  118. * @return An association handle for the server or null on failure
  119. */
  120. public static function getHandle($server) {
  121. self::loadData();
  122. if (!isset(self::$data[$server])) {
  123. return null;
  124. }
  125. foreach (self::$data[$server] as $handle => $data) {
  126. if ($handle == '__private') { continue; }
  127. if ($data['expires_at'] < time()) {
  128. unset(self::$data[$server][$handle]);
  129. } else {
  130. return $handle;
  131. }
  132. }
  133. return null;
  134. }
  135. /**
  136. * Determines if the KeyManager has at least one assoc_handle for the
  137. * specified server.
  138. *
  139. * @param String $server The server to check for
  140. * @return True if the KeyManager has a handle, false otherwise
  141. */
  142. public static function hasHandle($server) {
  143. return self::getHandle($server) !== null;
  144. }
  145. /**
  146. * Retrieves the association data array for the specified server and assoc
  147. * handle.
  148. *
  149. * @param String $server The server whose data is being requested
  150. * @param String $handle The current association handle for the server
  151. * @return Array of association data or null if none was found
  152. */
  153. public static function getData($server, $handle) {
  154. self::loadData();
  155. if (isset(self::$data[$server][$handle])) {
  156. if (self::$data[$server][$handle]['expires_at'] < time()) {
  157. self::revokeHandle($server, $handle);
  158. return null;
  159. } else {
  160. return self::$data[$server][$handle];
  161. }
  162. } else {
  163. return null;
  164. }
  165. }
  166. /**
  167. * Attempts to authenticate that the specified arguments are a valid query
  168. * from the specified server. If smart authentication is not available
  169. * or an error is encountered, throws an exception.
  170. *
  171. * @param String $server The server that supposedly sent the request
  172. * @param Array $args The arguments included in the request
  173. * @return True if the message was authenticated, false if it's a fake
  174. */
  175. public static function authenticate($server, $args) {
  176. $data = self::getData($server, $args['openid_assoc_handle']);
  177. if ($data === null) {
  178. throw new Exception('No key available for that server/handle');
  179. }
  180. $contents = '';
  181. foreach (explode(',', $args['openid_signed']) as $arg) {
  182. $argn = str_replace('.', '_', $arg);
  183. $contents .= $arg . ':' . $args['openid_' . $argn] . "\n";
  184. }
  185. switch (strtolower($data['assoc_type'])) {
  186. case 'hmac-sha1':
  187. $algo = 'sha1';
  188. break;
  189. case 'hmac-sha256':
  190. $algo = 'sha256';
  191. break;
  192. default:
  193. throw new Exception('Unable to handle association type ' . $data['assoc_type']);
  194. }
  195. $sig = base64_encode(hash_hmac($algo, $contents, base64_decode($data['mac_key']), true));
  196. if ($sig == $args['openid_sig']) {
  197. return true;
  198. } else {
  199. return false;
  200. }
  201. }
  202. /**
  203. * Validates the current request using dumb authentication (a POST to the
  204. * provider).
  205. *
  206. * @return True if the request has been authenticated, false otherwise.
  207. */
  208. public static function dumbAuthenticate() {
  209. $url = URLBuilder::buildAuth($_REQUEST);
  210. try {
  211. $data = Poster::post($_SESSION['openid']['server'], $url);
  212. } catch (Exception $ex) {
  213. return false;
  214. }
  215. $valid = false;
  216. foreach (explode("\n", $data) as $line) {
  217. if (substr($line, 0, 9) == 'is_valid:') {
  218. $valid = (boolean) substr($line, 9);
  219. }
  220. }
  221. return $valid;
  222. }
  223. /**
  224. * Removes the specified association handle from the specified server's
  225. * records.
  226. *
  227. * @param String $server The server which is revoking the handle
  228. * @param String $handle The handle which is being revoked
  229. */
  230. public static function revokeHandle($server, $handle) {
  231. self::loadData();
  232. unset(self::$data[$server][$handle]);
  233. self::saveData();
  234. }
  235. /**
  236. * Determines if the keymanager is supported by the local environment or not.
  237. *
  238. * @return True if the keymanager can be used, false otherwise
  239. */
  240. public static function isSupported() {
  241. return @is_writable(dirname(__FILE__) . '/keycache.php')
  242. && function_exists('hash_hmac');
  243. }
  244. /**
  245. * Returns the base64-encoded representation of the dh_modulus parameter.
  246. *
  247. * @return Base64-encoded representation of dh_modulus
  248. */
  249. public static function getDhModulus() {
  250. return base64_encode(self::$bigmath->btwoc(self::DH_P));
  251. }
  252. /**
  253. * Returns the base64-encoded representation of the dh_gen parameter.
  254. *
  255. * @return Base64-encoded representation of dh_gen
  256. */
  257. public static function getDhGen() {
  258. return base64_encode(self::$bigmath->btwoc(self::DH_G));
  259. }
  260. /**
  261. * Retrieves our private key for the specified server.
  262. *
  263. * @param String $server The server which we're communicating with
  264. * @return Our private key for the specified server, or null if we don't have
  265. * one
  266. */
  267. public static function getDhPrivateKey($server) {
  268. self::loadData();
  269. if (isset(self::$data[$server])) {
  270. return self::$data[$server]['__private'];
  271. } else {
  272. return null;
  273. }
  274. }
  275. /**
  276. * Retrieves our public key for use with the specified server.
  277. *
  278. * @param String $server The server we wish to send the public key to
  279. * @return Base64-encoded public key for the specified server
  280. */
  281. public static function getDhPublicKey($server) {
  282. self::loadData();
  283. $key = self::createDhKey($server);
  284. self::saveData();
  285. return base64_encode(self::$bigmath->btwoc(self::$bigmath->powmod(self::DH_G, $key, self::DH_P)));
  286. }
  287. /**
  288. * Creates a private key for use when exchanging keys with the specified
  289. * server.
  290. *
  291. * @param String $server The name of the server we're dealing with
  292. * @return The server's new private key
  293. */
  294. private static function createDhKey($server) {
  295. return self::$data[$server]['__private'] = self::$bigmath->rand(self::DH_P);
  296. }
  297. /**
  298. * Determines whether the keymanager can support Diffie-Hellman key exchange.
  299. *
  300. * @return True if D-H exchange is supported, false otherwise.
  301. */
  302. public static function supportsDH() {
  303. return self::$bigmath != null;
  304. }
  305. /**
  306. * Initialises the key manager.
  307. */
  308. public static function init() {
  309. self::$bigmath = BigMath::getBigMath();
  310. }
  311. }
  312. KeyManager::init();
  313. ?>