2018-06-11 08:11:04 +02:00
using Gtk ;
using Xmpp ;
using Gee ;
using Qlite ;
using Dino.Entities ;
2018-08-08 22:57:24 +02:00
using Qrencode ;
using Gdk ;
2018-06-11 08:11:04 +02:00
namespace Dino.Plugins.Omemo {
[ GtkTemplate ( ui = " /im/dino/Dino/omemo/contact_details_dialog.ui " ) ]
public class ContactDetailsDialog : Gtk . Dialog {
private Plugin plugin ;
private Account account ;
private Jid jid ;
2018-07-09 15:16:23 +02:00
private bool own = false ;
private int own_id = 0 ;
2019-12-20 02:06:36 +01:00
private int identity_id = 0 ;
private Signal . Store store ;
private Set < uint32 > displayed_ids = new HashSet < uint32 > ( ) ;
2018-06-11 08:11:04 +02:00
2021-10-11 23:08:50 +02:00
[ GtkChild ] private unowned Label automatically_accept_new_label ;
[ GtkChild ] private unowned Label automatically_accept_new_descr ;
[ GtkChild ] private unowned Label own_key_label ;
[ GtkChild ] private unowned Label new_keys_label ;
[ GtkChild ] private unowned Label associated_keys_label ;
[ GtkChild ] private unowned Label inactive_expander_label ;
2019-11-14 01:35:56 +01:00
2021-10-11 23:08:50 +02:00
[ GtkChild ] private unowned Box own_fingerprint_container ;
[ GtkChild ] private unowned Label own_fingerprint_label ;
[ GtkChild ] private unowned Box new_keys_container ;
[ GtkChild ] private unowned ListBox new_keys_listbox ;
[ GtkChild ] private unowned Box keys_container ;
[ GtkChild ] private unowned ListBox keys_listbox ;
[ GtkChild ] private unowned Expander inactive_keys_expander ;
[ GtkChild ] private unowned ListBox inactive_keys_listbox ;
[ GtkChild ] private unowned Switch auto_accept_switch ;
[ GtkChild ] private unowned Button copy_button ;
[ GtkChild ] private unowned Button show_qrcode_button ;
[ GtkChild ] private unowned Image qrcode_image ;
[ GtkChild ] private unowned Popover qrcode_popover ;
2018-06-11 08:11:04 +02:00
2019-10-09 23:32:03 +02:00
construct {
// If we set the strings in the .ui file, they don't get translated
title = _ ( " OMEMO Key Management " ) ;
automatically_accept_new_label . label = _ ( " Automatically accept new keys " ) ;
2020-03-24 21:34:10 +01:00
automatically_accept_new_descr . label = _ ( " New encryption keys from this contact will be accepted automatically. " ) ;
2019-10-09 23:32:03 +02:00
own_key_label . label = _ ( " Own key " ) ;
new_keys_label . label = _ ( " New keys " ) ;
associated_keys_label . label = _ ( " Associated keys " ) ;
2019-11-14 01:35:56 +01:00
inactive_expander_label . label = _ ( " Inactive keys " ) ;
2019-10-09 23:32:03 +02:00
}
2018-08-09 16:29:15 +02:00
public ContactDetailsDialog ( Plugin plugin , Account account , Jid jid ) {
2019-02-20 23:08:30 +01:00
Object ( use_header_bar : Environment . get_variable ( " GTK_CSD " ) ! = " 0 " ? 1 : 0 ) ;
2018-08-09 16:29:15 +02:00
this . plugin = plugin ;
this . account = account ;
this . jid = jid ;
2019-02-20 23:08:30 +01:00
if ( Environment . get_variable ( " GTK_CSD " ) ! = " 0 " ) {
2020-10-27 15:31:39 +01:00
( ( HeaderBar ) get_header_bar ( ) ) . set_subtitle ( jid . bare_jid . to_string ( ) ) ;
2019-02-20 23:08:30 +01:00
}
2018-08-09 16:29:15 +02:00
2019-10-22 01:23:43 +02:00
keys_listbox . row_activated . connect ( on_key_entry_clicked ) ;
2019-12-13 16:27:05 +01:00
inactive_keys_listbox . row_activated . connect ( on_key_entry_clicked ) ;
2019-10-22 01:23:43 +02:00
auto_accept_switch . state_set . connect ( on_auto_accept_toggled ) ;
2019-12-20 02:06:36 +01:00
identity_id = plugin . db . identity . get_id ( account . id ) ;
2018-08-12 12:04:40 +02:00
if ( identity_id < 0 ) return ;
2019-12-20 02:06:36 +01:00
Dino . Application ? app = Application . get_default ( ) as Dino . Application ;
if ( app ! = null ) {
store = app . stream_interactor . module_manager . get_module ( account , StreamModule . IDENTITY ) . store ;
}
2018-08-09 16:29:15 +02:00
2019-12-20 02:06:36 +01:00
auto_accept_switch . set_active ( plugin . db . trust . get_blind_trust ( identity_id , jid . bare_jid . to_string ( ) , true ) ) ;
2019-10-22 01:23:43 +02:00
// Dialog opened from the account settings menu
// Show the fingerprint for this device separately with buttons for a qrcode and to copy
2018-08-09 16:29:15 +02:00
if ( jid . equals ( account . bare_jid ) ) {
own = true ;
own_id = plugin . db . identity . row_with ( plugin . db . identity . account_id , account . id ) [ plugin . db . identity . device_id ] ;
2020-03-24 21:34:10 +01:00
automatically_accept_new_descr . label = _ ( " New encryption keys from your other devices will be accepted automatically. " ) ;
2018-11-10 17:24:48 +01:00
2018-08-09 16:29:15 +02:00
own_fingerprint_container . visible = true ;
string own_b64 = plugin . db . identity . row_with ( plugin . db . identity . account_id , account . id ) [ plugin . db . identity . identity_key_public_base64 ] ;
string fingerprint = fingerprint_from_base64 ( own_b64 ) ;
2018-08-10 01:45:22 +02:00
own_fingerprint_label . set_markup ( fingerprint_markup ( fingerprint ) ) ;
2018-08-09 16:29:15 +02:00
2018-08-10 01:45:22 +02:00
copy_button . clicked . connect ( ( ) = > { Clipboard . get_default ( get_display ( ) ) . set_text ( fingerprint , fingerprint . length ) ; } ) ;
2018-08-09 16:29:15 +02:00
int sid = plugin . db . identity . row_with ( plugin . db . identity . account_id , account . id ) [ plugin . db . identity . device_id ] ;
2022-02-26 01:08:00 +01:00
var iri_query = @" omemo-sid-$(sid)=$(fingerprint) " ;
2022-03-16 15:33:13 +01:00
# if GLIB_2_66 & & VALA_0_50
2022-03-09 01:33:42 +01:00
string iri = GLib . Uri . join ( UriFlags . NONE , " xmpp " , null , null , 0 , jid . to_string ( ) , iri_query , null ) ;
# else
2022-02-26 01:08:00 +01:00
var iri_path_seg = escape_for_iri_path_segment ( jid . to_string ( ) ) ;
var iri = @" xmpp:$(iri_path_seg)?$(iri_query) " ;
2022-03-09 01:33:42 +01:00
# endif
2022-02-18 22:41:28 +01:00
const int QUIET_ZONE_MODULES = 4 ; // MUST be at least 4
const int MODULE_SIZE_PX = 4 ; // arbitrary
2022-02-26 01:08:00 +01:00
var qr_pixbuf = new QRcode ( iri , 2 )
2022-02-26 00:06:57 +01:00
. to_pixbuf ( MODULE_SIZE_PX * qrcode_image . scale_factor ) ;
qrcode_image . set_from_surface (
Gdk . cairo_surface_create_from_pixbuf ( qr_pixbuf , 0 , get_window ( ) ) ) ;
2022-02-18 22:41:28 +01:00
qrcode_image . margin = QUIET_ZONE_MODULES * MODULE_SIZE_PX ;
qrcode_popover . get_style_context ( ) . add_class ( " qrcode-container " ) ;
2018-08-10 01:45:22 +02:00
show_qrcode_button . clicked . connect ( qrcode_popover . popup ) ;
2018-08-09 16:29:15 +02:00
}
2018-08-10 01:45:22 +02:00
new_keys_listbox . set_header_func ( header_function ) ;
2018-08-09 16:29:15 +02:00
2018-08-10 01:45:22 +02:00
keys_listbox . set_header_func ( header_function ) ;
2018-08-09 16:29:15 +02:00
2018-08-10 01:45:22 +02:00
//Show any new devices for which the user must decide whether to accept or reject
2018-08-12 12:04:40 +02:00
foreach ( Row device in plugin . db . identity_meta . get_new_devices ( identity_id , jid . to_string ( ) ) ) {
2018-08-09 16:29:15 +02:00
add_new_fingerprint ( device ) ;
}
2018-08-10 01:45:22 +02:00
//Show the normal devicelist
2018-08-12 12:04:40 +02:00
foreach ( Row device in plugin . db . identity_meta . get_known_devices ( identity_id , jid . to_string ( ) ) ) {
2018-08-09 16:29:15 +02:00
if ( own & & device [ plugin . db . identity_meta . device_id ] = = own_id ) {
continue ;
}
2019-05-16 20:41:41 +02:00
add_fingerprint ( device , ( TrustLevel ) device [ plugin . db . identity_meta . trust_level ] ) ;
2018-08-09 16:29:15 +02:00
}
2019-12-20 02:06:36 +01:00
// Check for unknown devices
fetch_unknown_bundles ( ) ;
}
2022-02-26 01:08:00 +01:00
private static string escape_for_iri_path_segment ( string s ) {
// from RFC 3986, 2.2. Reserved Characters:
2022-03-09 01:33:42 +01:00
string SUB_DELIMS = " !$&'()*+,;= " ;
2022-02-26 01:08:00 +01:00
// from RFC 3986, 3.3. Path (pchar without unreserved and pct-encoded):
2022-03-09 01:33:42 +01:00
string ALLOWED_RESERVED_CHARS = SUB_DELIMS + " :@ " ;
2022-02-26 01:08:00 +01:00
return GLib . Uri . escape_string ( s , ALLOWED_RESERVED_CHARS , true ) ;
}
2019-12-20 02:06:36 +01:00
private void fetch_unknown_bundles ( ) {
Dino . Application app = Application . get_default ( ) as Dino . Application ;
XmppStream ? stream = app . stream_interactor . get_stream ( account ) ;
if ( stream = = null ) return ;
StreamModule ? module = stream . get_module ( StreamModule . IDENTITY ) ;
if ( module = = null ) return ;
module . bundle_fetched . connect_after ( ( bundle_jid , device_id , bundle ) = > {
if ( bundle_jid . equals ( jid ) & & ! displayed_ids . contains ( device_id ) ) {
Row ? device = plugin . db . identity_meta . get_device ( identity_id , jid . to_string ( ) , device_id ) ;
if ( device = = null ) return ;
if ( auto_accept_switch . active ) {
add_fingerprint ( device , ( TrustLevel ) device [ plugin . db . identity_meta . trust_level ] ) ;
} else {
add_new_fingerprint ( device ) ;
}
}
} ) ;
foreach ( Row device in plugin . db . identity_meta . get_unknown_devices ( identity_id , jid . to_string ( ) ) ) {
2019-12-22 04:10:53 +01:00
try {
module . fetch_bundle ( stream , new Jid ( device [ plugin . db . identity_meta . address_name ] ) , device [ plugin . db . identity_meta . device_id ] , false ) ;
} catch ( InvalidJidError e ) {
warning ( " Ignoring device with invalid Jid: %s " , e . message ) ;
}
2019-12-20 02:06:36 +01:00
}
2018-08-09 16:29:15 +02:00
}
2018-08-10 01:45:22 +02:00
private void header_function ( ListBoxRow row , ListBoxRow ? before ) {
if ( row . get_header ( ) = = null & & before ! = null ) {
row . set_header ( new Separator ( Orientation . HORIZONTAL ) ) ;
}
2018-06-11 08:11:04 +02:00
}
2019-10-22 01:23:43 +02:00
private void add_fingerprint ( Row device , TrustLevel trust ) {
string key_base64 = device [ plugin . db . identity_meta . identity_key_public_base64 ] ;
bool key_active = device [ plugin . db . identity_meta . now_active ] ;
2019-12-20 02:06:36 +01:00
if ( store ! = null ) {
try {
Signal . Address address = new Signal . Address ( jid . to_string ( ) , device [ plugin . db . identity_meta . device_id ] ) ;
Signal . SessionRecord ? session = null ;
if ( store . contains_session ( address ) ) {
session = store . load_session ( address ) ;
string session_key_base64 = Base64 . encode ( session . state . remote_identity_key . serialize ( ) ) ;
if ( key_base64 ! = session_key_base64 ) {
critical ( " Session and database identity key mismatch! " ) ;
key_base64 = session_key_base64 ;
}
}
} catch ( Error e ) {
print ( " Error while reading session store: %s " , e . message ) ;
}
}
2019-10-22 01:23:43 +02:00
FingerprintRow fingerprint_row = new FingerprintRow ( device , key_base64 , trust , key_active ) { visible = true , activatable = true , hexpand = true } ;
if ( device [ plugin . db . identity_meta . now_active ] ) {
keys_container . visible = true ;
keys_listbox . add ( fingerprint_row ) ;
} else {
2019-12-13 16:27:05 +01:00
inactive_keys_expander . visible = true ;
inactive_keys_listbox . add ( fingerprint_row ) ;
2018-08-03 20:07:23 +02:00
}
2019-12-20 02:06:36 +01:00
displayed_ids . add ( device [ plugin . db . identity_meta . device_id ] ) ;
2018-08-07 02:06:59 +02:00
}
2019-10-22 01:23:43 +02:00
private void on_key_entry_clicked ( ListBoxRow widget ) {
FingerprintRow ? fingerprint_row = widget as FingerprintRow ;
if ( fingerprint_row = = null ) return ;
Row updated_device = plugin . db . identity_meta . get_device ( fingerprint_row . row [ plugin . db . identity_meta . identity_id ] , fingerprint_row . row [ plugin . db . identity_meta . address_name ] , fingerprint_row . row [ plugin . db . identity_meta . device_id ] ) ;
ManageKeyDialog manage_dialog = new ManageKeyDialog ( updated_device , plugin . db ) ;
manage_dialog . set_transient_for ( ( Gtk . Window ) get_toplevel ( ) ) ;
manage_dialog . present ( ) ;
manage_dialog . response . connect ( ( response ) = > {
fingerprint_row . update_trust_state ( response , fingerprint_row . row [ plugin . db . identity_meta . now_active ] ) ;
update_stored_trust ( response , fingerprint_row . row ) ;
} ) ;
}
2018-08-03 20:07:23 +02:00
2019-10-22 01:23:43 +02:00
private bool on_auto_accept_toggled ( bool active ) {
plugin . trust_manager . set_blind_trust ( account , jid , active ) ;
2018-08-03 20:07:23 +02:00
2019-10-22 01:23:43 +02:00
if ( active ) {
int identity_id = plugin . db . identity . get_id ( account . id ) ;
if ( identity_id < 0 ) return false ;
2018-08-03 20:07:23 +02:00
2019-10-22 01:23:43 +02:00
new_keys_container . visible = false ;
foreach ( Row device in plugin . db . identity_meta . get_new_devices ( identity_id , jid . to_string ( ) ) ) {
plugin . trust_manager . set_device_trust ( account , jid , device [ plugin . db . identity_meta . device_id ] , TrustLevel . TRUSTED ) ;
add_fingerprint ( device , TrustLevel . TRUSTED ) ;
2018-08-03 20:07:23 +02:00
}
2019-10-22 01:23:43 +02:00
}
return false ;
2018-08-03 20:07:23 +02:00
}
2018-06-11 08:11:04 +02:00
2019-10-22 01:23:43 +02:00
private void update_stored_trust ( int response , Row device ) {
2018-08-03 20:07:23 +02:00
switch ( response ) {
2019-05-16 20:41:41 +02:00
case TrustLevel . TRUSTED :
plugin . trust_manager . set_device_trust ( account , jid , device [ plugin . db . identity_meta . device_id ] , TrustLevel . TRUSTED ) ;
2018-08-03 20:07:23 +02:00
break ;
2019-05-16 20:41:41 +02:00
case TrustLevel . UNTRUSTED :
plugin . trust_manager . set_device_trust ( account , jid , device [ plugin . db . identity_meta . device_id ] , TrustLevel . UNTRUSTED ) ;
2018-08-03 20:07:23 +02:00
break ;
2019-05-16 20:41:41 +02:00
case TrustLevel . VERIFIED :
plugin . trust_manager . set_device_trust ( account , jid , device [ plugin . db . identity_meta . device_id ] , TrustLevel . VERIFIED ) ;
2018-08-10 01:45:22 +02:00
plugin . trust_manager . set_blind_trust ( account , jid , false ) ;
auto_accept_switch . set_active ( false ) ;
2018-08-03 20:07:23 +02:00
break ;
}
2018-06-11 08:11:04 +02:00
}
2019-10-22 01:23:43 +02:00
private void add_new_fingerprint ( Row device ) {
2018-08-03 20:07:23 +02:00
new_keys_container . visible = true ;
2018-06-11 08:11:04 +02:00
2018-08-03 20:07:23 +02:00
ListBoxRow lbr = new ListBoxRow ( ) { visible = true , activatable = false , hexpand = true } ;
Box box = new Box ( Gtk . Orientation . HORIZONTAL , 40 ) { visible = true , margin_start = 20 , margin_end = 20 , margin_top = 14 , margin_bottom = 14 , hexpand = true } ;
2018-07-04 22:26:14 +02:00
2019-10-28 01:27:34 +01:00
Button accept_button = new Button ( ) { visible = true , valign = Align . CENTER , hexpand = true } ;
accept_button . add ( new Image . from_icon_name ( " emblem-ok-symbolic " , IconSize . BUTTON ) { visible = true } ) ; // using .image = sets .image-button. Together with .suggested/destructive action that breaks the button Adwaita
accept_button . get_style_context ( ) . add_class ( " suggested-action " ) ;
accept_button . tooltip_text = _ ( " Accept key " ) ;
2018-07-09 15:16:23 +02:00
2019-10-28 01:27:34 +01:00
Button reject_button = new Button ( ) { visible = true , valign = Align . CENTER , hexpand = true } ;
reject_button . add ( new Image . from_icon_name ( " action-unavailable-symbolic " , IconSize . BUTTON ) { visible = true } ) ;
reject_button . get_style_context ( ) . add_class ( " destructive-action " ) ;
reject_button . tooltip_text = _ ( " Reject key " ) ;
2018-07-09 15:16:23 +02:00
2019-10-28 01:27:34 +01:00
accept_button . clicked . connect ( ( ) = > {
2019-05-16 20:41:41 +02:00
plugin . trust_manager . set_device_trust ( account , jid , device [ plugin . db . identity_meta . device_id ] , TrustLevel . TRUSTED ) ;
add_fingerprint ( device , TrustLevel . TRUSTED ) ;
2018-08-10 01:45:22 +02:00
new_keys_listbox . remove ( lbr ) ;
if ( new_keys_listbox . get_children ( ) . length ( ) < 1 ) new_keys_container . visible = false ;
2018-08-03 20:07:23 +02:00
} ) ;
2018-06-11 08:11:04 +02:00
2019-10-28 01:27:34 +01:00
reject_button . clicked . connect ( ( ) = > {
2019-05-16 20:41:41 +02:00
plugin . trust_manager . set_device_trust ( account , jid , device [ plugin . db . identity_meta . device_id ] , TrustLevel . UNTRUSTED ) ;
add_fingerprint ( device , TrustLevel . UNTRUSTED ) ;
2018-08-10 01:45:22 +02:00
new_keys_listbox . remove ( lbr ) ;
if ( new_keys_listbox . get_children ( ) . length ( ) < 1 ) new_keys_container . visible = false ;
2018-08-03 20:07:23 +02:00
} ) ;
2018-06-11 08:11:04 +02:00
2018-08-03 20:07:23 +02:00
string res = fingerprint_markup ( fingerprint_from_base64 ( device [ plugin . db . identity_meta . identity_key_public_base64 ] ) ) ;
2019-10-28 01:27:34 +01:00
Label fingerprint_label = new Label ( res ) { use_markup = true , justify = Justification . RIGHT , visible = true , halign = Align . START , valign = Align . CENTER , hexpand = false } ;
box . add ( fingerprint_label ) ;
2018-06-11 08:11:04 +02:00
2019-10-28 01:27:34 +01:00
Box control_box = new Box ( Gtk . Orientation . HORIZONTAL , 0 ) { visible = true , hexpand = true } ;
control_box . add ( accept_button ) ;
control_box . add ( reject_button ) ;
control_box . get_style_context ( ) . add_class ( " linked " ) ; // .linked: Visually link the accept / reject buttons
2018-08-10 01:45:22 +02:00
box . add ( control_box ) ;
2018-06-11 08:11:04 +02:00
2018-08-03 20:07:23 +02:00
lbr . add ( box ) ;
2018-08-10 01:45:22 +02:00
new_keys_listbox . add ( lbr ) ;
2019-12-20 02:06:36 +01:00
displayed_ids . add ( device [ plugin . db . identity_meta . device_id ] ) ;
2018-08-03 20:07:23 +02:00
}
2018-06-11 08:11:04 +02:00
}
2019-10-22 01:23:43 +02:00
public class FingerprintRow : ListBoxRow {
private Image trust_image = new Image ( ) { visible = true , halign = Align . END , icon_size = IconSize . BUTTON } ;
private Label fingerprint_label = new Label ( " " ) { use_markup = true , justify = Justification . RIGHT , visible = true , halign = Align . START , valign = Align . CENTER , hexpand = false } ;
private Label trust_label = new Label ( null ) { visible = true , hexpand = true , xalign = 0 } ;
public Row row ;
construct {
Box box = new Box ( Gtk . Orientation . HORIZONTAL , 40 ) { visible = true , margin_start = 20 , margin_end = 20 , margin_top = 14 , margin_bottom = 14 , hexpand = true } ;
Box status_box = new Box ( Gtk . Orientation . HORIZONTAL , 5 ) { visible = true , hexpand = true } ;
box . add ( fingerprint_label ) ;
box . add ( status_box ) ;
status_box . add ( trust_label ) ;
status_box . add ( trust_image ) ;
this . add ( box ) ;
}
public FingerprintRow ( Row row , string key_base64 , int trust , bool now_active ) {
this . row = row ;
fingerprint_label . label = fingerprint_markup ( fingerprint_from_base64 ( key_base64 ) ) ;
update_trust_state ( trust , now_active ) ;
}
public void update_trust_state ( int trust , bool now_active ) {
switch ( trust ) {
case TrustLevel . TRUSTED :
trust_image . icon_name = " emblem-ok-symbolic " ;
trust_label . set_markup ( " <span color='#1A63D9'>%s</span> " . printf ( _ ( " Accepted " ) ) ) ;
fingerprint_label . get_style_context ( ) . remove_class ( " dim-label " ) ;
break ;
case TrustLevel . UNTRUSTED :
trust_image . icon_name = " action-unavailable-symbolic " ;
trust_label . set_markup ( " <span color='#D91900'>%s</span> " . printf ( _ ( " Rejected " ) ) ) ;
fingerprint_label . get_style_context ( ) . add_class ( " dim-label " ) ;
break ;
case TrustLevel . VERIFIED :
trust_image . icon_name = " security-high-symbolic " ;
trust_label . set_markup ( " <span color='#1A63D9'>%s</span> " . printf ( _ ( " Verified " ) ) ) ;
fingerprint_label . get_style_context ( ) . remove_class ( " dim-label " ) ;
break ;
}
if ( ! now_active ) {
trust_image . icon_name = " appointment-missed-symbolic " ;
trust_label . set_markup ( " <span color='#8b8e8f'>%s</span> " . printf ( _ ( " Unused " ) ) ) ;
}
}
}
2018-06-11 08:11:04 +02:00
}