181 lines
7.4 KiB
JavaScript
181 lines
7.4 KiB
JavaScript
var Account = {
|
|
resetPassword: function () {
|
|
var form = document.querySelector('form[name=password]');
|
|
form.reset();
|
|
document.querySelector('#password_save').className = 'button color flat';
|
|
},
|
|
resolveSessionsStates: function () {
|
|
var store = new ChatOmemoStorage();
|
|
store.getSessionsIds(store.jid).map(id => {
|
|
store.getSessionState(store.jid + '.' + id).then(state => {
|
|
if (state) {
|
|
let checkbox = document.querySelector('input[name=accountsessionstate_' + id + ']');
|
|
|
|
if (checkbox) {
|
|
checkbox.checked = true;
|
|
}
|
|
}
|
|
})
|
|
});
|
|
},
|
|
toggleFingerprintState: function (checkbox) {
|
|
var store = new ChatOmemoStorage();
|
|
store.setSessionState(checkbox.dataset.identifier, checkbox.checked);
|
|
},
|
|
deleteBundle: async function (bundleid) {
|
|
ChatOmemo.resolveContactFingerprints(USER_JID).then(remoteKeys => {
|
|
remoteKeys = remoteKeys.filter(remoteKey =>
|
|
remoteKey.bundleid == bundleid
|
|
);
|
|
|
|
Account_ajaxDeleteBundle(remoteKeys[0])
|
|
});
|
|
},
|
|
deleteBundleConfirm: function (bundleid) {
|
|
var store = new ChatOmemoStorage();
|
|
|
|
var address = new libsignal.SignalProtocolAddress(USER_JID, bundleid);
|
|
store.removeSession(address);
|
|
|
|
Account_ajaxDeleteBundleConfirm(bundleid, store.getOwnSessionsIds());
|
|
},
|
|
/**
|
|
* OMEMO key backup of this device: identity, prekeys and sessions,
|
|
* always encrypted (PBKDF2 + AES-256-GCM), even with an empty password.
|
|
* Everything happens in the browser, the server never sees the keys.
|
|
*/
|
|
backupFormat: 'movim-omemo-backup',
|
|
backupIterations: 600000,
|
|
backupCipherKey: async function (password, salt, iterations) {
|
|
const base = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']);
|
|
return crypto.subtle.deriveKey(
|
|
{ name: 'PBKDF2', salt: salt, iterations: iterations, hash: 'SHA-256' },
|
|
base, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']
|
|
);
|
|
},
|
|
backupToBase64: function (buffer) {
|
|
let bytes = new Uint8Array(buffer);
|
|
let binary = '';
|
|
for (let i = 0; i < bytes.length; i += 0x8000) {
|
|
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
|
|
}
|
|
return btoa(binary);
|
|
},
|
|
backupError: function (text) {
|
|
document.querySelector('#omemo_backup_error').textContent = text;
|
|
},
|
|
omemoBackup: async function () {
|
|
let form = document.querySelector('form[name=omemo_backup]');
|
|
let password = form.password.value;
|
|
|
|
if (password != form.password_confirmation.value) return Account.backupError(form.dataset.notsame);
|
|
|
|
let store = new ChatOmemoStorage();
|
|
let prefix = store.storageVersion + '.' + store.jid + '.';
|
|
let keys = {};
|
|
Object.keys(localStorage).filter(k => k.startsWith(prefix)).forEach(k => keys[k] = localStorage.getItem(k));
|
|
let deviceId = await store.getLocalRegistrationId();
|
|
|
|
let payload = JSON.stringify({
|
|
jid: store.jid,
|
|
deviceId: deviceId,
|
|
keys: keys
|
|
});
|
|
|
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
const key = await Account.backupCipherKey(password, salt, Account.backupIterations);
|
|
const cipher = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, new TextEncoder().encode(payload));
|
|
|
|
let backup = JSON.stringify({
|
|
format: Account.backupFormat,
|
|
version: 1,
|
|
jid: store.jid,
|
|
deviceId: deviceId,
|
|
created: new Date().toISOString(),
|
|
kdf: { name: 'PBKDF2-SHA256', iterations: Account.backupIterations, salt: Account.backupToBase64(salt) },
|
|
iv: Account.backupToBase64(iv),
|
|
data: Account.backupToBase64(cipher)
|
|
});
|
|
|
|
let link = document.createElement('a');
|
|
link.href = URL.createObjectURL(new Blob([backup], { type: 'application/json' }));
|
|
link.download = 'movim-omemo-' + store.jid + '-' + deviceId + '-' + new Date().toISOString().slice(0, 10) + '.json';
|
|
link.click();
|
|
URL.revokeObjectURL(link.href);
|
|
|
|
Dialog_ajaxClear();
|
|
},
|
|
omemoRestore: async function () {
|
|
let form = document.querySelector('form[name=omemo_restore]');
|
|
let file = form.file.files[0];
|
|
if (!file) return Account.backupError(form.dataset.nofile);
|
|
|
|
let backup, payload;
|
|
try {
|
|
backup = JSON.parse(await file.text());
|
|
if (backup.format != Account.backupFormat || backup.version != 1) throw new Error();
|
|
} catch (e) {
|
|
return Account.backupError(form.dataset.invalid);
|
|
}
|
|
|
|
if (backup.jid != USER_JID) return Account.backupError(form.dataset.otheraccount + ' ' + backup.jid);
|
|
|
|
try {
|
|
const key = await Account.backupCipherKey(form.password.value,
|
|
MovimUtils.base64ToArrayBuffer(backup.kdf.salt), backup.kdf.iterations);
|
|
const plain = await crypto.subtle.decrypt(
|
|
{ name: 'AES-GCM', iv: MovimUtils.base64ToArrayBuffer(backup.iv) },
|
|
key, MovimUtils.base64ToArrayBuffer(backup.data));
|
|
payload = JSON.parse(new TextDecoder().decode(plain));
|
|
} catch (e) {
|
|
return Account.backupError(form.dataset.wrongpassword);
|
|
}
|
|
|
|
let store = new ChatOmemoStorage();
|
|
let prefix = store.storageVersion + '.' + store.jid + '.';
|
|
if (Object.keys(payload.keys).some(k => !k.startsWith(prefix))) {
|
|
return Account.backupError(form.dataset.invalid);
|
|
}
|
|
|
|
// Replace the keys of this device with the backup ones
|
|
let replaced = await store.getLocalRegistrationId();
|
|
Object.keys(localStorage).filter(k => k.startsWith(prefix)).forEach(k => localStorage.removeItem(k));
|
|
Object.entries(payload.keys).forEach(([k, v]) => localStorage.setItem(k, v));
|
|
|
|
localStorage.setObject(OMEMO_RESTORED_KEY, { replaced: replaced != payload.deviceId ? replaced ?? null : null });
|
|
|
|
// The page reload publishes the restored key (ChatOmemo.ownDevicesReceived)
|
|
location.reload();
|
|
},
|
|
refreshFingerprints: async function () {
|
|
ChatOmemo.resolveContactFingerprints(USER_JID).then(remoteKeys => {
|
|
let omemoStorage = new ChatOmemoStorage;
|
|
|
|
omemoStorage.getLocalRegistrationId().then(localBundleId => {
|
|
omemoStorage.getIdentityKeyPair().then(identityKey => {
|
|
// Remove it if its already there
|
|
remoteKeys = remoteKeys.filter(remoteKey =>
|
|
remoteKey.bundleid != localBundleId.toString()
|
|
);
|
|
|
|
remoteKeys.unshift({
|
|
jid: USER_JID,
|
|
self: true,
|
|
bundleid: localBundleId.toString(),
|
|
fingerprint: MovimUtils.arrayBufferToBase64(identityKey.pubKey)
|
|
});
|
|
|
|
Account_ajaxHttpGetFingerprints(remoteKeys);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
MovimWebsocket.attach(function () {
|
|
if (OMEMO_ENABLED) Account.refreshFingerprints();
|
|
Account_ajaxHttpGetPresences();
|
|
Account_ajaxGetGateways();
|
|
});
|