From 57c2ff71260e9edea6bd080bb4c0916b8e949160 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 15 Jun 2026 18:11:57 -0700 Subject: [PATCH 1/2] Add TPM 2.0 resident server host keys for ECDSA and RSA --- .github/workflows/tpm-ssh.yml | 144 +++++++++-------- README.md | 34 ++++ examples/echoserver/echoserver.c | 262 +++++++++++++++++++++++++++---- src/internal.c | 198 +++++++++++++++++++++-- src/ssh.c | 69 ++++++++ wolfssh/internal.h | 11 ++ wolfssh/ssh.h | 2 + 7 files changed, 611 insertions(+), 109 deletions(-) diff --git a/.github/workflows/tpm-ssh.yml b/.github/workflows/tpm-ssh.yml index 1ecdf4aae..0958e3e45 100644 --- a/.github/workflows/tpm-ssh.yml +++ b/.github/workflows/tpm-ssh.yml @@ -11,12 +11,17 @@ jobs: test-tpm-ssh: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + keytype: [ rsa, ecc ] + sim: [ ibmswtpm2, fwtpm ] + steps: - uses: actions/checkout@v6 with: path: wolfssh - # Clone dependencies - name: Clone wolfSSL uses: actions/checkout@v6 with: @@ -29,38 +34,46 @@ jobs: repository: wolfSSL/wolftpm path: wolftpm - # Install dependencies - 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 + tpm2-tools openssh-client sshpass - # Clone, build, and start TPM Simulator - - name: Clone and Build TPM Simulator + - name: Build wolfSSL run: | - git clone https://github.com/kgoldman/ibmswtpm2 - cd ibmswtpm2/src + cd wolfssl + ./autogen.sh + ./configure --enable-wolftpm --enable-wolfssh --enable-keygen \ + CFLAGS="-DWC_RSA_NO_PADDING" make - ./tpm_server & - sleep 2 - cd ../.. + sudo make install + sudo ldconfig - # Build and install wolfSSL - - name: Build wolfSSL + # 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 wolfssl + cd wolftpm ./autogen.sh - ./configure --enable-wolftpm --enable-wolfssh + ./configure --enable-fwtpm --enable-swtpm make sudo make install sudo ldconfig - cd .. - # Build and install wolfTPM - - name: Build wolfTPM + - 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 @@ -68,9 +81,17 @@ jobs: make sudo make install sudo ldconfig - cd .. - # Build wolfSSH + - 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 @@ -79,66 +100,53 @@ jobs: make sudo make install sudo ldconfig - cd .. - # Test TPM SSH Default Password - - name: Test TPM SSH Default Password + # Server host key resident in the TPM: the private key never enters RAM. + - name: Test TPM host key (${{ matrix.keytype }}) run: | - # Generate key with default password cd wolftpm - ./examples/keygen/keygen keyblob1.bin -rsa -t -pem -eh - cp key.pem key1.pem # Save the key for first test - - # Convert key to SSH format - ssh-keygen -f key1.pem -i -m PKCS8 > ../wolfssh/key1.ssh - cd .. - - # Start echoserver and wait for it to be ready - cd wolfssh - ./examples/echoserver/echoserver -1 -s key1.ssh & - echo "Echoserver started with PID: $!" + ./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 - cd .. - - # Test client connection with default password - cd wolfssh - ./examples/client/client -i ../wolftpm/keyblob1.bin -u hansel -K ThisIsMyKeyAuth - cd .. - # Test the TPM SSH Custom Password - - name: Test TPM SSH Custom Password + 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 + + # Client public-key authentication with a TPM-resident key (RSA only). + - name: Test TPM client public-key auth + if: matrix.keytype == 'rsa' run: | - # Test with custom password cd wolftpm - ./examples/keygen/keygen keyblob2.bin -rsa -t -pem -eh -auth=custompassword - cp key.pem key2.pem # Save the key for second test - - # Convert key to SSH format - ssh-keygen -f key2.pem -i -m PKCS8 > ../wolfssh/key2.ssh - cd .. - - # Start echoserver and wait for it to be ready - cd wolfssh - ./examples/echoserver/echoserver -1 -s key2.ssh & - echo "Echoserver started with PID: $!" + ./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 - cd .. - - # Test with custom password - cd wolfssh - ./examples/client/client -i ../wolftpm/keyblob2.bin -u hansel -K custompassword - cd .. + ./examples/client/client -i ../wolftpm/keyblob.bin -u hansel \ + -K ThisIsMyKeyAuth - # Archive artifacts for debugging - name: Archive test artifacts if: always() uses: actions/upload-artifact@v7 with: - name: test-artifacts + name: test-artifacts-${{ matrix.keytype }}-${{ matrix.sim }} path: | - wolftpm/keyblob1.bin - wolftpm/keyblob2.bin - wolftpm/key1.pem - wolftpm/key2.pem - wolfssh/key1.ssh - wolfssh/key2.ssh + wolftpm/hostkey.bin + wolfssh/ssh_out.txt diff --git a/README.md b/README.md index f47512221..554f1298f 100644 --- a/README.md +++ b/README.md @@ -588,6 +588,40 @@ If you used a custom password for keygen you must specify the password you used: $ ./examples/client/client -i ../wolfTPM/keyblob.bin -u hansel -K +TPM SERVER HOST KEY (ECDSA / RSA) +================================= + +The server can also keep its own host key inside the TPM so the host private +key is never present in RAM. Build wolfSSL, wolfTPM, and wolfSSH the same way +as above (`--enable-tpm`). Both ECDSA and RSA host keys are supported. + +Generate a host key blob under the endorsement hierarchy (ECC or RSA): + + $ ./examples/keygen/keygen hostkey.bin -ecc -t -eh + $ ./examples/keygen/keygen hostkey.bin -rsa -t -eh + +Start the echoserver with the TPM-resident host key using `-G`: + + $ ./examples/echoserver/echoserver -G ../wolfTPM/hostkey.bin + +The server loads the key blob into the TPM, registers it with +`wolfSSH_CTX_UseTpmHostKey()`, and advertises the matching host key algorithm +(`ecdsa-sha2-nistp256` or `rsa-sha2-256`). The exchange hash is signed by the +TPM; the host private key never leaves it. Any client that accepts the host +key can connect: + + $ ssh -o HostKeyAlgorithms=ecdsa-sha2-nistp256 user@host + $ ssh -o HostKeyAlgorithms=rsa-sha2-256 user@host + +To integrate this into your own server, provision the key once into the TPM, +load its handle at boot into a `WOLFTPM2_KEY`, and register it: + + wolfSSH_CTX_UseTpmHostKey(ctx, &tpmDev, &tpmKey); + +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`). + WOLFSSH APPLICATIONS ==================== diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 571882a91..6a6e3a403 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -45,6 +45,10 @@ #include #include #include +#ifdef WOLFSSH_TPM + #include + #include +#endif #include "examples/echoserver/echoserver.h" @@ -2233,6 +2237,173 @@ static int LoadPubKeyList(StrList* strList, int format, PwMapList* mapList) #endif #ifdef WOLFSSH_TPM +/* Default key auth produced by 'keygen ... -ecc -t -eh' (override with -auth). */ +#define ECHOSERVER_TPM_KEY_AUTH "ThisIsMyKeyAuth" +static WOLFTPM2_DEV tpmHostDev; +static WOLFTPM2_KEY tpmHostKey; +static int tpmHostKeyValid = 0; + +static int EchoserverReadKeyBlob(const char* filename, WOLFTPM2_KEYBLOB* key) +{ + int rc = 0; +#if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) + WFILE* fp = NULL; + size_t fileSz = 0; + size_t bytesRead = 0; + byte pubAreaBuffer[sizeof(TPM2B_PUBLIC)]; + int pubAreaSize; + + WMEMSET(key, 0, sizeof(WOLFTPM2_KEYBLOB)); + + if (WFOPEN(NULL, &fp, filename, "rb") != 0 || fp == WBADFILE) { + fprintf(stderr, "Failed to open TPM key blob %s\n", filename); + return BUFFER_E; + } + + WFSEEK(NULL, fp, 0, WSEEK_END); + fileSz = WFTELL(NULL, fp); + WREWIND(NULL, fp); + + if (fileSz > sizeof(key->priv) + sizeof(key->pub)) { + rc = BUFFER_E; + } + + if (rc == 0) { + bytesRead = WFREAD(NULL, &key->pub.size, 1, sizeof(key->pub.size), fp); + if (bytesRead != sizeof(key->pub.size)) + rc = BUFFER_E; + else + fileSz -= bytesRead; + } + + if (rc == 0 && + (sizeof(UINT16) + (size_t)key->pub.size) > sizeof(pubAreaBuffer)) { + rc = BUFFER_E; + } + + if (rc == 0) { + bytesRead = WFREAD(NULL, pubAreaBuffer, 1, + sizeof(UINT16) + key->pub.size, fp); + if (bytesRead != sizeof(UINT16) + key->pub.size) + rc = BUFFER_E; + else + fileSz -= bytesRead; + } + + if (rc == 0) { + /* Bound the parse to the bytes actually read so a malformed size + * field cannot consume the uninitialized tail of the buffer. */ + rc = TPM2_ParsePublic(&key->pub, pubAreaBuffer, + (word32)(sizeof(UINT16) + key->pub.size), &pubAreaSize); + } + + if (rc == 0 && + pubAreaSize != (int)(sizeof(UINT16) + key->pub.size)) { + rc = BUFFER_E; + } + + if (rc == 0 && fileSz > sizeof(key->priv)) { + rc = BUFFER_E; + } + + if (rc == 0 && fileSz > 0) { + bytesRead = WFREAD(NULL, &key->priv, 1, fileSz, fp); + if (bytesRead != fileSz) + rc = BUFFER_E; + } + + if (rc == 0 && key->priv.size > sizeof(key->priv.buffer)) { + rc = BUFFER_E; + } + + WFCLOSE(NULL, fp); +#else + (void)filename; + (void)key; + rc = WS_NOT_COMPILED; +#endif + return rc; +} + +/* Loads an ECC host key blob into the TPM and registers it as the server host + * key so the private key never enters RAM. */ +static int EchoserverInitTpmHostKey(WOLFSSH_CTX* ctx, const char* keyFile) +{ + int rc; + TPMI_ALG_PUBLIC alg = TPM_ALG_ECC; + WOLFTPM2_KEY endorse; + WOLFTPM2_KEYBLOB keyBlob; + WOLFTPM2_SESSION tpmSession; + + WMEMSET(&endorse, 0, sizeof(endorse)); + WMEMSET(&tpmSession, 0, sizeof(tpmSession)); + WMEMSET(&tpmHostKey, 0, sizeof(tpmHostKey)); + + rc = wolfTPM2_Init(&tpmHostDev, TPM2_IoCb, NULL); + + if (rc == 0) { + rc = EchoserverReadKeyBlob(keyFile, &keyBlob); + } + + /* Match the endorsement key type to the host key (RSA or ECC). */ + if (rc == 0) { + alg = keyBlob.pub.publicArea.type; + rc = wolfTPM2_CreateEK(&tpmHostDev, &endorse, alg); + } + + if (rc == 0) { + endorse.handle.policyAuth = 1; + rc = wolfTPM2_CreateAuthSession_EkPolicy(&tpmHostDev, &tpmSession); + } + + if (rc == 0) { + rc = wolfTPM2_SetAuthSession(&tpmHostDev, 0, &tpmSession, 0); + } + + if (rc == 0) { + keyBlob.handle.auth.size = (word32)XSTRLEN(ECHOSERVER_TPM_KEY_AUTH); + XMEMCPY(keyBlob.handle.auth.buffer, ECHOSERVER_TPM_KEY_AUTH, + keyBlob.handle.auth.size); + rc = wolfTPM2_LoadKey(&tpmHostDev, &keyBlob, &endorse.handle); + } + + if (rc == 0) { + XMEMCPY(&tpmHostKey.handle, &keyBlob.handle, sizeof(tpmHostKey.handle)); + XMEMCPY(&tpmHostKey.pub, &keyBlob.pub, sizeof(tpmHostKey.pub)); + rc = wolfSSH_CTX_UseTpmHostKey(ctx, &tpmHostDev, &tpmHostKey); + } + + /* The EK and policy session are only needed to load the key. Drop the + * session so signing uses the key's own auth, then flush both handles. */ + wolfTPM2_UnsetAuth(&tpmHostDev, 0); + wolfTPM2_UnloadHandle(&tpmHostDev, &endorse.handle); + wolfTPM2_UnloadHandle(&tpmHostDev, &tpmSession.handle); + + if (rc == 0) { + tpmHostKeyValid = 1; + } + else { + wolfTPM2_UnloadHandle(&tpmHostDev, &tpmHostKey.handle); + wolfTPM2_Cleanup(&tpmHostDev); + } + + /* keyBlob holds the private blob and key auth; the session may hold auth. */ + wc_ForceZero(&keyBlob, sizeof(keyBlob)); + wc_ForceZero(&tpmSession, sizeof(tpmSession)); + + return rc; +} + +static void EchoserverCleanupTpmHostKey(void) +{ + if (tpmHostKeyValid) { + wolfTPM2_UnloadHandle(&tpmHostDev, &tpmHostKey.handle); + wolfTPM2_Cleanup(&tpmHostDev); + wc_ForceZero(&tpmHostKey, sizeof(tpmHostKey)); + tpmHostKeyValid = 0; + } +} + static char* LoadTpmSshKey(const char* keyFile, const char* username) { WFILE* file = NULL; @@ -2546,6 +2717,9 @@ static void ShowUsage(void) printf(" -I :\n" " load in a SSH public key to accept from peer\n"); printf(" -s load in a TPM public key file to replace default hansel key\n"); +#ifdef WOLFSSH_TPM + printf(" -G load an ECC/RSA host key blob from the TPM (private key stays in the TPM)\n"); +#endif printf(" -J :\n" " load in an X.509 PEM cert to accept from peer\n"); printf(" -K :\n" @@ -2584,11 +2758,20 @@ static INLINE void SignalTcpReady(tcp_ready* ready, word16 port) #endif } +#ifdef WOLFSSH_TPM +#define ES_ERROR(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + serverArgs->return_code = EXIT_FAILURE; \ + EchoserverCleanupTpmHostKey(); \ + WOLFSSL_RETURN_FROM_THREAD(0); \ +} while(0) +#else #define ES_ERROR(...) do { \ fprintf(stderr, __VA_ARGS__); \ serverArgs->return_code = EXIT_FAILURE; \ WOLFSSL_RETURN_FROM_THREAD(0); \ } while(0) +#endif static byte wantwrite = 0; /*flag to return want write on first highwater call*/ @@ -2646,6 +2829,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) ES_HEAP_HINT* heap = NULL; #ifdef WOLFSSH_TPM static char* tpmKeyPath = NULL; + static char* tpmHostKeyPath = NULL; #endif int multipleConnections = 1; int userEcc = 0; @@ -2671,7 +2855,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #endif if (argc > 0) { - const char* optlist = "?1a:d:efEp:R:Ni:j:i:I:J:K:P:k:b:x:m:c:s:H"; + const char* optlist = "?1a:d:efEp:R:Ni:j:i:I:J:K:P:k:b:x:m:c:s:G:H"; myoptind = 0; while ((ch = mygetopt(argc, argv, optlist)) != -1) { switch (ch) { @@ -2783,6 +2967,15 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #endif break; + case 'G': + #ifdef WOLFSSH_TPM + tpmHostKeyPath = myoptarg; + #else + ES_ERROR("-G requires wolfSSH built with " + "WOLFSSH_TPM (--enable-tpm)\n"); + #endif + break; + case 'H': useCustomHighWaterCb = 1; break; @@ -2950,6 +3143,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) { const char* bufName = NULL; + int loadDefaultHostKeys = 1; #ifndef WOLFSSH_SMALL_STACK byte buf[EXAMPLE_KEYLOAD_BUFFER_SZ]; #endif @@ -2967,40 +3161,51 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #endif bufSz = EXAMPLE_KEYLOAD_BUFFER_SZ; - bufSz = load_key(peerEcc, keyLoadBuf, bufSz); - if (bufSz == 0) { - ES_ERROR("Couldn't load first key file.\n"); - } - if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, keyLoadBuf, bufSz, - WOLFSSH_FORMAT_ASN1) < 0) { - ES_ERROR("Couldn't use first key buffer.\n"); + #ifdef WOLFSSH_TPM + if (tpmHostKeyPath != NULL) { + if (EchoserverInitTpmHostKey(ctx, tpmHostKeyPath) != 0) { + ES_ERROR("Couldn't load TPM host key from %s.\n", tpmHostKeyPath); + } + loadDefaultHostKeys = 0; } + #endif + + if (loadDefaultHostKeys) { + bufSz = load_key(peerEcc, keyLoadBuf, bufSz); + if (bufSz == 0) { + ES_ERROR("Couldn't load first key file.\n"); + } + if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, keyLoadBuf, bufSz, + WOLFSSH_FORMAT_ASN1) < 0) { + ES_ERROR("Couldn't use first key buffer.\n"); + } #if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECC) - peerEcc = !peerEcc; - bufSz = EXAMPLE_KEYLOAD_BUFFER_SZ; + peerEcc = !peerEcc; + bufSz = EXAMPLE_KEYLOAD_BUFFER_SZ; - bufSz = load_key(peerEcc, keyLoadBuf, bufSz); - if (bufSz == 0) { - ES_ERROR("Couldn't load second key file.\n"); - } - if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, keyLoadBuf, bufSz, - WOLFSSH_FORMAT_ASN1) < 0) { - ES_ERROR("Couldn't use second key buffer.\n"); - } + bufSz = load_key(peerEcc, keyLoadBuf, bufSz); + if (bufSz == 0) { + ES_ERROR("Couldn't load second key file.\n"); + } + if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, keyLoadBuf, bufSz, + WOLFSSH_FORMAT_ASN1) < 0) { + ES_ERROR("Couldn't use second key buffer.\n"); + } #endif #ifndef WOLFSSH_NO_ED25519 - bufSz = EXAMPLE_KEYLOAD_BUFFER_SZ; - bufSz = load_key_ed25519(keyLoadBuf, bufSz); - if (bufSz == 0) { - ES_ERROR("Couldn't load Ed25519 key file.\n"); - } - if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, keyLoadBuf, bufSz, - WOLFSSH_FORMAT_ASN1) < 0) { - ES_ERROR("Couldn't use Ed25519 key buffer.\n"); - } + bufSz = EXAMPLE_KEYLOAD_BUFFER_SZ; + bufSz = load_key_ed25519(keyLoadBuf, bufSz); + if (bufSz == 0) { + ES_ERROR("Couldn't load Ed25519 key file.\n"); + } + if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, keyLoadBuf, bufSz, + WOLFSSH_FORMAT_ASN1) < 0) { + ES_ERROR("Couldn't use Ed25519 key buffer.\n"); + } #endif /* WOLFSSH_NO_ED25519 */ + } #ifndef NO_FILESYSTEM if (userPubKey) { @@ -3269,6 +3474,9 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) wc_FreeMutex(&doneLock); PwMapListDelete(&pwMapList); wolfSSH_CTX_free(ctx); +#ifdef WOLFSSH_TPM + EchoserverCleanupTpmHostKey(); +#endif #ifdef WOLFSSH_STATIC_MEMORY wolfSSH_MemoryPrintStats(heap); #endif diff --git a/src/internal.c b/src/internal.c index 9d314dff1..499eca759 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2247,23 +2247,50 @@ static int UpdateHostCertificates(WOLFSSH_CTX* ctx, if (HINTISSET(keyHint) && HINTISSET(certHint)) { byte* key = NULL; word32 keySz; +#ifdef WOLFSSH_TPM + int keyIsTpm = ctx->privateKey[keyHint].isTpm; +#endif - keySz = ctx->privateKey[keyHint].keySz; - key = (byte*)WMALLOC(keySz, ctx->heap, DYNTYPE_PRIVKEY); - if (key == NULL) { - ret = WS_MEMORY_E; - } - else { - WMEMCPY(key, ctx->privateKey[keyHint].key, keySz); - +#ifdef WOLFSSH_TPM + /* A TPM-backed key has no software bytes to copy; clear any stale + * software key on the certificate slot and mark it TPM-backed. */ + if (keyIsTpm) { if (ctx->privateKey[certHint].key != NULL) { ForceZero(ctx->privateKey[certHint].key, ctx->privateKey[certHint].keySz); WFREE(ctx->privateKey[certHint].key, ctx->heap, DYNTYPE_PRIVKEY); + ctx->privateKey[certHint].key = NULL; + ctx->privateKey[certHint].keySz = 0; + } + ctx->privateKey[certHint].isTpm = 1; + } +#endif + if (ret == WS_SUCCESS +#ifdef WOLFSSH_TPM + && !keyIsTpm +#endif + ) { + keySz = ctx->privateKey[keyHint].keySz; + key = (byte*)WMALLOC(keySz, ctx->heap, DYNTYPE_PRIVKEY); + if (key == NULL) { + ret = WS_MEMORY_E; + } + else { + WMEMCPY(key, ctx->privateKey[keyHint].key, keySz); + + if (ctx->privateKey[certHint].key != NULL) { + ForceZero(ctx->privateKey[certHint].key, + ctx->privateKey[certHint].keySz); + WFREE(ctx->privateKey[certHint].key, + ctx->heap, DYNTYPE_PRIVKEY); + } + ctx->privateKey[certHint].key = key; + ctx->privateKey[certHint].keySz = keySz; + #ifdef WOLFSSH_TPM + ctx->privateKey[certHint].isTpm = 0; + #endif } - ctx->privateKey[certHint].key = key; - ctx->privateKey[certHint].keySz = keySz; } } @@ -2367,6 +2394,9 @@ static int SetHostPrivateKey(WOLFSSH_CTX* ctx, pvtKey->key = der; pvtKey->keySz = derSz; + #ifdef WOLFSSH_TPM + pvtKey->isTpm = 0; + #endif #ifdef WOLFSSH_CERTS if (ret == WS_SUCCESS) { @@ -2384,6 +2414,71 @@ static int SetHostPrivateKey(WOLFSSH_CTX* ctx, } +#ifdef WOLFSSH_TPM +/* Registers a host key whose private material lives in the TPM. The slot + * carries only the key type so KEX can advertise it; signing and K_S come + * from ctx->tpmKey. */ +int wolfSSH_SetHostTpmKey(WOLFSSH_CTX* ctx, byte keyId) +{ + word32 destIdx = 0; + int ret = WS_SUCCESS; +#ifdef WOLFSSH_CERTS + word32 certIdx; + byte certId; +#endif + + while (destIdx < ctx->privateKeyCount + && ctx->privateKey[destIdx].publicKeyFmt != keyId) { + destIdx++; + } + + if (destIdx >= WOLFSSH_MAX_PVT_KEYS) { + ret = WS_CTX_KEY_COUNT_E; + } + else { + WOLFSSH_PVT_KEY* pvtKey = ctx->privateKey + destIdx; + + if (pvtKey->publicKeyFmt != keyId) { + ctx->privateKeyCount++; + pvtKey->publicKeyFmt = keyId; + } + else if (pvtKey->key != NULL) { + ForceZero(pvtKey->key, pvtKey->keySz); + WFREE(pvtKey->key, ctx->heap, DYNTYPE_PRIVKEY); + } + + pvtKey->key = NULL; + pvtKey->keySz = 0; + pvtKey->isTpm = 1; + + #ifdef WOLFSSH_CERTS + /* Mark the matching certificate slot TPM-backed so certificate KEX + * also signs through the TPM instead of a stale software key. */ + certId = CertTypeForId(keyId); + for (certIdx = 0; certIdx < ctx->privateKeyCount; certIdx++) { + if (ctx->privateKey[certIdx].publicKeyFmt == certId) { + if (ctx->privateKey[certIdx].key != NULL) { + ForceZero(ctx->privateKey[certIdx].key, + ctx->privateKey[certIdx].keySz); + WFREE(ctx->privateKey[certIdx].key, ctx->heap, + DYNTYPE_PRIVKEY); + ctx->privateKey[certIdx].key = NULL; + ctx->privateKey[certIdx].keySz = 0; + } + ctx->privateKey[certIdx].isTpm = 1; + break; + } + } + #endif /* WOLFSSH_CERTS */ + + RefreshPublicKeyAlgo(ctx); + } + + return ret; +} +#endif /* WOLFSSH_TPM */ + + int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx, const byte* in, word32 inSz, int format, int type) @@ -11657,6 +11752,10 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, heap = ssh->ctx->heap; +#ifdef WOLFSSH_TPM + ssh->handshake->useTpm = ssh->ctx->privateKey[keyIdx].isTpm; +#endif + switch (sigKeyBlock_ptr->pubKeyId) { #ifndef WOLFSSH_NO_RSA #ifdef WOLFSSH_CERTS @@ -11673,6 +11772,16 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, sigKeyBlock_ptr->sk.rsa.nSz = (word32)sizeof(sigKeyBlock_ptr->sk.rsa.n); ret = wc_InitRsaKey(&sigKeyBlock_ptr->sk.rsa.key, heap); + #ifdef WOLFSSH_TPM + if (ret == 0 && ssh->ctx->privateKey[keyIdx].isTpm) { + /* No private key in RAM; take the public key from the TPM. */ + ret = wolfTPM2_RsaKey_TpmToWolf(ssh->ctx->tpmDev, + ssh->ctx->tpmKey, &sigKeyBlock_ptr->sk.rsa.key); + if (ret != 0) + ret = WS_RSA_E; + } + else + #endif /* WOLFSSH_TPM */ if (ret == 0) ret = wc_RsaPrivateKeyDecode(ssh->ctx->privateKey[keyIdx].key, &scratch, &sigKeyBlock_ptr->sk.rsa.key, @@ -11782,6 +11891,16 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, ret = wc_ecc_init_ex(&sigKeyBlock_ptr->sk.ecc.key, heap, INVALID_DEVID); scratch = 0; + #ifdef WOLFSSH_TPM + if (ret == 0 && ssh->ctx->privateKey[keyIdx].isTpm) { + /* No private key in RAM; take the public point from the TPM. */ + ret = wolfTPM2_EccKey_TpmToWolf(ssh->ctx->tpmDev, + ssh->ctx->tpmKey, &sigKeyBlock_ptr->sk.ecc.key); + if (ret != 0) + ret = WS_ECC_E; + } + else + #endif /* WOLFSSH_TPM */ if (ret == 0) ret = wc_EccPrivateKeyDecode(ssh->ctx->privateKey[keyIdx].key, &scratch, &sigKeyBlock_ptr->sk.ecc.key, @@ -12708,10 +12827,15 @@ static int SignHRsa(WOLFSSH* ssh, byte* sig, word32* sigSz, WLOG(WS_LOG_INFO, "Signing hash with %s.", IdToName(ssh->handshake->pubKeyId)); #ifdef WOLFSSH_TPM - if (ssh->ctx->tpmDev && ssh->ctx->tpmKey) { + if (ssh->handshake->useTpm) { + /* Pass the raw digest; the TPM builds the PKCS#1 DigestInfo. */ ret = wolfTPM2_SignHashScheme(ssh->ctx->tpmDev, - ssh->ctx->tpmKey, encSig, encSigSz, sig, (int*)sigSz, + ssh->ctx->tpmKey, digest, (int)digestSz, sig, (int*)sigSz, TPM_ALG_RSASSA, TPM2_GetTpmHashType(hashId)); + if (ret == 0) + ret = (int)*sigSz; + else + ret = WS_RSA_E; } else #endif /* WOLFSSH_TPM */ @@ -12728,7 +12852,11 @@ static int SignHRsa(WOLFSSH* ssh, byte* sig, word32* sigSz, } } - if (ret == WS_SUCCESS) { + if (ret == WS_SUCCESS + #ifdef WOLFSSH_TPM + && !ssh->handshake->useTpm + #endif + ) { ret = wolfSSH_RsaVerify(sig, *sigSz, encSig, encSigSz, &sigKey->sk.rsa.key, heap, "SignHRsa"); } @@ -12770,9 +12898,21 @@ static int SignHEcdsa(WOLFSSH* ssh, byte* sig, word32* sigSz, byte r_s[MAX_ECC_BYTES + ECC_MAX_PAD_SZ]; byte s_s[MAX_ECC_BYTES + ECC_MAX_PAD_SZ]; #endif +#ifdef WOLFSSH_TPM + byte rawSig[2 * MAX_ECC_BYTES]; + word32 rawSigSz = (word32)sizeof(rawSig); + word32 curveSz = 0; + word32 rOff = 0, sOff = 0; + byte useTpm = 0; +#endif WLOG(WS_LOG_DEBUG, "Entering SignHEcdsa()"); +#ifdef WOLFSSH_TPM + useTpm = (ssh->handshake->useTpm && ssh->ctx->tpmDev != NULL + && ssh->ctx->tpmKey != NULL) ? 1 : 0; +#endif + hashId = HashForId(ssh->handshake->pubKeyId); digestSz = wc_HashGetDigestSize(hashId); @@ -12784,9 +12924,16 @@ static int SignHEcdsa(WOLFSSH* ssh, byte* sig, word32* sigSz, if (ret == WS_SUCCESS) { WLOG(WS_LOG_INFO, "Signing hash with %s.", IdToName(ssh->handshake->pubKeyId)); + #ifdef WOLFSSH_TPM + if (useTpm) + ret = wolfTPM2_SignHashScheme(ssh->ctx->tpmDev, ssh->ctx->tpmKey, + digest, (int)digestSz, rawSig, (int*)&rawSigSz, + TPM_ALG_ECDSA, TPM2_GetTpmHashType(hashId)); + else + #endif /* WOLFSSH_TPM */ ret = wc_ecc_sign_hash(digest, digestSz, sig, sigSz, ssh->rng, &sigKey->sk.ecc.key); - if (ret != MP_OKAY) { + if (ret != 0) { WLOG(WS_LOG_DEBUG, "SignHEcdsa: Bad ECDSA Sign"); ret = WS_ECC_E; } @@ -12810,7 +12957,30 @@ static int SignHEcdsa(WOLFSSH* ssh, byte* sig, word32* sigSz, } if (ret == WS_SUCCESS) { + #ifdef WOLFSSH_TPM + if (useTpm) { + /* TPM returns raw R||S, each half left-padded to the curve size. */ + curveSz = (word32)TPM2_GetCurveSize( + ssh->ctx->tpmKey->pub.publicArea.parameters.eccDetail.curveID); + if (curveSz == 0 || (curveSz * 2) > rawSigSz) { + ret = WS_ECC_E; + } + else { + /* Strip leading zeros so R and S are minimal mpints. */ + while (rOff < curveSz - 1 && rawSig[rOff] == 0) + rOff++; + while (sOff < curveSz - 1 && rawSig[curveSz + sOff] == 0) + sOff++; + rSz = curveSz - rOff; + sSz = curveSz - sOff; + WMEMCPY(r, rawSig + rOff, rSz); + WMEMCPY(s, rawSig + curveSz + sOff, sSz); + } + } + else + #endif /* WOLFSSH_TPM */ ret = wc_ecc_sig_to_rs(sig, *sigSz, r, &rSz, s, &sSz); + if (ret != 0) { ret = WS_ECC_E; } diff --git a/src/ssh.c b/src/ssh.c index d3d3e0d1e..45761d808 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -478,6 +478,75 @@ void* wolfSSH_GetTpmKey(WOLFSSH* ssh) } return NULL; } + + +int wolfSSH_CTX_UseTpmHostKey(WOLFSSH_CTX* ctx, + WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key) +{ + int ret = WS_SUCCESS; + byte keyId = ID_NONE; + + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UseTpmHostKey()"); + + if (ctx == NULL || dev == NULL || key == NULL) { + ret = WS_BAD_ARGUMENT; + } + + /* Only one TPM host key is supported per context (single ctx->tpmKey). */ + if (ret == WS_SUCCESS && ctx->tpmKey != NULL && ctx->tpmKey != key) { + WLOG(WS_LOG_DEBUG, + "wolfSSH_CTX_UseTpmHostKey: a TPM host key is already set"); + ret = WS_BAD_ARGUMENT; + } + + if (ret == WS_SUCCESS) { + if (key->pub.publicArea.type == TPM_ALG_ECC) { + switch (key->pub.publicArea.parameters.eccDetail.curveID) { + #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 + case TPM_ECC_NIST_P256: + keyId = ID_ECDSA_SHA2_NISTP256; + break; + #endif + #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384 + case TPM_ECC_NIST_P384: + keyId = ID_ECDSA_SHA2_NISTP384; + break; + #endif + #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521 + case TPM_ECC_NIST_P521: + keyId = ID_ECDSA_SHA2_NISTP521; + break; + #endif + default: + ret = WS_INVALID_PRIME_CURVE; + } + } + else if (key->pub.publicArea.type == TPM_ALG_RSA) { + #if !defined(WOLFSSH_NO_RSA) && \ + (!defined(WOLFSSH_NO_RSA_SHA2_256) || \ + !defined(WOLFSSH_NO_RSA_SHA2_512) || \ + (defined(WOLFSSH_NO_SHA1_SOFT_DISABLE) && \ + !defined(WOLFSSH_NO_SSH_RSA_SHA1))) + keyId = ID_SSH_RSA; + #else + ret = WS_INVALID_ALGO_ID; + #endif + } + else { + ret = WS_INVALID_ALGO_ID; + } + } + + if (ret == WS_SUCCESS) { + ctx->tpmDev = dev; + ctx->tpmKey = key; + ret = wolfSSH_SetHostTpmKey(ctx, keyId); + } + + WLOG(WS_LOG_DEBUG, + "Leaving wolfSSH_CTX_UseTpmHostKey(), ret = %d", ret); + return ret; +} #endif /* WOLFSSH_TPM */ diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 4af8b7357..054cb9443 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -559,6 +559,11 @@ typedef struct WOLFSSH_PVT_KEY { byte publicKeyFmt; /* Public key format for the private key. Note, some public key * formats are used with multiple public key signing algorithms. */ +#ifdef WOLFSSH_TPM + byte isTpm; + /* When set, the host key material lives in the TPM and key/keySz are + * unused; signing and the public K_S come from ctx->tpmKey. */ +#endif } WOLFSSH_PVT_KEY; @@ -689,6 +694,9 @@ typedef struct HandshakeInfo { byte useEccMlKem:1; byte useCurve25519:1; byte useCurve25519MlKem:1; +#ifdef WOLFSSH_TPM + byte useTpm:1; +#endif union { #ifndef WOLFSSH_NO_DH @@ -1038,6 +1046,9 @@ WOLFSSH_LOCAL int ChannelPutData(WOLFSSH_CHANNEL* channel, byte* data, WOLFSSH_LOCAL int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx, const byte* in, word32 inSz, int format, int type); +#ifdef WOLFSSH_TPM +WOLFSSH_LOCAL int wolfSSH_SetHostTpmKey(WOLFSSH_CTX* ctx, byte keyId); +#endif WOLFSSH_LOCAL int wolfSSH_FwdWorker(WOLFSSH* ssh); diff --git a/wolfssh/ssh.h b/wolfssh/ssh.h index 65bb90f8a..3b3f2279b 100644 --- a/wolfssh/ssh.h +++ b/wolfssh/ssh.h @@ -293,6 +293,8 @@ WOLFSSH_API void wolfSSH_SetTpmDev(WOLFSSH* ssh, WOLFTPM2_DEV* dev); WOLFSSH_API void wolfSSH_SetTpmKey(WOLFSSH* ssh, WOLFTPM2_KEY* key); WOLFSSH_API void* wolfSSH_GetTpmDev(WOLFSSH* ssh); WOLFSSH_API void* wolfSSH_GetTpmKey(WOLFSSH* ssh); +WOLFSSH_API int wolfSSH_CTX_UseTpmHostKey(WOLFSSH_CTX* ctx, + WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key); #endif /* WOLFSSH_TPM */ /* I/O callbacks */ From d8bea41fec24fd556f8ffcde5dae087971dc2218 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Tue, 16 Jun 2026 12:39:09 -0700 Subject: [PATCH 2/2] Use shared wolfTPM2_SetKeyBlobFromBuffer parser and guard TPM signing args --- examples/echoserver/echoserver.c | 128 +++++++++---------------------- src/internal.c | 3 +- 2 files changed, 39 insertions(+), 92 deletions(-) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 6a6e3a403..dd6adb301 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -2237,113 +2237,51 @@ static int LoadPubKeyList(StrList* strList, int format, PwMapList* mapList) #endif #ifdef WOLFSSH_TPM -/* Default key auth produced by 'keygen ... -ecc -t -eh' (override with -auth). */ -#define ECHOSERVER_TPM_KEY_AUTH "ThisIsMyKeyAuth" +/* Default key auth produced by 'keygen ... -t -eh'; pass a different value to + * EchoserverInitTpmHostKey() to override. */ +#define ECHOSERVER_TPM_KEY_AUTH_DEFAULT "ThisIsMyKeyAuth" static WOLFTPM2_DEV tpmHostDev; static WOLFTPM2_KEY tpmHostKey; static int tpmHostKeyValid = 0; -static int EchoserverReadKeyBlob(const char* filename, WOLFTPM2_KEYBLOB* key) -{ - int rc = 0; -#if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) - WFILE* fp = NULL; - size_t fileSz = 0; - size_t bytesRead = 0; - byte pubAreaBuffer[sizeof(TPM2B_PUBLIC)]; - int pubAreaSize; - - WMEMSET(key, 0, sizeof(WOLFTPM2_KEYBLOB)); - - if (WFOPEN(NULL, &fp, filename, "rb") != 0 || fp == WBADFILE) { - fprintf(stderr, "Failed to open TPM key blob %s\n", filename); - return BUFFER_E; - } - - WFSEEK(NULL, fp, 0, WSEEK_END); - fileSz = WFTELL(NULL, fp); - WREWIND(NULL, fp); - - if (fileSz > sizeof(key->priv) + sizeof(key->pub)) { - rc = BUFFER_E; - } - - if (rc == 0) { - bytesRead = WFREAD(NULL, &key->pub.size, 1, sizeof(key->pub.size), fp); - if (bytesRead != sizeof(key->pub.size)) - rc = BUFFER_E; - else - fileSz -= bytesRead; - } - - if (rc == 0 && - (sizeof(UINT16) + (size_t)key->pub.size) > sizeof(pubAreaBuffer)) { - rc = BUFFER_E; - } - - if (rc == 0) { - bytesRead = WFREAD(NULL, pubAreaBuffer, 1, - sizeof(UINT16) + key->pub.size, fp); - if (bytesRead != sizeof(UINT16) + key->pub.size) - rc = BUFFER_E; - else - fileSz -= bytesRead; - } - - if (rc == 0) { - /* Bound the parse to the bytes actually read so a malformed size - * field cannot consume the uninitialized tail of the buffer. */ - rc = TPM2_ParsePublic(&key->pub, pubAreaBuffer, - (word32)(sizeof(UINT16) + key->pub.size), &pubAreaSize); - } - - if (rc == 0 && - pubAreaSize != (int)(sizeof(UINT16) + key->pub.size)) { - rc = BUFFER_E; - } - - if (rc == 0 && fileSz > sizeof(key->priv)) { - rc = BUFFER_E; - } - - if (rc == 0 && fileSz > 0) { - bytesRead = WFREAD(NULL, &key->priv, 1, fileSz, fp); - if (bytesRead != fileSz) - rc = BUFFER_E; - } - - if (rc == 0 && key->priv.size > sizeof(key->priv.buffer)) { - rc = BUFFER_E; - } - - WFCLOSE(NULL, fp); -#else - (void)filename; - (void)key; - rc = WS_NOT_COMPILED; -#endif - return rc; -} - -/* Loads an ECC host key blob into the TPM and registers it as the server host - * key so the private key never enters RAM. */ -static int EchoserverInitTpmHostKey(WOLFSSH_CTX* ctx, const char* keyFile) +/* Loads a TPM host key blob (ECC or RSA) into the TPM and registers it as the + * server host key so the private key never enters RAM. */ +static int EchoserverInitTpmHostKey(WOLFSSH_CTX* ctx, const char* keyFile, + const char* keyAuth) { int rc; TPMI_ALG_PUBLIC alg = TPM_ALG_ECC; WOLFTPM2_KEY endorse; WOLFTPM2_KEYBLOB keyBlob; WOLFTPM2_SESSION tpmSession; +#ifndef NO_FILESYSTEM + byte fileBuf[sizeof(WOLFTPM2_KEYBLOB)]; + word32 fileSz = (word32)sizeof(fileBuf); + int readSz = 0; +#endif WMEMSET(&endorse, 0, sizeof(endorse)); WMEMSET(&tpmSession, 0, sizeof(tpmSession)); + WMEMSET(&keyBlob, 0, sizeof(keyBlob)); WMEMSET(&tpmHostKey, 0, sizeof(tpmHostKey)); rc = wolfTPM2_Init(&tpmHostDev, TPM2_IoCb, NULL); + /* Read the key blob and parse it with the shared wolfTPM helper. */ +#ifndef NO_FILESYSTEM if (rc == 0) { - rc = EchoserverReadKeyBlob(keyFile, &keyBlob); + readSz = load_file(keyFile, fileBuf, &fileSz); + if (readSz <= 0) + rc = WS_BAD_FILE_E; } + if (rc == 0) { + rc = wolfTPM2_SetKeyBlobFromBuffer(&keyBlob, fileBuf, (word32)readSz); + } +#else + (void)keyFile; + if (rc == 0) + rc = WS_NOT_COMPILED; +#endif /* Match the endorsement key type to the host key (RSA or ECC). */ if (rc == 0) { @@ -2360,9 +2298,13 @@ static int EchoserverInitTpmHostKey(WOLFSSH_CTX* ctx, const char* keyFile) rc = wolfTPM2_SetAuthSession(&tpmHostDev, 0, &tpmSession, 0); } + if (rc == 0 && XSTRLEN(keyAuth) > sizeof(keyBlob.handle.auth.buffer)) { + rc = WS_BAD_ARGUMENT; + } + if (rc == 0) { - keyBlob.handle.auth.size = (word32)XSTRLEN(ECHOSERVER_TPM_KEY_AUTH); - XMEMCPY(keyBlob.handle.auth.buffer, ECHOSERVER_TPM_KEY_AUTH, + keyBlob.handle.auth.size = (word32)XSTRLEN(keyAuth); + XMEMCPY(keyBlob.handle.auth.buffer, keyAuth, keyBlob.handle.auth.size); rc = wolfTPM2_LoadKey(&tpmHostDev, &keyBlob, &endorse.handle); } @@ -2390,6 +2332,9 @@ static int EchoserverInitTpmHostKey(WOLFSSH_CTX* ctx, const char* keyFile) /* keyBlob holds the private blob and key auth; the session may hold auth. */ wc_ForceZero(&keyBlob, sizeof(keyBlob)); wc_ForceZero(&tpmSession, sizeof(tpmSession)); +#ifndef NO_FILESYSTEM + wc_ForceZero(fileBuf, sizeof(fileBuf)); +#endif return rc; } @@ -3163,7 +3108,8 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #ifdef WOLFSSH_TPM if (tpmHostKeyPath != NULL) { - if (EchoserverInitTpmHostKey(ctx, tpmHostKeyPath) != 0) { + if (EchoserverInitTpmHostKey(ctx, tpmHostKeyPath, + ECHOSERVER_TPM_KEY_AUTH_DEFAULT) != 0) { ES_ERROR("Couldn't load TPM host key from %s.\n", tpmHostKeyPath); } loadDefaultHostKeys = 0; diff --git a/src/internal.c b/src/internal.c index 499eca759..ece6185ae 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12827,7 +12827,8 @@ static int SignHRsa(WOLFSSH* ssh, byte* sig, word32* sigSz, WLOG(WS_LOG_INFO, "Signing hash with %s.", IdToName(ssh->handshake->pubKeyId)); #ifdef WOLFSSH_TPM - if (ssh->handshake->useTpm) { + if (ssh->handshake->useTpm && ssh->ctx->tpmDev != NULL + && ssh->ctx->tpmKey != NULL) { /* Pass the raw digest; the TPM builds the PKCS#1 DigestInfo. */ ret = wolfTPM2_SignHashScheme(ssh->ctx->tpmDev, ssh->ctx->tpmKey, digest, (int)digestSz, sig, (int*)sigSz,