#!/usr/bin/python3 # *-* coding:utf-8 *-* #*************************************************************************** # This file is part of the CRYPTO BONE # File : cryptobone2 installed in /usr/bin # Version : 2.0 (ALL-IN-ONE) # License : BSD-3-Clause # Date : 29 May 2025 # Contact : Please send enquiries and bug-reports to innovation@senderek.ie # # # Copyright (c) 2015-2025 # Ralf Senderek, Ireland. All rights reserved. (https://senderek.ie) # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. All advertising materials mentioning features or use of this software # must display the following acknowledgement: # This product includes software developed by Ralf Senderek. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. #**************************************************************************** # cryptobone2 is the graphical user interface (GUI) for a secure messaging system # that makes sure a user's message and attachment is always encrypted without burdening the user # with complex message key management. Based on a separate daemon, both ease-of-use # and security are assured by a novel approach to encryption key management. # # While the message keys are secured by a daemon running on the Linux machine, # additional protection can be achieved by using an external device for storing # the encryption keys. This external device can be another Linux computer dedicated # to this task or a Raspberry Pi or any device that can install the CryptoBone software. # # The Crypto Bone secure messaging system automatically encrypts a message with # a strong message key, stored in the cryptobone daemon's data base for secrets. # Except for an initial registration of a message key for a recipient, the user # does not need to remember any of these message keys being in use, as the system # updates keys when messages are sent out or received. # # While only strongly end-to-end encrypted messages and attachments are sent out, # via the safewebdrop transport mechanism, incoming messages that cannot be decrypted # with registered message keys are discarded. # # In order to transfer encrypted messages the system has to be set up with a # working safewebdrop account used for this purpose only. (see https://safewebdrop.com) import os import sys import base64 import time # debugging off DEBUG_DISPLAY = False DEBUG = False #DEBUG_DISPLAY = True #DEBUG = True OS = os.name Version = "2.0" # find out askpass path ASKPASS = "/usr/bin/systemd-ask-password" if os.path.isfile ("/usr/libexec/openssh/gnome-ssh-askpass"): ASKPASS="/usr/libexec/openssh/gnome-ssh-askpass" else: if os.path.isfile ("/usr/lib64/seahorse/seahorse-ssh-askpass"): ASKPASS="/usr/lib64/seahorse/seahorse-ssh-askpass" else: if os.path.isfile("/usr/lib/openssh/gnome-ssh-askpass"): ASKPASS = "/usr/lib/openssh/gnome-ssh-askpass" GUI = False try: from tkinter import * from tkinter import font as tkFont GUI = True except: print ("The GUI cannot be run. \nMaybe Tkinter is not installed?") sys.exit(4) # STATUS Allinone Finished Active # 0 0 0 External device is not enabled # 0 0 1 N/A # 0 1 0 External device is offline # 0 1 1 External device works correctly # 1 0 0 Allinone has no database and no running daemon # 1 0 1 N/A # 1 1 0 Allinone has a database but the daemon is not running # 1 1 1 Allinone works correctly Allinone = True Finished = False Active = False TRANSPORT="WEBDROP" Name="none" StatusCount=3 MaxCount=0 Masterkey = False MODE = "read" MessageID = "None" Recipient = "None" Attachment = "None" BONEIP="" # GUI result of yes-no dialog REPLY = False CONTENT = "" # GUI constants BACKGROUND = "#ddd" GRAY = "#ccc" LABEL = "#eee" GREEN = "#cfc" DEBUGCOLOR = "#aaa" TAB = "#ffc809" INFO = "#ddd" ERRORCOLOR = "#800" OKCOLOR = "#080" INFOCOLOR = "#008" KEYCOLOR = "#efe" YESCOLOR = "#afa" NOCOLOR = "#faa" #------------------------------------------------------------# def unix (command) : if OS == "posix": Pipe = os.popen(command, "r") Result = Pipe.read() Pipe.close() return Result #------------------------------------------------------------# def askyesno(Header, Msg): global REPLY # create a pop-up Toplevel window to receive a choice in REPLY def returnyes(): global REPLY REPLY = True dialog.destroy() return REPLY def returnno(): global REPLY REPLY = False dialog.destroy() return REPLY dialog = Toplevel() dialog.title (Header) L = Label(dialog, text=Msg, font=Bold ) ReplyFrame = Frame(dialog, bg=BACKGROUND, pady=5) Yes = Button(ReplyFrame, text= " Yes ", font=BFont, width=16, bg=YESCOLOR, command=returnyes) No = Button(ReplyFrame, text= " No ", font=BFont, width=16, bg=NOCOLOR, command=returnno) L.pack(padx=30, pady=10) Yes.pack(padx=8, side=LEFT, pady=10) No.pack(padx=8, side=LEFT, pady=10) ReplyFrame.pack() dialog.transient(Window) try: dialog.grab_set() except: pass dialog.wait_window() #------------------------------------------------------------# def askinput(Header, Msg, Value): global CONTENT, REPLY # create a pop-up Toplevel window to receive a string in CONTENT and REPLY=True def returnyes(): global REPLY, CONTENT CONTENT = Input.get() REPLY = True dialog.destroy() return REPLY def returnno(): global REPLY, CONTENT CONTENT = Value REPLY = False dialog.destroy() return REPLY dialog = Toplevel() dialog.title (Header) L = Label(dialog, text=Msg, font=Bold ) ReplyFrame = Frame(dialog, bg=BACKGROUND, pady=5) Input = Entry(master=ReplyFrame, width=30, font=Bold) Input.insert(0,Value) Yes = Button(ReplyFrame, text= " Yes ", font=BFont, width=16, bg=YESCOLOR, command=returnyes) No = Button(ReplyFrame, text= " No ", font=BFont, width=16, bg=NOCOLOR, command=returnno) L.pack(padx=30, pady=10) Input.pack(padx=10, pady=10) Yes.pack(padx=8, side=LEFT, pady=10) No.pack(padx=8, side=LEFT, pady=10) ReplyFrame.pack() dialog.transient(Window) try: dialog.grab_set() except: pass dialog.wait_window() #------------------------------------------------------------# def clean (): Text.delete("0.0","end") #------------------------------------------------------------# def clear_error(): ErrorFrame.configure(bg=INFO) ErrorLabel.configure(bg=INFO) ErrorLabel.configure(text="") Window.update_idletasks() #------------------------------------------------------------# def is_active(): Result = send_command("STATUS") if "active" in Result: return True return False #------------------------------------------------------------# def format_error(e): E = "" i = 0 for C in e : if C == '\n': E = E + C i = 0 else: if i < 67 : E = E + C i += 1 else: E = E + C +'\n' i = 0 return E #------------------------------------------------------------# def check_webdrop(user, server): Result = send_command("CHECKWEBDROP OUT " + user + " " + server) if (user and ("TESTMAIL sent" in Result)) : message = "Test message is sent out successfully.\n" + "Please be patient, as receiving the message may take a minute!" print(message) InfoPanel.configure(fg="darkgreen") InfoPanel.configure( text=format_error(message) ) Window.update_idletasks() else: print("Test message cannot be sent out !") InfoPanel.configure(fg="red") InfoPanel.configure( text=format_error(Result) ) Window.update_idletasks() unix("sleep 3") Result = send_command("CHECKWEBDROP IN") if ("unencrypted test email from root" in Result) : print("Test message received successfully") InfoPanel.configure(fg="darkgreen") InfoPanel.configure( text=format_error("Test message received successfully") ) Window.update_idletasks() else: print("No webdrop received !") InfoPanel.configure(fg="red") InfoPanel.configure( text=format_error(Result) ) Window.update_idletasks() print () #------------------------------------------------------------# def get_initial_status(): global Allinone, Finished, Active, BONEIP, ID, Name Message="" RESULT = send_command("GETALLINONE") print("GETALLINONE -> "+RESULT) if ("EXTERNAL" in RESULT): Allinone = False if ( "unfinished" in RESULT ): Active = False StatusLabel.configure(text="UNFINISHED") StatusLabel.configure(bg="orange") Message = "Please Copy External Secrets in SETUP" if ( "waiting" in RESULT ): print("Master key is not yet uploaded.") Active = False StatusLabel.configure(text="waiting ") StatusLabel.configure(bg="orange") BONEIP = RESULT[19:] IPLabel.configure(text=BONEIP) Message = "Please REBOOT the computer to upload the master key" elif ("active" in RESULT) : # The flag UPLOADED has been set on the client machine # tell if the external device is waiting or active by sending the STATUS command EXTSTATUS = send_command("STATUS") if "waiting" in EXTSTATUS : Active = False StatusLabel.configure(text="waiting") StatusLabel.configure(bg="orange") Message = "Please REBOOT the computer to upload the master key" if "active" in EXTSTATUS: Active = True StatusLabel.configure(text="active") StatusLabel.configure(bg="lightgreen") BONEIP = RESULT[19:] IPLabel.configure(text=BONEIP) if Name == "none": Result = send_command("SETUP WID") if not "failed" in Result: ID.configure(text=Result[:-1]) Name=Result[:-1] elif ("offline" in RESULT) : Active = False StatusLabel.configure(text="OFFLINE") StatusLabel.configure(bg="yellow") BONEIP = RESULT[19:] IPLabel.configure(text=BONEIP) Message = "Please check the network on your external device." else: IPLabel.configure(text="EXTERNAL CRYPTO BONE") Finished = False Active = False if ("0.0.0.0" in RESULT): IPLabel.configure(text="0.0.0.0") Message = "Please enter the IP address of your Cryptobone in SETUP." elif ("ALLINONE" in RESULT): Allinone = True IPLabel.configure(text="ALL-IN-ONE") if ("active" in RESULT): ###Message = "Reading messages and attachments, please be patient!" Finished = True Active = True StatusLabel.configure(text="active") StatusLabel.configure(bg="lightgreen") if Name == "none": Result = send_command("SETUP WID") if not "failed" in Result: ID.configure(text=Result[:-1]) Name=Result[:-1] elif ("nodatabase" in RESULT): Message = "Please REBOOT!" Finished = False Active = False StatusLabel.configure(text="inactive") StatusLabel.configure(bg="yellow") if Name == "none": Result = send_command("SETUP WID") if not "failed" in Result: ID.configure(text=Result[:-1]) Name=Result[:-1] Text.insert("end",Message) showinfo("",Message) ### print("Status: ",Allinone,Finished,Active) #------------------------------------------------------------# def contact(): import subprocess Content = InputField.get().split(" ") if InputField.get() == '': Content = ["none"] args = ["sudo", "-A", "/usr/lib/cryptobone/cbcontrol"] for Word in Content: args.append(Word) ENV = os.environ.copy() ENV['SUDO_ASKPASS'] = ASKPASS p = subprocess.Popen(args, bufsize=-1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False, env=ENV) if DEBUG_DISPLAY: print (InputField.get()) (out,err) = p.communicate() if err != None: print ("error:",str(err.decode('utf-8'))) if DEBUG_DISPLAY: Text.insert("end",str(out.decode('utf-8'))) Text.insert("end", "\n") #------------------------------------------------------------# def send_command(Command): import subprocess # do NOT sanitize input Content = Command.split(" ") args = ["sudo", "-A", "/usr/lib/cryptobone/cbcontrol"] for Word in Content: args.append(Word) ENV = os.environ.copy() ENV['SUDO_ASKPASS'] = ASKPASS p = subprocess.Popen(args, bufsize=-1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False, env=ENV) if DEBUG_DISPLAY: print (Command) (out,err) = p.communicate() if err != None: print ("error:",str(err.decode('utf-8'))) if DEBUG_DISPLAY: print (out.decode('utf-8')) Text.insert("end",str(out.decode('utf-8'))) Text.insert("end", "\n") return str(out.decode('utf-8')) #------------------------------------------------------------# def set_select_box(): List = [] if MODE == "write" or MODE == "key" : List = send_command("KEY RECIPIENTLIST").split("\n") if MODE == "read": List = send_command("READ MESSAGELIST").split("\n") if MODE == "setup": List = send_command("READ WEBDROPLIST").split("\n") SBox.delete(0,20) Count = 0 SBox.delete(0,END) for Item in List: if Item : if Item != "ssh key not available" : SBox.insert(Count, str(Item)) Count += 1 #------------------------------------------------------------# def set_attach_box(): List = [] if MODE == "read": List = send_command("ATTACHMENT LIST").split("\n") AttachSBox.delete(0,20) Count = 0 AttachSBox.delete(0,END) for Item in List: if Item : if Item != "ssh key not available" : AttachSBox.insert(Count, str(Item)) Count += 1 #------------------------------------------------------------# def set_read(): global MODE, MessageID MODE = "read" Mode.configure(text="READ") L.configure(text="Messages") Key1.pack_forget() Setup1.pack_forget() AttachAdd.pack_forget() ConsoleButton.pack_forget() clean() TextFrame.grid(row=2, column=0) ControlFrame.grid(row=3, column=0) Button1.configure(text="Reply") Button2.configure(text="Check for new") Button3.configure(text="Destroy") Button1.pack(padx=8, side=LEFT) Button2.pack(padx=8, side=LEFT) Button3.pack(padx=8, side=RIGHT) if Active: SelectBox.pack() set_select_box() AttachBox.pack() set_attach_box() else: SelectBox.pack_forget() AttachBox.pack_forget() MessageID = "None" L1.configure(text=MessageID) L2.configure(text="NEW") RightFrame.grid(row=1, column=1, sticky=N, rowspan=2) clear_error() #------------------------------------------------------------# def set_write(): global MODE, Recipient MODE = "write" Mode.configure(text="WRITE") L.configure(text="Recipients") ControlFrame.pack_forget() Key1.pack_forget() Setup1.pack_forget() ConsoleButton.pack_forget() AttachAdd.pack_forget() AttachBox.pack_forget() clean() TextFrame.grid(row=2, column=0) ControlFrame.grid(row=3, column=0) Button1.configure(text="Send Message") Button2.configure(text="Clear") Button1.pack(padx=8, side=LEFT) Button2.pack(padx=8, side=LEFT) Button3.pack_forget() if Active: set_select_box() SelectBox.pack() clear_attachment() AttachAdd.pack() Recipient = "None" L1.configure(text=Recipient) L2.configure(text="NEW") RightFrame.grid(row=1, column=1, sticky=N, rowspan=2) clear_error() #------------------------------------------------------------# def set_reply(): global MODE MODE = "write" Mode.configure(text="WRITE") ControlFrame.pack_forget() Key1.pack_forget() Setup1.pack_forget() ConsoleButton.pack_forget() SelectBox.pack_forget() AttachBox.pack_forget() clean() TextFrame.grid(row=2, column=0) ControlFrame.grid(row=3, column=0) Button1.configure(text="Send Message") Button2.configure(text="Clear") Button3.pack_forget() if Active: clear_attachment() AttachAdd.pack() L1.configure(text="Recipient") L2.configure(text="REPLY") clear_error() RightFrame.grid(row=1, column=1, sticky=N, rowspan=2) ####clear_attachment() #------------------------------------------------------------# def set_key(): global MODE MODE = "key" Mode.configure(text="KEYS") L.configure(text="Registered Contacts") SelectBox.pack_forget() AttachAdd.pack_forget() TextFrame.grid_forget() ControlFrame.grid_forget() DebugFrame.pack_forget() Setup1.pack_forget() ConsoleButton.pack_forget() AttachBox.pack_forget() Key1.pack() LeftFrame.grid(row=2, column=0, sticky=N, padx=10) if Active: set_select_box() SelectBox.pack() L1.configure(text="") L2.configure(text="") RightFrame.grid(row=1, column=1, sticky=N, rowspan=2) clear_error() #------------------------------------------------------------# def set_setup(): global MODE, Allinone MODE = "setup" Mode.configure(text="SETUP") SelectBox.pack_forget() AttachAdd.pack_forget() TextFrame.grid_forget() ControlFrame.grid_forget() Key1.pack_forget() AttachBox.pack_forget() Setup1.pack() LeftFrame.grid(row=2, column=0, sticky=N, padx=10) ControlFrame.grid(row=3, column=0) Linkstatus = send_command("GETALLINONE") if "ALLINONE" in Linkstatus : Button1.configure(text="Use EXTERNAL") Button1.pack(padx=8, side=LEFT) Button2.pack_forget() Button3.pack_forget() else: Button3.pack_forget() Button1.configure(text="Use ALL-IN-ONE") Button2.configure(text="Copy EXT Secrets") Button3.configure(text=" Set EXT-BONEIP") Button1.pack(side=LEFT, padx=8) Button2.pack(side=LEFT, padx=8) Button3.pack(side=LEFT, padx=8) L1.configure(text="") L2.configure(text="") if Active: set_select_box() L.configure(text="INCOMING Safe Webdrops") SelectBox.pack() ConsoleButton.pack(side=BOTTOM, pady=100) RightFrame.grid(row=1, column=1, sticky=N, rowspan=2) clear_error() #------------------------------------------------------------# def copy_external_secrets(): BootDir = "/dev/shm/BOOT" RES=unix("ls -a " + BootDir + " 2>/dev/null") if BootDir and RES: # the RAMdisk exists, see what is in it RES=unix("ls "+BootDir+"/master.key 2>/dev/null") if RES: print("RES = "+ RES) # this device holds a master key print("FOUND master.key") print ("Copying external secrets from "+BootDir+" into the All-IN-ONE database.") askyesno("Moving Keys from RAMDISK", "Copying external secrets from "+BootDir+" into the All-IN-ONE database.\n\nProceed?") if REPLY: askyesno("Moving Keys from RAMDISK","You will now replace the keys that are used to access your EXTERNAL Crypto Bone.\n\nIf your EXTERNAL Crypto Bone is already working, you will DESTROY\n the keys used to access this working EXTERNAL Crypto Bone\n by replacing the old keys with new keys from the RAMDISK.\n\nOnce you have replaced the external keys, this action\n cannot be undone.\n\n You will continue with a new EXTERNAL Crypto Bone, if you proceed here.\n\nDo your really want to do that?\n\nThink twice.") if REPLY: RES = send_command("COPY_EXTERNAL_SECRETS "+BootDir) if "success" in RES : showinfo("Moving Keys from RAMDISK","Your Crypto Bone keys have been moved successfully to your computer.\nYou need to reboot this main computer in order to activate the new keys.") elif "failed" in RES : showinfo("Moving Keys from RAMDISK","The secrets in your RAMDISK have not been transferred completely. Please try again.") elif "nokeys" in RES : showinfo("Moving Keys from RAMDISK","There are no keys in " + BootDir) else: showinfo("Moving Secrets from RAMDISK","Cannot locate the external master.key in " + BootDir) return "no master.key" return "SUCCESS" else: showinfo("Moving Secrets from RAMDISK","There is no RAMDISK "+ BootDir + " where the external masterkey must be stored" ) return "no RAMDISK" return "failed" #------------------------------------------------------------# def action1(): global Recipient, MessageID, Allinone, Name, StatusCount if MODE == "read": # reply to the selected message MESSAGE = Text.get("0.0","end").split("\n") set_reply() Text.insert("end","\n\n\n") for Line in MESSAGE: if Line: Text.insert("end", "> "+Line+"\n") if len(MessageID) > 4 : while MessageID[len(MessageID)-1] != '.': MessageID = MessageID[:-1] Recipient = MessageID[:-1] L1.configure(text=Recipient) L2.configure(text="REPLY") elif MODE == "write": MESSAGE = Text.get("0.0","end") # do NOT sanitize message, all characters allowed ENCODEDMESSAGE = MESSAGE.encode('utf-8') MESSAGE = base64.b64encode(ENCODEDMESSAGE).decode('utf-8') if Recipient != "None" and Recipient != "Recipient": if Attachment == "None": Result = send_command("WEBDROP "+Recipient+" "+MESSAGE+ " none") print("GUI " +Result) if ("WEBDROP MESSAGE SAVED" in Result) : Text.insert("end", "\n\n"+Result) showsuccess("Sending Message Out","Message is sent out successfully") else: showinfo("Sending Message Out","Message is not sent out!") else: # send an attachment with the message Result = send_command("WEBDROP "+Recipient+" " + MESSAGE + " " + Attachment) print (Result) print (Attachment) if ("WEBDROP MESSAGE+ATTACHMENT SAVED" in Result) : Text.insert("end", "\n\n"+Result) showsuccess("Sending Message Out","Message and attachment are sent out successfully") else: if "Too Large" in Result: Text.insert("end", "\n\n"+Result) showerror("Sending Message Out","The attachment is too large and is not sent with the message!") elif "ATTACHMENT EXCEEDS LIMIT" in Result: Text.insert("end", "\n\n"+Result) showerror("Sending Message Out","The attachment exceeds the limit the administrator has set on the server!") else: showinfo("Missing Information","Please select a recipient from the menu before sending the message") elif MODE == "key": print ("action one key not implemented") elif MODE == "setup": if not Allinone: Allinone = True Finished = True Button1.configure(text="Use EXTERNAL") IPLabel.configure(text="ALL-IN-ONE") send_command("USEALLINONE") else: Allinone = False Button1.configure(text="Use ALL-IN-ONE") IPLabel.configure(text="EXTERNAL CRYPTO BONE") send_command("USECRYPTOBONE") StatusCount = 100 Name = "none" ID.configure(text="") time.sleep(2) get_initial_status() #------------------------------------------------------------# def action2(): global Recipient, MessageID if MODE == "read": # check for new messages send_command("FETCH") set_attach_box() set_select_box() elif MODE == "write": # forget message clean() clear_attachment() MessageID = "none" L1.configure(text=Recipient) L2.configure(text="NEW") elif MODE == "key": print ("action two key not implemented") elif MODE == "setup": print ("NOW COPY EXTERNAL SECRETS") copy_external_secrets() #------------------------------------------------------------# def action3(): global BONEIP if MODE == "read": select() # destroy selected message if MessageID : askyesno("Destroy Message", "Do you really want to destroy this message: "+MessageID) if REPLY: send_command("READ DESTROY "+MessageID) clean() set_select_box() elif MODE == "setup": # update EXTERNAL BONEIP address askinput("Update External Bone IP Address", "Do you want to update the current IP address\n for your EXTERNAL Crypto Bone? " , str(BONEIP) ) if REPLY: if CONTENT : print ("UPDATE BONEIP = " + CONTENT) send_command("BONEIP " + CONTENT) IPLabel.configure(text=CONTENT) #------------------------------------------------------------# def select(): global MessageID, Recipient index = 0 if len(SBox.curselection()) > 0: index = SBox.curselection()[0] Selection = SBox.get(index) L1.configure(text=Selection) if MODE == "read": MessageID = Selection if MessageID: MESSAGE = send_command("READ MESSAGE "+MessageID) if MESSAGE: clean() try: Plaintext = base64.b64decode(MESSAGE).decode('utf-8') Text.insert("end",Plaintext) except: pass elif MODE == "write": Recipient = Selection elif MODE == "setup": Setup1.pack_forget() Button1.pack_forget() Button2.pack_forget() Button3.pack_forget() LeftFrame.grid_forget() TextFrame.grid(row=2, column=0) Recipient = Selection MessageID = Selection MESSAGE = send_command("READ WEBDROP " + MessageID) if MESSAGE: clean() Plaintext = base64.b64decode(MESSAGE) Text.insert("end",Plaintext) #------------------------------------------------------------# def attach() : global Attachment empty = False FileName = AttachFilename.get() if FileName == "": empty = True FileName = os.path.expanduser("~/" + FileName) if os.path.isfile( FileName ) : AttachFilename.configure(bg="lightgreen") showinfo( "", FileName + " will be attached to your message." ) Attachment = FileName else: Attachment = "None" AttachFilename.configure(bg="white") if (not empty) : showerror( "", FileName + " : does not exist" ) #------------------------------------------------------------# def clear_attachment(): global Attachment Attachment = "None" AttachFilename.delete(0,END) AttachFilename.configure(bg="white") #------------------------------------------------------------# def attachment_copy(): index = 0 if len(AttachSBox.curselection()) > 0: index = AttachSBox.curselection()[0] Filename = AttachSBox.get(index) if MODE == "read" and Filename: askyesno("","Do you want to copy " + Filename + " into your home directory?\nExisting files in $HOME/.safewebdrop will be overwritten!") if REPLY : RES = send_command( "ATTACHMENT COPY " + Filename ) if ("SAVED" in RES) : showsuccess("", Filename + " has been copied into the directory .safewebdrop") else: showerror("", Filename + " cannot be copied into the directory .safewebdrop") set_attach_box() #------------------------------------------------------------# def message_copy(): index = 0 if len(SBox.curselection()) > 0: index = SBox.curselection()[0] Messagename = SBox.get(index) if MODE == "read" and Messagename: askyesno("","Do you want to copy " + Messagename + " into your home directory?\nThis may reduce the protection of your clear text message.\nExisting files in $HOME/.safewebdrop will be overwritten!") if REPLY : RES = send_command( "MESSAGE COPY " + Messagename ) print(RES) if ("SAVED" in RES) : showsuccess("", Messagename + " has been copied into the directory .safewebdrop") else: showerror("", Messagename + " cannot be copied into the directory .safewebdrop") set_attach_box() #------------------------------------------------------------# def attachment_delete(): index = 0 if len(AttachSBox.curselection()) > 0: index = AttachSBox.curselection()[0] Filename = AttachSBox.get(index) if MODE == "read" and Filename: askyesno("","Do you want to delete " + Filename + "? You can copy this attachment before you destroy it") if REPLY : RES = send_command( "ATTACHMENT DELETE " + Filename ) if ("DELETED" in RES) : showsuccess("", Filename + " has been deleted.") set_attach_box() #------------------------------------------------------------# def is_message_key(NewKey): if NewKey == "none": return False if len(NewKey) > 19: return True return False #------------------------------------------------------------# def register(): if not is_active(): showerror("", "Your Crypto Bone is not active! ") return "failed" K = InitKey.get() ID = Address.get() if (K == "none") or (K == "delete") or (K == "destroy"): K = "00000000000000000000" if "@" in ID : showerror('Contact Registration', "This is an EMAIL ID, please use % instead of @") elif is_message_key(K) : # TODO !!! : check if email address is stored already List = [] List = send_command("KEY RECIPIENTLIST").split("\n") if ID in List: askyesno("Update A Key?","The key for "+ID+" is already in use.\n\n If you wish to reset this key, you can set a new initial secret.\nYour contact needs to do the same to continue the conversation.\n\nIf you wish to delete this contact, then you can register the key \"none\"\n\nDo you wish to proceed with the key update?\n") if not REPLY: # abort return "nothing done" # KEY USE registers without checking RES = send_command("KEY USE "+ ID +" "+K) if "failed" in RES : showerror('Contact Registration', RES[8:]) if "deleted" in RES : showsuccess('Deleted Contact', RES) if "success" in RES : showsuccess('Contact Registration', ID + "\nis now registered and can be used.") unix("sleep 3") set_key() else: showerror('Contact Registration', "The message key is too short.") #------------------------------------------------------------# def show_webdrop_setup(): Result = send_command("SETUP WEBDROP") if Result: List = Result.split("\n") for Line in List: Secretvalue = Line.split(":") if Secretvalue[0] == "webdropserver": Hostname.delete(0,END) Hostname.insert(0,Secretvalue[1]) if Secretvalue[0] == "webdropuser": Username.delete(0,END) Username.insert(0,Secretvalue[1]) # never show the webdropsecret CODE=send_command("SETUP REGISTRATION") RegistrationCode.delete(0,END) RegistrationCode.insert(0,CODE[:-1]) InfoPanel.configure(text="") #------------------------------------------------------------# def webdrop_setup(): H = Hostname.get() U = Username.get() if H : send_command("SETUP WEBDROPSERVER "+H) if U : send_command("SETUP WEBDROPUSER "+U) InfoPanel.configure( text="" ) Window.update_idletasks() check_webdrop(U,H) #------------------------------------------------------------# def register_webdrop_account(): CODE=send_command("SETUP REGISTRATION") RegistrationCode.delete(0,END) RegistrationCode.insert(0,CODE[:-1]) print ("Registration") askyesno("New Safe Webdrop Account Registration", "Do you really want to register a new Safe Webdrop account?\n This will destroy any Safe Webdrop account you have registered before!\n\nYou need to enter your Safe Webdrop ID and the Safe Webdrop server name in the setup above.\nIf your registration goes through you will find a different registation code below.\nYou must show the new registration code to your Safe Webdrop administrator in order to enable your new account.") if REPLY: Result = send_command("SETUP CLEARREG") show_webdrop_setup() CODE=send_command("SETUP REGISTRATION") RegistrationCode.delete(0,END) RegistrationCode.insert(0,CODE[:-1]) askyesno("New Safe Webdrop Account Registration", "Is the setup information (webdrop server and ID) korrect?") if REPLY: Result = send_command("SETUP REGISTER") CODE=send_command("SETUP REGISTRATION") RegistrationCode.delete(0,END) RegistrationCode.insert(0,CODE[:-1]) #------------------------------------------------------------# def terminate_GUI(): Window.destroy() #------------------------------------------------------------# def toggle_console(): global DEBUG, DEBUG_DISPLAY, DebugFrame DEBUG = not DEBUG DEBUG_DISPLAY = not DEBUG_DISPLAY if DEBUG: if MODE == "read" or MODE == "write" or MODE == "setup" : DebugFrame.grid(row=5, column=0, columnspan=2) else: DebugFrame.grid_forget() #------------------------------------------------------------# def readuser(): USER = InputField.get() clear_error() # check if this is a valid user if USER and (USER in unix("grep \"^"+USER+":\" /etc/passwd")): RET=unix("/usr/bin/pkexec /usr/bin/activate-cryptobone " + USER) print (RET) if RET == "success": showsuccess( "SUCCESS","Your Crypto Bone can now be used.\nPlease reboot your computer to create the secrets that are missing.") unix("sleep 10") sys.exit(0) if RET == "initialised": showerror( "ERROR","This Crypto Bone is already set up.\nYour Crypto Bone Daemon is not enabled.\nTry: systemctl enable cryptoboned") unix("sleep 10") sys.exit(1) if RET == "nosuchuser": showerror( "ERROR","No such user. Please try again.") if RET == "noparameter": showerror( "ERROR","You must specify a user name.") else: showerror( "ERROR","No such user. Please try again.") #------------------------------------------------------------# def showinfo(Title, Text): ErrorFrame.configure(bg=INFO) ErrorLabel.configure(text=Text) ErrorLabel.configure(fg=INFOCOLOR, bg=INFO) Window.update_idletasks() #------------------------------------------------------------# def showsuccess(Title, Text): ErrorFrame.configure(bg=INFO) ErrorLabel.configure(text=Text) ErrorLabel.configure(fg=OKCOLOR, bg=INFO) Window.update_idletasks() #------------------------------------------------------------# def showerror(Title, Text): ErrorFrame.configure(bg=INFO) ErrorLabel.configure(text="ERROR: "+Text) ErrorLabel.configure(fg=ERRORCOLOR, bg=INFO) Window.update_idletasks() #------------------------------------------------------------# def OpenHelpUrl(): import webbrowser webbrowser.open_new("https://crypto-bone.com/help2") #------------------------------------------------------------# def OpenHomeUrl(): import webbrowser webbrowser.open_new("https://crypto-bone.com") #------------------------------------------------------------# def HelpPage(): import webbrowser if MODE == "read" : webbrowser.open_new("https://crypto-bone.com/help2/read") elif MODE == "write" : webbrowser.open_new("https://crypto-bone.com/help2/write") elif MODE == "key" : webbrowser.open_new("https://crypto-bone.com/help2/keys") elif MODE == "setup" : webbrowser.open_new("https://crypto-bone.com/help2/setup") else : webbrowser.open_new("https://crypto-bone.com/help2") ############################################################### # Main ############################################################### if GUI: Window = Tk() # this work-around fixes a bug introduced with fractional scaling in Gnome, # as the system scaling factor is not active on first use after login # and causes the window being too small Window.tk.call("tk","scaling",2.5) Window.title("Crypto Bone Control 2.0") MainFrame = Frame(Window, bg=BACKGROUND) DebugFrame = Frame(MainFrame, bg=DEBUGCOLOR, pady=5) ErrorFrame = Frame(MainFrame, bg=BACKGROUND, pady=10) HeadFrame = Frame(MainFrame, bg=BACKGROUND, padx=0, pady=0) StatusFrame = Frame(HeadFrame, bg=BACKGROUND, padx=0, pady=5) ModeFrame = Frame(HeadFrame, bg=BACKGROUND, padx=0, pady=5) LeftFrame = Frame(MainFrame, bg=BACKGROUND, padx=5, pady=2) TopFrame = Frame(MainFrame, bg=BACKGROUND, padx=10, pady=10) TextFrame = Frame(MainFrame, bg=BACKGROUND) ControlFrame = Frame(MainFrame, bg=BACKGROUND, pady=5) RightFrame = Frame(MainFrame, bg=BACKGROUND, padx=15, pady=2) DisplayFrame = Frame(LeftFrame, bg=BACKGROUND, pady=2) Big = tkFont.Font(family="utopia", size=16) Title = tkFont.Font(family="utopia", size=12) Info = tkFont.Font(family="arial", size=10, slant="italic") Bold = tkFont.Font(family="arial", size=11, weight="bold") BFont = tkFont.Font(family="utopia", size=12, weight="normal") TextFont = tkFont.Font(family="arial", size=11, weight="normal") MonoFont = tkFont.Font(family="monospace", size=11, weight="normal") # ERROR frame ErrorLabel = Label(ErrorFrame, text="checking the status ...", font=Bold, bg=BACKGROUND) ErrorLabel.pack(side=LEFT, padx=10) # DEBUG frame ConsoleLabel = Label(DebugFrame, text="Console", font=Bold, width=16, bg=DEBUGCOLOR) CleanButton = Button(master=DebugFrame, text="Clean", command=clean) InputField = Entry(master=DebugFrame, font=Bold, width=56) ExecButton = Button(master=DebugFrame, text="Send Command", command=contact) ConsoleLabel2 = Label(DebugFrame, text="", font=Bold, width=10, bg=DEBUGCOLOR) ConsoleLabel.pack(side=LEFT, pady=5) CleanButton.pack(side=LEFT) InputField.pack(padx=20,side=LEFT) ExecButton.pack(side=LEFT) ConsoleLabel2.pack(side=LEFT, padx=0) TransportLabel = Label(MainFrame, text=" Transport: SAFE WEBDROP ", font=Bold, height=1, bg=BACKGROUND) # Logo image CBS_btn_img = PhotoImage(file='/usr/share/icons/default/logo-cryptobone-safewebdrop.png') CBSButton = Button(HeadFrame, image=CBS_btn_img, bg=GRAY, width=180, height=150) CBSButton.bind("",lambda c: OpenHomeUrl()) CBSButton.bind("", lambda c: CBSButton.configure(bg="#eee")) CBSButton.bind("", lambda c: CBSButton.configure(bg=BACKGROUND)) # Status BoneLabel = Label(StatusFrame, text="CRYPTO BONE " + Version + " ", font=Big, width=18, bg=BACKGROUND) BoneLabel.bind("",lambda e: OpenHelpUrl()) BoneLabel.bind("", lambda e: BoneLabel.configure(bg=BACKGROUND)) BoneLabel.bind("", lambda e: BoneLabel.configure(bg=BACKGROUND)) IPLabel = Label(StatusFrame, text="", width=22, height=1, font=Bold, bg="#ccc") StatusLabel = Label(StatusFrame, text="cut off", font=Bold, width=12, height=1, bg=TAB) ID = Label(MainFrame, text="", font=Bold, width=30, height=1, bg="#ccc") CBSButton.pack(padx=10, side=LEFT, pady=0) BoneLabel.pack(padx=30, side=LEFT) IPLabel.pack(side=LEFT) StatusLabel.pack(side=LEFT) # Labels in TopFrame Mode = Label(TopFrame, text="READ", font=Bold, bg=TAB, width=11) L1 = Label(TopFrame, text="None", font=Bold, width=55, bg=LABEL) L2 = Label(TopFrame, text="None", font=Bold, width=10, bg=LABEL) Mode.pack(side=LEFT, padx=0) L1.pack(side=LEFT, padx=0) L2.pack(side=LEFT, padx=0) # TextFrame Scroll = Scrollbar(TextFrame) Text = Text(TextFrame, width=75, height=23, font=TextFont, borderwidth=8, relief=FLAT, yscrollcommand = Scroll.set) Scroll.pack( side = RIGHT, fill=Y ) Text.pack() Scroll.config( command = Text.yview ) # ModeFrame ReadButton = Button(master=ModeFrame, text="READ", bg=GRAY, font=BFont, command=set_read) WriteButton = Button(master=ModeFrame, text="WRITE", bg=GRAY, font=BFont, command=set_write) KeyButton = Button(master=ModeFrame, text="KEYS", bg=GRAY, font=BFont, command=set_key) SetupButton = Button(master=ModeFrame, text="SETUP", bg=GRAY, font=BFont, command=set_setup) Help_btn_img = PhotoImage(file='/usr/share/icons/default/question-mark.png') HelpButton = Button(master=ModeFrame, image=Help_btn_img, bg=GRAY, width=60, height=60, command=HelpPage) ReadButton.pack(padx=20, side=LEFT, pady=5) WriteButton.pack(padx=20, side=LEFT, pady=5) KeyButton.pack(padx=20, side=LEFT, pady=5) SetupButton.pack(padx=20, side=LEFT, pady=5) HelpButton.pack(padx=20, side=LEFT, pady=5) # ControlFrame Button1 = Button(master=ControlFrame, text="", bg=GRAY, font=BFont, width=16, command=action1) Button2 = Button(master=ControlFrame, text="", bg=GRAY, font=BFont, width=16, command=action2) Button3 = Button(master=ControlFrame, text="", bg=GRAY, font=BFont, width=16, command=action3) Button1.pack(padx=8, pady=5, side=LEFT) Button2.pack(padx=8, pady=5, side=LEFT) Button3.pack(padx=8, pady=5, side=LEFT) # RightFrame ### Select Box SelectBox = Frame(RightFrame, bg=BACKGROUND) L = Label(SelectBox, text="", bg=TAB,font=Bold, width=30) L.pack() BoxFrame = Frame(master=SelectBox) XScroll = Scrollbar(BoxFrame, orient=HORIZONTAL) SBox = Listbox(BoxFrame, height=10, width=29, font=TextFont, borderwidth=5, relief=FLAT, xscrollcommand = XScroll.set) XScroll.config(command=SBox.xview) XScroll.pack(side=BOTTOM, fill=X ) SBox.pack() BoxFrame.pack(pady=10) SelectButton = Button(master=SelectBox, text="Select", bg=GRAY, font=BFont, command=select) SBox.bind('', lambda e: message_copy()) SelectButton.pack(pady=0) # make space FillRight = Label(SelectBox, text=" ", height=1, width=1, bg=BACKGROUND) FillRight.pack(pady=5) ### Attachment Box AttachBox = Frame(RightFrame, bg=BACKGROUND) AttachBoxFrame = Frame(master=AttachBox) LAttach = Label(AttachBoxFrame, text="Attachments", bg=TAB, font=Bold, width=30) AttachXScroll = Scrollbar(AttachBoxFrame, orient=HORIZONTAL) AttachSBox = Listbox(AttachBoxFrame, height=4, width=29, font=TextFont, borderwidth=5, relief=FLAT, xscrollcommand = XScroll.set) AttachSBox.bind('', lambda e: attachment_copy()) AttachXScroll.config(command=AttachSBox.yview) AttachXScroll.pack(side=BOTTOM, fill=X ) LAttach.pack() AttachBoxFrame.pack(pady=10) AttachSBox.pack() AttachSelectButton = Button(master=AttachBox, text="Delete", bg=GRAY, font=BFont, command=attachment_delete) AttachSelectButton.pack(pady=0) ### Attachment Select Box AttachAdd = Frame(RightFrame, bg=BACKGROUND) AttachAddInfo = Label(AttachAdd, text="Send this file in your \nhome directory securely:", height=2, fg="black", font=Bold, width=30, bg=BACKGROUND) AttachAddInfo.pack() AttachLabel = Label(AttachAdd, text="Attachment", bg=TAB, font=Bold, width=30) AttachLabel.pack(pady=5) AttachFilename = Entry(AttachAdd, font=Bold, width=30) AttachFilename.pack() AttachButton = Button(master=AttachAdd, text="Add File", bg=GRAY, font=BFont, command=attach) AttachButton.pack(pady=10) if Active: SelectBox.pack() AttachBox.pack() # Key Management Key1 = Frame(LeftFrame, height=110, pady=10, bg=BACKGROUND) KL1 = Label(Key1, text="Register a New Contact", font=Title, bg=TAB, width=30) # first Key frame SubKL1 = Label(Key1, text="S-Webdrop-ID % Server", font=Bold, bg="white", width=27) SubKL2 = Label(Key1, text="Initial Secret", font=Bold, bg="white", width=27) KL1Info = Label(Key1, text=" Enter an initial secret for a new contact SafeWebdrop ID that is not yet registered ", font=Info, bg=BACKGROUND) Address = Entry(Key1, font=Bold, width=40) InitKey = Entry(Key1, font=Bold, width=40) K1Button = Button(master=Key1, text="Register", bg=GRAY, font=BFont, command=register) KL1.grid(row=0, column=0, columnspan=2) KL1Info.grid(row=1, column=0, columnspan=2, padx=3, pady=8) SubKL1.grid(row=2, column=0, padx=3, pady=2) Address.grid(row=2, column=1, padx=3, pady=2) SubKL2.grid(row=3,column=0, padx=3, pady=2) InitKey.grid(row=3, column=1, padx=3, pady=2) K1Button.grid(row=4, column=0, columnspan=2, padx=3, pady=15) # Setup frame Setup1 = Frame(LeftFrame, height=100, pady=10, bg=BACKGROUND) SL1 = Label(Setup1, text="Setup the SAFE WEBDROP Server for Message Exchange", font=Title, bg=TAB, width=50) InfoPanel = Label(Setup1, text="" , width=60, height=8, bg=BACKGROUND, fg="red", pady=5, justify="left") SubSL1 = Label(Setup1, text="Safe Webdrop Server Name", font=Bold, bg="white", width=27) SubSL2 = Label(Setup1, text="Safe Webdrop User ID", font=Bold, bg="white", width=27) SL1Info = Label(Setup1, text="Please enter your SAFE WEBDROP address (no spaces) and give it to your contacts", font=Info, padx=10) Hostname = Entry(Setup1, width=40, font=Bold) Username = Entry(Setup1, width=40, font=Bold) S1Button = Button(master=Setup1, text="Update SAFE WEBDROP Setup", bg=GRAY, font=BFont, command=webdrop_setup) ShowButton = Button(master=Setup1, text="Show Setup", bg=GRAY, font=BFont, command=show_webdrop_setup) # Space Space = Label(Setup1, height=3) # setup 2 SL2 = Label(Setup1, text="Registration of a new Safe Webdrop account (if you don't have one already)", font=Title, bg=TAB, width=66) SL2Info = Label(Setup1, text="In order to send safe webdrops you need a registered account on your safe webdrop server", font=Info, padx=10) SubSL5 = Label(Setup1, text="Registration code", font=Bold, bg="white", width=27) RegistrationCode = Entry(Setup1, width=28, font=Bold ) REGButton = Button(master=Setup1, text="Registration", bg=GRAY, font=BFont, command=register_webdrop_account) SL1.grid(row=0, column=0, columnspan=2, sticky=W, padx=10, pady=8) SL1Info.grid(row=1, column=0, columnspan=2, padx=10, pady=8, sticky=W) SubSL1.grid(row=2, column=0, padx=3, pady=2) Hostname.grid(row=2, column=1, padx=3, pady=2, sticky=W) SubSL2.grid(row=3,column=0, padx=3, pady=2) Username.grid(row=3, column=1, padx=3, pady=2, sticky=W) ShowButton.grid(row=4, column=0, padx=3, pady=15) S1Button.grid(row=4, column=1, padx=3, pady=15) Space.grid(row=5, column=0) SL2.grid(row=6, column=0, columnspan=2, sticky=W, padx=10, pady=8) SL2Info.grid(row=7, column=0, columnspan=2, padx=10, pady=8, sticky=W) SubSL5.grid(row=8, column=0, padx=3, pady=2) RegistrationCode.grid(row=8, column=1, padx=3, pady=2, sticky=W) REGButton.grid(row=9, column=1, padx=3, pady=15) QuitButton = Button(master=MainFrame, text="Exit!", bg=GRAY, font=BFont, command=terminate_GUI, width=14) ConsoleButton = Button(master=RightFrame, text="Console", bg=GRAY, font=BFont, command=toggle_console, width=14) if not os.path.islink("/etc/systemd/system/multi-user.target.wants/cryptoboned.service"): AdminFrame = Frame(Window) AdminFill = Label(AdminFrame, text="", height=2) AdminLabel = Label(AdminFrame, text="You want to activate your Crypto Bone?", width=60, height=1, font=Bold, bg="#eeffee") Text1 = Label(AdminFrame, text="Please input the login name of the user that\n will be using the Crypto Bone:") Text2 = Label(AdminFrame,text="This user will be listed in the sudoers file.") ExecButton = Button(master=AdminFrame, text=" Enable this user ", bg=GREEN, font=BFont, command=readuser) InputField = Entry(master=AdminFrame, width=15) AdminFill2= Label(AdminFrame, text="", height=1) AdminFill.pack() AdminLabel.pack(pady=10) Text1.pack(pady=5) Text2.pack(pady=5) InputField.pack(pady=10) ExecButton.pack() AdminFill2.pack() AdminFrame.pack() ErrorFrame.pack(fill=X) clear_error() Window.mainloop() # pack frames StatusFrame.pack() ModeFrame.pack() # set the main grid # set row 2 to xxx pixels MainFrame.rowconfigure(2, minsize=810) HeadFrame.grid(row=0, column=0, sticky=NW) ID.grid(row=0, column=1, sticky=N, padx=10, pady=10) TransportLabel.grid(row=0, column=1, padx=10, pady=20) StatusFrame.pack() ModeFrame.pack() TopFrame.grid(row=1, column=0, sticky=NW) TextFrame.grid(row=2, column=0, sticky=N, padx=10) QuitButton.grid(row=3, column=1, pady=10) ErrorFrame.grid(row=4, column=0, columnspan=2) MainFrame.pack(padx=5, fill=X) if DEBUG: DebugFrame.grid(row=5, column=0, columnspan=2) Name = "none" RESULT = unix("pidof /usr/lib/cryptobone/cryptoboned") if "x"+RESULT == "x" : # crypto bone daemon is not running showerror('Crypto Bone Daemon failed', "You need to reboot your computer because the Crypto Bone Daemon is not running.") unix("sleep 5") sys.exit(2) Window.update_idletasks() get_initial_status() Window.mainloop() #------------------------------------------------------------#