Skip to content

Commit

Permalink
Temporarily replace built-in webcrypto with @peculiar/webcrypto
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
laymonage committed Apr 3, 2021
1 parent b01ee15 commit 3a52b18
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 15 deletions.
24 changes: 9 additions & 15 deletions 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().
Expand All @@ -22,16 +16,16 @@ const cryptolib = ((nodecrypto as unknown) as NodeCrypto).webcrypto;
*/
export async function aesGcmEncrypt(plaintext: string, password: string): Promise<string> {
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
Expand All @@ -58,7 +52,7 @@ export async function aesGcmEncrypt(plaintext: string, password: string): Promis
*/
export async function aesGcmDecrypt(ciphertext: string, password: string): Promise<string> {
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)
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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",
Expand Down
68 changes: 68 additions & 0 deletions yarn.lock
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

1 comment on commit 3a52b18

@vercel
Copy link

@vercel vercel bot commented on 3a52b18 Apr 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.