Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/scripts/check-workflow-result.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
271 changes: 167 additions & 104 deletions .github/scripts/pam-pkcs11-test.sh
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand All @@ -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/<name> that holds <key> and <cert> 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" <<EOF
directories.tokendir = $dir/tokens
objectstore.backend = file
log.level = INFO
EOF
softhsm2-util --init-token --free --label testtoken \
--pin "$PIN" --so-pin 123456
softhsm2-util --import "$2" --token testtoken \
--id 01 --label testkey --pin "$PIN"
pkcs11-tool --module "$SOFTHSM_MODULE" --login --pin "$PIN" \
--write-object "$3" --type cert --id 01 --label testcert
pkcs11-tool --module "$SOFTHSM_MODULE" --login --pin "$PIN" -O
)

echo "[*] Creating SoftHSM tokens (simulated smartcards)..."
make_token valid "$WORK/user.key" "$WORK/user.der"
make_token unknown-ca "$WORK/user.key" "$WORK/unknown-ca-user.der"
make_token bad-key "$WORK/other.key" "$WORK/user.der"

# Temporarily unset WOLFPROV_FORCE_FAIL so we can generate certs correctly
ORIG_WOLFPROV_FORCE_FAIL="${WOLFPROV_FORCE_FAIL:-}"
unset WOLFPROV_FORCE_FAIL || true

# Generate dummy CA cert if missing
if [ ! -f /test/certs/test-ca.crt ]; then
echo "[*] Generating dummy test-ca.crt..."
mkdir -p /test/certs
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout /test/certs/test-ca.key \
-out /test/certs/test-ca.crt \
-days 365 -subj "/CN=Test CA/O=Example"
fi

mkdir -p /etc/pam_pkcs11/cacerts
cp /test/certs/test-ca.crt /etc/pam_pkcs11/cacerts/
pkcs11_make_hash_link /etc/pam_pkcs11/cacerts/

# Generate test certificate and key if missing
if [ ! -f /test/certs/test-cert.pem ]; then
echo "[*] Generating test-cert.pem and key..."
mkdir -p /test/certs
openssl req -newkey rsa:2048 -nodes \
-keyout /test/certs/test-key.pem \
-x509 -days 365 -out /test/certs/test-cert.pem \
-subj "/CN=Test User/OU=Testing/O=Example Corp/C=US"
echo "[*] Configuring pam_pkcs11..."
cat > "$WORK/pam_pkcs11.conf" <<EOF
pam_pkcs11 {
nullok = false;
debug = true;
card_only = true;
use_pkcs11_module = softhsm;
pkcs11_module softhsm {
module = $SOFTHSM_MODULE;
slot_num = 0;
ca_dir = $WORK/cacerts;
crl_dir = $WORK/crls;
cert_policy = ca,signature;
token_type = "SoftHSM token";
}
use_mappers = cn;
mapper cn {
debug = true;
module = internal;
ignorecase = false;
mapfile = "none";
}
}
EOF
cat > "$PAM_SERVICE_FILE" <<EOF
auth sufficient pam_pkcs11.so debug config_file=$WORK/pam_pkcs11.conf
auth requisite pam_deny.so
EOF

# Authenticate testuser through PAM_SERVICE with <token> and <pin>, logging
# to $WORK/<log> 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 <token> and <pin> must fail with <msg>. 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"
13 changes: 3 additions & 10 deletions .github/workflows/pam-pkcs11.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading