#!/bin/sh
ERTP_MODULE="eset_rtp"
EWAP_MODULE="eset_wap"
ERTP_SRCS_DIR="/var/opt/eset/efs/ertp"
EWAP_SRCS_DIR="/var/opt/eset/efs/ewap"
SIGN_ALL_KERNELS=0
KEY_ENROLLED=0
INTERACTIVE_MODE=1
SIGN_EWAP="1"
help() {
FOLD="$(which fold 2>/dev/null || true)"
if [ -n "$FOLD" ]; then
FOLD="$FOLD -s"
TPUT="$(which tput 2>/dev/null || true)"
if [ -n "$TPUT" ]; then
COLS="$(tput cols || true)"
if [ -n "$COLS" ]; then
FOLD="$FOLD -w $COLS"
fi
fi
else
FOLD="cat"
fi
KERNEL_MODULES_TO_COPY_STR="/lib/modules/<kernel-version>/eset/efs/$ERTP_MODULE.ko"
if [ -n "$SIGN_EWAP" ]; then
if [ "$SIGN_EWAP" -eq 1 ]; then
KERNEL_MODULES_TO_COPY_STR="${KERNEL_MODULES_TO_COPY_STR} and ${EWAP_MODULE}.ko"
fi
fi
$FOLD << HELP
This is ESET Server Security 12.2.69.0 script for signing and building kernel modules.
Usage:
$0 -h
$0 [-d PUBKEY -p PRIVKEY] [-k KERNEL] [-a]
Without parameters, the script runs in interactive mode.
Options:
-h, --help
Show this help of command-line arguments
-d, --public-key
Set the path to the public key in DER format to use for signing
-p, --private-key
Set the path to the private key to use for signing
-k, --kernel
Set the kernel name whose kernel modules have to be signed (and built if missing).
The current kernel is selected by default
-a, --kernel-all
Sign (and build) kernel modules on all existing kernels which contain headers
Kernel module transfer:
Kernel module for a specific kernel can be built and signed on one machine (which contains a private key) and then transferred to other machines with the same kernel. To do this, simply copy ${KERNEL_MODULES_TO_COPY_STR} from the build machine to the same path on the target machine.
It's essential to call "depmod <kernel-version>" and to restart ESET Server Security on the target machine to update the modules table after copying.
HELP
}
print_error() {
echo "$1" 1>&2
}
while [ $# -gt 0 ]; do
key="$1"
case $key in
-h|--help) help; exit 0;;
-d|--public-key)
PUB_KEY="$2"
if ! [ -f "$PUB_KEY" ]; then
print_error "$key has to contain a valid path to DER key!"
exit 1
fi
shift 2 # past argument & value
;;
-p|--private-key)
PRIV_KEY="$2"
if ! [ -f "$PRIV_KEY" ]; then
print_error "$key has to contain a valid path to the private key!"
exit 1
fi
shift 2 # past argument & value
;;
-k|--kernel)
if [ "$#" -lt 2 ]; then
print_error "$key option requires a value"
exit 1
fi
CUSTOM_KERNEL="$2"
shift 2 # past argument & value
;;
-a|--kernel-all)
SIGN_ALL_KERNELS=1
shift # past argument
;;
*) # unknown option
print_error "Unknown option $key"
help
exit 1
;;
esac
done
# helper functions
ask_for_keys() {
printf "Please enter the path to the private key, which can be used for module signing: "
read PRIV_KEY
if ! [ -f "$PRIV_KEY" ]; then
print_error "Path to the private key is invalid!"
exit 1
fi
echo
printf "Please enter the path to the public key in DER format, which can be used for module signing: "
read PUB_KEY
if ! [ -f "$PUB_KEY" ]; then
print_error "Path to the public key is invalid!"
exit 1
fi
}
KEYS_GENERATED=0
KEYS_DIR=
generate_keys() {
printf "Do you want to generate new keys? [y/n] "
read r
case $r in
[Yy]|[Yy][Ee][Ss]) ;;
*) print_error "Since you neither provided keys to sign the modules with nor agreed to generate keys, the modules remain unsigned, and the script closes."
exit 1;;
esac
KEYS_DIR=$(mktemp -d) || exit 1
mount -t tmpfs -o size=100k,mode=600 tmpfs "$KEYS_DIR" || exit 1
PRIV_KEY="$KEYS_DIR/efs_mok.priv"
PUB_KEY="$KEYS_DIR/efs_mok.der"
OPENSSL_VERSION=$(openssl version | cut -d" " -f 2 | grep -o -E '[0-9,.]+' | tr -d '.')
if [ "$OPENSSL_VERSION" -ge 111 ]; then
OPENSSL_ADDITIONAL_FLAG="-addext extendedKeyUsage=codeSigning"
fi
# Disabling shellcheck rule for one line - SC2086 = missing quotation.
# OPENSSL_ADDITIONAL_FLAG needs to be expanded into multiple arguments for openssl req
# therefore it cannot be surrounded by quotation marks.
#
# shellcheck disable=SC2086
if ! openssl req -new -x509 -newkey rsa:4096 -keyout "$PRIV_KEY" -outform DER -out "$PUB_KEY" -nodes -days 1825 -subj "/CN=Custom MOK for ESET Server Security/" $OPENSSL_ADDITIONAL_FLAG; then
print_error "Cannot generate new keys. Please try it manually and then provide their path when requested by the script."
exit 1
fi
chmod 600 "$PRIV_KEY"
trap "remove_generated_keys && exit" EXIT
KEYS_GENERATED=1
echo "New keys have been generated."
echo
}
build_kernel_module() {
MODULE_NAME="$1"
MODULE_SRCS_DIR="$2"
KERNEL_DIR="$3"
KERNEL_VERSION="$(basename "$KERNEL_DIR")"
MODULE_TARGET_PATH="$4"
echo "Compiling kernel module in $KERNEL_DIR"
( \
cd "$MODULE_SRCS_DIR" && \
KDIR="${KERNEL_DIR}/build" make -B modules >/dev/null && \
cp "${MODULE_NAME}/${MODULE_NAME}.ko" "$MODULE_TARGET_PATH"
depmod "$KERNEL_VERSION" \
) || (print_error "Error compiling ${MODULE_NAME} kernel module" && exit 1)
}
sign_module() {
KERNEL_DIR="$1"
MODULE_TO_SIGN="$2"
if which kmodsign >/dev/null 2>&1; then
SIGNER_UTIL="kmodsign"
elif [ -f "$KERNEL_DIR/build/scripts/sign-file" ]; then
SIGNER_UTIL="$KERNEL_DIR/build/scripts/sign-file"
else
print_error "We cannot find any script to sign the kernel module. Please sign it manually or install kmodsign or sign-file in headers/scripts."
exit 1
fi
if ! $SIGNER_UTIL sha512 "$PRIV_KEY" "$PUB_KEY" "$MODULE_TO_SIGN"; then
print_error "Kernel module $MODULE_TO_SIGN cannot be signed. Please check if $PRIV_KEY and $PUB_KEY are valid keys."
exit 1
fi
}
build_and_sign_kernel_module() {
MODULE_NAME="$1"
MODULE_SRCS_DIR="$2"
KERNEL_DIR="$3"
KERNEL_VERSION="$(basename "$KERNEL_DIR")"
MODULE_TARGET_PATH="$4"
if ! [ -f "$MODULE_TARGET_PATH" ]; then
build_kernel_module "$MODULE_NAME" "$MODULE_SRCS_DIR" "$KERNEL_DIR" "$MODULE_TARGET_PATH"
fi
sign_module "$KERNEL_DIR" "$MODULE_TARGET_PATH"
echo "Kernel module $MODULE_TARGET_PATH has been signed."
}
build_and_sign_kernel_modules() {
KERNEL_DIR="$1"
if ! [ -d "$KERNEL_DIR/build" ]; then
echo "Kernel directory $KERNEL_DIR does not contain headers, skipping..."
return
fi
ESET_MODULE_DIR="$KERNEL_DIR/eset/efs"
mkdir -p "$ESET_MODULE_DIR"
ESET_ERTP_MODULE_PATH="$ESET_MODULE_DIR/$ERTP_MODULE.ko"
build_and_sign_kernel_module "$ERTP_MODULE" "$ERTP_SRCS_DIR" "$KERNEL_DIR" "$ESET_ERTP_MODULE_PATH"
if [ -n "$SIGN_EWAP" ]; then
if [ "$SIGN_EWAP" -eq 1 ]; then
ESET_EWAP_MODULE_PATH="$ESET_MODULE_DIR/$EWAP_MODULE.ko"
build_and_sign_kernel_module "$EWAP_MODULE" "$EWAP_SRCS_DIR" "$KERNEL_DIR" "$ESET_EWAP_MODULE_PATH"
fi
fi
}
sign_all_kernel_modules() {
for KERNEL_DIR in /lib/modules/*; do
build_and_sign_kernel_modules "$KERNEL_DIR"
done
}
enroll_key() {
echo "mokutil will ask you to set a password. Remember the password. After reboot, enter UEFI settings, select \"Enroll MOK\" and enter the password you remembered."
echo "Note that MOK (Machine Owner Key) cannot be authorized remotely. Therefore you have to have physical access to the machine to update UEFI settings."
mokutil --import "$PUB_KEY" || exit 1
KEY_ENROLLED=1
}
save_keys() {
echo "Please provide the path to the directory where you want the generated keys to be copied. Keys should be secured and not accessible for processes or other users. ESET Server Security does not store them and will remove its copy at the end of this script."
printf "Directory to store keys: "
read directory
if ! [ -d "$directory" ]; then
print_error "Not a directory!"
save_keys
elif ! cp "$PUB_KEY" "$PRIV_KEY" "$directory"; then
print_error "Error during cp. Please check the permissions."
save_keys
fi
}
remove_generated_keys() {
umount -f "$KEYS_DIR"
rm -rf "$KEYS_DIR"
}
restart_product() {
systemctl restart efs >/dev/null
}
# main
if [ "$(id -u)" -ne 0 ]; then
print_error "ERROR: This script must be run as root."
exit 1
fi
if [ -z "$PUB_KEY" ] || [ -z "$PRIV_KEY" ]; then
printf "Private & public key has not been given via arguments. Do you want to provide them? (recommended to provide already enrolled keys) [y/n] "
read r
case $r in
[Yy]|[Yy][Ee][Ss]) ask_for_keys;;
*) generate_keys;;
esac
else
INTERACTIVE_MODE=0
fi
if [ "$SIGN_ALL_KERNELS" -eq 1 ]; then
sign_all_kernel_modules
elif [ -n "$CUSTOM_KERNEL" ]; then
build_and_sign_kernel_modules "/lib/modules/$CUSTOM_KERNEL"
else
build_and_sign_kernel_modules "/lib/modules/$(uname -r)"
fi
if [ "$INTERACTIVE_MODE" -eq 0 ]; then
restart_product
exit
fi
if mokutil --test-key "$PUB_KEY" >/dev/null; then
echo
printf "Public key %s is not imported in UEFI. Do you want to enroll it now? [y/n] " "$PUB_KEY"
read r
case $r in
[Yy]|[Yy][Ee][Ss]) enroll_key;;
*) echo "Please enroll it at UEFI manually using \"mokutil --import /path/to/key.der\", and approve upon system reboot. Otherwise, our kernel module cannot load."
;;
esac
echo
else
echo "Restarting ESET Server Security to use the new signed kernel module..."
restart_product
exit
fi
if [ "$KEYS_GENERATED" -eq "1" ]; then
if [ "$KEY_ENROLLED" -eq "0" ]; then
save_keys
else
printf "Do you want to save the generated keys? [y/n] "
read r
case $r in
[Yy]|[Yy][Ee][Ss]) save_keys;;
*) ;;
esac
fi
fi
if [ "$KEY_ENROLLED" -eq "1" ]; then
printf "Do you want to reboot the system and approve the new MOK (Machine Owner Key) in UEFI? (recommended) [y/n] "
read -r r
case $r in
[Yy]|[Yy][Ee][Ss]) reboot;;
*) echo "Please do that manually.";;
esac
fi