Error upgrading Node 16 -> 18: "unsupported at configSecureContext"

Hi,

We run parse-server via express in a very similar way to what the README says. So our app only depends on express (4.18.2) and parse-server (6.3.1). It’s running on Heroku (heroku-22).

We tried upgrading from node 16.18.1 to node 18.12.1, but get an error when we try to send a push notification:

verbose: _PushStatus H0Dmigt9ae: sending push to installations with 1 batches
verbose: Sending push to 72
verb parse-server-push-adapter GCM sending to 70 devices
/app/node_modules/parse-server/lib/ParseServer.js:263
throw err;
^

Error: unsupported
at configSecureContext (node:internal/tls/secure-context:277:15)
at Object.createSecureContext (node:_tls_common:117:3)
at Object.connect (node:_tls_wrap:1629:48)
at Object.connect (node:internal/http2/core:3287:22)
at /app/node_modules/@parse/node-apn/lib/client.js:121:45
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Node.js v18.12.1
Process exited with status 7
State changed from up to crashed

Any pointers?

It’s clearly to do with TLS, but if anyone has further insight that would be helpful.

Our config around certs is as follows:

push: {
  ios: [
    {
      pfx: process.env.PUSH_IOS_SANDBOX_PFX || '', // The filename of private key and certificate in PFX or PKCS12 format from disk
      passphrase: process.env.PUSH_IOS_SANDBOX_PWD || '', // p12 password
      topic: process.env.PUSH_IOS_SANDBOX_BUNDLE_ID || '', // The bundle identifier associated with your app
      production: false // Specifies which environment to connect to: Sandbox
    },
    {
      pfx: process.env.PUSH_IOS_PRODUCTION_PFX || '', // The filename of private key and certificate in PFX or PKCS12 format from disk
      passphrase: process.env.PUSH_IOS_PRODUCTION_PWD || '', // p12 password
      topic: process.env.PUSH_IOS_PRODUCTION_BUNDLE_ID || '', // The bundle identifier associated with your app
      production: true // Specifies which environment to connect to: Production
    }
  ],
  android: {
    apiKey: process.env.PUSH_ANDROID_API_KEY || ''
  }
}

Where PUSH_IOS_SANDBOX_PFX etc are paths like files/Production/Sandbox.p12, and we have those files in our app.

These are our instructions for generating those files:


  • Go to the Apple Developer “Identifiers” list

  • Click the relevant app

  • Click “Edit” next to “Push Notifications”.

  • Under “Production SSL Certificate”, click “Create Certificate”.

  • You’ll now need to provide a “certificate signing request” (CSR) via Keychain Access

    • Open “Keychain Access.app” in macOS
    • In the menus: Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority…
    • As email, enter your work email
    • As name, enter e.g. “Auctionet Staging Push Certificate” (the name doesn’t really matter)
    • Leave the “CA Email Address” field empty (it will no longer be required when we pick “Save to disk” in the next step)
    • Choose “Saved to disk”, and click Continue
    • Save it to disk, e.g. your desktop
  • On the Apple Developer website, in the “Create a New Certificate” wizard, under the “Upload a Certificate Signing Request” header, provide the CertificateSigningRequest.certSigningRequest file you just saved.

  • When you’re through the wizard, you’ll get a .cer file. Double-click it to open it in Keychain Access.

  • Find the certificate in Keychain Access under “My Certificates” in the sidebar. It will be named e.g. “Apple Push Services: com.myapp” and will expire about a year from now.

  • Right-click the certificate and choose “Export …”.

  • When it asks, provide the same password as we have configured on Heroku. Find it like this:

    heroku config -a someapp | grep PWD
    

That’s it! Now we’ve got a cert.

Download it and copy it to e.g. files/Staging/Production.p12


Phew! That was a lot, but I wanted to provide relevant details.

Since node-apn (Apple Push Notifications) was in the backtrace, I looked at its issues, and I found my way to Release 6.0.0 · parse-community/node-apn · GitHub which suggests that version 6 of node-apn adds support for node 18.

Which confuses me since the changelog said that parse-server 5.3.0 added support for node 18, and even parse-server 6.3.1 that we are on does not include node-apn 6.0.0+.

So my next step will be to see if we can get onto node-apn 6.0.0.

