diff --git a/.github/workflows/tpm-ssh.yml b/.github/workflows/tpm-ssh.yml
index 419fcd71c..0da943478 100644
--- a/.github/workflows/tpm-ssh.yml
+++ b/.github/workflows/tpm-ssh.yml
@@ -17,6 +17,10 @@ jobs:
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
@@ -47,8 +51,17 @@ jobs:
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 \
- CFLAGS="-DWC_RSA_NO_PADDING"
+ --enable-certgen --enable-certreq --enable-certext --enable-cryptocb \
+ CFLAGS="$EXTRA_CFLAGS"
make
sudo make install
sudo ldconfig
@@ -97,13 +110,14 @@ jobs:
run: |
cd wolfssh
./autogen.sh
- ./configure --enable-tpm
+ ./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
@@ -129,9 +143,55 @@ jobs:
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'
+ if: matrix.keytype == 'rsa' && matrix.hostkey == 'raw'
run: |
cd wolftpm
./examples/keygen/keygen keyblob.bin -rsa -t -pem -eh
@@ -147,7 +207,12 @@ jobs:
if: always()
uses: actions/upload-artifact@v7
with:
- name: test-artifacts-${{ matrix.keytype }}-${{ matrix.sim }}
+ 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
diff --git a/README.md b/README.md
index 554f1298f..020a49a79 100644
--- a/README.md
+++ b/README.md
@@ -622,6 +622,66 @@ Note: RSA host keys are signed with `rsa-sha2-256`. The default echoserver key
auth produced by keygen is `ThisIsMyKeyAuth` (override with the `-G` example's
`ECHOSERVER_TPM_KEY_AUTH`).
+TPM SERVER HOST KEY WITH X.509 CERTIFICATE (ECDSA / RSA)
+=======================================================
+
+The server can present an X.509 certificate as its host key while the matching
+private key stays non-exportable inside the TPM. The client verifies the
+certificate against a trusted CA, so the server's identity is authenticated and
+a man-in-the-middle cannot impersonate it. The exchange hash is signed inside
+the TPM; the private key never enters RAM.
+
+This requires wolfSSH built with certificate support in addition to TPM support,
+and wolfSSL/wolfTPM built with certificate generation:
+
+ wolfSSL
+ $ ./configure --enable-wolfssh --enable-wolftpm --enable-keygen \
+ --enable-certgen --enable-certreq --enable-certext \
+ --enable-cryptocb \
+ CFLAGS="-DWC_RSA_NO_PADDING"
+ wolfTPM
+ $ ./configure --enable-fwtpm --enable-swtpm
+ wolfSSH
+ $ ./configure --enable-tpm --enable-certs
+
+The example under `examples/tpmcertserver` creates a signing key inside the TPM,
+generates a self-signed X.509 certificate from it with
+`wolfTPM2_CSR_Generate_ex()`, then serves with `wolfSSH_CTX_UseTpmHostKey()` and
+`wolfSSH_CTX_UseCert_buffer()`. Run a TPM simulator first (`fwtpm_server` or
+`ibmswtpm2`), then:
+
+ ECDSA: $ ./examples/tpmcertserver/tpmcertserver -k ecc
+ RSA: $ ./examples/tpmcertserver/tpmcertserver -k rsa
+
+The server writes its certificate to `tpm-server-cert.der`. The companion
+client verifies the server against that certificate used as the trusted root:
+
+ $ ./examples/tpmcertserver/tpmcertclient -A tpm-server-cert.der
+
+To integrate this into your own server, load the certificate and bind the TPM
+key. Call `wolfSSH_CTX_UseTpmHostKey()` before `wolfSSH_CTX_UseCert_buffer()` so
+the certificate is linked to the TPM key slot:
+
+ wolfSSH_CTX_UseTpmHostKey(ctx, &tpmDev, &tpmKey);
+ wolfSSH_CTX_UseCert_buffer(ctx, certDer, certDerSz, WOLFSSH_FORMAT_ASN1);
+
+On the client, restrict the accepted host key algorithms to the certificate
+algorithms so the connection cannot silently fall back to a plain host key and
+skip certificate (CA) verification:
+
+ wolfSSH_CTX_SetAlgoListKey(ctx,
+ "x509v3-ecdsa-sha2-nistp256,x509v3-ssh-rsa");
+ wolfSSH_CTX_AddRootCert_buffer(ctx, caDer, caDerSz, WOLFSSH_FORMAT_ASN1);
+
+Notes:
+
+- ECDSA is recommended. It uses SHA-256 and needs no extra build options.
+- RSA certificate host keys use the `x509v3-ssh-rsa` algorithm, which is defined
+ with SHA-1. Modern wolfSSL rejects SHA-1 RSA signatures by default, so RSA
+ additionally requires wolfSSL built with
+ `-DWC_SIG_MIN_HASH_TYPE=WC_HASH_TYPE_SHA`. This re-enables a deprecated hash;
+ prefer ECDSA unless RSA is mandated.
+
WOLFSSH APPLICATIONS
====================
diff --git a/examples/include.am b/examples/include.am
index fb3d8a844..b43b1d7de 100644
--- a/examples/include.am
+++ b/examples/include.am
@@ -4,6 +4,7 @@
include examples/client/include.am
include examples/echoserver/include.am
+include examples/tpmcertserver/include.am
include examples/portfwd/include.am
include examples/sftpclient/include.am
include examples/scpclient/include.am
diff --git a/examples/tpmcertserver/.gitignore b/examples/tpmcertserver/.gitignore
new file mode 100644
index 000000000..31c577208
--- /dev/null
+++ b/examples/tpmcertserver/.gitignore
@@ -0,0 +1,3 @@
+/tpmcertserver
+/tpmcertclient
+/tpm-server-cert.der
diff --git a/examples/tpmcertserver/include.am b/examples/tpmcertserver/include.am
new file mode 100644
index 000000000..0fc68d0f8
--- /dev/null
+++ b/examples/tpmcertserver/include.am
@@ -0,0 +1,21 @@
+# vim:ft=automake
+# All paths should be given relative to the root
+
+if BUILD_TPM
+if BUILD_CERTS
+if BUILD_EXAMPLE_SERVERS
+noinst_PROGRAMS += examples/tpmcertserver/tpmcertserver
+examples_tpmcertserver_tpmcertserver_SOURCES = \
+ examples/tpmcertserver/tpmcertserver.c
+examples_tpmcertserver_tpmcertserver_LDADD = src/libwolfssh.la
+examples_tpmcertserver_tpmcertserver_DEPENDENCIES = src/libwolfssh.la
+endif
+if BUILD_EXAMPLE_CLIENTS
+noinst_PROGRAMS += examples/tpmcertserver/tpmcertclient
+examples_tpmcertserver_tpmcertclient_SOURCES = \
+ examples/tpmcertserver/tpmcertclient.c
+examples_tpmcertserver_tpmcertclient_LDADD = src/libwolfssh.la
+examples_tpmcertserver_tpmcertclient_DEPENDENCIES = src/libwolfssh.la
+endif
+endif
+endif
diff --git a/examples/tpmcertserver/tpmcertclient.c b/examples/tpmcertserver/tpmcertclient.c
new file mode 100644
index 000000000..4262017cb
--- /dev/null
+++ b/examples/tpmcertserver/tpmcertclient.c
@@ -0,0 +1,245 @@
+/* tpmcertclient.c
+ *
+ * Copyright (C) 2014-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSH.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+#ifdef HAVE_CONFIG_H
+ #include
+#endif
+
+#define WOLFSSH_TEST_CLIENT
+
+#include
+#include
+
+#ifdef WOLFSSH_CERTS
+
+#include
+#include
+
+#define TPMCC_USER "jill"
+#define TPMCC_PASSWORD "upthehill"
+#define TPMCC_BUF_SZ 1024
+
+static int TpmCcUserAuth(byte authType, WS_UserAuthData* authData, void* ctx)
+{
+ int ret = WOLFSSH_USERAUTH_FAILURE;
+
+ WOLFSSH_UNUSED(ctx);
+
+ if (authType == WOLFSSH_USERAUTH_PASSWORD) {
+ authData->sf.password.password = (const byte*)TPMCC_PASSWORD;
+ authData->sf.password.passwordSz = (word32)XSTRLEN(TPMCC_PASSWORD);
+ ret = WOLFSSH_USERAUTH_SUCCESS;
+ }
+
+ return ret;
+}
+
+
+/* Host key acceptance callback. wolfSSH verifies the server's X.509 certificate
+ * chain against the root CA loaded with wolfSSH_CTX_AddRootCert_buffer() later,
+ * during the key exchange, when it extracts the public key from the certificate.
+ * Because the client only accepts x509v3 host key algorithms, that CA
+ * verification is always performed. This callback just accepts the presented
+ * host key blob. */
+static int TpmCcHostKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx)
+{
+ WOLFSSH_UNUSED(pubKey);
+ WOLFSSH_UNUSED(pubKeySz);
+ WOLFSSH_UNUSED(ctx);
+ return 0;
+}
+
+
+static int TpmCcLoadFile(const char* file, byte* buf, word32* bufSz)
+{
+ int ret = 0;
+ FILE* f = fopen(file, "rb");
+ size_t n;
+
+ if (f == NULL) {
+ ret = -1;
+ }
+ else {
+ n = fread(buf, 1, *bufSz, f);
+ fclose(f);
+ if (n == 0)
+ ret = -1;
+ else
+ *bufSz = (word32)n;
+ }
+
+ return ret;
+}
+
+
+int main(int argc, char* argv[])
+{
+ int ret;
+ int i;
+ word16 port = 22222;
+ const char* host = "127.0.0.1";
+ const char* caFile = "tpm-server-cert.der";
+ WOLFSSH_CTX* ctx = NULL;
+ WOLFSSH* ssh = NULL;
+ WS_SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID;
+ SOCKADDR_IN_T addr;
+ byte caDer[2048];
+ word32 caDerSz = (word32)sizeof(caDer);
+ byte txt[] = "hello from tpmcertclient\n";
+ byte rxBuf[TPMCC_BUF_SZ];
+
+ /* Line-buffer stdout so output is visible immediately when redirected. */
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
+ for (i = 1; i < argc; i++) {
+ if (XSTRCMP(argv[i], "-p") == 0 && i + 1 < argc)
+ port = (word16)atoi(argv[++i]);
+ else if (XSTRCMP(argv[i], "-h") == 0 && i + 1 < argc)
+ host = argv[++i];
+ else if (XSTRCMP(argv[i], "-A") == 0 && i + 1 < argc)
+ caFile = argv[++i];
+ }
+
+ if (TpmCcLoadFile(caFile, caDer, &caDerSz) != 0) {
+ fprintf(stderr, "Could not read CA file %s\n", caFile);
+ return 1;
+ }
+
+#ifdef DEBUG_WOLFSSH
+ wolfSSH_Debugging_ON();
+#endif
+
+ ret = (wolfSSH_Init() == WS_SUCCESS) ? 0 : -1;
+
+ if (ret == 0) {
+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
+ if (ctx == NULL)
+ ret = -1;
+ }
+
+ if (ret == 0) {
+ wolfSSH_SetUserAuth(ctx, TpmCcUserAuth);
+ wolfSSH_CTX_SetPublicKeyCheck(ctx, TpmCcHostKeyCheck);
+
+ /* Only accept X.509 certificate host keys. Without this the client
+ * would also accept a plain host key, and the server's certificate
+ * (and therefore CA verification) would be bypassed. */
+ if (wolfSSH_CTX_SetAlgoListKey(ctx,
+ "x509v3-ecdsa-sha2-nistp256,x509v3-ecdsa-sha2-nistp384,"
+ "x509v3-ecdsa-sha2-nistp521,x509v3-ssh-rsa") != WS_SUCCESS) {
+ fprintf(stderr, "Could not set host key algorithm list\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ if (wolfSSH_CTX_AddRootCert_buffer(ctx, caDer, caDerSz,
+ WOLFSSH_FORMAT_ASN1) != WS_SUCCESS) {
+ fprintf(stderr, "Could not load root CA certificate\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ ssh = wolfSSH_new(ctx);
+ if (ssh == NULL)
+ ret = -1;
+ }
+
+ if (ret == 0)
+ ret = (wolfSSH_SetUsername(ssh, TPMCC_USER) == WS_SUCCESS) ? 0 : -1;
+
+ if (ret == 0) {
+ WSTARTTCP();
+ build_addr(&addr, host, port);
+ tcp_socket(&sockFd, ((struct sockaddr_in*)&addr)->sin_family);
+ if (connect(sockFd, (const struct sockaddr*)&addr, sizeof(addr)) != 0) {
+ fprintf(stderr, "Could not connect to %s:%u\n", host, port);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ wolfSSH_set_fd(ssh, (int)sockFd);
+ ret = wolfSSH_connect(ssh);
+ if (ret == WS_SUCCESS) {
+ printf("Connected: verified server X.509 host certificate.\n");
+ ret = 0;
+ }
+ else {
+ fprintf(stderr, "wolfSSH_connect failed: %d (%s)\n", ret,
+ wolfSSH_ErrorToName(ret));
+ }
+ }
+
+ if (ret == 0) {
+ int txSz = (int)XSTRLEN((char*)txt);
+ int sent = 0;
+ int rxSz;
+ int n;
+
+ /* stream_send may transmit fewer bytes than requested; loop until the
+ * whole message is sent. */
+ while (sent < txSz) {
+ n = wolfSSH_stream_send(ssh, txt + sent, (word32)(txSz - sent));
+ if (n <= 0) {
+ fprintf(stderr, "stream send failed: %d\n", n);
+ ret = -1;
+ break;
+ }
+ sent += n;
+ }
+
+ if (ret == 0) {
+ rxSz = wolfSSH_stream_read(ssh, rxBuf, sizeof(rxBuf) - 1);
+ if (rxSz <= 0) {
+ fprintf(stderr, "stream read failed: %d\n", rxSz);
+ ret = -1;
+ }
+ else {
+ rxBuf[rxSz] = 0;
+ printf("Echo from server: %s", (char*)rxBuf);
+ }
+ }
+ }
+
+ if (ssh != NULL) {
+ wolfSSH_stream_exit(ssh, 0);
+ wolfSSH_free(ssh);
+ }
+ if (sockFd != WOLFSSH_SOCKET_INVALID)
+ WCLOSESOCKET(sockFd);
+ if (ctx != NULL)
+ wolfSSH_CTX_free(ctx);
+ wolfSSH_Cleanup();
+
+ printf("Done (ret = %d).\n", ret);
+ return (ret == 0) ? 0 : 1;
+}
+
+#else /* !WOLFSSH_CERTS */
+
+int main(void)
+{
+ printf("This example requires wolfSSH built with --enable-certs.\n");
+ return 0;
+}
+
+#endif /* WOLFSSH_CERTS */
diff --git a/examples/tpmcertserver/tpmcertserver.c b/examples/tpmcertserver/tpmcertserver.c
new file mode 100644
index 000000000..70aaa0f3b
--- /dev/null
+++ b/examples/tpmcertserver/tpmcertserver.c
@@ -0,0 +1,398 @@
+/* tpmcertserver.c
+ *
+ * Copyright (C) 2014-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSH.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+#ifdef HAVE_CONFIG_H
+ #include
+#endif
+
+#define WOLFSSH_TEST_SERVER
+
+#include
+#include
+
+#include
+#include
+
+#if defined(WOLFSSH_TPM) && defined(WOLFSSH_CERTS)
+ #include
+#endif
+
+/* wolfTPM's self-signed certificate generation and crypto callback (used to
+ * sign the host certificate through the TPM) require wolfTPM built with cert
+ * generation and the crypto callback. Match wolfTPM's own csr example. */
+#if defined(WOLFSSH_TPM) && defined(WOLFSSH_CERTS) && \
+ !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM2_CERT_GEN) && \
+ defined(WOLFTPM_CRYPTOCB)
+
+#include
+
+#include
+#include
+
+#define TPMCS_USER "jill"
+#define TPMCS_PASSWORD "upthehill"
+#define TPMCS_KEY_AUTH "ThisIsMyKeyAuth"
+#define TPMCS_CERT_MAX 2048
+#define TPMCS_BUF_SZ 1024
+
+static const char* certOutFile = "tpm-server-cert.der";
+
+/* Accept a single fixed user/password. The host identity we are proving to the
+ * client is the X.509 host certificate, not the user credential. */
+static int TpmCsUserAuth(byte authType, WS_UserAuthData* authData, void* ctx)
+{
+ int ret = WOLFSSH_USERAUTH_INVALID_PASSWORD;
+
+ WOLFSSH_UNUSED(ctx);
+
+ if (authType == WOLFSSH_USERAUTH_PASSWORD) {
+ const char* pw = (const char*)authData->sf.password.password;
+ word32 pwSz = authData->sf.password.passwordSz;
+
+ if (pwSz == (word32)XSTRLEN(TPMCS_PASSWORD)
+ && XMEMCMP(pw, TPMCS_PASSWORD, pwSz) == 0) {
+ ret = WOLFSSH_USERAUTH_SUCCESS;
+ }
+ }
+
+ return ret;
+}
+
+
+/* Create a signing key inside the TPM and produce a self-signed X.509
+ * certificate over its public key. The private key never leaves the TPM: the
+ * certificate is signed through the wolfTPM crypto callback. */
+static int TpmCsMakeKeyAndCert(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key,
+ int useEcc, byte* certDer, word32* certDerSz)
+{
+ int rc;
+ int devId = INVALID_DEVID;
+ int sigType;
+ TpmCryptoDevCtx tpmCtx;
+ WOLFTPM2_KEY srk;
+ TPMT_PUBLIC pub;
+ const char* subject;
+ const char* keyUsage = "serverAuth,clientAuth";
+ TPMA_OBJECT attr;
+
+ XMEMSET(&tpmCtx, 0, sizeof(tpmCtx));
+ XMEMSET(&srk, 0, sizeof(srk));
+ XMEMSET(&pub, 0, sizeof(pub));
+
+ attr = TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth
+ | TPMA_OBJECT_sign | TPMA_OBJECT_noDA;
+
+ rc = wolfTPM2_SetCryptoDevCb(dev, wolfTPM2_CryptoDevCb, &tpmCtx, &devId);
+
+ if (rc == 0) {
+ rc = wolfTPM2_CreateSRK(dev, &srk, TPM_ALG_RSA,
+ (const byte*)TPMCS_KEY_AUTH, (int)XSTRLEN(TPMCS_KEY_AUTH));
+ }
+
+ if (rc == 0) {
+ if (useEcc) {
+ subject = "/C=US/ST=Washington/L=Seattle/O=wolfSSL"
+ "/OU=ECC/CN=127.0.0.1";
+ sigType = CTC_SHA256wECDSA;
+ rc = wolfTPM2_GetKeyTemplate_ECC(&pub, attr,
+ TPM_ECC_NIST_P256, TPM_ALG_ECDSA);
+ }
+ else {
+ subject = "/C=US/ST=Washington/L=Seattle/O=wolfSSL"
+ "/OU=RSA/CN=127.0.0.1";
+ sigType = CTC_SHA256wRSA;
+ rc = wolfTPM2_GetKeyTemplate_RSA(&pub,
+ attr | TPMA_OBJECT_decrypt);
+ }
+ }
+
+ if (rc == 0) {
+ rc = wolfTPM2_CreateAndLoadKey(dev, key, &srk.handle, &pub,
+ (const byte*)TPMCS_KEY_AUTH, (int)XSTRLEN(TPMCS_KEY_AUTH));
+ }
+
+ /* Point the crypto callback at the freshly created key so the self-signed
+ * certificate is signed by the TPM. */
+ if (rc == 0) {
+ if (useEcc)
+ tpmCtx.eccKey = key;
+ else
+ tpmCtx.rsaKey = key;
+
+ rc = wolfTPM2_CSR_Generate_ex(dev, key, subject, keyUsage,
+ ENCODING_TYPE_ASN1, certDer, (int)*certDerSz, sigType,
+ 1, devId);
+ }
+
+ if (rc >= 0) {
+ *certDerSz = (word32)rc;
+ rc = 0;
+ }
+
+ /* The crypto callback is only needed to self-sign the certificate. Always
+ * clear it (including on error paths) before wolfSSH runs: host-key signing
+ * uses wolfTPM2_SignHashScheme() directly, and a registered callback would
+ * route wolfSSH's certificate parsing through the TPM. */
+ if (devId != INVALID_DEVID) {
+ int clearRc = wolfTPM2_ClearCryptoDevCb(dev, devId);
+ if (rc == 0)
+ rc = clearRc;
+ }
+
+ /* Restore a clean password session on the device. The certificate signing
+ * left the active session state unset; without this, wolfSSH's
+ * wolfTPM2_SignHashScheme() call fails with ctx->session == NULL. */
+ if (rc == 0) {
+ rc = wolfTPM2_UnsetAuth(dev, 0);
+ }
+
+ /* On any failure, unload the signing key so a retry does not exhaust TPM
+ * transient object memory. */
+ if (rc != 0) {
+ wolfTPM2_UnloadHandle(dev, &key->handle);
+ }
+
+ /* The parent storage key is only needed to create the signing key. */
+ wolfTPM2_UnloadHandle(dev, &srk.handle);
+
+ return rc;
+}
+
+
+static int TpmCsWriteCert(const char* file, const byte* der, word32 derSz)
+{
+ int ret = 0;
+ FILE* f = fopen(file, "wb");
+
+ if (f == NULL) {
+ ret = -1;
+ }
+ else {
+ if (fwrite(der, 1, derSz, f) != derSz)
+ ret = -1;
+ fclose(f);
+ }
+
+ return ret;
+}
+
+
+static int TpmCsEcho(WOLFSSH* ssh)
+{
+ int ret = WS_SUCCESS;
+ int rxSz;
+ int txSz;
+ int sent;
+ byte buf[TPMCS_BUF_SZ];
+
+ do {
+ rxSz = wolfSSH_stream_read(ssh, buf, sizeof(buf));
+ if (rxSz > 0) {
+ /* stream_send may transmit fewer bytes than requested; loop until
+ * the whole chunk is echoed. */
+ sent = 0;
+ while (sent < rxSz) {
+ txSz = wolfSSH_stream_send(ssh, buf + sent, rxSz - sent);
+ if (txSz <= 0) {
+ ret = txSz;
+ break;
+ }
+ sent += txSz;
+ }
+ if (ret != WS_SUCCESS)
+ break;
+ }
+ } while (rxSz > 0);
+
+ if (rxSz < 0 && rxSz != WS_EOF)
+ ret = rxSz;
+
+ return ret;
+}
+
+
+int main(int argc, char* argv[])
+{
+ int ret;
+ int useEcc = 1;
+ word16 port = 22222;
+ int i;
+ WOLFTPM2_DEV dev;
+ WOLFTPM2_KEY hostKey;
+ WOLFSSH_CTX* ctx = NULL;
+ WOLFSSH* ssh = NULL;
+ WS_SOCKET_T listenFd = WOLFSSH_SOCKET_INVALID;
+ WS_SOCKET_T clientFd = WOLFSSH_SOCKET_INVALID;
+ SOCKADDR_IN_T clientAddr;
+ socklen_t clientAddrSz = sizeof(clientAddr);
+ byte certDer[TPMCS_CERT_MAX];
+ word32 certDerSz = (word32)sizeof(certDer);
+
+ /* Line-buffer stdout so progress is visible immediately when redirected to
+ * a file (the server runs while a test reads its output). */
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
+ XMEMSET(&dev, 0, sizeof(dev));
+ XMEMSET(&hostKey, 0, sizeof(hostKey));
+
+ for (i = 1; i < argc; i++) {
+ if (XSTRCMP(argv[i], "-k") == 0 && i + 1 < argc) {
+ i++;
+ if (XSTRCMP(argv[i], "rsa") == 0)
+ useEcc = 0;
+ else
+ useEcc = 1;
+ }
+ else if (XSTRCMP(argv[i], "-p") == 0 && i + 1 < argc) {
+ i++;
+ port = (word16)atoi(argv[i]);
+ }
+ }
+
+ printf("wolfSSH TPM X.509 host-key server (%s)\n", useEcc ? "ECDSA" : "RSA");
+
+ ret = wolfTPM2_Init(&dev, TPM2_IoCb, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "wolfTPM2_Init failed: %d\n", ret);
+ return 1;
+ }
+
+ ret = TpmCsMakeKeyAndCert(&dev, &hostKey, useEcc, certDer, &certDerSz);
+ if (ret != 0) {
+ fprintf(stderr, "TPM key/cert provisioning failed: %d\n", ret);
+ wolfTPM2_Cleanup(&dev);
+ return 1;
+ }
+ printf("Provisioned TPM host key and self-signed X.509 cert (%u bytes)\n",
+ certDerSz);
+
+ /* The companion client trusts this file as its root CA. If it cannot be
+ * written, fail now rather than serve a certificate the client cannot
+ * load (or let it read a stale certificate from a previous run). */
+ if (TpmCsWriteCert(certOutFile, certDer, certDerSz) != 0) {
+ fprintf(stderr, "Could not write server certificate to %s\n",
+ certOutFile);
+ wolfTPM2_UnloadHandle(&dev, &hostKey.handle);
+ wolfTPM2_Cleanup(&dev);
+ return 1;
+ }
+ printf("Wrote server certificate to %s (use as client -A CA)\n",
+ certOutFile);
+
+#ifdef DEBUG_WOLFSSH
+ wolfSSH_Debugging_ON();
+#endif
+
+ if (wolfSSH_Init() != WS_SUCCESS) {
+ fprintf(stderr, "wolfSSH_Init failed\n");
+ wolfTPM2_UnloadHandle(&dev, &hostKey.handle);
+ wolfTPM2_Cleanup(&dev);
+ return 1;
+ }
+
+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
+ if (ctx == NULL) {
+ fprintf(stderr, "wolfSSH_CTX_new failed\n");
+ ret = WS_MEMORY_E;
+ }
+
+ if (ret == 0) {
+ wolfSSH_SetUserAuth(ctx, TpmCsUserAuth);
+
+ /* Bind the TPM host key first so a private-key slot exists, then load
+ * the matching X.509 certificate. UpdateHostCertificates() ties the
+ * certificate to the TPM key so the exchange signs through the TPM. */
+ ret = wolfSSH_CTX_UseTpmHostKey(ctx, &dev, &hostKey);
+ if (ret != WS_SUCCESS)
+ fprintf(stderr, "wolfSSH_CTX_UseTpmHostKey failed: %d (%s)\n",
+ ret, wolfSSH_ErrorToName(ret));
+ }
+
+ if (ret == 0) {
+ ret = wolfSSH_CTX_UseCert_buffer(ctx, certDer, certDerSz,
+ WOLFSSH_FORMAT_ASN1);
+ if (ret != WS_SUCCESS)
+ fprintf(stderr, "wolfSSH_CTX_UseCert_buffer failed: %d (%s)\n",
+ ret, wolfSSH_ErrorToName(ret));
+ }
+
+ if (ret == 0) {
+ WSTARTTCP();
+ tcp_listen(&listenFd, &port, 1);
+ printf("Listening on port %u. Waiting for a client...\n", port);
+
+ clientFd = accept(listenFd, (struct sockaddr*)&clientAddr,
+ (socklen_t*)&clientAddrSz);
+ if (clientFd == WOLFSSH_SOCKET_INVALID)
+ ret = WS_SOCKET_ERROR_E;
+ }
+
+ if (ret == 0) {
+ ssh = wolfSSH_new(ctx);
+ if (ssh == NULL)
+ ret = WS_MEMORY_E;
+ }
+
+ if (ret == 0) {
+ wolfSSH_set_fd(ssh, (int)clientFd);
+
+ ret = wolfSSH_accept(ssh);
+ if (ret == WS_SUCCESS) {
+ printf("Client connected and verified the TPM-backed host "
+ "certificate.\n");
+ ret = TpmCsEcho(ssh);
+ }
+ else {
+ fprintf(stderr, "wolfSSH_accept failed: %d (%s)\n", ret,
+ wolfSSH_ErrorToName(ret));
+ }
+ }
+
+ if (ssh != NULL) {
+ wolfSSH_stream_exit(ssh, 0);
+ wolfSSH_free(ssh);
+ }
+ if (clientFd != WOLFSSH_SOCKET_INVALID)
+ WCLOSESOCKET(clientFd);
+ if (listenFd != WOLFSSH_SOCKET_INVALID)
+ WCLOSESOCKET(listenFd);
+ if (ctx != NULL)
+ wolfSSH_CTX_free(ctx);
+ wolfSSH_Cleanup();
+
+ wolfTPM2_UnloadHandle(&dev, &hostKey.handle);
+ wolfTPM2_Cleanup(&dev);
+
+ printf("Done (ret = %d).\n", (ret == WS_SUCCESS) ? 0 : ret);
+ return (ret == WS_SUCCESS) ? 0 : 1;
+}
+
+#else /* missing TPM/cert prerequisites */
+
+int main(void)
+{
+ printf("This example requires wolfSSH built with --enable-tpm "
+ "--enable-certs, and wolfTPM/wolfSSL with certificate generation "
+ "and the crypto callback (wolfSSL --enable-certgen --enable-certreq "
+ "--enable-certext --enable-cryptocb, wolfTPM cert generation).\n");
+ return 0;
+}
+
+#endif
diff --git a/src/internal.c b/src/internal.c
index 22c070ca9..050caf8fa 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -8760,11 +8760,6 @@ static int DoUserAuthFailure(WOLFSSH* ssh,
for (j = 0; j < sizeof(ssh->supportedAuth); j++) {
if (authList[i] == ssh->supportedAuth[j]) {
switch(authList[i]) {
-#ifdef WOLFSSH_TPM
- case ID_USERAUTH_PUBLICKEY:
- authType |= WOLFSSH_USERAUTH_PUBLICKEY;
- break;
-#else /* !WOLFSSH_TPM */
case ID_USERAUTH_PASSWORD:
authType |= WOLFSSH_USERAUTH_PASSWORD;
break;
@@ -8776,12 +8771,12 @@ static int DoUserAuthFailure(WOLFSSH* ssh,
}
break;
#endif
-#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA)
+#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) || \
+ defined(WOLFSSH_TPM)
case ID_USERAUTH_PUBLICKEY:
authType |= WOLFSSH_USERAUTH_PUBLICKEY;
break;
#endif
-#endif /* WOLFSSH_TPM */
default:
break;
}
@@ -16422,6 +16417,19 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig)
keySig_ptr->sigId = ID_NONE;
keySig_ptr->heap = ssh->ctx->heap;
+#ifdef WOLFSSH_TPM
+ /* When the client has a TPM key configured, prefer publickey auth so
+ * the TPM key is used even if the server also offers password or
+ * keyboard-interactive. Applied before any method-specific branch. */
+ if (ssh->ctx->tpmKey != NULL
+ && (authType & WOLFSSH_USERAUTH_PUBLICKEY)) {
+ authType &= ~WOLFSSH_USERAUTH_PASSWORD;
+ #ifdef WOLFSSH_KEYBOARD_INTERACTIVE
+ authType &= ~WOLFSSH_USERAUTH_KEYBOARD;
+ #endif
+ }
+#endif
+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
/* Callback happens later for keyboard auth */
if (authType & WOLFSSH_USERAUTH_KEYBOARD) {