From 07786c16660c6b9854ef97246e46f25015dafd77 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 17 Jun 2022 14:56:11 +0200 Subject: [PATCH 1/4] fix adapter --- spec/AuthenticationAdapters.spec.js | 147 +++++++++++++++++++++++++--- src/Adapters/Auth/gcenter.js | 93 ++++++++++++++---- 2 files changed, 208 insertions(+), 32 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 850039c5..7e2c98fd 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -1644,8 +1644,41 @@ describe('apple signin auth adapter', () => { describe('Apple Game Center Auth adapter', () => { const gcenter = require('../lib/Adapters/Auth/gcenter'); - + const fs = require('fs'); + const testCert = fs.readFileSync(__dirname + '/support/cert/game_center.pem'); + it('can load adapter', async () => { + const options = { + gcenter: { + rootCertificateUrl: + 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', + }, + }; + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'gcenter', + options + ); + await adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ); + }); it('validateAuthData should validate', async () => { + const options = { + gcenter: { + rootCertificateUrl: + 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', + }, + }; + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'gcenter', + options + ); + await adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ); // real token is used const authData = { id: 'G:1965586982', @@ -1656,29 +1689,49 @@ describe('Apple Game Center Auth adapter', () => { salt: 'DzqqrQ==', bundleId: 'cloud.xtralife.gamecenterauth', }; - + gcenter.cache['https://static.gc.apple.com/public-key/gc-prod-4.cer'] = testCert; await gcenter.validateAuthData(authData); }); it('validateAuthData invalid signature id', async () => { + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'gcenter', + {} + ); + await adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ); const authData = { id: 'G:1965586982', - publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer', + publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-6.cer', timestamp: 1565257031287, signature: '1234', salt: 'DzqqrQ==', - bundleId: 'cloud.xtralife.gamecenterauth', + bundleId: 'com.example.com', }; - - try { - await gcenter.validateAuthData(authData); - fail(); - } catch (e) { - expect(e.message).toBe('Apple Game Center - invalid signature'); - } + await expectAsync(gcenter.validateAuthData(authData)).toBeRejectedWith( + new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Apple Game Center - invalid signature') + ); }); it('validateAuthData invalid public key http url', async () => { + const options = { + gcenter: { + rootCertificateUrl: + 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', + }, + }; + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'gcenter', + options + ); + await adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ); const publicKeyUrls = [ 'example.com', 'http://static.gc.apple.com/public-key/gc-prod-4.cer', @@ -1706,6 +1759,78 @@ describe('Apple Game Center Auth adapter', () => { ) ); }); + + it('should not validate Symantec Cert', async () => { + const options = { + gcenter: { + rootCertificateUrl: + 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem', + }, + }; + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'gcenter', + options + ); + await adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ); + expect(() => + gcenter.verifyPublicKeyIssuer( + testCert, + 'https://static.gc.apple.com/public-key/gc-prod-4.cer' + ) + ); + }); + + it('adapter should load default cert', async () => { + const options = { + gcenter: {}, + }; + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'gcenter', + options + ); + await adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ); + const previous = new Date(); + await adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ); + + const duration = new Date().getTime() - previous.getTime(); + expect(duration).toEqual(0); + }); + + it('adapter should throw', async () => { + const options = { + gcenter: { + rootCertificateUrl: 'https://example.com', + }, + }; + const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter( + 'gcenter', + options + ); + await expectAsync( + adapter.validateAppId( + appIds, + { publicKeyUrl: 'https://static.gc.apple.com/public-key/gc-prod-4.cer' }, + providerOptions + ) + ).toBeRejectedWith( + new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.' + ) + ); + }); }); describe('phant auth adapter', () => { diff --git a/src/Adapters/Auth/gcenter.js b/src/Adapters/Auth/gcenter.js index 5cd8e8af..dde5bd66 100644 --- a/src/Adapters/Auth/gcenter.js +++ b/src/Adapters/Auth/gcenter.js @@ -14,7 +14,8 @@ const authData = { const { Parse } = require('parse/node'); const crypto = require('crypto'); const https = require('https'); - +const { pki } = require('node-forge'); +const ca = { cert: null, url: null }; const cache = {}; // (publicKey -> cert) cache function verifyPublicKeyUrl(publicKeyUrl) { @@ -52,39 +53,52 @@ async function getAppleCertificate(publicKeyUrl) { path: url.pathname, method: 'HEAD', }; - const headers = await new Promise((resolve, reject) => + const cert_headers = await new Promise((resolve, reject) => https.get(headOptions, res => resolve(res.headers)).on('error', reject) ); if ( - headers['content-type'] !== 'application/pkix-cert' || - headers['content-length'] == null || - headers['content-length'] > 10000 + cert_headers['content-type'] !== 'application/pkix-cert' || + cert_headers['content-length'] == null || + cert_headers['content-length'] > 10000 ) { throw new Parse.Error( Parse.Error.OBJECT_NOT_FOUND, `Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}` ); } + const {certificate, headers} = await getCertificate(publicKeyUrl); + if (headers['cache-control']) { + const expire = headers['cache-control'].match(/max-age=([0-9]+)/); + if (expire) { + cache[publicKeyUrl] = certificate; + // we'll expire the cache entry later, as per max-age + setTimeout(() => { + delete cache[publicKeyUrl]; + }, parseInt(expire[1], 10) * 1000); + } + } + return verifyPublicKeyIssuer(certificate, publicKeyUrl); +} + +function getCertificate(url, buffer) { return new Promise((resolve, reject) => { https - .get(publicKeyUrl, res => { - let data = ''; + .get(url, res => { + const data = []; res.on('data', chunk => { - data += chunk.toString('base64'); + data.push(chunk); }); res.on('end', () => { - const cert = convertX509CertToPEM(data); - if (res.headers['cache-control']) { - var expire = res.headers['cache-control'].match(/max-age=([0-9]+)/); - if (expire) { - cache[publicKeyUrl] = cert; - // we'll expire the cache entry later, as per max-age - setTimeout(() => { - delete cache[publicKeyUrl]; - }, parseInt(expire[1], 10) * 1000); - } + if (buffer) { + resolve({certificate: Buffer.concat(data), headers: res.headers}); + return; } - resolve(cert); + let cert = ''; + for (const chunk of data) { + cert += chunk.toString('base64'); + } + const certificate = convertX509CertToPEM(cert); + resolve({certificate, headers: res.headers}); }); }) .on('error', reject); @@ -115,6 +129,27 @@ function verifySignature(publicKey, authData) { } } +function verifyPublicKeyIssuer(cert, publicKeyUrl) { + const publicKeyCert = pki.certificateFromPem(cert); + if (!ca.cert) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'); + } + try { + if (!ca.cert.verify(publicKeyCert)) { + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + `Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}` + ); + } + } catch (e) { + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + `Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}` + ); + } + return cert; +} + // Returns a promise that fulfills if this user id is valid. async function validateAuthData(authData) { if (!authData.id) { @@ -126,11 +161,27 @@ async function validateAuthData(authData) { } // Returns a promise that fulfills if this app id is valid. -function validateAppId() { - return Promise.resolve(); +async function validateAppId(appIds, authData, options = {}) { + if (!options.rootCertificateUrl) { + options.rootCertificateUrl = 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem' + } + if (ca.url === options.rootCertificateUrl) { + return; + } + const {certificate, headers} = await getCertificate(options.rootCertificateUrl, true); + if ( + headers['content-type'] !== 'application/x-pem-file' || + headers['content-length'] == null || + headers['content-length'] > 10000 + ) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'); + } + ca.cert = pki.certificateFromPem(certificate); + ca.url = options.rootCertificateUrl } module.exports = { validateAppId, validateAuthData, + cache, }; From c411c48d49791667454021af0f1a2ca0a13d857e Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 17 Jun 2022 16:16:52 +0200 Subject: [PATCH 2/4] Create game_center.pem --- spec/support/cert/game_center.pem | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 spec/support/cert/game_center.pem diff --git a/spec/support/cert/game_center.pem b/spec/support/cert/game_center.pem new file mode 100644 index 00000000..b5dffcd8 --- /dev/null +++ b/spec/support/cert/game_center.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEvDCCA6SgAwIBAgIQXRHxNXkw1L9z5/3EZ/T/hDANBgkqhkiG9w0BAQsFADB/ +MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAd +BgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5bWFudGVj +IENsYXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQTAeFw0xODA5MTcwMDAwMDBa +Fw0xOTA5MTcyMzU5NTlaMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y +bmlhMRIwEAYDVQQHDAlDdXBlcnRpbm8xFDASBgNVBAoMC0FwcGxlLCBJbmMuMQ8w +DQYDVQQLDAZHQyBTUkUxFDASBgNVBAMMC0FwcGxlLCBJbmMuMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA06fwIi8fgKrTQu7cBcFkJVF6+Tqvkg7MKJTM +IOYPPQtPF3AZYPsbUoRKAD7/JXrxxOSVJ7vU1mP77tYG8TcUteZ3sAwvt2dkRbm7 +ZO6DcmSggv1Dg4k3goNw4GYyCY4Z2/8JSmsQ80Iv/UOOwynpBziEeZmJ4uck6zlA +17cDkH48LBpKylaqthym5bFs9gj11pto7mvyb5BTcVuohwi6qosvbs/4VGbC2Nsz +ie416nUZfv+xxoXH995gxR2mw5cDdeCew7pSKxEhvYjT2nVdQF0q/hnPMFnOaEyT +q79n3gwFXyt0dy8eP6KBF7EW9J6b7ubu/j7h+tQfxPM+gTXOBQIDAQABo4IBPjCC +ATowCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH +AwMwYQYDVR0gBFowWDBWBgZngQwBBAEwTDAjBggrBgEFBQcCARYXaHR0cHM6Ly9k +LnN5bWNiLmNvbS9jcHMwJQYIKwYBBQUHAgIwGQwXaHR0cHM6Ly9kLnN5bWNiLmNv +bS9ycGEwHwYDVR0jBBgwFoAUljtT8Hkzl699g+8uK8zKt4YecmYwKwYDVR0fBCQw +IjAgoB6gHIYaaHR0cDovL3N2LnN5bWNiLmNvbS9zdi5jcmwwVwYIKwYBBQUHAQEE +SzBJMB8GCCsGAQUFBzABhhNodHRwOi8vc3Yuc3ltY2QuY29tMCYGCCsGAQUFBzAC +hhpodHRwOi8vc3Yuc3ltY2IuY29tL3N2LmNydDANBgkqhkiG9w0BAQsFAAOCAQEA +I/j/PcCNPebSAGrcqSFBSa2mmbusOX01eVBg8X0G/z8Z+ZWUfGFzDG0GQf89MPxV +woec+nZuqui7o9Bg8s8JbHV0TC52X14CbTj9w/qBF748WbH9gAaTkrJYPm+MlNhu +tjEuQdNl/YXVMvQW4O8UMHTi09GyJQ0NC4q92Wxvx1m/qzjvTLvrXHGQ9pEHhPyz +vfBLxQkWpNoCNKU7UeESyH06XOrGc9MsII9deeKsDJp9a0jtx+pP4MFVtFME9SSQ +tMBs0It7WwEf7qcRLpialxKwY2EzQ9g4WnANHqo18PrDBE10TFpZPzUh7JhMViVr +EEbl0YdElmF8Hlamah/yNw== +-----END CERTIFICATE----- From 53afafa13f98aa3c159154e9d152a71ea030fc82 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 17 Jun 2022 17:30:54 +0200 Subject: [PATCH 3/4] Update gcenter.js --- src/Adapters/Auth/gcenter.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Adapters/Auth/gcenter.js b/src/Adapters/Auth/gcenter.js index dde5bd66..f70c2541 100644 --- a/src/Adapters/Auth/gcenter.js +++ b/src/Adapters/Auth/gcenter.js @@ -56,8 +56,9 @@ async function getAppleCertificate(publicKeyUrl) { const cert_headers = await new Promise((resolve, reject) => https.get(headOptions, res => resolve(res.headers)).on('error', reject) ); + const validContentTypes = ['application/x-x509-ca-cert', 'application/pkix-cert']; if ( - cert_headers['content-type'] !== 'application/pkix-cert' || + !validContentTypes.includes(cert_headers['content-type']) || cert_headers['content-length'] == null || cert_headers['content-length'] > 10000 ) { @@ -66,7 +67,7 @@ async function getAppleCertificate(publicKeyUrl) { `Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}` ); } - const {certificate, headers} = await getCertificate(publicKeyUrl); + const { certificate, headers } = await getCertificate(publicKeyUrl); if (headers['cache-control']) { const expire = headers['cache-control'].match(/max-age=([0-9]+)/); if (expire) { @@ -90,7 +91,7 @@ function getCertificate(url, buffer) { }); res.on('end', () => { if (buffer) { - resolve({certificate: Buffer.concat(data), headers: res.headers}); + resolve({ certificate: Buffer.concat(data), headers: res.headers }); return; } let cert = ''; @@ -98,7 +99,7 @@ function getCertificate(url, buffer) { cert += chunk.toString('base64'); } const certificate = convertX509CertToPEM(cert); - resolve({certificate, headers: res.headers}); + resolve({ certificate, headers: res.headers }); }); }) .on('error', reject); @@ -132,7 +133,10 @@ function verifySignature(publicKey, authData) { function verifyPublicKeyIssuer(cert, publicKeyUrl) { const publicKeyCert = pki.certificateFromPem(cert); if (!ca.cert) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'); + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.' + ); } try { if (!ca.cert.verify(publicKeyCert)) { @@ -163,21 +167,25 @@ async function validateAuthData(authData) { // Returns a promise that fulfills if this app id is valid. async function validateAppId(appIds, authData, options = {}) { if (!options.rootCertificateUrl) { - options.rootCertificateUrl = 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem' + options.rootCertificateUrl = + 'https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt.pem'; } if (ca.url === options.rootCertificateUrl) { return; } - const {certificate, headers} = await getCertificate(options.rootCertificateUrl, true); + const { certificate, headers } = await getCertificate(options.rootCertificateUrl, true); if ( headers['content-type'] !== 'application/x-pem-file' || headers['content-length'] == null || headers['content-length'] > 10000 ) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.'); + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + 'Apple Game Center auth adapter parameter `rootCertificateURL` is invalid.' + ); } ca.cert = pki.certificateFromPem(certificate); - ca.url = options.rootCertificateUrl + ca.url = options.rootCertificateUrl; } module.exports = { From 8580a524eb2ad1d321f1aee085c4537fc312e475 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 17 Jun 2022 17:57:22 +0200 Subject: [PATCH 4/4] fix CI timeout --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a1873af..fc9db9ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,8 @@ jobs: PARSE_SERVER_TEST_CACHE: ${{ matrix.PARSE_SERVER_TEST_CACHE }} NODE_VERSION: ${{ matrix.NODE_VERSION }} steps: + - name: Fix usage of insecure GitHub protocol + run: sudo git config --system url."https://github".insteadOf "git://github" - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} uses: actions/setup-node@v1 @@ -89,6 +91,8 @@ jobs: POSTGRES_MAJOR_VERSION: 11 PARSE_SERVER_TEST_DATABASE_URI: postgres://postgres:postgres@localhost:5432/parse_server_postgres_adapter_test_database steps: + - name: Fix usage of insecure GitHub protocol + run: sudo git config --system url."https://github".insteadOf "git://github" - uses: actions/checkout@v2 - name: Use Node.js 10 uses: actions/setup-node@v1