#!/bin/bash
set -euo pipefail
shopt -s inherit_errexit

# shellcheck source=scripts/libcc
. /usr/lib/common-criteria/scripts/libcc

# SLES16 ships its default sshd_config at /usr/etc/ssh/sshd_config (the
# vendor-owned file, not meant to be edited) and includes
# /etc/ssh/sshd_config.d/*.conf near the top of it via OpenSSH's own native
# Include mechanism. So instead of editing the live config in place, this
# script only ever has to manage its own drop-in file here.
SSHD_VENDOR_CONFIG="/usr/etc/ssh/sshd_config"
SSHD_DROPIN_DIR="/etc/ssh/sshd_config.d"
SSHD_DROPIN="$SSHD_DROPIN_DIR/50-cc.conf"

# Single canonical list of accepted public-key algorithm families (RSA and
# ECDSA P-384/P-521 - the only key types the CC evaluation guidance's
# key-generation requirement sanctions), used below for every sshd_config
# directive that restricts key/signature algorithms. Keep this as the only
# place that list is spelled out.
PUBKEY_ALGOS="rsa-sha2-256,rsa-sha2-512,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521"

# The authorized_keys "key type" field uses different names than the
# negotiation algorithm names above for RSA: an RSA key's on-disk type is
# always "ssh-rsa" regardless of whether rsa-sha2-256 or rsa-sha2-512 signing
# is negotiated at auth time. ECDSA's names happen to match directly. This is
# the same accepted-key-family set as PUBKEY_ALGOS above, just expressed in
# authorized_keys' vocabulary instead of sshd_config's.
PUBKEY_AUTHORIZED_KEY_TYPES="ssh-rsa ecdsa-sha2-nistp384 ecdsa-sha2-nistp521"

# Read-only: does the drop-in have the directives we'd write, and does the
# full merged config (vendor file + all drop-ins) still parse? Used both to
# skip re-applying when already compliant and to verify after applying.
check_20_config_sshd() {
	if [ ! -f "$SSHD_DROPIN" ]; then
		echo "$SSHD_DROPIN missing"
		return 1
	fi
	if ! grep -q "^PermitRootLogin no$" "$SSHD_DROPIN"; then
		echo "$SSHD_DROPIN missing 'PermitRootLogin no'"
		return 1
	fi
	if ! grep -q "^PubkeyAcceptedAlgorithms $PUBKEY_ALGOS$" "$SSHD_DROPIN"; then
		echo "$SSHD_DROPIN missing the expected PubkeyAcceptedAlgorithms line"
		return 1
	fi
	if ! /usr/sbin/sshd -t -f "$SSHD_VENDOR_CONFIG" 2>/dev/null; then
		echo "merged sshd configuration fails 'sshd -t'"
		return 1
	fi
}

apply_20_config_sshd() {
	[ -f "$SSHD_VENDOR_CONFIG" ] || {
		cc_echo "OpenSSH server does not appear to be installed ($SSHD_VENDOR_CONFIG missing) - skipping configuration"
		return 0
	}

	if check_20_config_sshd; then
		cc_echo "$SSHD_DROPIN already installed and verified"
		return 0
	fi

	# Applying PUBKEY_ALGOS below excludes ssh-ed25519 and ECDSA P-256.
	# Applying it without checking first can permanently lock out
	# administrators whose authorized_keys only has such a key - refuse to
	# proceed rather than risk that silently.
	local have_compliant_key=0
	local admin_homes="/root"
	local key_type_pattern="${PUBKEY_AUTHORIZED_KEY_TYPES// /|}"
	local u h f

	for u in $(getent group trusted | cut -d: -f4 | tr ',' ' '); do
		h=$(getent passwd "$u" | cut -d: -f6)
		if [ -n "$h" ]; then
			admin_homes="$admin_homes $h"
		fi
	done
	for h in $admin_homes; do
		f="$h/.ssh/authorized_keys"
		[ -r "$f" ] || continue
		if grep -qE "^($key_type_pattern) " "$f" 2>/dev/null; then
			have_compliant_key=1
		fi
	done

	if [ "$have_compliant_key" = "0" ]; then
		cc_echo "FAILED: no authorized_keys entry found for root or a 'trusted' group member using an RSA or ECDSA P-384/P-521 key"
		cc_echo "Refusing to apply the sshd hardening: it would restrict PubkeyAcceptedAlgorithms to exclude ssh-ed25519/ECDSA P-256 (per the CC guidance's key-generation requirement), which would lock out any administrator who only has such a key authorized"
		cc_echo "Provision a compliant key first (ssh-keygen -t rsa -b 3072 or larger, or -t ecdsa -b 384/521) for at least one administrative account, then re-run this script"
		return 1
	fi
	# Note: this only checks for the presence of an accepted key *type* in
	# authorized_keys, not RSA key strength - a ssh-rsa entry weaker than 3072
	# bits will satisfy this check but still violate the guidance's key-strength
	# requirement. Verify RSA key sizes separately, e.g. with ssh-keygen -lf.

	mkdir -p "$SSHD_DROPIN_DIR"

	cat > "$SSHD_DROPIN" <<EOF
# Common Criteria evaluated configuration - managed by certification-sles-eal4.
# Do not edit; changes here will be overwritten. sshd applies the first
# value it encounters for each keyword and ignores later ones (confirmed:
# an earlier-sorting drop-in's Ciphers wins over a later one's), so a local
# exception needs a file that sorts BEFORE this one in
# /etc/ssh/sshd_config.d/, not after.
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes
KbdInteractiveAuthentication no
GSSAPIAuthentication no
HostbasedAuthentication no
Ciphers aes256-gcm@openssh.com
KexAlgorithms ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
MACs hmac-sha2-256,hmac-sha2-512
HostKeyAlgorithms $PUBKEY_ALGOS
HostbasedAcceptedAlgorithms $PUBKEY_ALGOS
# Note: the SLES15 SP7 CC evaluation guidance document's "SSH Server
# Configuration" section lists ecdh-sha2-nistp384/521 (KEX algorithm names)
# as the required value here, but sshd rejects those with "Bad key types"
# since this directive takes public-key signature algorithm names, not KEX
# names - that section of the guide appears to have a copy/paste error.
# Using the same set the guide specifies for HostKeyAlgorithms instead, as
# the closest available approximation of intent.
PubkeyAcceptedAlgorithms $PUBKEY_ALGOS
RekeyLimit 1G 1h
EOF
	chmod 644 "$SSHD_DROPIN"
	cc_echo "Installed $SSHD_DROPIN"

	# Validate the full merged configuration before declaring success, so a
	# bad directive is reported loudly instead of silently leaving sshd
	# unable to start or reload.
	check_20_config_sshd || {
		cc_echo "FAILED: sshd configuration does not pass verification after installing $SSHD_DROPIN"
		return 1
	}
	cc_echo "$SSHD_DROPIN installed and verified with 'sshd -t'"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
	cc_start_logging
	trap 'cc_exit $?' ERR
	apply_20_config_sshd
	cc_exit 0
fi
