diff --git a/src/TokenType/MAC.php b/src/TokenType/MAC.php new file mode 100644 index 00000000..bc4f82c4 --- /dev/null +++ b/src/TokenType/MAC.php @@ -0,0 +1,141 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\TokenType; + +use League\OAuth2\Server\Util\SecureKey; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; + +/** + * MAC Token Type + */ +class MAC extends AbstractTokenType implements TokenTypeInterface +{ + /** + * {@inheritdoc} + */ + public function generateResponse() + { + $macKey = SecureKey::generate(); + $this->server->getMacStorage()->create($macKey, $this->getParam('access_token')); + + $response = [ + 'access_token' => $this->getParam('access_token'), + 'token_type' => 'mac', + 'expires_in' => $this->getParam('expires_in'), + 'mac_key' => $macKey, + 'mac_algorithm' => 'hmac-sha-256', + ]; + + return $response; + } + + /** + * {@inheritdoc} + */ + public function determineAccessTokenInHeader(Request $request) + { + if ($request->headers->has('Authorization') === false) { + return; + } + + $header = $request->headers->get('Authorization'); + + if (substr($header, 0, 4) !== 'MAC ') { + return; + } + + // Find all the parameters expressed in the header + $paramsRaw = explode(',', substr($header, 4)); + $params = new ParameterBag(); + + array_map(function ($param) use (&$params) { + $param = trim($param); + + preg_match_all('/([a-zA-Z]*)="([\w=]*)"/', $param, $matches); + + if (count($matches) !== 3) { + return; + } + + $key = reset($matches[1]); + $value = trim(reset($matches[2])); + + if (empty($value)) { + return; + } + + $params->add($key, $value); + }, $paramsRaw); + + // Validate parameters + if ($params->has('id') === false || $params->has('ts') === false || $params->has('nonce') === false || $params->has('mac') === false) { + return; + } + + if ((int) $params->get('ts') !== time()) { + return; + } + + $accessToken = $params->get('id'); + $timestamp = (int) $params->get('ts'); + $nonce = $params->get('nonce'); + $signature = $params->get('mac'); + + // Try to find the MAC key for the access token + $macKey = $this->server->getMacStorage()->getByAccessToken($accessToken); + + if ($macKey === null) { + return; + } + + // Calculate and compare the signature + $calculatedSignatureParts = [ + $timestamp, + $nonce, + strtoupper($request->getMethod()), + $request->getUri(), + $request->getHost(), + $request->getPort(), + ]; + + if ($params->has('ext')) { + $calculatedSignatureParts[] = $params->get('ext'); + } + + $calculatedSignature = base64_encode(hash_hmac('sha256', implode("\n", $calculatedSignatureParts), $macKey))); + + // Return the access token if the signature matches + return ($this->hash_equals($calculatedSignature, $signature)) ? $accessToken : null; + } + + private function hash_equals($knownString, $userString) + { + if (!function_exists('hash_equals')) { + function hash_equals($knownString, $userString) + { + if (strlen($knownString) !== strlen($userString)) { + return false; + } + $len = strlen($knownString); + $result = 0; + for ($i = 0; $i < $len; $i++) { + $result |= (ord($knownString[$i]) ^ ord($userString[$i])); + } + // They are only identical strings if $result is exactly 0... + return 0 === $result; + } + } + + return hash_equals($knownString, $userString); + } +} \ No newline at end of file