From 5bf9aeb2a1ad59e7bcf64d47a1449934f20a9805 Mon Sep 17 00:00:00 2001 From: leolivier Date: Sun, 18 Mar 2018 17:37:36 +0100 Subject: [PATCH 1/6] New plugin haveibeenpwned --- docs/plugins/haveibeenpwned/index.html | 21 ++ docs/plugins/haveibeenpwned/manifest.json | 18 ++ docs/plugins/haveibeenpwned/plugin.js | 250 ++++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 docs/plugins/haveibeenpwned/index.html create mode 100644 docs/plugins/haveibeenpwned/manifest.json create mode 100644 docs/plugins/haveibeenpwned/plugin.js diff --git a/docs/plugins/haveibeenpwned/index.html b/docs/plugins/haveibeenpwned/index.html new file mode 100644 index 0000000..3f75e6c --- /dev/null +++ b/docs/plugins/haveibeenpwned/index.html @@ -0,0 +1,21 @@ + + + + + KeeWeb Plugin: HaveIBeenPwned + + + + +

KeeWeb Plugin: HaveIBeenPwned

+https://plugins.keeweb.info/plugins/haveibeenpwned +

This plugin checks the HaveIBeenPwned site each time you enter either a user name or a password to look if they have been pawned in a breach. The password is safely checked (not sent over the network).

+

Freely inspired from the equivalent keepass plugin

