#!/usr/bin/python3 # *-* coding:utf-8 *-* #*************************************************************************** # This file is part of the CRYPTO BONE # File : external-cryptobone-admin (installed in /usr/bin) # Version : 1.5 (external cryptobone) # License : BSD # Date : 1 March 2023 # Contact : Please send enquiries and bug-reports to innovation@senderek.ie # # # Copyright (c) 2015-2023 # 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. #**************************************************************************** import os import base64 import time OS = os.name ############################################################### def unix (command) : if OS == "posix": Pipe = os.popen(command, "r") Result = Pipe.read() Pipe.close() return Result ############################################################### # GUI result of yes-no dialog REPLY = False RED = "#fcc" GREEN = "#cfc" GRAY = "#ccc" STATUS = "" INFO = "#ddd" DEBUGCOLOR = "#aaa" BACKGROUND = "#ddd" ERRORCOLOR = "#f00" OKCOLOR = "#0f0" INFOCOLOR = "#00f" KEYCOLOR = "#efe" YESCOLOR = "#afa" NOCOLOR = "#faa" 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?") import sys sys.exit(4) X11 = False PID1=unix("pidof gdm-wayland-session") PID2=unix("pidof Xorg") PID3=unix("pidof Xwayland") if ( (len(PID1) > 0) or (len(PID2) > 0) or (len(PID3) > 0)): X11=True ############################################################### # GUI functions ############################################################### def clear_error(): ErrorFrame.configure(bg=INFO) ErrorLabel.configure(bg=INFO) ErrorLabel.configure(text="") Window.update_idletasks() ############################################################### def showinfo(Title, Text): Text = Title + " " + Text ErrorFrame.configure(bg=INFO) ErrorLabel.configure(text=Text) ErrorLabel.configure(fg=INFOCOLOR, bg=INFO) Window.update_idletasks() ############################################################### def showsuccess(Title, Text): Text = Title + " " + Text ErrorFrame.configure(bg=INFO) ErrorLabel.configure(text=Text) ErrorLabel.configure(fg=OKCOLOR, bg=INFO) Window.update_idletasks() ############################################################### def showerror(Title, Text): Text = Title + " " + Text ErrorFrame.configure(bg=INFO) ErrorLabel.configure(text=Text) ErrorLabel.configure(fg=ERRORCOLOR, bg=INFO) Window.update_idletasks() ############################################################### 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) dialog.grab_set() dialog.wait_window() ############################################################### def get_status(): global ACTIVE, DB, FIREWALL, REMOTE, SSHD print (STATUS) print () RES=unix("systemctl is-enabled cryptoboneexternd") if "enabled" in RES : ACTIVE = True EnableButton.configure(text="Disable External Crypto Bone") RES=unix("systemctl is-active cryptoboneexternd") if "xactive" in "x"+RES: ActiveLabel.configure(text="This external Crypto Bone is enabled and running.") ActiveLabel.configure(bg=GREEN) else: if "failed" in RES: ActiveLabel.configure(text="This external Crypto Bone is enabled but failed to start.") ActiveLabel.configure(bg=GRAY) else: ActiveLabel.configure(text="This external Crypto Bone is enabled but not running.") ActiveLabel.configure(bg=GRAY) else: ACTIVE = False EnableButton.configure(text="Enable External Crypto Bone") ActiveLabel.configure(text="The external Crypto Bone is not enabled on this machine.") ActiveLabel.configure(bg=GRAY) if "database:initialised" in STATUS: DB = True DBLabel.configure(text="The encrypted data base is initialized.") DBLabel.configure(bg=GREEN) else: DB = False DBLabel.configure(text="The encrypted data base is missing.") DBLabel.configure(bg=GRAY) RES=unix("systemctl is-active firewalld") if "xactive" in "x"+RES : FIREWALL = False FWButton.configure(text="Activate Restrictive Firewall") FirewallLabel.configure(text="The system's original firewall daemon is active.") FirewallLabel.configure(bg=GRAY) else: # firewalld is inactive, check the existence of usefirewall if "firewall:restrictive" in STATUS: FIREWALL = True FWButton.configure(text="Return To Original Firewall Daemon") FirewallLabel.configure(text="The Crypto Bone's restrictive firewall is active.") FirewallLabel.configure(bg=GREEN) else: # custom or no firewall FirewallLabel.configure(text="The Crypto Bone's restrictive firewall is not active.") FirewallLabel.configure(bg=GRAY) RES=unix("systemctl is-enabled sshd 2>&1") if "Failed to get unit" in RES : SSHD = False SSHDButton.configure(text="Install Secure Shell Daemon") RemoteLabel.configure(text="Secure Shell Daemon is not installed but needed.") RemoteLabel.configure(bg=RED) else: # sshd is installed SSHD = True RES=unix("systemctl is-active sshd 2>&1") if ("inactive" in RES) or ("unknown" in RES) : RemoteLabel.configure(text="The Secure Shell Daemon is not running.") RemoteLabel.configure(bg=RED) showerror( "ERROR","Please enable the secure shell daemon.") elif "xactive" in "x"+RES: if "sshdconfig:hardened" in STATUS : REMOTE = True SSHDButton.configure(text="Return To Original SSHD Configuration") RemoteLabel.configure(text="The system's secure shell daemon is hardened.") RemoteLabel.configure(bg=GREEN) else: REMOTE = False SSHDButton.configure(text="Harden Secure Shell Daemon") RemoteLabel.configure(text="The system's secure shell daemon is untouched.") RemoteLabel.configure(bg=GRAY) ############################################################### def terminate_GUI(): Window.destroy() ############################################################### def OpenHelpUrl(): import webbrowser webbrowser.open_new("https://crypto-bone.com/help/external") ############################################################### def activate_external(): global ACTIVE, STATUS clear_error() if not ACTIVE: print ("Enabling cryptoboneexternd ...") print ("This takes about half a minute ...") RES=unix("df | grep BOOT") if not "BOOT" in RES: showerror( "ERROR","There is no USB partition with a BOOT label which is needed to store secrets\n for the main machine. Please insert a USB key labelled BOOT and enable again.") return STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone enable") if "missing" in STATUS: showinfo( "ATTENTION","Please reboot this machine to make sure that the secrets database will be created.") return # check the result RES=unix("/usr/bin/pkexec /bin/ls /usr/lib/cryptobone/ext/masterkey") if "masterkey" in RES: showsuccess( "SUCCESS","The external cryptobone daemon on this machine is initialised.\n\nPlease transfer the new secrets to your main machine now,\n if you have enabled the external Crypto Bone for the first time.") else: showerror( "ERROR","New secrets have been created but there is no USB partition to write to.\n Please insert a USB key labelled BOOT and reboot this machine to initialise again.") ACTIVE = True else: print ("Disabling cryptoboneexternd ...") STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone disable") ACTIVE = False get_status() ############################################################### def harden_firewall(): global FIREWALL, STATUS clear_error() if not FIREWALL: print ("Activating the restrictive firewall for the external Crypto Bone ...") showinfo( "ATTENTION","If you activate the restrictive firewall this machine cannot be used as a general purpose computer\n with access to the internet.\nThe restrictive firewall replaces any firewall setting after booting this machine") unix("sleep 10") STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone usefirewall") FIREWALL = True showinfo( "REBOOT","You need to reboot your computer to ensure that this change takes effect.") else: print ("Restoring the original firewalld configuration ...") STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone restorefirewall") FIREWALL = False showinfo( "ATTENTION","Your original firewall daemon is now active again.") if not ACTIVE: showerror( "ERROR","You also need to activate the external cryptobone daemon on this machine.") get_status() ############################################################### def harden_sshd(): global REMOTE, STATUS clear_error() if not SSHD: install_sshd() else: if not REMOTE: print ("Hardening the secure shell daemon for the external Crypto Bone ...") STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone hardensshd") print (STATUS) REMOTE = True else: print ("Restoring the original secure shell daemon configuration ...") STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone restoresshd") print (STATUS) REMOTE = False get_status() ############################################################### def install_sshd(): global SSHD, STATUS clear_error() Info = """You are about to install the secure shell daemon on this machine. You will then be able to access this machine from your main computer through the network interface. Do you want to install the Secure Shell Daemon now? """ askyesno('SSHD Installation', Info) if REPLY: print ("INSTALLING SSH DAEMON") STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone installsshd") SSHD = True get_status() ############################################################### def reset_cryptobone(): global STATUS clear_error() Info = """You are about to destroy the EXTERNAL Crypto Bone on this machine. After completing this step the encryption key database and all access information\n for the EXTERNAL Crypto Bone on this machine will be destroyed.\n While the external cryptobone software will still be there, you will return to square one\n and this machine will try to create new secrets on the next boot. Do you want to remove all EXTERNAL Crypto Bone data now? """ askyesno('Destroying an External Crypto Bone', Info) if REPLY: print ("DESTROYING AN EXTERNAL CRYPTO BONE") STATUS = unix("/usr/bin/pkexec /usr/bin/external-cryptobone reset") print (STATUS) get_status() ############################################################### # Main ############################################################### ACTIVE=False DB=False FIREWALL=False REMOTE=False SSHD=False STATUS=unix("/usr/bin/pkexec /usr/bin/external-cryptobone status") print ("This is the administration tool for the EXTERNAL Crypto Bone on this machine.") print () print ("In order to collect certain status information root permission is required.") print ("As this GUI runs non-root, all administrative tasks require root permission too.") print ("You may be prompted for the super user (root) password several times.") print () if GUI and X11: Window = Tk() Window.title("External Crypto Bone Administration") MainFrame = Frame(Window) TopFrame = Frame(MainFrame, pady=10) StatusFrame = Frame(MainFrame, pady=5) BottomFrame = Frame(Window, pady=5) 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") Normal = tkFont.Font(family="arial", size=10, weight="normal") BFont = tkFont.Font(family="utopia", size=12, weight="normal") TextFont = tkFont.Font(family="arial", size=11, weight="normal") STARTINFO = """ The Crypto Bone has two different modes of operation that can be selected\n in the SETUP window of the cryptobone program. In its default mode (ALL-IN-ONE) the GUI (cryptobone) uses the internal, encrypted data base on the main machine through a UNIX socket on the same computer. In EXTERNAL mode, a separate device is used to store the data base and the main machine establishes contact to this second external device via a secure shell link. If this machine is going to be the second device, a secure shell daemon must be activated here. You are now turning this machine into a separate, EXTERNAL Crypto Bone. Do you wish to do this? (for more information: man external-cryptobone-admin and https://crypto-bone.com) """ print (STARTINFO) # Status Help_btn_img = PhotoImage(file='/usr/share/icons/default/question-mark.png') HelpButton = Button(master=TopFrame, image=Help_btn_img, bg=GRAY, width=60, height=60, command=OpenHelpUrl) BoneLabel = Label(TopFrame, text="EXTERNAL CRYPTO BONE 1.5 (Administration)", font=Big, width=50) BoneLabel.bind("",lambda e: OpenHelpUrl()) ActiveLabel = Label(StatusFrame, text="no information", width=60, height=1, font=Bold, bg=GRAY) DBLabel = Label(StatusFrame, text="no information", width=60, height=1, font=Bold, bg=GRAY) FirewallLabel = Label(StatusFrame, text="no information", width=60, height=1, font=Bold, bg=GRAY) RemoteLabel = Label(StatusFrame, text="no information", width=60, height=1, font=Bold, bg=GRAY) InfoLabel = Label(StatusFrame, text=STARTINFO, width=100, height=16, font=Normal, bg="#eee") BoneLabel.pack(side=LEFT) HelpButton.pack(padx=20, side=LEFT, pady=5) ActiveLabel.pack(fill=Y) DBLabel.pack(fill=Y) FirewallLabel.pack(fill=Y) RemoteLabel.pack(fill=Y) InfoLabel.pack(fill=Y, pady=20) SSHDButton = Button(master=BottomFrame, text="Harden Secure Shell Daemon", width=40, bg=GRAY, font=BFont, command=harden_sshd) RESETButton = Button(master=BottomFrame, text="Forget Everything", width=40, bg=RED, font=BFont, command=reset_cryptobone) FWButton = Button(master=BottomFrame, text="Activate Restrictive Firewall", width=40, bg=GRAY, font=BFont, command=harden_firewall) EnableButton = Button(master=BottomFrame, text="Enable External Crypto Bone", width=40, bg=GRAY, font=BFont, command=activate_external) ExitButton = Button(master=BottomFrame, text="EXIT", width=20, bg=YESCOLOR, font=BFont, command=terminate_GUI) EnableButton.pack(pady=8) FWButton.pack(pady=8) SSHDButton.pack(pady=8) RESETButton.pack(pady=8) ExitButton.pack(pady=20) # ERROR frame ErrorFrame = Frame(Window, bg=DEBUGCOLOR, pady=10) ErrorLabel = Label(ErrorFrame, text="", font=Bold, bg=BACKGROUND) ErrorLabel.pack(side=LEFT, padx=10) TopFrame.pack() StatusFrame.pack() MainFrame.pack() BottomFrame.pack() ErrorFrame.pack(fill=X) clear_error() get_status() if not "database:initialised" in STATUS and ACTIVE: showinfo( "ATTENTION","Please reboot this machine to make sure that the secrets database will be created.") Window.mainloop() else: print ("Console version (not yet available)") ##############################################################