#!/usr/bin/bash

#
# Copyright 2014 - 2024  Senderek Web Security, Ireland. All rights reserved.
#                        <https://senderek.ie/opensource/secureboot2>
#
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

#
# Author:       Ralf Senderek <innovation@senderek.ie>
#
# license:      GNU General Public License version 3 or higher
# description:  starts an encrypted filesystem on boot up via systemd
# config:       /etc/systemd/system/secureboot2.service
#               /etc/systemd/system/secureboot2-halt.service
#               /etc/systemd/system/securebootpassword
#               /usr/lib/secureboot/.rebootonfailure
# date:         3/5/2024


TERMINAL=/dev/tty8
NAME=secure
REBOOT="no"
BASE=/usr/lib/secureboot
FILE=securefilesystem
LOOPDEVICE=$BASE/loopdevice
LOG=$BASE/log
BLOCKFILE=$BASE/.rebootonfailure

# set SHORTCUT="yes" if you wish to use either a USB memory key or the USB DONGLE
# make sure that your computer is used in a safe environment and remove the setting 
# when you move out of the safe environment !

# see /lib/secureboot/readme.OTP for details

SHORTCUT="no"

# old encrypted big files traditionally use ripemd160 to hash the passphrase in order to generate a AES key.
CREATE="/sbin/cryptsetup open --type plain --cipher="aes-cbc-essiv:sha256" --key-size=256 --hash=ripemd160"
# You can switch to sha256, if you create a new filesystem instead of using your old one.

# DEFAULT 
ASKPASS=/bin/systemd-ask-password

BOOTKEYDEVICE=/dev/sda1
BOOTKEY=/mnt/securebootkey
TMPBOOTKEY=/tmp/securebootkey
BOOTKEYHASH=$BASE/securebootkey.sha256

# print error message if permissions are not -r-------- root
PERMS=$(/bin/ls -l /usr/lib/secureboot/secure.OTP | /bin/cut -c-10)
if [[ $PERMS = "-r--------" ]]
then
     . /usr/lib/secureboot/secure.OTP
else
     echo "The permissions on /usr/lib/secureboot/secure.OTP must be -r--------"
fi

if [ ! -d /$NAME ] ; then
     /bin/mkdir /$NAME
fi

RET=0


##########################################################################
mounterror() {
     echo -en "\\0033[1;31m"
     echo "Mounting a secure filesystem failed."
     echo -en "\\0033[0;39m"
}

##########################################################################
errorreboot() {
     echo -en "\\0033[1;31m"
     echo "REBOOTING NOW ..."
     echo -en "\\0033[0;39m"
}

##########################################################################
success() {
     echo -en "\\0033[1;32m"
     echo "### SUCCESSFUL ###"
     echo -en "\\0033[0;39m"
}

##########################################################################
green() {
     echo -en "\\0033[1;32m"
     echo "### $1 ###"
     echo -en "\\0033[0;39m"

}

##########################################################################
orange() {
     echo -en "\\0033[1;33m"
     echo "### $1 ###"
     echo -en "\\0033[0;39m"

}

