diff --git a/examples/include.am b/examples/include.am index fb3d8a84..b43b1d7d 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 00000000..31c57720 --- /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 00000000..0fc68d0f --- /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 00000000..78fcac7b --- /dev/null +++ b/examples/tpmcertserver/tpmcertclient.c @@ -0,0 +1,238 @@ +/* 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; +} + + +/* The server host key is an X.509 certificate; it was already verified against + * the root CA loaded with wolfSSH_CTX_AddRootCert_buffer(). Accept it. */ +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]; + + 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 00000000..6d0b753b --- /dev/null +++ b/examples/tpmcertserver/tpmcertserver.c @@ -0,0 +1,393 @@ +/* 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. Clear + * it before wolfSSH runs: host-key signing uses wolfTPM2_SignHashScheme() + * directly, and a registered callback would route wolfSSH's certificate + * parsing through the TPM. This reset is required, so treat a failure as + * fatal. */ + if (rc == 0 && devId != INVALID_DEVID) { + rc = wolfTPM2_ClearCryptoDevCb(dev, devId); + } + + /* 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); + + 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