#!/bin/bash
# barium helper scripts
# author: rosalinux.ru: betcher_

# Не используем root_only и цвета из b-lib/lib,
# а также SYSMNT из /etc/initvars чтобы не сорсить ничего в скрипт

PATH="/bin:/usr/bin:/sbin:/usr/sbin"
unset BOOTPART
unset SET
unset FORCE

red='\033[0;31m'
green='\033[1;32m'
yellow='\033[0;33m'
default='\033[0m'

fprint=/root/.fingerprint

SYSMNT="/.memory"
if [ $(id -un) != "root" ] ; then
    echo -e "${red}You must be root !!!${default}"
    exit 1
fi

HLP () {
    placeholder__="$(basename $0 |sed 's/./ /g')"
    echo "
$(basename $0) - Utility for checking unauthorized system changes

Usage:
    $(basename $0)                     - set fingerprint if file not exists or
    $placeholder__                       check if fingerprint found
    $(basename $0) -f                  - set fingerprint
    $(basename $0) /home/user/.fprint  - set or check fingerprint with own path

Parameters:
    -h | --help            - this help
    -s | --set             - set new fingerprint
    -b | --boot-part       - specify boot partition
    -f | --force           - skip warnings
"
    exit
}

get_token_lib(){
    ret=$(barium token |cut -f2 -d ' ')
    if [ $(echo $ret |wc -w) -eq 1 ] ; then
      if echo $ret | grep -qE '.*\.so$' ; then
        echo $ret
        return 0
      elif echo $ret | grep -qE '.*\.xzm$' ; then
        echo "Token lib not found, you need to inst mod $ret" >&2
        return 1
      else
        echo "Token not found? or token unknown" >&2
        return 1
      fi
    elif [ $(echo $ret |wc -w) -eq 0 ] ; then
      echo "Token not found? or token unknown" >&2
      return 1
    else
      echo "More then one token plugged" >&2
      return 1
    fi
}

get_token_label() {
    local module="$1"
    local labels
    labels=$(pkcs11-tool --module "$module" --list-token-slots 2>/dev/null |
             grep -E '^[[:space:]]*token label' |
             awk '{print $NF}')

    if [ -z "$labels" ]; then
        echo "Error: tokens not found" >&2
        echo "not_found"
        return 1
    fi

    local label_count=$(echo "$labels" | wc -l)

    if [ $label_count -eq 1 ]; then
        echo "$labels"
    else
        echo "More than one token labels found:" >&2
        local i=1
        while IFS= read -r line; do
            echo "  $i) $line" >&2
            i=$((i + 1))
        done <<< "$labels"

        echo -n "Choose: (1-$label_count): " >&2
        read -r choice
        if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le $label_count ]; then
            echo "$labels" | sed -n "${choice}p"
        else
            echo "Error: wrong number" >&2
            echo "not_found"
            return 1
        fi
    fi
}


sign () {
    local fprint="$1"
    local module=$(get_token_lib)
    if [ -z "$module" ]; then
        echo "Error: PKCS11 module not found" >&2
        return 1
    fi
    
    local label=$(get_token_label "$module")
    if [ -z "$label" ] || [ "$label" = "not_found" ]; then
        echo "Error: token label not found" >&2
        return 1
    fi
    
    echo "TOKEN_LABEL=$label" >> "$fprint"
    
    # Вычисляем SHA256 хеш в бинарном формате
    local hash_file="${fprint}.hash"
    openssl dgst -sha256 -binary "$fprint" > "$hash_file"
    
    # Подписываем бинарный хеш с помощью токена
    if pkcs11-tool --module "$module" --login --sign \
        --input-file "$hash_file" \
        --output-file "${fprint}.sign" \
        --mechanism RSA-PKCS --token-label "$label" ; then
        echo "Success: $fprint signed successfully"
        rm -f "$hash_file"
    else
        echo "Error: $fprint not signed." >&2
        rm -f "$hash_file"
        return 1
    fi
}