##########################################################################
cleanup(){
     echo "Cleaning up ..."
     /sbin/losetup -a
     ls -l /dev/mapper/$NAME 2> /dev/null > /dev/null
     if [[ $? == 0 ]]
     then
          echo "Remove mapping ..."
          /sbin/cryptsetup remove $NAME
     fi
     if [[ -f $LOOPDEVICE ]]
     then
          echo "Remove loopdevice $LOOP ..."
          LOOP=$(cat $LOOPDEVICE 2> /dev/null )
          /sbin/losetup -d $LOOP > /dev/null 2>&1
          rm $LOOPDEVICE
     fi

     # get filename if it is a symlink
     LINK=$(ls -l $BASE/$FILE)
     FILENAME=${LINK##* }
     for D in $(/sbin/losetup -a | /bin/grep $FILENAME | cut -f1 -d:)
     do
          echo "Removing $D"
          losetup -d $D
     done
}

##########################################################################
haltfilesystem () {
     RET=0
     LOOP=$(cat $LOOPDEVICE  2> /dev/null)

     echo
     echo "Unmounting the secure filesystem on /$NAME"

     mount | grep "on /$NAME " 2> /dev/null > /dev/null
     if [[ $? = 0  ]]
     then
          echo "Waiting for /secure to be removed ..."
          date > $LOG

          for X in $(/sbin/fuser -m /$NAME)
          do
               /bin/kill -9 $X 2>/dev/null
          done

          echo "trying to unmount /$NAME ..." >> $LOG

          /bin/umount -v   /dev/mapper/$NAME  >> $LOG 2>&1
          /bin/mount | grep "on /$NAME"  >> $LOG 2>&1

          /bin/umount -v   /dev/mapper/$NAME  >> $LOG 2>&1
          /bin/mount | grep "on /$NAME"  >> $LOG 2>&1

          /bin/umount -v   /dev/mapper/$NAME  >> $LOG 2>&1
          /bin/mount | grep "on /$NAME"  >> $LOG 2>&1

          /bin/ls -l /dev/mapper/$NAME >> $LOG
          echo "removing cryptsetup ... " >> $LOG
          /sbin/cryptsetup close $NAME  >> $LOG 2>&1
          echo "removing losetup ... " >> $LOG
          /sbin/losetup -d $LOOP >> $LOG 2>&1
          /sbin/losetup -a >> $LOG
          rm $LOOP 2> /dev/null
          echo "DONE." >> $LOG
     else
          echo "/$NAME is not mounted."
          cleanup
          RET=0
     fi
}

##########################################################################
umountfilesystem () {
     RET=0
     LOOP=$(cat $LOOPDEVICE  2> /dev/null)
     echo
     echo "Unmounting the secure filesystem on /$NAME"

     /bin/mount | grep "on /$NAME " 2> /dev/null > /dev/null
     if [[ $? = 0  ]]
     then
          echo "Trying to unmount /$NAME ..."
          /bin/umount -v  /dev/mapper/$NAME
          /bin/mount | /bin/grep "on /$NAME " 2> /dev/null
          if [[ $? = 0  ]]
          then
               echo "Error: Cannot unmount /$NAME. Filesystem is busy."
               RET=3
          else
               /bin/ls -l /dev/mapper/$NAME 2> /dev/null > /dev/null
               if [[ $? != 0 ]]
               then
                    echo "No mapping for /$NAME exists."
                    RET=0
               else
                    /sbin/cryptsetup remove $NAME
                    [[ $? = 0 ]] && echo "Mapping removed ..."
                    /sbin/losetup -d $LOOP
                    [[ $? = 0 ]] && echo "Loop device removed ..."
                    rm $LOOPDEVICE
                    RET=0
               fi
          fi
     else
          echo "/$NAME is not mounted."
          cleanup
          RET=0
     fi
}

##########################################################################
mountfilesystem () {

     LOOP=$(losetup -f)
     RET=0
     if /bin/mount | /bin/grep "on /$NAME " 2>&1 > /dev/null
     then
          echo "/$NAME is still mounted."
          RET=1
     else
          echo "Setting up a secure filesystem on $LOOP"
          if [[ ! -f $BASE/$FILE ]]
          then
               echo "No crypto filesystem $NAME available."
               RET=2
          else
               # create a loop device
               /sbin/losetup $LOOP $BASE/$FILE 2> /dev/null
               RET=$?
               if [[ $RET != 0 ]]
               then
                    echo "Error : Cannot create loop device $LOOP."
                    RET=3
               else
                    echo $LOOP > $LOOPDEVICE
                    REBOOT="yes"
                    RETV=1
                    # FIRST try USB key
                    # USB is not mounted
                    if [[ -b $BOOTKEYDEVICE ]] && [[ $SHORTCUT = "yes" ]]
                    then
                         echo "USB key ..."
                         /bin/mount $BOOTKEYDEVICE /mnt 2> /dev/null
                           
                         if [[ -f $BOOTKEY ]] && [[ "x${OTP}" != "x"  ]]
                         then
                            /bin/rm $TMPBOOTKEY 2> /dev/null
                            /bin/sha256sum $BOOTKEY | cut -c-64 > $TMPBOOTKEY
                            if /bin/diff $BOOTKEYHASH $TMPBOOTKEY 2> /dev/null > /dev/null
                            then
                                 orange "Trying to read key from device" > $TERMINAL
                                 echo > $TERMINAL
                                 # get the passphrase from HEXOR
                                 PW=$($BASE/hexor $(cat $BOOTKEY) $OTP)
                                 echo $PW | ${CREATE} $LOOP $NAME
                                 RETV=$?
                                 KEYSOURCE=USB
                                 PW="7.,<hwo4#46hdksi*990-089kl.,>;sjiwudhhhee[wyr@#1teysm,mz"
                                 unset PW
                            fi
                            # destroy the temporary hashfile
                            rm $TMPBOOTKEY 2> /dev/null
                         else
                            orange "USB device is not available"
                         fi
                         umount /mnt
                    else
                         RETV=5
                    fi
                    

		    if [[ $RETV != 0 ]] && [[ $SHORTCUT = "yes" ]]
		    then
                         echo "USB dongle ..."
                         # if no $BOOTKEYDEVICE available
			 # try USB dongle
                         orange "Trying to read key from USB dongle" > $TERMINAL
                         FINGERPRINT=$(lsusb | sha256sum | cut -c-40) 
			 PW=$($BASE/hexor $FINGERPRINT $USBOTP)

                         # TEST is used to check if the recovered passphrase will work
                         # As with using sha256 as the hash instead of ripemd160 hashing 
                         # only once would result to have the AES-key = sha256sum($PW) stored
			 # in the root-readable file.
                         # So hashing twice will protect both $PW and the AES-key 

			 TEST=$(echo -n "$PW" | sha256sum | sha256sum | cut -c-40) 
			 if [ "x$TEST" = "x$KEYHASH" ]
			 then
                                 echo $PW | ${CREATE}  $LOOP $NAME
                                 RETV=$?
                                 KEYSOURCE=DONGLE
			 else
                                 orange "USB dongle is not available"
                                 RETV=7
			 fi

                         if [[ $RETV != 0 ]]
                         then
                              /sbin/cryptsetup remove $NAME
			 fi
                         PW="7.,<hwo4#46hdksi*990-089kl.,>;sjiwudhhhee[wyr@#1teysm,mz"
                         unset PW
		    fi
                    
                    # try to catch a passphrase from user input
                    if [[ $RETV != 0 ]]
                    then
                         green "Trying to read key from $TERMINAL " > $TERMINAL
                         # try terminal
                         green "Please enter your PASSPHRASE:     " > $TERMINAL

                         $ASKPASS  " " < $TERMINAL | ${CREATE} $LOOP $NAME
                         RETV=$?
                         ls -l /dev/mapper/$NAME > $TERMINAL
                    fi
                    
		    if [[ $RETV != 0 ]]
                    then
                         # cryptsetup failed
                         echo "Error : Cannot create /dev/mapper/$NAME."
                         orange "Error : Cannot create /dev/mapper/$NAME."
                         /sbin/losetup -d $LOOP
                         rm $LOOPDEVICE
                         RET=4
                    else
                         # fsck
                         echo
                         /sbin/fsck -y /dev/mapper/$NAME 2> /dev/null > /dev/null
                         RETV=$?
                         if [[ $RETV != 0 ]]
                         then
                              if [[ $KEYSOURCE = "USB" ]]
                              then
                                   orange "The passphrase provided by the USB memory key is incorrect"
                              fi
                              if [[ $KEYSOURCE = "DONGLE" ]]
                              then
                                   orange "The passphrase provided by the USB DONGLE is incorrect"
                                   orange "Please update USBOTP"
                              fi
                         fi
                         # try to mount new filesystem
                         /bin/mount /dev/mapper/$NAME /$NAME 2> /dev/null > /dev/null
                         if [[ $? != 0 ]]
                         then
                              # mount failed
                              umountfilesystem
                              mounterror
                              RET=5
                              if [[ $REBOOT == "yes" ]]
                              then
                                   # REBOOT, if not blocked
                                   if [[ -f $BLOCKFILE ]]
                                   then
                                        errorreboot
                                        /sbin/reboot
                                   else
                                        echo
                                        echo "  To stop the boot process here, "
                                        echo "  create the file /usr/lib/secureboot/.rebootonfailure"
                                        echo
                                   fi

                              fi
                         else
                              # everything is fine!
                              green "/secure mounted successfully" > $TERMINAL
                              /bin/df | /bin/grep /dev/mapper/$NAME
                              RET=0
                         fi

                    fi
               fi
          fi
     fi
}


##########################################################################
start() {
     mountfilesystem
     sleep 1

     if [[ $RET = 0 ]]
     then
         success
     fi
}


##########################################################################
stop() {

     umountfilesystem

     echo

     if [[ $RET != 0 ]]
     then
          echo -en "\\0033[1;31m"
          echo "################ FAILED #################"
          echo -en "\\0033[0;39m"
          echo
     else
          echo -en "\\0033[1;32m"
          echo "################ REMOVED ################"
          echo -en "\\0033[0;39m"
          echo
     fi
}

##########################################################################
halt () {
    haltfilesystem
}

##########################################################################
restart() {
        stop
        start
}

case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  halt)
        halt
        ;;
  cleanup)
        cleanup
        ;;
  restart)
        restart
        ;;
  force-reload)
        restart
        ;;
  *)
        echo "Usage: /usr/lib/secureboot/secureboot2 [start|stop|halt|cleanup|restart|force-reload]"
        exit 1
esac

exit $RET
