From 8f4e5448bde15dcf2dd1800a4944d29b7a29b91f Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 7 Nov 2024 16:37:46 -0800 Subject: [PATCH] Support for TPM based public key authentication using wolfTPM (WIP). --- README.md | 59 +++++++- configure.ac | 16 +++ examples/client/client.c | 76 +++++++++-- examples/client/common.c | 287 ++++++++++++++++++++++++++++++++++++++- examples/client/common.h | 3 + src/internal.c | 92 ++++++++++--- src/ssh.c | 90 ++++++++++-- wolfssh/internal.h | 7 + wolfssh/ssh.h | 25 ++++ 9 files changed, 603 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 357643931..35284d708 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,13 @@ Additional build options for wolfSSL are located in [chapter two](https://www.wolfssl.com/docs/wolfssl-manual/ch2/). of the wolfSSH manual. + building -------- From the wolfSSH source directory run: - $ ./autogen.sh + $ ./autogen.sh (if cloned from GitHub) $ ./configure --with-wolfssl=[/usr/local] $ make $ make check @@ -528,6 +529,62 @@ fred-cert.der would be: $ ./examples/client/client -u fred -J ./keys/fred-cert.der -i ./keys/fred-key.der +TPM +=== + +wolfSSH now supports TPM support with client key authentication. + +When using TPM for client side public key authentication wolfSSH has dependencies +on wolfCrypt and wolfTPM. Youll also need to have a tpm simulator +[wolfTPM](https://www.wolfssl.com/products/wolftpm/) +[wolfSSL](https://www.wolfssl.com/products/wolfssl/) +You'll need to build and configure wolfTPM, wolfSSL, and wolfSSH like so: + + $ cd + $ ./autogen.sh (if cloned from GitHub) + $ + $ make + $ make check + + + wolfSSL + $ ./configure --enable-wolftpm --enable-wolfssh + wolfTPM + $ ./configure --enable-swtpm + wolfSSH + $ ./configure --enable-tpm + +For testing TPM with private rsa key you'll need to run the server from a TPM +simulator like `ibmswtpm2`. This can be done as followed: + + $ cd src + $ ./tpm_server + +Before starting the echoserver you need to run the keygen for keyblob in wolfTPM +using: + + $ ./examples/keygen/keygen keyblob.bin -rsa -t -pem + +Take key.pem and convert the TPM public key to the ssh-rsa BASE64 username format: +`ssh-keygen -f key.pem -i -m PKCS8`. Update echoserver.c user "jill"'s public key. + +The directory `examples` contains an echoserver that any client should +be able to connect to. From wolfSSH open two terminal instances and run the +server: + + $ ./examples/echoserver/echoserver + +From another terminal run the client with the keyblob: + + $ ./examples/client/client -i ../wolfTPM/keyblob.bin -u jill + +For debuging run server like above then: + + $ ./examples/client/client + +Set break point or just run: + + $ r -i ../wolfTPM/keyblob.bin -u jill WOLFSSH APPLICATIONS ==================== diff --git a/configure.ac b/configure.ac index 206157756..b731ad0ab 100644 --- a/configure.ac +++ b/configure.ac @@ -171,6 +171,16 @@ AC_ARG_ENABLE([certs], [AS_HELP_STRING([--enable-certs],[Enable X.509 cert support (default: disabled)])], [ENABLED_CERTS=$enableval],[ENABLED_CERTS=no]) +# TPM 2.0 Support +AC_ARG_ENABLE([tpm], + [AS_HELP_STRING([--enable-tpm],[Enable TPM 2.0 support (default: disabled)])], + [ENABLED_TPM=$enableval],[ENABLED_TPM=no]) + +if test "$ENABLED_TPM" != "no" +then + AC_CHECK_LIB([wolftpm],[wolfTPM2_Init],,[AC_MSG_ERROR([libwolftpm is required for ${PACKAGE}. It can be obtained from https://www.wolfssl.com/download.html/ .])]) +fi + # smallstack AC_ARG_ENABLE([smallstack], [AS_HELP_STRING([--enable-smallstack],[Enable small stack (default: disabled)])], @@ -225,6 +235,10 @@ AS_IF([test "x$ENABLED_SSHD" = "xyes"], [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SSHD"]) AS_IF([test "x$ENABLED_SSHCLIENT" = "xyes"], [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SSHCLIENT"]) +AS_IF([test "x$ENABLED_TPM" = "xyes"], + [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_TPM"]) +AS_IF([test "x$ENABLED_SMALLSTACK" = "xyes"], + [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SMALL_STACK"]) if test "$ENABLED_SSHD" = "yes"; then if test -n "$PAM_LIB" @@ -279,6 +293,7 @@ AM_CONDITIONAL([BUILD_AGENT],[test "x$ENABLED_AGENT" = "xyes"]) AM_CONDITIONAL([BUILD_SSHD],[test "x$ENABLED_SSHD" = "xyes"]) AM_CONDITIONAL([BUILD_SSHCLIENT],[test "x$ENABLED_SSHCLIENT" = "xyes"]) AM_CONDITIONAL([BUILD_CERTS],[test "x$ENABLED_CERTS" = "xyes"]) +AM_CONDITIONAL([BUILD_TPM],[test "x$ENABLED_TPM" = "xyes"]) AX_HARDEN_CC_COMPILER_FLAGS @@ -322,6 +337,7 @@ AS_ECHO([" * sftp: $ENABLED_SFTP"]) AS_ECHO([" * sshd: $ENABLED_SSHD"]) AS_ECHO([" * ssh client: $ENABLED_SSHCLIENT"]) AS_ECHO([" * agent: $ENABLED_AGENT"]) +AS_ECHO([" * TPM 2.0 support: $ENABLED_TPM"]) AS_ECHO([" * TCP/IP Forwarding: $ENABLED_FWD"]) AS_ECHO([" * X.509 Certs: $ENABLED_CERTS"]) AS_ECHO([" * Examples: $ENABLED_EXAMPLES"]) diff --git a/examples/client/client.c b/examples/client/client.c index c6cde31bc..3dd3b634e 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -74,6 +74,10 @@ #include #endif +#ifdef WOLFSSH_TPM + #include + #include +#endif /* WOLFSSH_TPM */ #ifndef NO_WOLFSSH_CLIENT @@ -125,7 +129,9 @@ static void ShowUsage(void) static const char* pubKeyName = NULL; -static const char* certName = NULL; +#ifdef WOLFSSH_CERTS + static const char* certName = NULL; +#endif static const char* caCert = NULL; @@ -549,7 +555,7 @@ static int wolfSSH_AGENT_DefaultActions(WS_AgentCbAction action, void* vCtx) ret = WS_AGENT_NOT_AVAILABLE; if (ret == WS_AGENT_SUCCESS) { - memset(name, 0, sizeof(struct sockaddr_un)); + WMEMSET(name, 0, sizeof(struct sockaddr_un)); name->sun_family = AF_LOCAL; strncpy(name->sun_path, sockName, sizeof(name->sun_path)); name->sun_path[sizeof(name->sun_path) - 1] = '\0'; @@ -781,11 +787,17 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) if (keepOpen) err_sys("Threading needed for terminal session\n"); #endif - +#ifndef WOLFSSH_TPM + #ifdef WOLFSSH_CERTS if ((pubKeyName == NULL && certName == NULL) && privKeyName != NULL) { err_sys("If setting priv key, need pub key."); } - + #else + if (pubKeyName == NULL && privKeyName != NULL) { + err_sys("If setting priv key, need pub key."); + } + #endif +#endif ret = ClientSetPrivateKey(privKeyName, userEcc, NULL); if (ret != 0) { err_sys("Error setting private key"); @@ -840,6 +852,9 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) if (ssh == NULL) err_sys("Couldn't create wolfSSH session."); +#ifdef WOLFSSH_TPM + CLientSetTpm(ssh); +#endif #if defined(WOLFSSL_PTHREADS) && defined(WOLFSSL_TEST_GLOBAL_REQ) wolfSSH_SetGlobalReq(ctx, callbackGlobalReq); wolfSSH_SetGlobalReqCtx(ssh, &ssh); /* dummy ctx */ @@ -850,7 +865,7 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) #ifdef WOLFSSH_AGENT if (useAgent) { - memset(&agentCbCtx, 0, sizeof(agentCbCtx)); + WMEMSET(&agentCbCtx, 0, sizeof(agentCbCtx)); agentCbCtx.state = AGENT_STATE_INIT; wolfSSH_set_agent_cb_ctx(ssh, &agentCbCtx); } @@ -913,28 +928,44 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) tcp_socket(&sockFd, ((struct sockaddr_in *)&clientAddr)->sin_family); ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz); - if (ret != 0) + if (ret != 0) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Couldn't connect to server."); + } if (nonBlock) tcp_set_nonblocking(&sockFd); ret = wolfSSH_set_fd(ssh, (int)sockFd); - if (ret != WS_SUCCESS) + if (ret != WS_SUCCESS) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Couldn't set the session's socket."); + } if (cmd != NULL) { ret = wolfSSH_SetChannelType(ssh, WOLFSSH_SESSION_EXEC, (byte*)cmd, (word32)WSTRLEN((char*)cmd)); - if (ret != WS_SUCCESS) + if (ret != WS_SUCCESS) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Couldn't set the channel type."); + } } #ifdef WOLFSSH_TERM if (keepOpen) { ret = wolfSSH_SetChannelType(ssh, WOLFSSH_SESSION_TERMINAL, NULL, 0); - if (ret != WS_SUCCESS) + if (ret != WS_SUCCESS) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Couldn't set the terminal channel type."); + } } #endif @@ -942,8 +973,12 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) ret = wolfSSH_connect(ssh); else ret = NonBlockSSH_connect(ssh); - if (ret != WS_SUCCESS) + if (ret != WS_SUCCESS) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Couldn't connect SSH stream."); + } #if !defined(SINGLE_THREADED) && !defined(WOLFSSL_NUCLEUS) && \ defined(WOLFSSH_TERM) && !defined(NO_FILESYSTEM) @@ -1040,16 +1075,23 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) #endif ret = wolfSSH_stream_send(ssh, (byte*)testString, (word32)strlen(testString)); - if (ret <= 0) + if (ret <= 0) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Couldn't send test string."); - + } do { ret = wolfSSH_stream_read(ssh, (byte*)rxBuf, sizeof(rxBuf) - 1); if (ret <= 0) { ret = wolfSSH_get_error(ssh); if (ret != WS_WANT_READ && ret != WS_WANT_WRITE && - ret != WS_CHAN_RXD) + ret != WS_CHAN_RXD) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Stream read failed."); + } } } while (ret == WS_WANT_READ || ret == WS_WANT_WRITE); @@ -1065,11 +1107,17 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) if (ret != WS_SOCKET_ERROR_E && wolfSSH_get_error(ssh) != WS_SOCKET_ERROR_E && wolfSSH_get_error(ssh) != WS_CHANNEL_CLOSED) { if (ret != WS_SUCCESS) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Sending the shutdown messages failed."); } ret = wolfSSH_worker(ssh, NULL); if (ret != WS_SUCCESS && ret != WS_SOCKET_ERROR_E && ret != WS_CHANNEL_CLOSED) { + ClientFreeBuffers(pubKeyName, privKeyName, NULL); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); err_sys("Failed to listen for close messages from the peer."); } } @@ -1079,6 +1127,7 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) ((func_args*)args)->return_code = wolfSSH_GetExitStatus(ssh); #endif + ClientFreeBuffers(pubKeyName, privKeyName, NULL); wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); if (ret != WS_SUCCESS && ret != WS_SOCKET_ERROR_E && @@ -1086,7 +1135,6 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) err_sys("Closing client stream failed"); } - ClientFreeBuffers(pubKeyName, privKeyName, NULL); #if !defined(WOLFSSH_NO_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) wc_ecc_fp_free(); /* free per thread cache */ #endif diff --git a/examples/client/common.c b/examples/client/common.c index db27d0f96..d480c0896 100644 --- a/examples/client/common.c +++ b/examples/client/common.c @@ -33,6 +33,12 @@ #include #include #include + +#ifdef WOLFSSH_TPM + #include + #include +#endif + #include "examples/client/common.h" #if !defined(USE_WINDOWS_API) && !defined(MICROCHIP_PIC32) && \ !defined(WOLFSSH_ZEPHYR) @@ -53,6 +59,7 @@ static byte pubKeyLoaded = 0; /* was a public key loaded */ static byte userPrivateKeyBuf[1191]; /* Size equal to hanselPrivateRsaSz. */ static byte* userPrivateKey = userPrivateKeyBuf; static word32 userPublicKeyTypeSz = 0; +static byte userPrivateKeyAlloc = 0; static word32 userPrivateKeySz = sizeof(userPrivateKeyBuf); static word32 userPrivateKeyTypeSz = 0; static byte isPrivate = 0; @@ -69,6 +76,10 @@ static const byte publicKeyType[] = "x509v3-ecdsa-sha2-nistp256"; #endif +#ifdef WOLFSSH_TPM + WOLFTPM2_DEV tpmDev; + WOLFTPM2_KEY tpmKey; +#endif /* WOLFSSH_TPM */ #ifndef WOLFSSH_NO_RSA @@ -677,6 +688,253 @@ int ClientUseCert(const char* certName, void* heap) return ret; } +#ifdef WOLFSSH_TPM + +#define TPM2_DEMO_STORAGE_KEY_HANDLE 0x81000200 /* Persistent Storage Key Handle (RSA) */ + +static const char gStorageKeyAuth[] = "ThisIsMyStorageKeyAuth"; + +static int getPrimaryStoragekey(WOLFTPM2_DEV* pDev, + WOLFTPM2_KEY* pStorageKey, + TPM_ALG_ID alg) +{ + int rc; + + WLOG(WS_LOG_DEBUG, "Entering getPrimaryStoragekey()"); + + /* See if SRK already exists */ + rc = wolfTPM2_ReadPublicKey(pDev, pStorageKey, TPM2_DEMO_STORAGE_KEY_HANDLE); + if (rc != 0) { + /* Create primary storage key */ + rc = wolfTPM2_CreateSRK(pDev, pStorageKey, alg, + (byte*)gStorageKeyAuth, sizeof(gStorageKeyAuth)-1); + #ifndef WOLFTPM_WINAPI + if (rc == TPM_RC_SUCCESS) { + /* Move storage key into persistent NV */ + rc = wolfTPM2_NVStoreKey(pDev, TPM_RH_OWNER, pStorageKey, + TPM2_DEMO_STORAGE_KEY_HANDLE); + } + #endif + } + else { + /* specify auth password for storage key */ + pStorageKey->handle.auth.size = sizeof(gStorageKeyAuth)-1; + XMEMCPY(pStorageKey->handle.auth.buffer, gStorageKeyAuth, + pStorageKey->handle.auth.size); + } + if (rc != 0) { + printf("Loading SRK: Storage failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + return rc; + } + printf("Loading SRK: Storage 0x%x (%d bytes)\n", + (word32)pStorageKey->handle.hndl, pStorageKey->pub.size); + WLOG(WS_LOG_DEBUG, "Leaving getPrimaryStoragekey(), rc = %d", rc); + return rc; +} + + +static int readKeyBlob(const char* filename, WOLFTPM2_KEYBLOB* key) +{ + int rc = 0; +#if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) + XFILE fp = NULL; + size_t fileSz = 0; + size_t bytes_read = 0; + byte pubAreaBuffer[sizeof(TPM2B_PUBLIC)]; + int pubAreaSize; + + WLOG(WS_LOG_DEBUG, "Entering readKeyBlob()"); + + XMEMSET(key, 0, sizeof(WOLFTPM2_KEYBLOB)); + + fp = XFOPEN(filename, "rb"); + if (fp != XBADFILE) { + XFSEEK(fp, 0, XSEEK_END); + fileSz = XFTELL(fp); + XREWIND(fp); + if (fileSz > sizeof(key->priv) + sizeof(key->pub)) { + printf("File size check failed\n"); + rc = BUFFER_E; goto exit; + } + printf("Reading %d bytes from %s\n", (int)fileSz, filename); + + bytes_read = XFREAD(&key->pub.size, 1, sizeof(key->pub.size), fp); + if (bytes_read != sizeof(key->pub.size)) { + printf("Read %zu, expected size marker of %zu bytes\n", + bytes_read, sizeof(key->pub.size)); + goto exit; + } + fileSz -= bytes_read; + + bytes_read = XFREAD(pubAreaBuffer, 1, sizeof(UINT16) + key->pub.size, fp); + if (bytes_read != sizeof(UINT16) + key->pub.size) { + printf("Read %zu, expected public blob %zu bytes\n", + bytes_read, sizeof(UINT16) + key->pub.size); + goto exit; + } + fileSz -= bytes_read; /* Reminder bytes for private key part */ + + /* Decode the byte stream into a publicArea structure ready for use */ + rc = TPM2_ParsePublic(&key->pub, pubAreaBuffer, + (word32)sizeof(pubAreaBuffer), &pubAreaSize); + if (rc != TPM_RC_SUCCESS) return rc; + + if (fileSz > 0) { + printf("Reading the private part of the key\n"); + bytes_read = XFREAD(&key->priv, 1, fileSz, fp); + if (bytes_read != fileSz) { + printf("Read %zu, expected private blob %zu bytes\n", + bytes_read, fileSz); + goto exit; + } + rc = 0; /* success */ + } + + /* sanity check the sizes */ + if (pubAreaSize != (key->pub.size + (int)sizeof(key->pub.size)) || + key->priv.size > sizeof(key->priv.buffer)) { + printf("Struct size check failed (pub %d, priv %d)\n", + key->pub.size, key->priv.size); + rc = BUFFER_E; + } + } + else { + rc = BUFFER_E; + printf("File %s not found!\n", filename); + printf("Keys can be generated by running:\n" + " ./examples/keygen/keygen rsa_test_blob.raw -rsa -t\n" + " ./examples/keygen/keygen ecc_test_blob.raw -ecc -t\n"); + } + +exit: + if (fp) + XFCLOSE(fp); +#else + (void)filename; + (void)key; +#endif /* !NO_FILESYSTEM && !NO_WRITE_TEMP_FILES */ + WLOG(WS_LOG_DEBUG, "Leaving readKeyBlob(), rc = %d", rc); + return rc; +} + +static int wolfSSH_TPM_InitKey(WOLFTPM2_DEV* dev, const char* name, + WOLFTPM2_KEY* pTpmKey) +{ + int rc; + WOLFTPM2_KEY storage; + WOLFTPM2_KEYBLOB tpmKeyBlob; + byte der[512]; + word32 derSz = (word32)sizeof(der); + void* heap = NULL; + + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_TPM_InitKey()"); + + rc = wolfTPM2_Init(dev, TPM2_IoCb, NULL); + if (rc != TPM_RC_SUCCESS) { +#ifdef DEBUG_WOLFSSH + printf("TPM 2.0 Device initialization failed\n"); +#endif + return WOLFSSH_TPM_FAILED_INIT; + } + + /* TPM 2.0 keys live under a Primary Key, acquire such key */ + rc = getPrimaryStoragekey(dev, &storage, TPM_ALG_RSA); + if (rc != TPM_RC_SUCCESS) { +#ifdef DEBUG_WOLFSSH + printf("Acquiring a Primary TPM 2.0 Key failed\n"); +#endif + return WOLFSSH_TPM_FAILED_LOAD_PRIMARY; + } + + /* Load the TPM 2.0 key blob from disk */ + rc = readKeyBlob(name, &tpmKeyBlob); + if (rc != TPM_RC_SUCCESS) { +#ifdef DEBUG_WOLFSSH + printf("Reading key blob from disk failed\n"); +#endif + return WOLFSSH_TPM_FAILED_READ_KEYBLOB; + } + + /* workaround until password can be supplied */ + /* consider a refactor to take a 32-bit handle and key auth password */ + static const char gKeyAuth[] = "ThisIsMyKeyAuth"; + /* set session for authorization key */ + tpmKeyBlob.handle.auth.size = (int)sizeof(gKeyAuth)-1; + XMEMCPY(tpmKeyBlob.handle.auth.buffer, gKeyAuth, tpmKeyBlob.handle.auth.size); + + /* Load the public key into the TPM device */ + rc = wolfTPM2_LoadKey(dev, &tpmKeyBlob, &storage.handle); + if (rc != TPM_RC_SUCCESS) { +#ifdef DEBUG_WOLFSSH + printf("wolfTPM2_LoadKey failed\n"); +#endif + return WOLFSSH_TPM_FAILED_LOAD_KEY; + } +#ifdef DEBUG_WOLFSSH + printf("Loaded key to 0x%x\n", (word32)tpmKeyBlob.handle.hndl); +#endif + + /* Read the public key and extract the public key as a DER/ASN.1 */ + rc = wolfTPM2_ExportPublicKeyBuffer(dev, (WOLFTPM2_KEY*)&tpmKeyBlob, + ENCODING_TYPE_ASN1, der, &derSz); + if (rc != TPM_RC_SUCCESS) { +#ifdef DEBUG_WOLFSSH + printf("Exporting TPM key failed\n"); +#endif + return WOLFSSH_TPM_FAILED_EXPORT_KEY; + } + + /* Read public key from the buffer */ + byte* p = userPublicKey; + userPublicKeySz = sizeof(userPublicKeyBuf); + + rc = wolfSSH_ReadPublicKey_buffer(der, derSz, WOLFSSH_FORMAT_ASN1, &p, + &userPublicKeySz, &userPublicKeyType, &userPublicKeyTypeSz, heap); + if (rc != TPM_RC_SUCCESS) { +#ifdef DEBUG_WOLFSSH + printf("Reading public key failed returned: %d\n", rc); +#endif + return WOLFSSH_TPM_FAILED_READ_PUBLIC_KEY; + } + + XMEMCPY(&pTpmKey->handle, &tpmKeyBlob.handle, sizeof(pTpmKey->handle)); + XMEMCPY(&pTpmKey->pub, &tpmKeyBlob.pub, sizeof(pTpmKey->pub)); + + /* Unload SRK storage handle */ + wolfTPM2_UnloadHandle(dev, &storage.handle); + /* Key handle is unloaded on TPM cleanup */ + + WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_TPM_InitKey()"); + return WOLFSSH_TPM_SUCCESS; +} + +static void wolfSSH_TPM_Cleanup(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key) +{ + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_TPM_Cleanup()"); + if (key != NULL) { + wolfTPM2_UnloadHandle(dev, &key->handle); + } + + if (dev != NULL) { + wolfTPM2_Cleanup(dev); + } + WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_TPM_Cleanup()"); +} + +/* Set the tpm device and + * key for the client side */ +int CLientSetTpm(WOLFSSH* ssh) +{ + if (ssh != NULL) { + wolfSSH_SetTpmDev(ssh, &tpmDev); + wolfSSH_SetTpmKey(ssh, &tpmKey); + } + return 0; +} + +#endif /* WOLFSSH_TPM */ + /* Reads the private key to use from file name privKeyName. * returns 0 on success */ @@ -702,8 +960,22 @@ int ClientSetPrivateKey(const char* privKeyName, int userEcc, void* heap) isPrivate = 1; } else { - #ifndef NO_FILESYSTEM + #if defined(WOLFSSH_TPM) + /* Protecting the SSH Private Key using a TPM 2.0 device + * + * TPM-backed keys do not require a user buffer, because + * the private key is loaded securely inside the TPM and + * used only from within the TPM for higher security. + * + * Successfully loaded TPM key has a TPM Handle that is + * later passed to wolfSSH for use + */ + WMEMSET(&tpmDev, 0, sizeof(tpmDev)); + WMEMSET(&tpmKey, 0, sizeof(tpmKey)); + ret = wolfSSH_TPM_InitKey(&tpmDev, privKeyName, &tpmKey); + #elif !defined(NO_FILESYSTEM) userPrivateKey = NULL; /* create new buffer based on parsed input */ + userPrivateKeyAlloc = 1; ret = wolfSSH_ReadKey_file(privKeyName, (byte**)&userPrivateKey, &userPrivateKeySz, (const byte**)&userPrivateKeyType, &userPrivateKeyTypeSz, @@ -711,13 +983,12 @@ int ClientSetPrivateKey(const char* privKeyName, int userEcc, void* heap) #else printf("file system not compiled in!\n"); ret = NOT_COMPILED_IN; - #endif + #endif /* WOLFSSH_TPM / NO_FILESYSTEM */ } return ret; } - /* Set public key to use * returns 0 on success */ int ClientUsePubKey(const char* pubKeyName, int userEcc, void* heap) @@ -755,8 +1026,8 @@ int ClientUsePubKey(const char* pubKeyName, int userEcc, void* heap) &isPrivate, heap); #else printf("file system not compiled in!\n"); - ret = -1; - #endif + ret = NOT_COMPILED_IN; + #endif /* NO_FILESYSTEM */ if (ret == 0) { pubKeyLoaded = 1; } @@ -793,15 +1064,17 @@ int ClientLoadCA(WOLFSSH_CTX* ctx, const char* caCert) return ret; } - void ClientFreeBuffers(const char* pubKeyName, const char* privKeyName, void* heap) { +#ifdef WOLFSSH_TPM + wolfSSH_TPM_Cleanup(&tpmDev, &tpmKey); +#endif if (pubKeyName != NULL && userPublicKey != NULL) { WFREE(userPublicKey, heap, DYNTYPE_PRIVKEY); } - if (privKeyName != NULL && userPrivateKey != NULL) { + if (privKeyName != NULL && userPrivateKey != NULL && userPrivateKeyAlloc) { WFREE(userPrivateKey, heap, DYNTYPE_PRIVKEY); } } diff --git a/examples/client/common.h b/examples/client/common.h index 395d4288a..99dc078a0 100644 --- a/examples/client/common.h +++ b/examples/client/common.h @@ -32,6 +32,9 @@ int ClientPublicKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx); void ClientIPOverride(int flag); void ClientFreeBuffers(const char* pubKeyName, const char* privKeyName, void* heap); +#ifdef WOLFSSH_TPM +int CLientSetTpm(WOLFSSH* ssh); +#endif #endif /* WOLFSSH_COMMON_H */ diff --git a/src/internal.c b/src/internal.c index 64445967c..aee9cc19e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -70,6 +70,10 @@ #endif #endif +#ifdef WOLFSSH_TPM + #include + #include +#endif /* Flags: @@ -7490,6 +7494,11 @@ 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; @@ -7498,6 +7507,7 @@ static int DoUserAuthFailure(WOLFSSH* ssh, authType |= WOLFSSH_USERAUTH_PUBLICKEY; break; #endif +#endif /* WOLFSSH_TPM */ default: break; } @@ -11186,6 +11196,14 @@ static int SignHRsa(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 (ssh->ctx->tpmDev && ssh->ctx->tpmKey) { + ret = wolfTPM2_SignHashScheme(ssh->ctx->tpmDev, + ssh->ctx->tpmKey, encSig, encSigSz, sig, (int*)sigSz, + TPM_ALG_RSASSA, TPM2_GetTpmHashType(hashId)); + } + else + #endif /* WOLFSSH_TPM */ ret = wc_RsaSSL_Sign(encSig, encSigSz, sig, KEX_SIG_SIZE, &sigKey->sk.rsa.key, ssh->rng); @@ -12689,9 +12707,15 @@ static int PrepareUserAuthRequestRsa(WOLFSSH* ssh, word32* payloadSz, ret = wc_RsaPublicKeyDecode(authData->sf.publicKey.publicKey, &idx, &keySig->ks.rsa.key, authData->sf.publicKey.publicKeySz); + } else + #endif /* WOLFSSH_AGENT */ + #ifdef WOLFSSH_TPM + { + ret = wc_RsaPublicKeyDecode(authData->sf.publicKey.publicKey, + &idx, &keySig->ks.rsa.key, + authData->sf.publicKey.publicKeySz); } - else - #endif + #else /* !WOLFSSH_TPM */ { ret = wc_RsaPrivateKeyDecode(authData->sf.publicKey.privateKey, &idx, &keySig->ks.rsa.key, @@ -12703,6 +12727,7 @@ static int PrepareUserAuthRequestRsa(WOLFSSH* ssh, word32* payloadSz, authData->sf.publicKey.privateKeySz, &idx); } } + #endif /* WOLFSSH_TPM */ } if (ret == WS_SUCCESS) { @@ -12788,8 +12813,7 @@ static int BuildUserAuthRequestRsa(WOLFSSH* ssh, } } else - #endif - { + #endif /* WOLFSSH_AGENT */ if (ret == WS_SUCCESS) { WMEMSET(digest, 0, sizeof(digest)); ret = wc_HashInit(&hash, hashId); @@ -12844,27 +12868,51 @@ static int BuildUserAuthRequestRsa(WOLFSSH* ssh, ret = WS_CRYPTO_FAILED; } else { - int sigSz; + int sigSz = 0; WLOG(WS_LOG_INFO, "Signing hash with RSA."); - sigSz = wc_RsaSSL_Sign(encDigest, encDigestSz, - output + begin, keySig->sigSz, - &keySig->ks.rsa.key, ssh->rng); - if (sigSz <= 0 || (word32)sigSz != keySig->sigSz) { - WLOG(WS_LOG_DEBUG, "SUAR: Bad RSA Sign"); - ret = WS_RSA_E; - } - else { - ret = wolfSSH_RsaVerify(output + begin, keySig->sigSz, - encDigest, encDigestSz, &keySig->ks.rsa.key, - ssh->ctx->heap, "SUAR"); + #ifdef WOLFSSH_TPM + sigSz = keySig->sigSz; + if(ssh->ctx->tpmDev && ssh->ctx->tpmKey) { + ret = wc_RsaPad_ex(encDigest, encDigestSz, output+begin, + sigSz, RSA_BLOCK_TYPE_1, ssh->rng, WC_RSA_PKCSV15_PAD, + WC_HASH_TYPE_NONE, WC_MGF1NONE, NULL, 0, 0, 0, + ssh->ctx->heap); + if (ret == 0) { + /* private RSA operation */ + ret = wolfTPM2_RsaDecrypt(ssh->ctx->tpmDev, + ssh->ctx->tpmKey, TPM_ALG_NULL, /* no padding */ + output+begin, sigSz, output+begin, (int*)&sigSz); + if (ret != 0) { + ret = WS_RSA_E; + } } } - } + else { + WLOG(WS_LOG_DEBUG, "SendKexDhReply: TPM key or device not set"); + ret = WS_CRYPTO_FAILED; + } + #else /* !WOLFSSH_TPM */ + sigSz = wc_RsaSSL_Sign(encDigest, encDigestSz, + output + begin, keySig->sigSz, + &keySig->ks.rsa.key, ssh->rng); + #endif /* WOLFSSH_TPM */ + if (sigSz <= 0 || (word32)sigSz != keySig->sigSz) { + WLOG(WS_LOG_DEBUG, "SUAR: Bad RSA Sign"); + ret = WS_RSA_E; + } + else { + #ifndef WOLFSSH_TPM + ret = wolfSSH_RsaVerify(output + begin, keySig->sigSz, + encDigest, encDigestSz, &keySig->ks.rsa.key, + ssh->ctx->heap, "SUAR"); + #endif + } - if (ret == WS_SUCCESS) - begin += keySig->sigSz; + if (ret == WS_SUCCESS) + begin += keySig->sigSz; + } + } } - } if (ret == WS_SUCCESS) *idx = begin; @@ -12875,7 +12923,7 @@ static int BuildUserAuthRequestRsa(WOLFSSH* ssh, } return ret; -} +} /* END BuildUserAuthRequestRsa */ #ifdef WOLFSSH_CERTS @@ -14006,6 +14054,7 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig) authData.username = (const byte*)ssh->userName; authData.usernameSz = ssh->userNameSz; + #ifndef WOLFSSH_TPM if (authType & WOLFSSH_USERAUTH_PASSWORD) { ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_PASSWORD, &authData, ssh->userAuthCtx); @@ -14021,6 +14070,7 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig) authData.type = authType; } } + #endif /* fall into public key case if password case was not successful */ if ((ret == WS_FATAL_ERROR || !(authType & WOLFSSH_USERAUTH_PASSWORD)) && diff --git a/src/ssh.c b/src/ssh.c index 4fdff0c77..963063b0a 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -400,6 +400,56 @@ const char* wolfSSH_ErrorToName(int err) } +#ifdef WOLFSSH_TPM +void wolfSSH_SetTpmDev(WOLFSSH* ssh, WOLFTPM2_DEV* dev) +{ + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmDev()"); + + if (ssh && ssh->ctx) + ssh->ctx->tpmDev = dev; + + if (ssh->ctx->tpmDev == NULL) { + WLOG(WS_LOG_DEBUG, "wolfSSH_SetTpmDev: Set tpm dev failed"); + } +} + + +void wolfSSH_SetTpmKey(WOLFSSH* ssh, WOLFTPM2_KEY* key) +{ + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmKey()"); + + if (ssh && ssh->ctx) + ssh->ctx->tpmKey = key; + + if (ssh->ctx->tpmDev == NULL) { + WLOG(WS_LOG_DEBUG, "wolfSSH_SetTpmKey: Set tpm key failed"); + } +} + + +void* wolfSSH_GetTpmDev(WOLFSSH* ssh) +{ + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmDev()"); + + if (ssh && ssh->ctx) { + return ssh->ctx->tpmDev; + } + return NULL; +} + + +void* wolfSSH_GetTpmKey(WOLFSSH* ssh) +{ + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SetTpmKey()"); + + if (ssh && ssh->ctx) { + return ssh->ctx->tpmKey; + } + return NULL; +} +#endif /* WOLFSSH_TPM */ + + #ifndef NO_WOLFSSH_SERVER const char acceptError[] = "accept error: %s, %d"; @@ -1657,7 +1707,7 @@ static int DoSshPubKey(const byte* in, word32 inSz, byte** out, static int DoAsn1Key(const byte* in, word32 inSz, byte** out, word32* outSz, const byte** outType, word32* outTypeSz, - void* heap) + int isPrivate, void* heap) { int ret = WS_SUCCESS; byte* newKey = NULL; @@ -1672,13 +1722,13 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, } else { if (*outSz < inSz) { - WLOG(WS_LOG_DEBUG, "DER private key output size too small"); + WLOG(WS_LOG_DEBUG, "DER key output size too small"); return WS_BUFFER_E; } newKey = *out; } - ret = IdentifyAsn1Key(in, inSz, 1, heap); + ret = IdentifyAsn1Key(in, inSz, isPrivate, heap); if (ret > 0) { *out = newKey; @@ -1701,7 +1751,7 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, static int DoPemKey(const byte* in, word32 inSz, byte** out, word32* outSz, const byte** outType, word32* outTypeSz, - void* heap) + int isPrivate, void* heap) { int ret = WS_SUCCESS; byte* newKey = NULL; @@ -1724,7 +1774,12 @@ static int DoPemKey(const byte* in, word32 inSz, byte** out, } /* If it is PEM, convert to ASN1 then process. */ - ret = wc_KeyPemToDer(in, inSz, newKey, newKeySz, NULL); + if (isPrivate) { + ret = wc_KeyPemToDer(in, inSz, newKey, newKeySz, NULL); + } + else { + ret = wc_PubKeyPemToDer(in, inSz, newKey, newKeySz); + } if (ret > 0) { newKeySz = (word32)ret; ret = WS_SUCCESS; @@ -1817,9 +1872,9 @@ static int DoOpenSshKey(const byte* in, word32 inSz, byte** out, to a constant string. Format indicates the format of the key, currently either SSH format (a public key) or ASN.1 in DER or PEM format (a private key). */ -int wolfSSH_ReadKey_buffer(const byte* in, word32 inSz, int format, +static int wolfSSL_readkey_buffer_ex(const byte* in, word32 inSz, int format, byte** out, word32* outSz, const byte** outType, word32* outTypeSz, - void* heap) + int isPrivate, void* heap) { int ret = WS_SUCCESS; @@ -1831,10 +1886,12 @@ int wolfSSH_ReadKey_buffer(const byte* in, word32 inSz, int format, ret = DoSshPubKey(in, inSz, out, outSz, outType, outTypeSz, heap); } else if (format == WOLFSSH_FORMAT_ASN1) { - ret = DoAsn1Key(in, inSz, out, outSz, outType, outTypeSz, heap); + ret = DoAsn1Key(in, inSz, out, outSz, outType, outTypeSz, + isPrivate, heap); } else if (format == WOLFSSH_FORMAT_PEM) { - ret = DoPemKey(in, inSz, out, outSz, outType, outTypeSz, heap); + ret = DoPemKey(in, inSz, out, outSz, outType, outTypeSz, + isPrivate, heap); } else if (format == WOLFSSH_FORMAT_OPENSSH) { ret = DoOpenSshKey(in, inSz, out, outSz, outType, outTypeSz, heap); @@ -1846,6 +1903,21 @@ int wolfSSH_ReadKey_buffer(const byte* in, word32 inSz, int format, return ret; } +int wolfSSH_ReadKey_buffer(const byte* in, word32 inSz, int format, + byte** out, word32* outSz, const byte** outType, word32* outTypeSz, + void* heap) +{ + return wolfSSL_readkey_buffer_ex(in, inSz, format, out, outSz, + outType, outTypeSz, 1, heap); +} + +int wolfSSH_ReadPublicKey_buffer(const byte* in, word32 inSz, int format, + byte** out, word32* outSz, const byte** outType, word32* outTypeSz, + void* heap) +{ + return wolfSSL_readkey_buffer_ex(in, inSz, format, out, outSz, + outType, outTypeSz, 0, heap); +} #if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM) diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 731a9688d..3e733ca5f 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -48,6 +48,9 @@ #include #endif /* WOLFSSH_CERTS */ +#ifdef WOLFSSH_TPM + #include +#endif /* WOLFSSH_TPM */ #if !defined (ALIGN16) #if defined (__GNUC__) @@ -549,6 +552,10 @@ struct WOLFSSH_CTX { #ifdef WOLFSSH_AGENT byte agentEnabled; #endif /* WOLFSSH_AGENT */ +#ifdef WOLFSSH_TPM + WOLFTPM2_DEV* tpmDev; + WOLFTPM2_KEY* tpmKey; +#endif /* WOLFSSH_TPM */ WS_CallbackKeyingCompletion keyingCompletionCb; }; diff --git a/wolfssh/ssh.h b/wolfssh/ssh.h index 8f6a2a115..e711c9e1c 100644 --- a/wolfssh/ssh.h +++ b/wolfssh/ssh.h @@ -39,6 +39,10 @@ #include #include +#ifdef WOLFSSH_TPM +#include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -86,6 +90,9 @@ WOLFSSH_API void* wolfSSH_GetHighwaterCtx(WOLFSSH*); WOLFSSH_API int wolfSSH_ReadKey_buffer(const byte* in, word32 inSz, int format, byte** out, word32* outSz, const byte** outType, word32* outTypeSz, void* heap); +WOLFSSH_API int wolfSSH_ReadPublicKey_buffer(const byte* in, word32 inSz, int format, + byte** out, word32* outSz, const byte** outType, word32* outTypeSz, + void* heap); WOLFSSH_API int wolfSSH_ReadKey_file(const char* name, byte** out, word32* outSz, const byte** outType, word32* outTypeSz, byte* isPrivate, void* heap); @@ -258,6 +265,14 @@ WOLFSSH_API int wolfSSH_get_error(const WOLFSSH*); WOLFSSH_API const char* wolfSSH_get_error_name(const WOLFSSH*); WOLFSSH_API const char* wolfSSH_ErrorToName(int); +/* TPM 2.0 integration related functions */ +#ifdef WOLFSSH_TPM +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); +#endif /* WOLFSSH_TPM */ + /* I/O callbacks */ typedef int (*WS_CallbackIORecv)(WOLFSSH*, void*, word32, void*); typedef int (*WS_CallbackIOSend)(WOLFSSH*, void*, word32, void*); @@ -458,6 +473,16 @@ enum WS_DisconnectReasonCodes { WOLFSSH_DISCONNECT_ILLEGAL_USER_NAME = 15 }; +enum WS_TpmResults +{ + WOLFSSH_TPM_SUCCESS, + WOLFSSH_TPM_FAILED_INIT, + WOLFSSH_TPM_FAILED_LOAD_PRIMARY, + WOLFSSH_TPM_FAILED_READ_KEYBLOB, + WOLFSSH_TPM_FAILED_EXPORT_KEY, + WOLFSSH_TPM_FAILED_LOAD_KEY, + WOLFSSH_TPM_FAILED_READ_PUBLIC_KEY +}; WOLFSSH_API int wolfSSH_RealPath(const char* defaultPath, char* in, char* out, word32 outSz);