/
#!/usr/bin/bash

umask 022

usage=0
enable_fips=
check=0
boot_config=1
err_if_disabled=0
fips_install_complete=0
output_text=1

is_ostree_system=0
if test -f /run/ostree-booted -o -d /ostree; then
    is_ostree_system=1
fi

enable2txt () {
    case "$1" in
        0)
            echo "disabled"
            ;;
        1)
            echo "enabled"
            ;;
    esac
}

cond_echo () {
    if test "$output_text" != 0;then
        echo "$@"
    fi
}

while test $# -ge 1 ; do
    case "$1" in
        --enable)
            enable_fips=1
            ;;
        --disable)
            enable_fips=0
            ;;
        --check)
            check=1
            enable_fips=2
            ;;
        --is-enabled)
            check=1
            enable_fips=2
            err_if_disabled=1
            output_text=0
            ;;
        --no-bootcfg)
            boot_config=0
            ;;
        *)
            usage=1
            ;;
    esac
    shift
done

if test $usage = 1 -o x$enable_fips = x ; then
    echo "Check, enable, or disable (unsupported) the system FIPS mode."
    echo "usage: $0 --enable|--disable [--no-bootcfg]"
    echo "usage: $0 --check"
    echo "usage: $0 --is-enabled"
    exit 2
fi

# We don't handle the boot config on OSTree systems for now; it is assumed to be
# handled at a higher level. E.g. in Fedora CoreOS and RHEL CoreOS, it is
# intrinsically tied to the firstboot procedure.
if test "$is_ostree_system" = 1 && test "$enable_fips" = 1 && test "$boot_config" = 1; then
    cond_echo "Cannot perform boot config changes on OSTree systems (use --no-bootcfg)"
    exit 1
fi

if test -f /etc/system-fips ; then
    # On OSTree systems, /etc/system-fips in the real root marks completion.
    if test ! -d /boot -o "$is_ostree_system" = 1 -o ! -x /usr/bin/lsinitrd -o x"$(/usr/bin/lsinitrd -f etc/system-fips 2>/dev/null || test $? = 2 && echo y)" != x ; then
        fips_install_complete=1
    fi
fi

if test $check = 1 ; then
    test $fips_install_complete = 0 && cond_echo "Installation of FIPS modules is not completed."
    fips_enabled=$(cat /proc/sys/crypto/fips_enabled)
    cond_echo "FIPS mode is $(enable2txt $fips_enabled)."
    if test "$fips_enabled" = 1 ; then
        if test $fips_install_complete = 0 ; then
            cond_echo "Inconsistent state detected."
            exit 1
        fi
        current="$(cat /etc/crypto-policies/state/current)"
        if test "$(echo $current | cut -f 1 -d :)" != "FIPS" ; then
            cond_echo -n "The current crypto policy ($current) "
            cond_echo -n 'neither is the FIPS policy '
            cond_echo 'nor is based on the FIPS policy.'
            cond_echo 'Inconsistent state detected.'
            exit 1
        fi
    else
        if test $fips_install_complete = 1 ; then
            cond_echo "Inconsistent state detected."
            exit 1
        fi
        current="$(cat /etc/crypto-policies/state/current)"
        if test "$(echo $current | cut -f 1 -d :)" == "FIPS" ; then
            cond_echo -n "The current crypto policy ($current) "
            cond_echo -n 'is based on the FIPS policy, '
            cond_echo 'but FIPS mode is not enabled.'
            cond_echo 'Inconsistent state detected.'
            exit 1
        fi
    fi
    if test "$fips_enabled" != 1 && test "$err_if_disabled" = 1;then
        exit 2
    fi
    exit 0
fi

if [ $(id -u) != 0 ]; then
    echo "You must be root to run $(basename $0)"
    exit 1
fi

# Boot configuration
if test "$boot_config" = 1 && test ! -x "$(command -v grubby)" ; then
    echo "The grubby command is missing, please configure the bootloader manually."
    boot_config=0
fi

if test "$boot_config" = 1 && test ! -d /boot ; then
    echo >&2 "/boot directory is missing, FIPS mode cannot be $(enable2txt $enable_fips)."
    echo >&2 "If you want to configure the bootloader manually, re-run with --no-bootcfg."
    exit 1
fi

if test "$boot_config" = 1 && test -z "$(ls -A /boot)" ; then
    echo >&2 "/boot directory is empty, FIPS mode cannot be $(enable2txt $enable_fips)."
    echo >&2 "If you want to configure the bootloader manually, re-run with --no-bootcfg."
    exit 1
fi

