How to authenticate the GKLocalPlayer on my 'third party server' using PHP?

前端 未结 2 1291
死守一世寂寞
死守一世寂寞 2020-12-10 21:46

Please forgive my clumsiness, I\'m new to Stackoverflow, C#, and Objective C.

In a nutshell, I\'m trying to do what is answered in this question, but in PHP: How to

相关标签:
2条回答
  • 2020-12-10 22:12

    I had a heck of a time with this. Garraeth's code was helpful, but there were a few other helpful hints scattered around SO, plus the php docs, plus some lucky guessing, and I finally arrived at this:

    On the iOS side:

    Main verify-user code:

    // 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.
    }
    

    My verifyPlayer: routine:

    -(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
    }
    

    My routine that passes code to the server (Cribbed from this question):

    - (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];
    }
    

    On the server side:

    Somewhat cribbed from this question, and others, and php docs and...

        $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
        }
    

    Don't forget the handy utility routine:

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

    Using all of this, I was finally able to get "user validated" back from my server. Yay! :)

    NOTE: This method seems very open to hacking, as anyone could sign whatever they want with their own certificate then pass the server the data, signature and URL to their certificate and get back a "that's a valid GameCenter login" answer so, while this code "works" in the sense that it implements the GC algorithm, the algorithm itself seems flawed. Ideally, we would also check that the certificate came from a trusted source. Extra-paranoia to check that it is Apple's Game Center certificate would be good, too.

    0 讨论(0)
  • 2020-12-10 22:22

    Thank you @garraeth, your code helped me implement the logic.

    From the C# code, concat a payload data on server side is working fine for me. When using openssl_verify we needn't do the hash ourselves.

    Also, I think validate the publicKeyUrl is form HTTPS and apple.com is required.

    Some pseudo code here (Note that Apple has change the algorithm to OPENSSL_ALGO_SHA256 in 2015).

    // do some urls, input params validate...
    
    // do the signature validate
    $payload = concatPayload($playerId, $bundleId, $timestamp, $salt);
    $pubkeyId = openssl_pkey_get_public($pem);
    $isValid = openssl_verify($payload, base64_decode($signature), 
    $pubkeyId, OPENSSL_ALGO_SHA256);
    
    function concatPayload($playerId, $bundleId, $timestamp, $salt) {
        $bytes = array_merge(
                unpack('C*', $playerId),
                unpack('C*', $bundleId),
                int64ToBigEndianArray($timestamp),
                base64ToByteArray($salt)
        );
    
        $payload = '';
        foreach ($bytes as $byte) {
            $payload .= chr($byte);
        }
        return $payload;
    }
    
    function int64ToBigEndianArray() {
        //... follow the C# code
    }
    
    function base64ToByteArray() {
        //...
    }
    
    0 讨论(0)
提交回复
热议问题