xmpp.chapril.org-conversejs/src/plugins/omemo/mixins/converse.js

120 lines
4.9 KiB
JavaScript
Raw Normal View History

2021-04-12 04:29:00 +02:00
import concat from 'lodash-es/concat';
import { UNTRUSTED } from '../consts.js';
import { __ } from 'i18n';
import { _converse, converse } from '@converse/headless/core';
import {
addKeysToMessageStanza,
generateFingerprint,
getDevicesForContact,
getSession,
omemo,
} from '../utils.js';
const { Strophe, $msg } = converse.env;
const ConverseMixins = {
generateFingerprints: async function (jid) {
const devices = await getDevicesForContact(jid);
return Promise.all(devices.map(d => generateFingerprint(d)));
},
getDeviceForContact: function (jid, device_id) {
return getDevicesForContact(jid).then(devices => devices.get(device_id));
},
contactHasOMEMOSupport: async function (jid) {
/* Checks whether the contact advertises any OMEMO-compatible devices. */
const devices = await getDevicesForContact(jid);
return devices.length > 0;
},
getBundlesAndBuildSessions: async function (chatbox) {
const no_devices_err = __('Sorry, no devices found to which we can send an OMEMO encrypted message.');
let devices;
if (chatbox.get('type') === _converse.CHATROOMS_TYPE) {
const collections = await Promise.all(chatbox.occupants.map(o => getDevicesForContact(o.get('jid'))));
devices = collections.reduce((a, b) => concat(a, b.models), []);
} else if (chatbox.get('type') === _converse.PRIVATE_CHAT_TYPE) {
const their_devices = await getDevicesForContact(chatbox.get('jid'));
if (their_devices.length === 0) {
const err = new Error(no_devices_err);
err.user_facing = true;
throw err;
}
const own_devices = _converse.devicelists.get(_converse.bare_jid).devices;
devices = [...own_devices.models, ...their_devices.models];
}
// Filter out our own device
const id = _converse.omemo_store.get('device_id');
devices = devices.filter(d => d.get('id') !== id);
// Fetch bundles if necessary
await Promise.all(devices.map(d => d.getBundle()));
const sessions = devices.filter(d => d).map(d => getSession(d));
await Promise.all(sessions);
if (sessions.includes(null)) {
// We couldn't build a session for certain devices.
devices = devices.filter(d => sessions[devices.indexOf(d)]);
if (devices.length === 0) {
const err = new Error(no_devices_err);
err.user_facing = true;
throw err;
}
}
return devices;
},
createOMEMOMessageStanza: function (chatbox, message, devices) {
const body = __(
'This is an OMEMO encrypted message which your client doesnt seem to support. ' +
'Find more information on https://conversations.im/omemo'
);
if (!message.get('message')) {
throw new Error('No message body to encrypt!');
}
const stanza = $msg({
'from': _converse.connection.jid,
'to': chatbox.get('jid'),
'type': chatbox.get('message_type'),
'id': message.get('msgid')
}).c('body').t(body).up();
if (message.get('type') === 'chat') {
stanza.c('request', { 'xmlns': Strophe.NS.RECEIPTS }).up();
}
// An encrypted header is added to the message for
// each device that is supposed to receive it.
// These headers simply contain the key that the
// payload message is encrypted with,
// and they are separately encrypted using the
// session corresponding to the counterpart device.
stanza
.c('encrypted', { 'xmlns': Strophe.NS.OMEMO })
.c('header', { 'sid': _converse.omemo_store.get('device_id') });
return omemo.encryptMessage(message.get('message')).then(obj => {
// The 16 bytes key and the GCM authentication tag (The tag
// SHOULD have at least 128 bit) are concatenated and for each
// intended recipient device, i.e. both own devices as well as
// devices associated with the contact, the result of this
// concatenation is encrypted using the corresponding
// long-standing SignalProtocol session.
const promises = devices
.filter(device => device.get('trusted') != UNTRUSTED && device.get('active'))
.map(device => chatbox.encryptKey(obj.key_and_tag, device));
return Promise.all(promises)
.then(dicts => addKeysToMessageStanza(stanza, dicts, obj.iv))
.then(stanza => {
stanza.c('payload').t(obj.payload).up().up();
stanza.c('store', { 'xmlns': Strophe.NS.HINTS });
return stanza;
});
});
}
}
export default ConverseMixins;