if test "$FIPS_MODE_SETUP_SKIP_ARGON2_CHECK" != 1 && \
        test -x "$(command -v cryptsetup)" ; then
    # Best-effort detection of LUKS Argon2 usage
    argon2_found=''
    # two redundant ways to list device names
    devs=$( (find /dev/mapper/ -type l -printf '%f\n'; \
        dmsetup ls --target crypt | cut -f1) \
        | sort -u)
        while IFS= read -r devname; do
            back=$(cryptsetup status "$devname" | \
                grep -F device: |
                sed -E 's/.*device:\s+//')
            if test -z "$back"; then
                continue
            fi
            if ! test -b "$back"; then
                echo >&2 -n "Warning: detected device '$back' "
                echo >&2 -n 'is not a valid block device. '
                echo >&2 'Cannot check whether it uses Argon2.'
                continue
            fi
            dump=$(cryptsetup luksDump "$back")
            if grep -qEi 'PBKDF:.*argon' <<<"$dump"; then
                argon2_found+=" $back($devname)"
            fi
        done <<<"$devs"
    if test -n "$argon2_found" ; then
        echo >&2 -n "The following encrypted devices use Argon2 PBKDF:"
        echo >&2 "$argon2_found"
        echo >&2 'Aborting fips-mode-setup because of that.'
        echo >&2 -n 'Please refer to the '
        echo >&2 'cryptsetup-luksConvertKey(8) manpage.'
        exit 76
    fi
fi

if test "$FIPS_MODE_SETUP_SKIP_WARNING" != 1 ; then
    if test $enable_fips = 1 ; then
        echo >&2 "*****************************************************************"
        echo >&2 "* PRESS CONTROL-C WITHIN 15 SECONDS TO ABORT...                 *"
        echo >&2 "*                                                               *"
        echo >&2 "* ENABLING FIPS MODE AFTER THE INSTALLATION IS NOT RECOMMENDED. *"
        echo >&2 "* THIS OPERATION CANNOT BE UNDONE.                              *"
        echo >&2 "* REINSTALL WITH fips=1 INSTEAD.                                *"
        echo >&2 "*****************************************************************"
    elif test $enable_fips = 0 ; then
        echo >&2 "*****************************************************************"
        echo >&2 "* PRESS CONTROL-C WITHIN 15 SECONDS TO ABORT...                 *"
        echo >&2 "*                                                               *"
        echo >&2 "* DISABLING FIPS MODE AFTER THE INSTALLATION IS NOT SUPPORTED.  *"
        echo >&2 "* THIS OPERATION CANNOT BE UNDONE.                              *"
        echo >&2 "* WIPE ALL MEDIA AND REINSTALL WITHOUT fips=1 INSTEAD.          *"
        echo >&2 "*****************************************************************"
    fi
    for i in {15..1}; do
        echo >&2 -n "$i... "
        sleep 1 || exit 77
    done
    echo >&2
fi

if test $enable_fips = 1 ; then
    if test $fips_install_complete = 0 ; then
        fips-finish-install --complete
        if test $? != 0 ; then
            echo "Installation of FIPS modules could not be completed."
            exit 1
        fi
    fi
    target="$(cat /etc/crypto-policies/state/current)"
    if test "$(echo $target | cut -f 1 -d :)" == "FIPS" ; then
        cond_echo "Preserving current FIPS-based policy ${target}."
        cond_echo -n 'Please review the subpolicies to ensure they '
        cond_echo 'only restrict, not relax the FIPS policy.'
    else
        target=FIPS
    fi
    update-crypto-policies --no-reload --set "${target}" 2>/dev/null
else
    fips-finish-install --undo
    update-crypto-policies --no-reload --set DEFAULT 2>/dev/null
fi


boot_device_opt=" boot=UUID=<your-boot-device-uuid>"
if test "$boot_config" = 1 ; then
    boot_device="$(stat -c %d:%m /boot)"
    root_device="$(stat -c %d:%m /)"  # contrary to findmnt, works in chroot
    if test "$boot_device" = "$root_device"; then
        # /boot is not separate from /root
        boot_device_opt=""
    else
        # trigger autofs, when boot is mounted with
        # automount.boot / systemd-gpt-auto-generator(8)
        pushd /boot >/dev/null
        FINDMNT_UUID='findmnt --first-only -t noautofs --noheadings --output uuid'
        boot_uuid=$(
            $FINDMNT_UUID --mountpoint /boot --fstab ||  # priority
            $FINDMNT_UUID --mountpoint /boot
        )
        if test -z "$boot_uuid"; then
            echo "Boot device not identified, you have to configure the bootloader manually."
            boot_config=0
        else
            boot_device_opt=" boot=UUID=$boot_uuid"
        fi
        popd >/dev/null
    fi
fi

echo "FIPS mode will be $(enable2txt $enable_fips)."

fipsopts="fips=$enable_fips$boot_device_opt"

if test "$boot_config" = 1 ; then
    grubby --update-kernel=ALL --args="$fipsopts"
    if test x"$(uname -m)" = xs390x; then
        if command -v zipl >/dev/null; then
            zipl
        else
            echo -n '`zipl` execution has been skipped: '
            echo '`zipl` not found.'
        fi
    fi
    echo "Please reboot the system for the setting to take effect."
else
    echo "Now you need to configure the bootloader to add kernel options \"$fipsopts\""
    echo "and reboot the system for the setting to take effect."
fi

exit 0