From 428f591330bd7eabe2ef56977b4d8cee564c50d3 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Tue, 16 Oct 2018 12:48:51 -0400 Subject: [PATCH] Don't send token in request body. Instead, we want to hash a header to sign a request with a client nonce, http method and URL. This is a first step towards protecting the backend against eavesdropping. Please note that this will still be susceptible to replay attacks. --- embark-ui/package-lock.json | 97 +++++++++++++++++++----------- embark-ui/package.json | 1 + embark-ui/src/services/api.js | 33 +++++++++- lib/modules/authenticator/index.js | 23 ++++++- 4 files changed, 114 insertions(+), 40 deletions(-) diff --git a/embark-ui/package-lock.json b/embark-ui/package-lock.json index 72a1042f3..404a194d5 100644 --- a/embark-ui/package-lock.json +++ b/embark-ui/package-lock.json @@ -496,7 +496,7 @@ }, "axios": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "requires": { "follow-redirects": "^1.3.0", @@ -777,7 +777,7 @@ }, "babel-plugin-istanbul": { "version": "4.1.6", - "resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", "requires": { "babel-plugin-syntax-object-rest-spread": "^6.13.0", @@ -823,7 +823,7 @@ }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" }, "babel-plugin-syntax-trailing-function-commas": { @@ -1573,7 +1573,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "requires": { "buffer-xor": "^1.0.3", @@ -1607,13 +1607,21 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" } }, + "browserify-sha3": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/browserify-sha3/-/browserify-sha3-0.0.1.tgz", + "integrity": "sha1-P/NKMAbvFcD7NWflQbkaI0ASPRE=", + "requires": { + "js-sha3": "^0.3.1" + } + }, "browserify-sign": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", @@ -1655,7 +1663,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { "base64-js": "^1.0.2", @@ -2321,7 +2329,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "requires": { "cipher-base": "^1.0.1", @@ -2333,7 +2341,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "requires": { "cipher-base": "^1.0.3", @@ -2807,7 +2815,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "requires": { "bn.js": "^4.1.0", @@ -2936,7 +2944,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" }, "duplexer3": { @@ -3364,7 +3372,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "requires": { "graceful-fs": "^4.1.2", @@ -3527,7 +3535,7 @@ }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, "eventsource": { @@ -3662,7 +3670,7 @@ }, "express": { "version": "4.16.3", - "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "requires": { "accepts": "~1.3.5", @@ -3956,7 +3964,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", @@ -4575,7 +4583,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "get-value": { @@ -5036,7 +5044,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { "depd": "~1.1.2", @@ -5289,7 +5297,7 @@ }, "inline-style-prefixer": { "version": "2.0.5", - "resolved": "http://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz", "integrity": "sha1-wVPH6I/YT+9cYC6VqBaLJ3BnH+c=", "requires": { "bowser": "^1.0.0", @@ -5421,7 +5429,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "requires": { "builtin-modules": "^1.0.0" @@ -5563,7 +5571,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-cwd": { @@ -6389,6 +6397,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.8.tgz", "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==" }, + "js-sha3": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.3.1.tgz", + "integrity": "sha1-hhIoAhQvCChQKg0d7h2V4lO7AkM=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6482,7 +6495,7 @@ }, "json5": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, "jsonfile": { @@ -6514,6 +6527,15 @@ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=" }, + "keccakjs": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/keccakjs/-/keccakjs-0.2.1.tgz", + "integrity": "sha1-HWM6+QfvMFu/ny+mFtVsRFYd+k0=", + "requires": { + "browserify-sha3": "^0.0.1", + "sha3": "^1.1.0" + } + }, "killable": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", @@ -6569,7 +6591,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", @@ -7000,7 +7022,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mixin-deep": { @@ -7024,7 +7046,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -7067,8 +7089,7 @@ "nan": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "optional": true + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, "nanomatch": { "version": "1.2.13", @@ -7497,7 +7518,7 @@ }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "requires": { "asn1.js": "^4.0.0", @@ -7676,7 +7697,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" } } @@ -9286,7 +9307,7 @@ }, "react-redux": { "version": "5.0.7", - "resolved": "http://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", "requires": { "hoist-non-react-statics": "^2.5.0", @@ -9885,7 +9906,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -10038,13 +10059,21 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, + "sha3": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.2.tgz", + "integrity": "sha1-pmxQmN5MJbyIM27ItIF9AFvKe6k=", + "requires": { + "nan": "2.10.0" + } + }, "shallowequal": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", @@ -10460,7 +10489,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -10571,7 +10600,7 @@ }, "table": { "version": "4.0.3", - "resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "requires": { "ajv": "^6.0.1", @@ -10662,7 +10691,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "thunky": { @@ -11724,7 +11753,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", diff --git a/embark-ui/package.json b/embark-ui/package.json index 5559ef52d..194a3d3c4 100644 --- a/embark-ui/package.json +++ b/embark-ui/package.json @@ -37,6 +37,7 @@ "history": "^4.7.2", "html-webpack-plugin": "2.29.0", "jest": "20.0.4", + "keccakjs": "0.2.1", "monaco-editor": "^0.14.3", "monaco-editor-webpack-plugin": "^1.5.4", "object-assign": "4.1.1", diff --git a/embark-ui/src/services/api.js b/embark-ui/src/services/api.js index 4c6e510e5..616f651d9 100644 --- a/embark-ui/src/services/api.js +++ b/embark-ui/src/services/api.js @@ -1,9 +1,36 @@ import axios from "axios"; +import keccak from "keccakjs"; + +function hash(cnonce, token, type, path, params = {}) { + let hash = new keccak(); + hash.update(JSON.stringify(cnonce)); + hash.update(token); + hash.update(type.toUpperCase()); + hash.update(`/embark-api${path}`); + return hash.digest('hex'); +} function request(type, path, params = {}) { - axios.defaults.headers.common['Authorization'] = params.credentials.token; - const endpoint = `http://${params.credentials.host}/embark-api${path}`; - return axios[type](endpoint, params) + const cnonce = Date.now() + Math.random(); + + // Extract credentials out of the params and delete so the token doesn't get sent + // as cleartext. + let credentials = params.credentials; + delete params.credentials; + + let requestHash = hash(cnonce, credentials.token, type, path, params); + + const endpoint = `http://${credentials.host}/embark-api${path}`; + const req = { + method: type, + url: endpoint, + headers: { + 'X-Embark-Request-Hash': requestHash, + 'X-Embark-Cnonce': cnonce + } + } + + return axios(req, params) .then((response) => { return (response.data && response.data.error) ? {error: response.data.error} : {response, error: null}; }).catch((error) => { diff --git a/lib/modules/authenticator/index.js b/lib/modules/authenticator/index.js index 98e98ad7f..0786174d1 100644 --- a/lib/modules/authenticator/index.js +++ b/lib/modules/authenticator/index.js @@ -1,5 +1,6 @@ const uuid = require('uuid/v1'); const utils = require("../../utils/utils.js") +const keccak = require('keccakjs'); const ERROR_OBJ = {error: __('Wrong authentication token. Get your token from the Embark console by typing `token`')}; @@ -14,12 +15,25 @@ class Authenticator { this.registerEvents(); } + generateRequestHash(req) { + let cnonce = req.headers['x-embark-cnonce']; + let hash = new keccak(); + hash.update(cnonce); + hash.update(this.authToken); + hash.update(req.method); + hash.update(req.url); + return hash.digest('hex'); + } + registerCalls() { + let self = this; + this.embark.registerAPICall( 'post', '/embark-api/authenticate', (req, res) => { - if (req.body.token !== this.authToken) { + let hash = self.generateRequestHash(req); + if(hash !== req.headers['x-embark-request-hash']) { this.logger.warn(__('Someone tried and failed to authenticate to the backend')); this.logger.warn(__('- User-Agent: %s', req.headers['user-agent'])); this.logger.warn(__('- Referer: %s', req.headers.referer)); @@ -51,14 +65,17 @@ class Authenticator { } registerEvents() { + let self = this; + this.events.once('outputDone', () => { const {port, host} = this.embark.config.webServerConfig; this.logger.info(__('Access the web backend with the following url: %s', (`http://${host}:${port}/embark?token=${this.authToken}`.underline))); }); - this.events.setCommandHandler('authenticator:authorize', (token, cb) => { - if (token !== this.authToken) { + this.events.setCommandHandler('authenticator:authorize', (req, cb) => { + let hash = self.generateRequestHash(req); + if(hash !== req.headers['x-embark-request-hash']) { return cb(ERROR_OBJ); } cb();