From e99441433c2b0ac846befb2a9d77fc9ce65eaab3 Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Wed, 7 Oct 2026 16:44:12 +0900 Subject: [PATCH] pam-pkcs11: authenticate through pam_pkcs11 with pamtester - pam-pkcs11-test.sh puts CA-issued, unknown-CA and mismatched-key credentials on SoftHSM tokens and authenticates through a pam_pkcs11-only PAM service via pamtester, in place of su. - The valid token must pass the signature check; a wrong PIN, the unknown CA and the mismatched key must each fail. Under force fail only the second authentication runs with it set. - check-workflow-result.sh has a pam_pkcs11 branch that requires one successful authentication and "Error 2320" in the log. - Unused packages and the pcscd start are dropped. --- .github/scripts/check-workflow-result.sh | 17 ++ .github/scripts/pam-pkcs11-test.sh | 271 ++++++++++++++--------- .github/workflows/pam-pkcs11.yml | 13 +- 3 files changed, 187 insertions(+), 114 deletions(-) diff --git a/.github/scripts/check-workflow-result.sh b/.github/scripts/check-workflow-result.sh index 53326c55..3ffdcdf7 100755 --- a/.github/scripts/check-workflow-result.sh +++ b/.github/scripts/check-workflow-result.sh @@ -296,6 +296,23 @@ if [ "$WOLFPROV_FORCE_FAIL" = "WOLFPROV_FORCE_FAIL=1" ]; then echo "FAIL: iperf exited $TEST_RESULT but iperf-test.log is missing or does not show the credential encryption failure" exit 1 fi + # ----- PAM_PKCS11 ----- + elif [ "$TEST_SUITE" = "pam_pkcs11" ]; then + # Only the authentication without force fail may succeed, and the one + # under force fail must stop at SoftHSM's PIN check + if [ "$TEST_RESULT" -ne 0 ] \ + && [ -f "pam-pkcs11-test.log" ] \ + && [ "$(grep -c '^pamtester: successfully authenticated' pam-pkcs11-test.log)" -eq 1 ] \ + && grep -q 'Error 2320: Wrong smartcard PIN' pam-pkcs11-test.log; then + echo "PASS: pam_pkcs11 authentication failed at the token PIN check as expected with force fail enabled" + exit 0 + elif [ "$TEST_RESULT" -eq 0 ]; then + echo "FAIL: pam_pkcs11 tests unexpectedly succeeded with force fail enabled" + exit 1 + else + echo "FAIL: pam_pkcs11 exited $TEST_RESULT but pam-pkcs11-test.log does not show one successful authentication and then the token PIN check failure" + exit 1 + fi else if [ $TEST_RESULT -eq 0 ]; then echo "$TEST_SUITE tests unexpectedly succeeded with force fail enabled" diff --git a/.github/scripts/pam-pkcs11-test.sh b/.github/scripts/pam-pkcs11-test.sh index 8ea57e99..3f796543 100755 --- a/.github/scripts/pam-pkcs11-test.sh +++ b/.github/scripts/pam-pkcs11-test.sh @@ -1,7 +1,31 @@ #!/bin/bash +# Build pam_pkcs11 and authenticate testuser with a key and certificate held +# on a SoftHSM token, through a PAM service where pam_pkcs11 is the only +# module that can succeed. +# +# The valid token must first authenticate after the CA and signature checks. +# Then a wrong PIN, a certificate from an unknown CA and a key that does not +# match its certificate must each be rejected. With WOLFPROV_FORCE_FAIL=1, +# the valid token instead authenticates again under force fail and the script +# exits with pamtester's status. Setup and the first authentication always +# run without force fail. pamtester output is appended to PAM_PKCS11_TEST_LOG. set -euo pipefail set -x +FORCE_FAIL="${WOLFPROV_FORCE_FAIL:-0}" +unset WOLFPROV_FORCE_FAIL + +PIN=1234 +WRONG_PIN=0000 +PAM_SERVICE=pam_pkcs11_test +PAM_SERVICE_FILE="/etc/pam.d/$PAM_SERVICE" +SOFTHSM_MODULE=/usr/lib/softhsm/libsofthsm2.so +WRONG_PIN_MSG="Error 2320: Wrong smartcard PIN" +UNKNOWN_CA_MSG="Error 2328: Certificate signature invalid" +BAD_SIG_MSG="Error 2342: Verifying signature failed" +TEST_LOG=$(realpath -m "${PAM_PKCS11_TEST_LOG:-pam-pkcs11-test.log}") +: > "$TEST_LOG" + # Confirm wolfProvider is configured by running openssl list -providers if openssl list -providers | grep -qi wolf; then echo "wolfProvider is configured" @@ -20,26 +44,18 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y \ libtool \ pkg-config \ libpam0g-dev \ - libnss3-dev \ libpcsclite-dev \ opensc \ softhsm2 \ - pcscd \ - pcsc-tools \ - sudo \ - systemd \ - ssh \ - vim \ - gnupg \ - wget \ - curl + pamtester + +WORK=$(mktemp -d /tmp/pam-pkcs11-test.XXXXXX) +trap 'rm -f "$PAM_SERVICE_FILE"' EXIT echo "[*] Cloning pam_pkcs11..." -cd /opt -if [[ ! -d "pam_pkcs11" ]]; then - git clone --branch=${PAM_PKCS11_REF} https://github.com/OpenSC/pam_pkcs11.git -fi -cd pam_pkcs11 +git clone --depth 1 --branch="${PAM_PKCS11_REF}" \ + https://github.com/OpenSC/pam_pkcs11.git "$WORK/src" +cd "$WORK/src" echo "[*] Building pam_pkcs11 from source..." ./bootstrap @@ -50,103 +66,150 @@ make install echo "[*] Creating test user..." if ! id -u testuser &>/dev/null; then useradd -m testuser - echo 'testuser:testpass' | chpasswd - echo "[*] Created user 'testuser'" -else - echo "[*] User 'testuser' already exists, skipping creation" fi -echo "[*] Configuring pam_pkcs11..." +echo "[*] Creating the certificates and keys..." +mkdir -p "$WORK/cacerts" "$WORK/crls" +openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ + -keyout "$WORK/ca.key" -out "$WORK/cacerts/ca.crt" \ + -subj "/CN=Test CA/O=Example" \ + -addext "basicConstraints=critical,CA:TRUE" \ + -addext "keyUsage=critical,keyCertSign,cRLSign" +openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ + -keyout "$WORK/unknown-ca.key" -out "$WORK/unknown-ca.crt" \ + -subj "/CN=Unknown CA/O=Example" \ + -addext "basicConstraints=critical,CA:TRUE" \ + -addext "keyUsage=critical,keyCertSign,cRLSign" +openssl req -newkey rsa:2048 -nodes \ + -keyout "$WORK/user.key" -out "$WORK/user.csr" \ + -subj "/CN=testuser/O=Example" +openssl x509 -req -days 365 -in "$WORK/user.csr" \ + -CA "$WORK/cacerts/ca.crt" -CAkey "$WORK/ca.key" \ + -outform DER -out "$WORK/user.der" +openssl x509 -req -days 365 -in "$WORK/user.csr" \ + -CA "$WORK/unknown-ca.crt" -CAkey "$WORK/unknown-ca.key" \ + -outform DER -out "$WORK/unknown-ca-user.der" +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \ + -out "$WORK/other.key" +pkcs11_make_hash_link "$WORK/cacerts" + +# Create a SoftHSM token in $WORK/ that holds and under +# one ID, so pam_pkcs11 signs with that key +make_token() ( + local dir="$WORK/$1" + + mkdir -p "$dir/tokens" + export SOFTHSM2_CONF="$dir/softhsm2.conf" + cat > "$SOFTHSM2_CONF" < "$WORK/pam_pkcs11.conf" < "$PAM_SERVICE_FILE" < and , logging +# to $WORK/ and TEST_LOG. Any further arguments are environment settings +# for pamtester. +run_pam() { + local token="$1" + local pin="$2" + local log="$WORK/$3" + local rc=0 + shift 3 + + env SOFTHSM2_CONF="$WORK/$token/softhsm2.conf" "$@" \ + pamtester -v "$PAM_SERVICE" testuser authenticate \ + <<< "$pin" > "$log" 2>&1 || rc=$? + tee -a "$TEST_LOG" < "$log" + return $rc +} + +# Authentication with and must fail with . Any further +# arguments are environment settings for pamtester. +expect_fail() { + local token="$1" + local pin="$2" + local log="$3" + local msg="$4" + shift 4 + + if run_pam "$token" "$pin" "$log" "$@"; then + echo "FAIL: $log: authentication succeeded" + exit 1 + fi + if ! grep -qF "$msg" "$WORK/$log"; then + echo "FAIL: $log: authentication failed without '$msg'" + exit 1 + fi +} + +echo "[*] Authenticating with the valid token and the correct PIN..." +if ! run_pam valid "$PIN" auth.log; then + echo "FAIL: authentication with the valid token failed" + exit 1 fi - -# Extract cert subject in one-line format suitable for pam_pkcs11 -CERT_SUBJECT=$(openssl x509 -in /test/certs/test-cert.pem -noout -subject -nameopt oneline | sed 's/subject=//') - -echo "[*] Writing pkcs11_mapper.map with subject: $CERT_SUBJECT" - -echo "subject=$CERT_SUBJECT; uid=testuser" | tee /etc/pam_pkcs11/pkcs11_mapper.map > /dev/null - -# Backup and modify PAM config -cp /etc/pam.d/common-auth /etc/pam.d/common-auth.bak -echo "auth sufficient pam_pkcs11.so debug" | tee /etc/pam.d/common-auth > /dev/null -cat /etc/pam.d/common-auth.bak | tee -a /etc/pam.d/common-auth > /dev/null - -# Restore WOLFPROV_FORCE_FAIL -export WOLFPROV_FORCE_FAIL="$ORIG_WOLFPROV_FORCE_FAIL" - -echo "[*] Initializing SoftHSM (simulated smartcard)..." -mkdir -p /var/lib/softhsm/tokens -softhsm2-util --init-token --free --label "testtoken" --pin 1234 --so-pin 123456 - -echo "[*] Importing test certificate into SoftHSM..." -pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ - --login --pin 1234 --write-object /test/certs/test-cert.pem --type cert --label "testcert" - -echo "[*] Starting pcscd..." -if ps aux | grep '[p]cscd' > /dev/null; then - echo "pcscd is already running" -else - echo "pcscd is not running, starting it now..." - pcscd & +if ! grep -q "Checking signature" "$WORK/auth.log" \ + || ! grep -q "successfully authenticated" "$WORK/auth.log"; then + echo "FAIL: authentication succeeded without the signature check" + exit 1 fi -echo "[*] Creating pam_pkcs11.conf..." -if [ -f "./etc/pam_pkcs11.conf.example" ]; then - cp ./etc/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf -else - echo "ERROR: pam_pkcs11.conf.example not found in current directory" - exit 1 +if [ "$FORCE_FAIL" = "1" ]; then + echo "[*] Authenticating with the valid token and WOLFPROV_FORCE_FAIL=1..." + rc=0 + run_pam valid "$PIN" force-fail.log WOLFPROV_FORCE_FAIL=1 || rc=$? + exit $rc fi -echo "[*] Configuring pam_pkcs11.conf for SoftHSM module..." - -# Set correct module usage line -sed -i 's|^use_pkcs11_module.*|use_pkcs11_module = softhsm;|' /etc/pam_pkcs11/pam_pkcs11.conf - -# Set the SoftHSM module path -sed -i '/^pkcs11_module softhsm {/,/^}/ s|^\s*module\s*=.*| module = /usr/lib/softhsm/libsofthsm2.so;|' /etc/pam_pkcs11/pam_pkcs11.conf +echo "[*] Authenticating with a wrong PIN..." +expect_fail valid "$WRONG_PIN" wrong-pin.log "$WRONG_PIN_MSG" -echo "[*] Checking SoftHSM PKCS#11 module dependencies..." -ldd /usr/lib/softhsm/libsofthsm2.so | tee /tmp/libsofthsm2.ldd -if grep -q "not found" /tmp/libsofthsm2.ldd; then - echo "ERROR: Missing dependencies for SoftHSM PKCS#11 module!" - exit 1 -fi - -echo "[*] Testing SoftHSM PKCS#11 module loadability with pkcs11-tool..." -if ! pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -L; then - echo "ERROR: Failed to load SoftHSM PKCS#11 module" - exit 1 -fi +echo "[*] Authenticating with a certificate from an unknown CA..." +expect_fail unknown-ca "$PIN" unknown-ca.log "$UNKNOWN_CA_MSG" -echo "[*] Testing login via su..." -su testuser -c 'echo "✅ Logged in as testuser"' +echo "[*] Authenticating with a key that does not match the certificate..." +expect_fail bad-key "$PIN" bad-key.log "$BAD_SIG_MSG" -echo "[*] All done." +echo "PASS: the valid token authenticated after the CA and signature checks," \ + "and the wrong PIN, unknown CA and mismatched key were rejected" diff --git a/.github/workflows/pam-pkcs11.yml b/.github/workflows/pam-pkcs11.yml index 9960b8a9..ea7bd358 100644 --- a/.github/workflows/pam-pkcs11.yml +++ b/.github/workflows/pam-pkcs11.yml @@ -96,18 +96,11 @@ jobs: - name: Run pam_pkcs11 tests shell: bash run: | - set +o pipefail # ignore errors from make check export ${{ matrix.force_fail }} export PAM_PKCS11_REF=${{ matrix.pam_pkcs11_ref }} + export PAM_PKCS11_TEST_LOG=pam-pkcs11-test.log - # Run tests - if timeout 300 $GITHUB_WORKSPACE/.github/scripts/pam-pkcs11-test.sh; then - TEST_RESULT=0 - else - TEST_RESULT=1 - fi + TEST_RESULT=0 + timeout 300 $GITHUB_WORKSPACE/.github/scripts/pam-pkcs11-test.sh || TEST_RESULT=$? - echo "TEST_RESULT: $TEST_RESULT" - - # Capture result $GITHUB_WORKSPACE/.github/scripts/check-workflow-result.sh $TEST_RESULT ${{ matrix.force_fail }} pam_pkcs11