-
-
Save andyzinsser/8044165 to your computer and use it in GitHub Desktop.
// request Game Center verification data for localPlayer from apple | |
- (void)authenticateGameCenterPlayer:(GKLocalPlayer *)localPlayer | |
{ | |
[localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) { | |
if (error) { | |
NSLog(@"ERROR: %@", error); | |
} | |
else { | |
// package data to be sent to server for verification | |
NSDictionary *params = @{@"publicKeyUrl": publicKeyUrl, | |
@"timestamp": [NSString stringWithFormat:@"%llu", timestamp], | |
@"signature": [signature base64EncodedStringWithOptions:0], | |
@"salt": [salt base64EncodedStringWithOptions:0], | |
@"playerID": localPlayer.playerID, | |
@"bundleID": [[NSBundle mainBundle] bundleIdentifier]}; | |
// setup AFNetworking request to send the data to the server | |
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; | |
[manager POST:APILinkWithGameCenterURL # update to your own service URL | |
parameters:params | |
constructingBodyWithBlock:nil | |
success:^(AFHTTPRequestOperation *operation, id responseObject) { | |
NSLog(@"%@", responseObject); | |
} | |
failure:^(AFHTTPRequestOperation *operation, NSError *error) { | |
NSLog(@"%@", error); | |
}]; | |
} | |
}]; | |
} |
# NOTE: using django rest framework | |
class authenticateWithGameCenter(APIView): | |
def post(self, request): | |
response = {'errors': []} | |
# validation | |
publicKeyUrl = request.DATA.get('publicKeyUrl', None) | |
if publicKeyUrl is None: | |
response['errors'].append('publicKeyUrl is required.') | |
signature = request.DATA.get('signature', None) | |
if signature is None: | |
response['errors'].append('signature is required.') | |
salt = request.DATA.get('salt', None) | |
if salt is None: | |
response['errors'].append('salt is required.') | |
timestamp = request.DATA.get('timestamp', None) | |
if timestamp is None: | |
response['errors'].append('timestamp is required.') | |
playerID = request.DATA.get('playerID', None) | |
if playerID is None: | |
response['errors'].append('playerID is required.') | |
bundleID = request.DATA.get('bundleID', None) | |
if bundleID is None: | |
response['errors'].append('bundleID is required.') | |
if len(response['errors']) > 0: | |
return APIResponse(response) | |
decoded_sig = signature.decode('base64') | |
decoded_salt = salt.decode('base64') | |
# Download and read the .cer from apple and extract the public key | |
r = requests.get(publicKeyUrl, stream=True) | |
local_filename = publicKeyUrl.split('/')[-1] | |
with open(local_filename, 'wb') as f: | |
for chunk in r.iter_content(chunk_size=1024): | |
if chunk: | |
f.write(chunk) | |
f.flush() | |
der = open(local_filename, 'r').read() | |
x509 = crypto.load_certificate(crypto.FILETYPE_ASN1, der) | |
payload = playerID.encode('UTF-8') + bundleID.encode('UTF-8') + struct.pack('>Q', int(timestamp)) + decoded_salt | |
try: | |
verified = crypto.verify(x509, decoded_sig, payload, 'sha1') | |
logger.info('Successfully verified certificate with signaure') | |
except Exception as err: | |
logger.info(err) | |
response['errors'].append(err) | |
# TODO: | |
# Verify that a hash value of the payload matches the signature parameter provided by apple. | |
# If the generated and retrieved signatures match, the local player has been authenticated. | |
# Get or create a user for the given playerID | |
# authenticate a session for this player | |
# return the user | |
return APIResponse(response) |
The only hints can be found in HTTP response headers. But it is still unclear without clear statement in documentation, whether the headers can be trusted.
Google at least states in the docs regarding id_token validation:
Since Google changes its public keys only infrequently (on the order of once per day), you can cache them and, in the vast majority of cases, perform local validation much more efficiently.
And Java Google API contains a Manager that downloads and caches certificates based on HTTP headers values.
Also note that even though cache headers or certificate "Valid till" or "Expires" attributes may hint you to cache the certificate, nothing prevents Apple (or Google) from changing the certificate at will - and new user data will get signed by a new certificate - not the one cached.
@Maximusya isn't that the point of having a variable publicKeyUrl..? Current url is https://static.gc.apple.com/public-key/gc-prod-2.cer and if that needs to be revoked or renewed the new certificate would presumably be https://static.gc.apple.com/public-key/gc-prod-3.cer and so on..
Is publicKeyUrl stable?
I don't want to download the cer file every time a player log in.