gpt4 book ai didi

php - 如何使用 PHP 验证我的 'third party server' 上的 GKLocalPlayer?

转载 作者:塔克拉玛干 更新时间:2023-11-02 09:42:48 24 4
gpt4 key购买 nike

请原谅我的笨拙,我是 Stackoverflow、C# 和 Objective C 的新手。

简而言之,我正在尝试做这个问题的答案,但在 PHP 中: How to authenticate the GKLocalPlayer on my 'third party server'?希望这也能帮助其他从事相同工作的 PHP 开发人员。

我正在使用 Unity (Unity3D) 和 PHP 服务器端。我已经让 Objective C 正确连接到 GameCenter 并通过调用 generateIdentityVerificationSignatureWithCompletionHandler 返回数据。不幸的是,我无法弄清楚我在验证 SHA1 哈希时做错了什么。过去一周我一直在研究这个问题,尝试了各种方法,但没有成功。

我正在尝试三种不同的方法来制作 SHA1 哈希(如下所示)。一次在 Objective C 中,另一个在 Unity 的 C# 中,最后第三次在我的服务器上用 PHP。 Objective C 和 C# SHA1 哈希值最终相同。但是,PHP 不匹配它们。并且没有人根据 Apple 的公共(public)证书和签名进行验证。

不可否认,我可能误解了一些基本的东西。至少让 Objective C 和 C# 哈希得到验证将是一个巨大的进步。

谢谢。

objective-c 代码:

[localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
NSDictionary *params = @{@"public_key_url": [publicKeyUrl absoluteString],
@"timestamp": [NSString stringWithFormat:@"%llu", timestamp],
@"signature": [signature base64EncodedStringWithOptions:0],
@"salt": [salt base64EncodedStringWithOptions:0],
@"player_id": [GKLocalPlayer localPlayer].playerID,
@"app_bundle_id": [[NSBundle mainBundle] bundleIdentifier]};
// Build hash using iOS...
NSMutableData *payload = [[NSMutableData alloc] init];
[payload appendData:[[GKLocalPlayer localPlayer].playerID dataUsingEncoding:NSASCIIStringEncoding]];
[payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSASCIIStringEncoding]];
uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes:&timestampBE length:sizeof(timestampBE)];
[payload appendData:salt];
uint8_t sha1HashDigest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([payload bytes], [payload length], sha1HashDigest);
// Convert to hex string so it can be sent to Unity's C# then to the PHP webserver...
NSString *sIOSHash = [self stringFromDigest:sha1HashDigest length:CC_SHA1_DIGEST_LENGTH];
// END - Build hash using iOS

// Build string to send to Unity's C#...
NSMutableString * data = [[NSMutableString alloc] init];
[data appendString:params[@"public_key_url"]];
[data appendString:@","];
[data appendString:params[@"timestamp"]];
[data appendString:@","];
[data appendString:params[@"signature"]];
[data appendString:@","];
[data appendString:params[@"salt"]];
[data appendString:@","];
[data appendString:params[@"player_id"]];
[data appendString:@","];
[data appendString:params[@"app_bundle_id"]];
[data appendString:@","];
[data appendString:sIOSHash];
// END - Build string to send to Unity's C#.

// Send string to Unity's C# for parsing and sending off to PHP webserver.
NSString *str = [[data copy] autorelease];
UnitySendMessage("GameCenterManager", "onAuthenticateLocalPlayer", [ISNDataConvertor NSStringToChar:str]);
}];
// Helper method to convert uint8_t into a hex string for sending to the webserver.
- (NSString *)stringFromDigest:(uint8_t *)digest length:(int)length {
NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:length * 2];
for (int i = 0; i < length; i++) {
[ms appendFormat: @"%02x", (int)digest[i]];
}
return [ms copy];
}

接下来是用于生成第二个版本的 SHA1 哈希的 C# 代码(在 Unity3D 中)。这些变量都从 iOS 代码(上面)发送到 Unity,并以字符串形式传入:player_idapp_bundle_idtimestamp。(我没有显示任何要发送到我的服务器的 Unity3D C# 代码。但我正在使用 WWWFormAddField 发送它。我也没有显示“桥"将数据从 Objective C 移动到 C# 的代码。)

