the browser-facing portion of osu!
at master 3.4 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6declare(strict_types=1); 7 8namespace App\Libraries; 9 10use App\Exceptions\ClientCheckParseTokenException; 11use App\Models\Build; 12use Illuminate\Http\Request; 13 14class ClientCheck 15{ 16 public static function parseToken(Request $request): array 17 { 18 $token = $request->header('x-token'); 19 $assertValid = $GLOBALS['cfg']['osu']['client']['check_version']; 20 $ret = [ 21 'buildId' => $GLOBALS['cfg']['osu']['client']['default_build_id'], 22 'token' => null, 23 ]; 24 25 try { 26 if ($token === null) { 27 throw new ClientCheckParseTokenException('missing token header'); 28 } 29 30 $input = static::splitToken($token); 31 32 $build = Build::firstWhere([ 33 'hash' => $input['clientHash'], 34 'allow_ranking' => true, 35 ]); 36 37 if ($build === null) { 38 throw new ClientCheckParseTokenException('invalid client hash'); 39 } 40 41 $ret['buildId'] = $build->getKey(); 42 43 $computed = hash_hmac( 44 'sha1', 45 $input['clientData'], 46 static::getKey($build), 47 true, 48 ); 49 50 if (!hash_equals($computed, $input['expected'])) { 51 throw new ClientCheckParseTokenException('invalid verification hash'); 52 } 53 54 $now = time(); 55 if (abs($now - $input['clientTime']) > $GLOBALS['cfg']['osu']['client']['token_lifetime']) { 56 throw new ClientCheckParseTokenException('expired token'); 57 } 58 59 $ret['token'] = $token; 60 // to be included in queue 61 $ret['body'] = base64_encode($request->getContent()); 62 $ret['url'] = $request->getRequestUri(); 63 } catch (ClientCheckParseTokenException $e) { 64 abort_if($assertValid, 422, $e->getMessage()); 65 } 66 67 return $ret; 68 } 69 70 public static function queueToken(?array $tokenData, int $scoreId): void 71 { 72 if ($tokenData['token'] === null) { 73 return; 74 } 75 76 \LaravelRedis::lpush($GLOBALS['cfg']['osu']['client']['token_queue'], json_encode([ 77 'body' => $tokenData['body'], 78 'id' => $scoreId, 79 'token' => $tokenData['token'], 80 'url' => $tokenData['url'], 81 ])); 82 } 83 84 private static function getKey(Build $build): string 85 { 86 return $GLOBALS['cfg']['osu']['client']['token_keys'][$build->platform()] 87 ?? $GLOBALS['cfg']['osu']['client']['token_keys']['default'] 88 ?? ''; 89 } 90 91 private static function splitToken(string $token): array 92 { 93 $data = substr($token, -82); 94 if (strlen($data) !== 82 || !ctype_xdigit(substr($data, 0, 80))) { 95 $data = str_repeat('0', 82); 96 } 97 $clientTime = unpack('V', hex2bin(substr($data, 32, 8)))[1]; 98 99 return [ 100 'clientData' => substr($data, 0, 40), 101 'clientHash' => hex2bin(substr($data, 0, 32)), 102 'clientTime' => $clientTime, 103 'expected' => hex2bin(substr($data, 40, 40)), 104 'version' => substr($data, 80, 2), 105 ]; 106 } 107}