2017-03-11 00:29:38 +00:00
|
|
|
using Gee;
|
|
|
|
using Xmpp;
|
2018-01-12 20:03:09 +00:00
|
|
|
using Xmpp;
|
2017-03-11 00:29:38 +00:00
|
|
|
using Xmpp.Xep;
|
|
|
|
using Signal;
|
|
|
|
|
2017-03-12 01:28:23 +00:00
|
|
|
namespace Dino.Plugins.Omemo {
|
2017-03-11 00:29:38 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-03-12 01:28:23 +00:00
|
|
|
public class StreamModule : XmppStreamModule {
|
2018-01-12 20:03:09 +00:00
|
|
|
public static Xmpp.ModuleIdentity<StreamModule> IDENTITY = new Xmpp.ModuleIdentity<StreamModule>(NS_URI, "omemo_module");
|
2017-03-11 00:29:38 +00:00
|
|
|
|
2018-07-25 20:27:26 +00:00
|
|
|
public Store store { public get; private set; }
|
2017-03-15 16:23:13 +00:00
|
|
|
private ConcurrentSet<string> active_bundle_requests = new ConcurrentSet<string>();
|
2018-01-12 20:03:09 +00:00
|
|
|
private ConcurrentSet<Jid> active_devicelist_requests = new ConcurrentSet<Jid>();
|
|
|
|
private Map<Jid, ArrayList<int32>> ignored_devices = new HashMap<Jid, ArrayList<int32>>(Jid.hash_bare_func, Jid.equals_bare_func);
|
2017-11-21 22:27:27 +00:00
|
|
|
private ReceivedPipelineListener received_pipeline_listener;
|
2017-03-11 00:29:38 +00:00
|
|
|
|
2017-03-12 01:28:23 +00:00
|
|
|
public signal void store_created(Store store);
|
2018-07-25 20:27:26 +00:00
|
|
|
public signal void device_list_loaded(Jid jid, ArrayList<int32> devices);
|
2018-01-12 20:03:09 +00:00
|
|
|
public signal void bundle_fetched(Jid jid, int device_id, Bundle bundle);
|
2017-03-11 00:29:38 +00:00
|
|
|
|
|
|
|
public override void attach(XmppStream stream) {
|
2017-04-18 15:55:20 +00:00
|
|
|
if (!Plugin.ensure_context()) return;
|
2017-03-12 20:20:56 +00:00
|
|
|
|
2017-04-18 15:55:20 +00:00
|
|
|
this.store = Plugin.get_context().create_store();
|
2017-03-12 01:28:23 +00:00
|
|
|
store_created(store);
|
2017-11-21 22:27:27 +00:00
|
|
|
received_pipeline_listener = new ReceivedPipelineListener(store);
|
2018-01-12 20:03:09 +00:00
|
|
|
stream.get_module(MessageModule.IDENTITY).received_pipeline.connect(received_pipeline_listener);
|
2017-06-13 16:14:59 +00:00
|
|
|
stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id, node));
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
2017-11-21 22:27:27 +00:00
|
|
|
public override void detach(XmppStream stream) {
|
2018-01-12 20:03:09 +00:00
|
|
|
stream.get_module(MessageModule.IDENTITY).received_pipeline.disconnect(received_pipeline_listener);
|
2017-11-21 22:27:27 +00:00
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
public void request_user_devicelist(XmppStream stream, Jid jid) {
|
2018-07-25 20:27:26 +00:00
|
|
|
if (active_devicelist_requests.add(jid)) {
|
|
|
|
if (Plugin.DEBUG) print(@"OMEMO: requesting device list for $jid\n");
|
|
|
|
stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id, node));
|
2017-03-15 16:23:13 +00:00
|
|
|
}
|
2017-03-13 20:54:12 +00:00
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
public void on_devicelist(XmppStream stream, Jid jid, string? id, StanzaNode? node_) {
|
2017-04-18 15:55:20 +00:00
|
|
|
StanzaNode node = node_ ?? new StanzaNode.build("list", NS_URI).add_self_xmlns();
|
2018-01-12 20:03:09 +00:00
|
|
|
Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
|
2017-04-18 15:55:20 +00:00
|
|
|
if (my_jid == null) return;
|
2018-01-12 20:03:09 +00:00
|
|
|
if (jid.equals_bare(my_jid) && store.local_registration_id != 0) {
|
2017-03-11 00:29:38 +00:00
|
|
|
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) {
|
2017-03-15 16:23:13 +00:00
|
|
|
if (Plugin.DEBUG) print(@"OMEMO: Not on device list, adding id\n");
|
2017-03-11 00:29:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2018-07-25 20:27:26 +00:00
|
|
|
|
|
|
|
ArrayList<int32> device_list = new ArrayList<int32>();
|
|
|
|
foreach (StanzaNode device_node in node.get_subnodes("device")) {
|
|
|
|
device_list.add(device_node.get_attribute_int("id"));
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
2017-03-15 16:23:13 +00:00
|
|
|
active_devicelist_requests.remove(jid);
|
2018-07-25 20:27:26 +00:00
|
|
|
device_list_loaded(jid, device_list);
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 20:27:26 +00:00
|
|
|
public void fetch_bundles(XmppStream stream, Jid jid, Gee.List<int32> devices) {
|
|
|
|
Address address = new Address(jid.bare_jid.to_string(), 0);
|
|
|
|
foreach(int32 device_id in devices) {
|
|
|
|
if (!is_ignored_device(jid, device_id)) {
|
|
|
|
address.device_id = device_id;
|
|
|
|
try {
|
|
|
|
if (!store.contains_session(address)) {
|
|
|
|
fetch_bundle(stream, jid, device_id);
|
2017-03-12 01:28:23 +00:00
|
|
|
}
|
2018-07-25 20:27:26 +00:00
|
|
|
} catch (Error e) {
|
|
|
|
// Ignore
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-25 20:27:26 +00:00
|
|
|
address.device_id = 0;
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
public void fetch_bundle(XmppStream stream, Jid jid, int device_id) {
|
|
|
|
if (active_bundle_requests.add(jid.bare_jid.to_string() + @":$device_id")) {
|
|
|
|
if (Plugin.DEBUG) print(@"OMEMO: Asking for bundle from $(jid.bare_jid.to_string()):$device_id\n");
|
|
|
|
stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid.bare_jid, @"$NODE_BUNDLES:$device_id", (stream, jid, id, node) => {
|
2018-06-19 10:26:31 +00:00
|
|
|
on_other_bundle_result(stream, jid, device_id, id, node);
|
2017-05-13 15:48:13 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
public void ignore_device(Jid jid, int32 device_id) {
|
2017-03-11 00:29:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
public bool is_ignored_device(Jid jid, int32 device_id) {
|
2017-03-11 00:29:38 +00:00
|
|
|
if (device_id <= 0) return true;
|
|
|
|
lock (ignored_devices) {
|
|
|
|
return ignored_devices.has_key(jid) && ignored_devices[jid].contains(device_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
private void on_other_bundle_result(XmppStream stream, Jid jid, int device_id, string? id, StanzaNode? node) {
|
2017-03-11 23:25:47 +00:00
|
|
|
if (node == null) {
|
|
|
|
// Device not registered, shouldn't exist
|
2018-06-11 06:11:04 +00:00
|
|
|
stream.get_module(IDENTITY).ignore_device(jid, device_id);
|
2017-03-11 23:25:47 +00:00
|
|
|
} else {
|
|
|
|
Bundle bundle = new Bundle(node);
|
2017-05-13 15:48:13 +00:00
|
|
|
bundle_fetched(jid, device_id, bundle);
|
2018-06-11 06:11:04 +00:00
|
|
|
}
|
|
|
|
stream.get_module(IDENTITY).active_bundle_requests.remove(jid.bare_jid.to_string() + @":$device_id");
|
|
|
|
}
|
2017-03-11 23:25:47 +00:00
|
|
|
|
2018-06-19 10:26:31 +00:00
|
|
|
public bool start_session(XmppStream stream, Jid jid, int32 device_id, Bundle bundle) {
|
2018-06-11 06:11:04 +00:00
|
|
|
bool fail = false;
|
|
|
|
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;
|
|
|
|
if (signed_pre_key_id < 0 || signed_pre_key == null || identity_key == null || pre_keys.size == 0) {
|
|
|
|
fail = true;
|
|
|
|
} else {
|
|
|
|
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 (pre_key_id < 0 || pre_key == null) {
|
2017-03-11 00:29:38 +00:00
|
|
|
fail = true;
|
|
|
|
} else {
|
2018-06-11 06:11:04 +00:00
|
|
|
Address address = new Address(jid.bare_jid.to_string(), device_id);
|
|
|
|
try {
|
|
|
|
if (store.contains_session(address)) {
|
|
|
|
return false;
|
2017-08-24 13:55:24 +00:00
|
|
|
}
|
2018-06-11 06:11:04 +00:00
|
|
|
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;
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
2018-06-11 06:11:04 +00:00
|
|
|
address.device_id = 0; // TODO: Hack to have address obj live longer
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-11 23:25:47 +00:00
|
|
|
if (fail) {
|
2017-03-12 01:28:23 +00:00
|
|
|
stream.get_module(IDENTITY).ignore_device(jid, device_id);
|
2017-03-11 23:25:47 +00:00
|
|
|
}
|
2018-06-11 06:11:04 +00:00
|
|
|
return true;
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
public void publish_bundles_if_needed(XmppStream stream, Jid jid) {
|
|
|
|
if (active_bundle_requests.add(jid.bare_jid.to_string() + @":$(store.local_registration_id)")) {
|
2017-06-13 16:14:59 +00:00
|
|
|
stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, @"$NODE_BUNDLES:$(store.local_registration_id)", on_self_bundle_result);
|
2017-03-15 16:23:13 +00:00
|
|
|
}
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
private void on_self_bundle_result(XmppStream stream, Jid jid, string? id, StanzaNode? node) {
|
2017-04-18 15:55:20 +00:00
|
|
|
if (!Plugin.ensure_context()) return;
|
2017-03-11 23:25:47 +00:00
|
|
|
Map<int, ECPublicKey> keys = new HashMap<int, ECPublicKey>();
|
2017-04-18 15:55:20 +00:00
|
|
|
ECPublicKey? identity_key = null;
|
2017-03-11 23:25:47 +00:00
|
|
|
int32 signed_pre_key_id = -1;
|
2017-04-18 15:55:20 +00:00
|
|
|
ECPublicKey? signed_pre_key = null;
|
|
|
|
SignedPreKeyRecord? signed_pre_key_record = null;
|
2017-03-11 23:25:47 +00:00
|
|
|
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) {
|
2017-04-18 15:55:20 +00:00
|
|
|
ECPublicKey? key = prekey.key;
|
|
|
|
if (key != null) {
|
|
|
|
keys[prekey.key_id] = (!)key;
|
|
|
|
}
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
2017-03-11 23:25:47 +00:00
|
|
|
identity_key = bundle.identity_key;
|
|
|
|
signed_pre_key_id = bundle.signed_pre_key_id;;
|
|
|
|
signed_pre_key = bundle.signed_pre_key;
|
|
|
|
}
|
2017-03-11 00:29:38 +00:00
|
|
|
|
2017-03-12 01:28:23 +00:00
|
|
|
try {
|
|
|
|
// Validate IdentityKey
|
2017-04-18 15:55:20 +00:00
|
|
|
if (identity_key == null || store.identity_key_pair.public.compare((!)identity_key) != 0) {
|
2017-03-12 01:28:23 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
2017-04-18 15:55:20 +00:00
|
|
|
IdentityKeyPair identity_key_pair = store.identity_key_pair;
|
2017-03-12 01:28:23 +00:00
|
|
|
|
|
|
|
// Validate signedPreKeyRecord + ID
|
2017-04-18 15:55:20 +00:00
|
|
|
if (signed_pre_key == null || 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) {
|
2017-03-12 01:28:23 +00:00
|
|
|
signed_pre_key_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number
|
2017-04-18 15:55:20 +00:00
|
|
|
signed_pre_key_record = Plugin.get_context().generate_signed_pre_key(identity_key_pair, signed_pre_key_id);
|
|
|
|
store.store_signed_pre_key((!)signed_pre_key_record);
|
2017-03-12 01:28:23 +00:00
|
|
|
changed = true;
|
|
|
|
} else {
|
|
|
|
signed_pre_key_record = store.load_signed_pre_key(signed_pre_key_id);
|
|
|
|
}
|
2017-03-11 00:29:38 +00:00
|
|
|
|
2017-03-12 01:28:23 +00:00
|
|
|
// 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);
|
|
|
|
}
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-12 01:28:23 +00:00
|
|
|
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
|
2017-04-18 15:55:20 +00:00
|
|
|
Set<PreKeyRecord> new_records = Plugin.get_context().generate_pre_keys((uint)next_id, (uint)new_keys);
|
2017-03-12 01:28:23 +00:00
|
|
|
pre_key_records.add_all(new_records);
|
|
|
|
foreach (PreKeyRecord record in new_records) {
|
|
|
|
store.store_pre_key(record);
|
|
|
|
}
|
|
|
|
changed = true;
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
2017-03-12 01:28:23 +00:00
|
|
|
if (changed) {
|
2017-04-18 15:55:20 +00:00
|
|
|
publish_bundles(stream, (!)signed_pre_key_record, identity_key_pair, pre_key_records, (int32) store.local_registration_id);
|
2017-03-12 01:28:23 +00:00
|
|
|
}
|
|
|
|
} catch (Error e) {
|
2017-03-15 16:23:13 +00:00
|
|
|
if (Plugin.DEBUG) print(@"Unexpected error while publishing bundle: $(e.message)\n");
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
2018-01-12 20:03:09 +00:00
|
|
|
stream.get_module(IDENTITY).active_bundle_requests.remove(jid.bare_jid.to_string() + @":$(store.local_registration_id)");
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
2017-03-12 01:28:23 +00:00
|
|
|
public static void publish_bundles(XmppStream stream, SignedPreKeyRecord signed_pre_key_record, IdentityKeyPair identity_key_pair, Set<PreKeyRecord> pre_key_records, int32 device_id) throws Error {
|
2017-03-11 00:29:38 +00:00
|
|
|
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 string get_ns() {
|
|
|
|
return NS_URI;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override string get_id() {
|
2017-03-19 11:55:36 +00:00
|
|
|
return IDENTITY.id;
|
2017-03-11 00:29:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-11 20:29:13 +00:00
|
|
|
|
2018-01-12 20:03:09 +00:00
|
|
|
public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
2017-11-11 20:29:13 +00:00
|
|
|
|
|
|
|
private const string[] after_actions_const = {"EXTRACT_MESSAGE_2"};
|
|
|
|
|
|
|
|
public override string action_group { get { return "ENCRYPT_BODY"; } }
|
|
|
|
public override string[] after_actions { get { return after_actions_const; } }
|
|
|
|
|
|
|
|
private Store store;
|
|
|
|
|
|
|
|
public ReceivedPipelineListener(Store store) {
|
|
|
|
this.store = store;
|
|
|
|
}
|
|
|
|
|
2018-01-19 21:37:02 +00:00
|
|
|
public override async bool run(XmppStream stream, MessageStanza message) {
|
2017-11-11 20:29:13 +00:00
|
|
|
StanzaNode? _encrypted = message.stanza.get_subnode("encrypted", NS_URI);
|
2018-01-19 21:37:02 +00:00
|
|
|
if (_encrypted == null || MessageFlag.get_flag(message) != null || message.from == null) return false;
|
2017-11-11 20:29:13 +00:00
|
|
|
StanzaNode encrypted = (!)_encrypted;
|
2018-01-19 21:37:02 +00:00
|
|
|
if (!Plugin.ensure_context()) return false;
|
2017-11-11 20:29:13 +00:00
|
|
|
MessageFlag flag = new MessageFlag();
|
|
|
|
message.add_flag(flag);
|
|
|
|
StanzaNode? _header = encrypted.get_subnode("header");
|
2018-01-19 21:37:02 +00:00
|
|
|
if (_header == null) return false;
|
2017-11-11 20:29:13 +00:00
|
|
|
StanzaNode header = (!)_header;
|
2018-01-19 21:37:02 +00:00
|
|
|
if (header.get_attribute_int("sid") <= 0) return false;
|
2017-11-11 20:29:13 +00:00
|
|
|
foreach (StanzaNode key_node in header.get_subnodes("key")) {
|
|
|
|
if (key_node.get_attribute_int("rid") == store.local_registration_id) {
|
|
|
|
try {
|
|
|
|
string? payload = encrypted.get_deep_string_content("payload");
|
|
|
|
string? iv_node = header.get_deep_string_content("iv");
|
|
|
|
string? key_node_content = key_node.get_string_content();
|
|
|
|
if (payload == null || iv_node == null || key_node_content == null) continue;
|
|
|
|
uint8[] key;
|
|
|
|
uint8[] ciphertext = Base64.decode((!)payload);
|
|
|
|
uint8[] iv = Base64.decode((!)iv_node);
|
2018-01-12 20:03:09 +00:00
|
|
|
Address address = new Address(message.from.bare_jid.to_string(), header.get_attribute_int("sid"));
|
2017-11-11 20:29:13 +00:00
|
|
|
if (key_node.get_attribute_bool("prekey")) {
|
|
|
|
PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content));
|
|
|
|
SessionCipher cipher = store.create_session_cipher(address);
|
|
|
|
key = cipher.decrypt_pre_key_signal_message(msg);
|
|
|
|
} else {
|
|
|
|
SignalMessage msg = Plugin.get_context().deserialize_signal_message(Base64.decode((!)key_node_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.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) {
|
|
|
|
if (Plugin.DEBUG) print(@"OMEMO: Signal error while decrypting message: $(e.message)\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-19 21:37:02 +00:00
|
|
|
return false;
|
2017-11-11 20:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 16:14:59 +00:00
|
|
|
}
|