var sha1 = new SHA1Managed();
var data = new List<byte>();
data.AddRange(Encoding.UTF8.GetBytes(player_id));
data.AddRange(Encoding.UTF8.GetBytes(app_bundle_id));
data.AddRange(ToBigEndian(Convert.ToUInt64(timestamp)));
data.AddRange(Convert.FromBase64String(salt));
var sig = data.ToArray();
public static string CSharpHash = ToHex(sha1.ComputeHash(sig), false);

最后一个代码块是我的服务器端 PHP,它从客户端接收数据,验证公共(public)证书,然后尝试根据它和签名验证哈希。最后一部分是我卡住的地方。

/*
Sample data as received within the PHP (all strings):
$public_cert_url eg: https://sandbox.gc.apple.com/public-key/gc-sb.cer
$timestamp eg: 00-00-01-47-12-9C-16-D4 [derived from: 1404766525140]
$signature eg: EGc8J9D7SdZ0qq2xl2XLz2[lots more...]
$salt eg: LDfyIQ==
$player_id eg: G:[#########]
$app_bundle_id eg: com.[mydomain].[myapp]

$sIOSHash eg: 00032b9416315c8298b5a6e7f5d9dec71bd5ace2 [The C# and Objective C code both generate the same hash.]
$CSharpHash eg: 00032b9416315c8298b5a6e7f5d9dec71bd5ace2
*/


// Verify the public cert.
// As far as I understand, PHP's openssl_pkey_get_public() cannot read raw
// cer data, so I download and convert to PEM. Optimize later.
$fp = fopen("temp.cer", "w"); // Open file for writing.
$header[] = "Content-Type: application/pkix-cert";
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_URL, $public_cert_url);
curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_FILE, $fp);
curl_exec($curl);
curl_close($curl);
fclose($fp);
shell_exec("openssl x509 -inform der -in temp.cer -out temp.pem"); // Convert to PEM.
$pub_cert = file_get_contents("temp.pem");
$sKey = openssl_pkey_get_public($pub_cert); // Validate PEM file here.
If( $sKey === False ) echo "pkey bad";
// This ^^ works.


// This is where I am stuck:

// Verify the data from the client against the signature from the client
// and the downloaded public key.
// First, try to verify against a hash created within PHP:
$iResult = openssl_verify(
sha1($player_id . $app_bundle_id . $timestamp . $salt),
$signature,
$pub_cert,
OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) echo "not valid PHP hash!\n";

// Second, see if it will verify by using the hash created in.
$iResult = openssl_verify($sIOSHash, $signature, $pub_cert, OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) echo "not valid sIOSHash hash!\n";

// Finally, does the C# has verify?
$iResult = openssl_verify($CSharpHash, $signature, $pub_cert, OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) echo "not valid CSharpHash hash!\n";

// None of these ^^ ever validate.

更新:2014 年 7 月 9 日
我通过不对其执行 SHA1 来验证数据。我对 Apple 文档感到困惑(https://developer.apple.com/library/prerelease/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/index.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler :)。特别是#7,它说:“为缓冲区生成一个 SHA-1 哈希值。”

我删除了所有 C# 代码(以尝试生成有效载荷),现在只使用 Objective C。

修改如下:

NSMutableData *payload = [[NSMutableData alloc] init];
[payload appendData:[[GKLocalPlayer localPlayer].playerID dataUsingEncoding:NSUTF8StringEncoding]];
[payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSUTF8StringEncoding]];
uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes:&timestampBE length:sizeof(timestampBE)];
[payload appendData:salt];
NSString *siOSData = [payload base64EncodedStringWithOptions:0];

注意 SHA1 的删除。

我放弃了用 PHP 创建负载的尝试。我尝试了许多不同的包、转换、将我的服务器升级到 64 位等。但我认为(如果我错了请纠正我)因为我从客户端传输与构成有效载荷完全相同的数据,它应该没关系。

