#!/usr/bin/bash

if [ $(/usr/bin/id -u) != 0 ]; then echo "only root can do that"; exit 2; fi

#
# Copyright 2015-2025 Senderek Web Security, Ireland. All rights reserved.
#                <https://senderek.ie>

##############################################################################
# This file is part of the CRYPTO BONE
# File     : init.d/cryptoboned 
# Version  : 2.0 (ALL-IN-ONE)
# License  : BSD-3-Clause
# Date     : 24 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.
##############################################################################

# Author:       Ralf Senderek <innovation@senderek.ie>
# license:      BSD-3-Clause
# description:  Activates the cryptobone daemon during the boot process 
# processname:  cryptoboned 
# config:       none
# date:		24 May 2025

# This script makes sure that the cryptobone daemon is started during the boot process
# of the machine working in ALL-IN-ONE mode. The cryptobone daemon cannot be started when the 
# machine has finished the boot process, because the secret information that is used to
# decrypt the database of secrets is only visible in a fraction of a second during the 
# boot process.
# To ensure that this script is started exclusively via the systemd service cryptoboned.service at
# boot time a bootswitch is used to indicate boot time. The bootswitch is protected from deletion
# using the chattr command to prevent modification.
#
# If the link /usr/lib/cryptobone/masterkey does not exist, the script /usr/lib/cryptobone/createmasterkey
# initialises the necessary files that don't exist when the cryptobone is started for the first time.
# Once the files boot.fs and permanent.fs exist, the encrypted boot.fs is mounted on the directory
# /usr/lib/cryptobone/keys  while cryptsetup has access to the key $KEYS/$DIR/$FILE.
# Once the boot.fs is decrypted and successfully mounted and the encrypted database exists, 
# the essential cryptobone daemon is being started. The daemon now has access to the masterkey
# that decrypts the database so that all secrets can be accessed in the daemon's memory.
#
# Secrets that reside in the daemon's memory can now be read and modified via the UNIX-socket (as root).
#
# In order to make the information stored in the decrypted boot file system invisible as soon as possible,
# a permanent file system is mounted on the $KEYS directory after the boot file system is unmounted and
# destroyed via cryptsetup.
# This permanent file system remains in the $KEYS directory and holds only non-sensitive information
# like hash values of the master key and a local key indentifying the machine that uses the daemon via
# the UNIX socket.
# If this machine is running in the ALL-IN-ONE mode the daemon has beem established and all is done.
# If this machine uses an EXTERNAL device to store secrets in the memory of a cryptoboneexternd 
# on the external device, the external device must be contacted and queried for the secrets that are
# stored in the external daemon.
#
# In order to secure the information on the external device, the daemon on the external device 
# does not possess the master key to decrypt the external database. This master key must be transferred
# to the exernal device in a secure process.
#
# When the external device pings back under the IP address that is stored in the client, a ssh tunnel
# is used to upload the master key the external daemon is waiting for.
# In this case the master key (real.key) and the ssh private key (cbb) has been read during the
# short time the boot file system was visible, so that the master key can be transported securely
# to the external device with this script. This machine authenticates itself by opening the ssh tunnel
# with the correct ssh private key (cbb) and by sending its local key to the external device
# for verification there.
# Once the external daemon has got the master key through the ssh tunnel, it is working and can
# respond to queries using the same ssh tunnel by sending the external device's current status.
# If this status is "active" this fact is recorded on this machine by creating the file UPLOADED.
# If the external device is not working, the master key can only be sent again by rebooting this
# machine.
#
# To examine the sequence of events, debugging can be enabled by creating (and subsequently destroying)
# a file /root/.cryptobone.debug, that shows events and timestamps (to the root user) when the boot
# process has finished.

KEYS="/usr/lib/cryptobone/keys"
REAL="none"
SOCK="/usr/lib/cryptobone/secrets.sock"

#-----------------------------------------------------#
log_debug() {
     # make sure, non-sensitive information is stored for debugging purposes
     # if  a file /root/.cryptobone.debug exists with permissions 0600 for the
     # root user

     DEB=/root/.cryptobone.debug
     if /bin/ls $DEB 2> /dev/null > /dev/null ; then
          PERM=$(/bin/ls -l $DEB | cut -c2-10)
	  TIME=$(date '+%T %N')
          if [[ $PERM = "rw-------" ]] ; then
	       if [[ x$1 = xclean ]] ; then
                    echo -n > $DEB
	       fi
	       echo "$TIME $1 $2" >> $DEB
	  fi 
     fi
}

