omemo: store and display identity keys of all devices
This commit is contained in:
parent
ad033beea8
commit
9840774a87
|
@ -11,11 +11,27 @@ find_packages(OMEMO_PACKAGES REQUIRED
|
||||||
GTK3
|
GTK3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(RESOURCE_LIST
|
||||||
|
account_settings_dialog.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
compile_gresources(
|
||||||
|
OMEMO_GRESOURCES_TARGET
|
||||||
|
OMEMO_GRESOURCES_XML
|
||||||
|
TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c
|
||||||
|
TYPE EMBED_C
|
||||||
|
RESOURCES ${RESOURCE_LIST}
|
||||||
|
PREFIX /im/dino/omemo
|
||||||
|
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data
|
||||||
|
)
|
||||||
|
|
||||||
vala_precompile(OMEMO_VALA_C
|
vala_precompile(OMEMO_VALA_C
|
||||||
SOURCES
|
SOURCES
|
||||||
|
src/account_settings_dialog.vala
|
||||||
src/account_settings_entry.vala
|
src/account_settings_entry.vala
|
||||||
src/account_settings_widget.vala
|
src/account_settings_widget.vala
|
||||||
src/bundle.vala
|
src/bundle.vala
|
||||||
|
src/contact_details_provider.vala
|
||||||
src/database.vala
|
src/database.vala
|
||||||
src/encrypt_state.vala
|
src/encrypt_state.vala
|
||||||
src/encryption_list_entry.vala
|
src/encryption_list_entry.vala
|
||||||
|
@ -27,6 +43,7 @@ SOURCES
|
||||||
src/session_store.vala
|
src/session_store.vala
|
||||||
src/signed_pre_key_store.vala
|
src/signed_pre_key_store.vala
|
||||||
src/stream_module.vala
|
src/stream_module.vala
|
||||||
|
src/util.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
|
||||||
|
@ -34,10 +51,12 @@ CUSTOM_VAPIS
|
||||||
${CMAKE_BINARY_DIR}/exports/dino.vapi
|
${CMAKE_BINARY_DIR}/exports/dino.vapi
|
||||||
PACKAGES
|
PACKAGES
|
||||||
${OMEMO_PACKAGES}
|
${OMEMO_PACKAGES}
|
||||||
|
GRESOURCES
|
||||||
|
${OMEMO_GRESOURCES_XML}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\")
|
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\")
|
||||||
add_library(omemo SHARED ${OMEMO_VALA_C})
|
add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET})
|
||||||
add_dependencies(omemo ${GETTEXT_PACKAGE}-translations)
|
add_dependencies(omemo ${GETTEXT_PACKAGE}-translations)
|
||||||
target_link_libraries(omemo libdino signal-protocol-vala ${OMEMO_PACKAGES})
|
target_link_libraries(omemo libdino signal-protocol-vala ${OMEMO_PACKAGES})
|
||||||
set_target_properties(omemo PROPERTIES PREFIX "")
|
set_target_properties(omemo PROPERTIES PREFIX "")
|
||||||
|
|
124
plugins/omemo/data/account_settings_dialog.ui
Normal file
124
plugins/omemo/data/account_settings_dialog.ui
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="DinoPluginsOmemoAccountSettingsDialog">
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<property name="title" translatable="yes">OMEMO Keys</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="margin-left">40</property>
|
||||||
|
<property name="margin-right">40</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="margin-top">12</property>
|
||||||
|
<property name="orientation">horizontal</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Own fingerprint</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="yalign">1</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="margin-bottom">2</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="copy_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
<signal name="clicked" handler="copy_button_clicked"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="icon-name">edit-copy-symbolic</property>
|
||||||
|
<property name="icon-size">1</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<!--<child>
|
||||||
|
<object class="GtkButton" id="qr_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="icon-name">camera-photo-symbolic</property>
|
||||||
|
<property name="icon-size">1</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>-->
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="own_fingerprint">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="margin">8</property>
|
||||||
|
<property name="label">...</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="margin-top">12</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Other devices</property>
|
||||||
|
<property name="margin-bottom">2</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="margin-bottom">18</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="hscrollbar_policy">never</property>
|
||||||
|
<property name="vscrollbar_policy">never</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox" id="other_list">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="margin">8</property>
|
||||||
|
<property name="label" translatable="yes">- None -</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
53
plugins/omemo/src/account_settings_dialog.vala
Normal file
53
plugins/omemo/src/account_settings_dialog.vala
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
using Gtk;
|
||||||
|
using Qlite;
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
[GtkTemplate (ui = "/im/dino/omemo/account_settings_dialog.ui")]
|
||||||
|
public class AccountSettingsDialog : Gtk.Dialog {
|
||||||
|
|
||||||
|
private Plugin plugin;
|
||||||
|
private Account account;
|
||||||
|
private string fingerprint;
|
||||||
|
|
||||||
|
[GtkChild] private Label own_fingerprint;
|
||||||
|
[GtkChild] private ListBox other_list;
|
||||||
|
|
||||||
|
public AccountSettingsDialog(Plugin plugin, Account account) {
|
||||||
|
Object(use_header_bar : 1);
|
||||||
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64];
|
||||||
|
fingerprint = fingerprint_from_base64(own_b64);
|
||||||
|
own_fingerprint.set_markup(fingerprint_markup(fingerprint));
|
||||||
|
|
||||||
|
int own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (Row row in plugin.db.identity_meta.with_address(account.bare_jid.to_string())) {
|
||||||
|
if (row[plugin.db.identity_meta.device_id] == own_id) continue;
|
||||||
|
if (i == 0) {
|
||||||
|
other_list.foreach((widget) => { other_list.remove(widget); });
|
||||||
|
}
|
||||||
|
string? other_b64 = row[plugin.db.identity_meta.identity_key_public_base64];
|
||||||
|
Label lbl = new Label(other_b64 != null ? fingerprint_markup(fingerprint_from_base64(other_b64)) : _("Unknown device (0x%xd)").printf(row[plugin.db.identity_meta.device_id])) { use_markup = true, visible = true, margin = 8, selectable=true };
|
||||||
|
if (row[plugin.db.identity_meta.now_active] && other_b64 != null) {
|
||||||
|
other_list.insert(lbl, 0);
|
||||||
|
} else {
|
||||||
|
lbl.sensitive = false;
|
||||||
|
other_list.insert(lbl, i);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
public void copy_button_clicked() {
|
||||||
|
Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box {
|
||||||
private Plugin plugin;
|
private Plugin plugin;
|
||||||
private Label fingerprint;
|
private Label fingerprint;
|
||||||
private Account account;
|
private Account account;
|
||||||
|
private Button btn;
|
||||||
|
|
||||||
public AccountSettingWidget(Plugin plugin) {
|
public AccountSettingWidget(Plugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
@ -18,38 +19,31 @@ public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box {
|
||||||
fingerprint.visible = true;
|
fingerprint.visible = true;
|
||||||
pack_start(fingerprint);
|
pack_start(fingerprint);
|
||||||
|
|
||||||
Button btn = new Button();
|
btn = new Button();
|
||||||
btn.image = new Image.from_icon_name("view-list-symbolic", IconSize.BUTTON);
|
btn.image = new Image.from_icon_name("view-list-symbolic", IconSize.BUTTON);
|
||||||
btn.relief = ReliefStyle.NONE;
|
btn.relief = ReliefStyle.NONE;
|
||||||
btn.visible = true;
|
btn.visible = false;
|
||||||
btn.valign = Align.CENTER;
|
btn.valign = Align.CENTER;
|
||||||
btn.clicked.connect(() => { activated(); });
|
btn.clicked.connect(() => {
|
||||||
|
activated();
|
||||||
|
AccountSettingsDialog dialog = new AccountSettingsDialog(plugin, account);
|
||||||
|
dialog.set_transient_for((Window) get_toplevel());
|
||||||
|
dialog.present();
|
||||||
|
});
|
||||||
pack_start(btn, false);
|
pack_start(btn, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_account(Account account) {
|
public void set_account(Account account) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
|
btn.visible = false;
|
||||||
try {
|
try {
|
||||||
Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id).inner;
|
Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id).inner;
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
fingerprint.set_markup("%s\n<span font='8'>%s</span>".printf(_("Own fingerprint"), _("Will be generated on first connect")));
|
fingerprint.set_markup("%s\n<span font='8'>%s</span>".printf(_("Own fingerprint"), _("Will be generated on first connect")));
|
||||||
} else {
|
} else {
|
||||||
uint8[] arr = Base64.decode(((!)row)[plugin.db.identity.identity_key_public_base64]);
|
string res = fingerprint_markup(fingerprint_from_base64(((!)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("%s\n<span font_family='monospace' font='8'>%s</span>".printf(_("Own fingerprint"), res));
|
fingerprint.set_markup("%s\n<span font_family='monospace' font='8'>%s</span>".printf(_("Own fingerprint"), res));
|
||||||
|
btn.visible = true;
|
||||||
}
|
}
|
||||||
} catch (Qlite.DatabaseError e) {
|
} catch (Qlite.DatabaseError e) {
|
||||||
fingerprint.set_markup("%s\n<span font='8'>%s</span>".printf(_("Own fingerprint"), _("Database error")));
|
fingerprint.set_markup("%s\n<span font='8'>%s</span>".printf(_("Own fingerprint"), _("Database error")));
|
||||||
|
|
37
plugins/omemo/src/contact_details_provider.vala
Normal file
37
plugins/omemo/src/contact_details_provider.vala
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using Gtk;
|
||||||
|
using Qlite;
|
||||||
|
using Dino.Entities;
|
||||||
|
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object {
|
||||||
|
public string id { get { return "omemo_info"; } }
|
||||||
|
|
||||||
|
private Plugin plugin;
|
||||||
|
|
||||||
|
public ContactDetailsProvider(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) {
|
||||||
|
if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK) {
|
||||||
|
string res = "";
|
||||||
|
int i = 0;
|
||||||
|
foreach (Row row in plugin.db.identity_meta.with_address(conversation.counterpart.to_string())) {
|
||||||
|
if (row[plugin.db.identity_meta.identity_key_public_base64] != null) {
|
||||||
|
if (i != 0) {
|
||||||
|
res += "\n\n";
|
||||||
|
}
|
||||||
|
res += fingerprint_markup(fingerprint_from_base64(row[plugin.db.identity_meta.identity_key_public_base64]));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i > 0) {
|
||||||
|
Label label = new Label(res) { use_markup=true, justify=Justification.RIGHT, selectable=true, visible=true };
|
||||||
|
contact_details.add(_("Encryption"), _("OMEMO"), n("%d OMEMO device", "%d OMEMO devices", i).printf(i), label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,7 +6,48 @@ using Dino.Entities;
|
||||||
namespace Dino.Plugins.Omemo {
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
public class Database : Qlite.Database {
|
public class Database : Qlite.Database {
|
||||||
private const int VERSION = 0;
|
private const int VERSION = 1;
|
||||||
|
|
||||||
|
public class IdentityMetaTable : Table {
|
||||||
|
public Column<string> address_name = new Column.Text("address_name") { not_null = true };
|
||||||
|
public Column<int> device_id = new Column.Integer("device_id") { not_null = true };
|
||||||
|
public Column<string?> identity_key_public_base64 = new Column.Text("identity_key_public_base64");
|
||||||
|
public Column<bool> trusted_identity = new Column.BoolInt("trusted_identity") { default = "0" };
|
||||||
|
public Column<bool> now_active = new Column.BoolInt("now_active") { default = "1" };
|
||||||
|
public Column<long> last_active = new Column.Long("last_active");
|
||||||
|
|
||||||
|
internal IdentityMetaTable(Database db) {
|
||||||
|
base(db, "identity_meta");
|
||||||
|
init({address_name, device_id, identity_key_public_base64, trusted_identity, now_active, last_active});
|
||||||
|
index("identity_meta_idx", {address_name, device_id}, true);
|
||||||
|
index("identity_meta_list_idx", {address_name});
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryBuilder with_address(string address_name) throws DatabaseError {
|
||||||
|
return select().with(this.address_name, "=", address_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insert_device_list(string address_name, ArrayList<int32> devices) throws DatabaseError {
|
||||||
|
update().with(this.address_name, "=", address_name).set(now_active, false).perform();
|
||||||
|
foreach (int32 device_id in devices) {
|
||||||
|
upsert()
|
||||||
|
.value(this.address_name, address_name, true)
|
||||||
|
.value(this.device_id, device_id, true)
|
||||||
|
.value(this.now_active, true)
|
||||||
|
.value(this.last_active, (long) new DateTime.now_utc().to_unix())
|
||||||
|
.perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int64 insert_device_bundle(string address_name, int device_id, Bundle bundle) throws DatabaseError {
|
||||||
|
if (bundle == null || bundle.identity_key == null) return -1;
|
||||||
|
return upsert()
|
||||||
|
.value(this.address_name, address_name, true)
|
||||||
|
.value(this.device_id, device_id, true)
|
||||||
|
.value(this.identity_key_public_base64, Base64.encode(bundle.identity_key.serialize()))
|
||||||
|
.perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class IdentityTable : Table {
|
public class IdentityTable : Table {
|
||||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||||
|
@ -30,6 +71,7 @@ public class Database : Qlite.Database {
|
||||||
base(db, "signed_pre_key");
|
base(db, "signed_pre_key");
|
||||||
init({identity_id, signed_pre_key_id, record_base64});
|
init({identity_id, signed_pre_key_id, record_base64});
|
||||||
unique({identity_id, signed_pre_key_id});
|
unique({identity_id, signed_pre_key_id});
|
||||||
|
index("signed_pre_key_idx", {identity_id, signed_pre_key_id}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +84,7 @@ public class Database : Qlite.Database {
|
||||||
base(db, "pre_key");
|
base(db, "pre_key");
|
||||||
init({identity_id, pre_key_id, record_base64});
|
init({identity_id, pre_key_id, record_base64});
|
||||||
unique({identity_id, pre_key_id});
|
unique({identity_id, pre_key_id});
|
||||||
|
index("pre_key_idx", {identity_id, pre_key_id}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +98,11 @@ public class Database : Qlite.Database {
|
||||||
base(db, "session");
|
base(db, "session");
|
||||||
init({identity_id, address_name, device_id, record_base64});
|
init({identity_id, address_name, device_id, record_base64});
|
||||||
unique({identity_id, address_name, device_id});
|
unique({identity_id, address_name, device_id});
|
||||||
|
index("session_idx", {identity_id, address_name, device_id}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IdentityMetaTable identity_meta { get; private set; }
|
||||||
public IdentityTable identity { get; private set; }
|
public IdentityTable identity { get; private set; }
|
||||||
public SignedPreKeyTable signed_pre_key { get; private set; }
|
public SignedPreKeyTable signed_pre_key { get; private set; }
|
||||||
public PreKeyTable pre_key { get; private set; }
|
public PreKeyTable pre_key { get; private set; }
|
||||||
|
@ -64,11 +110,13 @@ public class Database : Qlite.Database {
|
||||||
|
|
||||||
public Database(string fileName) throws DatabaseError {
|
public Database(string fileName) throws DatabaseError {
|
||||||
base(fileName, VERSION);
|
base(fileName, VERSION);
|
||||||
|
identity_meta = new IdentityMetaTable(this);
|
||||||
identity = new IdentityTable(this);
|
identity = new IdentityTable(this);
|
||||||
signed_pre_key = new SignedPreKeyTable(this);
|
signed_pre_key = new SignedPreKeyTable(this);
|
||||||
pre_key = new PreKeyTable(this);
|
pre_key = new PreKeyTable(this);
|
||||||
session = new SessionTable(this);
|
session = new SessionTable(this);
|
||||||
init({identity, signed_pre_key, pre_key, session});
|
init({identity_meta, identity, signed_pre_key, pre_key, session});
|
||||||
|
exec("PRAGMA synchronous=0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void migrate(long oldVersion) {
|
public override void migrate(long oldVersion) {
|
||||||
|
|
|
@ -83,7 +83,12 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
message.marked = Entities.Message.Marked.UNSENT;
|
message.marked = Entities.Message.Marked.UNSENT;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StreamModule module = ((!)stream).get_module(StreamModule.IDENTITY);
|
StreamModule? module_ = ((!)stream).get_module(StreamModule.IDENTITY);
|
||||||
|
if (module_ == null) {
|
||||||
|
message.marked = Entities.Message.Marked.UNSENT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StreamModule module = (!)module_;
|
||||||
EncryptState enc_state = module.encrypt(message_stanza, conversation.account.bare_jid.to_string());
|
EncryptState enc_state = module.encrypt(message_stanza, conversation.account.bare_jid.to_string());
|
||||||
MessageState state;
|
MessageState state;
|
||||||
lock (message_states) {
|
lock (message_states) {
|
||||||
|
@ -122,6 +127,7 @@ 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, StreamModule.IDENTITY).store_created.connect((store) => on_store_created(account, 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, StreamModule.IDENTITY).device_list_loaded.connect((jid) => on_device_list_loaded(account, jid));
|
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).device_list_loaded.connect((jid) => on_device_list_loaded(account, jid));
|
||||||
|
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect((jid, device_id, bundle) => on_bundle_fetched(account, jid, device_id, bundle));
|
||||||
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).session_started.connect((jid, device_id) => on_session_started(account, jid, false));
|
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).session_started.connect((jid, device_id) => on_session_started(account, jid, false));
|
||||||
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).session_start_failed.connect((jid, device_id) => on_session_started(account, jid, true));
|
stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).session_start_failed.connect((jid, device_id) => on_session_started(account, jid, true));
|
||||||
}
|
}
|
||||||
|
@ -180,6 +186,40 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
if (conv == null) continue;
|
if (conv == null) continue;
|
||||||
stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true);
|
stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update meta database
|
||||||
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
|
if (stream == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StreamModule? module = ((!)stream).get_module(StreamModule.IDENTITY);
|
||||||
|
if (module == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ArrayList<int32> device_list = module.get_device_list(jid);
|
||||||
|
db.identity_meta.insert_device_list(jid, device_list);
|
||||||
|
int inc = 0;
|
||||||
|
foreach (Row row in db.identity_meta.with_address(jid).with_null(db.identity_meta.identity_key_public_base64)) {
|
||||||
|
module.fetch_bundle(stream, row[db.identity_meta.address_name], row[db.identity_meta.device_id]);
|
||||||
|
inc++;
|
||||||
|
}
|
||||||
|
if (inc > 0) {
|
||||||
|
if (Plugin.DEBUG) print(@"OMEMO: new bundles $inc/$(device_list.size) for $jid\n");
|
||||||
|
}
|
||||||
|
} catch (DatabaseError e) {
|
||||||
|
// Ignore
|
||||||
|
print(@"OMEMO: failed to use database: $(e.message)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_bundle_fetched(Account account, string jid, int32 device_id, Bundle bundle) {
|
||||||
|
try {
|
||||||
|
db.identity_meta.insert_device_bundle(jid, device_id, bundle);
|
||||||
|
} catch (DatabaseError e) {
|
||||||
|
// Ignore
|
||||||
|
print(@"OMEMO: failed to use database: $(e.message)\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_store_created(Account account, Store store) {
|
private void on_store_created(Account account, Store store) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ public class Plugin : RootInterface, Object {
|
||||||
public Database db;
|
public Database db;
|
||||||
public EncryptionListEntry list_entry;
|
public EncryptionListEntry list_entry;
|
||||||
public AccountSettingsEntry settings_entry;
|
public AccountSettingsEntry settings_entry;
|
||||||
|
public ContactDetailsProvider contact_details_provider;
|
||||||
|
|
||||||
public void registered(Dino.Application app) {
|
public void registered(Dino.Application app) {
|
||||||
try {
|
try {
|
||||||
|
@ -35,8 +36,10 @@ public class Plugin : RootInterface, Object {
|
||||||
this.db = new Database(Path.build_filename(Application.get_storage_dir(), "omemo.db"));
|
this.db = new Database(Path.build_filename(Application.get_storage_dir(), "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);
|
||||||
|
this.contact_details_provider = new ContactDetailsProvider(this);
|
||||||
this.app.plugin_registry.register_encryption_list_entry(list_entry);
|
this.app.plugin_registry.register_encryption_list_entry(list_entry);
|
||||||
this.app.plugin_registry.register_account_settings_entry(settings_entry);
|
this.app.plugin_registry.register_account_settings_entry(settings_entry);
|
||||||
|
this.app.plugin_registry.register_contact_details_entry(contact_details_provider);
|
||||||
this.app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => {
|
this.app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => {
|
||||||
list.add(new StreamModule());
|
list.add(new StreamModule());
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,7 @@ public class StreamModule : XmppStreamModule {
|
||||||
|
|
||||||
public signal void store_created(Store store);
|
public signal void store_created(Store store);
|
||||||
public signal void device_list_loaded(string jid);
|
public signal void device_list_loaded(string jid);
|
||||||
|
public signal void bundle_fetched(string jid, int device_id, Bundle bundle);
|
||||||
public signal void session_started(string jid, int device_id);
|
public signal void session_started(string jid, int device_id);
|
||||||
public signal void session_start_failed(string jid, int device_id);
|
public signal void session_start_failed(string jid, int device_id);
|
||||||
|
|
||||||
|
@ -183,11 +184,11 @@ public class StreamModule : XmppStreamModule {
|
||||||
public void request_user_devicelist(XmppStream stream, string jid) {
|
public void request_user_devicelist(XmppStream stream, string jid) {
|
||||||
if (active_devicelist_requests.add(jid)) {
|
if (active_devicelist_requests.add(jid)) {
|
||||||
if (Plugin.DEBUG) print(@"OMEMO: requesting device list for $jid\n");
|
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));
|
stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id, node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void on_devicelist(XmppStream stream, string jid, string id, StanzaNode? node_) {
|
public void on_devicelist(XmppStream stream, string jid, string? id, StanzaNode? node_) {
|
||||||
StanzaNode node = node_ ?? new StanzaNode.build("list", NS_URI).add_self_xmlns();
|
StanzaNode node = node_ ?? new StanzaNode.build("list", NS_URI).add_self_xmlns();
|
||||||
string? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
|
string? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
|
||||||
if (my_jid == null) return;
|
if (my_jid == null) return;
|
||||||
|
@ -219,7 +220,6 @@ public class StreamModule : XmppStreamModule {
|
||||||
|
|
||||||
public void start_sessions_with(XmppStream stream, string bare_jid) {
|
public void start_sessions_with(XmppStream stream, string bare_jid) {
|
||||||
if (!device_lists.has_key(bare_jid)) {
|
if (!device_lists.has_key(bare_jid)) {
|
||||||
// TODO: manually request a device list
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Address address = new Address(bare_jid, 0);
|
Address address = new Address(bare_jid, 0);
|
||||||
|
@ -247,6 +247,23 @@ public class StreamModule : XmppStreamModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fetch_bundle(XmppStream stream, string bare_jid, int device_id) {
|
||||||
|
if (active_bundle_requests.add(bare_jid + @":$device_id")) {
|
||||||
|
if (Plugin.DEBUG) print(@"OMEMO: Asking for bundle from $bare_jid:$device_id\n");
|
||||||
|
stream.get_module(Pubsub.Module.IDENTITY).request(stream, bare_jid, @"$NODE_BUNDLES:$device_id", (stream, jid, id, node) => {
|
||||||
|
bundle_fetched(jid, device_id, new Bundle(node));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<int32> get_device_list(string jid) {
|
||||||
|
if (is_known_address(jid)) {
|
||||||
|
return device_lists[jid];
|
||||||
|
} else {
|
||||||
|
return new ArrayList<int32>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool is_known_address(string name) {
|
public bool is_known_address(string name) {
|
||||||
return device_lists.has_key(name);
|
return device_lists.has_key(name);
|
||||||
}
|
}
|
||||||
|
@ -276,6 +293,7 @@ public class StreamModule : XmppStreamModule {
|
||||||
fail = true;
|
fail = true;
|
||||||
} else {
|
} else {
|
||||||
Bundle bundle = new Bundle(node);
|
Bundle bundle = new Bundle(node);
|
||||||
|
bundle_fetched(jid, device_id, bundle);
|
||||||
int32 signed_pre_key_id = bundle.signed_pre_key_id;
|
int32 signed_pre_key_id = bundle.signed_pre_key_id;
|
||||||
ECPublicKey? signed_pre_key = bundle.signed_pre_key;
|
ECPublicKey? signed_pre_key = bundle.signed_pre_key;
|
||||||
uint8[] signed_pre_key_signature = bundle.signed_pre_key_signature;
|
uint8[] signed_pre_key_signature = bundle.signed_pre_key_signature;
|
||||||
|
|
60
plugins/omemo/src/util.vala
Normal file
60
plugins/omemo/src/util.vala
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
namespace Dino.Plugins.Omemo {
|
||||||
|
|
||||||
|
public static string fingerprint_from_base64(string b64) {
|
||||||
|
uint8[] arr = Base64.decode(b64);
|
||||||
|
|
||||||
|
arr = arr[1:arr.length];
|
||||||
|
string s = "";
|
||||||
|
foreach (uint8 i in arr) {
|
||||||
|
string tmp = i.to_string("%x");
|
||||||
|
if (tmp.length == 1) tmp = "0" + tmp;
|
||||||
|
s = s + tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string fingerprint_markup(string s) {
|
||||||
|
string markup = "";
|
||||||
|
for (int i = 0; i < s.length; i += 4) {
|
||||||
|
string four_chars = s.substring(i, 4).down();
|
||||||
|
|
||||||
|
int raw = (int) four_chars.to_long(null, 16);
|
||||||
|
uint8[] bytes = {(uint8) ((raw >> 8) & 0xff - 128), (uint8) (raw & 0xff - 128)};
|
||||||
|
|
||||||
|
Checksum checksum = new Checksum(ChecksumType.SHA1);
|
||||||
|
checksum.update(bytes, bytes.length);
|
||||||
|
uint8[] digest = new uint8[20];
|
||||||
|
size_t len = 20;
|
||||||
|
checksum.get_digest(digest, ref len);
|
||||||
|
|
||||||
|
uint8 r = digest[0];
|
||||||
|
uint8 g = digest[1];
|
||||||
|
uint8 b = digest[2];
|
||||||
|
|
||||||
|
if (r == 0 && g == 0 && b == 0) r = g = b = 1;
|
||||||
|
|
||||||
|
double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||||
|
|
||||||
|
if (brightness < 80) {
|
||||||
|
double factor = 80.0 / brightness;
|
||||||
|
r = uint8.min(255, (uint8) (r * factor));
|
||||||
|
g = uint8.min(255, (uint8) (g * factor));
|
||||||
|
b = uint8.min(255, (uint8) (b * factor));
|
||||||
|
|
||||||
|
} else if (brightness > 180) {
|
||||||
|
double factor = 180.0 / brightness;
|
||||||
|
r = (uint8) (r * factor);
|
||||||
|
g = (uint8) (g * factor);
|
||||||
|
b = (uint8) (b * factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % 32 == 0 && i != 0) markup += "\n";
|
||||||
|
markup += @"<span foreground=\"$("#%02x%02x%02x".printf(r, g, b))\">$four_chars</span>";
|
||||||
|
if (i % 8 == 4 && i % 32 != 28) markup += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "<span font_family='monospace' font='8'>" + markup + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,11 +29,12 @@ namespace Xmpp.Xep.Pubsub {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publish(XmppStream stream, string? jid, string node_id, string node, string item_id, StanzaNode content) {
|
public void publish(XmppStream stream, string? jid, string node_id, string node, string? item_id, StanzaNode content) {
|
||||||
StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns();
|
StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns();
|
||||||
StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id);
|
StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id);
|
||||||
pubsub_node.put_node(publish_node);
|
pubsub_node.put_node(publish_node);
|
||||||
StanzaNode items_node = new StanzaNode.build("item", NS_URI).put_attribute("id", item_id);
|
StanzaNode items_node = new StanzaNode.build("item", NS_URI);
|
||||||
|
if (item_id != null) items_node.put_attribute("id", item_id);
|
||||||
items_node.put_node(content);
|
items_node.put_node(content);
|
||||||
publish_node.put_node(items_node);
|
publish_node.put_node(items_node);
|
||||||
Iq.Stanza iq = new Iq.Stanza.set(pubsub_node);
|
Iq.Stanza iq = new Iq.Stanza.set(pubsub_node);
|
||||||
|
|
Loading…
Reference in a new issue