#!/bin/sh
### BEGIN INIT INFO
# Provides:            integrity
# Required-Start:      mountfs udev
# Should-Start:
# Required-Stop:
# Should-Stop:
# Default-Start:       3 4 5
# Default-Stop:
# Short-Description:   Enabling IMA/EVM.
# Description:         This service tries to load IMA/EVM keys and
#                      policy and enables the IMA/EVM subsytem.
### END INIT INFO

# A script to configure IMA and EVM enforcement at initrd time.
# Copyright (C) 2019  Mikhail Efremov.
# Copyright (C) 2024  Paul Wolneykien.
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

. /etc/init.d/template

IMA_POLICY_ADMIN=/etc/integrity/policy
IMA_POLICY_DEFAULT=/usr/share/integrity/policy
SECFS=/sys/kernel/security

[ ! -e /etc/integrity/config ] || . /etc/integrity/config
[ ! -e /etc/sysconfig/integrity ] || . /etc/sysconfig/integrity

IMA_KEYRING="${IMA_KEYRING:-_ima}"
EVM_KEYRING="${EVM_KEYRING:-_evm}"

load_keys() {
    local keyring="$1"
    local suf="${2:-$keyring}"

    keyring_id="$(keyctl id %keyring:"$keyring")"
    if [ -z "$keyring_id" ]; then
	keyring_id="$(keyctl newring "$keyring" @u)"
    fi

    [ -n "$keyring_id" ] || return 1

    find /etc/keys -name "*$suf.der" | sort -u | \
	while read -r cert; do
	    key_id="$(evmctl import "$cert" "$keyring_id")" || exit $?
	    [ -n "$key_id" ] || exit 1
	    if [ "${PROTECT_KEYS:-0}" -ne 0 ]; then
		# protecting key from revoking (against DoS)
		keyctl setperm "$key_id" 0x0b0b0000 || exit $?
	    fi
	done || return $?
}

protect_keyring() {
    local keyring="$1"
    local keyring_id=
    keyring_id="$(keyctl id %keyring:"$keyring")" ||:
    if [ -n "$keyring_id" ]; then
	keyctl setperm "$keyring_id" 0x0b0b0000 || return $?
    fi
}

do_prestart() {
    echo '*** do_prestart() ***' >&2
    # Configure OpenSSL
    mkdir -p /var/lib/ssl
    cat <<EOF >/var/lib/ssl/openssl.cnf || return $?
HOME = .
openssl_conf = openssl_init

[openssl_init]
EOF

    # load GOST modules if needed
    if grep -qsw 'ima_hash=streebog.*' /proc/cmdline; then
	modprobe ecrdsa_generic && \
        modprobe streebog_generic || return $?
        cat <<EOF >>/var/lib/ssl/openssl.cnf || return $?
engines = engine_section

[engine_section]
gost = gost_section

[ gost_section ]
engine_id = gost
default_algorithms = ALL
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet
EOF
    fi

    if [ -n "$SECONDARY_SUFFIX" ]; then
	load_keys '.secondary_trusted_keys' "$SECONDARY_SUFFIX" || \
	    return $?
    fi

    load_keys "$IMA_KEYRING" '_ima' || return $?

    [ "${PROTECT_KEYRINGS:-0}" -eq 0 ] || \
	protect_keyring "$IMA_KEYRING" || return $?

    if [ -n "${WITH_EVM:-}" ]; then
	load_keys "$EVM_KEYRING" '_evm' || return $?

	# import EVM encrypted key
	keyctl show | grep -q kmk-user || keyctl add user kmk-user "$(cat /etc/keys/kmk-user.blob)" @u
	keyctl add encrypted evm-key "load $(cat /etc/keys/evm-key.blob)" @u

	[ "${PROTECT_KEYRINGS:-0}" -eq 0 ] || \
	    protect_keyring "$EVM_KEYRING" || return $?
    fi
}

do_start() {
    echo '*** do_start() ***' >&2

    local need_unmount=
    if ! grep -q  "$SECFS" /proc/mounts; then
	mount -n -t securityfs securityfs "$SECFS" || return 1
	need_unmount=1
    fi

    if [ -n "${WITH_EVM:-}" ]; then
	# enable EVM
	case "$WITH_EVM" in
	    0x*)
		echo "$WITH_EVM"
		;;
	    *)
		echo '0x80000002'
		;;
	esac >"$SECFS"/evm
    fi

    # load IMA policy
    cat "$IMA_POLICY" >"$SECFS"/ima/policy || return 1

    if [ -n "$need_unmount" ]; then
	umount "$SECFS"
    fi
}

start() {
    local ret=0

    if [ -f "$IMA_POLICY_ADMIN" ]; then
	IMA_POLICY="$IMA_POLICY_ADMIN"
    elif [ -f "$IMA_POLICY_DEFAULT" ]; then
	IMA_POLICY="$IMA_POLICY_DEFAULT"
    fi

    if grep -qsw 'ima_appraise=enforce' /proc/cmdline &&
       [ -n "$IMA_POLICY" ]
    then
    	echo_msg "Loading IMA/EVM keys..."
	if do_prestart 1>/tmp/integrity.log 2>&1; then
	    echo_success
	else
	    [ "${IGNORE_KEY_ERRORS:-1}" -ne 0 ] || ret=1
	    echo_failure
	fi
	echo_newline

	if [ "$ret" -eq 0 ]; then
	    echo_msg "Enabling IMA/EVM..."
	    if do_start 1>>/tmp/integrity.log 2>&1; then
		echo_success
	    else
		ret=$?
		echo_failure
	    fi
	    echo_newline
	fi
    fi

    if [ "$ret" -ne 0 ]; then
	if [ -x /etc/integrity/on-initrd-error ]; then
	    /etc/integrity/on-initrd-error
	    exit $?
	elif [ -e /etc/integrity/reboot-on-initrd-error ]; then
	    echo "WARNING! Failed to enable IMA/EVM. The system will reboot within 30 sec..." >&2
	    sleep 30
	    echo '6' >/.initrd/telinit
	fi
	exit 1
    fi
}

switch "${1-}"
