aboutsummaryrefslogtreecommitdiff
path: root/plugins/omemo/src/module.vala
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/omemo/src/module.vala')
-rw-r--r--plugins/omemo/src/module.vala547
1 files changed, 0 insertions, 547 deletions
diff --git a/plugins/omemo/src/module.vala b/plugins/omemo/src/module.vala
deleted file mode 100644
index 728251f0..00000000
--- a/plugins/omemo/src/module.vala
+++ /dev/null
@@ -1,547 +0,0 @@
-using Gee;
-using Xmpp;
-using Xmpp.Core;
-using Xmpp.Xep;
-using Signal;
-
-namespace Dino.Omemo {
-
-private const string NS_URI = "eu.siacs.conversations.axolotl";
-private const string NODE_DEVICELIST = NS_URI + ".devicelist";
-private const string NODE_BUNDLES = NS_URI + ".bundles";
-private const string NODE_VERIFICATION = NS_URI + ".verification";
-
-private const int NUM_KEYS_TO_PUBLISH = 100;
-
-public class Module : XmppStreamModule {
- private const string ID = "axolotl_module";
- public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, ID);
-
- private Store store;
- internal static Context context;
- private bool device_list_loading = false;
- private bool device_list_modified = false;
- private Map<string, ArrayList<int32>> device_lists = new HashMap<string, ArrayList<int32>>();
- private Map<string, ArrayList<int32>> ignored_devices = new HashMap<string, ArrayList<int32>>();
-
- public signal void store_created(Context context, Store store);
- public signal void device_list_loaded();
- public signal void session_started(string jid, int device_id);
-
- public Module() {
- lock(context) {
- if (context == null) {
- try {
- context = new Context(true);
- } catch (Error e) {
- print(@"Error initializing axolotl: $(e.message)\n");
- }
- }
- }
- }
-
- public EncryptStatus encrypt(Message.Stanza message, string self_bare_jid) {
- EncryptStatus status = new EncryptStatus();
- if (context == null) return status;
- try {
- string name = get_bare_jid(message.to);
- if (device_lists.get(name) == null || device_lists.get(self_bare_jid) == null) return status;
- status.other_devices = device_lists.get(name).size;
- status.own_devices = device_lists.get(self_bare_jid).size;
- if (status.other_devices == 0) return status;
-
- uint8[] key = new uint8[16];
- context.randomize(key);
- uint8[] iv = new uint8[16];
- context.randomize(iv);
-
- uint8[] ciphertext = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, message.body.data);
-
- StanzaNode header = null;
- StanzaNode encrypted = new StanzaNode.build("encrypted", NS_URI).add_self_xmlns()
- .put_node(header = new StanzaNode.build("header", NS_URI)
- .put_attribute("sid", store.local_registration_id.to_string())
- .put_node(new StanzaNode.build("iv", NS_URI)
- .put_node(new StanzaNode.text(Base64.encode(iv)))))
- .put_node(new StanzaNode.build("payload", NS_URI)
- .put_node(new StanzaNode.text(Base64.encode(ciphertext))));
-
- Address address = new Address();
- address.name = name;
- foreach(int32 device_id in device_lists[name]) {
- if (is_ignored_device(name, device_id)) {
- status.other_lost++;
- continue;
- }
- try {
- address.device_id = (int) device_id;
- StanzaNode key_node = create_encrypted_key(key, address);
- header.put_node(key_node);
- status.other_success++;
- } catch (Error e) {
- if (e.code == ErrorCode.UNKNOWN) status.other_unknown++;
- else status.other_failure++;
- }
- }
- address.name = self_bare_jid;
- foreach(int32 device_id in device_lists[self_bare_jid]) {
- if (is_ignored_device(self_bare_jid, device_id)) {
- status.own_lost++;
- continue;
- }
- if (device_id != store.local_registration_id) {
- address.device_id = (int) device_id;
- try {
- StanzaNode key_node = create_encrypted_key(key, address);
- header.put_node(key_node);
- status.own_success++;
- } catch (Error e) {
- if (e.code == ErrorCode.UNKNOWN) status.own_unknown++;
- else status.own_failure++;
- }
- }
- }
-
- message.stanza.put_node(encrypted);
- message.body = "[This message is OMEMO encrypted]";
- status.encrypted = true;
- } catch (Error e) {
- print(@"Axolotl error while encrypting message: $(e.message)\n");
- }
- return status;
- }
-
- private StanzaNode create_encrypted_key(uint8[] key, Address address) throws GLib.Error {
- SessionCipher cipher = store.create_session_cipher(address);
- CiphertextMessage device_key = cipher.encrypt(key);
- StanzaNode key_node = new StanzaNode.build("key", NS_URI)
- .put_attribute("rid", address.device_id.to_string())
- .put_node(new StanzaNode.text(Base64.encode(device_key.serialized)));
- if (device_key.type == CiphertextType.PREKEY) key_node.put_attribute("prekey", "true");
- return key_node;
- }
-
- public override void attach(XmppStream stream) {
- if (context == null) return;
- Message.Module.require(stream);
- Pubsub.Module.require(stream);
- stream.get_module(Message.Module.IDENTITY).pre_received_message.connect(on_pre_received_message);
- stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NODE_DEVICELIST, on_devicelist, this);
- this.store = context.create_store();
- store_created(context, store);
- }
-
- private void on_pre_received_message(XmppStream stream, Message.Stanza message) {
- StanzaNode? encrypted = message.stanza.get_subnode("encrypted", NS_URI);
- if (encrypted == null) return;
- MessageFlag flag = new MessageFlag();
- message.add_flag(flag);
- StanzaNode? header = encrypted.get_subnode("header");
- if (header == null || header.get_attribute_int("sid") <= 0) return;
- foreach (StanzaNode key_node in header.get_subnodes("key")) {
- if (key_node.get_attribute_int("rid") == store.local_registration_id) {
- try {
- uint8[] key = null;
- uint8[] ciphertext = Base64.decode(encrypted.get_subnode("payload").get_string_content());
- uint8[] iv = Base64.decode(header.get_subnode("iv").get_string_content());
- Address address = new Address();
- address.name = get_bare_jid(message.from);
- address.device_id = header.get_attribute_int("sid");
- if (key_node.get_attribute_bool("prekey")) {
- PreKeySignalMessage msg = context.deserialize_pre_key_signal_message(Base64.decode(key_node.get_string_content()));
- SessionCipher cipher = store.create_session_cipher(address);
- key = cipher.decrypt_pre_key_signal_message(msg);
- } else {
- SignalMessage msg = context.deserialize_signal_message(Base64.decode(key_node.get_string_content()));
- SessionCipher cipher = store.create_session_cipher(address);
- key = cipher.decrypt_signal_message(msg);
- }
- address.device_id = 0; // TODO: Hack to have address obj live longer
-
-
- if (key != null && ciphertext != null && iv != null) {
- if (key.length >= 32) {
- int authtaglength = key.length - 16;
- uint8[] new_ciphertext = new uint8[ciphertext.length + authtaglength];
- uint8[] new_key = new uint8[16];
- Memory.copy(new_ciphertext, ciphertext, ciphertext.length);
- Memory.copy((uint8*)new_ciphertext + ciphertext.length, (uint8*)key + 16, authtaglength);
- Memory.copy(new_key, key, 16);
- ciphertext = new_ciphertext;
- key = new_key;
- }
-
- message.body = arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, iv, ciphertext));
- flag.decrypted = true;
- }
- } catch (Error e) {
- print(@"Axolotl error while decrypting message: $(e.message)\n");
- }
- }
- }
- }
-
- private string arr_to_str(uint8[] arr) {
- // null-terminate the array
- uint8[] rarr = new uint8[arr.length+1];
- Memory.copy(rarr, arr, arr.length);
- return (string)rarr;
- }
-
- public void on_devicelist(XmppStream stream, string jid, string id, StanzaNode node) {
- if (jid == get_bare_jid(Bind.Flag.get_flag(stream).my_jid) && store.local_registration_id != 0) {
- lock (device_list_loading) {
- if (!device_list_loading) {
- device_list_loading = true;
- GLib.Timeout.add_seconds(3, () => {
- bool cont = false;
- lock (device_lists) {
- if (device_list_modified) {
- cont = true;
- device_list_modified = false;
- }
- }
- if (!cont) {
- lock (device_list_loading) {
- device_list_loading = false;
- device_list_loaded();
- }
- }
- return cont;
- });
- }
- }
-
- bool am_on_devicelist = false;
- foreach (StanzaNode device_node in node.get_subnodes("device")) {
- int device_id = device_node.get_attribute_int("id");
- if (store.local_registration_id == device_id) {
- am_on_devicelist = true;
- }
- }
- if (!am_on_devicelist) {
- print(@"Not on device list, adding id\n");
- node.put_node(new StanzaNode.build("device", NS_URI).put_attribute("id", store.local_registration_id.to_string()));
- stream.get_module(Pubsub.Module.IDENTITY).publish(stream, jid, NODE_DEVICELIST, NODE_DEVICELIST, id, node);
- } else {
- publish_bundles_if_needed(stream, jid);
- }
- }
- lock(device_lists) {
- device_list_modified = true;
- device_lists[jid] = new ArrayList<int32>();
- foreach (StanzaNode device_node in node.get_subnodes("device")) {
- device_lists[jid].add(device_node.get_attribute_int("id"));
- }
- }
- }
-
- public void start_sessions_with(XmppStream stream, string bare_jid) {
- if (!device_lists.has_key(bare_jid)) {
- // TODO: manually request a device list
- return;
- }
- Address address = new Address();
- address.name = bare_jid;
- foreach(int32 device_id in device_lists[bare_jid]) {
- if (!is_ignored_device(bare_jid, device_id)) {
- address.device_id = device_id;
- if (!store.contains_session(address)) {
- start_session_with(stream, bare_jid, device_id);
- }
- }
- }
- address.device_id = 0;
- }
-
- public void start_session_with(XmppStream stream, string bare_jid, int device_id) {
- print(@"Asking for bundle from $bare_jid/$device_id\n");
- stream.get_module(Pubsub.Module.IDENTITY).request(stream, bare_jid, @"$NODE_BUNDLES:$device_id", on_other_bundle_result, Tuple.create(store, device_id));
- }
-
- public void ignore_device(string jid, int32 device_id) {
- if (device_id <= 0) return;
- lock (ignored_devices) {
- if (!ignored_devices.has_key(jid)) {
- ignored_devices[jid] = new ArrayList<int32>();
- }
- ignored_devices[jid].add(device_id);
- }
- }
-
- public bool is_ignored_device(string jid, int32 device_id) {
- if (device_id <= 0) return true;
- lock (ignored_devices) {
- return ignored_devices.has_key(jid) && ignored_devices[jid].contains(device_id);
- }
- }
-
- private static void on_other_bundle_result(XmppStream stream, string jid, string? id, StanzaNode? node, Object? storage) {
- Tuple<Store, int> tuple = (Tuple<Store, int>)storage;
- Store store = tuple.a;
- int device_id = tuple.b;
-
- bool fail = false;
- if (node == null) {
- // Device not registered, shouldn't exist
- fail = true;
- } else {
- Bundle bundle = new Bundle(node);
- int32 signed_pre_key_id = bundle.signed_pre_key_id;
- ECPublicKey? signed_pre_key = bundle.signed_pre_key;
- uint8[] signed_pre_key_signature = bundle.signed_pre_key_signature;
- ECPublicKey? identity_key = bundle.identity_key;
-
- ArrayList<Bundle.PreKey> pre_keys = bundle.pre_keys;
- int pre_key_idx = Random.int_range(0, pre_keys.size);
- int32 pre_key_id = pre_keys[pre_key_idx].key_id;
- ECPublicKey? pre_key = pre_keys[pre_key_idx].key;
-
- if (signed_pre_key_id < 0 || signed_pre_key == null || identity_key == null || pre_key_id < 0 || pre_key == null) {
- fail = true;
- } else {
- Address address = new Address();
- address.name = jid;
- address.device_id = device_id;
- try {
- if (store.contains_session(address)) {
- return;
- }
- SessionBuilder builder = store.create_session_builder(address);
- builder.process_pre_key_bundle(create_pre_key_bundle(device_id, device_id, pre_key_id, pre_key, signed_pre_key_id, signed_pre_key, signed_pre_key_signature, identity_key));
- } catch (Error e) {
- fail = true;
- }
- address.device_id = 0; // TODO: Hack to have address obj live longer
- get_module(stream).session_started(jid, device_id);
- }
- }
- if (fail) {
- get_module(stream).ignore_device(jid, device_id);
- }
- }
-
- public void publish_bundles_if_needed(XmppStream stream, string jid) {
- stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, @"$NODE_BUNDLES:$(store.local_registration_id)", on_self_bundle_result, store);
- }
-
- private static void on_self_bundle_result(XmppStream stream, string jid, string? id, StanzaNode? node, Object? storage) {
- Store store = (Store)storage;
- Map<int, ECPublicKey> keys = new HashMap<int, ECPublicKey>();
- ECPublicKey identity_key = null;
- IdentityKeyPair identity_key_pair = null;
- int32 signed_pre_key_id = -1;
- ECPublicKey signed_pre_key = null;
- SignedPreKeyRecord signed_pre_key_record = null;
- bool changed = false;
- if (node == null) {
- identity_key = store.identity_key_pair.public;
- changed = true;
- } else {
- Bundle bundle = new Bundle(node);
- foreach (Bundle.PreKey prekey in bundle.pre_keys) {
- keys[prekey.key_id] = prekey.key;
- }
- identity_key = bundle.identity_key;
- signed_pre_key_id = bundle.signed_pre_key_id;;
- signed_pre_key = bundle.signed_pre_key;
- }
-
- // Validate IdentityKey
- if (store.identity_key_pair.public.compare(identity_key) != 0) {
- changed = true;
- }
- identity_key_pair = store.identity_key_pair;
-
- // Validate signedPreKeyRecord + ID
- if (signed_pre_key_id == -1 || !store.contains_signed_pre_key(signed_pre_key_id) || store.load_signed_pre_key(signed_pre_key_id).key_pair.public.compare(signed_pre_key) != 0) {
- signed_pre_key_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number
- signed_pre_key_record = context.generate_signed_pre_key(identity_key_pair, signed_pre_key_id);
- store.store_signed_pre_key(signed_pre_key_record);
- changed = true;
- } else {
- signed_pre_key_record = store.load_signed_pre_key(signed_pre_key_id);
- }
-
- // Validate PreKeys
- Set<PreKeyRecord> pre_key_records = new HashSet<PreKeyRecord>();
- foreach (var entry in keys.entries) {
- if (store.contains_pre_key(entry.key)) {
- PreKeyRecord record = store.load_pre_key(entry.key);
- if (record.key_pair.public.compare(entry.value) == 0) {
- pre_key_records.add(record);
- }
- }
- }
- int new_keys = NUM_KEYS_TO_PUBLISH - pre_key_records.size;
- if (new_keys > 0) {
- int32 next_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number
- Set<PreKeyRecord> new_records = context.generate_pre_keys((uint)next_id, (uint)new_keys);
- pre_key_records.add_all(new_records);
- foreach (PreKeyRecord record in new_records) {
- store.store_pre_key(record);
- }
- changed = true;
- }
-
- if (changed) {
- publish_bundles(stream, signed_pre_key_record, identity_key_pair, pre_key_records, (int32) store.local_registration_id);
- }
- }
-
- public static void publish_bundles(XmppStream stream, SignedPreKeyRecord signed_pre_key_record, IdentityKeyPair identity_key_pair, Set<PreKeyRecord> pre_key_records, int32 device_id) {
- ECKeyPair tmp;
- StanzaNode bundle = new StanzaNode.build("bundle", NS_URI)
- .add_self_xmlns()
- .put_node(new StanzaNode.build("signedPreKeyPublic", NS_URI)
- .put_attribute("signedPreKeyId", signed_pre_key_record.id.to_string())
- .put_node(new StanzaNode.text(Base64.encode((tmp = signed_pre_key_record.key_pair).public.serialize()))))
- .put_node(new StanzaNode.build("signedPreKeySignature", NS_URI)
- .put_node(new StanzaNode.text(Base64.encode(signed_pre_key_record.signature))))
- .put_node(new StanzaNode.build("identityKey", NS_URI)
- .put_node(new StanzaNode.text(Base64.encode(identity_key_pair.public.serialize()))));
- StanzaNode prekeys = new StanzaNode.build("prekeys", NS_URI);
- foreach (PreKeyRecord pre_key_record in pre_key_records) {
- prekeys.put_node(new StanzaNode.build("preKeyPublic", NS_URI)
- .put_attribute("preKeyId", pre_key_record.id.to_string())
- .put_node(new StanzaNode.text(Base64.encode(pre_key_record.key_pair.public.serialize()))));
- }
- bundle.put_node(prekeys);
-
- stream.get_module(Pubsub.Module.IDENTITY).publish(stream, null, @"$NODE_BUNDLES:$device_id", @"$NODE_BUNDLES:$device_id", "1", bundle);
- }
-
- public override void detach(XmppStream stream) {
-
- }
-
- public static Module? get_module(XmppStream stream) {
- return (Module?) stream.get_module(IDENTITY);
- }
-
- public override string get_ns() {
- return NS_URI;
- }
-
- public override string get_id() {
- return ID;
- }
-}
-
-public class MessageFlag : Message.MessageFlag {
- public const string id = "axolotl";
-
- public bool decrypted = false;
-
- public static MessageFlag? get_flag(Message.Stanza message) {
- return (MessageFlag) message.get_flag(NS_URI, id);
- }
-
- public override string get_ns() {
- return NS_URI;
- }
-
- public override string get_id() {
- return id;
- }
-}
-
-internal class Bundle {
- private StanzaNode? node;
-
- public Bundle(StanzaNode? node) {
- this.node = node;
- }
-
- public int32 signed_pre_key_id { owned get {
- if (node == null) return -1;
- string id = node.get_deep_attribute("signedPreKeyPublic", "signedPreKeyId");
- if (id == null) return -1;
- return id.to_int();
- }}
-
- public ECPublicKey? signed_pre_key { owned get {
- if (node == null) return null;
- string? key = node.get_deep_string_content("signedPreKeyPublic");
- if (key == null) return null;
- try {
- return Module.context.decode_public_key(Base64.decode(key));
- } catch (Error e) {
- return null;
- }
- }}
-
- public uint8[] signed_pre_key_signature { owned get {
- if (node == null) return null;
- string? sig = node.get_deep_string_content("signedPreKeySignature");
- if (sig == null) return null;
- try {
- return Base64.decode(sig);
- } catch (Error e) {
- return null;
- }
- }}
-
- public ECPublicKey? identity_key { owned get {
- if (node == null) return null;
- string? key = node.get_deep_string_content("identityKey");
- if (key == null) return null;
- try {
- return Module.context.decode_public_key(Base64.decode(key));
- } catch (Error e) {
- return null;
- }
- }}
-
- public ArrayList<PreKey> pre_keys { owned get {
- if (node == null || node.get_subnode("prekeys") == null) return null;
- ArrayList<PreKey> list = new ArrayList<PreKey>();
- node.get_deep_subnodes("prekeys", "preKeyPublic")
- .filter((node) => node.get_attribute("preKeyId") != null)
- .map<PreKey>(PreKey.create)
- .foreach((key) => list.add(key));
- return list;
- }}
-
- internal class PreKey {
- private StanzaNode node;
-
- public static PreKey create(owned StanzaNode node) {
- return new PreKey(node);
- }
-
- public PreKey(StanzaNode node) {
- this.node = node;
- }
-
- public int32 key_id { owned get {
- return (node.get_attribute("preKeyId") ?? "-1").to_int();
- }}
-
- public ECPublicKey? key { owned get {
- string? key = node.get_string_content();
- if (key == null) return null;
- try {
- return Module.context.decode_public_key(Base64.decode(key));
- } catch (Error e) {
- return null;
- }
- }}
- }
-}
-
-public class EncryptStatus {
- public bool encrypted { get; internal set; }
- public int other_devices { get; internal set; }
- public int other_success { get; internal set; }
- public int other_lost { get; internal set; }
- public int other_unknown { get; internal set; }
- public int other_failure { get; internal set; }
- public int own_devices { get; internal set; }
- public int own_success { get; internal set; }
- public int own_lost { get; internal set; }
- public int own_unknown { get; internal set; }
- public int own_failure { get; internal set; }
-}
-
-} \ No newline at end of file