diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f969fdef2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Shell scripts must keep LF line endings, they are run by bash on all platforms. +*.sh text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c223ca40d..18ae75075 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: - name: Prepare run: | sudo apt-get update -qq - sudo apt-get install -y libcppunit-dev libssl-dev p11-kit + sudo apt-get install -y libcppunit-dev libssl-dev p11-kit pkcs11-provider - name: Build # Once all OpenSSL deprecations fixed, uncomment this # env: diff --git a/src/bin/util/test/.gitignore b/src/bin/util/test/.gitignore new file mode 100644 index 000000000..1f0bf658b --- /dev/null +++ b/src/bin/util/test/.gitignore @@ -0,0 +1 @@ +/tokens-*/ diff --git a/src/bin/util/test/CMakeLists.txt b/src/bin/util/test/CMakeLists.txt index 3b0a78e53..e7fa8cf05 100644 --- a/src/bin/util/test/CMakeLists.txt +++ b/src/bin/util/test/CMakeLists.txt @@ -20,6 +20,17 @@ add_test( bash ${CMAKE_CURRENT_SOURCE_DIR}/mlkem512-import-key-test.sh ) +add_test( + NAME ${PROJECT_NAME}-p11prov + COMMAND ${CMAKE_COMMAND} -E env + top_builddir=${CMAKE_BINARY_DIR} + top_srcdir=${CMAKE_SOURCE_DIR} + srcdir=${CMAKE_CURRENT_SOURCE_DIR} + builddir=${CMAKE_CURRENT_BINARY_DIR} + bash ${CMAKE_CURRENT_SOURCE_DIR}/p11prov-test.sh +) + # Make tests returning code 77 considered as skipped, to avoid marking the test suite as failed when running on an unsupported OpenSSL version or with a Botan-based SoftHSM2. set_tests_properties(${PROJECT_NAME}-ml-dsa PROPERTIES SKIP_RETURN_CODE 77) -set_tests_properties(${PROJECT_NAME}-ml-kem PROPERTIES SKIP_RETURN_CODE 77) \ No newline at end of file +set_tests_properties(${PROJECT_NAME}-ml-kem PROPERTIES SKIP_RETURN_CODE 77) +set_tests_properties(${PROJECT_NAME}-p11prov PROPERTIES SKIP_RETURN_CODE 77) diff --git a/src/bin/util/test/Makefile.am b/src/bin/util/test/Makefile.am index fb7ce2f94..f4db2393d 100644 --- a/src/bin/util/test/Makefile.am +++ b/src/bin/util/test/Makefile.am @@ -2,11 +2,13 @@ MAINTAINERCLEANFILES = $(srcdir)/Makefile.in TESTS_ENVIRONMENT = top_builddir='$(top_builddir)' top_srcdir='$(top_srcdir)' srcdir='$(srcdir)' builddir='$(builddir)' -TESTS = mldsa44-import-key-test.sh mlkem512-import-key-test.sh +TESTS = mldsa44-import-key-test.sh mlkem512-import-key-test.sh p11prov-test.sh check_SCRIPTS = $(TESTS) EXTRA_DIST = $(srcdir)/CMakeLists.txt \ + $(srcdir)/test-common.sh \ $(srcdir)/import-key-test-common.sh \ $(srcdir)/mldsa44-import-key-test.sh \ - $(srcdir)/mlkem512-import-key-test.sh + $(srcdir)/mlkem512-import-key-test.sh \ + $(srcdir)/p11prov-test.sh diff --git a/src/bin/util/test/import-key-test-common.sh b/src/bin/util/test/import-key-test-common.sh index f7beb9cc0..6ebbf8b58 100755 --- a/src/bin/util/test/import-key-test-common.sh +++ b/src/bin/util/test/import-key-test-common.sh @@ -18,78 +18,13 @@ if [[ "$CI" == "true" ]] && [[ "$CI_TEST_ENABLED" != "true" ]] ; then exit 77 fi -# ---------- locate the SoftHSM library ---------- +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test-common.sh" -if test "$RUNNER_OS" = "Windows" ; then - if test -n "${CONFIG:-${CMAKE_BUILD_TYPE:-}}" ; then - _PROBE_DIRS="${CONFIG:-$CMAKE_BUILD_TYPE}" - else - _PROBE_DIRS="Debug Release RelWithDebInfo MinSizeRel" - fi - D= - for _CFG in $_PROBE_DIRS ; do - if test -d "../../../lib/$_CFG/" ; then - D=$(cd "../../../lib/$_CFG/" 2>/dev/null && pwd) - test -n "$D" || continue - CONFIG="$_CFG" - break - fi - done -else - if ! D=$(cd ../../../lib/.libs/ 2>/dev/null && pwd) ; then - D= - fi -fi - -if test -z "$D" ; then - echo "unexpectedly missing library directory" >&2 - exit 99 -fi - -P11MODULE= -for S in so dll ; do - for F in "$D"/*softhsm2."$S" ; do - test -f "$F" || continue - P11MODULE="$F" - break - done - test -n "$P11MODULE" && break -done -if test -z "$P11MODULE" ; then - echo "error: unexpected module suffix" >&2 - exit 1 -fi -if command -v realpath > /dev/null ; then - P11MODULE=$(realpath "$P11MODULE") -fi - -IS_BOTAN=$(strings "$P11MODULE" | grep botan | wc -l) -if [[ "$IS_BOTAN" -gt 0 ]] ; then +if is_botan_module ; then echo "This test is not compatible with the Botan2-based SoftHSM2, skipping." >&2 exit 77 fi -CWD=$(pwd) - -# ---------- binaries ---------- - -OPENSSL=${OPENSSL-openssl} -OPENSSL=$(command -v "$OPENSSL") -if test -z "$OPENSSL" ; then - echo "error: openssl utility not found" >&2 - exit 1 -fi - -openssl() { - "$OPENSSL" ${1+"$@"} -} - -openssl_version=$(openssl version) || exit $? -if test -z "$openssl_version" ; then - echo "cannot determine OpenSSL version" >&2 - exit 1 -fi - case $openssl_version in *"OpenSSL 0.9."*|\ *"OpenSSL 1."*|\ @@ -104,51 +39,9 @@ case $openssl_version in esac # NOTE OpenSSL > 3.5 -# ---------- helper functions ---------- - -softhsm2_tool() { - if test "$RUNNER_OS" = "Windows" ; then - "$CWD"/../"${CONFIG:-Debug}"/softhsm2-util.exe --module "$P11MODULE" ${1+"$@"} - else - "$CWD"/../softhsm2-util --module "$P11MODULE" ${1+"$@"} - fi -} - -clean_tokendir() { - rm -rf "$TOKEN_DIR" -} - # ---------- token configuration ---------- -TOKEN_DIR="$CWD"/"$TOKEN_DIR_NAME" -clean_tokendir -export SOFTHSM2_CONF="$TOKEN_DIR"/softhsm2.conf - -mkdir -p "$TOKEN_DIR" - -if test "$RUNNER_OS" = "Windows" ; then - WINDOWS_TOKEN_DIR=$(realpath "$TOKEN_DIR") - WINDOWS_TOKEN_DIR=$(cygpath -w "$WINDOWS_TOKEN_DIR") - cat > "$SOFTHSM2_CONF" < "$SOFTHSM2_CONF" <"$INIT_OUT" 2>&1; then - echo "Failed to init token" - cat "$INIT_OUT" - cat "$TOKEN_DIR/token.log" - clean_tokendir - exit 1 + fail "Failed to init token" "$INIT_OUT" fi openssl genpkey -algorithm "$OPENSSL_ALGO" -out "$KEY_FILE" if ! softhsm2_tool --import "$KEY_FILE" --import-type keypair --id $TOKEN_ID --label test_key --token test0 --pin $TOKEN_PIN >"$IMPORT_OUT" 2>&1; then - echo "Failed to import $ALGO_NAME key" - cat "$IMPORT_OUT" - cat "$TOKEN_DIR/token.log" - clean_tokendir - exit 1 + fail "Failed to import $ALGO_NAME key" "$IMPORT_OUT" fi if ! grep -q "The $ALGO_NAME key pair with label=test_key has been imported." "$IMPORT_OUT"; then - echo "ERROR: Expected $ALGO_NAME import success message" - cat "$IMPORT_OUT" - cat "$TOKEN_DIR/token.log" - clean_tokendir - exit 1 + fail "ERROR: Expected $ALGO_NAME import success message" "$IMPORT_OUT" fi -clean_tokendir \ No newline at end of file +clean_tokendir diff --git a/src/bin/util/test/p11prov-test.sh b/src/bin/util/test/p11prov-test.sh new file mode 100755 index 000000000..b97c1200a --- /dev/null +++ b/src/bin/util/test/p11prov-test.sh @@ -0,0 +1,126 @@ +#! /bin/bash +# +# Copyright (c) 2026 SoftHSMv2 contributors +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Regression test for the process-exit crash reported in issues #729 and +# #780: an OpenSSL application using the pkcs11-provider closes its cached +# PKCS#11 sessions and finalises the module from an atexit handler, after +# the C++ runtime has started destroying static objects. SoftHSM2 must stay +# usable until C_Finalize is called, so the openssl process must exit +# cleanly. +# +# Requires OpenSSL 3 and the pkcs11-provider module. The provider is +# looked up in the OpenSSL MODULESDIR; set PKCS11_PROVIDER_MODULE to +# override the location. The test is skipped when either is missing. + +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test-common.sh" + +case $openssl_version in +*"OpenSSL 0.9."*|\ +*"OpenSSL 1."*) + echo "$openssl_version has no provider support, skipping." >&2 + exit 77 + ;; +*"OpenSSL "*) + ;; +*) + echo "unsupported: $openssl_version, skipping." >&2 + exit 77 + ;; +esac + +# ---------- locate the PKCS#11 provider ---------- + +if test -n "$PKCS11_PROVIDER_MODULE" ; then + if ! test -f "$PKCS11_PROVIDER_MODULE" ; then + echo "error: PKCS11_PROVIDER_MODULE=$PKCS11_PROVIDER_MODULE does not exist" >&2 + exit 1 + fi +else + MODULESDIR=$(openssl version -m | sed -n 's/^MODULESDIR: *"\{0,1\}\([^"]*\)"\{0,1\}$/\1/p') + if test -z "$MODULESDIR" ; then + echo "cannot determine OpenSSL MODULESDIR, skipping." >&2 + exit 77 + fi + for N in pkcs11 libpkcs11 ; do + for S in so dll ; do + test -f "$MODULESDIR/$N.$S" || continue + PKCS11_PROVIDER_MODULE="$MODULESDIR/$N.$S" + break + done + test -n "$PKCS11_PROVIDER_MODULE" && break + done + if test -z "$PKCS11_PROVIDER_MODULE" ; then + echo "pkcs11-provider not found in $MODULESDIR, skipping." >&2 + exit 77 + fi +fi + +# ---------- token and OpenSSL configuration ---------- + +TOKEN_DIR_NAME="tokens-p11prov" +setup_tokendir + +OPENSSL_CONF="$TOKEN_DIR"/openssl.cnf +cat > "$OPENSSL_CONF" <"$INIT_OUT" 2>&1; then + fail "Failed to init token" "$INIT_OUT" +fi + +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "$KEY_FILE" 2>/dev/null + +if ! softhsm2_tool --import "$KEY_FILE" --import-type keypair --id $TOKEN_ID --label test_key --token test0 --pin $TOKEN_PIN >"$IMPORT_OUT" 2>&1; then + fail "Failed to import RSA key" "$IMPORT_OUT" +fi + +echo "SoftHSM2 pkcs11-provider exit test" > "$DATA_FILE" + +# Sign through the provider. The process must exit cleanly: before the fix +# for #729/#780 it produced the signature and then crashed with SIGSEGV in +# C_CloseSession during OpenSSL's atexit cleanup. +openssl pkeyutl -sign -inkey "$KEY_URI" -passin pass:$TOKEN_PIN -rawin -in "$DATA_FILE" -out "$SIG_FILE" >"$SIGN_OUT" 2>&1 \ + || fail "openssl pkeyutl -sign via pkcs11-provider failed with exit status $?" "$SIGN_OUT" + +# Verify with the software copy of the key to make sure the signature +# really came from the imported key. +if ! openssl pkeyutl -verify -inkey "$KEY_FILE" -rawin -in "$DATA_FILE" -sigfile "$SIG_FILE" >"$VERIFY_OUT" 2>&1; then + fail "Signature verification failed" "$VERIFY_OUT" +fi + +clean_tokendir diff --git a/src/bin/util/test/test-common.sh b/src/bin/util/test/test-common.sh new file mode 100755 index 000000000..b0e31a8a6 --- /dev/null +++ b/src/bin/util/test/test-common.sh @@ -0,0 +1,154 @@ +#! /bin/bash +# +# Copyright (c) 2026 SoftHSMv2 contributors +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Common setup for the softhsm2-util shell tests. Source this file from a +# test script. It locates the SoftHSM2 PKCS#11 module, the softhsm2-util +# binary and the openssl utility, and provides helpers to prepare a token +# directory. +# +# Provided variables: +# P11MODULE – absolute path to the SoftHSM2 PKCS#11 module +# CWD – directory the test is executed from +# OPENSSL – path to the openssl utility +# openssl_version – output of "openssl version" +# +# Provided functions: +# openssl – wrapper running $OPENSSL +# softhsm2_tool – runs softhsm2-util with --module $P11MODULE +# is_botan_module – succeeds when the module is built with Botan +# setup_tokendir – creates $TOKEN_DIR from $TOKEN_DIR_NAME and writes +# softhsm2.conf, exported as SOFTHSM2_CONF +# clean_tokendir – removes $TOKEN_DIR +# fail – prints a message and the token log, cleans up, exits 1 + + +# ---------- locate the SoftHSM library ---------- + +if test "$RUNNER_OS" = "Windows" ; then + if test -n "${CONFIG:-${CMAKE_BUILD_TYPE:-}}" ; then + _PROBE_DIRS="${CONFIG:-$CMAKE_BUILD_TYPE}" + else + _PROBE_DIRS="Debug Release RelWithDebInfo MinSizeRel" + fi + D= + for _CFG in $_PROBE_DIRS ; do + if test -d "../../../lib/$_CFG/" ; then + D=$(cd "../../../lib/$_CFG/" 2>/dev/null && pwd) + test -n "$D" || continue + CONFIG="$_CFG" + break + fi + done +else + if ! D=$(cd ../../../lib/.libs/ 2>/dev/null && pwd) ; then + D= + fi +fi + +if test -z "$D" ; then + echo "unexpectedly missing library directory" >&2 + exit 99 +fi + +P11MODULE= +for S in so dll ; do + for F in "$D"/*softhsm2."$S" ; do + test -f "$F" || continue + P11MODULE="$F" + break + done + test -n "$P11MODULE" && break +done +if test -z "$P11MODULE" ; then + echo "error: SoftHSM2 module not found in $D" >&2 + exit 1 +fi +if command -v realpath > /dev/null ; then + P11MODULE=$(realpath "$P11MODULE") +fi + +is_botan_module() { + test "$(strings "$P11MODULE" | grep -c botan)" -gt 0 +} + +CWD=$(pwd) + +# ---------- binaries ---------- + +OPENSSL=${OPENSSL-openssl} +OPENSSL=$(command -v "$OPENSSL") +if test -z "$OPENSSL" ; then + echo "error: openssl utility not found" >&2 + exit 1 +fi + +openssl() { + "$OPENSSL" ${1+"$@"} +} + +openssl_version=$(openssl version) || exit $? +if test -z "$openssl_version" ; then + echo "cannot determine OpenSSL version" >&2 + exit 1 +fi + +softhsm2_tool() { + if test "$RUNNER_OS" = "Windows" ; then + "$CWD"/../"${CONFIG:-Debug}"/softhsm2-util.exe --module "$P11MODULE" ${1+"$@"} + else + "$CWD"/../softhsm2-util --module "$P11MODULE" ${1+"$@"} + fi +} + +# ---------- token directory helpers ---------- + +clean_tokendir() { + rm -rf "$TOKEN_DIR" +} + +# Convert a path for use in configuration files read by native binaries. +native_path() { + if test "$RUNNER_OS" = "Windows" ; then + cygpath -w "$(realpath "$1")" + else + printf '%s\n' "$1" + fi +} + +# Requires TOKEN_DIR_NAME to be set by the caller. +setup_tokendir() { + TOKEN_DIR="$CWD"/"$TOKEN_DIR_NAME" + clean_tokendir + mkdir -p "$TOKEN_DIR" + + export SOFTHSM2_CONF="$TOKEN_DIR"/softhsm2.conf + TOKEN_LOG="$TOKEN_DIR"/token.log + + local native_tokendir native_tokenlog + native_tokendir=$(native_path "$TOKEN_DIR") + native_tokenlog=$(native_path "$TOKEN_LOG") + cat > "$SOFTHSM2_CONF" <&2 + shift + for f in "$@" "$TOKEN_LOG" ; do + test -f "$f" && cat "$f" + done + clean_tokendir + exit 1 +}