diff --git a/sass/_modal.scss b/sass/_modal.scss index 682e0b688..d19f18360 100644 --- a/sass/_modal.scss +++ b/sass/_modal.scss @@ -23,6 +23,7 @@ } .fingerprint-removal { label { + display: flex; padding: 0.75rem 1.25rem; } } @@ -47,6 +48,9 @@ display: flex; justify-content: space-between; font-size: 95%; + .fingerprint { + margin-left: 1em; + } } } } diff --git a/spec/omemo.js b/spec/omemo.js index 1a0eb1450..6fafa1e6b 100644 --- a/spec/omemo.js +++ b/spec/omemo.js @@ -912,7 +912,7 @@ .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'}) .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up() .c('signedPreKeySignature').t(btoa('2222')).up() - .c('identityKey').t(btoa('3333')).up() + .c('identityKey').t('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI').up() .c('prekeys') .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up() .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up() @@ -927,7 +927,9 @@ const modal = view.user_details_modal; expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1); const el = modal.el.querySelector('.fingerprints .fingerprint'); - expect(el.textContent).toBe('f56d6351aa71cff0debea014d13525e42036187a'); + expect(el.textContent.trim()).toBe( + u.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI'))) + ); expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2); diff --git a/src/converse-chatview.js b/src/converse-chatview.js index 57efcde6a..7870b4bc7 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -252,7 +252,8 @@ '_converse': _converse, 'allow_contact_removal': _converse.allow_contact_removal, 'display_name': this.model.getDisplayName(), - 'is_roster_contact': !_.isUndefined(this.model.contact) + 'is_roster_contact': !_.isUndefined(this.model.contact), + 'utils': u })); }, diff --git a/src/converse-omemo.js b/src/converse-omemo.js index fc63135aa..9c972a8ee 100644 --- a/src/converse-omemo.js +++ b/src/converse-omemo.js @@ -49,7 +49,7 @@ } }); return { - 'identity_key': bundle_el.querySelector('identityKey').textContent, + 'identity_key': bundle_el.querySelector('identityKey').textContent.trim(), 'signed_prekey': { 'id': parseInt(signed_prekey_public_el.getAttribute('signedPreKeyId'), 10), 'public_key': signed_prekey_public_el.textContent, @@ -490,12 +490,11 @@ _converse.NUM_PREKEYS = 100; // Set here so that tests can override function generateFingerprint (device) { - let bundle; - return device.getBundle().then(b => { - bundle = b; - return crypto.subtle.digest('SHA-1', u.base64ToArrayBuffer(bundle['identity_key'])); - }).then(fp => { - bundle['fingerprint'] = u.arrayBufferToHex(fp); + if (_.get(device.get('bundle'), 'fingerprint')) { + return; + } + return device.getBundle().then(bundle => { + bundle['fingerprint'] = u.arrayBufferToHex(u.base64ToArrayBuffer(bundle['identity_key'])); device.save('bundle', bundle); device.trigger('change:bundle'); // Doesn't get triggered automatically due to pass-by-reference }); diff --git a/src/converse-profile.js b/src/converse-profile.js index 11c8f1442..dfdb149a9 100644 --- a/src/converse-profile.js +++ b/src/converse-profile.js @@ -74,6 +74,7 @@ 'label_role': __('Role'), 'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'), 'label_url': __('URL'), + 'utils': u, 'view': this })); }, diff --git a/src/templates/profile_modal.html b/src/templates/profile_modal.html index ce312f1ef..8b520119b 100644 --- a/src/templates/profile_modal.html +++ b/src/templates/profile_modal.html @@ -72,7 +72,7 @@
  • {{{o.__("This device's OMEMO fingerprint")}}}
  • {[ if (o.view.current_device.get('bundle') && o.view.current_device.get('bundle').fingerprint) { ]} - {{{o.view.current_device.get('bundle').fingerprint}}} + {{{o.utils.formatFingerprint(o.view.current_device.get('bundle').fingerprint)}}} {[ } else {]} {[ } ]} @@ -93,7 +93,7 @@
  • {[ } else {]} diff --git a/src/templates/user_details_modal.html b/src/templates/user_details_modal.html index 726a70bc0..96a4e9e44 100644 --- a/src/templates/user_details_modal.html +++ b/src/templates/user_details_modal.html @@ -50,7 +50,7 @@ {[ if (device.get('trusted') === -1) { ]} checked="checked" {[ } ]}>{{{o.__('Untrusted')}}} - {{{device.get('bundle').fingerprint}}} + {{{o.utils.formatFingerprint(device.get('bundle').fingerprint)}}} {[ } ]} diff --git a/src/utils/core.js b/src/utils/core.js index 4961d47ed..7fde1e201 100644 --- a/src/utils/core.js +++ b/src/utils/core.js @@ -912,20 +912,19 @@ return result; }; - u.arrayBufferToHex = function (ab) { - const hexCodes = []; - const padding = '00000000'; - const view = new window.DataView(ab); - for (var i = 0; i < view.byteLength; i += 4) { - // Using getUint32 reduces the number of iterations needed (we process 4 bytes each time) - const value = view.getUint32(i) - // toString(16) will give the hex representation of the number without padding - const stringValue = value.toString(16) - // We use concatenation and slice for padding - const paddedValue = (padding + stringValue).slice(-padding.length) - hexCodes.push(paddedValue); + u.formatFingerprint = function (fp) { + fp = fp.replace(/^05/, ''); + const arr = []; + for (let i=1; i<8; i++) { + const idx = i*8+i-1; + fp = fp.slice(0, idx) + ' ' + fp.slice(idx); } - return hexCodes.join(""); + return fp; + }; + + u.arrayBufferToHex = function (ab) { + // https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex#40031979 + return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join(''); }; u.arrayBufferToString = function (ab) { @@ -934,8 +933,7 @@ }; u.arrayBufferToBase64 = function (ab) { - return btoa(new Uint8Array(ab) - .reduce((data, byte) => data + String.fromCharCode(byte), '')); + return btoa((new Uint8Array(ab)).reduce((data, byte) => data + String.fromCharCode(byte), '')); }; u.stringToArrayBuffer = function (string) {