Refactored keys screen

This commit is contained in:
Mark Qvist 2025-12-25 16:10:16 +01:00
parent 199d9f9bdf
commit 2e8a17854e
3 changed files with 281 additions and 255 deletions

View file

@ -436,6 +436,7 @@ else:
from ui.utilities import Utilities
from ui.voice import Voice
from ui.guide import Guide
from ui.keys import Keys
from ui.objectdetails import ObjectDetails
from ui.announces import Announces
from ui.messages import Messages, ts_format, messages_screen_kv
@ -469,6 +470,7 @@ else:
from .ui.utilities import Utilities
from .ui.voice import Voice
from .ui.guide import Guide
from .ui.keys import Keys
from .ui.objectdetails import ObjectDetails
from .ui.messages import Messages, ts_format, messages_screen_kv
from .ui.helpers import ContentNavigationDrawer, DrawerList, IconListItem
@ -1446,6 +1448,7 @@ class SidebandApp(MDApp):
self.announces_view = None
self.guide_view = None
self.keys_view = None
if RNS.vendor.platformutils.is_android():
ActivityInfo = autoclass('android.content.pm.ActivityInfo')
@ -5384,37 +5387,20 @@ class SidebandApp(MDApp):
######################################
def keys_action(self, sender=None, direction="left"):
if self.root.ids.screen_manager.has_screen("keys_screen"):
self.keys_open(direction=direction)
if self.root.ids.screen_manager.has_screen("keys_screen"): self.keys_open(direction=direction)
else:
self.loader_action(direction=direction)
def final(dt):
self.keys_init()
def o(dt):
self.keys_open(no_transition=True)
self.init_keys_view()
def o(dt): self.keys_open(no_transition=True)
Clock.schedule_once(o, ll_ot)
Clock.schedule_once(final, ll_ft)
def keys_init(self):
if not self.root.ids.screen_manager.has_screen("keys_screen"):
self.keys_screen = Builder.load_string(layout_keys_screen)
self.keys_screen.app = self
self.root.ids.screen_manager.add_widget(self.keys_screen)
self.bind_clipboard_actions(self.keys_screen.ids)
self.keys_screen.ids.keys_scrollview.effect_cls = ScrollEffect
info1 = "[size=18dp][b]Encryption Keys[/b][/size][size=5dp]\n \n[/size]Your primary encryption keys are stored in a Reticulum Identity within the Sideband app. If you want to backup this Identity for later use on this or another device, you can export it as a plain text blob, with the key data encoded in Base32 format. This will allow you to restore your address in Sideband or other LXMF clients at a later point.\n\n[b]Warning![/b] Anyone that gets access to the key data will be able to control your LXMF address, impersonate you, and read your messages. It is [b]extremely important[/b] that you keep the Identity data secure if you export it.\n\nBefore displaying or exporting your Identity data, make sure that no machine or person in your vicinity is able to see, copy or record your device screen or similar."
info2 = "[size=18dp][b]Backup & Restore[/b][/size][size=5dp]\n \n[/size]You can backup your entire Sideband profile for import on a computer or other device. The exported backup archive will be saved in the downloads folder of your device. Please note that the exported archive contains all your messages, data and encryption keys. Take extreme care to keep this archive secure."
if not RNS.vendor.platformutils.get_platform() == "android":
self.widget_hide(self.keys_screen.ids.keys_share)
self.keys_screen.ids.keys_info.text = info1
self.keys_screen.ids.backup_info.text = info2
def init_keys_view(self, sender=None):
if not self.keys_view: self.keys_view = Keys(self)
def keys_open(self, sender=None, direction="left", no_transition=False):
if no_transition:
self.root.ids.screen_manager.transition = self.no_transition
if no_transition: self.root.ids.screen_manager.transition = self.no_transition
else:
self.root.ids.screen_manager.transition = self.slide_transition
self.root.ids.screen_manager.transition.direction = direction
@ -5423,119 +5409,10 @@ class SidebandApp(MDApp):
self.root.ids.screen_manager.current = "keys_screen"
self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
if no_transition: self.root.ids.screen_manager.transition = self.slide_transition
if no_transition:
self.root.ids.screen_manager.transition = self.slide_transition
def close_keys_action(self, sender=None): self.open_conversations(direction="right")
def close_keys_action(self, sender=None):
self.open_conversations(direction="right")
def _profile_backup_job(self):
import tarfile
from ui.helpers import file_ts_format
output_path = plyer.storagepath.get_downloads_dir()
time_str = time.strftime(file_ts_format, time.localtime(time.time()))
target_file = f"{output_path}/sideband_backup_{time_str}.tar.gz"
tar = tarfile.open(target_file, "w:gz")
tar.add(f"{self.sideband.app_dir}/app_storage", arcname="Sideband Backup")
tar.close()
def job(dt):
self.keys_screen.ids.keys_backup.disabled = False
toast("Backup done")
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(text=f"Backup has been saved to {target_file}",
buttons=[ok_button])
def dl_yes(s): dialog.dismiss()
ok_button.bind(on_release=dl_yes)
dialog.open()
Clock.schedule_once(job, 0.3)
def profile_backup_action(self, sender=None):
self.keys_screen.ids.keys_backup.disabled = True
toast("Creating backup...")
threading.Thread(target=self._profile_backup_job, daemon=True).start()
def identity_display_action(self, sender=None):
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(
text="Your Identity key, in base32 format is as follows:\n\n[b]"+str(base64.b32encode(self.sideband.identity.get_private_key()).decode("utf-8"))+"[/b]",
buttons=[ yes_button ],
# elevation=0,
)
def dl_yes(s):
dialog.dismiss()
yes_button.bind(on_release=dl_yes)
dialog.open()
def identity_copy_action(self, sender=None):
c_yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.color_reject, text_color=self.color_reject)
c_no_button = MDRectangleFlatButton(text="No, go back",font_size=dp(18))
c_dialog = MDDialog(text="[b]Caution![/b]\n\nYour Identity key will be copied to the system clipboard. Take extreme care that no untrusted app steals your key by reading the clipboard data. Clear the system clipboard immediately after pasting your key where you need it.\n\nAre you sure that you wish to proceed?", buttons=[ c_no_button, c_yes_button ])
def c_dl_no(s):
c_dialog.dismiss()
def c_dl_yes(s):
c_dialog.dismiss()
yes_button = MDRectangleFlatButton(text="OK")
dialog = MDDialog(text="Your Identity key was copied to the system clipboard", buttons=[ yes_button ])
def dl_yes(s):
dialog.dismiss()
yes_button.bind(on_release=dl_yes)
Clipboard.copy(str(base64.b32encode(self.sideband.identity.get_private_key()).decode("utf-8")))
dialog.open()
c_yes_button.bind(on_release=c_dl_yes)
c_no_button.bind(on_release=c_dl_no)
c_dialog.open()
def identity_share_action(self, sender=None):
if RNS.vendor.platformutils.get_platform() == "android":
self.share_text(str(base64.b32encode(self.sideband.identity.get_private_key()).decode("utf-8")))
def identity_restore_action(self, sender=None):
c_yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.color_reject, text_color=self.color_reject)
c_no_button = MDRectangleFlatButton(text="No, go back",font_size=dp(18))
c_dialog = MDDialog(text="[b]Caution![/b]\n\nYou are about to import a new Identity key into Sideband. The currently active key will be irreversibly destroyed, and you will loose your LXMF address if you have not already backed up your current Identity key.\n\nAre you sure that you wish to import the key?", buttons=[ c_no_button, c_yes_button ])
def c_dl_no(s):
c_dialog.dismiss()
def c_dl_yes(s):
c_dialog.dismiss()
b32_text = self.keys_screen.ids.key_restore_text.text
try:
key_bytes = base64.b32decode(b32_text)
new_id = RNS.Identity.from_bytes(key_bytes)
if new_id != None:
new_id.to_file(self.sideband.identity_path)
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(text="[b]The provided Identity key data was imported[/b]\n\nThe app will now exit. Please restart Sideband to use the new Identity.", buttons=[ yes_button ])
def dl_yes(s):
dialog.dismiss()
self.quit_action(sender=self)
yes_button.bind(on_release=dl_yes)
dialog.open()
except Exception as e:
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(text="[b]The provided Identity key data was not valid[/b]\n\nThe error reported by Reticulum was:\n\n[i]"+str(e)+"[/i]\n\nNo Identity was imported into Sideband.", buttons=[ yes_button ])
def dl_yes(s):
dialog.dismiss()
yes_button.bind(on_release=dl_yes)
dialog.open()
c_yes_button.bind(on_release=c_dl_yes)
c_no_button.bind(on_release=c_dl_no)
c_dialog.open()
### Plugins & Services screen
######################################

