From 29afc855efd936a0d3b3b66b3312a0fa43dc0588 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 20 Dec 2022 13:57:53 +0100 Subject: [PATCH 1/2] Revert "Remove pbkdf2Sha512Crypto and getCryptoModule" This reverts commit 1eb7f494462b04ec4cd0fa72fbd7b47ec8878fd1 (PR #1342). --- CHANGELOG.md | 5 --- packages/crypto/src/pbkdf2.spec.ts | 28 +++++++++++++++- packages/crypto/src/pbkdf2.ts | 51 +++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 465110ae..e037bb59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,6 @@ and this project adheres to - all: Add full support for Node.js 18 and run all CI tests with it ([#1240]). - @cosmjs/tendermint-rpc: Remove unused `index` field from `RpcTxEvent` and `TxEvent`. This is unset starting with Tendermint 0.34. -- @cosmjs/crypto: The pbkdf2 implementation for old Node.js versions - `pbkdf2Sha512Crypto` was removed. Node.js has sufficient support for WebCrypto - these days and we still have a pure-JS fallback implementation. This avoids - unnecessary problems around importing Node.js modules. ([#1341]) - @cosmjs/proto-signing: Make input and output of `decodePubkey` non-optional ([#1289]). - @cosmjs/stargate: Remove unnecessary address prefix argument from @@ -31,7 +27,6 @@ and this project adheres to [#1289]: https://github.com/cosmos/cosmjs/issues/1289 [#1291]: https://github.com/cosmos/cosmjs/issues/1291 [#1329]: https://github.com/cosmos/cosmjs/pull/1329 -[#1341]: https://github.com/cosmos/cosmjs/issues/1341 ## [0.29.5] - 2022-12-07 diff --git a/packages/crypto/src/pbkdf2.spec.ts b/packages/crypto/src/pbkdf2.spec.ts index e22e3199..d76b48c4 100644 --- a/packages/crypto/src/pbkdf2.spec.ts +++ b/packages/crypto/src/pbkdf2.spec.ts @@ -1,6 +1,13 @@ import { fromHex, toAscii, toUtf8 } from "@cosmjs/encoding"; -import { getSubtle, pbkdf2Sha512, pbkdf2Sha512Noble, pbkdf2Sha512Subtle } from "./pbkdf2"; +import { + getCryptoModule, + getSubtle, + pbkdf2Sha512, + pbkdf2Sha512Crypto, + pbkdf2Sha512Noble, + pbkdf2Sha512Subtle, +} from "./pbkdf2"; interface TestVector { secret: Uint8Array; @@ -139,6 +146,25 @@ describe("pbkdf2", () => { }); }); + describe("pbkdf2Sha512Crypto", () => { + it("works", async () => { + const crypto = await getCryptoModule(); + if (!crypto) pending("The crypto module is not available in this environment"); + + { + const { secret, salt, iterations, keylen, expected } = botanTest; + const hash = await pbkdf2Sha512Crypto(crypto, secret, salt, iterations, keylen); + expect(hash).toEqual(expected); + } + + for (const [index, test] of brycxTests.entries()) { + const { secret, salt, iterations, keylen, expected } = test; + const hash = await pbkdf2Sha512Crypto(crypto, secret, salt, iterations, keylen); + expect(hash).withContext(`brycx tests index ${index}`).toEqual(expected); + } + }); + }); + describe("pbkdf2Sha512Noble", () => { it("works", async () => { { diff --git a/packages/crypto/src/pbkdf2.ts b/packages/crypto/src/pbkdf2.ts index fb60f836..38ad1ab3 100644 --- a/packages/crypto/src/pbkdf2.ts +++ b/packages/crypto/src/pbkdf2.ts @@ -2,6 +2,27 @@ import { assert } from "@cosmjs/utils"; import { pbkdf2Async as noblePbkdf2Async } from "@noble/hashes/pbkdf2"; import { sha512 as nobleSha512 } from "@noble/hashes/sha512"; +/** + * Returns the Node.js crypto module when available and `undefined` + * otherwise. + * + * Detects an unimplemented fallback module from Webpack 5 and returns + * `undefined` in that case. + */ +export async function getCryptoModule(): Promise { + try { + const crypto = await import("crypto"); + // We get `Object{default: Object{}}` as a fallback when using + // `crypto: false` in Webpack 5, which we interprete as unavailable. + if (typeof crypto === "object" && Object.keys(crypto).length <= 1) { + return undefined; + } + return crypto; + } catch { + return undefined; + } +} + export async function getSubtle(): Promise { // From Node.js 15 onwards, webcrypto is available in globalThis. // In version 15 and 16 this was stored under the webcrypto key. @@ -47,6 +68,29 @@ export async function pbkdf2Sha512Subtle( ); } +export async function pbkdf2Sha512Crypto( + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + crypto: any, + secret: Uint8Array, + salt: Uint8Array, + iterations: number, + keylen: number, +): Promise { + assert(crypto, "Argument crypto is falsy"); + assert(typeof crypto === "object", "Argument crypto is not of type object"); + assert(typeof crypto.pbkdf2 === "function", "crypto.pbkdf2 is not a function"); + + return new Promise((resolve, reject) => { + crypto.pbkdf2(secret, salt, iterations, keylen, "sha512", (error: any, result: any) => { + if (error) { + reject(error); + } else { + resolve(Uint8Array.from(result)); + } + }); + }); +} + export async function pbkdf2Sha512Noble( secret: Uint8Array, salt: Uint8Array, @@ -69,6 +113,11 @@ export async function pbkdf2Sha512( if (subtle) { return pbkdf2Sha512Subtle(subtle, secret, salt, iterations, keylen); } else { - return pbkdf2Sha512Noble(secret, salt, iterations, keylen); + const crypto = await getCryptoModule(); + if (crypto) { + return pbkdf2Sha512Crypto(crypto, secret, salt, iterations, keylen); + } else { + return pbkdf2Sha512Noble(secret, salt, iterations, keylen); + } } } From f5e9bb13f26f03a90dfac662226f683c41f5185e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 20 Dec 2022 14:13:38 +0100 Subject: [PATCH 2/2] Improve readability crypro -> nodeCrypto --- packages/crypto/src/pbkdf2.spec.ts | 14 +++++++------- packages/crypto/src/pbkdf2.ts | 30 +++++++++++++++++------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/crypto/src/pbkdf2.spec.ts b/packages/crypto/src/pbkdf2.spec.ts index d76b48c4..a4d969b8 100644 --- a/packages/crypto/src/pbkdf2.spec.ts +++ b/packages/crypto/src/pbkdf2.spec.ts @@ -1,11 +1,11 @@ import { fromHex, toAscii, toUtf8 } from "@cosmjs/encoding"; import { - getCryptoModule, + getNodeCrypto, getSubtle, pbkdf2Sha512, - pbkdf2Sha512Crypto, pbkdf2Sha512Noble, + pbkdf2Sha512NodeCrypto, pbkdf2Sha512Subtle, } from "./pbkdf2"; @@ -146,20 +146,20 @@ describe("pbkdf2", () => { }); }); - describe("pbkdf2Sha512Crypto", () => { + describe("pbkdf2Sha512NodeCrypto", () => { it("works", async () => { - const crypto = await getCryptoModule(); - if (!crypto) pending("The crypto module is not available in this environment"); + const nodeCrypto = await getNodeCrypto(); + if (!nodeCrypto) pending("The crypto module is not available in this environment"); { const { secret, salt, iterations, keylen, expected } = botanTest; - const hash = await pbkdf2Sha512Crypto(crypto, secret, salt, iterations, keylen); + const hash = await pbkdf2Sha512NodeCrypto(nodeCrypto, secret, salt, iterations, keylen); expect(hash).toEqual(expected); } for (const [index, test] of brycxTests.entries()) { const { secret, salt, iterations, keylen, expected } = test; - const hash = await pbkdf2Sha512Crypto(crypto, secret, salt, iterations, keylen); + const hash = await pbkdf2Sha512NodeCrypto(nodeCrypto, secret, salt, iterations, keylen); expect(hash).withContext(`brycx tests index ${index}`).toEqual(expected); } }); diff --git a/packages/crypto/src/pbkdf2.ts b/packages/crypto/src/pbkdf2.ts index 38ad1ab3..5176346d 100644 --- a/packages/crypto/src/pbkdf2.ts +++ b/packages/crypto/src/pbkdf2.ts @@ -9,15 +9,15 @@ import { sha512 as nobleSha512 } from "@noble/hashes/sha512"; * Detects an unimplemented fallback module from Webpack 5 and returns * `undefined` in that case. */ -export async function getCryptoModule(): Promise { +export async function getNodeCrypto(): Promise { try { - const crypto = await import("crypto"); + const nodeCrypto = await import("crypto"); // We get `Object{default: Object{}}` as a fallback when using // `crypto: false` in Webpack 5, which we interprete as unavailable. - if (typeof crypto === "object" && Object.keys(crypto).length <= 1) { + if (typeof nodeCrypto === "object" && Object.keys(nodeCrypto).length <= 1) { return undefined; } - return crypto; + return nodeCrypto; } catch { return undefined; } @@ -68,20 +68,24 @@ export async function pbkdf2Sha512Subtle( ); } -export async function pbkdf2Sha512Crypto( +/** + * Implements pbkdf2-sha512 using the Node.js crypro module (`import "crypto"`). + * This does not use subtle from [Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto). + */ +export async function pbkdf2Sha512NodeCrypto( // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - crypto: any, + nodeCrypto: any, secret: Uint8Array, salt: Uint8Array, iterations: number, keylen: number, ): Promise { - assert(crypto, "Argument crypto is falsy"); - assert(typeof crypto === "object", "Argument crypto is not of type object"); - assert(typeof crypto.pbkdf2 === "function", "crypto.pbkdf2 is not a function"); + assert(nodeCrypto, "Argument nodeCrypto is falsy"); + assert(typeof nodeCrypto === "object", "Argument nodeCrypto is not of type object"); + assert(typeof nodeCrypto.pbkdf2 === "function", "nodeCrypto.pbkdf2 is not a function"); return new Promise((resolve, reject) => { - crypto.pbkdf2(secret, salt, iterations, keylen, "sha512", (error: any, result: any) => { + nodeCrypto.pbkdf2(secret, salt, iterations, keylen, "sha512", (error: any, result: any) => { if (error) { reject(error); } else { @@ -113,9 +117,9 @@ export async function pbkdf2Sha512( if (subtle) { return pbkdf2Sha512Subtle(subtle, secret, salt, iterations, keylen); } else { - const crypto = await getCryptoModule(); - if (crypto) { - return pbkdf2Sha512Crypto(crypto, secret, salt, iterations, keylen); + const nodeCrypto = await getNodeCrypto(); + if (nodeCrypto) { + return pbkdf2Sha512NodeCrypto(nodeCrypto, secret, salt, iterations, keylen); } else { return pbkdf2Sha512Noble(secret, salt, iterations, keylen); }