+ + + diff --git a/docs/plugins/haveibeenpwned/manifest.json b/docs/plugins/haveibeenpwned/manifest.json new file mode 100644 index 0000000..eb9612b --- /dev/null +++ b/docs/plugins/haveibeenpwned/manifest.json @@ -0,0 +1,18 @@ +{ + "version": "0.0.1", + "manifestVersion": "0.1.0", + "name": "haveibeenpwned", + "description": "Check HaveIBeenPwned password database", + "author": { + "name": "Olivier LEVILLAIN", + "email": "olivier.levillain@free.fr", + "url": "https://github.com/leolivier" + }, + "resources": { + "js": "SW1cs/8tJoK2bZHqncy4J17/PbFn81tacYjfF6TKm297f6F+MVCPWT0ErZN7LzOsCEbOwVEYzrIrJXrOm0u/Gbd7v3TxGZra7hYcLKzknCcunW4vWTxA75rgQ/+ep2ef/voQhPif7kAPW7FwwFKMK+DcRamSEK/OSj0QhG31i96ynTKA1ET6T5Rcr5+0Sltsm4yM7fLC2uYhbRa6ce1aB9uY9bXq5uze7Lw9G/yXoRMgAdo3+2D6dZJk5x3Co5CLkFzVNKu/47HquYpbygec9cp5/Zuw9pn8q80ULEkELA8L5KieCL38zfE6tUAlTmparQfTMDg1+nfgDIxON0l94Q==", + "css": "HT4dBpVJ6MOr3EeB1Ic8aHXpgYzPqnvTiWJT3acr/fZUpvrMsoqwChtp/zmw1v5ApZ+wyswH5DMvJazgZeAJ19zeTU5+0+xKJ9DDcsIz9dtnJGbF0DhRkQjbVozQdibEF4QGBAOvWu675LoC0EG/ujXlIon0mO8rgNj8B81oJwFbX9xqmdmFJrYMWXibCHtgK7+4MVJN4HvuUIIpkR6n/gGdabhUQDAYpZ8G9xDdgZjL3CcbmirXT5bxDG7npKOjupqQDCKdWpsx3OcTw0uctVgvALkui1iHOX/r3V9eJz8fCkgk9oa5Jg4e7jV5SEq8lQwbwIpGRFpkl4TRlF7nwQ==" + }, + "url": "https://github.com/leolivier/keeweb-haveibeenpwned", + "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9Cay/z7whBsHcf9klDjlA4qylWT7a/igTJ2nvUq2XuQrx98PTOexzzzg5oflk8nPEaMIsaFIf90V/rvQjJ1z9DR4zuQKDb4/GZVzxoylECAwNk80LvSPc1G0+6mwXIFp48wc6Advd4iYQCMkzWDCJXEm/1E+q85ty+H6EaLleKcJI0vlW96bbA9vFCmOsM5PYZfoGnVFRBLVthyUcGneilMvsxu5J7DKggQKPs04/WQZ5oHbUG83mxkxTdYDC3glpvV4BiaAD6z+2usO+fA97bXb+rY3O2iHJgWsa7jH0ybO0Nif6txE4d2+LJOLmfoImv7kdyu/eN3A78KejCOhwIDAQAB", + "license": "MIT" +} \ No newline at end of file diff --git a/docs/plugins/haveibeenpwned/plugin.js b/docs/plugins/haveibeenpwned/plugin.js new file mode 100644 index 0000000..7f2fcae --- /dev/null +++ b/docs/plugins/haveibeenpwned/plugin.js @@ -0,0 +1,250 @@ +/** + * KeeWeb plugin: haveibeenpwned + * @author Olivier LEVILLAIN + * @license MIT + */ + +const DetailsView = require("views/details/details-view"); +const Alerts = require("comp/alerts"); +const Logger = require('util/logger'); +const InputFx = require('util/input-fx'); + +const detailsViewFieldChanged = DetailsView.prototype.fieldChanged; + +DetailsView.prototype.checkPwnedPwd = false; +DetailsView.prototype.checkPwnedName = false; +DetailsView.prototype.blockPwnedPwd = false; +DetailsView.prototype.blockPwnedName = false; +DetailsView.prototype.logger = new Logger("HaveIBeenPwned"); + +DetailsView.prototype._alert = function (msg) { + Alerts.info({ body: msg, title: "HaveIBeenPwned" }); +} + +DetailsView.prototype.checkPwnedOnSettingsChanged = function (changes) { + //if (changes['CheckPwnedPwd'] || changes['CheckPwnedName'] || changes['CheckPwnedName'] || changes['CheckPwnedName']) { + // info('Full HaveIBeenPwned check not yet implemented. Checks are done one by one when you change a name or a password.'); + //} +}; + +seen = []; +class HIBPUtils { + constructor() { + seen = []; + } + + replacer(key, value) { + if (value != null && typeof value == "object") { + if (seen.indexOf(value) >= 0) { + return; + } + seen.push(value); + } + return value; + } + stringify(obj) { + var ret = JSON.stringify(obj, this.replacer); + seen = []; + return ret; + } + xhrcall (config) { + const xhr = new XMLHttpRequest(); + if (config.responseType) { + xhr.responseType = config.responseType; + } + const statuses = config.statuses || [200]; + xhr.addEventListener('load', () => { + if (statuses.indexOf(xhr.status) >= 0) { + return config.success && config.success(xhr.response, xhr); + } else { + return config.error && config.error('http status ' + xhr.status, xhr); + } + }); + xhr.addEventListener('error', () => { + return config.error && config.error('network error', xhr); + }); + xhr.addEventListener('timeout', () => { + return config.error && config.error('timeout', xhr); + }); + xhr.open(config.method || 'GET', config.url); + if (config.headers) config.headers.forEach((value, key) => { + xhr.setRequestHeader(key, value); + }); + xhr.send(config.data); + }; + + hex (buffer) { + var hexCodes = []; + var view = new DataView(buffer); + for (var i = 0; i < view.byteLength; i += 4) { + // Using getUint32 reduces the number of iterations needed (we process 4 bytes each time) + var value = view.getUint32(i) + // toString(16) will give the hex representation of the number without padding + var stringValue = value.toString(16) + // We use concatenation and slice for padding + var padding = '00000000' + var paddedValue = (padding + stringValue).slice(-padding.length) + hexCodes.push(paddedValue); + } + + // Join all the hex strings into one + return hexCodes.join(""); + } + + digest(algo, str) { + // We transform the string into an arraybuffer. + const buffer = new TextEncoder("utf-8").encode(str); + const subtle = window.crypto.subtle || window.crypto.webkitSubtle; + var _self = this; + return crypto.subtle.digest(algo, buffer).then(function (hash) { + return _self.hex(hash); + }); + } + + + sha1(str) { + return this.digest("SHA-1", str); + } + + sha256(str) { + return this.digest("SHA-256", str); + } +} + +DetailsView.prototype.checkNamePwned = function (name) { + this.logger.info('check hibp name ' + name); + name = encodeURIComponent(name); + const url = `https://haveibeenpwned.com/api/v2/breachedaccount/${name}?truncateResponse=true`; + this.logger.info('url ' + url); + new HIBPUtils().xhrcall({ + url: url, + method: 'GET', + responseType: 'json', + headers: undefined, + data: null, + statuses: [200, 404], + success: (data, xhr) => { + this.logger.info('xhr ' + JSON.stringify(xhr)); + if (data && data.length > 0) { + this.logger.info('found breaches ' + JSON.stringify(data)); + var breaches = ""; + data.forEach(breach => { breaches += `
  • ${breach.Name}
  • \n`; }); + this._alert(`WARNING! This account has been pawned in the following breaches
    \n\n

    Please check on https://haveibeenpwned.com\n`); + this.userEditView.$el.focus(); + this.userEditView.$el.addClass('input--error'); + InputFx.shake(this.userEditView.$el); + } else { + this.logger.info("check pwnd name passed..."); + this.userEditView.$el.removeClass('input--error'); + } + }, + error: (e, xhr) => { + let err = xhr.response && xhr.response.error || new Error('Network error'); + this.logger.error('Pwned Password API error', 'GET', xhr.status, err); + err.status = xhr.status; + } + }); +}; + +DetailsView.prototype.checkPwdPwned = function (passwordHash) { + this.logger.info('check hibp pwd (hash) ' + passwordHash); + + prefix = passwordHash.substring(0, 5); + new HIBPUtils().xhrcall({ + url: `https://api.pwnedpasswords.com/range/${prefix}`, + method: 'GET', + responseType: 'text', + headers: undefined, + data: null, + statuses: [200, 404], + success: data => { + if (data) { + this.logger.info('found breaches ' + JSON.stringify(data)); + data.split('\r\n').forEach(line => { + h = line.split(':'); + suffix = h[0]; nb = h[1]; + if (prefix + suffix === passwordHash) { + this._alert(`WARNING: This password is referenced as pawned ${nb} times on https://haveibeenpwned.com!\n`); + this.passEditView.$el.focus(); + this.passEditView.$el.addClass('input--error'); + InputFx.shake(this.passEditView.$el); + } + }); + } else { + this.logger.info("check pwnd passwd passed..."); + this.passEditView.$el.removeClass('input--error'); + } + }, + error: (e, xhr) => { + let err = xhr.response && xhr.response.error || new Error('Network error'); + this.logger.error('Pwned Password API error', 'GET', xhr.status, err); + err.status = xhr.status; + } + }); +}; + +DetailsView.prototype.fieldChanged = function (e) { + //this.logger.info('field changed ' + new HIBPUtils().stringify(e)); + detailsViewFieldChanged.apply(this, [e]); + if (e.field) { + if (e.field === '$Password' && this.checkPwnedPwd) { + if (this.passEditView.value) { + const pwd = this.passEditView.value.getText(); + if (pwd.replace(/\s/, '') !== '' && !pwd.startsWith('{REF:')) { + new HIBPUtils().sha1(pwd).then(hash => { + this.checkPwdPwned(hash.toUpperCase()); + }); + } + } + } else if (e.field === '$UserName' && this.checkPwnedName) { + this.checkNamePwned(e.val); + } + } +}; + +module.exports.getSettings = function () { + return [{ + name: 'checkPwnedPwd', + label: 'Check passwords against HaveIBeenPwned list', + type: 'checkbox', + value: 'true' + }, { + name: 'checkPwnedName', + label: 'Check user ids against HaveIBeenPwned list', + type: 'checkbox', + value: 'true' + }, { + name: 'blockPwnedPwd', + label: 'Block pwned passwords if they are in HaveIBeenPwned list', + type: 'checkbox', + value: 'true' + }, { + name: 'blockPwnedName', + label: 'Block pwned names if they are in HaveIBeenPwned list', + type: 'checkbox', + value: 'true' + }]; +}; + +module.exports.setSettings = function (changes) { + // apply changed settings in plugin logic + // this method will be called: + // 1. when any of settings fields is modified by user + // 2. after plugin startup, with saved values + // only changed settings will be passed + + // example: { MyText: 'value', MySel: 'selected-value', MyCheckbox: true } + // info(JSON.stringify(changes)); + s = '' + for (field in changes) { + ccfield = field.substr(0, 1).toLowerCase() + field.substring(1); + DetailsView.prototype[ccfield] = changes[field]; + //s += ccfield + '=' + DetailsView.prototype[ccfield] + '; '; + } + DetailsView.prototype.checkPwnedOnSettingsChanged.apply(changes); + //alert(s); +}; + +module.exports.uninstall = function () { + fieldChanged = detailsViewFieldChanged; +}; From 63179646809368cd1b1129e2bbab01c7e503d153 Mon Sep 17 00:00:00 2001 From: leolivier Date: Sun, 18 Mar 2018 17:48:06 +0100 Subject: [PATCH 2/6] updated manifest.json --- docs/plugins/haveibeenpwned/manifest.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/plugins/haveibeenpwned/manifest.json b/docs/plugins/haveibeenpwned/manifest.json index eb9612b..52eb0a9 100644 --- a/docs/plugins/haveibeenpwned/manifest.json +++ b/docs/plugins/haveibeenpwned/manifest.json @@ -9,10 +9,9 @@ "url": "https://github.com/leolivier" }, "resources": { - "js": "SW1cs/8tJoK2bZHqncy4J17/PbFn81tacYjfF6TKm297f6F+MVCPWT0ErZN7LzOsCEbOwVEYzrIrJXrOm0u/Gbd7v3TxGZra7hYcLKzknCcunW4vWTxA75rgQ/+ep2ef/voQhPif7kAPW7FwwFKMK+DcRamSEK/OSj0QhG31i96ynTKA1ET6T5Rcr5+0Sltsm4yM7fLC2uYhbRa6ce1aB9uY9bXq5uze7Lw9G/yXoRMgAdo3+2D6dZJk5x3Co5CLkFzVNKu/47HquYpbygec9cp5/Zuw9pn8q80ULEkELA8L5KieCL38zfE6tUAlTmparQfTMDg1+nfgDIxON0l94Q==", - "css": "HT4dBpVJ6MOr3EeB1Ic8aHXpgYzPqnvTiWJT3acr/fZUpvrMsoqwChtp/zmw1v5ApZ+wyswH5DMvJazgZeAJ19zeTU5+0+xKJ9DDcsIz9dtnJGbF0DhRkQjbVozQdibEF4QGBAOvWu675LoC0EG/ujXlIon0mO8rgNj8B81oJwFbX9xqmdmFJrYMWXibCHtgK7+4MVJN4HvuUIIpkR6n/gGdabhUQDAYpZ8G9xDdgZjL3CcbmirXT5bxDG7npKOjupqQDCKdWpsx3OcTw0uctVgvALkui1iHOX/r3V9eJz8fCkgk9oa5Jg4e7jV5SEq8lQwbwIpGRFpkl4TRlF7nwQ==" + "js": "SW1cs/8tJoK2bZHqncy4J17/PbFn81tacYjfF6TKm297f6F+MVCPWT0ErZN7LzOsCEbOwVEYzrIrJXrOm0u/Gbd7v3TxGZra7hYcLKzknCcunW4vWTxA75rgQ/+ep2ef/voQhPif7kAPW7FwwFKMK+DcRamSEK/OSj0QhG31i96ynTKA1ET6T5Rcr5+0Sltsm4yM7fLC2uYhbRa6ce1aB9uY9bXq5uze7Lw9G/yXoRMgAdo3+2D6dZJk5x3Co5CLkFzVNKu/47HquYpbygec9cp5/Zuw9pn8q80ULEkELA8L5KieCL38zfE6tUAlTmparQfTMDg1+nfgDIxON0l94Q==" }, - "url": "https://github.com/leolivier/keeweb-haveibeenpwned", + "url": "https://plugins.keeweb.info/plugins/haveibeenpwned", "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9Cay/z7whBsHcf9klDjlA4qylWT7a/igTJ2nvUq2XuQrx98PTOexzzzg5oflk8nPEaMIsaFIf90V/rvQjJ1z9DR4zuQKDb4/GZVzxoylECAwNk80LvSPc1G0+6mwXIFp48wc6Advd4iYQCMkzWDCJXEm/1E+q85ty+H6EaLleKcJI0vlW96bbA9vFCmOsM5PYZfoGnVFRBLVthyUcGneilMvsxu5J7DKggQKPs04/WQZ5oHbUG83mxkxTdYDC3glpvV4BiaAD6z+2usO+fA97bXb+rY3O2iHJgWsa7jH0ybO0Nif6txE4d2+LJOLmfoImv7kdyu/eN3A78KejCOhwIDAQAB", "license": "MIT" -} \ No newline at end of file +} From f28857edf006405fe9e40bd57a3d87d000423201 Mon Sep 17 00:00:00 2001 From: leolivier Date: Sun, 18 Mar 2018 20:14:15 +0100 Subject: [PATCH 3/6] Updated signature --- docs/plugins/haveibeenpwned/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/haveibeenpwned/manifest.json b/docs/plugins/haveibeenpwned/manifest.json index 52eb0a9..72071c2 100644 --- a/docs/plugins/haveibeenpwned/manifest.json +++ b/docs/plugins/haveibeenpwned/manifest.json @@ -9,9 +9,9 @@ "url": "https://github.com/leolivier" }, "resources": { - "js": "SW1cs/8tJoK2bZHqncy4J17/PbFn81tacYjfF6TKm297f6F+MVCPWT0ErZN7LzOsCEbOwVEYzrIrJXrOm0u/Gbd7v3TxGZra7hYcLKzknCcunW4vWTxA75rgQ/+ep2ef/voQhPif7kAPW7FwwFKMK+DcRamSEK/OSj0QhG31i96ynTKA1ET6T5Rcr5+0Sltsm4yM7fLC2uYhbRa6ce1aB9uY9bXq5uze7Lw9G/yXoRMgAdo3+2D6dZJk5x3Co5CLkFzVNKu/47HquYpbygec9cp5/Zuw9pn8q80ULEkELA8L5KieCL38zfE6tUAlTmparQfTMDg1+nfgDIxON0l94Q==" + "js": "WdONjQv+jhcNeqvi8wyfuwWIIdoG3A5Dgt7dKN6jM7UqvUulAgZWWFrm1fcgWQK1t/EeK6/OMJxFHfBQWLUeBadghOCzhDOA4pglkQE3dj3PdZmTuQ1x41NH19EzfO0Mn0KSNqU9vjnSzcDvzi6pIP9SHMqmj/UVEYHjDt17+cFYpX/U4hlGLMUpgkOLOpPumuXSQD4OENqsvuNlRsYeNfu6FBK9gAJZ4ZqpQ1G19glEBexbad1OnhVMNCyhGNVibUoliq+7h/bjsLkYZkkOJlpxwh5UmD5sr2nDfg0QfpynTut8rWGFfymbGUhQTU5vqILVR6JaHbjof3nXRII5Ig==" }, "url": "https://plugins.keeweb.info/plugins/haveibeenpwned", "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9Cay/z7whBsHcf9klDjlA4qylWT7a/igTJ2nvUq2XuQrx98PTOexzzzg5oflk8nPEaMIsaFIf90V/rvQjJ1z9DR4zuQKDb4/GZVzxoylECAwNk80LvSPc1G0+6mwXIFp48wc6Advd4iYQCMkzWDCJXEm/1E+q85ty+H6EaLleKcJI0vlW96bbA9vFCmOsM5PYZfoGnVFRBLVthyUcGneilMvsxu5J7DKggQKPs04/WQZ5oHbUG83mxkxTdYDC3glpvV4BiaAD6z+2usO+fA97bXb+rY3O2iHJgWsa7jH0ybO0Nif6txE4d2+LJOLmfoImv7kdyu/eN3A78KejCOhwIDAQAB", "license": "MIT" -} +} \ No newline at end of file From 573f6583d8c050042d7035f0d113855a500cefd3 Mon Sep 17 00:00:00 2001 From: leolivier Date: Sun, 18 Mar 2018 20:15:07 +0100 Subject: [PATCH 4/6] Lint passed plus most remarks from @antelle taken into account except the network throttling --- docs/plugins/haveibeenpwned/plugin.js | 174 ++++++++++++-------------- 1 file changed, 82 insertions(+), 92 deletions(-) diff --git a/docs/plugins/haveibeenpwned/plugin.js b/docs/plugins/haveibeenpwned/plugin.js index 7f2fcae..de44334 100644 --- a/docs/plugins/haveibeenpwned/plugin.js +++ b/docs/plugins/haveibeenpwned/plugin.js @@ -4,49 +4,41 @@ * @license MIT */ -const DetailsView = require("views/details/details-view"); -const Alerts = require("comp/alerts"); +const DetailsView = require('views/details/details-view'); +const Alerts = require('comp/alerts'); const Logger = require('util/logger'); const InputFx = require('util/input-fx'); - +const Kdbxweb = require('kdbxweb'); const detailsViewFieldChanged = DetailsView.prototype.fieldChanged; -DetailsView.prototype.checkPwnedPwd = false; -DetailsView.prototype.checkPwnedName = false; -DetailsView.prototype.blockPwnedPwd = false; -DetailsView.prototype.blockPwnedName = false; -DetailsView.prototype.logger = new Logger("HaveIBeenPwned"); - -DetailsView.prototype._alert = function (msg) { - Alerts.info({ body: msg, title: "HaveIBeenPwned" }); -} +const settings = { checkPwnedPwd: false, checkPwnedName: false, blockPwnedPwd: false, blockPwnedName: false }; +const logger = new Logger('HaveIBeenPwned'); DetailsView.prototype.checkPwnedOnSettingsChanged = function (changes) { - //if (changes['CheckPwnedPwd'] || changes['CheckPwnedName'] || changes['CheckPwnedName'] || changes['CheckPwnedName']) { - // info('Full HaveIBeenPwned check not yet implemented. Checks are done one by one when you change a name or a password.'); - //} + // if (changes['CheckPwnedPwd'] || changes['CheckPwnedName'] || changes['BlockPwnedPwd'] || changes['BlockPwnedName']) { + // info('Full HaveIBeenPwned check not yet implemented. Checks are done one by one when you change a name or a password.'); + // } }; -seen = []; +let _seen = []; class HIBPUtils { constructor() { - seen = []; - } - + _seen = []; + }; replacer(key, value) { - if (value != null && typeof value == "object") { - if (seen.indexOf(value) >= 0) { + if (value != null && typeof value === 'object') { + if (_seen.indexOf(value) >= 0) { return; } - seen.push(value); + _seen.push(value); } return value; - } + }; stringify(obj) { - var ret = JSON.stringify(obj, this.replacer); - seen = []; + const ret = JSON.stringify(obj, this.replacer); + _seen = []; return ret; - } + }; xhrcall (config) { const xhr = new XMLHttpRequest(); if (config.responseType) { @@ -67,56 +59,57 @@ class HIBPUtils { return config.error && config.error('timeout', xhr); }); xhr.open(config.method || 'GET', config.url); - if (config.headers) config.headers.forEach((value, key) => { - xhr.setRequestHeader(key, value); - }); + if (config.headers) { + config.headers.forEach((value, key) => { + xhr.setRequestHeader(key, value); + }); + }; xhr.send(config.data); }; - hex (buffer) { - var hexCodes = []; - var view = new DataView(buffer); - for (var i = 0; i < view.byteLength; i += 4) { + const hexCodes = []; + const view = new DataView(buffer); + for (let i = 0; i < view.byteLength; i += 4) { // Using getUint32 reduces the number of iterations needed (we process 4 bytes each time) - var value = view.getUint32(i) + const value = view.getUint32(i); // toString(16) will give the hex representation of the number without padding - var stringValue = value.toString(16) + const stringValue = value.toString(16); // We use concatenation and slice for padding - var padding = '00000000' - var paddedValue = (padding + stringValue).slice(-padding.length) + const padding = '00000000'; + const paddedValue = (padding + stringValue).slice(-padding.length); hexCodes.push(paddedValue); } - // Join all the hex strings into one - return hexCodes.join(""); - } - + return hexCodes.join(''); + }; digest(algo, str) { // We transform the string into an arraybuffer. - const buffer = new TextEncoder("utf-8").encode(str); + const buffer = Kdbxweb.ByteUtils.stringToBytes(str); const subtle = window.crypto.subtle || window.crypto.webkitSubtle; - var _self = this; - return crypto.subtle.digest(algo, buffer).then(function (hash) { + const _self = this; + return subtle.digest(algo, buffer).then(hash => { return _self.hex(hash); - }); - } - - + }); + }; sha1(str) { - return this.digest("SHA-1", str); - } - + return this.digest('SHA-1', str); + }; sha256(str) { - return this.digest("SHA-256", str); - } + return this.digest('SHA-256', str); + }; + alert (msg) { + Alerts.info({ body: msg, title: 'HaveIBeenPwned' }); + }; } +const hibp = new HIBPUtils(); + DetailsView.prototype.checkNamePwned = function (name) { - this.logger.info('check hibp name ' + name); + logger.info('check hibp name ' + name); name = encodeURIComponent(name); const url = `https://haveibeenpwned.com/api/v2/breachedaccount/${name}?truncateResponse=true`; - this.logger.info('url ' + url); - new HIBPUtils().xhrcall({ + logger.info('url ' + url); + hibp.xhrcall({ url: url, method: 'GET', responseType: 'json', @@ -124,33 +117,32 @@ DetailsView.prototype.checkNamePwned = function (name) { data: null, statuses: [200, 404], success: (data, xhr) => { - this.logger.info('xhr ' + JSON.stringify(xhr)); + logger.info('xhr ' + JSON.stringify(xhr)); if (data && data.length > 0) { - this.logger.info('found breaches ' + JSON.stringify(data)); - var breaches = ""; + logger.info('found breaches ' + JSON.stringify(data)); + let breaches = ''; data.forEach(breach => { breaches += `

  • ${breach.Name}
  • \n`; }); - this._alert(`WARNING! This account has been pawned in the following breaches
    \n\n

    Please check on https://haveibeenpwned.com\n`); + hibp.alert(`WARNING! This account has been pawned in the following breaches
    \n

    \n

    Please check on https://haveibeenpwned.com\n`); this.userEditView.$el.focus(); this.userEditView.$el.addClass('input--error'); InputFx.shake(this.userEditView.$el); } else { - this.logger.info("check pwnd name passed..."); + logger.info('check pwnd name passed...'); this.userEditView.$el.removeClass('input--error'); } }, error: (e, xhr) => { - let err = xhr.response && xhr.response.error || new Error('Network error'); - this.logger.error('Pwned Password API error', 'GET', xhr.status, err); + const err = xhr.response && xhr.response.error || new Error('Network error'); + logger.error('Pwned Password API error', 'GET', xhr.status, err); err.status = xhr.status; } - }); + }); }; DetailsView.prototype.checkPwdPwned = function (passwordHash) { - this.logger.info('check hibp pwd (hash) ' + passwordHash); - - prefix = passwordHash.substring(0, 5); - new HIBPUtils().xhrcall({ + logger.info('check hibp pwd (hash) ' + passwordHash); + const prefix = passwordHash.substring(0, 5); + hibp.xhrcall({ url: `https://api.pwnedpasswords.com/range/${prefix}`, method: 'GET', responseType: 'text', @@ -159,44 +151,44 @@ DetailsView.prototype.checkPwdPwned = function (passwordHash) { statuses: [200, 404], success: data => { if (data) { - this.logger.info('found breaches ' + JSON.stringify(data)); + logger.info('found breaches ' + JSON.stringify(data)); data.split('\r\n').forEach(line => { - h = line.split(':'); - suffix = h[0]; nb = h[1]; + const h = line.split(':'); + const suffix = h[0]; const nb = h[1]; if (prefix + suffix === passwordHash) { - this._alert(`WARNING: This password is referenced as pawned ${nb} times on https://haveibeenpwned.com!\n`); + hibp.alert(`WARNING: This password is referenced as pawned ${nb} times on https://haveibeenpwned.com!\n`); this.passEditView.$el.focus(); this.passEditView.$el.addClass('input--error'); InputFx.shake(this.passEditView.$el); } }); } else { - this.logger.info("check pwnd passwd passed..."); + logger.info('check pwnd passwd passed...'); this.passEditView.$el.removeClass('input--error'); - } + } }, error: (e, xhr) => { - let err = xhr.response && xhr.response.error || new Error('Network error'); - this.logger.error('Pwned Password API error', 'GET', xhr.status, err); + const err = xhr.response && xhr.response.error || new Error('Network error'); + logger.error('Pwned Password API error', 'GET', xhr.status, err); err.status = xhr.status; } }); }; DetailsView.prototype.fieldChanged = function (e) { - //this.logger.info('field changed ' + new HIBPUtils().stringify(e)); - detailsViewFieldChanged.apply(this, [e]); + // logger.info('field changed ' + hibp.stringify(e)); + detailsViewFieldChanged.apply(this, arguments); if (e.field) { - if (e.field === '$Password' && this.checkPwnedPwd) { + if (e.field === '$Password' && settings.checkPwnedPwd) { if (this.passEditView.value) { const pwd = this.passEditView.value.getText(); if (pwd.replace(/\s/, '') !== '' && !pwd.startsWith('{REF:')) { - new HIBPUtils().sha1(pwd).then(hash => { + hibp.sha1(pwd).then(hash => { this.checkPwdPwned(hash.toUpperCase()); }); } } - } else if (e.field === '$UserName' && this.checkPwnedName) { + } else if (e.field === '$UserName' && settings.checkPwnedName) { this.checkNamePwned(e.val); } } @@ -207,22 +199,22 @@ module.exports.getSettings = function () { name: 'checkPwnedPwd', label: 'Check passwords against HaveIBeenPwned list', type: 'checkbox', - value: 'true' + value: true }, { name: 'checkPwnedName', label: 'Check user ids against HaveIBeenPwned list', type: 'checkbox', - value: 'true' + value: true }, { name: 'blockPwnedPwd', label: 'Block pwned passwords if they are in HaveIBeenPwned list', type: 'checkbox', - value: 'true' + value: true }, { name: 'blockPwnedName', label: 'Block pwned names if they are in HaveIBeenPwned list', type: 'checkbox', - value: 'true' + value: true }]; }; @@ -235,16 +227,14 @@ module.exports.setSettings = function (changes) { // example: { MyText: 'value', MySel: 'selected-value', MyCheckbox: true } // info(JSON.stringify(changes)); - s = '' - for (field in changes) { - ccfield = field.substr(0, 1).toLowerCase() + field.substring(1); - DetailsView.prototype[ccfield] = changes[field]; - //s += ccfield + '=' + DetailsView.prototype[ccfield] + '; '; + + for (const field in changes) { + const ccfield = field.substr(0, 1).toLowerCase() + field.substring(1); + settings[ccfield] = changes[field]; } DetailsView.prototype.checkPwnedOnSettingsChanged.apply(changes); - //alert(s); }; module.exports.uninstall = function () { - fieldChanged = detailsViewFieldChanged; + DetailsView.prototype.fieldChanged = detailsViewFieldChanged; }; From fef98fd29a96a8ca1ea9980bcee5809fa6562a5f Mon Sep 17 00:00:00 2001 From: leolivier Date: Mon, 19 Mar 2018 22:03:13 +0100 Subject: [PATCH 5/6] fixed possible XSS breaches --- docs/plugins/haveibeenpwned/plugin.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/plugins/haveibeenpwned/plugin.js b/docs/plugins/haveibeenpwned/plugin.js index de44334..8a28272 100644 --- a/docs/plugins/haveibeenpwned/plugin.js +++ b/docs/plugins/haveibeenpwned/plugin.js @@ -9,8 +9,9 @@ const Alerts = require('comp/alerts'); const Logger = require('util/logger'); const InputFx = require('util/input-fx'); const Kdbxweb = require('kdbxweb'); -const detailsViewFieldChanged = DetailsView.prototype.fieldChanged; +const _ = require('_'); +const detailsViewFieldChanged = DetailsView.prototype.fieldChanged; const settings = { checkPwnedPwd: false, checkPwnedName: false, blockPwnedPwd: false, blockPwnedName: false }; const logger = new Logger('HaveIBeenPwned'); @@ -121,7 +122,7 @@ DetailsView.prototype.checkNamePwned = function (name) { if (data && data.length > 0) { logger.info('found breaches ' + JSON.stringify(data)); let breaches = ''; - data.forEach(breach => { breaches += `

  • ${breach.Name}
  • \n`; }); + data.forEach(breach => { breaches += '
  • ' + _.escape(breach.Name) + '
  • \n'; }); hibp.alert(`WARNING! This account has been pawned in the following breaches
    \n\n

    Please check on https://haveibeenpwned.com\n`); this.userEditView.$el.focus(); this.userEditView.$el.addClass('input--error'); @@ -154,8 +155,9 @@ DetailsView.prototype.checkPwdPwned = function (passwordHash) { logger.info('found breaches ' + JSON.stringify(data)); data.split('\r\n').forEach(line => { const h = line.split(':'); - const suffix = h[0]; const nb = h[1]; + const suffix = h[0]; if (prefix + suffix === passwordHash) { + const nb = _.escape(h[1]); hibp.alert(`WARNING: This password is referenced as pawned ${nb} times on https://haveibeenpwned.com!\n`); this.passEditView.$el.focus(); this.passEditView.$el.addClass('input--error'); From 37e550eb79f51adc4d48fd54c6663618f68d33b0 Mon Sep 17 00:00:00 2001 From: leolivier Date: Mon, 19 Mar 2018 22:03:49 +0100 Subject: [PATCH 6/6] updated signature --- docs/plugins/haveibeenpwned/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/haveibeenpwned/manifest.json b/docs/plugins/haveibeenpwned/manifest.json index 72071c2..29eca46 100644 --- a/docs/plugins/haveibeenpwned/manifest.json +++ b/docs/plugins/haveibeenpwned/manifest.json @@ -9,7 +9,7 @@ "url": "https://github.com/leolivier" }, "resources": { - "js": "WdONjQv+jhcNeqvi8wyfuwWIIdoG3A5Dgt7dKN6jM7UqvUulAgZWWFrm1fcgWQK1t/EeK6/OMJxFHfBQWLUeBadghOCzhDOA4pglkQE3dj3PdZmTuQ1x41NH19EzfO0Mn0KSNqU9vjnSzcDvzi6pIP9SHMqmj/UVEYHjDt17+cFYpX/U4hlGLMUpgkOLOpPumuXSQD4OENqsvuNlRsYeNfu6FBK9gAJZ4ZqpQ1G19glEBexbad1OnhVMNCyhGNVibUoliq+7h/bjsLkYZkkOJlpxwh5UmD5sr2nDfg0QfpynTut8rWGFfymbGUhQTU5vqILVR6JaHbjof3nXRII5Ig==" + "js": "na+696slok4iEscUEExyD2dF6bwT1AlmtiIogdAda9xZiBGiSNM4u43Q9oaBbnT+U1Kgoumgh0lHbnb+Hy5YtSFUTCi5M3+DKgaGVIQD0DivXqojGQF+aPKRnBv5tYu4QlGCXVVr3rXf65FHghfTXTrN0vSXM/PcysZkkrgaY6qxUZsROfnLX4EeX9JKdHcqRA3JpQI3ptBiYPL6zzJuQZcD18IMuULvs07f3QpP5IiTsgOWftBEQ20RPr3gqAjd6aAhKYGiNqBaJ7wR8MCKqNiJ4yPv6fPpNdimbh/mP3LY69CFaGvi9ZyABcP7I5zenw1ZScQsYfC+Qix25LZqBw==" }, "url": "https://plugins.keeweb.info/plugins/haveibeenpwned", "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9Cay/z7whBsHcf9klDjlA4qylWT7a/igTJ2nvUq2XuQrx98PTOexzzzg5oflk8nPEaMIsaFIf90V/rvQjJ1z9DR4zuQKDb4/GZVzxoylECAwNk80LvSPc1G0+6mwXIFp48wc6Advd4iYQCMkzWDCJXEm/1E+q85ty+H6EaLleKcJI0vlW96bbA9vFCmOsM5PYZfoGnVFRBLVthyUcGneilMvsxu5J7DKggQKPs04/WQZ5oHbUG83mxkxTdYDC3glpvV4BiaAD6z+2usO+fA97bXb+rY3O2iHJgWsa7jH0ybO0Nif6txE4d2+LJOLmfoImv7kdyu/eN3A78KejCOhwIDAQAB",