270
sbapp/ui/keys.py Normal file
View file

@ -0,0 +1,270 @@
import time
import RNS
import base64
import threading
from kivy.metrics import dp,sp
from kivy.lang.builder import Builder
from kivy.core.clipboard import Clipboard
from kivymd.uix.button import MDRectangleFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.toast import toast
from kivy.effects.scroll import ScrollEffect
if RNS.vendor.platformutils.get_platform() == "android":
from ui.helpers import dark_theme_text_color
else:
from .helpers import dark_theme_text_color
class Keys():
def __init__(self, app):
self.app = app
self.keys_screen = None
if not self.app.root.ids.screen_manager.has_screen("keys_screen"):
self.keys_screen = Builder.load_string(layout_keys_screen)
self.keys_screen.app = self
self.app.root.ids.screen_manager.add_widget(self.keys_screen)
self.app.bind_clipboard_actions(self.keys_screen.ids)
self.keys_screen.ids.keys_scrollview.effect_cls = ScrollEffect
info1 = "[size=18dp][b]Encryption Keys[/b][/size][size=5dp]\n \n[/size]Your primary encryption keys are stored in a Reticulum Identity within the Sideband app. If you want to backup this Identity for later use on this or another device, you can export it as a plain text blob, with the key data encoded in Base32 format. This will allow you to restore your address in Sideband or other LXMF clients at a later point.\n\n[b]Warning![/b] Anyone that gets access to the key data will be able to control your LXMF address, impersonate you, and read your messages. It is [b]extremely important[/b] that you keep the Identity data secure if you export it.\n\nBefore displaying or exporting your Identity data, make sure that no machine or person in your vicinity is able to see, copy or record your device screen or similar."
info2 = "[size=18dp][b]Backup & Restore[/b][/size][size=5dp]\n \n[/size]You can backup your entire Sideband profile for import on a computer or other device. The exported backup archive will be saved in the downloads folder of your device. Please note that the exported archive contains all your messages, data and encryption keys. Take extreme care to keep this archive secure."
if not RNS.vendor.platformutils.get_platform() == "android":
self.app.widget_hide(self.keys_screen.ids.keys_share)
self.keys_screen.ids.keys_info.text = info1
self.keys_screen.ids.backup_info.text = info2
def _profile_backup_job(self):
import tarfile
import plyer
from .helpers import file_ts_format
from kivy.clock import Clock
output_path = plyer.storagepath.get_downloads_dir()
time_str = time.strftime(file_ts_format, time.localtime(time.time()))
target_file = f"{output_path}/sideband_backup_{time_str}.tar.gz"
tar = tarfile.open(target_file, "w:gz")
tar.add(f"{self.app.sideband.app_dir}/app_storage", arcname="Sideband Backup")
tar.close()
def job(dt):
self.keys_screen.ids.keys_backup.disabled = False
toast("Backup done")
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(text=f"Backup has been saved to {target_file}",
buttons=[ok_button])
def dl_yes(s): dialog.dismiss()
ok_button.bind(on_release=dl_yes)
dialog.open()
Clock.schedule_once(job, 0.3)
def profile_backup_action(self, sender=None):
self.keys_screen.ids.keys_backup.disabled = True
toast("Creating backup...")
threading.Thread(target=self._profile_backup_job, daemon=True).start()
def identity_display_action(self, sender=None):
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(
text="Your Identity key, in base32 format is as follows:\n\n[b]"+str(base64.b32encode(self.app.sideband.identity.get_private_key()).decode("utf-8"))+"[/b]",
buttons=[ yes_button ],
# elevation=0,
)
def dl_yes(s):
dialog.dismiss()
yes_button.bind(on_release=dl_yes)
dialog.open()
def identity_copy_action(self, sender=None):
c_yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject)
c_no_button = MDRectangleFlatButton(text="No, go back",font_size=dp(18))
c_dialog = MDDialog(text="[b]Caution![/b]\n\nYour Identity key will be copied to the system clipboard. Take extreme care that no untrusted app steals your key by reading the clipboard data. Clear the system clipboard immediately after pasting your key where you need it.\n\nAre you sure that you wish to proceed?", buttons=[ c_no_button, c_yes_button ])
def c_dl_no(s):
c_dialog.dismiss()
def c_dl_yes(s):
c_dialog.dismiss()
yes_button = MDRectangleFlatButton(text="OK")
dialog = MDDialog(text="Your Identity key was copied to the system clipboard", buttons=[ yes_button ])
def dl_yes(s):
dialog.dismiss()
yes_button.bind(on_release=dl_yes)
Clipboard.copy(str(base64.b32encode(self.app.sideband.identity.get_private_key()).decode("utf-8")))
dialog.open()
c_yes_button.bind(on_release=c_dl_yes)
c_no_button.bind(on_release=c_dl_no)
c_dialog.open()
def identity_share_action(self, sender=None):
if RNS.vendor.platformutils.get_platform() == "android":
self.share_text(str(base64.b32encode(self.app.sideband.identity.get_private_key()).decode("utf-8")))
def identity_restore_action(self, sender=None):
c_yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject)
c_no_button = MDRectangleFlatButton(text="No, go back",font_size=dp(18))
c_dialog = MDDialog(text="[b]Caution![/b]\n\nYou are about to import a new Identity key into Sideband. The currently active key will be irreversibly destroyed, and you will loose your LXMF address if you have not already backed up your current Identity key.\n\nAre you sure that you wish to import the key?", buttons=[ c_no_button, c_yes_button ])
def c_dl_no(s):
c_dialog.dismiss()
def c_dl_yes(s):
c_dialog.dismiss()
b32_text = self.keys_screen.ids.key_restore_text.text
try:
key_bytes = base64.b32decode(b32_text)
new_id = RNS.Identity.from_bytes(key_bytes)
if new_id != None:
new_id.to_file(self.app.sideband.identity_path)
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(text="[b]The provided Identity key data was imported[/b]\n\nThe app will now exit. Please restart Sideband to use the new Identity.", buttons=[ yes_button ])
def dl_yes(s):
dialog.dismiss()
self.app.quit_action(sender=self)
yes_button.bind(on_release=dl_yes)
dialog.open()
except Exception as e:
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(text="[b]The provided Identity key data was not valid[/b]\n\nThe error reported by Reticulum was:\n\n[i]"+str(e)+"[/i]\n\nNo Identity was imported into Sideband.", buttons=[ yes_button ])
def dl_yes(s):
dialog.dismiss()
yes_button.bind(on_release=dl_yes)
dialog.open()
c_yes_button.bind(on_release=c_dl_yes)
c_no_button.bind(on_release=c_dl_no)
c_dialog.open()
layout_keys_screen = """
MDScreen:
name: "keys_screen"
BoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Backup & Keys"
anchor_title: "left"
elevation: 0
left_action_items:
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
right_action_items:
[
['close', lambda x: root.app.close_keys_action(self)],
]
ScrollView:
id:keys_scrollview
MDBoxLayout:
orientation: "vertical"
spacing: "24dp"
size_hint_y: None
height: self.minimum_height
padding: [dp(35), dp(35), dp(35), dp(35)]
MDLabel:
id: backup_info
markup: True
text: ""
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
MDRectangleFlatIconButton:
id: keys_backup
icon: "home-export-outline"
text: "Backup Sideband Profile"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.profile_backup_action(self)
MDRectangleFlatIconButton:
id: keys_restore
icon: "home-import-outline"
text: "Restore Sideband Profile"
disabled: True
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.profile_backup_action(self)
MDLabel:
id: keys_info
markup: True
text: ""
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
MDRectangleFlatIconButton:
id: keys_display
icon: "eye"
text: "Display Identity Key"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_display_action(self)
MDRectangleFlatIconButton:
id: keys_copy
icon: "file-key"
text: "Copy Key To Clipboard"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_copy_action(self)
MDRectangleFlatIconButton:
id: keys_share
icon: "upload-lock"
text: "Send Key To Other App"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_share_action(self)
MDBoxLayout:
orientation: "vertical"
# spacing: "24dp"
size_hint_y: None
height: self.minimum_height
padding: [dp(0), dp(12), dp(0), dp(0)]
MDTextField:
id: key_restore_text
hint_text: "Enter base32 key for import"
mode: "rectangle"
# size_hint: [1.0, None]
pos_hint: {"center_x": .5}
MDRectangleFlatIconButton:
id: keys_restore
icon: "download-lock"
text: "Restore Identity From Key"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_restore_action(self)
"""

