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 # Builds every check_PROGRAM -- no other job compiles wolfSSH's tests # with TPM support -- but runs only api.test, the one suite with TPM # tests. One cell: these tests ignore the simulator and host key. - name: Run API tests if: matrix.keytype == 'ecc' && matrix.sim == 'ibmswtpm2' && matrix.hostkey == 'raw' run: | cd wolfssh # A non-TPM build compiles every TPM-guarded test out, silently. if ! grep -q '^AM_CPPFLAGS = .*-DWOLFSSH_TPM' Makefile; then echo "Not a TPM build; the TPM-guarded tests would compile out." grep '^AM_CPPFLAGS' Makefile exit 1 fi make check TESTS=tests/api.test - name: Show API test log on failure if: failure() && (matrix.keytype == 'ecc' && matrix.sim == 'ibmswtpm2' && matrix.hostkey == 'raw') run: cat wolfssh/tests/api.log || true # 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 "----- server -----"; cat tpmcert_server_neg.txt echo "client correctly rejected the untrusted server:" cat tpmcert_client_neg.txt # Guard against a false pass: the server must have come up, and the # client must have failed inside the SSH handshake (certificate # rejection) rather than from a plain connection failure. grep -q "Listening on port" tpmcert_server_neg.txt grep -q "wolfSSH_connect failed" 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 wolfssh/tests/api.log