Apple 注意事项:
请实现 OAuth 2.0。

我还想出了如何验证 Apple cer 文件而不浪费处理保存到文件的方法。如下:

// Get data from client. I urlencoded it before sending. So need to urldecode now.
// The payload is in "iosdata" and it, along with the signature, both need to be
// base64_decoded.
$sIOSData = ( isset($_REQUEST["iosdata"]) ) ? urldecode(Trim($_REQUEST["iosdata"])) : "";
$sIOSData = base64_decode($sIOSData);
$sSignature = ( isset($_REQUEST["signature"]) ) ? urldecode(Trim($_REQUEST["signature"])) : "";
$sSignature = base64_decode($sSignature);

// Here is where I download Apple's cert (DER format), save it as raw bits
// to a variable, convert it to PEM format (the ONLY format PHP's OpenSSL
// works with apparently...?) and then validate it.
// TODO: figure out if Apple live returns different results each time, and/or if
// this can be cached. Apple sandbox returns the same each time.
$header[0] = "Content-Type: application/pkix-cert";
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_URL, $sPublicKeyUrl);
curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
$der_data = curl_exec($curl);
curl_close($curl);
$sPublicKey = chunk_split(base64_encode($der_data), 64, "\n");
$sPublicKey = "-----BEGIN CERTIFICATE-----\n".$sPublicKey."-----END CERTIFICATE-----\n";
$sKey = openssl_pkey_get_public($sPublicKey);
If( $sKey === False ) Return "pkey bad";

// Here I use the package ($sIOSData) and signature to validate against Apple's
// public certificate.
$iResult = openssl_verify($sIOSData, $sSignature, $sKey, OPENSSL_ALGO_SHA1);
If( $iResult != 1 ) {
echo "BAD!\n";
echo "error: ".openssl_error_string()."\n";
}else{
echo "WORKED!\n";
}

欢迎反馈。我敢肯定有很多事情可以改进。但希望这会帮助某人节省一周的工作时间。

最佳答案

我玩得很开心。 Garraeth 的代码很有帮助,但 SO 周围散布着一些其他有用的提示,加上 php 文档,再加上一些幸运的猜测,我终于得出了这个结论:

在 iOS 端:

主要验证用户代码:

// Don't bother verifying not-authenticated players
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if (localPlayer.authenticated)
{
// __weak copy for use within code-block
__weak GKLocalPlayer *useLocalPlayer = localPlayer;
[useLocalPlayer generateIdentityVerificationSignatureWithCompletionHandler: ^(NSURL * _Nullable publicKeyUrl,
NSData * _Nullable signature,
NSData * _Nullable salt,
uint64_t timestamp,
NSError * _Nullable error) {

if (error == nil)
{
[self verifyPlayer: useLocalPlayer.playerID // our verify routine: below
publicKeyUrl: publicKeyUrl
signature: signature
salt: salt
timestamp: timestamp];
}
else
{
// GameCenter returned an error; deal with it here.
}
}];
}
else
{
// User is not authenticated; it makes no sense to try to verify them.
}

我的 verifyPlayer: 例程:

-(void)verifyPlayer: (NSString*) playerID
publicKeyUrl: (NSURL*) publicKeyUrl
signature: (NSData*) signature
salt: (NSData*) salt
timestamp: (uint64_t) timestamp
{
NSDictionary *paramsDict = @{ @"publicKeyUrl": [publicKeyUrl absoluteString],
@"timestamp" : [NSString stringWithFormat: @"%llu", timestamp],
@"signature" : [signature base64EncodedStringWithOptions: 0],
@"salt" : [salt base64EncodedStringWithOptions: 0],
@"playerID" : playerID,
@"bundleID" : [[NSBundle mainBundle] bundleIdentifier]
};

// NOTE: A lot of the code below was cribbed from another SO answer for which I have lost the URL.
// FIXME: <When found, insert other-SO-answer URL here>

// build payload
NSMutableData *payload = [NSMutableData new];
[payload appendData: [playerID dataUsingEncoding: NSASCIIStringEncoding]];
[payload appendData: [[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding: NSASCIIStringEncoding]];

uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes: &timestampBE length: sizeof(timestampBE)];
[payload appendData: salt];

// Verify with server
[self verifyPlayerOnServer: payload withSignature: signature publicKeyURL: publicKeyUrl];

#if 0 // verify locally (for testing)

//get certificate
NSData *certificateData = [NSData dataWithContentsOfURL: publicKeyUrl];

//sign
SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); // load the certificate
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();