View file

@ -1053,127 +1053,6 @@ MDScreen:
id: map_layout
"""
layout_keys_screen = """
MDScreen:
name: "keys_screen"
BoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Backup & Keys"
anchor_title: "left"
elevation: 0
left_action_items:
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
right_action_items:
[
['close', lambda x: root.app.close_keys_action(self)],
]
ScrollView:
id:keys_scrollview
MDBoxLayout:
orientation: "vertical"
spacing: "24dp"
size_hint_y: None
height: self.minimum_height
padding: [dp(35), dp(35), dp(35), dp(35)]
MDLabel:
id: backup_info
markup: True
text: ""
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
MDRectangleFlatIconButton:
id: keys_backup
icon: "home-export-outline"
text: "Backup Sideband Profile"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.profile_backup_action(self)
MDRectangleFlatIconButton:
id: keys_restore
icon: "home-import-outline"
text: "Restore Sideband Profile"
disabled: True
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.profile_backup_action(self)
MDLabel:
id: keys_info
markup: True
text: ""
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
MDRectangleFlatIconButton:
id: keys_display
icon: "eye"
text: "Display Identity Key"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_display_action(self)
MDRectangleFlatIconButton:
id: keys_copy
icon: "file-key"
text: "Copy Key To Clipboard"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_copy_action(self)
MDRectangleFlatIconButton:
id: keys_share
icon: "upload-lock"
text: "Send Key To Other App"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_share_action(self)
MDBoxLayout:
orientation: "vertical"
# spacing: "24dp"
size_hint_y: None
height: self.minimum_height
padding: [dp(0), dp(12), dp(0), dp(0)]
MDTextField:
id: key_restore_text
hint_text: "Enter base32 key for import"
mode: "rectangle"
# size_hint: [1.0, None]
pos_hint: {"center_x": .5}
MDRectangleFlatIconButton:
id: keys_restore
icon: "download-lock"
text: "Restore Identity From Key"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.app.identity_restore_action(self)
"""
layout_plugins_screen = """
MDScreen:
name: "plugins_screen"