gamecenter-identity-verifier
Version:
A small library to verify the identity of apple-gamecenter's local player for consuming it in node.js backend server.
96 lines (79 loc) • 2.5 kB
JavaScript
;
var _ = require('underscore');
var crypto = require('crypto');
var request = require('request');
var url = require('url');
function verifyPublicKeyUrl(publicKeyUrl) {
var parsedUrl = url.parse(publicKeyUrl);
if (parsedUrl.protocol !== 'https:') {
return false;
}
var hostnameParts = parsedUrl.hostname.split('.');
var domainParts = _.rest(hostnameParts, hostnameParts.length - 2);
var domain = domainParts.join('.');
if (domain !== 'apple.com') {
return false;
}
return true;
}
function convertX509CertToPEM(X509Cert) {
var pemPreFix = '-----BEGIN CERTIFICATE-----\n';
var pemPostFix = '-----END CERTIFICATE-----';
var base64 = X509Cert.toString('base64');
var certBody = base64.match(new RegExp('.{0,64}', 'g')).join('\n');
return pemPreFix + certBody + pemPostFix;
}
function getAppleCertificate(publicKeyUrl, callback) {
if (!verifyPublicKeyUrl(publicKeyUrl)) {
callback(new Error('Invalid publicKeyUrl'), null);
return;
}
var options = {
uri: publicKeyUrl,
encoding: null
};
request.get(options, function (error, response, body) {
if (!error && response.statusCode === 200) {
var cert = convertX509CertToPEM(body);
callback(null, cert);
} else {
callback(error, null);
}
});
}
/* jslint bitwise:true */
function convertTimestampToBigEndian(timestamp) {
// The timestamp parameter in Big-Endian UInt-64 format
var buffer = new Buffer(8);
buffer.fill(0);
var high = ~~(timestamp / 0xffffffff); // jshint ignore:line
var low = timestamp % (0xffffffff + 0x1); // jshint ignore:line
buffer.writeUInt32BE(parseInt(high, 10), 0);
buffer.writeUInt32BE(parseInt(low, 10), 4);
return buffer;
}
/* jslint bitwise:false */
function verifySignature(publicKey, idToken) {
var verifier = crypto.createVerify('sha256');
verifier.update(idToken.playerId, 'utf8');
verifier.update(idToken.bundleId, 'utf8');
verifier.update(convertTimestampToBigEndian(idToken.timestamp));
verifier.update(idToken.salt, 'base64');
if (!verifier.verify(publicKey, idToken.signature, 'base64')) {
throw new Error('Invalid Signature');
}
}
exports.verify = function (idToken, callback) {
getAppleCertificate(idToken.publicKeyUrl, function (err, publicKey) {
if (!err) {
try {
verifySignature(publicKey, idToken);
callback(null, idToken);
} catch (e) {
callback(e, null);
}
} else {
callback(err, null);
}
});
};