setfingerprint () {
    setbootloader () {
        [ "$BOOTPART" ] || BOOTPART=$(realpath /dev/disk/by-label/BARIUM_EFI 2>/dev/null)
        if [ $(echo "$BOOTPART" | wc -w) -ne 1 ] || [ "$BOOTPART" = '/dev/disk/by-label/BARIUM_EFI' ] || [ -z "$BOOTPART" ]; then
            echo "Please specify boot partition device name (aka /dev/sda1 )"
            read BOOTPART
        fi
        PARTUUID=$(blkid "$BOOTPART" -s PARTUUID -o value)
        if [ -z "$PARTUUID" ] ; then
            echo "Unknown device $BOOTPART"
            exit ${LINENO}
        fi
        md5sum=$(md5sum "$BOOTPART" |cut -f1 -d ' ')
        echo "BOOTPARTUUID=$PARTUUID" >> "$fprint"
        echo "BOOTPARTMD5=$md5sum" >> "$fprint"
    }

    setmodules () {
        for module in $(barium ls --raw '$dname_source/$bname_source' | tail -n +2) ; do
            echo "$module processing..."
            echo "$SYSMNT/layer-base/0/_REPOLIST" | grep -q "$(echo $module |cut -f 5,6 -d '/')" || \
            echo "md5sum ${module}#$(md5sum "$module" |cut -f1 -d ' ')" >> "$fprint"
        done
    }

    cmdline () {
        echo "CMDLINE=$(cat /proc/cmdline)" >> "$fprint"
    }

    devSN (){
        SN=$(getSN)
        echo "DEVSERIALNUMBER=$SN" >> "$fprint"
    }

    devSN
    setbootloader
    cmdline
    setmodules
    sign "$fprint"
    # touch заствит overly/aufs создать копию скрипта в changes, находящийся на шифрованном разделе. Подменить сложнее.
    touch "$0"
}

check () {
    local fprint="$1"
    EXITCODE=0

    get_fprint="cat ${fprint}"
    token_label=$($get_fprint 2>/dev/null |grep TOKEN_LABEL |cut -f2 -d '=')

    if [ ! -f "${fprint}.sign" ] ; then
        if [ ! "$FORCE" ] ; then
            echo -e "${red}Warning! Fingerprint file not signed!${default}"
            echo "ENTER to continue, ctrl-c to abort"
            read qqq
        fi
    elif [ -n "$token_label" ]; then
        local module=$(get_token_lib)
        if [ -n "$module" ]; then
            # Вычисляем хеш файла для проверки
            local hash_file="${fprint}.verify.hash"
            openssl dgst -sha256 -binary "$fprint" > "$hash_file"
            
            # Проверяем подпись хеша
            if ! pkcs11-tool --module "$module" --verify \
                --input-file "$hash_file" \
                --signature-file "${fprint}.sign" \
                --mechanism RSA-PKCS --token-label "$token_label" 2>/dev/null; then
                echo -e "${red}Sign check for $fprint - failed!${default}"
                echo -e "${red}Fingerprint file may be compromised!${default}"
                echo "ENTER to continue, ctrl-c to abort"
                read qqq
            else
                echo -e "Signature verification -- ${green}OK${default}"
            fi
            rm -f "$hash_file"
        else
            echo -e "${yellow}Warning: Cannot verify signature - token module not available${default}"
        fi
    fi

    BOOTPARTUUID=$($get_fprint 2>/dev/null |grep BOOTPARTUUID |cut -f2 -d '=')
    BOOTPARTMD5=$($get_fprint 2>/dev/null |grep BOOTPARTMD5 |cut -f2 -d '=')

    if [ -n "$BOOTPARTUUID" ] && [ -n "$BOOTPARTMD5" ]; then
        REALMD5=$(md5sum "/dev/disk/by-partuuid/$BOOTPARTUUID" 2>/dev/null |cut -f1 -d ' ')
        if [ "$BOOTPARTMD5" != "$REALMD5" ] ; then
            echo_exit "Boot partition compromised!"
            EXITCODE=$(( EXITCODE + 1 ))
        else
            checked "Bootloaders partition"
        fi
    else
        echo_exit "Boot partition information not found in fingerprint!"
        EXITCODE=$(( EXITCODE + 1 ))
    fi

    DEVSERIALNUMBER=$($get_fprint 2>/dev/null |grep DEVSERIALNUMBER |cut -f2 -d '=')
    SN=$(getSN)
    if [ "$DEVSERIALNUMBER" != "$SN" ] ; then
        echo_exit "device serial number modified. Wrong device!!!"
        EXITCODE=$(( EXITCODE + 1 ))
    else
        checked "Serial number checked"
    fi

    CMDLINE=$($get_fprint 2>/dev/null |grep CMDLINE |sed 's/CMDLINE=//')
    REALCMDLINE=$(cat /proc/cmdline)
    if [ "$CMDLINE" != "$REALCMDLINE" ] ; then
        echo_exit "kernel cmdline modified"
        EXITCODE=$(( EXITCODE + 1 ))
    else
        checked "Kernel cmdline"
    fi

    WARNINGS=$(barium update -cl 2>&1 |grep 'not equal')
    NONTRUSTED=''
    for module in  $(barium ls --raw '$dname_source/$bname_source'| tail -n +2) ; do
        echo "$WARNINGS" |grep -q "$(basename "$module")" && echo_exit "Check $module" && continue
        grep -qw "$(basename "$module")" "${SYSMNT}/layer-base/0/_REPOLIST" && checked "Check $module" && continue
        md5=$($get_fprint 2>/dev/null |grep "md5sum $module" |cut -f2 -d '#')
        if [ -z "$md5" ] ; then
            EXITCODE=$(( EXITCODE + 1 ))
            echo_exit "Unknown $module"
            NONTRUSTED="$module $NONTRUSTED"
        elif [ "$md5" != $(md5sum "$module" |cut -f1 -d ' ') ] ; then
            EXITCODE=$(( EXITCODE + 1 ))
            echo_exit "Check $module"
            NONTRUSTED="$module $NONTRUSTED"
        else
            checked "Check $module"
        fi
    done

    echo -e "${yellow}\nBarium update -cl warnings:\n=============================================================================${default}"
    echo "$WARNINGS"
    echo -e "\nExecutable files in non trusted layers:"
    echo -e "\n${yellow}Changes:\n=============================================================================${default}"
    find "$SYSMNT/changes" -executable -type f 2>/dev/null
    for a in $NONTRUSTED ; do
        BUNDLE=$(barium ls --raw '$bundle $dname_source/$bname_source' |grep "$a")
        echo -e "\n${yellow}${a}:${default}\n============================================================================="
        find "$BUNDLE" -executable -type f 2>/dev/null | while read a ; do
            REWRITE="\e[25D\e[1A\e[K"
            [ $str_n ] || str_n=1
            if [ $str_n -gt 20 ] ; then
                sleep 0.0001
                echo -e "${REWRITE}And ${red}$str_n${default} other non trusted executable files..."
            else
                echo "$a"
            fi
        str_n=$(( str_n + 1 ))
        done
    done
    return $EXITCODE
}

