Skip to content

Commit 07786c1

Browse files
committed
fix adapter
1 parent b00b041 commit 07786c1

File tree

2 files changed

+208
-32
lines changed

2 files changed

+208
-32
lines changed

spec/AuthenticationAdapters.spec.js

Lines changed: 136 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,8 +1644,41 @@ describe('apple signin auth adapter', () => {
16441644

16451645
describe('Apple Game Center Auth adapter', () => {
16461646
const gcenter = require('../lib/Adapters/Auth/gcenter');
1647-
1647+
const fs = require('fs');
1648+
const testCert = fs.readFileSync(__dirname + '/support/cert/game_center.pem');
1649+
it('can load adapter', async () => {
1650+
const options = {
1651+
gcenter: {
1652+
rootCertificateUrl:
1653+
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
1654+
},
1655+
};
1656+
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
1657+
'gcenter',
1658+
options
1659+
);
1660+
await adapter.validateAppId(
1661+
appIds,
1662+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1663+
providerOptions
1664+
);
1665+
});
16481666
it('validateAuthData should validate', async () => {
1667+
const options = {
1668+
gcenter: {
1669+
rootCertificateUrl:
1670+
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
1671+
},
1672+
};
1673+
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
1674+
'gcenter',
1675+
options
1676+
);
1677+
await adapter.validateAppId(
1678+
appIds,
1679+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1680+
providerOptions
1681+
);
16491682
// real token is used
16501683
const authData = {
16511684
id: 'G:1965586982',
@@ -1656,29 +1689,49 @@ describe('Apple Game Center Auth adapter', () => {
16561689
salt: 'DzqqrQ==',
16571690
bundleId: 'cloud.xtralife.gamecenterauth',
16581691
};
1659-
1692+
gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-4.cer'] = testCert;
16601693
await gcenter.validateAuthData(authData);
16611694
});
16621695

16631696
it('validateAuthData invalid signature id', async () => {
1697+
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
1698+
'gcenter',
1699+
{}
1700+
);
1701+
await adapter.validateAppId(
1702+
appIds,
1703+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1704+
providerOptions
1705+
);
16641706
const authData = {
16651707
id: 'G:1965586982',
1666-
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
1708+
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-6.cer',
16671709
timestamp: 1565257031287,
16681710
signature: '1234',
16691711
salt: 'DzqqrQ==',
1670-
bundleId: 'cloud.xtralife.gamecenterauth',
1712+
bundleId: 'com.example.com',
16711713
};
1672-
1673-
try {
1674-
await gcenter.validateAuthData(authData);
1675-
fail();
1676-
} catch (e) {
1677-
expect(e.message).toBe('Apple Game Center - invalid signature');
1678-
}
1714+
await expectAsync(gcenter.validateAuthData(authData)).toBeRejectedWith(
1715+
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Apple Game Center - invalid signature')
1716+
);
16791717
});
16801718

16811719
it('validateAuthData invalid public key http url', async () => {
1720+
const options = {
1721+
gcenter: {
1722+
rootCertificateUrl:
1723+
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
1724+
},
1725+
};
1726+
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
1727+
'gcenter',
1728+
options
1729+
);
1730+
await adapter.validateAppId(
1731+
appIds,
1732+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1733+
providerOptions
1734+
);
16821735
const publicKeyUrls = [
16831736
'example.com',
16841737
'http://static.gc.apple.com/public-key/gc-prod-4.cer',
@@ -1706,6 +1759,78 @@ describe('Apple Game Center Auth adapter', () => {
17061759
)
17071760
);
17081761
});
1762+
1763+
it('should not validate Symantec Cert', async () => {
1764+
const options = {
1765+
gcenter: {
1766+
rootCertificateUrl:
1767+
'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
1768+
},
1769+
};
1770+
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
1771+
'gcenter',
1772+
options
1773+
);
1774+
await adapter.validateAppId(
1775+
appIds,
1776+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1777+
providerOptions
1778+
);
1779+
expect(() =>
1780+
gcenter.verifyPublicKeyIssuer(
1781+
testCert,
1782+
'https://static.gc.apple.com/public-key/gc-prod-4.cer'
1783+
)
1784+
);
1785+
});
1786+
1787+
it('adapter should load default cert', async () => {
1788+
const options = {
1789+
gcenter: {},
1790+
};
1791+
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
1792+
'gcenter',
1793+
options
1794+
);
1795+
await adapter.validateAppId(
1796+
appIds,
1797+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1798+
providerOptions
1799+
);
1800+
const previous = new Date();
1801+
await adapter.validateAppId(
1802+
appIds,
1803+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1804+
providerOptions
1805+
);
1806+
1807+
const duration = new Date().getTime() - previous.getTime();
1808+
expect(duration).toEqual(0);
1809+
});
1810+
1811+
it('adapter should throw', async () => {
1812+
const options = {
1813+
gcenter: {
1814+
rootCertificateUrl: 'https://example.com',
1815+
},
1816+
};
1817+
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter(
1818+
'gcenter',
1819+
options
1820+
);
1821+
await expectAsync(
1822+
adapter.validateAppId(
1823+
appIds,
1824+
{ publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' },
1825+
providerOptions
1826+
)
1827+
).toBeRejectedWith(
1828+
new Parse.Error(
1829+
Parse.Error.OBJECT_NOT_FOUND,
1830+
'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'
1831+
)
1832+
);
1833+
});
17091834
});
17101835