SecTrustRef trust;
OSStatus statusTrust = SecTrustCreateWithCertificates(certificateFromFile, secPolicy, &trust);
if (statusTrust != errSecSuccess)
{
NSLog(@"%s ***** Could not create trust certificate", __PRETTY_FUNCTION__);
return;
}

SecTrustResultType resultType;
OSStatus statusTrustEval = SecTrustEvaluate(trust, &resultType);
if (statusTrustEval != errSecSuccess)
{
NSLog(@"%s ***** Could not evaluate trust", __PRETTY_FUNCTION__);
return;
}

if ((resultType != kSecTrustResultProceed)
&& (resultType != kSecTrustResultRecoverableTrustFailure) )
{
NSLog(@"%s ***** Server can not be trusted", __PRETTY_FUNCTION__);
return;
}

SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
uint8_t sha256HashDigest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([payload bytes], (CC_LONG)[payload length], sha256HashDigest);

NSLog(@"%s [DEBUG] sha256HashDigest: %@", __PRETTY_FUNCTION__, [NSData dataWithBytes: sha256HashDigest length: CC_SHA256_DIGEST_LENGTH]);

//check to see if its a match
OSStatus verficationResult = SecKeyRawVerify(publicKey, kSecPaddingPKCS1SHA256, sha256HashDigest, CC_SHA256_DIGEST_LENGTH, [signature bytes], [signature length]);

CFRelease(publicKey);
CFRelease(trust);
CFRelease(secPolicy);
CFRelease(certificateFromFile);
if (verficationResult == errSecSuccess)
{
NSLog(@"%s [DEBUG] Verified", __PRETTY_FUNCTION__);

dispatch_async(dispatch_get_main_queue(), ^{
[self updateGameCenterUI];
});
}
else
{
NSLog(@"%s ***** Danger!!!", __PRETTY_FUNCTION__);
}

#endif
}

我将代码传递到服务器的例程(摘自这个问题):

- (void) verifyPlayerOnServer: (NSData*) payload withSignature: signature publicKeyURL: (NSURL*) publicKeyUrl
{
// hint courtesy of: http://stackoverflow.com/questions/24621839/how-to-authenticate-the-gklocalplayer-on-my-third-party-server-using-php
NSDictionary *jsonDict = @{ @"data" : [payload base64EncodedStringWithOptions: 0] };

//NSLog(@"%s [DEBUG] jsonDict: %@", __PRETTY_FUNCTION__, jsonDict);

NSError *error = nil;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject: jsonDict options: 0 error: &error];

if (error != nil)
{
NSLog(@"%s ***** dataWithJson error: %@", __PRETTY_FUNCTION__, error);
}

// To validate at server end:
// http://stackoverflow.com/questions/21570700/how-to-authenticate-game-center-user-from-3rd-party-node-js-server

// NOTE: MFURLConnection is my subclass of NSURLConnection.
// .. this routine just builds an NSMutableURLRequest, then
// .. kicks it off, tracking a tag and calling back to delegate
// .. when the request is complete.
[MFURLConnection connectionWitURL: [self serverURLWithSuffix: @"gameCenter.php"]
headers: @{ @"Content-Type" : @"application/json",
@"Publickeyurl" : [publicKeyUrl absoluteString],
@"Signature" : [signature base64EncodedStringWithOptions: 0],
}
bodyData: bodyData
delegate: self
tag: worfc2_gameCenterVerifyConnection
userInfo: nil];
}

