From ae96fca4780a8781bbd2719d805b90624efd09e9 Mon Sep 17 00:00:00 2001 From: antelle Date: Sun, 14 Aug 2016 21:53:55 +0300 Subject: [PATCH] field references decoding --- app/scripts/mixins/protected-value-ex.js | 41 +++++++++++++++--- app/scripts/models/entry-model.js | 53 +++++++++++++++++++++++- app/scripts/models/file-model.js | 8 ++++ release-notes.md | 1 + 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/app/scripts/mixins/protected-value-ex.js b/app/scripts/mixins/protected-value-ex.js index 511e39aa..c7207df4 100644 --- a/app/scripts/mixins/protected-value-ex.js +++ b/app/scripts/mixins/protected-value-ex.js @@ -2,6 +2,9 @@ var kdbxweb = require('kdbxweb'); +const ExpectedFieldRefChars = '{REF:0@I:00000000000000000000000000000000}'.split(''); +const ExpectedFieldRefByteLength = ExpectedFieldRefChars.length; + kdbxweb.ProtectedValue.prototype.isProtected = true; kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) { @@ -10,30 +13,42 @@ kdbxweb.ProtectedValue.prototype.forEachChar = function(fn) { for (var i = 0, len = value.length; i < len; i++) { b = value[i] ^ salt[i]; if (b < 128) { - fn(b); + if (fn(b) === false) { + return; + } continue; } i++; b1 = value[i] ^ salt[i]; if (i === len) { break; } if (b >= 192 && b < 224) { - fn(((b & 0x1f) << 6) | (b1 & 0x3f)); + if (fn(((b & 0x1f) << 6) | (b1 & 0x3f)) === false) { + return; + } continue; } i++; b2 = value[i] ^ salt[i]; if (i === len) { break; } if (b >= 224 && b < 240) { - fn(((b & 0xf) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f)); + if (fn(((b & 0xf) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f)) === false) { + return; + } } i++; b3 = value[i] ^ salt[i]; if (i === len) { break; } if (b >= 240 && b < 248) { var c = ((b & 7) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f); if (c <= 0xffff) { - fn(c); + if (fn(c) === false) { + return; + } } else { c ^= 0x10000; - fn(0xd800 | (c >> 10)); - fn(0xdc00 | (c & 0x3ff)); + if (fn(0xd800 | (c >> 10)) === false) { + return; + } + if (fn(0xdc00 | (c & 0x3ff)) === false) { + return; + } } } // skip error @@ -98,4 +113,18 @@ kdbxweb.ProtectedValue.prototype.equals = function(other) { return true; }; +kdbxweb.ProtectedValue.prototype.isFieldReference = function() { + if (this.byteLength !== ExpectedFieldRefByteLength) { + return false; + } + let ix = 0; + this.forEachChar(ch => { + let expected = ExpectedFieldRefChars[ix++]; + if (expected !== '0' && ch !== expected) { + return false; + } + }); + return true; +}; + module.exports = kdbxweb.ProtectedValue; diff --git a/app/scripts/models/entry-model.js b/app/scripts/models/entry-model.js index dff24e64..cce07d75 100644 --- a/app/scripts/models/entry-model.js +++ b/app/scripts/models/entry-model.js @@ -10,9 +10,13 @@ var Backbone = require('backbone'), var EntryModel = Backbone.Model.extend({ defaults: {}, - urlRegex: /^https?:\/\//i, - builtInFields: ['Title', 'Password', 'Notes', 'URL', 'UserName', 'TOTP Seed', 'TOTP Settings'], + urlRegex: /^https?:\/\//i, + fieldRefRegex: /^\{REF:([TNPAU])@I:(\w{32})}$/, + + builtInFields: ['Title', 'Password', 'UserName', 'URL', 'Notes', 'TOTP Seed', 'TOTP Settings'], + fieldRefFields: ['title', 'password', 'user', 'url', 'notes'], + fieldRefIds: { T: 'Title', U: 'UserName', P: 'Password', A: 'URL', N: 'Notes' }, initialize: function() { }, @@ -24,7 +28,10 @@ var EntryModel = Backbone.Model.extend({ if (this.get('uuid') === entry.uuid.id) { this._checkUpdatedEntry(); } + // we cannot calculate field references now because database index has not yet been built + this.hasFieldRefs = false; this._fillByEntry(); + this.hasFieldRefs = true; }, _fillByEntry: function() { @@ -54,6 +61,9 @@ var EntryModel = Backbone.Model.extend({ this._buildSearchTags(); this._buildSearchColor(); this._buildAutoType(); + if (this.hasFieldRefs) { + this.resolveFieldReferences(); + } }, _checkUpdatedEntry: function() { @@ -261,6 +271,45 @@ var EntryModel = Backbone.Model.extend({ return val ? compare(val, search) : false; }, + resolveFieldReferences: function() { + this.hasFieldRefs = false; + this.fieldRefFields.forEach(field => { + let fieldValue = this[field]; + if (!fieldValue) { + return; + } + if (fieldValue.isProtected && fieldValue.isFieldReference()) { + fieldValue = fieldValue.getText(); + } + if (typeof fieldValue !== 'string') { + return; + } + let match = fieldValue.match(this.fieldRefRegex); + if (!match) { + return; + } + this.hasFieldRefs = true; + let value = this._getReferenceValue(match[1], match[2]); + if (!value) { + return; + } + this[field] = value; + }); + }, + + _getReferenceValue: function(fieldRefId, idStr) { + let id = new Uint8Array(16); + for (let i = 0; i < 16; i++) { + id[i] = parseInt(idStr.substr(i * 2, 2), 16); + } + let uuid = new kdbxweb.KdbxUuid(id); + let entry = this.file.getEntry(this.file.subId(uuid.id)); + if (!entry) { + return undefined; + } + return entry.entry.fields[this.fieldRefIds[fieldRefId]]; + }, + setColor: function(color) { this._entryModified(); this.entry.bgColor = Color.getKnownBgColor(color); diff --git a/app/scripts/models/file-model.js b/app/scripts/models/file-model.js index e555a501..64bbafcc 100644 --- a/app/scripts/models/file-model.js +++ b/app/scripts/models/file-model.js @@ -156,6 +156,7 @@ var FileModel = Backbone.Model.extend({ groups.add(groupModel); }, this); this.buildObjectMap(); + this.resolveFieldReferences(); }, subId: function(id) { @@ -175,6 +176,13 @@ var FileModel = Backbone.Model.extend({ this.groupMap = groupMap; }, + resolveFieldReferences: function() { + let entryMap = this.entryMap; + Object.keys(entryMap).forEach(e => { + entryMap[e].resolveFieldReferences(); + }); + }, + reload: function() { this.buildObjectMap(); this.readModel(); diff --git a/release-notes.md b/release-notes.md index 598dad66..413158e2 100644 --- a/release-notes.md +++ b/release-notes.md @@ -13,6 +13,7 @@ Audit, generator presets, auto-type and ui improvements `+` save displayed table columns `+` confirmation in password change dialog `+` inline generator keyboard management +`+` field references decoding `-` fix app redraw in background `-` fix idle timer on computer sleep