#-----------------------------------------------------#
start() {

     if ! df | grep /usr/lib/cryptobone/keys 2> /dev/null > /dev/null
     then
           # we're booting 
           # the package rng-tools should be installed and the rngd enabled by default
	   # starting rngd here will slow down the shutdown / stop job for cryptoboned
	   #/usr/sbin/rngd -b -r /dev/hwrnd 2> /dev/null

	   # load the SELinux policy for the daemon
	   log_debug "clean"
           if selinuxenabled ; then
	        log_debug "start selinux module"
	        semodule -i /usr/lib/cryptobone/selinux/cryptobone.pp
                semodule -e cryptobone
	        log_debug "end selinux module"
	   fi

           SWITCH=/usr/lib/cryptobone/bootswitch
           if [ ! -f $SWITCH ] 
           then
               touch $SWITCH
           fi
           chattr -i $SWITCH
           chmod 600 $SWITCH
	   chmod 700 $KEYS
          
	   if [ ! -L /usr/lib/cryptobone/masterkey ]
	   then
                # initialize database and create a master key and local key
		/usr/lib/cryptobone/createmasterkey
		# now there is $KEYS/random and both file systems (boot.fs and permanent.fs)
	   fi
	   /usr/bin/rm /usr/lib/cryptobone/UPLOADED 2> /dev/null
	   # record evidence that we're really booting
           typeset -i BOOTTIME=$(cat /proc/stat | grep btime | cut -f2 -d" ")
           typeset -i NOW=$(date +%s)
	   typeset -i DIFF=$NOW-$BOOTTIME
	   echo $DIFF > $SWITCH
	   log_debug "booting"
           DIR=$(echo -n $(cat $KEYS/random) $(stat -c "directory %i" $KEYS) | sha256sum | cut -c-64)
	   FILE=$(echo -n $DIR $(stat -c "file %i"  $KEYS/random) | sha256sum | cut -c-64)

           # establish the boot file system
	   L=$(losetup -f)
	   losetup $L $KEYS/boot.fs
           cat $KEYS/$DIR/$FILE | cryptsetup create cryptobone $L
	   if mount /dev/mapper/cryptobone $KEYS
	   then
	        # start the daemon unless there is a non-empty database
		if [ ! -s /usr/lib/cryptobone/database ] ; then
		     /bin/rm /usr/lib/cryptobone/database
		     log_debug "performing database initialisation"
		     systemctl start cryptobone-dbinit
		     /bin/sleep 1
                     typeset -i x=0
		     while [ $x -lt 8 ]
                     do
		          if [ ! -s /usr/lib/cryptobone/database ] ; then
		              log_debug "restarting database initialisation"
                              systemctl stop cryptobone-dbinit
			      /bin/rm /usr/lib/cryptobone/database
                              systemctl start cryptobone-dbinit
                              /bin/sleep 1
                              x=$x+1
                          else
                              x=10
                          fi
                     done
		fi
		rm -f /usr/lib/cryptobone/ssh.sock
		rm -f /usr/lib/cryptobone/secrets.sock
		log_debug "starting daemon ... "
	        /usr/lib/cryptobone/cryptoboned 
		log_debug "daemon started"
	        ssh-agent -s -a /usr/lib/cryptobone/ssh.sock
		export SSH_AUTH_SOCK=/usr/lib/cryptobone/ssh.sock 2>/dev/null > /dev/null


                OVERWRITE=$(echo "get-element EXTERN.OVERWRITE"  | socat - UNIX-connect:$SOCK 2> /dev/null)
                if [ x$OVERWRITE = "xyes" ]
		then
                     rm -f $KEYS/real.key $KEYS/cbb
		fi

		if  [ ! -r $KEYS/real.key ]
		then
                     # check if external keys are stored in the encrypted database
                     REALKEY=$(echo "get-element EXTERN.real.key"  | socat - UNIX-connect:$SOCK 2> /dev/null)
                     if [ x$REALKEY != "x" ]
		     then
                          echo $REALKEY > $KEYS/real.key
			  chmod 600 $KEYS/real.key
			  log_debug "overwriting master key"
	                  REALKEY="000000000000000000000000000000000000000000"
	                  unset REALKEY
		     fi
                fi

		if  [ ! -r $KEYS/cbb ]
                then
                     SSHKEY=$(echo "get-element EXTERN.cbb"  | socat - UNIX-connect:$SOCK 2> /dev/null)
                     if [ x$SSHKEY != "x" ]
		     then
                          echo "$SSHKEY" | base64 -d  > $KEYS/cbb
			  chmod 600 $KEYS/cbb
	                  SSHKEY=$(/bin/dd if=/dev/zero bs=1K count=4 2>/dev/null)
	                  unset SSHKEY
		     fi
		fi
		
                echo -n "replace EXTERN.OVERWRITE no"  | socat - UNIX-connect:$SOCK 2> /dev/null
		
		if [ -f $KEYS/cbb ]
		then
		     ssh-add $KEYS/cbb
		     REAL=$(cat /usr/lib/cryptobone/keys/real.key) 2> /dev/null
		fi
	   fi    
           
           # remove the boot file system
	   umount /dev/mapper/cryptobone
	   cryptsetup remove cryptobone
	   losetup -d $L
    
           # mount the permanent file system
	   L=$(losetup -f)
	   losetup $L $KEYS/permanent.fs
           mount $L $KEYS
	   chmod 700 $KEYS

           echo 9999 > $SWITCH
           chattr +i $SWITCH
	   log_debug "secrets become invisible"

    	   if [ ! -L /usr/lib/cryptobone/ALLINONE ]
	   then
	        # we're using a real EXTERNAL Crypto Bone
		log_debug "Trying to contact the external device"
	        if [ -f /usr/lib/cryptobone/cbb.config ]
	        then
	             chmod 600 /usr/lib/cryptobone/cbb.config
	             . /usr/lib/cryptobone/cbb.config
		     log_debug "reading the config file cbb.config"
                     # wait for BONEIP to come up
		     typeset -i count=0
		     while [ $count -lt 20 ]
                     do
		         log_debug "waiting for the external device to ping"
		         /bin/sleep 2
			 count=$count+1
                         /bin/ping -w1 -c1 ${BONEIP} 2>/dev/null > /dev/null
			 if [ $? -eq 0 ]
			 then
                             count=21
			 fi
                     done

                     /bin/ping -w1 -c1 ${BONEIP} 2>/dev/null > /dev/null
		     if [ $? -eq 0 ]
	             then
                          # upload the master key to the real crypto bone
			  log_debug "external device is reachable"
		          echo $(/usr/lib/cryptobone/getlocalsecret) SYSTEM UPLOAD $(echo $REAL) | /usr/bin/ssh -l cryptobone  ${BONEIP} /cbb/cbcontrol
                          log_debug "master key sent"

		          count=0
		          while [ $count -lt 8 ]
                          do
		               /bin/sleep 2
			       count=$count+1
                               /bin/ping -w1 -c1 ${BONEIP} 2>/dev/null > /dev/null
	                  
			       RES=$(echo $(/usr/lib/cryptobone/getlocalsecret) STATUS | /usr/bin/ssh -l cryptobone ${BONEIP} /cbb/cbcontrol)
  	                       if [ "${RES}x" = "activex" ]
	                       then 
			            log_debug "device becomes active"
				    echo "EXTERNAL is active now."
		                    touch /usr/lib/cryptobone/UPLOADED
				    # now remove the master key from the database
                                    echo "remove-element EXTERN.real.key"  | socat - UNIX-connect:$SOCK 2> /dev/null
                                    RES=$(echo "get-element EXTERN.real.key"  | socat - UNIX-connect:$SOCK 2> /dev/null)
                                    echo "reading the master key :$RES master key is not stored in the database"
				    count=11
			            RES=$(echo $(/usr/lib/cryptobone/getlocalsecret) SYSTEM RESTART | /usr/bin/ssh -l cryptobone ${BONEIP} /cbb/cbcontrol)

			       else
			            log_debug "device inactive [$RES]"
		                    RES=$(echo $(/usr/lib/cryptobone/getlocalsecret) SYSTEM UPLOAD $(echo $REAL) | /usr/bin/ssh -l cryptobone  ${BONEIP} /cbb/cbcontrol)
			            log_debug "uploading master key again [$RES]"

	                       fi
			       log_debug "$count" "sending master key"
			  done     
			  echo "EXTERNAL finished."
	                  REAL="000000000000000000000000000000000000000000"
	                  unset REAL

	             fi
	        fi
           else
	        echo "ALL-IN-ONE finished."
     	   fi
	   REAL="000000000000000000000000000000000000000000"
	   unset REAL
     fi
}


#-----------------------------------------------------#
stop() {
     if [ -d /dev/shm/RAM ]
     then
         /usr/lib/cryptobone/bin/savemessages
     fi
     killall cryptoboned
}


#-----------------------------------------------------#
case "$1" in
  start)
	start
	;;
  stop) 
	stop
	;;
  restart)
        echo "deliberately not implemented"
	;;
  status)
        echo "deliberately not implemented"
	;;
  force-reload)
        echo "deliberately not implemented"
        ;;
  *)
	echo "Usage: /usr/lib/cryptobone/init.d/cryptoboned {start|stop}"
	exit 1
esac

exit 0
