vietnamese

This commit is contained in:
nnduc
2026-10-24 17:46:20 +07:00
parent 5467b0aa2c
commit a3cf10cb0a
33 changed files with 4306 additions and 94 deletions
+109
View File
@@ -39,6 +39,115 @@ var Account = {
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;