Split OMEMO plug-in into files, various fixes
This commit is contained in:
parent
7e1ecb34cb
commit
a9ea0e9f87
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ Makefile
|
||||||
*.iml
|
*.iml
|
||||||
.idea
|
.idea
|
||||||
.sqlite3
|
.sqlite3
|
||||||
|
gschemas.compiled
|
||||||
|
|
|
@ -4,7 +4,6 @@ include(${VALA_USE_FILE})
|
||||||
|
|
||||||
set(LIBDINO_PACKAGES
|
set(LIBDINO_PACKAGES
|
||||||
gee-0.8
|
gee-0.8
|
||||||
gio-2.0
|
|
||||||
glib-2.0
|
glib-2.0
|
||||||
gtk+-3.0
|
gtk+-3.0
|
||||||
gmodule-2.0
|
gmodule-2.0
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
namespace Dino.Plugins {
|
namespace Dino.Plugins {
|
||||||
|
|
||||||
public errordomain Error {
|
|
||||||
NOT_SUPPORTED,
|
|
||||||
UNEXPECTED_TYPE,
|
|
||||||
NO_REGISTRATION_FUNCTION,
|
|
||||||
FAILED
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Info : Object {
|
private class Info : Object {
|
||||||
public Module module;
|
public Module module;
|
||||||
public Type gtype;
|
public Type gtype;
|
||||||
|
@ -26,24 +19,24 @@ public class Loader : Object {
|
||||||
|
|
||||||
public RootInterface load(string name, Dino.Application app) throws Error {
|
public RootInterface load(string name, Dino.Application app) throws Error {
|
||||||
if (Module.supported () == false) {
|
if (Module.supported () == false) {
|
||||||
throw new Error.NOT_SUPPORTED ("Plugins are not supported");
|
throw new Error (-1, 0, "Plugins are not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
Module module = Module.open ("plugins/" + name, ModuleFlags.BIND_LAZY);
|
Module module = Module.open ("plugins/" + name, ModuleFlags.BIND_LAZY);
|
||||||
if (module == null) {
|
if (module == null) {
|
||||||
throw new Error.FAILED (Module.error ());
|
throw new Error (-1, 1, Module.error ());
|
||||||
}
|
}
|
||||||
|
|
||||||
void* function;
|
void* function;
|
||||||
module.symbol ("register_plugin", out function);
|
module.symbol ("register_plugin", out function);
|
||||||
if (function == null) {
|
if (function == null) {
|
||||||
throw new Error.NO_REGISTRATION_FUNCTION ("register_plugin () not found");
|
throw new Error (-1, 2, "register_plugin () not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterPluginFunction register_plugin = (RegisterPluginFunction) function;
|
RegisterPluginFunction register_plugin = (RegisterPluginFunction) function;
|
||||||
Type type = register_plugin (module);
|
Type type = register_plugin (module);
|
||||||
if (type.is_a (typeof (RootInterface)) == false) {
|
if (type.is_a (typeof (RootInterface)) == false) {
|
||||||
throw new Error.UNEXPECTED_TYPE ("Unexpected type");
|
throw new Error (-1, 3, "Unexpected type");
|
||||||
}
|
}
|
||||||
|
|
||||||
Info info = new Plugins.Info (type, (owned) module);
|
Info info = new Plugins.Info (type, (owned) module);
|
||||||
|
|
|
@ -10,7 +10,7 @@ void main(string[] args) {
|
||||||
foreach(string plugin in new string[]{"omemo", "openpgp"}) {
|
foreach(string plugin in new string[]{"omemo", "openpgp"}) {
|
||||||
try {
|
try {
|
||||||
loader.load(plugin, app);
|
loader.load(plugin, app);
|
||||||
} catch (Plugins.Error e) {
|
} catch (Error e) {
|
||||||
print(@"Error loading plugin $plugin: $(e.message)\n");
|
print(@"Error loading plugin $plugin: $(e.message)\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,20 @@ pkg_check_modules(OMEMO REQUIRED ${OMEMO_PACKAGES})
|
||||||
|
|
||||||
vala_precompile(OMEMO_VALA_C
|
vala_precompile(OMEMO_VALA_C
|
||||||
SOURCES
|
SOURCES
|
||||||
src/plugin.vala
|
src/account_settings_entry.vala
|
||||||
src/module.vala
|
src/account_settings_widget.vala
|
||||||
src/manager.vala
|
src/bundle.vala
|
||||||
src/database.vala
|
src/database.vala
|
||||||
|
src/encrypt_status.vala
|
||||||
|
src/encryption_list_entry.vala
|
||||||
|
src/manager.vala
|
||||||
|
src/message_flag.vala
|
||||||
|
src/plugin.vala
|
||||||
|
src/pre_key_store.vala
|
||||||
|
src/register_plugin.vala
|
||||||
|
src/session_store.vala
|
||||||
|
src/signed_pre_key_store.vala
|
||||||
|
src/stream_module.vala
|
||||||
CUSTOM_VAPIS
|
CUSTOM_VAPIS
|
||||||
${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi
|
${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi
|
||||||
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
|
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
|
||||||
|
|
23
plugins/omemo/src/account_settings_entry.vala
Normal file
23
plugins/omemo/src/account_settings_entry.vala
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
public class AccountSettingsEntry : Plugins.AccountSettingsEntry {
|
||||||
|
private Plugin plugin;
|
||||||
|
|
||||||
|
public AccountSettingsEntry(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string id { get {
|
||||||
|
return "omemo_identity_key";
|
||||||
|
}}
|
||||||
|
|
||||||
|
public override string name { get {
|
||||||
|
return "OMEMO";
|
||||||
|
}}
|
||||||
|
|
||||||
|
public override Plugins.AccountSettingsWidget get_widget() {
|
||||||
|
return new AccountSettingWidget(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
plugins/omemo/src/account_settings_widget.vala
Normal file
63
plugins/omemo/src/account_settings_widget.vala
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using Gtk;
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box {
|
||||||
|
private Plugin plugin;
|
||||||
|
private Label fingerprint;
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
public AccountSettingWidget(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
fingerprint = new Label("...");
|
||||||
|
fingerprint.xalign = 0;
|
||||||
|
Border border = new Button().get_style_context().get_padding(StateFlags.NORMAL);
|
||||||
|
fingerprint.set_padding(border.left + 1, border.top + 1);
|
||||||
|
fingerprint.visible = true;
|
||||||
|
pack_start(fingerprint);
|
||||||
|
|
||||||
|
Button btn = new Button();
|
||||||
|
btn.image = new Image.from_icon_name("view-list-symbolic", IconSize.BUTTON);
|
||||||
|
btn.relief = ReliefStyle.NONE;
|
||||||
|
btn.visible = true;
|
||||||
|
btn.valign = Align.CENTER;
|
||||||
|
btn.clicked.connect(() => { activated(); });
|
||||||
|
pack_start(btn, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_account(Account account) {
|
||||||
|
this.account = account;
|
||||||
|
try {
|
||||||
|
Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id);
|
||||||
|
if (row == null) {
|
||||||
|
fingerprint.set_markup(@"Own fingerprint\n<span font='8'>Will be generated on first connect</span>");
|
||||||
|
} else {
|
||||||
|
uint8[] arr = Base64.decode(row[plugin.db.identity.identity_key_public_base64]);
|
||||||
|
arr = arr[1:arr.length];
|
||||||
|
string res = "";
|
||||||
|
foreach (uint8 i in arr) {
|
||||||
|
string s = i.to_string("%x");
|
||||||
|
if (s.length == 1) s = "0" + s;
|
||||||
|
res = res + s;
|
||||||
|
if ((res.length % 9) == 8) {
|
||||||
|
if (res.length == 35) {
|
||||||
|
res += "\n";
|
||||||
|
} else {
|
||||||
|
res += " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fingerprint.set_markup(@"Own fingerprint\n<span font_family='monospace' font='8'>$res</span>");
|
||||||
|
}
|
||||||
|
} catch (Qlite.DatabaseError e) {
|
||||||
|
fingerprint.set_markup(@"Own fingerprint\n<span font='8'>Database error</span>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivate() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
87
plugins/omemo/src/bundle.vala
Normal file
87
plugins/omemo/src/bundle.vala
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
using Gee;
|
||||||
|
using Signal;
|
||||||
|
using Xmpp.Core;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
public 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 int.parse(id);
|
||||||
|
}}
|
||||||
|
|
||||||
|
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 Plugin.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;
|
||||||
|
return Base64.decode(sig);
|
||||||
|
}}
|
||||||
|
|
||||||
|
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 Plugin.context.decode_public_key(Base64.decode(key));
|
||||||
|
} catch (Error e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
public ArrayList<PreKey> pre_keys { owned get {
|
||||||
|
ArrayList<PreKey> list = new ArrayList<PreKey>();
|
||||||
|
if (node == null || node.get_subnode("prekeys") == null) return list;
|
||||||
|
node.get_deep_subnodes("prekeys", "preKeyPublic")
|
||||||
|
.filter((node) => node.get_attribute("preKeyId") != null)
|
||||||
|
.map<PreKey>(PreKey.create)
|
||||||
|
.foreach((key) => list.add(key));
|
||||||
|
return list;
|
||||||
|
}}
|
||||||
|
|
||||||
|
public 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 int.parse(node.get_attribute("preKeyId") ?? "-1");
|
||||||
|
}}
|
||||||
|
|
||||||
|
public ECPublicKey? key { owned get {
|
||||||
|
string? key = node.get_string_content();
|
||||||
|
if (key == null) return null;
|
||||||
|
try {
|
||||||
|
return Plugin.context.decode_public_key(Base64.decode(key));
|
||||||
|
} catch (Error e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ using Qlite;
|
||||||
|
|
||||||
using Dino.Entities;
|
using Dino.Entities;
|
||||||
|
|
||||||
namespace Dino.Omemo {
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
public class Database : Qlite.Database {
|
public class Database : Qlite.Database {
|
||||||
private const int VERSION = 0;
|
private const int VERSION = 0;
|
||||||
|
@ -63,7 +63,7 @@ public class Database : Qlite.Database {
|
||||||
public PreKeyTable pre_key { get; private set; }
|
public PreKeyTable pre_key { get; private set; }
|
||||||
public SessionTable session { get; private set; }
|
public SessionTable session { get; private set; }
|
||||||
|
|
||||||
public Database(string fileName) {
|
public Database(string fileName) throws DatabaseError {
|
||||||
base(fileName, VERSION);
|
base(fileName, VERSION);
|
||||||
identity = new IdentityTable(this);
|
identity = new IdentityTable(this);
|
||||||
signed_pre_key = new SignedPreKeyTable(this);
|
signed_pre_key = new SignedPreKeyTable(this);
|
||||||
|
|
17
plugins/omemo/src/encrypt_status.vala
Normal file
17
plugins/omemo/src/encrypt_status.vala
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
plugins/omemo/src/encryption_list_entry.vala
Normal file
23
plugins/omemo/src/encryption_list_entry.vala
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
public class EncryptionListEntry : Plugins.EncryptionListEntry, Object {
|
||||||
|
private Plugin plugin;
|
||||||
|
|
||||||
|
public EncryptionListEntry(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entities.Encryption encryption { get {
|
||||||
|
return Entities.Encryption.OMEMO;
|
||||||
|
}}
|
||||||
|
|
||||||
|
public string name { get {
|
||||||
|
return "OMEMO";
|
||||||
|
}}
|
||||||
|
|
||||||
|
public bool can_encrypt(Entities.Conversation conversation) {
|
||||||
|
return Manager.get_instance(plugin.app.stream_interaction).can_encrypt(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ using Qlite;
|
||||||
using Xmpp;
|
using Xmpp;
|
||||||
using Gee;
|
using Gee;
|
||||||
|
|
||||||
namespace Dino.Omemo {
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
public class Manager : StreamInteractionModule, Object {
|
public class Manager : StreamInteractionModule, Object {
|
||||||
public const string id = "omemo_manager";
|
public const string id = "omemo_manager";
|
||||||
|
@ -31,7 +31,7 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
|
|
||||||
private void on_pre_message_send(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation) {
|
private void on_pre_message_send(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation) {
|
||||||
if (message.encryption == Encryption.OMEMO) {
|
if (message.encryption == Encryption.OMEMO) {
|
||||||
Module module = Module.get_module(stream_interactor.get_stream(conversation.account));
|
StreamModule module = stream_interactor.get_stream(conversation.account).get_module(StreamModule.IDENTITY);
|
||||||
EncryptStatus status = module.encrypt(message_stanza, conversation.account.bare_jid.to_string());
|
EncryptStatus status = module.encrypt(message_stanza, conversation.account.bare_jid.to_string());
|
||||||
if (status.other_failure > 0 || (status.other_lost == status.other_devices && status.other_devices > 0)) {
|
if (status.other_failure > 0 || (status.other_lost == status.other_devices && status.other_devices > 0)) {
|
||||||
message.marked = Entities.Message.Marked.WONTSEND;
|
message.marked = Entities.Message.Marked.WONTSEND;
|
||||||
|
@ -63,9 +63,9 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_account_added(Account account) {
|
private void on_account_added(Account account) {
|
||||||
stream_interactor.module_manager.get_module(account, Module.IDENTITY).store_created.connect((context, store) => on_store_created(account, context, store));
|
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store_created.connect((store) => on_store_created(account, store));
|
||||||
stream_interactor.module_manager.get_module(account, Module.IDENTITY).device_list_loaded.connect(() => on_device_list_loaded(account));
|
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).device_list_loaded.connect(() => on_device_list_loaded(account));
|
||||||
stream_interactor.module_manager.get_module(account, Module.IDENTITY).session_started.connect((jid, device_id) => on_session_started(account, jid));
|
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).session_started.connect((jid, device_id) => on_session_started(account, jid));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_session_started(Account account, string jid) {
|
private void on_session_started(Account account, string jid) {
|
||||||
|
@ -96,7 +96,7 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_store_created(Account account, Context context, Store store) {
|
private void on_store_created(Account account, Store store) {
|
||||||
Qlite.Row? row = null;
|
Qlite.Row? row = null;
|
||||||
try {
|
try {
|
||||||
row = db.identity.row_with(db.identity.account_id, account.id);
|
row = db.identity.row_with(db.identity.account_id, account.id);
|
||||||
|
@ -107,19 +107,19 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
|
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
// OMEMO not yet initialized, starting with empty base
|
// OMEMO not yet initialized, starting with empty base
|
||||||
store.identity_key_store.local_registration_id = Random.int_range(1, int32.MAX);
|
|
||||||
|
|
||||||
Signal.ECKeyPair key_pair = context.generate_key_pair();
|
|
||||||
store.identity_key_store.identity_key_private = key_pair.private.serialize();
|
|
||||||
store.identity_key_store.identity_key_public = key_pair.public.serialize();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
store.identity_key_store.local_registration_id = Random.int_range(1, int32.MAX);
|
||||||
|
|
||||||
|
Signal.ECKeyPair key_pair = Plugin.context.generate_key_pair();
|
||||||
|
store.identity_key_store.identity_key_private = key_pair.private.serialize();
|
||||||
|
store.identity_key_store.identity_key_public = key_pair.public.serialize();
|
||||||
|
|
||||||
identity_id = (int) db.identity.insert().or("REPLACE")
|
identity_id = (int) db.identity.insert().or("REPLACE")
|
||||||
.value(db.identity.account_id, account.id)
|
.value(db.identity.account_id, account.id)
|
||||||
.value(db.identity.device_id, (int) store.local_registration_id)
|
.value(db.identity.device_id, (int) store.local_registration_id)
|
||||||
.value(db.identity.identity_key_private_base64, Base64.encode(store.identity_key_store.identity_key_private))
|
.value(db.identity.identity_key_private_base64, Base64.encode(store.identity_key_store.identity_key_private))
|
||||||
.value(db.identity.identity_key_public_base64, Base64.encode(store.identity_key_store.identity_key_public))
|
.value(db.identity.identity_key_public_base64, Base64.encode(store.identity_key_store.identity_key_public))
|
||||||
.perform();
|
.perform();
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
// Ignore error
|
// Ignore error
|
||||||
}
|
}
|
||||||
|
@ -139,118 +139,9 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BackedSignedPreKeyStore : SimpleSignedPreKeyStore {
|
|
||||||
private Database db;
|
|
||||||
private int identity_id;
|
|
||||||
|
|
||||||
public BackedSignedPreKeyStore(Database db, int identity_id) {
|
public bool can_encrypt(Entities.Conversation conversation) {
|
||||||
this.db = db;
|
return stream_interactor.get_stream(conversation.account).get_module(StreamModule.IDENTITY).is_known_address(conversation.counterpart.bare_jid.to_string());
|
||||||
this.identity_id = identity_id;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
foreach (Row row in db.signed_pre_key.select().with(db.signed_pre_key.identity_id, "=", identity_id)) {
|
|
||||||
store_signed_pre_key(row[db.signed_pre_key.signed_pre_key_id], Base64.decode(row[db.signed_pre_key.record_base64]));
|
|
||||||
}
|
|
||||||
|
|
||||||
signed_pre_key_stored.connect(on_signed_pre_key_stored);
|
|
||||||
signed_pre_key_deleted.connect(on_signed_pre_key_deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_signed_pre_key_stored(SignedPreKeyStore.Key key) {
|
|
||||||
db.signed_pre_key.insert().or("REPLACE")
|
|
||||||
.value(db.signed_pre_key.identity_id, identity_id)
|
|
||||||
.value(db.signed_pre_key.signed_pre_key_id, (int) key.key_id)
|
|
||||||
.value(db.signed_pre_key.record_base64, Base64.encode(key.record))
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_signed_pre_key_deleted(SignedPreKeyStore.Key key) {
|
|
||||||
db.signed_pre_key.delete()
|
|
||||||
.with(db.signed_pre_key.identity_id, "=", identity_id)
|
|
||||||
.with(db.signed_pre_key.signed_pre_key_id, "=", (int) key.key_id)
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BackedPreKeyStore : SimplePreKeyStore {
|
|
||||||
private Database db;
|
|
||||||
private int identity_id;
|
|
||||||
|
|
||||||
public BackedPreKeyStore(Database db, int identity_id) {
|
|
||||||
this.db = db;
|
|
||||||
this.identity_id = identity_id;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
foreach (Row row in db.pre_key.select().with(db.pre_key.identity_id, "=", identity_id)) {
|
|
||||||
store_pre_key(row[db.pre_key.pre_key_id], Base64.decode(row[db.pre_key.record_base64]));
|
|
||||||
}
|
|
||||||
|
|
||||||
pre_key_stored.connect(on_pre_key_stored);
|
|
||||||
pre_key_deleted.connect(on_pre_key_deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_pre_key_stored(PreKeyStore.Key key) {
|
|
||||||
db.pre_key.insert().or("REPLACE")
|
|
||||||
.value(db.pre_key.identity_id, identity_id)
|
|
||||||
.value(db.pre_key.pre_key_id, (int) key.key_id)
|
|
||||||
.value(db.pre_key.record_base64, Base64.encode(key.record))
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_pre_key_deleted(PreKeyStore.Key key) {
|
|
||||||
db.pre_key.delete()
|
|
||||||
.with(db.pre_key.identity_id, "=", identity_id)
|
|
||||||
.with(db.pre_key.pre_key_id, "=", (int) key.key_id)
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BackedSessionStore : SimpleSessionStore {
|
|
||||||
private Database db;
|
|
||||||
private int identity_id;
|
|
||||||
|
|
||||||
public BackedSessionStore(Database db, int identity_id) {
|
|
||||||
this.db = db;
|
|
||||||
this.identity_id = identity_id;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
Address addr = new Address();
|
|
||||||
foreach (Row row in db.session.select().with(db.session.identity_id, "=", identity_id)) {
|
|
||||||
addr.name = row[db.session.address_name];
|
|
||||||
addr.device_id = row[db.session.device_id];
|
|
||||||
store_session(addr, Base64.decode(row[db.session.record_base64]));
|
|
||||||
}
|
|
||||||
|
|
||||||
session_stored.connect(on_session_stored);
|
|
||||||
session_removed.connect(on_session_deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_session_stored(SessionStore.Session session) {
|
|
||||||
db.session.insert().or("REPLACE")
|
|
||||||
.value(db.session.identity_id, identity_id)
|
|
||||||
.value(db.session.address_name, session.name)
|
|
||||||
.value(db.session.device_id, session.device_id)
|
|
||||||
.value(db.session.record_base64, Base64.encode(session.record))
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_session_deleted(SessionStore.Session session) {
|
|
||||||
db.session.delete()
|
|
||||||
.with(db.session.identity_id, "=", identity_id)
|
|
||||||
.with(db.session.address_name, "=", session.name)
|
|
||||||
.with(db.session.device_id, "=", session.device_id)
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool con_encrypt(Entities.Conversation conversation) {
|
|
||||||
return true; // TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string get_id() {
|
internal string get_id() {
|
||||||
|
|
23
plugins/omemo/src/message_flag.vala
Normal file
23
plugins/omemo/src/message_flag.vala
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using Xmpp;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
public class MessageFlag : Message.MessageFlag {
|
||||||
|
public const string id = "omemo";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,130 +1,34 @@
|
||||||
using Xmpp;
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
namespace Dino.Omemo {
|
public class Plugin : RootInterface, Object {
|
||||||
|
public static Signal.Context context;
|
||||||
|
|
||||||
public class EncryptionListEntry : Plugins.EncryptionListEntry, Object {
|
public Dino.Application app;
|
||||||
private Plugin plugin;
|
public Database db;
|
||||||
|
public EncryptionListEntry list_entry;
|
||||||
|
public AccountSettingsEntry settings_entry;
|
||||||
|
|
||||||
public EncryptionListEntry(Plugin plugin) {
|
public void registered(Dino.Application app) {
|
||||||
this.plugin = plugin;
|
try {
|
||||||
}
|
context = new Signal.Context(false);
|
||||||
|
|
||||||
public Entities.Encryption encryption { get {
|
|
||||||
return Entities.Encryption.OMEMO;
|
|
||||||
}}
|
|
||||||
|
|
||||||
public string name { get {
|
|
||||||
return "OMEMO";
|
|
||||||
}}
|
|
||||||
|
|
||||||
public bool can_encrypt(Entities.Conversation conversation) {
|
|
||||||
return Manager.get_instance(plugin.app.stream_interaction).con_encrypt(conversation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AccountSettingsEntry : Plugins.AccountSettingsEntry {
|
|
||||||
private Plugin plugin;
|
|
||||||
|
|
||||||
public AccountSettingsEntry(Plugin plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string id { get {
|
|
||||||
return "omemo_identity_key";
|
|
||||||
}}
|
|
||||||
|
|
||||||
public override string name { get {
|
|
||||||
return "OMEMO";
|
|
||||||
}}
|
|
||||||
|
|
||||||
public override Plugins.AccountSettingsWidget get_widget() {
|
|
||||||
return new AccountSettingWidget(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AccountSettingWidget : Plugins.AccountSettingsWidget, Gtk.Box {
|
|
||||||
private Plugin plugin;
|
|
||||||
private Gtk.Label fingerprint;
|
|
||||||
private Entities.Account account;
|
|
||||||
|
|
||||||
public AccountSettingWidget(Plugin plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
|
|
||||||
fingerprint = new Gtk.Label("...");
|
|
||||||
fingerprint.xalign = 0;
|
|
||||||
Gtk.Border border = new Gtk.Button().get_style_context().get_padding(Gtk.StateFlags.NORMAL);
|
|
||||||
fingerprint.set_padding(border.left + 1, border.top + 1);
|
|
||||||
fingerprint.visible = true;
|
|
||||||
pack_start(fingerprint);
|
|
||||||
|
|
||||||
Gtk.Button btn = new Gtk.Button();
|
|
||||||
btn.image = new Gtk.Image.from_icon_name("view-list-symbolic", Gtk.IconSize.BUTTON);
|
|
||||||
btn.relief = Gtk.ReliefStyle.NONE;
|
|
||||||
btn.visible = true;
|
|
||||||
btn.valign = Gtk.Align.CENTER;
|
|
||||||
btn.clicked.connect(() => { activated(); });
|
|
||||||
pack_start(btn, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_account(Entities.Account account) {
|
|
||||||
this.account = account;
|
|
||||||
try {
|
|
||||||
Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id);
|
|
||||||
if (row == null) {
|
|
||||||
fingerprint.set_markup(@"Own fingerprint\n<span font='8'>Will be generated on first connect</span>");
|
|
||||||
} else {
|
|
||||||
uint8[] arr = Base64.decode(row[plugin.db.identity.identity_key_public_base64]);
|
|
||||||
arr = arr[1:arr.length];
|
|
||||||
string res = "";
|
|
||||||
foreach (uint8 i in arr) {
|
|
||||||
string s = i.to_string("%x");
|
|
||||||
if (s.length == 1) s = "0" + s;
|
|
||||||
res = res + s;
|
|
||||||
if ((res.length % 9) == 8) {
|
|
||||||
if (res.length == 35) {
|
|
||||||
res += "\n";
|
|
||||||
} else {
|
|
||||||
res += " ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fingerprint.set_markup(@"Own fingerprint\n<span font_family='monospace' font='8'>$res</span>");
|
|
||||||
}
|
|
||||||
} catch (Qlite.DatabaseError e) {
|
|
||||||
fingerprint.set_markup(@"Own fingerprint\n<span font='8'>Database error</span>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deactivate() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Plugin : Plugins.RootInterface, Object {
|
|
||||||
public Dino.Application app;
|
|
||||||
public Database db;
|
|
||||||
public EncryptionListEntry list_entry;
|
|
||||||
public AccountSettingsEntry settings_entry;
|
|
||||||
|
|
||||||
public void registered(Dino.Application app) {
|
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.db = new Database("omemo.db");
|
this.db = new Database("omemo.db");
|
||||||
this.list_entry = new EncryptionListEntry(this);
|
this.list_entry = new EncryptionListEntry(this);
|
||||||
this.settings_entry = new AccountSettingsEntry(this);
|
this.settings_entry = new AccountSettingsEntry(this);
|
||||||
app.plugin_registry.register_encryption_list_entry(list_entry);
|
this.app.plugin_registry.register_encryption_list_entry(list_entry);
|
||||||
app.plugin_registry.register_account_settings_entry(settings_entry);
|
this.app.plugin_registry.register_account_settings_entry(settings_entry);
|
||||||
app.stream_interaction.module_manager.initialize_account_modules.connect((account, list) => {
|
this.app.stream_interaction.module_manager.initialize_account_modules.connect((account, list) => {
|
||||||
list.add(new Module());
|
list.add(new StreamModule());
|
||||||
});
|
});
|
||||||
Manager.start(app.stream_interaction, db);
|
Manager.start(this.app.stream_interaction, db);
|
||||||
}
|
} catch (Error e) {
|
||||||
|
print(@"Error initializing OMEMO: $(e.message)\n");
|
||||||
public void shutdown() {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type register_plugin(Module module) {
|
}
|
||||||
return typeof (Dino.Omemo.Plugin);
|
|
||||||
}
|
|
53
plugins/omemo/src/pre_key_store.vala
Normal file
53
plugins/omemo/src/pre_key_store.vala
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
using Signal;
|
||||||
|
using Qlite;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
private class BackedPreKeyStore : SimplePreKeyStore {
|
||||||
|
private Database db;
|
||||||
|
private int identity_id;
|
||||||
|
|
||||||
|
public BackedPreKeyStore(Database db, int identity_id) {
|
||||||
|
this.db = db;
|
||||||
|
this.identity_id = identity_id;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
try {
|
||||||
|
foreach (Row row in db.pre_key.select().with(db.pre_key.identity_id, "=", identity_id)) {
|
||||||
|
store_pre_key(row[db.pre_key.pre_key_id], Base64.decode(row[db.pre_key.record_base64]));
|
||||||
|
}
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while initializing pre key store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pre_key_stored.connect(on_pre_key_stored);
|
||||||
|
pre_key_deleted.connect(on_pre_key_deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_pre_key_stored(PreKeyStore.Key key) {
|
||||||
|
try {
|
||||||
|
db.pre_key.insert().or("REPLACE")
|
||||||
|
.value(db.pre_key.identity_id, identity_id)
|
||||||
|
.value(db.pre_key.pre_key_id, (int) key.key_id)
|
||||||
|
.value(db.pre_key.record_base64, Base64.encode(key.record))
|
||||||
|
.perform();
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while updating pre key store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_pre_key_deleted(PreKeyStore.Key key) {
|
||||||
|
try {
|
||||||
|
db.pre_key.delete()
|
||||||
|
.with(db.pre_key.identity_id, "=", identity_id)
|
||||||
|
.with(db.pre_key.pre_key_id, "=", (int) key.key_id)
|
||||||
|
.perform();
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while updating pre key store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
plugins/omemo/src/register_plugin.vala
Normal file
3
plugins/omemo/src/register_plugin.vala
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
public Type register_plugin(Module module) {
|
||||||
|
return typeof (Dino.Plugins.Omemo.Plugin);
|
||||||
|
}
|
58
plugins/omemo/src/session_store.vala
Normal file
58
plugins/omemo/src/session_store.vala
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using Signal;
|
||||||
|
using Qlite;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
private class BackedSessionStore : SimpleSessionStore {
|
||||||
|
private Database db;
|
||||||
|
private int identity_id;
|
||||||
|
|
||||||
|
public BackedSessionStore(Database db, int identity_id) {
|
||||||
|
this.db = db;
|
||||||
|
this.identity_id = identity_id;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
try {
|
||||||
|
Address addr = new Address();
|
||||||
|
foreach (Row row in db.session.select().with(db.session.identity_id, "=", identity_id)) {
|
||||||
|
addr.name = row[db.session.address_name];
|
||||||
|
addr.device_id = row[db.session.device_id];
|
||||||
|
store_session(addr, Base64.decode(row[db.session.record_base64]));
|
||||||
|
}
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while initializing session store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
session_stored.connect(on_session_stored);
|
||||||
|
session_removed.connect(on_session_deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_session_stored(SessionStore.Session session) {
|
||||||
|
try {
|
||||||
|
db.session.insert().or("REPLACE")
|
||||||
|
.value(db.session.identity_id, identity_id)
|
||||||
|
.value(db.session.address_name, session.name)
|
||||||
|
.value(db.session.device_id, session.device_id)
|
||||||
|
.value(db.session.record_base64, Base64.encode(session.record))
|
||||||
|
.perform();
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while updating session store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_session_deleted(SessionStore.Session session) {
|
||||||
|
try {
|
||||||
|
db.session.delete()
|
||||||
|
.with(db.session.identity_id, "=", identity_id)
|
||||||
|
.with(db.session.address_name, "=", session.name)
|
||||||
|
.with(db.session.device_id, "=", session.device_id)
|
||||||
|
.perform();
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while updating session store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
plugins/omemo/src/signed_pre_key_store.vala
Normal file
54
plugins/omemo/src/signed_pre_key_store.vala
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using Qlite;
|
||||||
|
using Signal;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
private class BackedSignedPreKeyStore : SimpleSignedPreKeyStore {
|
||||||
|
private Database db;
|
||||||
|
private int identity_id;
|
||||||
|
|
||||||
|
public BackedSignedPreKeyStore(Database db, int identity_id) {
|
||||||
|
this.db = db;
|
||||||
|
this.identity_id = identity_id;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
try {
|
||||||
|
foreach (Row row in db.signed_pre_key.select().with(db.signed_pre_key.identity_id, "=", identity_id)) {
|
||||||
|
store_signed_pre_key(row[db.signed_pre_key.signed_pre_key_id], Base64.decode(row[db.signed_pre_key.record_base64]));
|
||||||
|
}
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while initializing signed pre key store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
signed_pre_key_stored.connect(on_signed_pre_key_stored);
|
||||||
|
signed_pre_key_deleted.connect(on_signed_pre_key_deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_signed_pre_key_stored(SignedPreKeyStore.Key key) {
|
||||||
|
try {
|
||||||
|
db.signed_pre_key.insert().or("REPLACE")
|
||||||
|
.value(db.signed_pre_key.identity_id, identity_id)
|
||||||
|
.value(db.signed_pre_key.signed_pre_key_id, (int) key.key_id)
|
||||||
|
.value(db.signed_pre_key.record_base64, Base64.encode(key.record))
|
||||||
|
.perform();
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while updating signed pre key store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_signed_pre_key_deleted(SignedPreKeyStore.Key key) {
|
||||||
|
try {
|
||||||
|
db.signed_pre_key.delete()
|
||||||
|
.with(db.signed_pre_key.identity_id, "=", identity_id)
|
||||||
|
.with(db.signed_pre_key.signed_pre_key_id, "=", (int) key.key_id)
|
||||||
|
.perform();
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"OMEMO: Error while updating signed pre key store: $(e.message)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ using Xmpp.Core;
|
||||||
using Xmpp.Xep;
|
using Xmpp.Xep;
|
||||||
using Signal;
|
using Signal;
|
||||||
|
|
||||||
namespace Dino.Omemo {
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
private const string NS_URI = "eu.siacs.conversations.axolotl";
|
private const string NS_URI = "eu.siacs.conversations.axolotl";
|
||||||
private const string NODE_DEVICELIST = NS_URI + ".devicelist";
|
private const string NODE_DEVICELIST = NS_URI + ".devicelist";
|
||||||
|
@ -13,36 +13,23 @@ private const string NODE_VERIFICATION = NS_URI + ".verification";
|
||||||
|
|
||||||
private const int NUM_KEYS_TO_PUBLISH = 100;
|
private const int NUM_KEYS_TO_PUBLISH = 100;
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
public class StreamModule : XmppStreamModule {
|
||||||
private const string ID = "axolotl_module";
|
private const string ID = "omemo_module";
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, ID);
|
public static ModuleIdentity<StreamModule> IDENTITY = new ModuleIdentity<StreamModule>(NS_URI, ID);
|
||||||
|
|
||||||
private Store store;
|
private Store store;
|
||||||
internal static Context context;
|
|
||||||
private bool device_list_loading = false;
|
private bool device_list_loading = false;
|
||||||
private bool device_list_modified = false;
|
private bool device_list_modified = false;
|
||||||
private Map<string, ArrayList<int32>> device_lists = new HashMap<string, ArrayList<int32>>();
|
private Map<string, ArrayList<int32>> device_lists = new HashMap<string, ArrayList<int32>>();
|
||||||
private Map<string, ArrayList<int32>> ignored_devices = 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 store_created(Store store);
|
||||||
public signal void device_list_loaded();
|
public signal void device_list_loaded();
|
||||||
public signal void session_started(string jid, int device_id);
|
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) {
|
public EncryptStatus encrypt(Message.Stanza message, string self_bare_jid) {
|
||||||
EncryptStatus status = new EncryptStatus();
|
EncryptStatus status = new EncryptStatus();
|
||||||
if (context == null) return status;
|
if (Plugin.context == null) return status;
|
||||||
try {
|
try {
|
||||||
string name = get_bare_jid(message.to);
|
string name = get_bare_jid(message.to);
|
||||||
if (device_lists.get(name) == null || device_lists.get(self_bare_jid) == null) return status;
|
if (device_lists.get(name) == null || device_lists.get(self_bare_jid) == null) return status;
|
||||||
|
@ -51,9 +38,9 @@ public class Module : XmppStreamModule {
|
||||||
if (status.other_devices == 0) return status;
|
if (status.other_devices == 0) return status;
|
||||||
|
|
||||||
uint8[] key = new uint8[16];
|
uint8[] key = new uint8[16];
|
||||||
context.randomize(key);
|
Plugin.context.randomize(key);
|
||||||
uint8[] iv = new uint8[16];
|
uint8[] iv = new uint8[16];
|
||||||
context.randomize(iv);
|
Plugin.context.randomize(iv);
|
||||||
|
|
||||||
uint8[] ciphertext = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, message.body.data);
|
uint8[] ciphertext = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, message.body.data);
|
||||||
|
|
||||||
|
@ -106,7 +93,7 @@ public class Module : XmppStreamModule {
|
||||||
message.body = "[This message is OMEMO encrypted]";
|
message.body = "[This message is OMEMO encrypted]";
|
||||||
status.encrypted = true;
|
status.encrypted = true;
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
print(@"Axolotl error while encrypting message: $(e.message)\n");
|
print(@"Signal error while encrypting message: $(e.message)\n");
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -122,13 +109,13 @@ public class Module : XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void attach(XmppStream stream) {
|
public override void attach(XmppStream stream) {
|
||||||
if (context == null) return;
|
if (Plugin.context == null) return;
|
||||||
Message.Module.require(stream);
|
Message.Module.require(stream);
|
||||||
Pubsub.Module.require(stream);
|
Pubsub.Module.require(stream);
|
||||||
stream.get_module(Message.Module.IDENTITY).pre_received_message.connect(on_pre_received_message);
|
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);
|
stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NODE_DEVICELIST, on_devicelist, this);
|
||||||
this.store = context.create_store();
|
this.store = Plugin.context.create_store();
|
||||||
store_created(context, store);
|
store_created(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_pre_received_message(XmppStream stream, Message.Stanza message) {
|
private void on_pre_received_message(XmppStream stream, Message.Stanza message) {
|
||||||
|
@ -148,11 +135,11 @@ public class Module : XmppStreamModule {
|
||||||
address.name = get_bare_jid(message.from);
|
address.name = get_bare_jid(message.from);
|
||||||
address.device_id = header.get_attribute_int("sid");
|
address.device_id = header.get_attribute_int("sid");
|
||||||
if (key_node.get_attribute_bool("prekey")) {
|
if (key_node.get_attribute_bool("prekey")) {
|
||||||
PreKeySignalMessage msg = context.deserialize_pre_key_signal_message(Base64.decode(key_node.get_string_content()));
|
PreKeySignalMessage msg = Plugin.context.deserialize_pre_key_signal_message(Base64.decode(key_node.get_string_content()));
|
||||||
SessionCipher cipher = store.create_session_cipher(address);
|
SessionCipher cipher = store.create_session_cipher(address);
|
||||||
key = cipher.decrypt_pre_key_signal_message(msg);
|
key = cipher.decrypt_pre_key_signal_message(msg);
|
||||||
} else {
|
} else {
|
||||||
SignalMessage msg = context.deserialize_signal_message(Base64.decode(key_node.get_string_content()));
|
SignalMessage msg = Plugin.context.deserialize_signal_message(Base64.decode(key_node.get_string_content()));
|
||||||
SessionCipher cipher = store.create_session_cipher(address);
|
SessionCipher cipher = store.create_session_cipher(address);
|
||||||
key = cipher.decrypt_signal_message(msg);
|
key = cipher.decrypt_signal_message(msg);
|
||||||
}
|
}
|
||||||
|
@ -175,7 +162,7 @@ public class Module : XmppStreamModule {
|
||||||
flag.decrypted = true;
|
flag.decrypted = true;
|
||||||
}
|
}
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
print(@"Axolotl error while decrypting message: $(e.message)\n");
|
print(@"Signal error while decrypting message: $(e.message)\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,8 +233,12 @@ public class Module : XmppStreamModule {
|
||||||
foreach(int32 device_id in device_lists[bare_jid]) {
|
foreach(int32 device_id in device_lists[bare_jid]) {
|
||||||
if (!is_ignored_device(bare_jid, device_id)) {
|
if (!is_ignored_device(bare_jid, device_id)) {
|
||||||
address.device_id = device_id;
|
address.device_id = device_id;
|
||||||
if (!store.contains_session(address)) {
|
try {
|
||||||
start_session_with(stream, bare_jid, device_id);
|
if (!store.contains_session(address)) {
|
||||||
|
start_session_with(stream, bare_jid, device_id);
|
||||||
|
}
|
||||||
|
} catch (Error e) {
|
||||||
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,6 +250,10 @@ public class Module : XmppStreamModule {
|
||||||
stream.get_module(Pubsub.Module.IDENTITY).request(stream, bare_jid, @"$NODE_BUNDLES:$device_id", on_other_bundle_result, Tuple.create(store, device_id));
|
stream.get_module(Pubsub.Module.IDENTITY).request(stream, bare_jid, @"$NODE_BUNDLES:$device_id", on_other_bundle_result, Tuple.create(store, device_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool is_known_address(string name) {
|
||||||
|
return device_lists.has_key(name);
|
||||||
|
}
|
||||||
|
|
||||||
public void ignore_device(string jid, int32 device_id) {
|
public void ignore_device(string jid, int32 device_id) {
|
||||||
if (device_id <= 0) return;
|
if (device_id <= 0) return;
|
||||||
lock (ignored_devices) {
|
lock (ignored_devices) {
|
||||||
|
@ -313,11 +308,11 @@ public class Module : XmppStreamModule {
|
||||||
fail = true;
|
fail = true;
|
||||||
}
|
}
|
||||||
address.device_id = 0; // TODO: Hack to have address obj live longer
|
address.device_id = 0; // TODO: Hack to have address obj live longer
|
||||||
get_module(stream).session_started(jid, device_id);
|
stream.get_module(IDENTITY).session_started(jid, device_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fail) {
|
if (fail) {
|
||||||
get_module(stream).ignore_device(jid, device_id);
|
stream.get_module(IDENTITY).ignore_device(jid, device_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,49 +342,53 @@ public class Module : XmppStreamModule {
|
||||||
signed_pre_key = bundle.signed_pre_key;
|
signed_pre_key = bundle.signed_pre_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate IdentityKey
|
try {
|
||||||
if (store.identity_key_pair.public.compare(identity_key) != 0) {
|
// Validate IdentityKey
|
||||||
changed = true;
|
if (store.identity_key_pair.public.compare(identity_key) != 0) {
|
||||||
}
|
changed = true;
|
||||||
identity_key_pair = store.identity_key_pair;
|
}
|
||||||
|
identity_key_pair = store.identity_key_pair;
|
||||||
|
|
||||||
// Validate signedPreKeyRecord + ID
|
// 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) {
|
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_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);
|
signed_pre_key_record = Plugin.context.generate_signed_pre_key(identity_key_pair, signed_pre_key_id);
|
||||||
store.store_signed_pre_key(signed_pre_key_record);
|
store.store_signed_pre_key(signed_pre_key_record);
|
||||||
changed = true;
|
changed = true;
|
||||||
} else {
|
} else {
|
||||||
signed_pre_key_record = store.load_signed_pre_key(signed_pre_key_id);
|
signed_pre_key_record = store.load_signed_pre_key(signed_pre_key_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate PreKeys
|
// Validate PreKeys
|
||||||
Set<PreKeyRecord> pre_key_records = new HashSet<PreKeyRecord>();
|
Set<PreKeyRecord> pre_key_records = new HashSet<PreKeyRecord>();
|
||||||
foreach (var entry in keys.entries) {
|
foreach (var entry in keys.entries) {
|
||||||
if (store.contains_pre_key(entry.key)) {
|
if (store.contains_pre_key(entry.key)) {
|
||||||
PreKeyRecord record = store.load_pre_key(entry.key);
|
PreKeyRecord record = store.load_pre_key(entry.key);
|
||||||
if (record.key_pair.public.compare(entry.value) == 0) {
|
if (record.key_pair.public.compare(entry.value) == 0) {
|
||||||
pre_key_records.add(record);
|
pre_key_records.add(record);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
int new_keys = NUM_KEYS_TO_PUBLISH - pre_key_records.size;
|
||||||
int new_keys = NUM_KEYS_TO_PUBLISH - pre_key_records.size;
|
if (new_keys > 0) {
|
||||||
if (new_keys > 0) {
|
int32 next_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number
|
||||||
int32 next_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number
|
Set<PreKeyRecord> new_records = Plugin.context.generate_pre_keys((uint)next_id, (uint)new_keys);
|
||||||
Set<PreKeyRecord> new_records = context.generate_pre_keys((uint)next_id, (uint)new_keys);
|
pre_key_records.add_all(new_records);
|
||||||
pre_key_records.add_all(new_records);
|
foreach (PreKeyRecord record in new_records) {
|
||||||
foreach (PreKeyRecord record in new_records) {
|
store.store_pre_key(record);
|
||||||
store.store_pre_key(record);
|
}
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
publish_bundles(stream, signed_pre_key_record, identity_key_pair, pre_key_records, (int32) store.local_registration_id);
|
publish_bundles(stream, signed_pre_key_record, identity_key_pair, pre_key_records, (int32) store.local_registration_id);
|
||||||
|
}
|
||||||
|
} catch (Error e) {
|
||||||
|
print(@"Unexpected error while publishing bundle: $(e.message)\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void publish_bundles(XmppStream stream, SignedPreKeyRecord signed_pre_key_record, IdentityKeyPair identity_key_pair, Set<PreKeyRecord> pre_key_records, int32 device_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) throws Error {
|
||||||
ECKeyPair tmp;
|
ECKeyPair tmp;
|
||||||
StanzaNode bundle = new StanzaNode.build("bundle", NS_URI)
|
StanzaNode bundle = new StanzaNode.build("bundle", NS_URI)
|
||||||
.add_self_xmlns()
|
.add_self_xmlns()
|
||||||
|
@ -415,10 +414,6 @@ public class Module : XmppStreamModule {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Module? get_module(XmppStream stream) {
|
|
||||||
return (Module?) stream.get_module(IDENTITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string get_ns() {
|
public override string get_ns() {
|
||||||
return NS_URI;
|
return NS_URI;
|
||||||
}
|
}
|
||||||
|
@ -428,120 +423,4 @@ public class Module : XmppStreamModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
find_package(Vala REQUIRED)
|
find_package(Vala REQUIRED)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
find_package(GPGME REQUIRED)
|
|
||||||
find_package(LIBUUID REQUIRED)
|
find_package(LIBUUID REQUIRED)
|
||||||
include(GlibCompileResourcesSupport)
|
include(GlibCompileResourcesSupport)
|
||||||
include(${VALA_USE_FILE})
|
include(${VALA_USE_FILE})
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class StanzaNode : StanzaEntry {
|
||||||
return res.down() == "true" || res == "1";
|
return res.down() == "true" || res == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
public StanzaAttribute get_attribute_raw(string name, string? ns_uri = null) {
|
public StanzaAttribute? get_attribute_raw(string name, string? ns_uri = null) {
|
||||||
string _name = name;
|
string _name = name;
|
||||||
string? _ns_uri = ns_uri;
|
string? _ns_uri = ns_uri;
|
||||||
if (_ns_uri == null) {
|
if (_ns_uri == null) {
|
||||||
|
@ -225,12 +225,12 @@ public class StanzaNode : StanzaEntry {
|
||||||
public ArrayList<StanzaNode> get_deep_subnodes_(va_list l) {
|
public ArrayList<StanzaNode> get_deep_subnodes_(va_list l) {
|
||||||
StanzaNode? node = this;
|
StanzaNode? node = this;
|
||||||
string? subnode_name = l.arg();
|
string? subnode_name = l.arg();
|
||||||
if (subnode_name == null) return null;
|
if (subnode_name == null) return new ArrayList<StanzaNode>();
|
||||||
while(true) {
|
while(true) {
|
||||||
string? s = l.arg();
|
string? s = l.arg();
|
||||||
if (s == null) break;
|
if (s == null) break;
|
||||||
node = node.get_subnode(subnode_name);
|
node = node.get_subnode(subnode_name);
|
||||||
if (node == null) return null;
|
if (node == null) return new ArrayList<StanzaNode>();
|
||||||
subnode_name = s;
|
subnode_name = s;
|
||||||
}
|
}
|
||||||
return node.get_subnodes(subnode_name);
|
return node.get_subnodes(subnode_name);
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class XmppStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOStream? get_stream() {
|
internal IOStream? get_stream() {
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue