# Copyright (c) 2023-2025 Roumen Petrov, Sofia, Bulgaria
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# DESCRIPTION: setup PKCS#11 test environment.
#

TEST_SSH_PIN=${TEST_SSH_PIN-1234}
TEST_SSH_SOPIN=${TEST_SSH_SOPIN-12345678}

CWD=${CWD-$OBJ}


# find a PKCS#11 module
p11_find_module() {
  if test -z "$TEST_SSH_PKCS11" ; then
    for D in \
      /usr/lib64/pkcs11 \
      /usr/lib64/softhsm \
      /usr/lib/x86_64-linux-gnu/softhsm \
      /usr/local/lib/softhsm \
      /opt/local/lib/softhsm \
      /usr/lib/softhsm \
      /usr/lib \
    ; do
      test -d "$D" || continue

      if test -f "$D"/libsofthsm2.so ; then
        TEST_SSH_PKCS11="$D"/libsofthsm2.so
        break
      fi
    done
    test -n "$TEST_SSH_PKCS11"
  else
    test -f "$TEST_SSH_PKCS11"
  fi
}


# find a SoftHSM2 utility
p11_find_SOFTHSM2_TOOL() {
  if test -z "$SOFTHSM2_TOOL" ; then
    for D in \
      /usr/bin \
      /usr/local/bin \
      /opt/local/bin \
    ; do
      test -d "$D" || continue

      if test -x "$D/softhsm2-util" ; then
        SOFTHSM2_TOOL="$D/softhsm2-util"
        break
      fi
    done
    test -n "$SOFTHSM2_TOOL"
  else
    test -x "$SOFTHSM2_TOOL"
  fi
}


# find a SoftHSM2 utility
p11_find_PKCS11_TOOL() {
  if test -z "$PKCS11_TOOL" ; then
    for D in \
      /usr/bin \
      /usr/local/bin \
      /opt/local/bin \
    ; do
      test -d "$D" || continue

      if test -x "$D/pkcs11-tool" ; then
        PKCS11_TOOL="$D/pkcs11-tool"
        break
      fi
    done
    test -n "$PKCS11_TOOL"
  else
    test -x "$PKCS11_TOOL"
  fi
}


# prepare PKCS#11 askpass helper utility
P11_ASKPASS=$CWD/ssh_askpass-pkcs11
cre_ssh_p11_askpass() {
if test -x "$P11_ASKPASS" ; then :
else
  cat > $P11_ASKPASS <<EOF
#! $TEST_SHELL
echo $TEST_SSH_PIN
EOF
  chmod 0700 $P11_ASKPASS
fi
}


# force load with token PIN.
p11_ssh_add() {
  cre_ssh_p11_askpass

  SSHADD=${SSHADD-$TEST_SSH_SSHADD}
  env SSH_ASKPASS="$P11_ASKPASS" SSH_ASKPASS_REQUIRE=force \
  $SSHADD ${1+"$@"}
}


# prepares a softhsm2 token configuration
p11_token_conf() {
	EXTRA_AGENT_ARGS="$EXTRA_AGENT_ARGS -P$TEST_SSH_PKCS11"

	# setup environment for softhsm2 token
	SSH_SOFTHSM_DIR=$CWD/softhsm
	export SSH_SOFTHSM_DIR
	rm -rf $SSH_SOFTHSM_DIR

	TOKEN=$SSH_SOFTHSM_DIR/tokens
	$abs_top_srcdir/install-sh -d $TOKEN

	SOFTHSM2_CONF=$SSH_SOFTHSM_DIR/softhsm2.conf
	export SOFTHSM2_CONF
	cat > $SOFTHSM2_CONF << EOF
# PKIX-SSH test - SoftHSM v2 configuration file
directories.tokendir = $TOKEN
objectstore.backend = file

slots.removable = false
slots.mechanisms = ALL

# ERROR, WARNING, INFO, DEBUG
log.level = ERROR
#log.level = DEBUG
EOF
}


# find a PKCS#11 engine
p11_find_engine() {
  if test -z "$TEST_SSH_PKCS11_ENGINE" ; then

    # use command and try to extract path
    enginedir=`$OPENSSL version -e 2>/dev/null`
    if test -n "$enginedir" ; then
      enginedir=`set -- $enginedir; test "x$1" = "xENGINESDIR:" && echo $2`
    fi

    # failback to defaults
    if test -n "$enginedir" ; then
      case $openssl_version in
      *"OpenSSL 3."*)  enginedir=engines-3;;   #why command fail?
      *"OpenSSL 1.1"*) enginedir=engines-1.1;; #why command fail?
      *"OpenSSL 1."*)  enginedir=engines;;
      esac
    fi

    test -n "$enginedir" &&
    for D in \
      /usr/lib64/$enginedir \
      /usr/lib/$enginedir \
    ; do
      test -d "$D" || continue

      if test -f "$D"/pkcs11.so ; then
        TEST_SSH_PKCS11_ENGINE="$D"/pkcs11.so
        break
      fi
      if test -f "$D"/libpkcs11.so ; then
        TEST_SSH_PKCS11_ENGINE="$D"/libpkcs11.so
        break
      fi
    done
    test -n "$TEST_SSH_PKCS11_ENGINE"
  else
    test -f "$TEST_SSH_PKCS11_ENGINE"
  fi
}


# find a PKCS#11 provider
p11_find_provider() {
  if test -z "$TEST_SSH_PKCS11_PROVIDER" ; then

    # use command and try to extract path
    moduledir=`$OPENSSL version -m 2>/dev/null`
    if test -n "$moduledir" ; then
      moduledir=`set -- $moduledir; test "x$1" = "xMODULESDIR:" && echo $2`
    fi

    test -n "$moduledir" &&
    for D in \
      /usr/lib64/$moduledir \
      /usr/lib/$moduledir \
    ; do
      test -d "$D" || continue

      if test -f "$D"/pkcs11.so ; then
        TEST_SSH_PKCS11_PROVIDER="$D"/pkcs11.so
        break
      fi
      if test -f "$D"/libpkcs11.so ; then
        TEST_SSH_PKCS11_PROVIDER="$D"/libpkcs11.so
        break
      fi
    done
    test -n "$TEST_SSH_PKCS11_PROVIDER"
  else
    test -f "$TEST_SSH_PKCS11_PROVIDER"
  fi
}


# common pkcs#11 token setup ...

#env. vars:
#  TEST_SSH_PKCS11 - pkcs#11 module
#  userpin
p11_module_write() {
  OPENSC_DEBUG=1 \
  $PKCS11_TOOL --module $TEST_SSH_PKCS11 --verbose \
    --login --pin $userpin \
    ${1+"$@"} \
  >> $SSH_MODULE_LOG 2>&1
}


# load X.509 certificate to pkcs#11 token:
load_cert=:
#load_cert=false


#args:
#  $1 - userpin
#  $2 - key_file
#  $3 - key_id
#  $4 - key_label
p11_write_object() {
  userpin=$1
  key_file=$2
  key_id=$3
  key_label=$4

  echo "write ${extd}$key_label${norm} ..." >&2

  echo "= prepare $key_id ..." >> $SSH_MODULE_LOG
  $OPENSSL x509 -in $key_file -outform DER -out pkcs11_tool-$key_id-cert.der || return $?

  key_type=privkey
  echo "== write $key_id-$key_type" >> $SSH_MODULE_LOG
  $OPENSSL pkey -in $key_file -outform DER -out pkcs11_tool-$key_id-$key_type.der || return $?
  p11_module_write \
    --write-object pkcs11_tool-$key_id-$key_type.der --type $key_type \
    --id $key_id --label "$key_label" \
    --attr-from pkcs11_tool-$key_id-cert.der \
  || return $?
  rm -f pkcs11_tool-$key_id-$key_type.der

  key_type=pubkey
  echo "== write $key_id-$key_type" >> $SSH_MODULE_LOG
  $OPENSSL pkey -in $key_file -outform DER -pubout -out pkcs11_tool-$key_id-$key_type.der || return $?
  p11_module_write \
    --write-object pkcs11_tool-$key_id-$key_type.der --type $key_type \
    --id $key_id --label "$key_label" \
  || return $?
  rm -f pkcs11_tool-$key_id-$key_type.der

if $load_cert ; then
  key_type=cert
  echo "== write $key_id-$key_type" >> $SSH_MODULE_LOG
  p11_module_write \
    --write-object pkcs11_tool-$key_id-$key_type.der --type $key_type \
    --id $key_id --label "$key_label" \
  || return $?
fi

  rm -f pkcs11_tool-$key_id-cert.der
}


p11_setup_token() {
  p11_token_conf

  # stop on first rsa digest
  for type in $SSH_SIGN_TYPES; do
    case $type in
    *rsa*);;
    *) continue;;
    esac
    break;
  done

  # setup token
  echo "create ${extd}test0${norm} token ..." >&2
  echo "=== create token" >> $SSH_MODULE_LOG
  $SOFTHSM2_TOOL --init-token --label test0 --slot free \
    --so-pin $TEST_SSH_SOPIN --pin $TEST_SSH_PIN \
    >> $SSH_MODULE_LOG 2>&1 \
  || return $?

  # load keys and X.509 certificates
  for SSH_CLIENTKEY in $TEST_SSH_CLIENTKEYS; do
    case $SSH_CLIENTKEY in
    *rsa*)         key_id='0001'; key_label=p11-rsa;;
    *eccnistp256*) key_id='0002'; key_label=p11-ec256;;
    *eccnistp384*) key_id='0003'; key_label=p11-ec384;;
    *eccnistp521*) key_id='0004'; key_label=p11-ec521;;
    *ed25519*)     key_id='0005'; key_label=p11-ed;;
    *) continue;;
    esac
    p11_write_object $TEST_SSH_PIN $SSH_CLIENTKEY-$type $key_id $key_label \
    || return $?
  done
  echo "=== write end" >> $SSH_MODULE_LOG
}