EDIT: Turns out “adds support” was just adding it to the test matrix, and some test fixes: feat: Add support for Node 18, 20 drop support for Node 12 by dplewis · Pull Request #137 · parse-community/node-apn · GitHub So presumably earlier versions are supposed to work with Node 18.

I think I’ve solved it.

When creating certs per the instructions above, we got p12 cert files with the outdated RC2-40-CBC algorithm. We can determine this like so:

openssl pkcs12 -info -in files/Staging/Production.p12 

The output included “Algorithm (RC2-40-CBC : 0)”.

I tried generating new certs in a way that got us the newer AES-256-CBC algorithm and this seems to work fine both in Node 16 and Node 18. This is how I did it.

aps.cer is the file I created via the wizard on the Apple Developer site. This converts it to from aps.cer to staging.pem.

openssl x509 -in aps.cer -inform DER -out staging.pem -outform PEM

Then I need the private key associated with the CSR. I found that in Keychain Access (under “Keys”, has the same name as I picked for the CSR, kind is “private key”) and exported it to a p12 without a password.

Then I converted it from p12 to pem like this:

openssl pkcs12 -in staging_priv.p12 -nocerts -out staging_priv.pem

I provided an empty “Import Password” (since I exported it without one) but provided a simple “PEM pass phrase” like “1234” since one was required. Now I have a staging_priv.pem.

And now (phew) I can create a staging.p12 file:

openssl pkcs12 -export -inkey staging_priv.pem -in staging.pem -out staging.p12

When asked for “pass phrase for staging_priv.pem”, I provided “1234” from above.

When asked for an “Export Password”, I provided the secret passphrase that we pass into the ParseServer push settings.

1 Like

Why don’t you just use a APNS auth key instead of a certificate? I believe the push adapter supports that as well. Then you download the certificate directly from Apple.

Thank you, will look into that! Would be great for this to be simpler.

Alright, done. That was so much simpler :slightly_smiling_face:

I followed the instructions in Push Notifications Tutorial: Getting Started | Kodeco.

1 Like

Great to hear, maybe you want to post how you set up the Parse Server push adapter config here, in case someone else is looking for the same solution.

1 Like

Sure thing. Our new instructions:


The following is based on this guide and the Parse docs.

  • On Apple’s developer site, visit “Certificates, Identifiers & Profiles”.
  • Click the current “Push Notification Key”, “Edit”, rename it to “Old Push Notification Key”.
  • Click “+” next to “Keys”.
  • Check “Apple Push Notification service”.
  • Name it “Push Notification Key”.
  • “Continue”, “Register”, download the file and also make a note of the “Key ID”.

Then (assuming a macOS system), copy the key to clipboard:

cat ~/Downloads/AuthKey_*.p8 | pbcopy

Then in the development VM:

heroku config:set PUSH_IOS_KEY_ID=<you made a note of this> PUSH_IOS_KEY="<paste from clipboard, newlines and all>" -a my-app-staging

Verify it works (this links to our verification procedure), then do the same for my-app-production (and again verify it works).

On Apple’s developer site, click “Old Push Notification Key” then “Revoke”. (We can only have two keys, so let’s keep it tidy.)


The config is

push: {
    ios: [
      {
        token: {
          key: 'ios_key.p8',  // Generated from package.json scripts.start using the "PUSH_IOS_KEY" ENV.
          keyId: process.env.PUSH_IOS_KEY_ID,
          teamId: 'our hard-coded team ID',  // From https://developer.apple.com/account/#/membership.
        },
        topic: process.env.PUSH_IOS_BUNDLE_ID, // App bundle identifier.
        production: true,  // For the released production app.
      },
      {
        token: {
          key: 'ios_key.p8',
          keyId: process.env.PUSH_IOS_KEY_ID,
          teamId: 'our hard-coded team ID',
        },
        topic: process.env.PUSH_IOS_BUNDLE_ID, // App bundle identifier.
        production: false,  // "Sandbox", for the staging app. (And theoretically for the production app when being developed.)
      },
    ],
  …
}

And finally this in package.json:

  "scripts": {
    "start": "echo \"$PUSH_IOS_KEY\" > /app/ios_key.p8 && node index.js"
  },

We did not want this secret hard-coded in the repo, but it seems like Parse requires a file on disk. Thus we write a file from a Heroku ENV on launch.