17111836
describe('phant auth adapter', () => {

src/Adapters/Auth/gcenter.js

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const authData = {
1414
const { Parse } = require('parse/node');
1515
const crypto = require('crypto');
1616
const https = require('https');
17-
17+
const { pki } = require('node-forge');
18+
const ca = { cert: null, url: null };
1819
const cache = {}; // (publicKey -> cert) cache
1920

2021
function verifyPublicKeyUrl(publicKeyUrl) {
@@ -52,39 +53,52 @@ async function getAppleCertificate(publicKeyUrl) {
5253
path: url.pathname,
5354
method: 'HEAD',
5455
};
55-
const headers = await new Promise((resolve, reject) =>
56+
const cert_headers = await new Promise((resolve, reject) =>
5657
https.get(headOptions, res => resolve(res.headers)).on('error', reject)
5758
);
5859
if (
59-
headers['content-type'] !== 'application/pkix-cert' ||
60-
headers['content-length'] == null ||
61-
headers['content-length'] > 10000
60+
cert_headers['content-type'] !== 'application/pkix-cert' ||
61+
cert_headers['content-length'] == null ||
62+
cert_headers['content-length'] > 10000
6263
) {
6364
throw new Parse.Error(
6465
Parse.Error.OBJECT_NOT_FOUND,
6566
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
6667
);
6768
}
69+
const {certificate, headers} = await getCertificate(publicKeyUrl);
70+
if (headers['cache-control']) {
71+
const expire = headers['cache-control'].match(/max-age=([0-9]+)/);
72+
if (expire) {
73+
cache[publicKeyUrl] = certificate;
74+
// we'll expire the cache entry later, as per max-age
75+
setTimeout(() => {
76+
delete cache[publicKeyUrl];
77+
}, parseInt(expire[1], 10) * 1000);
78+
}
79+
}
80+
return verifyPublicKeyIssuer(certificate, publicKeyUrl);
81+
}
82+
83+
function getCertificate(url, buffer) {
6884
return new Promise((resolve, reject) => {
6985
https
70-
.get(publicKeyUrl, res => {
71-
let data = '';
86+
.get(url, res => {
87+
const data = [];
7288
res.on('data', chunk => {
73-
data += chunk.toString('base64');
89+
data.push(chunk);
7490
});
7591
res.on('end', () => {
76-
const cert = convertX509CertToPEM(data);
77-
if (res.headers['cache-control']) {
78-
var expire = res.headers['cache-control'].match(/max-age=([0-9]+)/);
79-
if (expire) {
80-
cache[publicKeyUrl] = cert;
81-
// we'll expire the cache entry later, as per max-age
82-
setTimeout(() => {
83-
delete cache[publicKeyUrl];
84-
}, parseInt(expire[1], 10) * 1000);
85-
}
92+
if (buffer) {
93+
resolve({certificate: Buffer.concat(data), headers: res.headers});
94+
return;
8695
}
87-
resolve(cert);
96+
let cert = '';
97+
for (const chunk of data) {
98+
cert += chunk.toString('base64');
99+
}
100+
const certificate = convertX509CertToPEM(cert);
101+
resolve({certificate, headers: res.headers});
88102
});
89103
})
90104
.on('error', reject);
@@ -115,6 +129,27 @@ function verifySignature(publicKey, authData) {
115129
}
116130
}
117131

132+
function verifyPublicKeyIssuer(cert, publicKeyUrl) {
133+
const publicKeyCert = pki.certificateFromPem(cert);
134+
if (!ca.cert) {
135+
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.');
136+
}
137+
try {
138+
if (!ca.cert.verify(publicKeyCert)) {
139+
throw new Parse.Error(
140+
Parse.Error.OBJECT_NOT_FOUND,
141+
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
142+
);
143+
}
144+
} catch (e) {
145+
throw new Parse.Error(
146+
Parse.Error.OBJECT_NOT_FOUND,
147+
`Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}`
148+
);
149+
}
150+
return cert;
151+
}
152+
118153
// Returns a promise that fulfills if this user id is valid.
119154
async function validateAuthData(authData) {
120155
if (!authData.id) {
@@ -126,11 +161,27 @@ async function validateAuthData(authData) {
126161
}
127162

128163
// Returns a promise that fulfills if this app id is valid.
129-
function validateAppId() {
130-
return Promise.resolve();
164+
async function validateAppId(appIds, authData, options = {}) {
165+
if (!options.rootCertificateUrl) {
166+
options.rootCertificateUrl = 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem'
167+
}
168+
if (ca.url === options.rootCertificateUrl) {
169+
return;
170+
}
171+
const {certificate, headers} = await getCertificate(options.rootCertificateUrl, true);
172+
if (
173+
headers['content-type'] !== 'application/x-pem-file' ||
174+
headers['content-length'] == null ||
175+
headers['content-length'] > 10000
176+
) {
177+
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.');
178+
}
179+
ca.cert = pki.certificateFromPem(certificate);
180+
ca.url = options.rootCertificateUrl
131181
}
132182

133183
module.exports = {
134184
validateAppId,
135185
validateAuthData,
186+
cache,
136187
};

0 commit comments

Comments
 (0)