KirylP KirylP - 6 months ago 36
iOS Question

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

iOS7 introduced new GKLocalPlayer method

generateIdentityVerificationSignatureWithCompletionHandler()
.

Does anyone know how to use it for good?
I assume there will be some public API at Apple server-side..

Answer

Here is how you can authenticate using objective c. If you need it in another language should be trivial to translate.

-(void)authenticate
{
    __weak GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
    localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
    {
        if(viewController)
        {
            [[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:viewController animated:YES completion:nil];
        }
        else if(localPlayer.isAuthenticated == YES)
        {
            [localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {

                if(error != nil)
                {
                    return; //some sort of error, can't authenticate right now
                }

                [self verifyPlayer:localPlayer.playerID publicKeyUrl:publicKeyUrl signature:signature salt:salt timestamp:timestamp];


            }];
        }
        else
        {
            NSLog(@"game center disabled");
        }
    };
}

-(void)verifyPlayer:(NSString *)playerID publicKeyUrl:(NSURL *)publicKeyUrl signature:(NSData *)signature salt:(NSData *)salt timestamp:(uint64_t)timestamp
{
    //get certificate
    NSData *certificateData = [NSData dataWithContentsOfURL:publicKeyUrl];

    //build payload
    NSMutableData *payload = [[NSMutableData alloc] init];
    [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];

    //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(@"could not create trust");
        return;
    }

    SecTrustResultType resultType;
    OSStatus statusTrustEval =  SecTrustEvaluate(trust, &resultType);
    if(statusTrustEval != errSecSuccess)
    {
        NSLog(@"could not evaluate trust");
        return;
    }

    if(resultType != kSecTrustResultProceed && resultType != kSecTrustResultRecoverableTrustFailure)
    {
        NSLog(@"server can not be trusted");
        return;
    }

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

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

    CFRelease(publicKey);
    CFRelease(trust);
    CFRelease(secPolicy);
    CFRelease(certificateFromFile);
    if (verficationResult == errSecSuccess)
    {
        NSLog(@"Verified");
    }
    else
    {
        NSLog(@"Danger!!!");
    }
}

EDIT:

as of March 2nd 2015, apple now uses SHA256 instead of SHA1 on the certificate. https://devforums.apple.com/thread/263789?tstart=0