From 3a52b18371e1045bff815d06ad6880052a81c23e Mon Sep 17 00:00:00 2001 From: sage Date: Sat, 3 Apr 2021 07:59:22 +0700 Subject: [PATCH] Temporarily replace built-in webcrypto with @peculiar/webcrypto The webcrypto API was introduced in Node 15. AWS Lambda (and by extension, Vercel and Netlify) only supports LTS versions of Node. Therefore, we need to use an alternative until Node 16 is supported. I tried replacing it with the built-in crypto module in Node, but I'm afraid it would be incorrect. --- lib/oauth/encryption.ts | 24 ++++++--------- package.json | 1 + yarn.lock | 68 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/lib/oauth/encryption.ts b/lib/oauth/encryption.ts index 2062987d6..41e9e1c40 100644 --- a/lib/oauth/encryption.ts +++ b/lib/oauth/encryption.ts @@ -1,12 +1,6 @@ -// Workaround for TypeScript -import nodecrypto from 'crypto'; +import { Crypto } from '@peculiar/webcrypto'; -interface NodeCrypto { - webcrypto: typeof crypto; -} - -const cryptolib = ((nodecrypto as unknown) as NodeCrypto).webcrypto; -// End workaround +const crypto = new Crypto(); /** * Encrypts plaintext using AES-GCM with supplied password, for decryption with aesGcmDecrypt(). @@ -22,16 +16,16 @@ const cryptolib = ((nodecrypto as unknown) as NodeCrypto).webcrypto; */ export async function aesGcmEncrypt(plaintext: string, password: string): Promise { const pwUtf8 = new TextEncoder().encode(password); // encode password as UTF-8 - const pwHash = await cryptolib.subtle.digest('SHA-256', pwUtf8); // hash the password + const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8); // hash the password - const iv = cryptolib.getRandomValues(new Uint8Array(12)); // get 96-bit random iv + const iv = crypto.getRandomValues(new Uint8Array(12)); // get 96-bit random iv const alg = { name: 'AES-GCM', iv: iv }; // specify algorithm to use - const key = await cryptolib.subtle.importKey('raw', pwHash, alg, false, ['encrypt']); // generate key from pw + const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['encrypt']); // generate key from pw const ptUint8 = new TextEncoder().encode(plaintext); // encode plaintext as UTF-8 - const ctBuffer = await cryptolib.subtle.encrypt(alg, key, ptUint8); // encrypt plaintext using key + const ctBuffer = await crypto.subtle.encrypt(alg, key, ptUint8); // encrypt plaintext using key const ctArray = Array.from(new Uint8Array(ctBuffer)); // ciphertext as byte array const ctStr = ctArray.map((byte) => String.fromCharCode(byte)).join(''); // ciphertext as string @@ -58,7 +52,7 @@ export async function aesGcmEncrypt(plaintext: string, password: string): Promis */ export async function aesGcmDecrypt(ciphertext: string, password: string): Promise { const pwUtf8 = new TextEncoder().encode(password); // encode password as UTF-8 - const pwHash = await cryptolib.subtle.digest('SHA-256', pwUtf8); // hash the password + const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8); // hash the password const iv = ciphertext .slice(0, 24) @@ -67,13 +61,13 @@ export async function aesGcmDecrypt(ciphertext: string, password: string): Promi const alg = { name: 'AES-GCM', iv: new Uint8Array(iv) }; // specify algorithm to use - const key = await cryptolib.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key + const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key const ctStr = Buffer.from(ciphertext.slice(24), 'base64').toString('binary'); // decode base64 ciphertext const ctUint8 = new Uint8Array(ctStr.match(/[\s\S]/g).map((ch) => ch.charCodeAt(0))); // ciphertext as Uint8Array // note: why doesn't ctUint8 = new TextEncoder().encode(ctStr) work? - const plainBuffer = await cryptolib.subtle.decrypt(alg, key, ctUint8); // decrypt ciphertext using key + const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); // decrypt ciphertext using key const plaintext = new TextDecoder().decode(plainBuffer); // decode password from UTF-8 return plaintext; // return the plaintext diff --git a/package.json b/package.json index 1e2d47bb3..97dacc3e4 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "eslint ." }, "dependencies": { + "@peculiar/webcrypto": "^1.1.6", "@primer/octicons-react": "^12.1.0", "date-fns": "^2.19.0", "jsonwebtoken": "^8.5.1", diff --git a/yarn.lock b/yarn.lock index 54018b943..c9196f812 100644 --- a/yarn.lock +++ b/yarn.lock @@ -146,6 +146,34 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.14.0.tgz#c67fc20a4d891447ca1a855d7d70fa79a3533001" integrity sha512-sDOAZcYwynHFTbLo6n8kIbLiVF3a3BLkrmehJUyEbT9F+Smbi47kLGS2gG2g0fjBLR/Lr1InPD7kXL7FaTqEkw== +"@peculiar/asn1-schema@^2.0.27": + version "2.0.27" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.27.tgz#1ee3b2b869ff3200bcc8ec60e6c87bd5a6f03fe0" + integrity sha512-1tIx7iL3Ma3HtnNS93nB7nhyI0soUJypElj9owd4tpMrRDmeJ8eZubsdq1sb0KSaCs5RqZNoABCP6m5WtnlVhQ== + dependencies: + "@types/asn1js" "^2.0.0" + asn1js "^2.0.26" + pvtsutils "^1.1.1" + tslib "^2.0.3" + +"@peculiar/json-schema@^1.1.12": + version "1.1.12" + resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339" + integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w== + dependencies: + tslib "^2.0.0" + +"@peculiar/webcrypto@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.1.6.tgz#484bb58be07149e19e873861b585b0d5e4f83b7b" + integrity sha512-xcTjouis4Y117mcsJslWAGypwhxtXslkVdRp7e3tHwtuw0/xCp1te8RuMMv/ia5TsvxomcyX/T+qTbRZGLLvyA== + dependencies: + "@peculiar/asn1-schema" "^2.0.27" + "@peculiar/json-schema" "^1.1.12" + pvtsutils "^1.1.2" + tslib "^2.1.0" + webcrypto-core "^1.2.0" + "@prefresh/babel-plugin@0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@prefresh/babel-plugin/-/babel-plugin-0.4.1.tgz#c4e843f7c5e56c15f1185979a8559c893ffb4a35" @@ -199,6 +227,11 @@ postcss-selector-parser "^6.0.4" quick-lru "^5.1.1" +"@types/asn1js@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/asn1js/-/asn1js-2.0.0.tgz#10ca75692575744d0117098148a8dc84cbee6682" + integrity sha512-Jjzp5EqU0hNpADctc/UqhiFbY1y2MqIxBVa2S4dBlbnZHTLPMuggoL5q43X63LpsOIINRDirBjP56DUUKIUWIA== + "@types/json-schema@^7.0.3": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -434,6 +467,13 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" +asn1js@^2.0.26: + version "2.1.1" + resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-2.1.1.tgz#bb3896191ebb5fb1caeda73436a6c6e20a2eedff" + integrity sha512-t9u0dU0rJN4ML+uxgN6VM2Z4H5jWIYm0w8LsZLzMJaQsgL3IJNbxHgmbWDvJAwspyHpDFuzUaUFh4c05UB4+6g== + dependencies: + pvutils latest + assert@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" @@ -2660,6 +2700,18 @@ purgecss@^3.1.3: postcss "^8.2.1" postcss-selector-parser "^6.0.2" +pvtsutils@^1.1.1, pvtsutils@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.1.2.tgz#483d72f4baa5e354466e68ff783ce8a9e2810030" + integrity sha512-Yfm9Dsk1zfEpOWCaJaHfqtNXAFWNNHMFSCLN6jTnhuCCBCC2nqge4sAgo7UrkRBoAAYIL8TN/6LlLoNfZD/b5A== + dependencies: + tslib "^2.1.0" + +pvutils@latest: + version "1.0.17" + resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.0.17.tgz#ade3c74dfe7178944fe44806626bd2e249d996bf" + integrity sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ== + querystring-es3@0.2.1, querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -3233,6 +3285,11 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + tsutils@^3.17.1: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -3373,6 +3430,17 @@ watchpack@2.1.1: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +webcrypto-core@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.2.0.tgz#44fda3f9315ed6effe9a1e47466e0935327733b5" + integrity sha512-p76Z/YLuE4CHCRdc49FB/ETaM4bzM3roqWNJeGs+QNY1fOTzKTOVnhmudW1fuO+5EZg6/4LG9NJ6gaAyxTk9XQ== + dependencies: + "@peculiar/asn1-schema" "^2.0.27" + "@peculiar/json-schema" "^1.1.12" + asn1js "^2.0.26" + pvtsutils "^1.1.2" + tslib "^2.1.0" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"