From b54b26b86437e6edd628268ba1d5d8c94a3caa26 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 18 Aug 2019 17:01:07 -0500 Subject: [PATCH 01/27] add argon2-browser dependency --- package-lock.json | 344 ++++++++++++++++++++++++---------------------- package.json | 5 +- webpack.config.js | 73 +++++----- 3 files changed, 221 insertions(+), 201 deletions(-) diff --git a/package-lock.json b/package-lock.json index aee9f5d30..0c1913635 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/argon2-browser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.1.0.tgz", + "integrity": "sha512-/WJvO4mFNefeZ6ordF+iVwNG7bEJbYhE/Ld5GlXXSuw5CNA2d2bMcJ1h0sTgO1wmKGvlI+50rhHxREywjiof3g==", + "dev": true + }, "@types/chrome": { "version": "0.0.86", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.86.tgz", @@ -266,15 +272,9 @@ "dev": true }, "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "dev": true }, "ajv": { @@ -343,6 +343,11 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, + "argon2-browser": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.7.1.tgz", + "integrity": "sha512-VgKVM0FBCsc2eH9C+4ZRiMNnDS+q8U+4QhV0VISejqSWIvB132WY7mVlhlkjKyQJUqX2Bhd955hgJNBEh7abTQ==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -558,9 +563,15 @@ } }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "base64-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64-loader/-/base64-loader-1.0.0.tgz", + "integrity": "sha1-5TC62I6QbdKh+tCvLZ5oP6i9kqg=", "dev": true }, "big.js": { @@ -739,31 +750,38 @@ "dev": true }, "cacache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", - "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", "dev": true, "requires": { - "bluebird": "^3.5.3", + "bluebird": "^3.5.5", "chownr": "^1.1.1", "figgy-pudding": "^3.5.1", - "glob": "^7.1.3", + "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", + "rimraf": "^2.6.3", "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" }, "dependencies": { + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true }, "lru-cache": { @@ -832,9 +850,9 @@ } }, "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", "dev": true }, "chrome-trace-event": { @@ -933,9 +951,9 @@ "dev": true }, "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", "dev": true }, "commondir": { @@ -1371,9 +1389,9 @@ } }, "elliptic": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", - "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", + "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -1483,9 +1501,9 @@ } }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { @@ -1690,30 +1708,6 @@ "commondir": "^1.0.1", "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } } }, "findup-sync": { @@ -2383,9 +2377,9 @@ "dev": true }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -2624,10 +2618,10 @@ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true }, "inflight": { @@ -2944,6 +2938,24 @@ "yallist": "^2.1.2" } }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "mamacro": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", @@ -3120,9 +3132,9 @@ } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -3216,9 +3228,9 @@ "dev": true }, "node-libs-browser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", - "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", "dev": true, "requires": { "assert": "^1.1.1", @@ -3231,7 +3243,7 @@ "events": "^3.0.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", - "path-browserify": "0.0.0", + "path-browserify": "0.0.1", "process": "^0.11.10", "punycode": "^1.2.4", "querystring-es3": "^0.2.0", @@ -3243,7 +3255,7 @@ "tty-browserify": "0.0.0", "url": "^0.11.0", "util": "^0.11.0", - "vm-browserify": "0.0.4" + "vm-browserify": "^1.0.1" }, "dependencies": { "punycode": { @@ -3494,9 +3506,9 @@ "dev": true }, "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", "dev": true }, "path-dirname": { @@ -3542,6 +3554,12 @@ "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", "dev": true }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -3987,12 +4005,12 @@ "dev": true }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "ripemd160": { @@ -4074,9 +4092,9 @@ "dev": true }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -4266,9 +4284,9 @@ } }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -4460,14 +4478,14 @@ "dev": true }, "terser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.0.tgz", - "integrity": "sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.1.4.tgz", + "integrity": "sha512-+ZwXJvdSwbd60jG0Illav0F06GDJF0R4ydZ21Q3wGAFKoBGyJGo34F63vzJHgvYxc1ukOtIjvwEvl9MkjzM6Pg==", "dev": true, "requires": { - "commander": "^2.19.0", + "commander": "^2.20.0", "source-map": "~0.6.1", - "source-map-support": "~0.5.10" + "source-map-support": "~0.5.12" }, "dependencies": { "source-map": { @@ -4479,20 +4497,19 @@ } }, "terser-webpack-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", - "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", + "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", "dev": true, "requires": { - "cacache": "^11.3.2", - "find-cache-dir": "^2.0.0", + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", - "loader-utils": "^1.2.3", "schema-utils": "^1.0.0", "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "terser": "^4.0.0", - "webpack-sources": "^1.3.0", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" }, "dependencies": { @@ -4520,9 +4537,9 @@ "integrity": "sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0=" }, "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", "dev": true, "requires": { "setimmediate": "^1.0.4" @@ -4641,9 +4658,9 @@ } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, "tty-browserify": { @@ -4665,38 +4682,15 @@ "dev": true }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "uniq": { @@ -4715,9 +4709,9 @@ } }, "unique-slug": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", - "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", "dev": true, "requires": { "imurmurhash": "^0.1.4" @@ -4846,13 +4840,10 @@ "dev": true }, "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true }, "vue": { "version": "2.6.10", @@ -4939,35 +4930,54 @@ } }, "webpack": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.33.0.tgz", - "integrity": "sha512-ggWMb0B2QUuYso6FPZKUohOgfm+Z0sVFs8WwWuSH1IAvkWs428VDNmOlAxvHGTB9Dm/qOB/qtE5cRx5y01clxw==", + "version": "4.39.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.2.tgz", + "integrity": "sha512-AKgTfz3xPSsEibH00JfZ9sHXGUwIQ6eZ9tLN8+VLzachk1Cw2LVmy+4R7ZiwTa9cZZ15tzySjeMui/UnSCAZhA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-module-context": "1.8.5", "@webassemblyjs/wasm-edit": "1.8.5", "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.0.5", - "acorn-dynamic-import": "^4.0.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", - "tapable": "^1.1.0", - "terser-webpack-plugin": "^1.1.0", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.1", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true + } } }, "webpack-cli": { @@ -5021,9 +5031,9 @@ } }, "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "dev": true, "requires": { "source-list-map": "^2.0.0", @@ -5125,9 +5135,9 @@ "dev": true }, "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, "y18n": { diff --git a/package.json b/package.json index 80b3a2329..89d96b9c0 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,11 @@ }, "homepage": "https://github.com/Authenticator-Extension/Authenticator#readme", "devDependencies": { + "@types/argon2-browser": "^1.1.0", "@types/chrome": "^0.0.86", "@types/crypto-js": "^3.1.43", "@types/jssha": "2.0.0", + "base64-loader": "^1.0.0", "css-loader": "^2.1.1", "fork-ts-checker-webpack-plugin": "^1.3.5", "prettier": "1.18.2", @@ -32,11 +34,12 @@ "vue-loader": "^15.7.0", "vue-svg-loader": "^0.12.0", "vue-template-compiler": "^2.6.10", - "webpack": "^4.33.0", + "webpack": "^4.38.0", "webpack-cli": "^3.3.3", "webpack-merge": "^4.2.1" }, "dependencies": { + "argon2-browser": "^1.7.1", "crypto-js": "^3.1.9-1", "jssha": "^2.3.1", "qrcode-generator": "^1.4.3", diff --git a/webpack.config.js b/webpack.config.js index 450109374..02953a937 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,25 +1,35 @@ -const path = require('path'); -const VueLoaderPlugin = require('vue-loader/lib/plugin'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const path = require("path"); +const VueLoaderPlugin = require("vue-loader/lib/plugin"); +const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); module.exports = { - mode: 'development', - devtool: 'source-map', + mode: "development", + devtool: "source-map", entry: { - background: './src/background.ts', - content: './src/content.ts', - popup: './src/popup.ts', - import: './src/import.ts', - qr: './src/qr.ts', - qrdebug: './src/qrdebug.ts', - test: './src/test/test.ts' + background: "./src/background.ts", + content: "./src/content.ts", + popup: "./src/popup.ts", + import: "./src/import.ts", + qr: "./src/qr.ts", + qrdebug: "./src/qrdebug.ts", + test: "./src/test/test.ts" + }, + // For argon2-browser + node: { + fs: "empty" }, module: { rules: [ + { + // argon2-browser overrides + test: /\.wasm$/, + loader: "base64-loader", + type: "javascript/auto" + }, { test: /\.tsx?$/, - loader: 'ts-loader', - options: { + loader: "ts-loader", + options: { appendTsSuffixTo: [/\.vue$/], transpileOnly: true }, @@ -27,38 +37,35 @@ module.exports = { }, { test: /\.vue$/, - loader: 'vue-loader' + loader: "vue-loader" }, { test: /\.svg$/, - loader: 'vue-svg-loader' + loader: "vue-svg-loader" } ] }, plugins: [ new VueLoaderPlugin(), - new ForkTsCheckerWebpackPlugin( - { - vue: true - } - ) + new ForkTsCheckerWebpackPlugin({ + vue: true + }) ], resolve: { extensions: [ - '.mjs', - '.js', - '.jsx', - '.vue', - '.json', - '.wasm', - '.ts', - '.tsx' + ".mjs", + ".js", + ".jsx", + ".vue", + ".json", + ".wasm", + ".ts", + ".tsx" ], - modules: [ - 'node_modules' - ] + modules: ["node_modules"] }, output: { - path: path.resolve(__dirname, 'dist') + path: path.resolve(__dirname, "dist"), + publicPath: "dist/" } }; From 49e78c6ffc272e701bbf2273d21fed8c52ef6538 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 18 Aug 2019 17:20:59 -0500 Subject: [PATCH 02/27] wrapper argon2-browser --- src/models/argon.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/models/argon.ts diff --git a/src/models/argon.ts b/src/models/argon.ts new file mode 100644 index 000000000..1fbf43447 --- /dev/null +++ b/src/models/argon.ts @@ -0,0 +1,29 @@ +import * as argon2 from "argon2-browser"; + +export class argon { + static async hash(value: string) { + const salt = window.crypto.getRandomValues(new Uint8Array(16)); + const hash = await argon2.hash({ + // TODO: Set stronger defaults + pass: value, + salt + }); + + return hash.encoded; + } + + static compareHash(hash: string, value: string) { + return new Promise((resolve: (value: boolean) => void) => { + argon2 + .verify({ + pass: value, + encoded: hash + }) + .then(() => resolve(true)) + .catch((e: { message: string; code: number }) => { + console.error("Error decoding hash", e); + resolve(false); + }); + }); + } +} From cb77e07665adda4c31b75063929a4e5512f7228e Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 18 Aug 2019 18:47:47 -0500 Subject: [PATCH 03/27] argon2 for new accounts and imports --- src/background.ts | 5 ++-- src/components/Import/FileImport.vue | 2 +- src/components/Import/TextImport.vue | 2 +- src/components/Popup/AddAccountPage.vue | 4 +-- src/definitions/otp.d.ts | 4 +-- src/import.ts | 5 ++-- src/models/encryption.ts | 16 +++++++++-- src/models/otp.ts | 7 +++-- src/models/storage.ts | 37 +++++++++++++++---------- webpack.config.js | 2 +- 10 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/background.ts b/src/background.ts index f52c2269f..8b6a200ee 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,3 @@ -import * as CryptoJS from "crypto-js"; -// tslint:disable-next-line:ban-ts-ignore // @ts-ignore import QRCode from "qrcode-reader"; @@ -7,6 +5,7 @@ import { getCredentials } from "./models/credentials"; import { Encryption } from "./models/encryption"; import { EntryStorage, ManagedStorage } from "./models/storage"; import { Dropbox, Drive } from "./models/backup"; +import { argon } from "./models/argon"; let cachedPassphrase = ""; chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { @@ -167,7 +166,7 @@ async function getTotp(text: string) { chrome.tabs.sendMessage(id, { action: "secretqr", secret }); } else { const encryption = new Encryption(cachedPassphrase); - const hash = CryptoJS.MD5(secret).toString(); + const hash = await argon.hash(secret); if ( !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && diff --git a/src/components/Import/FileImport.vue b/src/components/Import/FileImport.vue index 5f52b01d8..02e80207b 100644 --- a/src/components/Import/FileImport.vue +++ b/src/components/Import/FileImport.vue @@ -56,7 +56,7 @@ export default Vue.extend({ try { importData = JSON.parse(reader.result as string); } catch (e) { - importData = getEntryDataFromOTPAuthPerLine( + importData = await getEntryDataFromOTPAuthPerLine( reader.result as string ); } diff --git a/src/components/Import/TextImport.vue b/src/components/Import/TextImport.vue index 67dab9e42..c98f71ad0 100644 --- a/src/components/Import/TextImport.vue +++ b/src/components/Import/TextImport.vue @@ -44,7 +44,7 @@ export default Vue.extend({ exportData = JSON.parse(this.importCode); } catch (error) { // Maybe one-otpauth-per line text - exportData = getEntryDataFromOTPAuthPerLine(this.importCode); + exportData = await getEntryDataFromOTPAuthPerLine(this.importCode); } try { diff --git a/src/components/Popup/AddAccountPage.vue b/src/components/Popup/AddAccountPage.vue index 2f56c0c83..70062bb05 100644 --- a/src/components/Popup/AddAccountPage.vue +++ b/src/components/Popup/AddAccountPage.vue @@ -31,7 +31,7 @@ import Vue from "vue"; import { mapState } from "vuex"; import { OTPType, OTPEntry } from "../../models/otp"; -import * as CryptoJS from "crypto-js"; +import { argon } from "../../models/argon"; export default Vue.extend({ data: function(): { @@ -97,7 +97,7 @@ export default Vue.extend({ issuer: this.newAccount.issuer, account: this.newAccount.account, encrypted: false, - hash: CryptoJS.MD5(this.newAccount.secret).toString(), + hash: await argon.hash(this.newAccount.secret), secret: this.newAccount.secret, counter: 0, period: this.newAccount.period diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts index bbabe91b5..9554eadd5 100644 --- a/src/definitions/otp.d.ts +++ b/src/definitions/otp.d.ts @@ -12,7 +12,7 @@ interface IOTPEntry { create(encryption: IEncryption): Promise; update(encryption: IEncryption): Promise; next(encryption: IEncryption): Promise; - applyEncryption(encryption: IEncryption): void; + applyEncryption(encryption: IEncryption): Promise; delete(): Promise; generate(): void; } @@ -20,7 +20,7 @@ interface IOTPEntry { interface IEncryption { getEncryptedSecret(entry: IOTPEntry): string; getEncryptedString(data: string): string; - getDecryptedSecret(entry: OTPStorage): string | null; + getDecryptedSecret(entry: OTPStorage): Promise; getEncryptionStatus(): boolean; updateEncryptionPassword(password: string): void; } diff --git a/src/import.ts b/src/import.ts index 2fd827d72..13ac4d51f 100644 --- a/src/import.ts +++ b/src/import.ts @@ -5,6 +5,7 @@ import { loadI18nMessages } from "./store/i18n"; import { Encryption } from "./models/encryption"; import { EntryStorage } from "./models/storage"; import * as CryptoJS from "crypto-js"; +import { argon } from "./models/argon"; async function init() { // i18n @@ -88,7 +89,7 @@ export function decryptBackupData( return decryptedbackupData; } -export function getEntryDataFromOTPAuthPerLine(importCode: string) { +export async function getEntryDataFromOTPAuthPerLine(importCode: string) { const lines = importCode.split("\n"); const exportData: { [hash: string]: OTPStorage } = {}; for (let item of lines) { @@ -153,7 +154,7 @@ export function getEntryDataFromOTPAuthPerLine(importCode: string) { ) { continue; } else { - const hash = CryptoJS.MD5(secret).toString(); + const hash = await argon.hash(secret); if ( !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && diff --git a/src/models/encryption.ts b/src/models/encryption.ts index 1ccc79472..02bf09f70 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -1,4 +1,5 @@ import * as CryptoJS from "crypto-js"; +import { argon } from "./argon"; export class Encryption implements IEncryption { private password: string; @@ -32,9 +33,14 @@ export class Encryption implements IEncryption { } } - getDecryptedSecret(entry: { secret: string; hash: string }): string | null { + async getDecryptedSecret(entry: { secret: string; hash: string }) { try { - if (entry.hash === CryptoJS.MD5(entry.secret).toString()) { + // Test hash of secret + if (entry.hash.startsWith("$argon2")) { + if (await argon.compareHash(entry.hash, entry.secret)) { + return entry.secret; + } + } else if (entry.hash === CryptoJS.MD5(entry.secret).toString()) { return entry.secret; } @@ -51,7 +57,11 @@ export class Encryption implements IEncryption { return null; } - if (entry.hash === CryptoJS.MD5(decryptedSecret).toString()) { + if (entry.hash.startsWith("$argon2")) { + if (await argon.compareHash(entry.hash, decryptedSecret)) { + return decryptedSecret; + } + } else if (entry.hash === CryptoJS.MD5(decryptedSecret).toString()) { return decryptedSecret; } diff --git a/src/models/otp.ts b/src/models/otp.ts index bfacd5e75..894eea001 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -67,10 +67,13 @@ export class OTPEntry implements IOTPEntry { return; } - applyEncryption(encryption: Encryption) { + async applyEncryption(encryption: Encryption) { const secret = this.encSecret ? this.encSecret : null; if (secret) { - this.secret = encryption.getDecryptedSecret({ hash: this.hash, secret }); + this.secret = await encryption.getDecryptedSecret({ + hash: this.hash, + secret + }); if (this.type !== OTPType.hotp && this.type !== OTPType.hhex) { this.generate(); } diff --git a/src/models/storage.ts b/src/models/storage.ts index 7e7a88730..ee1721001 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -1,7 +1,6 @@ -import * as CryptoJS from "crypto-js"; - import { Encryption } from "./encryption"; import { OTPEntry, OTPType } from "./otp"; +import { argon } from "./argon"; export class BrowserStorage { private static async getStorageLocation() { @@ -185,7 +184,7 @@ export class EntryStorage { reject: (reason: Error) => void ) => { try { - BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + BrowserStorage.get(async (_data: { [hash: string]: OTPStorage }) => { for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { delete _data[hash]; @@ -194,7 +193,7 @@ export class EntryStorage { if (!encrypted) { // decrypt the data to export if (_data[hash].encrypted) { - const decryptedSecret = encryption.getDecryptedSecret( + const decryptedSecret = await encryption.getDecryptedSecret( _data[hash] ); if ( @@ -226,7 +225,7 @@ export class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + BrowserStorage.get(async (_data: { [hash: string]: OTPStorage }) => { for (const hash of Object.keys(data)) { // never trust data import from user // we do not support encrypted data import any longer @@ -284,9 +283,14 @@ export class EntryStorage { data[hash].type = OTPType[OTPType.hhex]; } - const _hash = CryptoJS.MD5(data[hash].secret).toString(); - // not a valid hash - if (!/^[0-9a-f]{32}$/.test(hash)) { + const _hash = await argon.hash(data[hash].secret); + + // not a valid / old hash + if ( + !/^\$argon2(?:d|i|di)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + hash + ) + ) { data[_hash] = data[hash]; data[_hash].hash = _hash; delete data[hash]; @@ -444,14 +448,19 @@ export class EntryStorage { counter: entryData.counter, period }); - entry.applyEncryption(encryption); + await entry.applyEncryption(encryption); data.push(entry); - if (entry.secret !== null && !/^[0-9a-f]{32}$/.test(hash)) { - const _hash = CryptoJS.MD5(entry.secret).toString(); - if (hash !== _hash) { - console.warn("Invalid hash:", entry); - } + if ( + entry.secret !== null && + !( + /^[0-9a-f]{32}$/.test(hash) || + /^\$argon2(?:d|i|di)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + hash + ) + ) + ) { + console.warn("Invalid hash:", entry); } } diff --git a/webpack.config.js b/webpack.config.js index 02953a937..aaf0d809f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -66,6 +66,6 @@ module.exports = { }, output: { path: path.resolve(__dirname, "dist"), - publicPath: "dist/" + publicPath: "/dist/" } }; From d6ea8c58673d87a628ef09e44fbe8878952f9759 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 18 Aug 2019 18:58:57 -0500 Subject: [PATCH 04/27] forgot an await --- src/store/Accounts.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index dfff8d064..885c86521 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -116,12 +116,6 @@ export class Accounts implements IModule { }, loadCodes(state: AccountsState, newCodes: IOTPEntry[]) { state.entries = newCodes; - - if (state.encryption.getEncryptionStatus()) { - for (const entry of state.entries) { - entry.applyEncryption(state.encryption); - } - } }, moveCode(state: AccountsState, opts: { from: number; to: number }) { state.entries.splice( @@ -193,10 +187,16 @@ export class Accounts implements IModule { localStorage.removeItem("encodedPhrase"); }, updateEntries: async (state: ActionContext) => { - state.commit( - "loadCodes", - await this.getEntries(state.state.encryption as Encryption) - ); + const entries = await this.getEntries(state.state + .encryption as Encryption); + + if (state.state.encryption.getEncryptionStatus()) { + for (const entry of entries) { + await entry.applyEncryption(state.state.encryption as Encryption); + } + } + + state.commit("loadCodes", entries); state.commit("updateCodes"); state.commit( "updateExport", From 193eac12d0a122dfba58cdc204807ca3a6d04e14 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Tue, 20 Aug 2019 19:22:51 -0500 Subject: [PATCH 05/27] migrate --- src/components/Popup/LoadingPage.vue | 10 ++++++++ src/components/Popup/PageHandler.vue | 6 +++-- src/definitions/otp.d.ts | 1 + src/models/encryption.ts | 34 +++++++++++----------------- src/models/otp.ts | 18 +++++++++++++++ src/store/Accounts.ts | 16 +++++++++++++ 6 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 src/components/Popup/LoadingPage.vue diff --git a/src/components/Popup/LoadingPage.vue b/src/components/Popup/LoadingPage.vue new file mode 100644 index 000000000..ebcea1544 --- /dev/null +++ b/src/components/Popup/LoadingPage.vue @@ -0,0 +1,10 @@ + + diff --git a/src/components/Popup/PageHandler.vue b/src/components/Popup/PageHandler.vue index c2cdfad9f..65ced06f6 100644 --- a/src/components/Popup/PageHandler.vue +++ b/src/components/Popup/PageHandler.vue @@ -2,7 +2,7 @@
@@ -25,6 +25,7 @@ import DropboxPage from "./DropboxPage.vue"; import DrivePage from "./DrivePage.vue"; import StorageSyncConfPage from "./StorageSyncConfPage.vue"; import PrefrencesPage from "./PrefrencesPage.vue"; +import LoadingPage from "./LoadingPage.vue"; export default Vue.extend({ computed: { @@ -48,7 +49,8 @@ export default Vue.extend({ DropboxPage, DrivePage, PrefrencesPage, - StorageSyncConfPage + StorageSyncConfPage, + LoadingPage } }); diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts index 9554eadd5..f85882e0a 100644 --- a/src/definitions/otp.d.ts +++ b/src/definitions/otp.d.ts @@ -15,6 +15,7 @@ interface IOTPEntry { applyEncryption(encryption: IEncryption): Promise; delete(): Promise; generate(): void; + rehash(encryption: IEncryption): Promise; } interface IEncryption { diff --git a/src/models/encryption.ts b/src/models/encryption.ts index 02bf09f70..503edd997 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -35,16 +35,7 @@ export class Encryption implements IEncryption { async getDecryptedSecret(entry: { secret: string; hash: string }) { try { - // Test hash of secret - if (entry.hash.startsWith("$argon2")) { - if (await argon.compareHash(entry.hash, entry.secret)) { - return entry.secret; - } - } else if (entry.hash === CryptoJS.MD5(entry.secret).toString()) { - return entry.secret; - } - - let decryptedSecret = CryptoJS.AES.decrypt( + const decryptedSecret = CryptoJS.AES.decrypt( entry.secret, this.password ).toString(CryptoJS.enc.Utf8); @@ -57,16 +48,6 @@ export class Encryption implements IEncryption { return null; } - if (entry.hash.startsWith("$argon2")) { - if (await argon.compareHash(entry.hash, decryptedSecret)) { - return decryptedSecret; - } - } else if (entry.hash === CryptoJS.MD5(decryptedSecret).toString()) { - return decryptedSecret; - } - - decryptedSecret = decryptedSecret.replace(/ /g, ""); - if ( !/^[a-z2-7]+=*$/i.test(decryptedSecret) && !/^[0-9a-f]+$/i.test(decryptedSecret) && @@ -77,8 +58,19 @@ export class Encryption implements IEncryption { return null; } + if (entry.hash.startsWith("$argon2")) { + if (await argon.compareHash(entry.hash, decryptedSecret)) { + return decryptedSecret; + } + } else if (entry.hash === CryptoJS.MD5(decryptedSecret).toString()) { + return decryptedSecret; + } + console.warn( - `Account ${entry.hash} may have secret ${decryptedSecret}, but hash did not match.` + `Account ${entry.hash} may have secret ${decryptedSecret.replace( + / /g, + "" + )}, but hash did not match.` ); return null; } catch (error) { diff --git a/src/models/otp.ts b/src/models/otp.ts index 894eea001..5b704b960 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -1,6 +1,8 @@ import { Encryption } from "./encryption"; import { KeyUtilities } from "./key-utilities"; import { EntryStorage } from "./storage"; +import { argon } from "./argon"; +import * as CryptoJS from "crypto-js"; export enum OTPType { totp = 1, @@ -98,6 +100,22 @@ export class OTPEntry implements IOTPEntry { return; } + async rehash(encryption: Encryption) { + const secret = this.secret; + if (!secret) { + return; + } + + if (this.hash !== CryptoJS.MD5(secret).toString()) { + return; + } + + const newHash = await argon.hash(secret); + await this.delete(); + this.hash = newHash; + await this.create(encryption); + } + generate() { if (!this.secret && !this.encSecret) { this.code = "Invalid"; diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index 885c86521..7525307cc 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -152,8 +152,24 @@ export class Accounts implements IModule { return; } + state.commit("currentView/changeView", "LoadingPage", { root: true }); + state.state.encryption.updateEncryptionPassword(password); await state.dispatch("updateEntries"); + + if (state.state.encryption.getEncryptionStatus()) { + for (const entry of state.state.entries) { + if ( + !/^\$argon2(?:d|i|di)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + entry.hash + ) + ) { + await entry.rehash(state.state.encryption); + } + } + await state.dispatch("updateEntries"); + } + state.commit("style/hideInfo", null, { root: true }); if (!state.getters.currentlyEncrypted) { From ba2adb625a883e3c14164da77b13fe3a10e44b5b Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 23 Aug 2019 20:00:22 -0500 Subject: [PATCH 06/27] fix performance issue --- src/models/storage.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/models/storage.ts b/src/models/storage.ts index ee1721001..b00b08eda 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -448,7 +448,9 @@ export class EntryStorage { counter: entryData.counter, period }); - await entry.applyEncryption(encryption); + if (encryption.getEncryptionStatus()) { + await entry.applyEncryption(encryption); + } data.push(entry); if ( From b00a276f099edce3b5bce234b332059663fb5499 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 23 Aug 2019 20:00:34 -0500 Subject: [PATCH 07/27] update argon2 and types --- package-lock.json | 144 ++-------------------------------------------- package.json | 5 +- 2 files changed, 8 insertions(+), 141 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c1913635..462fbc13a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@types/argon2-browser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.1.0.tgz", - "integrity": "sha512-/WJvO4mFNefeZ6ordF+iVwNG7bEJbYhE/Ld5GlXXSuw5CNA2d2bMcJ1h0sTgO1wmKGvlI+50rhHxREywjiof3g==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.6.0.tgz", + "integrity": "sha512-218mScZ+/w4Quuo9cZnVO9Q3u9yhIBMnQz7NZDbSaFLDpA00aj6caCPsRvkcf3iJ8BjX1VqhE5335Of+2doMtA==", "dev": true }, "@types/chrome": { @@ -344,9 +344,9 @@ "dev": true }, "argon2-browser": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.7.1.tgz", - "integrity": "sha512-VgKVM0FBCsc2eH9C+4ZRiMNnDS+q8U+4QhV0VISejqSWIvB132WY7mVlhlkjKyQJUqX2Bhd955hgJNBEh7abTQ==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.8.0.tgz", + "integrity": "sha512-5jDXBnzI66PPkSAivtmF0kfCOhSwXJEk/8rYnGaDEy/hgFlp2EsP8qngmPNbttPalBI0vTAN2kq0zRzVE/WFeA==" }, "argparse": { "version": "1.0.10", @@ -1114,39 +1114,6 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" }, - "css-loader": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.1.tgz", - "integrity": "sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==", - "dev": true, - "requires": { - "camelcase": "^5.2.0", - "icss-utils": "^4.1.0", - "loader-utils": "^1.2.3", - "normalize-path": "^3.0.0", - "postcss": "^7.0.14", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^2.0.6", - "postcss-modules-scope": "^2.1.0", - "postcss-modules-values": "^2.0.0", - "postcss-value-parser": "^3.3.0", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, "css-select": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", @@ -2569,21 +2536,6 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, - "icss-utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.0.tgz", - "integrity": "sha512-3DEun4VOeMvSczifM3F2cKQrDQ5Pj6WKhkOq6HD4QTnDUAq8MQRxy5TX6Sy1iY6WPBe4gQ3p5vTECjbIkglkkQ==", - "dev": true, - "requires": { - "postcss": "^7.0.14" - } - }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", @@ -3670,84 +3622,6 @@ } } }, - "postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "dev": true, - "requires": { - "postcss": "^7.0.5" - } - }, - "postcss-modules-local-by-default": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz", - "integrity": "sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA==", - "dev": true, - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0", - "postcss-value-parser": "^3.3.1" - }, - "dependencies": { - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-modules-scope": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz", - "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==", - "dev": true, - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - }, - "dependencies": { - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-modules-values": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz", - "integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==", - "dev": true, - "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^7.0.6" - } - }, "postcss-selector-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", @@ -3759,12 +3633,6 @@ "uniq": "^1.0.1" } }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, "prettier": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", diff --git a/package.json b/package.json index 89d96b9c0..656de3a86 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,11 @@ }, "homepage": "https://github.com/Authenticator-Extension/Authenticator#readme", "devDependencies": { - "@types/argon2-browser": "^1.1.0", + "@types/argon2-browser": "^1.6.0", "@types/chrome": "^0.0.86", "@types/crypto-js": "^3.1.43", "@types/jssha": "2.0.0", "base64-loader": "^1.0.0", - "css-loader": "^2.1.1", "fork-ts-checker-webpack-plugin": "^1.3.5", "prettier": "1.18.2", "sass": "^1.21.0", @@ -39,7 +38,7 @@ "webpack-merge": "^4.2.1" }, "dependencies": { - "argon2-browser": "^1.7.1", + "argon2-browser": "^1.8.0", "crypto-js": "^3.1.9-1", "jssha": "^2.3.1", "qrcode-generator": "^1.4.3", From db657f7d4d9a0c9ac9d666f656145cfc03e04cd9 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Mon, 26 Aug 2019 16:55:52 -0500 Subject: [PATCH 08/27] - remove the need for encryption where it wasn't needed - Optimize adding & deleting (using state instead of reloading and checking the hash again) - Use 16 KiB memory + Argon2di --- src/components/Popup/AddAccountPage.vue | 29 +++--- src/components/Popup/EntryComponent.vue | 4 +- src/components/Popup/MainBody.vue | 5 +- src/definitions/otp.d.ts | 7 +- src/import.ts | 10 +- src/models/argon.ts | 6 +- src/models/backup.ts | 4 +- src/models/encryption.ts | 23 +---- src/models/otp.ts | 46 ++++++---- src/models/storage.ts | 116 ++++++++++++++---------- src/popup.ts | 17 +++- src/store/Accounts.ts | 68 ++++++++++---- 12 files changed, 203 insertions(+), 132 deletions(-) diff --git a/src/components/Popup/AddAccountPage.vue b/src/components/Popup/AddAccountPage.vue index 70062bb05..898f65b99 100644 --- a/src/components/Popup/AddAccountPage.vue +++ b/src/components/Popup/AddAccountPage.vue @@ -91,20 +91,23 @@ export default Vue.extend({ this.newAccount.period = undefined; } - const entry = new OTPEntry({ - type, - index: 0, - issuer: this.newAccount.issuer, - account: this.newAccount.account, - encrypted: false, - hash: await argon.hash(this.newAccount.secret), - secret: this.newAccount.secret, - counter: 0, - period: this.newAccount.period - }); + const entry = new OTPEntry( + { + type, + index: 0, + issuer: this.newAccount.issuer, + account: this.newAccount.account, + encrypted: false, + hash: await argon.hash(this.newAccount.secret), + secret: this.newAccount.secret, + counter: 0, + period: this.newAccount.period + }, + this.$store.state.accounts.encryption + ); - await entry.create(this.$store.state.accounts.encryption); - await this.$store.dispatch("accounts/updateEntries"); + await entry.create(); + await this.$store.dispatch("accounts/addCode", entry); this.$store.commit("style/hideInfo"); this.$store.commit("style/toggleEdit"); diff --git a/src/components/Popup/EntryComponent.vue b/src/components/Popup/EntryComponent.vue index 01381ead4..2a904a6f5 100644 --- a/src/components/Popup/EntryComponent.vue +++ b/src/components/Popup/EntryComponent.vue @@ -127,7 +127,7 @@ export default Vue.extend({ ) ) { await entry.delete(); - await this.$store.dispatch("accounts/updateEntries"); + await this.$store.dispatch("accounts/deleteCode", entry.hash); } return; }, @@ -141,7 +141,7 @@ export default Vue.extend({ return; } this.$store.commit("style/toggleHotpDisabled"); - await entry.next(this.$store.state.accounts.encryption); + await entry.next(); setTimeout(() => { this.$store.commit("style/toggleHotpDisabled"); }, 3000); diff --git a/src/components/Popup/MainBody.vue b/src/components/Popup/MainBody.vue index c997f5c01..1e9647086 100644 --- a/src/components/Popup/MainBody.vue +++ b/src/components/Popup/MainBody.vue @@ -114,10 +114,7 @@ export default Vue.extend({ from: dragIndex, to: dropIndex }); - await EntryStorage.set( - this.$store.state.accounts.encryption, - this.$store.state.accounts.entries - ); + await EntryStorage.set(this.$store.state.accounts.entries); } ); }, diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts index f85882e0a..c18ca65a2 100644 --- a/src/definitions/otp.d.ts +++ b/src/definitions/otp.d.ts @@ -9,9 +9,9 @@ interface IOTPEntry { counter: number; code: string; period: number; - create(encryption: IEncryption): Promise; - update(encryption: IEncryption): Promise; - next(encryption: IEncryption): Promise; + create(): Promise; + update(): Promise; + next(): Promise; applyEncryption(encryption: IEncryption): Promise; delete(): Promise; generate(): void; @@ -19,7 +19,6 @@ interface IOTPEntry { } interface IEncryption { - getEncryptedSecret(entry: IOTPEntry): string; getEncryptedString(data: string): string; getDecryptedSecret(entry: OTPStorage): Promise; getEncryptionStatus(): boolean; diff --git a/src/import.ts b/src/import.ts index 13ac4d51f..f7feaa25e 100644 --- a/src/import.ts +++ b/src/import.ts @@ -13,7 +13,15 @@ async function init() { // Load entries to global const encryption = new Encryption(await getCachedPassphrase()); - Vue.prototype.$entries = await EntryStorage.get(encryption); + const entries = await EntryStorage.get(); + + if (encryption.getEncryptionStatus()) { + for (const entry of entries) { + await entry.applyEncryption(encryption); + } + } + + Vue.prototype.$entries = entries; Vue.prototype.$encryption = encryption; const instance = new Vue({ diff --git a/src/models/argon.ts b/src/models/argon.ts index 1fbf43447..57cac780a 100644 --- a/src/models/argon.ts +++ b/src/models/argon.ts @@ -4,9 +4,11 @@ export class argon { static async hash(value: string) { const salt = window.crypto.getRandomValues(new Uint8Array(16)); const hash = await argon2.hash({ - // TODO: Set stronger defaults pass: value, - salt + salt, + mem: 1024 * 16, + parallelism: 4, + type: argon2.ArgonType.Argon2id }); return hash.encoded; diff --git a/src/models/backup.ts b/src/models/backup.ts index 8a9cefb44..b9ed7ba8c 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -12,7 +12,7 @@ export class Dropbox implements BackupProvider { // Encrypt by default if user hasn't set yet localStorage.dropboxEncrypted = "true"; } - const exportData = await EntryStorage.getExport( + const exportData = await EntryStorage.backupGetExport( encryption, localStorage.dropboxEncrypted === "true" ); @@ -335,7 +335,7 @@ export class Drive implements BackupProvider { if (localStorage.driveEncrypted === undefined) { localStorage.driveEncrypted = "true"; } - const exportData = await EntryStorage.getExport( + const exportData = await EntryStorage.backupGetExport( encryption, localStorage.driveEncrypted === "true" ); diff --git a/src/models/encryption.ts b/src/models/encryption.ts index 503edd997..37b3969af 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -8,23 +8,6 @@ export class Encryption implements IEncryption { this.password = password; } - getEncryptedSecret(entry: IOTPEntry): string { - if (entry.encSecret) { - return entry.encSecret; - } else if (entry.secret) { - if (!this.password) { - // Not encrypted, give unencrypted. - return entry.secret; - } else { - // Not encrypted and password is set, encrypt. - return CryptoJS.AES.encrypt(entry.secret, this.password).toString(); - } - } else { - console.error(entry); - throw new Error("Invalid entry"); - } - } - getEncryptedString(data: string): string { if (!this.password) { return data; @@ -58,7 +41,11 @@ export class Encryption implements IEncryption { return null; } - if (entry.hash.startsWith("$argon2")) { + if ( + /^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + entry.hash + ) + ) { if (await argon.compareHash(entry.hash, decryptedSecret)) { return decryptedSecret; } diff --git a/src/models/otp.ts b/src/models/otp.ts index 5b704b960..973911980 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -25,28 +25,34 @@ export class OTPEntry implements IOTPEntry { period: number; code = "••••••"; - constructor(entry: { - account: string; - encrypted: boolean; - hash: string; - index: number; - issuer: string; - secret: string; - type: OTPType; - counter: number; - period?: number; - }) { + constructor( + entry: { + account: string; + encrypted: boolean; + hash: string; + index: number; + issuer: string; + secret: string; + type: OTPType; + counter: number; + period?: number; + }, + encryption?: Encryption + ) { this.type = entry.type; this.index = entry.index; this.issuer = entry.issuer; + this.account = entry.account; if (entry.encrypted) { this.encSecret = entry.secret; this.secret = null; } else { this.secret = entry.secret; this.encSecret = null; + if (encryption && encryption.getEncryptionStatus()) { + this.encSecret = encryption.getEncryptedString(this.secret); + } } - this.account = entry.account; this.hash = entry.hash; this.counter = entry.counter; if (this.type === OTPType.totp && entry.period) { @@ -59,13 +65,13 @@ export class OTPEntry implements IOTPEntry { } } - async create(encryption: Encryption) { - await EntryStorage.add(encryption, this); + async create() { + await EntryStorage.add(this); return; } - async update(encryption: Encryption) { - await EntryStorage.update(encryption, this); + async update() { + await EntryStorage.update(this); return; } @@ -88,19 +94,19 @@ export class OTPEntry implements IOTPEntry { return; } - async next(encryption: Encryption) { + async next() { if (this.type !== OTPType.hotp && this.type !== OTPType.hhex) { return; } this.generate(); if (this.secret !== null) { this.counter++; - await this.update(encryption); + await this.update(); } return; } - async rehash(encryption: Encryption) { + async rehash() { const secret = this.secret; if (!secret) { return; @@ -113,7 +119,7 @@ export class OTPEntry implements IOTPEntry { const newHash = await argon.hash(secret); await this.delete(); this.hash = newHash; - await this.create(encryption); + await this.create(); } generate() { diff --git a/src/models/storage.ts b/src/models/storage.ts index b00b08eda..ef384db73 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -89,18 +89,32 @@ export class BrowserStorage { export class EntryStorage { private static getOTPStorageFromEntry( - encryption: Encryption, - entry: OTPEntry + entry: OTPEntry, + unencrypted?: boolean ): OTPStorage { + let secret: string; + if (entry.encSecret) { + secret = entry.encSecret; + } else if (entry.secret) { + secret = entry.secret; + } else { + secret = ""; + console.warn("Invalid entry", entry); + } + + if (unencrypted && entry.secret) { + secret = entry.secret; + } + const storageItem: OTPStorage = { account: entry.account, - encrypted: encryption.getEncryptionStatus(), + encrypted: Boolean(entry.encSecret), hash: entry.hash, index: entry.index, issuer: entry.issuer, type: OTPType[entry.type], counter: entry.counter, // TODO: Make this optional for non HOTP accounts - secret: encryption.getEncryptedSecret(entry) + secret }; if (entry.period && entry.period !== 30) { @@ -133,8 +147,11 @@ export class EntryStorage { return newData; } - /* tslint:disable-next-line:no-any */ - private static isOTPStorage(entry: any) { + private static isOTPStorage(entry: unknown) { + if (typeof entry !== "object") { + return false; + } + if (!entry || !entry.hasOwnProperty("secret")) { return false; } @@ -177,7 +194,32 @@ export class EntryStorage { ); } - static getExport(encryption: Encryption, encrypted?: boolean) { + static getExport(data: IOTPEntry[], encrypted?: boolean) { + try { + let exportData: { [hash: string]: OTPStorage } = {}; + for (const entry of data) { + if (!encrypted) { + if (!entry.secret) { + // Not unencrypted + } else if (entry.encSecret) { + exportData[entry.hash] = this.getOTPStorageFromEntry( + entry as OTPEntry, + true + ); + } + } else { + exportData[entry.hash] = this.getOTPStorageFromEntry( + entry as OTPEntry + ); + } + } + return exportData; + } catch (error) { + return error; + } + } + + static backupGetExport(encryption: Encryption, encrypted?: boolean) { return new Promise( ( resolve: (value: { [hash: string]: OTPStorage }) => void, @@ -287,7 +329,7 @@ export class EntryStorage { // not a valid / old hash if ( - !/^\$argon2(?:d|i|di)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + !/^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( hash ) ) { @@ -319,28 +361,22 @@ export class EntryStorage { ); } - static add(encryption: Encryption, entry: OTPEntry) { + static add(entry: OTPEntry) { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { - if (_data.hasOwnProperty(entry.hash)) { - throw new Error("The specific entry has already existed."); - } - const storageItem = this.getOTPStorageFromEntry(encryption, entry); - _data[entry.hash] = storageItem; - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); - }); - return; + BrowserStorage.set( + { [entry.hash]: this.getOTPStorageFromEntry(entry) }, + resolve + ); } catch (error) { - return reject(error); + reject(error); } } ); } - static update(encryption: Encryption, entry: OTPEntry) { + static update(entry: OTPEntry) { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { @@ -348,7 +384,7 @@ export class EntryStorage { if (!_data.hasOwnProperty(entry.hash)) { throw new Error("Entry to change does not exist."); } - const storageItem = this.getOTPStorageFromEntry(encryption, entry); + const storageItem = this.getOTPStorageFromEntry(entry); _data[entry.hash] = storageItem; _data = this.ensureUniqueIndex(_data); BrowserStorage.set(_data, resolve); @@ -361,16 +397,13 @@ export class EntryStorage { ); } - static set(encryption: Encryption, entries: OTPEntry[]) { + static set(entries: OTPEntry[]) { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { entries.forEach(entry => { - const storageItem = this.getOTPStorageFromEntry( - encryption, - entry - ); + const storageItem = this.getOTPStorageFromEntry(entry); _data[entry.hash] = storageItem; }); _data = this.ensureUniqueIndex(_data); @@ -384,7 +417,7 @@ export class EntryStorage { ); } - static get(encryption: Encryption) { + static get() { return new Promise( ( resolve: (value: OTPEntry[]) => void, @@ -393,21 +426,19 @@ export class EntryStorage { try { BrowserStorage.get(async (_data: { [hash: string]: OTPStorage }) => { const data: OTPEntry[] = []; + for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { continue; } const entryData = _data[hash]; - let needMigrate = false; if (!entryData.hash) { entryData.hash = hash; - needMigrate = true; } if (!entryData.type) { entryData.type = OTPType[OTPType.totp]; - needMigrate = true; } let type: OTPType; @@ -425,7 +456,6 @@ export class EntryStorage { // and save it type = OTPType.totp; entryData.type = OTPType[OTPType.totp]; - needMigrate = true; } let period = 30; @@ -448,16 +478,18 @@ export class EntryStorage { counter: entryData.counter, period }); - if (encryption.getEncryptionStatus()) { - await entry.applyEncryption(encryption); - } + data.push(entry); + data.sort((a, b) => { + return a.index - b.index; + }); + if ( entry.secret !== null && !( /^[0-9a-f]{32}$/.test(hash) || - /^\$argon2(?:d|i|di)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + /^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( hash ) ) @@ -466,18 +498,6 @@ export class EntryStorage { } } - data.sort((a, b) => { - return a.index - b.index; - }); - - for (let i = 0; i < data.length; i++) { - if (data[i].index !== i) { - const exportData = await this.getExport(encryption); - await this.import(encryption, exportData); - break; - } - } - return resolve(data); }); return; diff --git a/src/popup.ts b/src/popup.ts index 775d73210..b437b1d9b 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -16,6 +16,7 @@ import { Menu } from "./store/Menu"; import { Notification } from "./store/Notification"; import { Qr } from "./store/Qr"; import { Dropbox, Drive } from "./models/backup"; +import { EntryStorage } from "./models/storage"; async function init() { // Add globals @@ -54,7 +55,21 @@ async function init() { // Prompt for password if needed if (instance.$store.state.accounts.shouldShowPassphrase) { instance.$store.commit("style/showInfo"); - instance.$store.commit("currentView/changeView", "EnterPasswordPage"); + // If we have cached password, use that + if (instance.$store.state.accounts.encryption.getEncryptionStatus()) { + instance.$store.commit("currentView/changeView", "LoadingPage"); + for (const entry of instance.$store.state.accounts.entries) { + await entry.applyEncryption(instance.$store.state.accounts.encryption); + } + instance.$store.commit( + "accounts/updateExport", + await EntryStorage.getExport(instance.$store.state.accounts.entries) + ); + instance.$store.commit("accounts/updateCodes"); + instance.$store.commit("style/hideInfo"); + } else { + instance.$store.commit("currentView/changeView", "EnterPasswordPage"); + } } // Set document title diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index 7525307cc..b3c982426 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -11,9 +11,7 @@ export class Accounts implements IModule { let shouldShowPassphrase = cachedPassphrase ? false : await EntryStorage.hasEncryptedEntry(); - const entries = shouldShowPassphrase - ? [] - : await this.getEntries(encryption); + const entries = shouldShowPassphrase ? [] : await this.getEntries(); for (let i = 0; i < entries.length; i++) { if (entries[i].code === "Encrypted") { @@ -34,8 +32,8 @@ export class Accounts implements IModule { filter: true, siteName: await this.getSiteName(), showSearch: false, - exportData: await EntryStorage.getExport(encryption), - exportEncData: await EntryStorage.getExport(encryption, true) + exportData: await EntryStorage.getExport(entries), + exportEncData: await EntryStorage.getExport(entries, true) }, getters: { shouldFilter( @@ -144,6 +142,39 @@ export class Accounts implements IModule { } }, actions: { + deleteCode: async ( + state: ActionContext, + hash: string + ) => { + const index = state.state.entries.findIndex( + entry => entry.hash === hash + ); + if (index > -1) { + state.state.entries.splice(index, 1); + } + state.commit( + "updateExport", + await EntryStorage.getExport(state.state.entries) + ); + state.commit( + "updateEncExport", + await EntryStorage.getExport(state.state.entries, true) + ); + }, + addCode: async ( + state: ActionContext, + entry: IOTPEntry + ) => { + state.state.entries.unshift(entry); + state.commit( + "updateExport", + await EntryStorage.getExport(state.state.entries) + ); + state.commit( + "updateEncExport", + await EntryStorage.getExport(state.state.entries, true) + ); + }, applyPassphrase: async ( state: ActionContext, password: string @@ -157,17 +188,24 @@ export class Accounts implements IModule { state.state.encryption.updateEncryptionPassword(password); await state.dispatch("updateEntries"); + // Migrate old hashes to argon2 if (state.state.encryption.getEncryptionStatus()) { + let changedHash = false; + for (const entry of state.state.entries) { if ( - !/^\$argon2(?:d|i|di)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + !/^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( entry.hash ) ) { await entry.rehash(state.state.encryption); + changedHash = true; } } - await state.dispatch("updateEntries"); + + if (changedHash) { + await state.dispatch("updateEntries"); + } } state.commit("style/hideInfo", null, { root: true }); @@ -187,7 +225,7 @@ export class Accounts implements IModule { ) => { await EntryStorage.import( new Encryption(password), - await EntryStorage.getExport(state.state.encryption as Encryption) + await EntryStorage.getExport(state.state.entries) ); state.state.encryption.updateEncryptionPassword(password); @@ -203,8 +241,7 @@ export class Accounts implements IModule { localStorage.removeItem("encodedPhrase"); }, updateEntries: async (state: ActionContext) => { - const entries = await this.getEntries(state.state - .encryption as Encryption); + const entries = await this.getEntries(); if (state.state.encryption.getEncryptionStatus()) { for (const entry of entries) { @@ -216,14 +253,11 @@ export class Accounts implements IModule { state.commit("updateCodes"); state.commit( "updateExport", - await EntryStorage.getExport(state.state.encryption as Encryption) + await EntryStorage.getExport(state.state.entries) ); state.commit( "updateEncExport", - await EntryStorage.getExport( - state.state.encryption as Encryption, - true - ) + await EntryStorage.getExport(state.state.entries, true) ); return; }, @@ -395,8 +429,8 @@ export class Accounts implements IModule { ); } - private async getEntries(encryption: Encryption) { - const otpEntries = await EntryStorage.get(encryption); + private async getEntries() { + const otpEntries = await EntryStorage.get(); return otpEntries; } From ff841350af28ead035779455ba827dde9270b13c Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Mon, 26 Aug 2019 17:11:06 -0500 Subject: [PATCH 09/27] don't animate password prompt --- sass/popup.scss | 4 ++++ src/components/Popup.vue | 6 +++++- src/definitions/module-interface.d.ts | 1 + src/popup.ts | 4 ++-- src/store/Accounts.ts | 2 +- src/store/Style.ts | 21 +++++++++++++++------ 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/sass/popup.scss b/sass/popup.scss index 949328672..094adbdee 100644 --- a/sass/popup.scss +++ b/sass/popup.scss @@ -650,6 +650,10 @@ svg { top: 110px; animation: fadeout 0.2s 1 ease-in; } + + &.show { + top: 10px; + } } // Menu diff --git a/src/components/Popup.vue b/src/components/Popup.vue index f77999845..3a6fcfeae 100644 --- a/src/components/Popup.vue +++ b/src/components/Popup.vue @@ -20,7 +20,11 @@ /> diff --git a/src/definitions/module-interface.d.ts b/src/definitions/module-interface.d.ts index 75b1dffeb..6d9322947 100644 --- a/src/definitions/module-interface.d.ts +++ b/src/definitions/module-interface.d.ts @@ -43,6 +43,7 @@ interface StyleState { slideout: Boolean; fadein: Boolean; fadeout: Boolean; + show: Boolean; qrfadein: Boolean; qrfadeout: Boolean; notificationFadein: Boolean; diff --git a/src/popup.ts b/src/popup.ts index b437b1d9b..0f92ab5df 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -54,7 +54,7 @@ async function init() { // Prompt for password if needed if (instance.$store.state.accounts.shouldShowPassphrase) { - instance.$store.commit("style/showInfo"); + instance.$store.commit("style/showInfo", true); // If we have cached password, use that if (instance.$store.state.accounts.encryption.getEncryptionStatus()) { instance.$store.commit("currentView/changeView", "LoadingPage"); @@ -66,7 +66,7 @@ async function init() { await EntryStorage.getExport(instance.$store.state.accounts.entries) ); instance.$store.commit("accounts/updateCodes"); - instance.$store.commit("style/hideInfo"); + instance.$store.commit("style/hideInfo", true); } else { instance.$store.commit("currentView/changeView", "EnterPasswordPage"); } diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index b3c982426..b4001fcd0 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -208,7 +208,7 @@ export class Accounts implements IModule { } } - state.commit("style/hideInfo", null, { root: true }); + state.commit("style/hideInfo", true, { root: true }); if (!state.getters.currentlyEncrypted) { document.cookie = "passphrase=" + password; diff --git a/src/store/Style.ts b/src/store/Style.ts index 993bb3237..21a6002f8 100644 --- a/src/store/Style.ts +++ b/src/store/Style.ts @@ -9,6 +9,7 @@ export class Style implements IModule { slideout: false, // menu fadein: false, // info fadeout: false, // info + show: false, // info qrfadein: false, qrfadeout: false, notificationFadein: false, @@ -28,13 +29,21 @@ export class Style implements IModule { state.style.slideout = false; }, 200); }, - showInfo(state: StyleState) { - state.style.fadein = true; - state.style.fadeout = false; + showInfo(state: StyleState, noAnimate?: boolean) { + if (noAnimate) { + state.style.show = true; + } else { + state.style.fadein = true; + state.style.fadeout = false; + } }, - hideInfo(state: StyleState) { - state.style.fadein = false; - state.style.fadeout = true; + hideInfo(state: StyleState, noAnimate?: boolean) { + if (noAnimate) { + state.style.show = false; + } else { + state.style.fadein = false; + state.style.fadeout = true; + } setTimeout(() => { state.style.fadeout = false; }, 200); From 78e1d30388fb22d44547bb565fcf64615bd4de56 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 30 Aug 2019 15:20:52 -0500 Subject: [PATCH 10/27] hash mem 16 KiB => 8 KiB 11 entries takes about 500ms with 16KiB, 8 KiB takes about 300ms --- src/models/argon.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/argon.ts b/src/models/argon.ts index 57cac780a..718cefac4 100644 --- a/src/models/argon.ts +++ b/src/models/argon.ts @@ -6,8 +6,7 @@ export class argon { const hash = await argon2.hash({ pass: value, salt, - mem: 1024 * 16, - parallelism: 4, + mem: 1024 * 8, type: argon2.ArgonType.Argon2id }); From b03fcad97f3b82b02566ec93e4a953919b7a932a Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Fri, 30 Aug 2019 16:15:11 -0500 Subject: [PATCH 11/27] fix password change not working empty backup with no password fix --- src/definitions/otp.d.ts | 1 + src/models/otp.ts | 15 +++++++++++++++ src/models/storage.ts | 2 +- src/store/Accounts.ts | 10 +++++----- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts index c18ca65a2..0cdb96be6 100644 --- a/src/definitions/otp.d.ts +++ b/src/definitions/otp.d.ts @@ -13,6 +13,7 @@ interface IOTPEntry { update(): Promise; next(): Promise; applyEncryption(encryption: IEncryption): Promise; + changeEncryption(encryption: IEncryption): Promise; delete(): Promise; generate(): void; rehash(encryption: IEncryption): Promise; diff --git a/src/models/otp.ts b/src/models/otp.ts index 973911980..12e73a880 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -75,6 +75,21 @@ export class OTPEntry implements IOTPEntry { return; } + async changeEncryption(encryption: Encryption) { + if (!this.secret) { + return; + } + + if (encryption.getEncryptionStatus()) { + this.encSecret = encryption.getEncryptedString(this.secret); + } else { + this.encSecret = null; + } + + await this.update(); + return; + } + async applyEncryption(encryption: Encryption) { const secret = this.encSecret ? this.encSecret : null; if (secret) { diff --git a/src/models/storage.ts b/src/models/storage.ts index ef384db73..931dd142d 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -201,7 +201,7 @@ export class EntryStorage { if (!encrypted) { if (!entry.secret) { // Not unencrypted - } else if (entry.encSecret) { + } else { exportData[entry.hash] = this.getOTPStorageFromEntry( entry as OTPEntry, true diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index b4001fcd0..209c3f800 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -223,18 +223,18 @@ export class Accounts implements IModule { state: ActionContext, password: string ) => { - await EntryStorage.import( - new Encryption(password), - await EntryStorage.getExport(state.state.entries) - ); - state.state.encryption.updateEncryptionPassword(password); + document.cookie = "passphrase=" + password; chrome.runtime.sendMessage({ action: "cachePassphrase", value: password }); + for (const entry of state.state.entries) { + await entry.changeEncryption(state.state.encryption); + } + await state.dispatch("updateEntries"); // remove cached passphrase in old version From 70e6ecdd6feebf26fa2a2ed3b05b3203ef18d848 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 09:58:08 -0500 Subject: [PATCH 12/27] no cookie again. developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Session_cookies --- src/components/Popup/MainHeader.vue | 1 - src/definitions/otp.d.ts | 4 +- src/import.ts | 10 ----- src/models/encryption.ts | 6 +-- src/models/otp.ts | 4 +- src/models/storage.ts | 36 +++++++++++++-- src/store/Accounts.ts | 69 +++++++++++++---------------- 7 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/components/Popup/MainHeader.vue b/src/components/Popup/MainHeader.vue index 1e37dbc27..4884c7272 100644 --- a/src/components/Popup/MainHeader.vue +++ b/src/components/Popup/MainHeader.vue @@ -100,7 +100,6 @@ export default Vue.extend({ this.$store.commit("accounts/stopFilter"); }, lock() { - document.cookie = 'passphrase=";expires=Thu, 01 Jan 1970 00:00:00 GMT"'; chrome.runtime.sendMessage({ action: "lock" }, window.close); return; }, diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts index 0cdb96be6..233fddf4b 100644 --- a/src/definitions/otp.d.ts +++ b/src/definitions/otp.d.ts @@ -12,7 +12,7 @@ interface IOTPEntry { create(): Promise; update(): Promise; next(): Promise; - applyEncryption(encryption: IEncryption): Promise; + applyEncryption(encryption: IEncryption): void; changeEncryption(encryption: IEncryption): Promise; delete(): Promise; generate(): void; @@ -21,7 +21,7 @@ interface IOTPEntry { interface IEncryption { getEncryptedString(data: string): string; - getDecryptedSecret(entry: OTPStorage): Promise; + getDecryptedSecret(entry: OTPStorage): string | null; getEncryptionStatus(): boolean; updateEncryptionPassword(password: string): void; } diff --git a/src/import.ts b/src/import.ts index f7feaa25e..2f8d6e805 100644 --- a/src/import.ts +++ b/src/import.ts @@ -41,16 +41,6 @@ init(); function getCachedPassphrase() { return new Promise( (resolve: (value: string) => void, reject: (reason: Error) => void) => { - const cookie = document.cookie; - const cookieMatch = cookie - ? document.cookie.match(/passphrase=([^;]*)/) - : null; - const cachedPassphrase = - cookieMatch && cookieMatch.length > 1 ? cookieMatch[1] : null; - if (cachedPassphrase) { - return resolve(cachedPassphrase); - } - chrome.runtime.sendMessage( { action: "passphrase" }, (passphrase: string) => { diff --git a/src/models/encryption.ts b/src/models/encryption.ts index 37b3969af..2ffb19627 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -16,7 +16,7 @@ export class Encryption implements IEncryption { } } - async getDecryptedSecret(entry: { secret: string; hash: string }) { + getDecryptedSecret(entry: { secret: string; hash: string }) { try { const decryptedSecret = CryptoJS.AES.decrypt( entry.secret, @@ -46,9 +46,7 @@ export class Encryption implements IEncryption { entry.hash ) ) { - if (await argon.compareHash(entry.hash, decryptedSecret)) { - return decryptedSecret; - } + return decryptedSecret; } else if (entry.hash === CryptoJS.MD5(decryptedSecret).toString()) { return decryptedSecret; } diff --git a/src/models/otp.ts b/src/models/otp.ts index 12e73a880..c729c3089 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -90,10 +90,10 @@ export class OTPEntry implements IOTPEntry { return; } - async applyEncryption(encryption: Encryption) { + applyEncryption(encryption: Encryption) { const secret = this.encSecret ? this.encSecret : null; if (secret) { - this.secret = await encryption.getDecryptedSecret({ + this.secret = encryption.getDecryptedSecret({ hash: this.hash, secret }); diff --git a/src/models/storage.ts b/src/models/storage.ts index 931dd142d..3031353af 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -53,16 +53,44 @@ export class BrowserStorage { } /* tslint:disable-next-line:no-any */ + // TODO: promise this static async get(callback: (items: { [key: string]: any }) => void) { const storageLocation = await this.getStorageLocation(); + const removeKey = function (items: { [key: string]: any }): void { + delete items.key; + callback(items); + } + if (storageLocation === "local") { - chrome.storage.local.get(callback); + chrome.storage.local.get(removeKey); } else if (storageLocation === "sync") { - chrome.storage.sync.get(callback); + chrome.storage.sync.get(removeKey); } return; } + static getKey() { + return new Promise( + async (resolve: (key: string | null) => void) => { + const storageLocation = await this.getStorageLocation(); + const callback = function (items: { [key: string]: any }): void { + if (typeof items.key === "string") { + resolve(items.key); + } else { + resolve(null); + } + return; + } + + if (storageLocation === "local") { + chrome.storage.local.get(callback); + } else if (storageLocation === "sync") { + chrome.storage.sync.get(callback); + } + return; + }) + } + static async set(data: object, callback?: (() => void) | undefined) { const storageLocation = await this.getStorageLocation(); if (storageLocation === "local") { @@ -226,7 +254,7 @@ export class EntryStorage { reject: (reason: Error) => void ) => { try { - BrowserStorage.get(async (_data: { [hash: string]: OTPStorage }) => { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { delete _data[hash]; @@ -235,7 +263,7 @@ export class EntryStorage { if (!encrypted) { // decrypt the data to export if (_data[hash].encrypted) { - const decryptedSecret = await encryption.getDecryptedSecret( + const decryptedSecret = encryption.getDecryptedSecret( _data[hash] ); if ( diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index 209c3f800..8c84c5bf3 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -1,4 +1,4 @@ -import { EntryStorage } from "../models/storage"; +import { EntryStorage, BrowserStorage } from "../models/storage"; import { Encryption } from "../models/encryption"; import * as CryptoJS from "crypto-js"; import { OTPType } from "../models/otp"; @@ -185,33 +185,40 @@ export class Accounts implements IModule { state.commit("currentView/changeView", "LoadingPage", { root: true }); - state.state.encryption.updateEncryptionPassword(password); - await state.dispatch("updateEntries"); + const key = await BrowserStorage.getKey(); + if (!key) { + // migrate to key + } else { + // decrypt using key + state.state.encryption.updateEncryptionPassword(password); + await state.dispatch("updateEntries"); + } - // Migrate old hashes to argon2 - if (state.state.encryption.getEncryptionStatus()) { - let changedHash = false; - - for (const entry of state.state.entries) { - if ( - !/^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( - entry.hash - ) - ) { - await entry.rehash(state.state.encryption); - changedHash = true; - } - } - if (changedHash) { - await state.dispatch("updateEntries"); - } - } + + // if (state.state.encryption.getEncryptionStatus()) { + // // Migrate old hashes to argon2 + // let changedHash = false; + + // for (const entry of state.state.entries) { + // if ( + // !/^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + // entry.hash + // ) + // ) { + // await entry.rehash(state.state.encryption); + // changedHash = true; + // } + // } + + // if (changedHash) { + // await state.dispatch("updateEntries"); + // } + // } state.commit("style/hideInfo", true, { root: true }); if (!state.getters.currentlyEncrypted) { - document.cookie = "passphrase=" + password; chrome.runtime.sendMessage({ action: "cachePassphrase", value: password @@ -225,7 +232,6 @@ export class Accounts implements IModule { ) => { state.state.encryption.updateEncryptionPassword(password); - document.cookie = "passphrase=" + password; chrome.runtime.sendMessage({ action: "cachePassphrase", value: password @@ -403,22 +409,7 @@ export class Accounts implements IModule { private getCachedPassphrase() { return new Promise( - (resolve: (value: string) => void, reject: (reason: Error) => void) => { - const cookie = document.cookie; - const cookieMatch = cookie - ? document.cookie.match(/passphrase=([^;]*)/) - : null; - const cachedPassphrase = - cookieMatch && cookieMatch.length > 1 ? cookieMatch[1] : null; - const cachedPassphraseLocalStorage = localStorage.encodedPhrase - ? CryptoJS.AES.decrypt(localStorage.encodedPhrase, "").toString( - CryptoJS.enc.Utf8 - ) - : ""; - if (cachedPassphrase || cachedPassphraseLocalStorage) { - return resolve(cachedPassphrase || cachedPassphraseLocalStorage); - } - + (resolve: (value: string) => void) => { chrome.runtime.sendMessage( { action: "passphrase" }, (passphrase: string) => { From cdc397e4140f380b5ff927922b88c9861bc10f31 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 13:14:45 -0500 Subject: [PATCH 13/27] migrate to key based system --- src/models/encryption.ts | 1 - src/models/storage.ts | 37 ++++++++--------- src/store/Accounts.ts | 89 +++++++++++++++++++++------------------- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/models/encryption.ts b/src/models/encryption.ts index 2ffb19627..006ca4b95 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -1,5 +1,4 @@ import * as CryptoJS from "crypto-js"; -import { argon } from "./argon"; export class Encryption implements IEncryption { private password: string; diff --git a/src/models/storage.ts b/src/models/storage.ts index 3031353af..b28637858 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -56,10 +56,10 @@ export class BrowserStorage { // TODO: promise this static async get(callback: (items: { [key: string]: any }) => void) { const storageLocation = await this.getStorageLocation(); - const removeKey = function (items: { [key: string]: any }): void { + const removeKey = function(items: { [key: string]: any }): void { delete items.key; callback(items); - } + }; if (storageLocation === "local") { chrome.storage.local.get(removeKey); @@ -70,25 +70,24 @@ export class BrowserStorage { } static getKey() { - return new Promise( - async (resolve: (key: string | null) => void) => { - const storageLocation = await this.getStorageLocation(); - const callback = function (items: { [key: string]: any }): void { - if (typeof items.key === "string") { - resolve(items.key); - } else { - resolve(null); - } - return; - } - - if (storageLocation === "local") { - chrome.storage.local.get(callback); - } else if (storageLocation === "sync") { - chrome.storage.sync.get(callback); + return new Promise(async (resolve: (key: string | null) => void) => { + const storageLocation = await this.getStorageLocation(); + const callback = function(items: { [key: string]: any }): void { + if (typeof items.key === "string") { + resolve(items.key); + } else { + resolve(null); } return; - }) + }; + + if (storageLocation === "local") { + chrome.storage.local.get(callback); + } else if (storageLocation === "sync") { + chrome.storage.sync.get(callback); + } + return; + }); } static async set(data: object, callback?: (() => void) | undefined) { diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index 8c84c5bf3..b1096b4bb 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -185,45 +185,52 @@ export class Accounts implements IModule { state.commit("currentView/changeView", "LoadingPage", { root: true }); - const key = await BrowserStorage.getKey(); - if (!key) { - // migrate to key - } else { - // decrypt using key + const encKey = await BrowserStorage.getKey(); + if (!encKey) { + // --- migrate to key + // verify current password state.state.encryption.updateEncryptionPassword(password); await state.dispatch("updateEntries"); - } - + if (state.getters.currentlyEncrypted) { + state.commit("style/hideInfo", true, { root: true }); + return; + } + // gen key + const randomKey = crypto.getRandomValues(new Uint32Array(30)); + const wordArray = CryptoJS.lib.WordArray.create(randomKey); + const encKey = CryptoJS.AES.encrypt(wordArray, password).toString(); + + // store key + BrowserStorage.set({ key: encKey }, async () => { + // change entry encryption to key + for (const entry of state.state.entries) { + await entry.changeEncryption( + new Encryption(wordArray.toString()) + ); + } + + state.state.encryption.updateEncryptionPassword( + wordArray.toString() + ); + await state.dispatch("updateEntries"); + }); + } else { + // --- decrypt using key + const key = CryptoJS.AES.decrypt(encKey, password).toString(); - // if (state.state.encryption.getEncryptionStatus()) { - // // Migrate old hashes to argon2 - // let changedHash = false; - - // for (const entry of state.state.entries) { - // if ( - // !/^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( - // entry.hash - // ) - // ) { - // await entry.rehash(state.state.encryption); - // changedHash = true; - // } - // } + state.state.encryption.updateEncryptionPassword(key); + await state.dispatch("updateEntries"); - // if (changedHash) { - // await state.dispatch("updateEntries"); - // } - // } + if (!state.getters.currentlyEncrypted) { + chrome.runtime.sendMessage({ + action: "cachePassphrase", + value: key + }); + } + } state.commit("style/hideInfo", true, { root: true }); - - if (!state.getters.currentlyEncrypted) { - chrome.runtime.sendMessage({ - action: "cachePassphrase", - value: password - }); - } return; }, changePassphrase: async ( @@ -408,16 +415,14 @@ export class Accounts implements IModule { } private getCachedPassphrase() { - return new Promise( - (resolve: (value: string) => void) => { - chrome.runtime.sendMessage( - { action: "passphrase" }, - (passphrase: string) => { - return resolve(passphrase); - } - ); - } - ); + return new Promise((resolve: (value: string) => void) => { + chrome.runtime.sendMessage( + { action: "passphrase" }, + (passphrase: string) => { + return resolve(passphrase); + } + ); + }); } private async getEntries() { From 31cc0980358d970c848d47135673422cf723eb48 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 13:48:01 -0500 Subject: [PATCH 14/27] add hash --- src/models/storage.ts | 32 +++++++++++++++++--------------- src/store/Accounts.ts | 29 +++++++++++++++++------------ 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/models/storage.ts b/src/models/storage.ts index b28637858..597464452 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -70,24 +70,26 @@ export class BrowserStorage { } static getKey() { - return new Promise(async (resolve: (key: string | null) => void) => { - const storageLocation = await this.getStorageLocation(); - const callback = function(items: { [key: string]: any }): void { - if (typeof items.key === "string") { - resolve(items.key); - } else { - resolve(null); + return new Promise( + async (resolve: (key: { enc: string; hash: string } | null) => void) => { + const storageLocation = await this.getStorageLocation(); + const callback = function(items: { [key: string]: any }): void { + if (typeof items.key === "object") { + resolve(items.key); + } else { + resolve(null); + } + return; + }; + + if (storageLocation === "local") { + chrome.storage.local.get(callback); + } else if (storageLocation === "sync") { + chrome.storage.sync.get(callback); } return; - }; - - if (storageLocation === "local") { - chrome.storage.local.get(callback); - } else if (storageLocation === "sync") { - chrome.storage.sync.get(callback); } - return; - }); + ); } static async set(data: object, callback?: (() => void) | undefined) { diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index b1096b4bb..e2e7c90d9 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -3,6 +3,7 @@ import { Encryption } from "../models/encryption"; import * as CryptoJS from "crypto-js"; import { OTPType } from "../models/otp"; import { ActionContext } from "vuex"; +import { argon } from "../models/argon"; export class Accounts implements IModule { async getModule() { @@ -200,24 +201,28 @@ export class Accounts implements IModule { const randomKey = crypto.getRandomValues(new Uint32Array(30)); const wordArray = CryptoJS.lib.WordArray.create(randomKey); const encKey = CryptoJS.AES.encrypt(wordArray, password).toString(); + const encKeyHash = await argon.hash(wordArray.toString()); // store key - BrowserStorage.set({ key: encKey }, async () => { - // change entry encryption to key - for (const entry of state.state.entries) { - await entry.changeEncryption( - new Encryption(wordArray.toString()) + BrowserStorage.set( + { key: { enc: encKey, hash: encKeyHash } }, + async () => { + // change entry encryption to key + for (const entry of state.state.entries) { + await entry.changeEncryption( + new Encryption(wordArray.toString()) + ); + } + + state.state.encryption.updateEncryptionPassword( + wordArray.toString() ); + await state.dispatch("updateEntries"); } - - state.state.encryption.updateEncryptionPassword( - wordArray.toString() - ); - await state.dispatch("updateEntries"); - }); + ); } else { // --- decrypt using key - const key = CryptoJS.AES.decrypt(encKey, password).toString(); + const key = CryptoJS.AES.decrypt(encKey.enc, password).toString(); state.state.encryption.updateEncryptionPassword(key); await state.dispatch("updateEntries"); From 6d0b41e16a570ca28bb04d1a842630243102f98b Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 14:01:54 -0500 Subject: [PATCH 15/27] fix bug --- src/components/Popup/EntryComponent.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Popup/EntryComponent.vue b/src/components/Popup/EntryComponent.vue index 2a904a6f5..e19aa145c 100644 --- a/src/components/Popup/EntryComponent.vue +++ b/src/components/Popup/EntryComponent.vue @@ -157,7 +157,7 @@ export default Vue.extend({ } if (entry.code === "Encrypted") { - this.$store.commit("style/showInfo"); + this.$store.commit("style/showInfo", true); this.$store.commit("currentView/changeView", "EnterPasswordPage"); return; } From 7931b86cff275dfb022a1769859df74deba21f40 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 15:54:51 -0500 Subject: [PATCH 16/27] entry hash -> uuid --- package-lock.json | 20 +++++++++++++++++ package.json | 2 ++ src/components/Popup/AddAccountPage.vue | 1 - src/components/Popup/ExportPage.vue | 1 - src/definitions/otp.d.ts | 2 +- src/models/encryption.ts | 18 +-------------- src/models/otp.ts | 26 +++++++++------------ src/store/Accounts.ts | 30 ++++++++++++------------- 8 files changed, 48 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 462fbc13a..5025bac2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,12 +46,27 @@ "integrity": "sha512-oBnY3csYnXfqZXDRBJwP1nDDJCW/+VMJ88UHT4DCy0deSXpJIQvMCwYlnmdW4M+u7PiSfQc44LmiFcUbJ8hLEw==", "dev": true }, + "@types/node": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz", + "integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A==", + "dev": true + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/uuid": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz", + "integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@vue/component-compiler-utils": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz", @@ -4701,6 +4716,11 @@ "object.getownpropertydescriptors": "^2.0.3" } }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, "v8-compile-cache": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", diff --git a/package.json b/package.json index 656de3a86..4556a60fe 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@types/chrome": "^0.0.86", "@types/crypto-js": "^3.1.43", "@types/jssha": "2.0.0", + "@types/uuid": "^3.4.5", "base64-loader": "^1.0.0", "fork-ts-checker-webpack-plugin": "^1.3.5", "prettier": "1.18.2", @@ -43,6 +44,7 @@ "jssha": "^2.3.1", "qrcode-generator": "^1.4.3", "qrcode-reader": "^1.0.4", + "uuid": "^3.3.3", "vue": "^2.6.10", "vue2-dragula": "^2.5.4", "vuex": "^3.1.1" diff --git a/src/components/Popup/AddAccountPage.vue b/src/components/Popup/AddAccountPage.vue index 898f65b99..21f718c11 100644 --- a/src/components/Popup/AddAccountPage.vue +++ b/src/components/Popup/AddAccountPage.vue @@ -98,7 +98,6 @@ export default Vue.extend({ issuer: this.newAccount.issuer, account: this.newAccount.account, encrypted: false, - hash: await argon.hash(this.newAccount.secret), secret: this.newAccount.secret, counter: 0, period: this.newAccount.period diff --git a/src/components/Popup/ExportPage.vue b/src/components/Popup/ExportPage.vue index c8cfe29df..41b7262bb 100644 --- a/src/components/Popup/ExportPage.vue +++ b/src/components/Popup/ExportPage.vue @@ -67,7 +67,6 @@ function hasUnsupportedAccounts(exportData: { [h: string]: OTPStorage }) { exportData[entry].type === "battle" || exportData[entry].type === "steam" ) { - console.log(exportData[entry]); return true; } } diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts index 233fddf4b..634db0957 100644 --- a/src/definitions/otp.d.ts +++ b/src/definitions/otp.d.ts @@ -16,7 +16,7 @@ interface IOTPEntry { changeEncryption(encryption: IEncryption): Promise; delete(): Promise; generate(): void; - rehash(encryption: IEncryption): Promise; + genUUID(): Promise; } interface IEncryption { diff --git a/src/models/encryption.ts b/src/models/encryption.ts index 006ca4b95..5fa187946 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -40,23 +40,7 @@ export class Encryption implements IEncryption { return null; } - if ( - /^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( - entry.hash - ) - ) { - return decryptedSecret; - } else if (entry.hash === CryptoJS.MD5(decryptedSecret).toString()) { - return decryptedSecret; - } - - console.warn( - `Account ${entry.hash} may have secret ${decryptedSecret.replace( - / /g, - "" - )}, but hash did not match.` - ); - return null; + return decryptedSecret; } catch (error) { return null; } diff --git a/src/models/otp.ts b/src/models/otp.ts index c729c3089..906bb700e 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -1,8 +1,7 @@ import { Encryption } from "./encryption"; import { KeyUtilities } from "./key-utilities"; import { EntryStorage } from "./storage"; -import { argon } from "./argon"; -import * as CryptoJS from "crypto-js"; +import * as uuid from "uuid/v4"; export enum OTPType { totp = 1, @@ -29,13 +28,13 @@ export class OTPEntry implements IOTPEntry { entry: { account: string; encrypted: boolean; - hash: string; index: number; issuer: string; secret: string; type: OTPType; counter: number; period?: number; + hash?: string; }, encryption?: Encryption ) { @@ -53,7 +52,12 @@ export class OTPEntry implements IOTPEntry { this.encSecret = encryption.getEncryptedString(this.secret); } } - this.hash = entry.hash; + + if (entry.hash) { + this.hash = entry.hash; + } else { + this.hash = uuid(); // UUID + } this.counter = entry.counter; if (this.type === OTPType.totp && entry.period) { this.period = entry.period; @@ -121,19 +125,9 @@ export class OTPEntry implements IOTPEntry { return; } - async rehash() { - const secret = this.secret; - if (!secret) { - return; - } - - if (this.hash !== CryptoJS.MD5(secret).toString()) { - return; - } - - const newHash = await argon.hash(secret); + async genUUID() { await this.delete(); - this.hash = newHash; + this.hash = uuid(); await this.create(); } diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index e2e7c90d9..850113c5b 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -204,22 +204,21 @@ export class Accounts implements IModule { const encKeyHash = await argon.hash(wordArray.toString()); // store key - BrowserStorage.set( - { key: { enc: encKey, hash: encKeyHash } }, - async () => { - // change entry encryption to key - for (const entry of state.state.entries) { - await entry.changeEncryption( - new Encryption(wordArray.toString()) - ); - } - - state.state.encryption.updateEncryptionPassword( - wordArray.toString() - ); - await state.dispatch("updateEntries"); - } + await BrowserStorage.set({ + key: { enc: encKey, hash: encKeyHash } + }); + // change entry encryption to key and remove old hash + for (const entry of state.state.entries) { + await entry.changeEncryption( + new Encryption(wordArray.toString()) + ); + await entry.genUUID(); + } + + state.state.encryption.updateEncryptionPassword( + wordArray.toString() ); + await state.dispatch("updateEntries"); } else { // --- decrypt using key const key = CryptoJS.AES.decrypt(encKey.enc, password).toString(); @@ -234,7 +233,6 @@ export class Accounts implements IModule { }); } } - state.commit("style/hideInfo", true, { root: true }); return; }, From 67d3b9c80c76f583d8a1c59f4082c92df025eaa7 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 16:18:21 -0500 Subject: [PATCH 17/27] password hash check + warning --- manifest-chrome.json | 4 ++-- sass/popup.scss | 6 ++++++ src/components/Popup/EnterPasswordPage.vue | 9 +++++++++ src/definitions/module-interface.d.ts | 1 + src/store/Accounts.ts | 14 +++++++++++++- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/manifest-chrome.json b/manifest-chrome.json index 49271dd7a..5d8788d2d 100644 --- a/manifest-chrome.json +++ b/manifest-chrome.json @@ -27,14 +27,14 @@ "oauth2": { "client_id": "292457304165-u8ve4j79gag5o231n5u2pdtdrbfdo1hh.apps.googleusercontent.com", "scopes": [ - "https://www.googleapis.com/auth/drive.file" + "https://www.googleapis.com/auth/drive.file" ] }, "background": { "scripts": [ "dist/background.js" ], - "persistent": false + "persistent": true }, "permissions": [ "activeTab", diff --git a/sass/popup.scss b/sass/popup.scss index 094adbdee..f30d8f2c0 100644 --- a/sass/popup.scss +++ b/sass/popup.scss @@ -641,6 +641,12 @@ svg { @include security-button(30px); } + .badInput { + @include themify($themes) { + border-color: themed("red-1"); + } + } + &.fadein { top: 10px; animation: fadein 0.2s 1 ease-out; diff --git a/src/components/Popup/EnterPasswordPage.vue b/src/components/Popup/EnterPasswordPage.vue index 3782dd41d..106b396a9 100644 --- a/src/components/Popup/EnterPasswordPage.vue +++ b/src/components/Popup/EnterPasswordPage.vue @@ -8,7 +8,11 @@ v-model="password" v-on:keyup.enter="applyPassphrase()" autofocus + v-bind:class="{ badInput: wrongPassword }" /> +
{{ i18n.ok }}
@@ -22,6 +26,11 @@ export default Vue.extend({ password: "" }; }, + computed: { + wrongPassword() { + return this.$store.state.accounts.wrongPassword; + } + }, methods: { applyPassphrase() { this.$store.dispatch("accounts/applyPassphrase", this.password); diff --git a/src/definitions/module-interface.d.ts b/src/definitions/module-interface.d.ts index 6d9322947..8ed0d4801 100644 --- a/src/definitions/module-interface.d.ts +++ b/src/definitions/module-interface.d.ts @@ -66,6 +66,7 @@ interface AccountsState { showSearch: Boolean; exportData: { [k: string]: IOTPEntry }; exportEncData: { [k: string]: IOTPEntry }; + wrongPassword: Boolean; } interface NotificationState { diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index 850113c5b..8ffd02511 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -34,7 +34,8 @@ export class Accounts implements IModule { siteName: await this.getSiteName(), showSearch: false, exportData: await EntryStorage.getExport(entries), - exportEncData: await EntryStorage.getExport(entries, true) + exportEncData: await EntryStorage.getExport(entries, true), + wrongPassword: false }, getters: { shouldFilter( @@ -140,6 +141,9 @@ export class Accounts implements IModule { exportData: { [k: string]: IOTPEntry } ) { state.exportEncData = exportData; + }, + wrongPassword(state: AccountsState) { + state.wrongPassword = true; } }, actions: { @@ -223,6 +227,14 @@ export class Accounts implements IModule { // --- decrypt using key const key = CryptoJS.AES.decrypt(encKey.enc, password).toString(); + if (!(await argon.compareHash(encKey.hash, key))) { + state.commit("wrongPassword"); + state.commit("currentView/changeView", "EnterPasswordPage", { + root: true + }); + return; + } + state.state.encryption.updateEncryptionPassword(key); await state.dispatch("updateEntries"); From 6198965c6765a0b02bcec46f2f1d846cdecca6e2 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 18:59:04 -0500 Subject: [PATCH 18/27] add key to backup --- src/components/Popup/ExportPage.vue | 11 +++++++++-- src/definitions/module-interface.d.ts | 3 ++- src/models/storage.ts | 3 ++- src/store/Accounts.ts | 8 ++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/components/Popup/ExportPage.vue b/src/components/Popup/ExportPage.vue index 41b7262bb..4869833f6 100644 --- a/src/components/Popup/ExportPage.vue +++ b/src/components/Popup/ExportPage.vue @@ -46,11 +46,12 @@ export default Vue.extend({ data: function() { const exportData = this.$store.state.accounts.exportData; const exportEncData = this.$store.state.accounts.exportEncData; + const key = this.$store.state.accounts.key; return { unsupportedAccounts: hasUnsupportedAccounts(exportData), exportFile: getBackupFile(exportData), - exportEncryptedFile: getBackupFile(exportEncData), + exportEncryptedFile: getBackupFile(exportEncData, key), exportOneLineOtpAuthFile: getOneLineOtpBackupFile(exportData) }; }, @@ -73,7 +74,13 @@ function hasUnsupportedAccounts(exportData: { [h: string]: OTPStorage }) { return false; } -function getBackupFile(entryData: { [hash: string]: OTPStorage }) { +function getBackupFile( + entryData: { [hash: string]: OTPStorage }, + key?: Object +) { + if (key) { + Object.assign(entryData, { key: key }); + } let json = JSON.stringify(entryData, null, 2); // for windows notepad json = json.replace(/\n/g, "\r\n"); diff --git a/src/definitions/module-interface.d.ts b/src/definitions/module-interface.d.ts index 8ed0d4801..b9038f306 100644 --- a/src/definitions/module-interface.d.ts +++ b/src/definitions/module-interface.d.ts @@ -4,7 +4,7 @@ interface IModule { interface VuexConstructor { state?: { - [key: string]: Object | Function; + [key: string]: any; }; mutations?: { [key: string]: Function; @@ -66,6 +66,7 @@ interface AccountsState { showSearch: Boolean; exportData: { [k: string]: IOTPEntry }; exportEncData: { [k: string]: IOTPEntry }; + key: { enc: string; hash: string } | null; wrongPassword: Boolean; } diff --git a/src/models/storage.ts b/src/models/storage.ts index 597464452..b277f2da4 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -255,7 +255,7 @@ export class EntryStorage { reject: (reason: Error) => void ) => { try { - BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + BrowserStorage.get(async (_data: { [hash: string]: OTPStorage }) => { for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { delete _data[hash]; @@ -282,6 +282,7 @@ export class EntryStorage { } } } + Object.assign(_data, await BrowserStorage.getKey()); return resolve(_data); }); return; diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index 8ffd02511..fbaa087e2 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -35,6 +35,7 @@ export class Accounts implements IModule { showSearch: false, exportData: await EntryStorage.getExport(entries), exportEncData: await EntryStorage.getExport(entries, true), + key: await BrowserStorage.getKey(), wrongPassword: false }, getters: { @@ -142,6 +143,12 @@ export class Accounts implements IModule { ) { state.exportEncData = exportData; }, + updateKeyExport( + state: AccountsState, + key: { enc: string; hash: string } | null + ) { + state.key = key; + }, wrongPassword(state: AccountsState) { state.wrongPassword = true; } @@ -287,6 +294,7 @@ export class Accounts implements IModule { "updateEncExport", await EntryStorage.getExport(state.state.entries, true) ); + state.commit("updateKeyExport", await BrowserStorage.getKey()); return; }, clearFilter: (state: ActionContext) => { From b7fa05f7ed27745f9cf29f79c29d74e57adfed1f Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Thu, 17 Oct 2019 19:21:06 -0500 Subject: [PATCH 19/27] update argon2-browser up the hasing memory to 16 --- package-lock.json | 6 +++--- package.json | 2 +- src/models/argon.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5025bac2d..f84048cd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -359,9 +359,9 @@ "dev": true }, "argon2-browser": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.8.0.tgz", - "integrity": "sha512-5jDXBnzI66PPkSAivtmF0kfCOhSwXJEk/8rYnGaDEy/hgFlp2EsP8qngmPNbttPalBI0vTAN2kq0zRzVE/WFeA==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.12.0.tgz", + "integrity": "sha512-qdEW311acXGGLTjslqO5f7cKG3foP6QUkeuav9au00aRY/B5AvNrqAVlL47a2MUdr6i7dx8Xp+5OreRGoXvCgQ==" }, "argparse": { "version": "1.0.10", diff --git a/package.json b/package.json index 4556a60fe..94736cfa3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "webpack-merge": "^4.2.1" }, "dependencies": { - "argon2-browser": "^1.8.0", + "argon2-browser": "^1.12.0", "crypto-js": "^3.1.9-1", "jssha": "^2.3.1", "qrcode-generator": "^1.4.3", diff --git a/src/models/argon.ts b/src/models/argon.ts index 718cefac4..457751c11 100644 --- a/src/models/argon.ts +++ b/src/models/argon.ts @@ -6,7 +6,7 @@ export class argon { const hash = await argon2.hash({ pass: value, salt, - mem: 1024 * 8, + mem: 1024 * 16, type: argon2.ArgonType.Argon2id }); From cda311495f35878a4790230cf924cdbd0838c016 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 20 Oct 2019 11:31:17 -0500 Subject: [PATCH 20/27] remove argon.hash --- src/background.ts | 4 ++-- src/import.ts | 4 ++-- src/models/storage.ts | 10 ++++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/background.ts b/src/background.ts index 8b6a200ee..b4c8d1fd9 100644 --- a/src/background.ts +++ b/src/background.ts @@ -5,7 +5,7 @@ import { getCredentials } from "./models/credentials"; import { Encryption } from "./models/encryption"; import { EntryStorage, ManagedStorage } from "./models/storage"; import { Dropbox, Drive } from "./models/backup"; -import { argon } from "./models/argon"; +import * as uuid from "uuid/v4"; let cachedPassphrase = ""; chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { @@ -166,7 +166,7 @@ async function getTotp(text: string) { chrome.tabs.sendMessage(id, { action: "secretqr", secret }); } else { const encryption = new Encryption(cachedPassphrase); - const hash = await argon.hash(secret); + const hash = await uuid(); if ( !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && diff --git a/src/import.ts b/src/import.ts index 2f8d6e805..a51466041 100644 --- a/src/import.ts +++ b/src/import.ts @@ -5,7 +5,7 @@ import { loadI18nMessages } from "./store/i18n"; import { Encryption } from "./models/encryption"; import { EntryStorage } from "./models/storage"; import * as CryptoJS from "crypto-js"; -import { argon } from "./models/argon"; +import * as uuid from "uuid/v4"; async function init() { // i18n @@ -152,7 +152,7 @@ export async function getEntryDataFromOTPAuthPerLine(importCode: string) { ) { continue; } else { - const hash = await argon.hash(secret); + const hash = await uuid(); if ( !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && diff --git a/src/models/storage.ts b/src/models/storage.ts index b277f2da4..87cd39f57 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -1,6 +1,6 @@ import { Encryption } from "./encryption"; import { OTPEntry, OTPType } from "./otp"; -import { argon } from "./argon"; +import * as uuid from "uuid/v4"; export class BrowserStorage { private static async getStorageLocation() { @@ -355,20 +355,18 @@ export class EntryStorage { data[hash].type = OTPType[OTPType.hhex]; } - const _hash = await argon.hash(data[hash].secret); - // not a valid / old hash if ( - !/^\$argon2(?:d|i|di|id)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([A-Za-z0-9+/=]+)\$([A-Za-z0-9+/=]*)$/.test( + !/^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}$/.test( hash ) ) { + const _hash = await uuid(); + data[_hash] = data[hash]; data[_hash].hash = _hash; delete data[hash]; - } - if (data[_hash]) { data[_hash].secret = encryption.getEncryptedString( data[_hash].secret ); From ce43fb73e508e323a6a4c1cdfa148d21e5cf8bec Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 20 Oct 2019 14:15:44 -0500 Subject: [PATCH 21/27] Sandbox argon2 in Chrome Chrome does not allow webassembly without adding 'unsafe-eval' to CSP. --- manifest-chrome.json | 6 +++ src/argon.ts | 67 +++++++++++++++++++++++++ src/components/Popup/AddAccountPage.vue | 1 - src/models/argon.ts | 30 ----------- src/store/Accounts.ts | 42 ++++++++++++++-- view/argon.html | 7 +++ view/popup.html | 8 ++- webpack.config.js | 1 + 8 files changed, 127 insertions(+), 35 deletions(-) create mode 100644 src/argon.ts delete mode 100644 src/models/argon.ts create mode 100644 view/argon.html diff --git a/manifest-chrome.json b/manifest-chrome.json index 5d8788d2d..66144ead8 100644 --- a/manifest-chrome.json +++ b/manifest-chrome.json @@ -36,6 +36,12 @@ ], "persistent": true }, + "sandbox": { + "pages": [ + "view/argon.html" + ], + "content_security_policy": "sandbox allow-scripts; script-src 'self' 'unsafe-eval';" + }, "permissions": [ "activeTab", "storage", diff --git a/src/argon.ts b/src/argon.ts new file mode 100644 index 000000000..ad64a3449 --- /dev/null +++ b/src/argon.ts @@ -0,0 +1,67 @@ +import * as argon2 from "argon2-browser"; + +window.addEventListener("message", event => { + const message = event.data; + const source = event.source as Window; + + if (!source) { + return; + } + + switch (message.action) { + case "hash": + argon + .hash(message.value) + .then(hash => { + source.postMessage({ response: hash }, event.origin); + }) + .catch(() => { + source.postMessage({ response: "error" }, event.origin); + }); + break; + + case "verify": + argon + .compareHash(message.hash, message.value) + .then(result => { + source.postMessage({ response: result }, event.origin); + }) + .catch(() => { + source.postMessage({ response: "error" }, event.origin); + }); + break; + + default: + break; + } + return; +}); + +class argon { + static async hash(value: string) { + const salt = window.crypto.getRandomValues(new Uint8Array(16)); + const hash = await argon2.hash({ + pass: value, + salt, + mem: 1024 * 16, + type: argon2.ArgonType.Argon2id + }); + + return hash.encoded; + } + + static compareHash(hash: string, value: string) { + return new Promise((resolve: (value: boolean) => void) => { + argon2 + .verify({ + pass: value, + encoded: hash + }) + .then(() => resolve(true)) + .catch((e: { message: string; code: number }) => { + console.error("Error decoding hash", e); + resolve(false); + }); + }); + } +} diff --git a/src/components/Popup/AddAccountPage.vue b/src/components/Popup/AddAccountPage.vue index 21f718c11..72e7e4f73 100644 --- a/src/components/Popup/AddAccountPage.vue +++ b/src/components/Popup/AddAccountPage.vue @@ -31,7 +31,6 @@ import Vue from "vue"; import { mapState } from "vuex"; import { OTPType, OTPEntry } from "../../models/otp"; -import { argon } from "../../models/argon"; export default Vue.extend({ data: function(): { diff --git a/src/models/argon.ts b/src/models/argon.ts deleted file mode 100644 index 457751c11..000000000 --- a/src/models/argon.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as argon2 from "argon2-browser"; - -export class argon { - static async hash(value: string) { - const salt = window.crypto.getRandomValues(new Uint8Array(16)); - const hash = await argon2.hash({ - pass: value, - salt, - mem: 1024 * 16, - type: argon2.ArgonType.Argon2id - }); - - return hash.encoded; - } - - static compareHash(hash: string, value: string) { - return new Promise((resolve: (value: boolean) => void) => { - argon2 - .verify({ - pass: value, - encoded: hash - }) - .then(() => resolve(true)) - .catch((e: { message: string; code: number }) => { - console.error("Error decoding hash", e); - resolve(false); - }); - }); - } -} diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index fbaa087e2..86fe3f4e9 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -3,7 +3,6 @@ import { Encryption } from "../models/encryption"; import * as CryptoJS from "crypto-js"; import { OTPType } from "../models/otp"; import { ActionContext } from "vuex"; -import { argon } from "../models/argon"; export class Accounts implements IModule { async getModule() { @@ -212,7 +211,27 @@ export class Accounts implements IModule { const randomKey = crypto.getRandomValues(new Uint32Array(30)); const wordArray = CryptoJS.lib.WordArray.create(randomKey); const encKey = CryptoJS.AES.encrypt(wordArray, password).toString(); - const encKeyHash = await argon.hash(wordArray.toString()); + const encKeyHash = await new Promise( + (resolve: (value: string) => void) => { + const iframe = document.getElementById("argon-sandbox"); + const message = { + action: "hash", + value: wordArray.toString() + }; + if (iframe) { + window.addEventListener("message", response => { + resolve(response.data.response); + }); + //@ts-ignore + iframe.contentWindow.postMessage(message, "*"); + } + } + ); + + if (encKeyHash === "error") { + state.commit("style/hideInfo", true, { root: true }); + return; + } // store key await BrowserStorage.set({ @@ -233,8 +252,25 @@ export class Accounts implements IModule { } else { // --- decrypt using key const key = CryptoJS.AES.decrypt(encKey.enc, password).toString(); + const isCorrectPassword = await new Promise( + (resolve: (value: string) => void) => { + const iframe = document.getElementById("argon-sandbox"); + const message = { + action: "verify", + value: key, + hash: encKey.hash + }; + if (iframe) { + window.addEventListener("message", response => { + resolve(response.data.response); + }); + //@ts-ignore + iframe.contentWindow.postMessage(message, "*"); + } + } + ); - if (!(await argon.compareHash(encKey.hash, key))) { + if (!isCorrectPassword) { state.commit("wrongPassword"); state.commit("currentView/changeView", "EnterPasswordPage", { root: true diff --git a/view/argon.html b/view/argon.html new file mode 100644 index 000000000..eb1dc47ac --- /dev/null +++ b/view/argon.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/view/popup.html b/view/popup.html index 2b4c75747..e1ad783f8 100644 --- a/view/popup.html +++ b/view/popup.html @@ -3,13 +3,19 @@ - +
+ diff --git a/webpack.config.js b/webpack.config.js index aaf0d809f..7c91c1cdd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ module.exports = { mode: "development", devtool: "source-map", entry: { + argon: "./src/argon.ts", background: "./src/background.ts", content: "./src/content.ts", popup: "./src/popup.ts", From b8a8f8891c5ada2fcebcf44a5a7c05250e9458b6 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 20 Oct 2019 16:19:43 -0500 Subject: [PATCH 22/27] - show pass if key in storage - change password --- src/argon.ts | 22 ++++---------- src/store/Accounts.ts | 67 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/argon.ts b/src/argon.ts index ad64a3449..cd482f22f 100644 --- a/src/argon.ts +++ b/src/argon.ts @@ -10,25 +10,15 @@ window.addEventListener("message", event => { switch (message.action) { case "hash": - argon - .hash(message.value) - .then(hash => { - source.postMessage({ response: hash }, event.origin); - }) - .catch(() => { - source.postMessage({ response: "error" }, event.origin); - }); + argon.hash(message.value).then(hash => { + source.postMessage({ response: hash }, event.origin); + }); break; case "verify": - argon - .compareHash(message.hash, message.value) - .then(result => { - source.postMessage({ response: result }, event.origin); - }) - .catch(() => { - source.postMessage({ response: "error" }, event.origin); - }); + argon.compareHash(message.hash, message.value).then(result => { + source.postMessage({ response: result }, event.origin); + }); break; default: diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts index 86fe3f4e9..4953fcedd 100644 --- a/src/store/Accounts.ts +++ b/src/store/Accounts.ts @@ -228,7 +228,7 @@ export class Accounts implements IModule { } ); - if (encKeyHash === "error") { + if (!encKeyHash) { state.commit("style/hideInfo", true, { root: true }); return; } @@ -295,18 +295,65 @@ export class Accounts implements IModule { state: ActionContext, password: string ) => { - state.state.encryption.updateEncryptionPassword(password); + if (password) { + const randomKey = crypto.getRandomValues(new Uint32Array(30)); + const wordArray = CryptoJS.lib.WordArray.create(randomKey); + const encKey = CryptoJS.AES.encrypt(wordArray, password).toString(); + const encKeyHash = await new Promise( + (resolve: (value: string) => void) => { + const iframe = document.getElementById("argon-sandbox"); + const message = { + action: "hash", + value: wordArray.toString() + }; + if (iframe) { + window.addEventListener("message", response => { + resolve(response.data.response); + }); + //@ts-ignore + iframe.contentWindow.postMessage(message, "*"); + } + } + ); - chrome.runtime.sendMessage({ - action: "cachePassphrase", - value: password - }); + if (!encKeyHash) { + return; + } - for (const entry of state.state.entries) { - await entry.changeEncryption(state.state.encryption); - } + // store key + await BrowserStorage.set({ + key: { enc: encKey, hash: encKeyHash } + }); + // change entry encryption and regen hash + for (const entry of state.state.entries) { + await entry.changeEncryption( + new Encryption(wordArray.toString()) + ); + await entry.genUUID(); + } - await state.dispatch("updateEntries"); + state.state.encryption.updateEncryptionPassword( + wordArray.toString() + ); + await state.dispatch("updateEntries"); + + chrome.runtime.sendMessage({ + action: "cachePassphrase", + value: wordArray.toString() + }); + } else { + for (const entry of state.state.entries) { + await entry.changeEncryption(new Encryption("")); + } + + state.state.encryption.updateEncryptionPassword(""); + + await state.dispatch("updateEntries"); + + chrome.runtime.sendMessage({ + action: "lock" + }); + } // remove cached passphrase in old version localStorage.removeItem("encodedPhrase"); From 287627b39dcd85ab1217477429dbc52127673193 Mon Sep 17 00:00:00 2001 From: Brendan Early Date: Sun, 20 Oct 2019 17:07:58 -0500 Subject: [PATCH 23/27] import --- src/components/Import/FileImport.vue | 31 +++++++++++++++++++++++----- src/components/Import/TextImport.vue | 27 +++++++++++++++++++++--- src/store/Accounts.ts | 2 ++ 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/components/Import/FileImport.vue b/src/components/Import/FileImport.vue index 02e80207b..b2186ef5a 100644 --- a/src/components/Import/FileImport.vue +++ b/src/components/Import/FileImport.vue @@ -26,6 +26,7 @@