getSN(){
    local SN="unknown"
    PART=$(realpath /dev/disk/by-label/ROSA-SYSTEM 2>/dev/null)
    [ "$PART" !=  '/dev/disk/by-label/ROSA-SYSTEM' ] && [ -n "$PART" ] && \
    SN=$(udevadm info -a -p  $(udevadm info -q path -n "$PART" 2>/dev/null) 2>/dev/null |grep serial |head -n1 |cut -f3 -d '=')
    echo "$SN"
}

checked () {
    echo -e "$@ -- ${green}OK${default}"
}

echo_exit () {
    echo -e "$1 -- ${red}Warning!${default}"
    [ -n "$2" ] && exit $2
}

while [ -n "$1" ] ; do
    case "${1}" in
    "-h" | "--help")  HLP ;;
    "-s" | "--set" )  SET=yes ;;
    "-b" | "--boot-part" ) shift
                           BOOTPART="$1" ;;
    "-f" | "--force") FORCE=yes ;;
    *) FPRINT="$1";;
    esac
    shift
done

if ! df "${SYSMNT}/layer-base/1/" 2>/dev/null |grep -q '/dev/mapper/' && [ ! "$FORCE" ] ; then
    echo -e "${red}Warning, partition for ROSA-DATA not encrypted,${default}"
    echo -e "${red}this use cannot be considered protection.${default}"
    echo "ENTER to continue, ctrl-c to abort"
    read qqq
    echo ''
fi

[ -n "$FPRINT" ] && fprint="$(realpath "$FPRINT")"

if [ -f "$fprint" ] && [ ! "$SET" ] ; then
    echo -e "fingerprint: $fprint - found.\n${yellow}Check...${default}"
    check "$fprint"
else
    echo -e "fingerprint: ${fprint}.\n${yellow}Set...${default}"
    rm -f "${fprint}" "${fprint}.sign"
    setfingerprint
fi
