decode($parts[0]),true,512,JSON_THROW_ON_ERROR); abort_unless(($header['alg']??'')==='ES256' && count($header['x5c']??[])>=2 && count($header['x5c'])<=3,422); $certs=array_map(fn($c)=>$this->pem($c),$header['x5c']);$root=file_get_contents($rootPath); foreach($certs as $index=>$cert){ $parsed=openssl_x509_parse($cert);abort_unless($parsed && $parsed['validFrom_time_t']<=time() && $parsed['validTo_time_t']>=time(),422); $issuer=$certs[$index+1]??$root; abort_unless(openssl_x509_verify($cert,openssl_pkey_get_public($issuer))===1,422,'Invalid Apple certificate chain.'); } $leaf=openssl_x509_parse($certs[0]);abort_unless(isset($leaf['extensions']['1.2.840.113635.100.6.11.1']),422,'Not an Apple transaction signing certificate.'); $sig=$this->decode($parts[2]);abort_unless(strlen($sig)===64,422); $der='';foreach([substr($sig,0,32),substr($sig,32)] as $integer){$integer=ltrim($integer,"\0");if($integer==='' || (ord($integer[0])&128)){$integer="\0".$integer;}$der.="\x02".chr(strlen($integer)).$integer;} $der="\x30".chr(strlen($der)).$der; abort_unless(openssl_verify($parts[0].'.'.$parts[1],$der,$certs[0],OPENSSL_ALGO_SHA256)===1,422,'Invalid transaction signature.'); $claims=json_decode($this->decode($parts[1]),true,512,JSON_THROW_ON_ERROR); abort_unless(config('tarnax.apple.bundle_id') && config('tarnax.apple.product_id'),503); abort_unless(($claims['bundleId']??'')===config('tarnax.apple.bundle_id') && ($claims['productId']??'')===config('tarnax.apple.product_id') && ($claims['environment']??'')===config('tarnax.apple.environment') && strtolower($claims['appAccountToken']??'')===strtolower($accountToken),422,'Transaction does not match this account or product.'); abort_if(isset($claims['revocationDate']) || (($claims['expiresDate']??PHP_INT_MAX)=time()*1000-86400000,422,'A recent signed transaction is required.'); return $claims; } }