Skip to content

Add TPM-backed X.509 host certificate support and fix TPM-build client auth #1168

Add TPM-backed X.509 host certificate support and fix TPM-build client auth

Add TPM-backed X.509 host certificate support and fix TPM-build client auth #1168

Workflow file for this run

name: TPM SSH Test
on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]
workflow_dispatch:
jobs:
test-tpm-ssh:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
keytype: [ rsa, ecc ]
sim: [ ibmswtpm2, fwtpm ]
# raw: plain TPM host key (verified with OpenSSH).
# x509: TPM host key presented as an X.509 certificate, verified by the
# tpmcertserver/tpmcertclient example.
hostkey: [ raw, x509 ]
steps:
- uses: actions/checkout@v6
with:
path: wolfssh
- name: Clone wolfSSL
uses: actions/checkout@v6
with:
repository: wolfSSL/wolfssl
path: wolfssl
- name: Clone wolfTPM
uses: actions/checkout@v6
with:
repository: wolfSSL/wolftpm
path: wolftpm
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libtool automake autoconf
sudo apt-get install -y build-essential git autoconf-archive \
libcmocka-dev libssl-dev uthash-dev libglib2.0-dev \
tpm2-tools openssh-client sshpass
- name: Build wolfSSL
run: |
cd wolfssl
./autogen.sh
# certgen/certreq/certext/cryptocb: generate the X.509 host certificate
# from the TPM key via the crypto callback.
EXTRA_CFLAGS="-DWC_RSA_NO_PADDING"
# The RSA x509v3-ssh-rsa host cert is SHA-1; modern wolfSSL otherwise
# rejects SHA-1 RSA signatures. Only needed for the RSA cells.
if [ "${{ matrix.keytype }}" = "rsa" ]; then
EXTRA_CFLAGS="$EXTRA_CFLAGS -DWC_SIG_MIN_HASH_TYPE=WC_HASH_TYPE_SHA"
fi
./configure --enable-wolftpm --enable-wolfssh --enable-keygen \
--enable-certgen --enable-certreq --enable-certext --enable-cryptocb \
CFLAGS="$EXTRA_CFLAGS"
make
sudo make install
sudo ldconfig
# The wolfTPM client library uses the SWTPM TCP transport (port 2321) for
# both simulators. The fwTPM build additionally produces fwtpm_server.
- name: Build wolfTPM (fwTPM)
if: matrix.sim == 'fwtpm'
run: |
cd wolftpm
./autogen.sh
./configure --enable-fwtpm --enable-swtpm
make
sudo make install
sudo ldconfig
- name: Start fwTPM simulator
if: matrix.sim == 'fwtpm'
run: |
cd wolftpm
./src/fwtpm/fwtpm_server &
echo "fwtpm_server started with PID: $!"
sleep 2
- name: Build wolfTPM (SWTPM)
if: matrix.sim == 'ibmswtpm2'
run: |
cd wolftpm
./autogen.sh
./configure --enable-swtpm
make
sudo make install
sudo ldconfig
- name: Start ibmswtpm2 simulator
if: matrix.sim == 'ibmswtpm2'
run: |
git clone https://github.com/kgoldman/ibmswtpm2
cd ibmswtpm2/src
make
./tpm_server &
echo "tpm_server started with PID: $!"
sleep 2
- name: Build wolfSSH
run: |
cd wolfssh
./autogen.sh
./configure --enable-tpm --enable-certs
make
sudo make install
sudo ldconfig
# Server host key resident in the TPM: the private key never enters RAM.
- name: Test TPM host key (${{ matrix.keytype }})
if: matrix.hostkey == 'raw'
run: |
cd wolftpm
./examples/keygen/keygen hostkey.bin -${{ matrix.keytype }} -t -eh
cd ../wolfssh
./examples/echoserver/echoserver -1 -p 22222 \
-G ../wolftpm/hostkey.bin &
echo "Echoserver (TPM ${{ matrix.keytype }} host key) PID: $!"
sleep 2
if [ "${{ matrix.keytype }}" = "ecc" ]; then
HKA=ecdsa-sha2-nistp256
else
HKA=rsa-sha2-256
fi
timeout 20 sshpass -p upthehill ssh -v -p 22222 \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o PreferredAuthentications=password -o PubkeyAuthentication=no \
-o HostKeyAlgorithms=$HKA \
jill@localhost exit > ssh_out.txt 2>&1 || true
echo "----- ssh output -----"
cat ssh_out.txt
grep -q "Authenticated to localhost" ssh_out.txt
# Server presents an X.509 host certificate whose private key lives in the
# TPM. The client verifies the certificate against it as the trusted CA and
# rejects a mismatched CA; the exchange hash is signed inside the TPM.
- name: Test TPM X.509 host certificate (${{ matrix.keytype }})
if: matrix.hostkey == 'x509'
run: |
cd wolfssh
./examples/tpmcertserver/tpmcertserver -k ${{ matrix.keytype }} \
-p 22223 > tpmcert_server.txt 2>&1 &
echo "tpmcertserver (X.509 ${{ matrix.keytype }}) PID: $!"
# RSA key generation inside the TPM can take longer than ECC.
for i in $(seq 1 40); do
if ss -ltn 2>/dev/null | grep -q ':22223'; then break; fi
sleep 1
done
./examples/tpmcertserver/tpmcertclient -A tpm-server-cert.der \
-p 22223 -h 127.0.0.1 > tpmcert_client.txt 2>&1
echo "----- server -----"; cat tpmcert_server.txt
echo "----- client -----"; cat tpmcert_client.txt
grep -q "verified server X.509 host certificate" tpmcert_client.txt
grep -q "Client connected and verified the TPM-backed host certificate" \
tpmcert_server.txt
# Negative test: a client that trusts an unrelated CA must reject the server.
- name: Test TPM X.509 host certificate rejects wrong CA (${{ matrix.keytype }})
if: matrix.hostkey == 'x509'
run: |
cd wolfssh
./examples/tpmcertserver/tpmcertserver -k ${{ matrix.keytype }} \
-p 22224 > tpmcert_server_neg.txt 2>&1 &
echo "tpmcertserver (X.509 neg ${{ matrix.keytype }}) PID: $!"
for i in $(seq 1 40); do
if ss -ltn 2>/dev/null | grep -q ':22224'; then break; fi
sleep 1
done
# keys/ca-cert-ecc.der is an unrelated CA; the self-signed host cert
# does not chain to it, so the client must refuse to connect.
if ./examples/tpmcertserver/tpmcertclient -A keys/ca-cert-ecc.der \
-p 22224 -h 127.0.0.1 > tpmcert_client_neg.txt 2>&1; then
echo "ERROR: client accepted a server with an untrusted CA"
cat tpmcert_client_neg.txt
exit 1
fi
echo "client correctly rejected the untrusted server:"
cat tpmcert_client_neg.txt
# Client public-key authentication with a TPM-resident key (RSA only).
- name: Test TPM client public-key auth
if: matrix.keytype == 'rsa' && matrix.hostkey == 'raw'
run: |
cd wolftpm
./examples/keygen/keygen keyblob.bin -rsa -t -pem -eh
ssh-keygen -f key.pem -i -m PKCS8 > ../wolfssh/key.ssh
cd ../wolfssh
./examples/echoserver/echoserver -1 -s key.ssh &
echo "Echoserver (authorized TPM client key) PID: $!"
sleep 2
timeout 20 ./examples/client/client -i ../wolftpm/keyblob.bin \
-u hansel -K ThisIsMyKeyAuth
- name: Archive test artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: test-artifacts-${{ matrix.keytype }}-${{ matrix.sim }}-${{ matrix.hostkey }}
if-no-files-found: ignore
path: |
wolftpm/hostkey.bin
wolfssh/ssh_out.txt
wolfssh/tpmcert_server.txt
wolfssh/tpmcert_client.txt
wolfssh/tpmcert_server_neg.txt
wolfssh/tpmcert_client_neg.txt