fix: Authentication provider credentials are usable across Parse Server apps; fixes security vulnerability [GHSA-837q-jhwx-cmpv](https://github.com/parse-community/parse-server/security/advisories/GHSA-837q-jhwx-cmpv) (#9668)
This commit is contained in:
220
spec/Adapters/Auth/gcenter.spec.js
Normal file
220
spec/Adapters/Auth/gcenter.spec.js
Normal file
@@ -0,0 +1,220 @@
|
||||
const GameCenterAuth = require('../../../lib/Adapters/Auth/gcenter').default;
|
||||
const { pki } = require('node-forge');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
describe('GameCenterAuth Adapter', function () {
|
||||
let adapter;
|
||||
|
||||
beforeEach(function () {
|
||||
adapter = new GameCenterAuth.constructor();
|
||||
|
||||
const gcProd4 = fs.readFileSync(path.resolve(__dirname, '../../support/cert/gc-prod-4.cer'));
|
||||
const digicertPem = fs.readFileSync(path.resolve(__dirname, '../../support/cert/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem')).toString();
|
||||
|
||||
mockFetch([
|
||||
{
|
||||
url: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
method: 'GET',
|
||||
response: {
|
||||
ok: true,
|
||||
headers: new Map(),
|
||||
arrayBuffer: () => Promise.resolve(
|
||||
gcProd4.buffer.slice(gcProd4.byteOffset, gcProd4.byteOffset + gcProd4.length)
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
url: 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem',
|
||||
method: 'GET',
|
||||
response: {
|
||||
ok: true,
|
||||
headers: new Map([['content-type', 'application/x-pem-file'], ['content-length', digicertPem.length.toString()]]),
|
||||
text: () => Promise.resolve(digicertPem),
|
||||
},
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
describe('Test config failing due to missing params or wrong types', function () {
|
||||
it('should throw error for invalid options', async function () {
|
||||
const invalidOptions = [
|
||||
null,
|
||||
undefined,
|
||||
{},
|
||||
{ bundleId: '' },
|
||||
{ enableInsecureAuth: false }, // Missing bundleId in secure mode
|
||||
];
|
||||
|
||||
for (const options of invalidOptions) {
|
||||
expect(() => adapter.validateOptions(options)).withContext(JSON.stringify(options)).toThrow()
|
||||
}
|
||||
});
|
||||
|
||||
it('should validate options successfully with valid parameters', function () {
|
||||
const validOptions = { bundleId: 'com.valid.app', enableInsecureAuth: false };
|
||||
expect(() => adapter.validateOptions(validOptions)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test payload failing due to missing params or wrong types', function () {
|
||||
it('should throw error for missing authData fields', async function () {
|
||||
await expectAsync(adapter.validateAuthData({})).toBeRejectedWithError(
|
||||
'AuthData id is missing.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test payload fails due to incorrect appId / certificate', function () {
|
||||
it('should throw error for invalid publicKeyUrl', async function () {
|
||||
const invalidPublicKeyUrl = 'https://malicious.url.com/key.cer';
|
||||
|
||||
spyOn(adapter, 'fetchCertificate').and.throwError(
|
||||
new Error('Invalid publicKeyUrl')
|
||||
);
|
||||
|
||||
await expectAsync(
|
||||
adapter.getAppleCertificate(invalidPublicKeyUrl)
|
||||
).toBeRejectedWithError('Invalid publicKeyUrl: https://malicious.url.com/key.cer');
|
||||
});
|
||||
|
||||
it('should throw error for invalid signature verification', async function () {
|
||||
const fakePublicKey = 'invalid-key';
|
||||
const fakeAuthData = {
|
||||
id: '1234567',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
timestamp: 1460981421303,
|
||||
salt: 'saltST==',
|
||||
signature: 'invalidSignature',
|
||||
};
|
||||
|
||||
spyOn(adapter, 'getAppleCertificate').and.returnValue(Promise.resolve(fakePublicKey));
|
||||
spyOn(adapter, 'verifySignature').and.throwError('Invalid signature.');
|
||||
|
||||
await expectAsync(adapter.validateAuthData(fakeAuthData)).toBeRejectedWithError(
|
||||
'Invalid signature.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test payload passing', function () {
|
||||
it('should successfully process valid payload and save auth data', async function () {
|
||||
const validAuthData = {
|
||||
id: '1234567',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
timestamp: 1460981421303,
|
||||
salt: 'saltST==',
|
||||
signature: 'validSignature',
|
||||
bundleId: 'com.valid.app',
|
||||
};
|
||||
|
||||
spyOn(adapter, 'getAppleCertificate').and.returnValue(Promise.resolve('validKey'));
|
||||
spyOn(adapter, 'verifySignature').and.returnValue(true);
|
||||
|
||||
await expectAsync(adapter.validateAuthData(validAuthData)).toBeResolved();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Certificate and Signature Validation', function () {
|
||||
it('should fetch and validate Apple certificate', async function () {
|
||||
const certUrl = 'https://static.gc.apple.com/public-key/gc-prod-4.cer';
|
||||
const mockCertificate = 'mockCertificate';
|
||||
|
||||
spyOn(adapter, 'fetchCertificate').and.returnValue(
|
||||
Promise.resolve({ certificate: mockCertificate, headers: new Map() })
|
||||
);
|
||||
spyOn(pki, 'certificateFromPem').and.returnValue({});
|
||||
|
||||
adapter.cache[certUrl] = mockCertificate;
|
||||
|
||||
const cert = await adapter.getAppleCertificate(certUrl);
|
||||
expect(cert).toBe(mockCertificate);
|
||||
});
|
||||
|
||||
it('should verify signature successfully', async function () {
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
timestamp: 1565257031287,
|
||||
signature:
|
||||
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
|
||||
salt: 'DzqqrQ==',
|
||||
};
|
||||
|
||||
adapter.bundleId = 'cloud.xtralife.gamecenterauth';
|
||||
adapter.enableInsecureAuth = false;
|
||||
|
||||
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
|
||||
|
||||
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
|
||||
|
||||
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
|
||||
|
||||
});
|
||||
|
||||
it('should not use bundle id from authData payload in secure mode', async function () {
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
timestamp: 1565257031287,
|
||||
signature:
|
||||
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'com.example.insecure.app',
|
||||
};
|
||||
|
||||
adapter.bundleId = 'cloud.xtralife.gamecenterauth';
|
||||
adapter.enableInsecureAuth = false;
|
||||
|
||||
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
|
||||
|
||||
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
|
||||
|
||||
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
|
||||
|
||||
});
|
||||
|
||||
it('should not use bundle id from authData payload in insecure mode', async function () {
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
timestamp: 1565257031287,
|
||||
signature:
|
||||
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'com.example.insecure.app',
|
||||
};
|
||||
|
||||
adapter.bundleId = 'cloud.xtralife.gamecenterauth';
|
||||
adapter.enableInsecureAuth = true;
|
||||
|
||||
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
|
||||
|
||||
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
|
||||
|
||||
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
|
||||
|
||||
});
|
||||
|
||||
it('can use bundle id from authData payload in insecure mode', async function () {
|
||||
const authData = {
|
||||
id: 'G:1965586982',
|
||||
publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer',
|
||||
timestamp: 1565257031287,
|
||||
signature:
|
||||
'uqLBTr9Uex8zCpc1UQ1MIDMitb+HUat2Mah4Kw6AVLSGe0gGNJXlih2i5X+0ZwVY0S9zY2NHWi2gFjmhjt/4kxWGMkupqXX5H/qhE2m7hzox6lZJpH98ZEUbouWRfZX2ZhUlCkAX09oRNi7fI7mWL1/o88MaI/y6k6tLr14JTzmlxgdyhw+QRLxRPA6NuvUlRSJpyJ4aGtNH5/wHdKQWL8nUnFYiYmaY8R7IjzNxPfy8UJTUWmeZvMSgND4u8EjADPsz7ZtZyWAPi8kYcAb6M8k0jwLD3vrYCB8XXyO2RQb/FY2TM4zJuI7PzLlvvgOJXbbfVtHx7Evnm5NYoyzgzw==',
|
||||
salt: 'DzqqrQ==',
|
||||
bundleId: 'cloud.xtralife.gamecenterauth',
|
||||
};
|
||||
|
||||
adapter.enableInsecureAuth = true;
|
||||
|
||||
spyOn(adapter, 'verifyPublicKeyIssuer').and.returnValue();
|
||||
|
||||
const publicKey = await adapter.getAppleCertificate(authData.publicKeyUrl);
|
||||
|
||||
expect(() => adapter.verifySignature(publicKey, authData)).not.toThrow();
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user