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
|
||||
)
|
||||
|
||||
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
|
||||
SOURCES
|
||||
src/account_settings_dialog.vala
|
||||
src/account_settings_entry.vala
|
||||
src/account_settings_widget.vala
|
||||
src/bundle.vala
|
||||
src/contact_details_provider.vala
|
||||
src/database.vala
|
||||
src/encrypt_state.vala
|
||||
src/encryption_list_entry.vala
|
||||
|
@ -27,6 +43,7 @@ SOURCES
|
|||
src/session_store.vala
|
||||
src/signed_pre_key_store.vala
|
||||
src/stream_module.vala
|
||||
src/util.vala
|
||||
CUSTOM_VAPIS
|
||||
${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi
|
||||
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
|
||||
|
@ -34,10 +51,12 @@ CUSTOM_VAPIS
|
|||
${CMAKE_BINARY_DIR}/exports/dino.vapi
|
||||
PACKAGES
|
||||
${OMEMO_PACKAGES}
|
||||
GRESOURCES
|
||||
${OMEMO_GRESOURCES_XML}
|
||||
)
|
||||
|
||||
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)
|
||||
target_link_libraries(omemo libdino signal-protocol-vala ${OMEMO_PACKAGES})
|
||||
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 Label fingerprint;
|
||||
private Account account;
|
||||
private Button btn;
|
||||
|
||||
public AccountSettingWidget(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
|
@ -18,38 +19,31 @@ public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box {
|
|||
fingerprint.visible = true;
|
||||
pack_start(fingerprint);
|
||||
|
||||
Button btn = new Button();
|
||||
btn = new Button();
|
||||
btn.image = new Image.from_icon_name("view-list-symbolic", IconSize.BUTTON);
|
||||
btn.relief = ReliefStyle.NONE;
|
||||
btn.visible = true;
|
||||
btn.visible = false;
|
||||
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);
|
||||
}
|
||||
|
||||
public void set_account(Account account) {
|
||||
this.account = account;
|
||||
btn.visible = false;
|
||||
try {
|
||||
Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id).inner;
|
||||
if (row == null) {
|
||||
fingerprint.set_markup("%s\n<span font='8'>%s</span>".printf(_("Own fingerprint"), _("Will be generated on first connect")));
|
||||
} 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 += " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
string res = fingerprint_markup(fingerprint_from_base64(((!)row)[plugin.db.identity.identity_key_public_base64]));
|
||||
fingerprint.set_markup("%s\n<span font_family='monospace' font='8'>%s</span>".printf(_("Own fingerprint"), res));
|
||||
btn.visible = true;
|
||||
}
|
||||
} catch (Qlite.DatabaseError e) {
|
||||
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 {
|
||||
|
||||
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 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");
|
||||
init({identity_id, signed_pre_key_id, record_base64});
|
||||
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");
|
||||
init({identity_id, pre_key_id, record_base64});
|
||||
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");
|
||||
init({identity_id, address_name, device_id, record_base64});
|
||||
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 SignedPreKeyTable signed_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 {
|
||||
base(fileName, VERSION);
|
||||
identity_meta = new IdentityMetaTable(this);
|
||||
identity = new IdentityTable(this);
|
||||
signed_pre_key = new SignedPreKeyTable(this);
|
||||
pre_key = new PreKeyTable(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) {
|
||||
|
|
|
@ -83,7 +83,12 @@ public class Manager : StreamInteractionModule, Object {
|
|||
message.marked = Entities.Message.Marked.UNSENT;
|
||||
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());
|
||||
MessageState state;
|
||||
lock (message_states) {
|
||||
|
@ -122,6 +127,7 @@ public class Manager : StreamInteractionModule, Object {
|
|||
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).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_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;
|
||||
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) {
|
||||
|
|
|
@ -27,6 +27,7 @@ public class Plugin : RootInterface, Object {
|
|||
public Database db;
|
||||
public EncryptionListEntry list_entry;
|
||||
public AccountSettingsEntry settings_entry;
|
||||
public ContactDetailsProvider contact_details_provider;
|
||||
|
||||
public void registered(Dino.Application app) {
|
||||
try {
|
||||
|
@ -35,8 +36,10 @@ public class Plugin : RootInterface, Object {
|
|||
this.db = new Database(Path.build_filename(Application.get_storage_dir(), "omemo.db"));
|
||||
this.list_entry = new EncryptionListEntry(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_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) => {
|
||||
list.add(new StreamModule());
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ public class StreamModule : XmppStreamModule {
|
|||
|
||||
public signal void store_created(Store store);
|
||||
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_start_failed(string jid, int device_id);
|
||||
|
||||
|
@ -183,11 +184,11 @@ public class StreamModule : XmppStreamModule {
|
|||
public void request_user_devicelist(XmppStream stream, string jid) {
|
||||
if (active_devicelist_requests.add(jid)) {
|
||||
if (Plugin.DEBUG) print(@"OMEMO: requesting device list for $jid\n");
|
||||
stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id ?? "", node));
|
||||
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();
|
||||
string? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
|
||||
if (my_jid == null) return;
|
||||
|
@ -219,7 +220,6 @@ public class StreamModule : XmppStreamModule {
|
|||
|
||||
public void start_sessions_with(XmppStream stream, string bare_jid) {
|
||||
if (!device_lists.has_key(bare_jid)) {
|
||||
// TODO: manually request a device list
|
||||
return;
|
||||
}
|
||||
Address address = new Address(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) {
|
||||
return device_lists.has_key(name);
|
||||
}
|
||||
|
@ -276,6 +293,7 @@ public class StreamModule : XmppStreamModule {
|
|||
fail = true;
|
||||
} else {
|
||||
Bundle bundle = new Bundle(node);
|
||||
bundle_fetched(jid, device_id, bundle);
|
||||
int32 signed_pre_key_id = bundle.signed_pre_key_id;
|
||||
ECPublicKey? signed_pre_key = bundle.signed_pre_key;
|
||||
uint8[] signed_pre_key_signature = bundle.signed_pre_key_signature;
|
||||
|
|
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 publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id);
|
||||
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);
|
||||
publish_node.put_node(items_node);
|
||||
Iq.Stanza iq = new Iq.Stanza.set(pubsub_node);
|
||||
|
|
Loading…
Reference in a new issue