VYPR
High severityNVD Advisory· Published Jun 17, 2022· Updated Apr 23, 2025

Authentication bypass in Parse Server Apple Game Center auth adapter

CVE-2022-31083

Description

Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to versions 4.10.11 and 5.2.2, the certificate in the Parse Server Apple Game Center auth adapter not validated. As a result, authentication could potentially be bypassed by making a fake certificate accessible via certain Apple domains and providing the URL to that certificate in an authData object. Versions 4.0.11 and 5.2.2 prevent this by introducing a new rootCertificateUrl property to the Parse Server Apple Game Center auth adapter which takes the URL to the root certificate of Apple's Game Center authentication certificate. If no value is set, the rootCertificateUrl property defaults to the URL of the current root certificate as of May 27, 2022. Keep in mind that the root certificate can change at any time and that it is the developer's responsibility to keep the root certificate URL up-to-date when using the Parse Server Apple Game Center auth adapter. There are no known workarounds for this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
< 4.10.114.10.11
parse-servernpm
>= 5.0.0, < 5.2.25.2.2

Affected products

1

Patches

1
ba2b0a9cb9a5

fix: certificate in Apple Game Center auth adapter not validated; this fixes a security vulnerability in which authentication could be bypassed using a fake certificate; if you are using the Apple Gamer Center auth adapter it is your responsibility to keep its root certificate up-to-date and we advice you read the security advisory ([GHSA-rh9j-f5f8-rvgc](https://github.com/parse-community/parse-server/security/advisories/GHSA-rh9j-f5f8-rvgc))

4 files changed · +249 38
  • release.config.js+5 6 modified
    @@ -83,21 +83,20 @@ async function config() {
           ['@semantic-release/git', {
             assets: [changelogFile, 'package.json', 'package-lock.json', 'npm-shrinkwrap.json'],
           }],
    +      ['@semantic-release/github', {
    +        successComment: getReleaseComment(),
    +        labels: ['type:ci'],
    +        releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>']
    +      }],
           [
             "@saithodev/semantic-release-backmerge",
             {
               "branches": [
                 { from: "beta", to: "alpha" },
                 { from: "release", to: "beta" },
    -            { from: "release", to: "alpha" },
               ]
             }
           ],
    -      ['@semantic-release/github', {
    -        successComment: getReleaseComment(),
    -        labels: ['type:ci'],
    -        releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>']
    -      }],
         ],
       };
     
    
  • spec/AuthenticationAdapters.spec.js+136 11 modified
    @@ -1652,8 +1652,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',
    @@ -1664,29 +1697,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',
    @@ -1714,6 +1767,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', () => {
    
  • spec/support/cert/game_center.pem+28 0 added
    @@ -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-----
    
  • src/Adapters/Auth/gcenter.js+80 21 modified
    @@ -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,53 @@ 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)
       );
    +  const validContentTypes = ['application/x-x509-ca-cert', 'application/pkix-cert'];
       if (
    -    headers['content-type'] !== 'application/pkix-cert' ||
    -    headers['content-length'] == null ||
    -    headers['content-length'] > 10000
    +    !validContentTypes.includes(cert_headers['content-type']) ||
    +    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 +130,30 @@ 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 +165,31 @@ 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,
     };
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.