Browse Source

Continue OMEMO implementation

pull/983/head
Timothée Jaussoin 5 years ago
parent
commit
4ab6268f1e
  1. 1
      app/views/chat.tpl
  2. 3
      app/widgets/Chat/Chat.php
  3. 21
      app/widgets/Chat/chat_omemo.js
  4. 34
      app/widgets/ChatOmemo/ChatOmemo.php
  5. 73
      app/widgets/ChatOmemo/chatomemo.js
  6. 122
      app/widgets/ChatOmemo/chatomemo_storage.js
  7. 0
      app/widgets/ChatOmemo/libsignal_protocol.js
  8. 112
      public/scripts/movim_utils.js

1
app/views/chat.tpl

@ -5,6 +5,7 @@
<?php $this->widget('Stickers');?>
<?php $this->widget('Notifications');?>
<?php $this->widget('ContactActions');?>
<?php $this->widget('ChatOmemo');?>
<nav class="color dark">
<?php $this->widget('Presence');?>

3
app/widgets/Chat/Chat.php

@ -32,9 +32,6 @@ class Chat extends \Movim\Widget\Base
public function load()
{
$this->addjs('chat.js');
$this->addjs('libsignal_protocol.js');
$this->addjs('chat_omemo.js');
$this->addcss('chat.css');
$this->registerEvent('carbons', 'onMessage');

21
app/widgets/Chat/chat_omemo.js

@ -1,21 +0,0 @@
var KeyHelper = libsignal.KeyHelper;
var registrationId = KeyHelper.generateRegistrationId();
// Store registrationId somewhere durable and safe.
console.log(registrationId);
KeyHelper.generateIdentityKeyPair().then(function(identityKeyPair) {
console.log(identityKeyPair);
// keyPair -> { pubKey: ArrayBuffer, privKey: ArrayBuffer }
// Store identityKeyPair somewhere durable and safe.
});
KeyHelper.generatePreKey(1).then(function(preKey) {
console.log('preKey');
console.log(preKey);
store.storePreKey(preKey.keyId, preKey.keyPair);
});
KeyHelper.generateSignedPreKey(identityKeyPair, keyId).then(function(signedPreKey) {
store.storeSignedPreKey(signedPreKey.keyId, signedPreKey.keyPair);
});

34
app/widgets/ChatOmemo/ChatOmemo.php

@ -0,0 +1,34 @@
<?php
use Moxl\Xec\Action\OMEMO\AnnounceBundle;
use Moxl\Xec\Action\OMEMO\SetDeviceList;
class ChatOmemo extends \Movim\Widget\Base
{
public function load()
{
$this->addjs('libsignal_protocol.js');
$this->addjs('chatomemo.js');
$this->addjs('chatomemo_storage.js');
}
public function ajaxAnnounceBundle($bundle)
{
$preKeys = [];
foreach ($bundle->preKeys as $preKey) {
array_push($preKeys, (string)$preKey->key);
}
$sdl = new SetDeviceList;
$sdl->setList([$bundle->deviceId])
->request();
$ab = new AnnounceBundle;
$ab->setId($bundle->deviceId)
->setSignedPreKeyPublic($bundle->signedPreKey->publicKey)
->setSignedPreKeySignature($bundle->signedPreKey->signature)
->setIdentityKey($bundle->identityKey)
->setPreKeys($preKeys)
->request();
}
}

73
app/widgets/ChatOmemo/chatomemo.js

@ -0,0 +1,73 @@
var KeyHelper = libsignal.KeyHelper;
/*
var deviceId = KeyHelper.generateRegistrationId();
// Store deviceId somewhere durable and safe.
console.log(deviceId);
KeyHelper.generateIdentityKeyPair().then(function(identityKeyPair) {
console.log(identityKeyPair);
// keyPair -> { pubKey: ArrayBuffer, privKey: ArrayBuffer }
// Store identityKeyPair somewhere durable and safe.
});
KeyHelper.generatePreKey(1).then(function(preKey) {
console.log('preKey');
console.log(preKey);
store.storePreKey(preKey.keyId, preKey.keyPair);
});
KeyHelper.generateSignedPreKey(identityKeyPair, keyId).then(function(signedPreKey) {
store.storeSignedPreKey(signedPreKey.keyId, signedPreKey.keyPair);
});*/
var ChatOmemo = {
generateBundle : async function()
{
const identityKeyPair = await KeyHelper.generateIdentityKeyPair();
const bundle = {};
const identityKey = MovimUtils.arrayBufferToBase64(identityKeyPair.pubKey);
const deviceId = KeyHelper.generateRegistrationId();
bundle['identityKey'] = identityKey;
bundle['deviceId'] = deviceId;
/*this.save({
'deviceId': deviceId,
'identityKeyPair': {
'privKey': MovimUtils.arrayBufferToBase64(identityKeyPair.privKey),
'pubKey': identityKey
},
'identityKey': identityKey
});*/
const signedPreKey = await KeyHelper.generateSignedPreKey(identityKeyPair, 0);
console.log(signedPreKey);
localStorage.setObject('signedPreKey', {
'privKey' : MovimUtils.arrayBufferToBase64(signedPreKey.keyPair.privKey),
'pubKey' : MovimUtils.arrayBufferToBase64(signedPreKey.keyPair.pubKey),
'signature' : MovimUtils.arrayBufferToBase64(signedPreKey.signature),
});
//_converse.omemo_store.storeSignedPreKey(signedPreKey);
bundle['signedPreKey'] = {
'id': signedPreKey.keyId,
'publicKey': MovimUtils.arrayBufferToBase64(signedPreKey.keyPair.privKey),
'signature': MovimUtils.arrayBufferToBase64(signedPreKey.signature)
}
const keys = await Promise.all(MovimUtils.range(0, 5).map(id => KeyHelper.generatePreKey(id)));
//keys.forEach(k => _converse.omemo_store.storePreKey(k.keyId, k.keyPair));
keys.forEach(k => {
localStorage.setObject('preKey' + k.keyId, {
'privKey' : MovimUtils.arrayBufferToBase64(k.keyPair.privKey),
'pubKey' : MovimUtils.arrayBufferToBase64(k.keyPair.pubKey),
});
});
const preKeys = keys.map(k => ({'id': k.keyId, 'key': MovimUtils.arrayBufferToBase64(k.keyPair.pubKey)}));
bundle['preKeys'] = preKeys;
ChatOmemo_ajaxAnnounceBundle(bundle);
/*const devicelist = _converse.devicelists.get(_converse.bare_jid);
const device = await devicelist.devices.create({'id': bundle.deviceId, 'jid': _converse.bare_jid}, {'promise': true});
const marshalled_keys = keys.map(k => ({'id': k.keyId, 'key': MovimUtils.arrayBufferToBase64(k.keyPair.pubKey)}));
bundle['prekeys'] = marshalled_keys;
device.save('bundle', bundle);*/
}
}

122
app/widgets/ChatOmemo/chatomemo_storage.js

@ -0,0 +1,122 @@
function ChatOmemoStorage() {
this.store = {};
}
ChatOmemoStorage.prototype = {
Direction: {
SENDING: 1,
RECEIVING: 2,
},
getIdentityKeyPair: function () {
return Promise.resolve(this.get('identityKey'));
},
getLocalRegistrationId: function () {
return Promise.resolve(this.get('registrationId'));
},
put: function (key, value) {
if (key === undefined || value === undefined || key === null || value === null)
throw new Error("Tried to store undefined/null");
localStorage.setObject(key, value);
},
get: function (key, defaultValue) {
if (key === null || key === undefined)
throw new Error("Tried to get value for undefined/null key");
if (key in localStorage) {
return localStorage.getObject(key);
} else {
return defaultValue;
}
},
remove: function (key) {
if (key === null || key === undefined)
throw new Error("Tried to remove value for undefined/null key");
localStorage.removeItem(key);
//delete this.store[key];
},
isTrustedIdentity: function (identifier, identityKey, direction) {
if (identifier === null || identifier === undefined) {
throw new Error("tried to check identity key for undefined/null key");
}
if (!(identityKey instanceof ArrayBuffer)) {
throw new Error("Expected identityKey to be an ArrayBuffer");
}
var trusted = this.get('identityKey' + identifier);
if (trusted === undefined) {
return Promise.resolve(true);
}
return Promise.resolve(util.toString(identityKey) === util.toString(trusted));
},
loadIdentityKey: function (identifier) {
if (identifier === null || identifier === undefined)
throw new Error("Tried to get identity key for undefined/null key");
return Promise.resolve(this.get('identityKey' + identifier));
},
saveIdentity: function (identifier, identityKey) {
if (identifier === null || identifier === undefined)
throw new Error("Tried to put identity key for undefined/null key");
var address = new libsignal.SignalProtocolAddress.fromString(identifier);
var existing = this.get('identityKey' + address.getName());
this.put('identityKey' + address.getName(), identityKey)
if (existing && util.toString(identityKey) !== util.toString(existing)) {
return Promise.resolve(true);
} else {
return Promise.resolve(false);
}
},
/* Returns a prekeypair object or undefined */
loadPreKey: function (keyId) {
var res = this.get('25519KeypreKey' + keyId);
if (res !== undefined) {
res = { pubKey: res.pubKey, privKey: res.privKey };
}
return Promise.resolve(res);
},
storePreKey: function (keyId, keyPair) {
return Promise.resolve(this.put('25519KeypreKey' + keyId, keyPair));
},
removePreKey: function (keyId) {
return Promise.resolve(this.remove('25519KeypreKey' + keyId));
},
/* Returns a signed keypair object or undefined */
loadSignedPreKey: function (keyId) {
var res = this.get('25519KeysignedKey' + keyId);
if (res !== undefined) {
res = { pubKey: res.pubKey, privKey: res.privKey };
}
return Promise.resolve(res);
},
storeSignedPreKey: function (keyId, keyPair) {
return Promise.resolve(this.put('25519KeysignedKey' + keyId, keyPair));
},
removeSignedPreKey: function (keyId) {
return Promise.resolve(this.remove('25519KeysignedKey' + keyId));
},
loadSession: function (identifier) {
return Promise.resolve(this.get('session' + identifier));
},
storeSession: function (identifier, record) {
return Promise.resolve(this.put('session' + identifier, record));
},
removeSession: function (identifier) {
return Promise.resolve(this.remove('session' + identifier));
},
removeAllSessions: function (identifier) {
for (var id in this.store) {
if (id.startsWith('session' + identifier)) {
delete this.store[id];
}
}
return Promise.resolve();
}
};

0
app/widgets/Chat/libsignal_protocol.js → app/widgets/ChatOmemo/libsignal_protocol.js

112
public/scripts/movim_utils.js

@ -9,7 +9,7 @@
* @param key string
* @param value the object
*/
Storage.prototype.setObject = function(key, value) {
Storage.prototype.setObject = function (key, value) {
this.setItem(key, JSON.stringify(value));
};
@ -17,86 +17,86 @@ Storage.prototype.setObject = function(key, value) {
* @brief Get object in localStorage
* @param key
*/
Storage.prototype.getObject = function(key) {
Storage.prototype.getObject = function (key) {
return JSON.parse(this.getItem(key));
};
var MovimUtils = {
cleanupId: function(string) {
cleanupId: function (string) {
return 'id-' + string.replace(/([^a-z0-9]+)/gi, '-').toLowerCase();
},
isMobile: function() {
isMobile: function () {
return window.matchMedia('(max-width: 1024px)').matches;
},
disconnect: function() {
disconnect: function () {
window.location.replace(ERROR_URI);
},
formToJson: function(formname) {
formToJson: function (formname) {
var form = document.forms[formname];
if (!form)
return false;
var json = {};
for(var i = 0; i < form.elements.length; i++) {
for (var i = 0; i < form.elements.length; i++) {
json_att = {};
for(var j = 0; j < form.elements[i].attributes.length; j++) {
for (var j = 0; j < form.elements[i].attributes.length; j++) {
json_att[form.elements[i].attributes[j].name] = form.elements[i].attributes[j].value;
}
if (form.elements[i].name.length != 0) {
if (form.elements[i].type == 'checkbox')
json[form.elements[i].name] = {
'value' : form.elements[i].checked,
'attributes' : json_att
'value': form.elements[i].checked,
'attributes': json_att
};
else if (form.elements[i].type == 'radio' && form.elements[i].checked )
else if (form.elements[i].type == 'radio' && form.elements[i].checked)
json[form.elements[i].name] = {
'value' : form.elements[i].value,
'attributes' : json_att
'value': form.elements[i].value,
'attributes': json_att
};
else if (form.elements[i].type != 'radio')
json[form.elements[i].name] = {
'value' : form.elements[i].value,
'attributes' : json_att
'value': form.elements[i].value,
'attributes': json_att
};
}
}
return json;
},
setTitle: function(title) {
setTitle: function (title) {
document.title = title;
},
pushState: function(url) {
pushState: function (url) {
window.history.pushState(null, '', url);
},
redirect: function(url) {
redirect: function (url) {
window.location.href = url;
},
softRedirect: function(url) {
softRedirect: function (url) {
var location = window.location.href;
if (location.substring(0, location.indexOf('#')) !== url) {
window.location.href = url;
}
},
reload: function(uri) {
reload: function (uri) {
window.location.replace(uri);
},
reloadThis: function() {
reloadThis: function () {
window.location.reload();
},
addClass: function(element, classname) {
addClass: function (element, classname) {
let el = document.querySelector(element);
if (el) el.classList.add(classname);
},
removeClass: function(element, classname) {
removeClass: function (element, classname) {
let el = document.querySelector(element);
if (el) el.classList.remove(classname);
},
textareaAutoheight: function(textbox) {
textareaAutoheight: function (textbox) {
if (textbox != null) {
var val = MovimUtils.htmlEscape(textbox.value).replace(/\n/g, '<br>');
var hidden = document.querySelector('#hiddendiv');
@ -112,7 +112,7 @@ var MovimUtils = {
textbox.style.height = hidden.scrollHeight + 'px';
}
},
copyToClipboard: function(text) {
copyToClipboard: function (text) {
var input = document.body.appendChild(document.createElement('input'));
input.value = text;
input.focus();
@ -120,23 +120,23 @@ var MovimUtils = {
document.execCommand('copy');
input.parentNode.removeChild(input);
},
applyAutoheight: function() {
applyAutoheight: function () {
var textareas = document.querySelectorAll('textarea[data-autoheight=true]')
for(var i = 0; i < textareas.length; i++) {
for (var i = 0; i < textareas.length; i++) {
MovimUtils.textareaAutoheight(textareas[i]);
textareas[i].addEventListener('keyup', e => MovimUtils.textareaAutoheight(e.target));
};
},
htmlEscape: function(string) {
htmlEscape: function (string) {
return String(string)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
},
enhanceArticlesContent: function() {
enhanceArticlesContent: function () {
document.querySelectorAll('article section > div video')
.forEach(item => item.setAttribute('controls', 'controls'));
@ -156,40 +156,40 @@ var MovimUtils = {
}
});
},
urlParts : function() {
urlParts: function () {
var str = window.location.search.split('/');
var page = str[0].substr(1);
str.shift();
var str = str.map(param => decodeURIComponent(param));
return {'page': page, 'params': str, 'hash': window.location.hash.substr(1)};
return { 'page': page, 'params': str, 'hash': window.location.hash.substr(1) };
},
humanFileSize : function(bytes, si) {
humanFileSize: function (bytes, si) {
var thresh = si ? 1000 : 1024;
if(Math.abs(bytes) < thresh) {
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = si
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
var u = -1;
do {
bytes /= thresh;
++u;
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1)+' '+units[u];
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1) + ' ' + units[u];
},
getOrientation : function(file, callback) {
getOrientation: function (file, callback) {
var testImageURL =
'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAIAAwMBEQACEQEDEQH/x' +
'ABRAAEAAAAAAAAAAAAAAAAAAAAKEAEBAQADAQEAAAAAAAAAAAAGBQQDCAkCBwEBAAAAAAA' +
'AAAAAAAAAAAAAABEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AG8T9NfSMEVMhQ' +
'voP3fFiRZ+MTHDifa/95OFSZU5OzRzxkyejv8ciEfhSceSXGjS8eSdLnZc2HDm4M3BxcXw' +
'H/9k='
'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAIAAwMBEQACEQEDEQH/x' +
'ABRAAEAAAAAAAAAAAAAAAAAAAAKEAEBAQADAQEAAAAAAAAAAAAGBQQDCAkCBwEBAAAAAAA' +
'AAAAAAAAAAAAAABEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AG8T9NfSMEVMhQ' +
'voP3fFiRZ+MTHDifa/95OFSZU5OzRzxkyejv8ciEfhSceSXGjS8eSdLnZc2HDm4M3BxcXw' +
'H/9k='
var img = document.createElement('img');
img.onload = function () {
@ -201,7 +201,7 @@ var MovimUtils = {
} else {
var reader = new FileReader();
reader.onload = function(e) {
reader.onload = function (e) {
var view = new DataView(e.target.result);
if (view.getUint16(0, false) != 0xFFD8) return callback(-2);
var length = view.byteLength, offset = 2;
@ -221,7 +221,7 @@ var MovimUtils = {
for (var i = 0; i < tags; i++)
if (view.getUint16(offset + (i * 12), little) == 0x0112)
return callback(view.getUint16(offset + (i * 12) + 8, little));
return callback(view.getUint16(offset + (i * 12) + 8, little));
}
else if ((marker & 0xFF00) != 0xFF00) break;
else offset += view.getUint16(offset, false);
@ -234,5 +234,11 @@ var MovimUtils = {
}
}
img.src = testImageURL
},
arrayBufferToBase64: function (ab) {
return btoa((new Uint8Array(ab)).reduce((data, byte) => data + String.fromCharCode(byte), ''));
},
range: function (start, end) {
return Array.from({ length: end - start + 1 }, (_, i) => i)
}
};
Loading…
Cancel
Save