在服务器端:

这个问题和其他问题以及 php 文档和...有些抄袭

    $publicKeyURL = filter_var($headers['Publickeyurl'], FILTER_SANITIZE_URL);
$pkURL = urlencode($publicKeyURL);
if (empty($pkURL))
{
$response->addparameters(array('msg' => "no pku"));
$response->addparameters(array("DEBUG-headers" => $headers));
$response->addparameters(array('DEBUG-publicKeyURL' => $publicKeyURL));
$response->addparameters(array('DEBUG-pkURL' => $pkURL));
$response->setStatusCode(400); // bad request
}
else
{
$sslCertificate = file_get_contents($publicKeyURL);
if ($sslCertificate === false)
{
// invalid read
$response->addparameters(array('msg' => "no certificate"));
$response->setStatusCode(400); // bad request
}
else
{
// Example code from http://php.net/manual/en/function.openssl-verify.php
try
{
// According to: http://stackoverflow.com/questions/10944071/parsing-x509-certificate
$pemData = der2pem($sslCertificate);

// fetch public key from certificate and ready it
$pubkeyid = openssl_pkey_get_public($pemData);
if ($pubkeyid === false)
{
$response->addparameters(array('msg' => "public key error"));
$response->setStatusCode(400); // bad request
}
else
{
// According to: http://stackoverflow.com/questions/24621839/how-to-authenticate-the-gklocalplayer-on-my-third-party-server-using-php
// .. we use differently-formatted parameters
$sIOSData = $body['data'];
$sIOSData = base64_decode($sIOSData);
$sSignature = $headers['Signature'];
$sSignature = base64_decode($sSignature);

//$iResult = openssl_verify($sIOSData, $sSignature, $sKey, OPENSSL_ALGO_SHA1);

$dataToUse = $sIOSData;
$signatureToUse = $sSignature;

// state whether signature is okay or not
$ok = openssl_verify($dataToUse, $signatureToUse, $pubkeyid, OPENSSL_ALGO_SHA256);
if ($ok == 1)
{
//* echo "good";
$response->addparameters(array('msg' => "user validated"));
}
elseif ($ok == 0)
{
//* echo "bad";
$response->addparameters(array('msg' => "INVALID USER SIGNATURE"));
$response->addparameters(array("DEBUG-$dataToUse" => $dataToUse));
$response->addparameters(array("DEBUG-$signatureToUse" => $signatureToUse));
$response->addparameters(array("DEBUG-body" => $body));
$response->setStatusCode(401); // unauthorized
}
else
{
//* echo "ugly, error checking signature";
$response->addparameters(array('msg' => "***** ERROR checking signature"));
$response->setStatusCode(500); // server error
}

// free the key from memory
openssl_free_key($pubkeyid);
}
}
catch (Exception $ex)
{
$response->addparameters(array('msg' => "verification error"));
$response->addparameters(array("DEBUG-headers" => $headers));
$response->addparameters(array('DEBUG-Exception' => $ex));
$response->setStatusCode(400); // bad request
}
}

// NODE.js code at http://stackoverflow.com/questions/21570700/how-to-authenticate-game-center-user-from-3rd-party-node-js-server
}

不要忘记方便的实用程序:

function der2pem($der_data)
{
$pem = chunk_split(base64_encode($der_data), 64, "\n");
$pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";
return $pem;
}

利用所有这些,我终于能够从我的服务器上获得“用户验证”。耶! :)

注意:这种方法似乎很容易被黑客攻击,因为任何人都可以用自己的证书签署任何他们想要的东西,然后将数据、签名和 URL 传递给服务器,然后返回一个“that's一个有效的 GameCenter 登录”这样回答,虽然这段代码在实现 GC 算法的意义上“有效”,但算法本身似乎有缺陷。理想情况下,我们还会检查证书是否来自可信来源。额外的偏执检查它是否是 Apple 的 Game Center 证书也很好。

关于php - 如何使用 PHP 验证我的 'third party server' 上的 GKLocalPlayer?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24621839/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com