From 5efd27a7ce14cc1c19376f29f960125e721fa109 Mon Sep 17 00:00:00 2001 From: amanda Date: Wed, 12 Jul 2023 09:48:32 +0800 Subject: [PATCH] refactor(myinfo): replace node-jose with jose for SSO (opengovsg#328) --- lib/express/myinfo/controllers.js | 32 ++++++++++++++++++++----------- package-lock.json | 14 ++++++++++++++ package.json | 1 + 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/express/myinfo/controllers.js b/lib/express/myinfo/controllers.js index 88c6d628..95f56b9d 100644 --- a/lib/express/myinfo/controllers.js +++ b/lib/express/myinfo/controllers.js @@ -5,7 +5,7 @@ const path = require('path') const express = require('express') const { pick, partition } = require('lodash') -const jose = require('node-jose') +const jose = require('jose') const jwt = require('jsonwebtoken') const assertions = require('../../assertions') @@ -31,16 +31,27 @@ module.exports = } const encryptPersona = async (persona) => { - const signedPersona = jwt.sign(persona, MOCKPASS_PRIVATE_KEY, { - algorithm: 'RS256', - }) - const serviceCertAsKey = await jose.JWK.asKey(serviceProvider.cert, 'pem') - const encryptedAndSignedPersona = await jose.JWE.createEncrypt( - { format: 'compact' }, - serviceCertAsKey, + /* + * We sign and encrypt the persona. It's important to note that although a signature is + * usually derived from the payload hash and is thus much smaller than the payload itself, + * we're specifically contructeding a JWT, which contains the original payload. + * + * We then construct a JWE and provide two headers specifying the encryption algorithms used. + * You can read about them here: https://www.rfc-editor.org/rfc/inline-errata/rfc7518.html + * + * These values weren't picked arbitrarily; they were the defaults used by a library we + * formerly used: node-jose. We opted to continue using them for backwards compatibility. + */ + const privateKey = await jose.importPKCS8(MOCKPASS_PRIVATE_KEY.toString()) + const sign = await new jose.SignJWT(persona) + .setProtectedHeader({ alg: 'RS256' }) + .sign(privateKey) + const publicKey = await jose.importX509(serviceProvider.cert.toString()) + const encryptedAndSignedPersona = await new jose.CompactEncrypt( + Buffer.from(sign), ) - .update(JSON.stringify(signedPersona)) - .final() + .setProtectedHeader({ alg: 'RSA-OAEP', enc: 'A128CBC-HS256' }) + .encrypt(publicKey) return encryptedAndSignedPersona } @@ -142,7 +153,6 @@ module.exports = redirect_uri, }) : {} - if (!tokenTemplate) { res.status(400).send({ code: 400, diff --git a/package-lock.json b/package-lock.json index cf210d2c..7371e8b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "dotenv": "^16.0.0", "expiry-map": "^2.0.0", "express": "^4.16.3", + "jose": "^4.14.4", "jsonwebtoken": "^9.0.0", "lodash": "^4.17.11", "morgan": "^1.9.1", @@ -2987,6 +2988,14 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7782,6 +7791,11 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index b448c58e..f496b22d 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "dotenv": "^16.0.0", "expiry-map": "^2.0.0", "express": "^4.16.3", + "jose": "^4.14.4", "jsonwebtoken": "^9.0.0", "lodash": "^4.17.11", "morgan": "^1.9.1",