From 4baf7101684b5d7524234823cb2a9b4c638e6260 Mon Sep 17 00:00:00 2001 From: Sergey Kintsel Date: Sat, 19 Oct 2024 11:26:45 +0100 Subject: [PATCH] Add minimal anti-bruteforce measures --- packages/crypto/package.json | 3 ++- packages/crypto/src/AES.ts | 25 ++++++++++++++++++++++++- pnpm-lock.yaml | 7 +++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/crypto/package.json b/packages/crypto/package.json index df55e8941b..c79fdb104a 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -58,6 +58,7 @@ ] }, "dependencies": { - "@taquito/utils": "^20.0.1" + "@taquito/utils": "^20.0.1", + "date-fns": "^4.1.0" } } diff --git a/packages/crypto/src/AES.ts b/packages/crypto/src/AES.ts index 3df50c73b9..899b307d6a 100644 --- a/packages/crypto/src/AES.ts +++ b/packages/crypto/src/AES.ts @@ -1,4 +1,5 @@ import { buf2hex, hex2Bytes } from "@taquito/utils"; +import { differenceInMinutes } from "date-fns"; import { AES_MODE } from "./AES_MODE"; import { derivePasswordBasedKeyV1, derivePasswordBasedKeyV2 } from "./KDF"; @@ -33,6 +34,9 @@ export const encrypt = async (data: string, password: string): Promise => { const { iv, salt, data: encrypted } = data; try { + if (getAttemptsCount() >= 3) { + const minutesSinceLastAttempt = differenceInMinutes( + new Date(), + new Date(localStorage.getItem("failedDecryptTime")!) + ); + if (minutesSinceLastAttempt < 5) { + throw new Error(TOO_MANY_ATTEMPTS_ERROR); + } + } const derivedKey = mode === "V2" ? await derivePasswordBasedKeyV2(password, hex2Bytes(salt)) @@ -52,8 +65,18 @@ export const decrypt = async ( derivedKey, hex2Bytes(encrypted) ); + setAttemptsCount(0); + localStorage.removeItem("failedDecryptTime"); return Buffer.from(decrypted).toString("utf-8"); - } catch (_) { + } catch (err: any) { + if (err?.message === TOO_MANY_ATTEMPTS_ERROR) { + throw err; + } + setAttemptsCount(getAttemptsCount() + 1); + localStorage.setItem("failedDecryptTime", new Date().toISOString()); throw new Error("Error decrypting data: Invalid password"); } }; + +const getAttemptsCount = () => Number(localStorage.getItem("passwordAttempts") || 0); +const setAttemptsCount = (count: number) => localStorage.setItem("passwordAttempts", String(count)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78404a708a..ff02a6dffb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,7 +14,7 @@ importers: devDependencies: jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.4)) + version: 29.7.0(@types/node@22.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1149,6 +1149,9 @@ importers: '@taquito/utils': specifier: ^20.0.1 version: 20.0.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 devDependencies: '@babel/core': specifier: ^7.25.7 @@ -1185,7 +1188,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)) + version: 29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.4)) madge: specifier: ^8.0.0 version: 8.0.0(typescript@5.5.4)