From 9e7bd3f0d6c38a1ab9fefaa7d712e00e29e33e09 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Fri, 10 Jan 2025 17:06:32 +0000 Subject: [PATCH] RFC-4256 Keyboard-Interactive authentication This implements Keyboard-Interactive authentication. Adds an additional callback set by `wolfSSH_KeyboarAuthPrompts()` which will set a callback in the server to ask the application to provide the prompt details for the client. --- examples/client/common.c | 64 +- examples/echoserver/echoserver.c | 94 ++- src/internal.c | 590 +++++++++++++++++- src/ssh.c | 32 + tests/api.c | 145 +++++ tests/auth.c | 569 +++++++++++++++++ tests/auth.h | 33 + tests/include.am | 35 +- wolfssh/internal.h | 13 +- wolfssh/ssh.h | 26 +- wolfssh/test.h | 6 +- zephyr/samples/tests/wolfssh_user_settings.h | 3 + .../tests/wolfssh_user_settings_nofs.h | 3 + 13 files changed, 1591 insertions(+), 22 deletions(-) create mode 100644 tests/auth.c create mode 100644 tests/auth.h diff --git a/examples/client/common.c b/examples/client/common.c index db27d0f96..ed21b0c7a 100644 --- a/examples/client/common.c +++ b/examples/client/common.c @@ -57,6 +57,10 @@ static word32 userPrivateKeySz = sizeof(userPrivateKeyBuf); static word32 userPrivateKeyTypeSz = 0; static byte isPrivate = 0; +static word32 keyboardResponseCount = 0; +static byte** keyboardResponses; +static word32* keyboardResponseLengths; + #ifdef WOLFSSH_CERTS #if 0 @@ -445,6 +449,9 @@ int ClientUserAuth(byte authType, { const char* defaultPassword = (const char*)ctx; word32 passwordSz = 0; +#ifdef WOLFSSH_TERM + word32 entry; +#endif int ret = WOLFSSH_USERAUTH_SUCCESS; #ifdef DEBUG_WOLFSSH @@ -456,6 +463,9 @@ int ClientUserAuth(byte authType, if (authData->type & WOLFSSH_USERAUTH_PUBLICKEY) { printf(" - publickey\n"); } + if (authData->type & WOLFSSH_USERAUTH_KEYBOARD) { + printf(" - keyboard\n"); + } printf("wolfSSH requesting to use type %d\n", authType); #endif @@ -523,7 +533,52 @@ int ClientUserAuth(byte authType, authData->sf.password.passwordSz = passwordSz; } } - +#ifdef WOLFSSH_TERM + else if (authType == WOLFSSH_USERAUTH_KEYBOARD) { + if (authData->sf.keyboard.promptName && + authData->sf.keyboard.promptName[0] != '\0') { + printf("%s\n", authData->sf.keyboard.promptName); + } + if (authData->sf.keyboard.promptInstruction && + authData->sf.keyboard.promptInstruction[0] != '\0') { + printf("%s\n", authData->sf.keyboard.promptInstruction); + } + keyboardResponseCount = authData->sf.keyboard.promptCount; + keyboardResponses = WMALLOC(sizeof(byte*) * keyboardResponseCount, NULL, + 0); + authData->sf.keyboard.responses = (byte**)keyboardResponses; + keyboardResponseLengths = WMALLOC( + sizeof(word32) * keyboardResponseCount, NULL, 0); + authData->sf.keyboard.responseLengths = keyboardResponseLengths; + + for (entry = 0; entry < authData->sf.keyboard.promptCount; entry++) { + printf("%s", authData->sf.keyboard.prompts[entry]); + if (!authData->sf.keyboard.promptEcho[entry]) { + ClientSetEcho(0); + } + if (WFGETS((char*)userPassword, sizeof(userPassword), stdin) + == NULL) { + fprintf(stderr, "Getting response failed.\n"); + ret = WOLFSSH_USERAUTH_FAILURE; + } + else { + char* c = strpbrk((char*)userPassword, "\r\n"); + if (c != NULL) + *c = '\0'; + } + passwordSz = (word32)strlen((const char*)userPassword); + ClientSetEcho(1); + #ifdef USE_WINDOWS_API + printf("\r\n"); + #endif + WFFLUSH(stdout); + authData->sf.keyboard.responses[entry] = + (byte*) WSTRDUP((char*)userPassword, NULL, 0); + authData->sf.keyboard.responseLengths[entry] = passwordSz; + authData->sf.keyboard.responseCount++; + } + } +#endif return ret; } @@ -797,6 +852,7 @@ int ClientLoadCA(WOLFSSH_CTX* ctx, const char* caCert) void ClientFreeBuffers(const char* pubKeyName, const char* privKeyName, void* heap) { + word32 entry; if (pubKeyName != NULL && userPublicKey != NULL) { WFREE(userPublicKey, heap, DYNTYPE_PRIVKEY); } @@ -804,4 +860,10 @@ void ClientFreeBuffers(const char* pubKeyName, const char* privKeyName, if (privKeyName != NULL && userPrivateKey != NULL) { WFREE(userPrivateKey, heap, DYNTYPE_PRIVKEY); } + + for (entry = 0; entry < keyboardResponseCount; entry++) { + WFREE(keyboardResponses[entry], NULL, 0); + } + WFREE(keyboardResponses, NULL, 0); + WFREE(keyboardResponseLengths, NULL, 0); } diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 97d3accfa..cdc17b50c 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -289,7 +289,6 @@ static int callbackReqFailure(WOLFSSH *ssh, void *buf, word32 sz, void *ctx) return WS_SUCCESS; } - static void *global_req(void *ctx) { int ret; @@ -1997,6 +1996,34 @@ static int LoadPasswdList(StrList* strList, PwMapList* mapList) return count; } +static int LoadKeyboardList(StrList* strList, PwMapList* mapList) +{ + char names[256]; + char* passwd; + int count = 0; + + while (strList) { + WSTRNCPY(names, strList->str, sizeof names - 1); + passwd = WSTRCHR(names, ':'); + if (passwd != NULL) { + *passwd = 0; + passwd++; + + PwMapNew(mapList, WOLFSSH_USERAUTH_KEYBOARD, + (byte*)names, (word32)WSTRLEN(names), + (byte*)passwd, (word32)WSTRLEN(passwd)); + } + else { + fprintf(stderr, "Ignoring password: %s\n", names); + } + + strList = strList->next; + count++; + } + + return count; +} + #ifndef NO_FILESYSTEM static int LoadPubKeyList(StrList* strList, int format, PwMapList* mapList) { @@ -2103,7 +2130,8 @@ static int wsUserAuth(byte authType, #ifdef WOLFSSH_ALLOW_USERAUTH_NONE authType != WOLFSSH_USERAUTH_NONE && #endif - authType != WOLFSSH_USERAUTH_PUBLICKEY) { + authType != WOLFSSH_USERAUTH_PUBLICKEY && + authType != WOLFSSH_USERAUTH_KEYBOARD) { return WOLFSSH_USERAUTH_FAILURE; } @@ -2113,6 +2141,14 @@ static int wsUserAuth(byte authType, authData->sf.password.passwordSz, authHash); } + else if (authType == WOLFSSH_USERAUTH_KEYBOARD) { + if (authData->sf.keyboard.responseCount != 1) { + return WOLFSSH_USERAUTH_FAILURE; + } + wc_Sha256Hash(authData->sf.keyboard.responses[0], + authData->sf.keyboard.responseLengths[0], + authHash); + } else if (authType == WOLFSSH_USERAUTH_PUBLICKEY) { wc_Sha256Hash(authData->sf.publicKey.publicKey, authData->sf.publicKey.publicKeySz, @@ -2213,6 +2249,14 @@ static int wsUserAuth(byte authType, WOLFSSH_USERAUTH_REJECTED; } } + else if (authData->type == WOLFSSH_USERAUTH_KEYBOARD) { + if (WMEMCMP(map->p, authHash, WC_SHA256_DIGEST_SIZE) == 0) { + return WOLFSSH_USERAUTH_SUCCESS; + } + else { + return WOLFSSH_USERAUTH_INVALID_PASSWORD; + } + } #ifdef WOLFSSH_ALLOW_USERAUTH_NONE else if (authData->type == WOLFSSH_USERAUTH_NONE) { return WOLFSSH_USERAUTH_SUCCESS; @@ -2228,6 +2272,13 @@ static int wsUserAuth(byte authType, return WOLFSSH_USERAUTH_INVALID_USER; } +static int keyboardCallback(WS_UserAuthData_Keyboard *kbAuth, void *ctx) +{ + WS_UserAuthData_Keyboard *kbAuthData = (WS_UserAuthData_Keyboard*) ctx; + WMEMCPY(kbAuth, kbAuthData, sizeof(WS_UserAuthData_Keyboard)); + + return WS_SUCCESS; +} #ifdef WOLFSSH_SFTP /* @@ -2312,6 +2363,9 @@ static void ShowUsage(void) " load in an X.509 DER cert to accept from peer\n"); printf(" -P :\n" " add password to accept from peer\n"); + printf(" -i :\n" + " add passowrd to accept via keyboard-interactive " + "from peer\n"); #ifdef WOLFSSH_CERTS printf(" -a load in a root CA certificate file\n"); #endif @@ -2352,6 +2406,8 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) StrList* derPubKeyList = NULL; #endif StrList* passwdList = NULL; + StrList* keyboardList = NULL; + WS_UserAuthData_Keyboard kbAuthData; WS_SOCKET_T listenFd = WOLFSSH_SOCKET_INVALID; word32 defaultHighwater = EXAMPLE_HIGHWATER_MARK; word32 threadCount = 0; @@ -2376,9 +2432,10 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) int argc = serverArgs->argc; char** argv = serverArgs->argv; serverArgs->return_code = EXIT_SUCCESS; + kbAuthData.promptCount = 0; if (argc > 0) { - const char* optlist = "?1a:d:efEp:R:Ni:j:I:J:K:P:k:b:"; + const char* optlist = "?1a:d:efEp:R:Ni:j:i:I:J:K:P:k:b:"; myoptind = 0; while ((ch = mygetopt(argc, argv, optlist)) != -1) { switch (ch) { @@ -2462,6 +2519,10 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) passwdList = StrListAdd(passwdList, myoptarg); break; + case 'i': + keyboardList = StrListAdd(keyboardList, myoptarg); + break; + case 'b': userAuthWouldBlock = atoi(myoptarg); break; @@ -2529,6 +2590,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) wolfSSH_SetUserAuth(ctx, wsUserAuth); else wolfSSH_SetUserAuth(ctx, ((func_args*)args)->user_auth); + wolfSSH_SetUserAuthResult(ctx, wsUserAuthResult); wolfSSH_CTX_SetBanner(ctx, echoserverBanner); #ifdef WOLFSSH_AGENT @@ -2561,6 +2623,26 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) passwdList = NULL; } + if (keyboardList) { + LoadKeyboardList(keyboardList, &pwMapList); + StrListFree(keyboardList); + keyboardList = NULL; + kbAuthData.promptCount = 1; + kbAuthData.promptName = NULL; + kbAuthData.promptNameSz = 0; + kbAuthData.promptInstruction = NULL; + kbAuthData.promptInstructionSz = 0; + kbAuthData.promptLanguage = NULL; + kbAuthData.promptLanguageSz = 0; + kbAuthData.prompts = WMALLOC(sizeof(char*), NULL, 0); + kbAuthData.prompts[0] = (byte*)"KB Auth Password: "; + kbAuthData.promptLengths = WMALLOC(sizeof(word32), NULL, 0); + kbAuthData.promptLengths[0] = 18; + kbAuthData.promptEcho = WMALLOC(sizeof(byte), NULL, 0); + kbAuthData.promptEcho[0] = 0; + wolfSSH_SetKeyboardAuthPrompts(ctx, keyboardCallback); + } + { const char* bufName = NULL; #ifndef WOLFSSH_SMALL_STACK @@ -2762,6 +2844,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #endif wolfSSH_SetUserAuthCtx(ssh, &pwMapList); wolfSSH_SetKeyingCompletionCbCtx(ssh, (void*)ssh); + wolfSSH_SetKeyboardAuthCtx(ssh, &kbAuthData); /* Use the session object for its own highwater callback ctx */ if (defaultHighwater > 0) { wolfSSH_SetHighwaterCtx(ssh, (void*)ssh); @@ -2834,6 +2917,11 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) if (listenFd != WOLFSSH_SOCKET_INVALID) { WCLOSESOCKET(listenFd); } + if (kbAuthData.promptCount > 0) { + WFREE(kbAuthData.promptLengths, NULL, 0); + WFREE(kbAuthData.prompts, NULL, 0); + WFREE(kbAuthData.promptEcho, NULL, 0); + } wc_FreeMutex(&doneLock); PwMapListDelete(&pwMapList); wolfSSH_CTX_free(ctx); diff --git a/src/internal.c b/src/internal.c index fb645fa74..cd03a586b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -598,7 +598,8 @@ INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg) } /* Explicitly check for the user authentication messages that * only the server sends, it shouldn't receive them. */ - if (msg > MSGID_USERAUTH_RESTRICT) { + if ((msg > MSGID_USERAUTH_RESTRICT) && + (msg != MSGID_USERAUTH_INFO_RESPONSE)) { WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by server " "during user authentication", msg); return 0; @@ -1035,7 +1036,8 @@ WOLFSSH* SshInit(WOLFSSH* ssh, WOLFSSH_CTX* ctx) ssh->authId = ID_USERAUTH_PUBLICKEY; ssh->supportedAuth[0] = ID_USERAUTH_PUBLICKEY; ssh->supportedAuth[1] = ID_USERAUTH_PASSWORD; - ssh->supportedAuth[2] = ID_NONE; /* ID_NONE is treated as empty slot */ + ssh->supportedAuth[2] = ID_USERAUTH_KEYBOARD; + ssh->supportedAuth[3] = ID_NONE; /* ID_NONE is treated as empty slot */ ssh->nextChannel = DEFAULT_NEXT_CHANNEL; ssh->blockSz = MIN_BLOCK_SZ; ssh->encryptId = ID_NONE; @@ -2523,6 +2525,7 @@ static const NameIdPair NameIdMap[] = { /* UserAuth IDs */ { ID_USERAUTH_PASSWORD, TYPE_OTHER, "password" }, { ID_USERAUTH_PUBLICKEY, TYPE_OTHER, "publickey" }, + { ID_USERAUTH_KEYBOARD, TYPE_OTHER, "keyboard-interactive" }, /* Channel Type IDs */ { ID_CHANTYPE_SESSION, TYPE_OTHER, "session" }, @@ -3441,7 +3444,8 @@ static int GetNameListRaw(byte* idList, word32* idListSz, { const char* displayName = IdToName(id); if (displayName) { - WLOG(WS_LOG_DEBUG, "GNL: name ID = %s", displayName); + WLOG(WS_LOG_DEBUG, "GNL: name ID %.*s matches %s", nameSz, + name, displayName); } } if (id != ID_UNKNOWN || idListIdx == 0) { @@ -6210,6 +6214,134 @@ static int DoUserAuthRequestNone(WOLFSSH* ssh, WS_UserAuthData* authData, #endif + +static int DoUserAuthInfoResponse(WOLFSSH* ssh, + byte* buf, word32 len, word32* idx) +{ + word32 begin; + WS_UserAuthData authData; + WS_UserAuthData_Keyboard* kb = NULL; + int ret = WS_SUCCESS; + int authFailure = 0; + byte partialSuccess = 0; + word32 entry; + + WLOG(WS_LOG_DEBUG, "Entering DoUserAuthInfoResponse()"); + + + if (ssh == NULL || buf == NULL || len == 0 || idx == NULL) { + + ret = WS_BAD_ARGUMENT; + } + + if (ret == WS_SUCCESS) { + begin = *idx; + kb = &authData.sf.keyboard; + authData.type = WOLFSSH_USERAUTH_KEYBOARD; + authData.username = (byte*)ssh->userName; + authData.usernameSz = ssh->userNameSz; + ret = GetUint32(&kb->responseCount, buf, len, &begin); + } + if (kb->responseCount != ssh->kbAuth.promptCount) { + WLOG(WS_LOG_DEBUG, + "DUARKB: keyboard response count did not match request count"); + ret = WS_USER_AUTH_E; + } + + if (ret == WS_SUCCESS && kb->responseCount) { + kb->responseLengths = WMALLOC(sizeof(word32) * kb->responseCount, NULL, + 0); + if (!kb->responseLengths) { + ret = WS_MEMORY_E; + } + } + else { + kb->responseLengths = NULL; + } + if (ret == WS_SUCCESS && kb->responseCount) { + kb->responses = WMALLOC(sizeof(char*) * kb->responseCount, NULL, 0); + if (!kb->responses) { + ret = WS_MEMORY_E; + } + } + else { + kb->responses = NULL; + } + + if (ret == WS_SUCCESS) { + for (entry = 0; entry < kb->responseCount; entry++) { + if (ret == WS_SUCCESS) { + GetStringRef(&kb->responseLengths[entry], + (const byte**)&kb->responses[entry], buf, len, &begin); + } + } + } + + if (ret == WS_SUCCESS) { + if (ssh->ctx->userAuthCb != NULL) { + WLOG(WS_LOG_DEBUG, "DUARKB: Calling the userauth callback"); + ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_KEYBOARD, + &authData, ssh->userAuthCtx); + + if (ret == WOLFSSH_USERAUTH_SUCCESS) { + WLOG(WS_LOG_DEBUG, "DUARKB: keyboard check success"); + ret = WS_SUCCESS; + } + else if (ret == WOLFSSH_USERAUTH_SUCCESS_ANOTHER) { + WLOG(WS_LOG_DEBUG, "DUARKB: keyboard check success, another wanted"); + } + else if (ret == WOLFSSH_USERAUTH_PARTIAL_SUCCESS) { + WLOG(WS_LOG_DEBUG, "DUARKB: keyboard check partial success"); + partialSuccess = 1; + ret = WS_SUCCESS; + } + else if (ret == WOLFSSH_USERAUTH_REJECTED) { + WLOG(WS_LOG_DEBUG, "DUARKB: keyboard rejected"); + #ifndef NO_FAILURE_ON_REJECTED + authFailure = 1; + #endif + ret = WS_USER_AUTH_E; + } + else if (ret == WOLFSSH_USERAUTH_WOULD_BLOCK) { + WLOG(WS_LOG_DEBUG, "DUARKB: keyboard callback would block"); + ret = WS_AUTH_PENDING; + } + else { + WLOG(WS_LOG_DEBUG, "DUARKB: keyboard check failed, retry"); + authFailure = 1; + ret = WS_SUCCESS; + } + } + else { + WLOG(WS_LOG_DEBUG, "DUARKB: No user auth callback"); + authFailure = 1; + } + } + + if (ret == WS_SUCCESS) + *idx = begin; + + if (authFailure || partialSuccess) { + ret = SendUserAuthFailure(ssh, partialSuccess); + } + else if (ret == WOLFSSH_USERAUTH_SUCCESS_ANOTHER) { + ret = SendUserAuthKeyboardRequest(ssh, &authData); + } + else if (ret == WS_SUCCESS) { + ssh->clientState = CLIENT_USERAUTH_DONE; + } + + if (kb->responseCount) { + WFREE(kb->responses, NULL, 0); + WFREE(kb->responseLengths, NULL, 0); + } + + WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthInfoResponse(), ret = %d", ret); + + return ret; +} + + /* Utility for DoUserAuthRequest() */ static int DoUserAuthRequestPassword(WOLFSSH* ssh, WS_UserAuthData* authData, byte* buf, word32 len, word32* idx) @@ -7421,6 +7553,10 @@ static int DoUserAuthRequest(WOLFSSH* ssh, ret = GetUint32(&authData.serviceNameSz, buf, len, &begin); } + if (ret == WS_SUCCESS) { + ret = wolfSSH_SetUsernameRaw(ssh, authData.username, authData.usernameSz); + } + if (ret == WS_SUCCESS) { if (authData.serviceNameSz > len - begin) { ret = WS_BUFFER_E; @@ -7441,6 +7577,9 @@ static int DoUserAuthRequest(WOLFSSH* ssh, if (authNameId == ID_USERAUTH_PASSWORD) ret = DoUserAuthRequestPassword(ssh, &authData, buf, len, &begin); + else if (authNameId == ID_USERAUTH_KEYBOARD) { + ret = SendUserAuthKeyboardRequest(ssh, &authData); + } #if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) else if (authNameId == ID_USERAUTH_PUBLICKEY) { authData.sf.publicKey.dataToSign = buf + *idx; @@ -7474,8 +7613,9 @@ static int DoUserAuthRequest(WOLFSSH* ssh, static int DoUserAuthFailure(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) { - byte authList[3]; /* Should only ever be password, publickey, hostname */ - word32 authListSz = 3; + byte authList[4]; /* Should only ever be password, publickey, hostname, + keyboard */ + word32 authListSz = 4; byte partialSuccess; byte authType = 0; int ret = WS_SUCCESS; @@ -7503,6 +7643,9 @@ static int DoUserAuthFailure(WOLFSSH* ssh, case ID_USERAUTH_PASSWORD: authType |= WOLFSSH_USERAUTH_PASSWORD; break; + case ID_USERAUTH_KEYBOARD: + authType |= WOLFSSH_USERAUTH_KEYBOARD; + break; #if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) case ID_USERAUTH_PUBLICKEY: authType |= WOLFSSH_USERAUTH_PUBLICKEY; @@ -7526,6 +7669,10 @@ static int DoUserAuthFailure(WOLFSSH* ssh, ret = SendUserAuthRequest(ssh, authType, 0); } + if ((ret == WS_SUCCESS) && (authType & WOLFSSH_USERAUTH_KEYBOARD)) { + //ret = SendUserAuthKeyboard(ssh, authType); + } + WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthFailure(), ret = %d", ret); return ret; } @@ -7545,7 +7692,10 @@ static int DoUserAuthSuccess(WOLFSSH* ssh, return ret; } - ssh->serverState = SERVER_USERAUTH_ACCEPT_DONE; + if (ssh->serverState == SERVER_USERAUTH_ACCEPT_KEYBOARD) + ssh->serverState = SERVER_USERAUTH_ACCEPT_KEYBOARD_DONE; + else + ssh->serverState = SERVER_USERAUTH_ACCEPT_DONE; WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthSuccess(), ret = %d", ret); return ret; @@ -7580,6 +7730,94 @@ static int DoUserAuthBanner(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) } +static int DoUserAuthInfoRequest(WOLFSSH* ssh, byte* buf, word32 len, + word32* idx) +{ + int ret = WS_SUCCESS; + word32 begin; + word32 promptSz = 0; + word32 entry; + byte *authName = NULL; + byte *authInstruction = NULL; + byte *language = NULL; + byte *echo = NULL; + byte **prompts = NULL; + + WLOG(WS_LOG_DEBUG, "Entering DoUserAuthInfoRequest()"); + + if (ssh == NULL || buf == NULL || len == 0 || idx == NULL) + ret = WS_BAD_ARGUMENT; + + + if (ret == WS_SUCCESS) { + begin = *idx; + ret = GetStringAlloc(ssh->ctx->heap, (char**)&authName, buf, len, + &begin); + } + + if (ret == WS_SUCCESS) + ret = GetStringAlloc(ssh->ctx->heap, (char**)&authInstruction, buf, len, + &begin); + + if (ret == WS_SUCCESS) + ret = GetStringAlloc(ssh->ctx->heap, (char**)&language, buf, len, + &begin); + + if (ret == WS_SUCCESS) + ret = GetSize(&promptSz, buf, len, &begin); + + if (ret == WS_SUCCESS && promptSz) { + prompts = WMALLOC(sizeof(byte*) * promptSz, ssh->ctx->heap, + DYNTYPE_BUFFER); + if (!prompts) { + ret = WS_MEMORY_E; + } else { + echo = (byte*)WMALLOC(sizeof(byte) * promptSz, ssh->ctx->heap, + DYNTYPE_BUFFER); + } + + if (!echo) { + ret = WS_MEMORY_E; + } else { + WMEMSET(prompts, '\0', sizeof(char*) * promptSz); + for (entry = 0; entry < promptSz; entry++) { + ret = GetStringAlloc(ssh->ctx->heap, (char**)&prompts[entry], + buf, len, &begin); + if (ret != WS_SUCCESS) + break; + ret = GetBoolean(&echo[entry], buf, len, &begin); + if (ret != WS_SUCCESS) + break; + } + } + } + + if (ret == WS_SUCCESS) { + *idx += len; + ssh->kbAuth.promptCount = promptSz; + ssh->kbAuth.prompts = (byte**)prompts; + ssh->kbAuth.promptInstruction = authInstruction; + ssh->kbAuth.promptName = authName; + ssh->kbAuth.promptLanguage = language; + ssh->kbAuth.promptEcho = echo; + WLOG(WS_LOG_DEBUG, "Got keyboard-interactive prompt '%s'", authName); + } else { + for (entry = 0; entry < promptSz; entry++) { + WFREE((void*)prompts[entry], ssh->ctx->heap, DYNTYPE_BUFFER); + } + WFREE(prompts, ssh->ctx->heap, DYNTYPE_BUFFER); + WFREE(echo, ssh->ctx->heap, DYNTYPE_BUFFER); + } + + if (ret == WS_SUCCESS) + ssh->serverState = SERVER_USERAUTH_ACCEPT_KEYBOARD; + + WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthInfoRequest(), ret = %d", ret); + + return ret; +} + + #ifdef WOLFSSH_FWD static int DoGlobalRequestFwd(WOLFSSH* ssh, byte* buf, word32 len, word32* idx, int wantReply, int isCancel) @@ -8930,6 +9168,16 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed) ret = DoUserAuthRequest(ssh, buf + idx, payloadSz, &payloadIdx); break; + case MSGID_USERAUTH_INFO_RESPONSE: + WLOG(WS_LOG_DEBUG, "Decoding MSG_USERAUTH_INFO_RESPONSE"); + ret = DoUserAuthInfoResponse(ssh, buf + idx, payloadSz, &payloadIdx); + break; + + case MSGID_USERAUTH_INFO_REQUEST: + WLOG(WS_LOG_DEBUG, "Decoding MSGID_USERAUTH_INFO_REQUEST"); + ret = DoUserAuthInfoRequest(ssh, buf + idx, payloadSz, &payloadIdx); + break; + case MSGID_USERAUTH_FAILURE: WLOG(WS_LOG_DEBUG, "Decoding MSGID_USERAUTH_FAILURE"); ret = DoUserAuthFailure(ssh, buf + idx, payloadSz, &payloadIdx); @@ -12679,6 +12927,218 @@ static int BuildUserAuthRequestPassword(WOLFSSH* ssh, return ret; } +static int PrepareUserAuthRequestKeyboard(WOLFSSH* ssh, word32* payloadSz, + const WS_UserAuthData* authData) +{ + int ret = WS_SUCCESS; + word32 entry; + + if (ssh == NULL || payloadSz == NULL || authData == NULL) + ret = WS_BAD_ARGUMENT; + + if (ret == WS_SUCCESS) + { + *payloadSz += LENGTH_SZ * 3; + if (authData->sf.keyboard.promptName) { + *payloadSz += authData->sf.keyboard.promptNameSz; + } + if (authData->sf.keyboard.promptInstruction) { + *payloadSz += authData->sf.keyboard.promptInstructionSz; + } + if (authData->sf.keyboard.promptLanguage) { + *payloadSz += authData->sf.keyboard.promptLanguageSz; + } + *payloadSz += LENGTH_SZ; + + for (entry = 0; entry < authData->sf.keyboard.promptCount; entry++) { + *payloadSz += LENGTH_SZ + + authData->sf.keyboard.promptLengths[entry]; + *payloadSz += BOOLEAN_SZ; + } + } + + return ret; +} + + +static int BuildUserAuthRequestKeyboard(WOLFSSH* ssh, byte* output, word32* idx, + const WS_UserAuthData* authData) +{ + int ret = WS_SUCCESS; + word32 begin; + word32 entry; + + if (ssh == NULL || output == NULL || idx == NULL || authData == NULL) + ret = WS_BAD_ARGUMENT; + + if (ret == WS_SUCCESS) { + begin = *idx; + if (authData->sf.keyboard.promptName) { + word32 slen = authData->sf.keyboard.promptNameSz; + c32toa(slen, output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, authData->sf.keyboard.promptName, slen); + begin += slen; + } else { + c32toa(0, output + begin); + begin += LENGTH_SZ; + } + if (authData->sf.keyboard.promptInstruction) { + word32 slen = authData->sf.keyboard.promptInstructionSz; + c32toa(slen, output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, authData->sf.keyboard.promptInstruction, slen); + begin += slen; + } else { + c32toa(0, output + begin); + begin += LENGTH_SZ; + } + if (authData->sf.keyboard.promptLanguage) { + word32 slen = authData->sf.keyboard.promptLanguageSz; + c32toa(slen, output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, authData->sf.keyboard.promptLanguage, slen); + begin += slen; + } else { + c32toa(0, output + begin); + begin += LENGTH_SZ; + } + c32toa(authData->sf.keyboard.promptCount, output + begin); + begin += LENGTH_SZ; + for (entry = 0; entry < authData->sf.keyboard.promptCount; + entry++) { + c32toa(authData->sf.keyboard.promptLengths[entry], output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, authData->sf.keyboard.prompts[entry], + authData->sf.keyboard.promptLengths[entry]); + begin += authData->sf.keyboard.promptLengths[entry]; + output[begin] = authData->sf.keyboard.promptEcho[entry]; + begin++; + } + *idx = begin; + } + + return ret; +} + +int SendUserAuthKeyboardRequest(WOLFSSH* ssh, WS_UserAuthData* authData) +{ + byte* output; + word32 idx; + word32 payloadSz = 0; + int ret = WS_SUCCESS; + + WLOG(WS_LOG_DEBUG, "Entering SendUserAuthKeyboardRequest()"); + + + if (ssh == NULL || authData == NULL) { + ret = WS_BAD_ARGUMENT; + } + + if (ret == WS_SUCCESS) { + ret = ssh->ctx->keyboardAuthCb(&authData->sf.keyboard, + ssh->keyboardAuthCtx); + } + + if (authData->sf.keyboard.promptCount > 0 && + (authData->sf.keyboard.prompts == NULL || + authData->sf.keyboard.promptLengths == NULL || + authData->sf.keyboard.promptEcho == NULL)) { + + ret = WS_BAD_USAGE; + } + + ssh->kbAuth.promptCount = authData->sf.keyboard.promptCount; + + payloadSz = MSG_ID_SZ; + if (ret == WS_SUCCESS) { + ret = PrepareUserAuthRequestKeyboard(ssh, &payloadSz, authData); + } + + if (ret == WS_SUCCESS) { + ret = PreparePacket(ssh, payloadSz); + } + + output = ssh->outputBuffer.buffer; + idx = ssh->outputBuffer.length; + + output[idx++] = MSGID_USERAUTH_INFO_REQUEST; + + if (ret == WS_SUCCESS) { + ret = BuildUserAuthRequestKeyboard(ssh, output, &idx, authData); + } + + if (ret == WS_SUCCESS) { + ssh->outputBuffer.length = idx; + ret = BundlePacket(ssh); + } + + if (ret == WS_SUCCESS) { + ret = wolfSSH_SendPacket(ssh); + } + + if ((ret != WS_WANT_WRITE) && (ret != WS_SUCCESS)) { + PurgePacket(ssh); + } + + WLOG(WS_LOG_DEBUG, "Leaving SendUserAuthKeyboardRequest(), ret = %d", ret); + + return ret; +} + +static int PrepareUserAuthResponseKeyboard(WOLFSSH* ssh, word32* payloadSz, + const WS_UserAuthData* authData) +{ + int ret = WS_SUCCESS; + word32 entry; + + if (ssh == NULL || payloadSz == NULL || authData == NULL) + ret = WS_BAD_ARGUMENT; + + if (ret == WS_SUCCESS) + { + *payloadSz += LENGTH_SZ; + for (entry = 0; entry < authData->sf.keyboard.responseCount; entry++) { + *payloadSz += LENGTH_SZ + + authData->sf.keyboard.responseLengths[entry]; + } + } + + return ret; +} + + +static int BuildUserAuthResponseKeyboard(WOLFSSH* ssh, byte* output, word32* idx, + const WS_UserAuthData* authData) +{ + int ret = WS_SUCCESS; + word32 begin; + word32 entry; + + if (ssh == NULL || output == NULL || idx == NULL || authData == NULL) + ret = WS_BAD_ARGUMENT; + + if (authData->sf.keyboard.promptCount != authData->sf.keyboard.responseCount) { + ret = WS_USER_AUTH_E; + WLOG(WS_LOG_DEBUG, "Not enough answers provided for prompts"); + } + + if (ret == WS_SUCCESS) { + begin = *idx; + c32toa(authData->sf.keyboard.responseCount, output + begin); + begin += LENGTH_SZ; + for (entry = 0; entry < authData->sf.keyboard.responseCount; entry++) { + c32toa(authData->sf.keyboard.responseLengths[entry], output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, authData->sf.keyboard.responses[entry], + authData->sf.keyboard.responseLengths[entry]); + begin += authData->sf.keyboard.responseLengths[entry]; + } + *idx = begin; + } + + return ret; +} #ifndef WOLFSSH_NO_RSA static int PrepareUserAuthRequestRsa(WOLFSSH* ssh, word32* payloadSz, @@ -13974,6 +14434,92 @@ static int BuildUserAuthRequestPublicKey(WOLFSSH* ssh, #endif +int SendUserAuthKeyboardResponse(WOLFSSH* ssh) +{ + byte* output; + int ret = WS_SUCCESS; + word32 idx; + word32 payloadSz = 0; + word32 prompt; + WS_UserAuthData authData; + + WLOG(WS_LOG_DEBUG, "Entering SendUserAuthKeyboardResponse()"); + + authData.type = WOLFSSH_USERAUTH_KEYBOARD; + authData.username = (const byte*)ssh->userName; + authData.usernameSz = ssh->userNameSz; + authData.sf.keyboard.promptCount = ssh->kbAuth.promptCount; + authData.sf.keyboard.promptName = ssh->kbAuth.promptName; + authData.sf.keyboard.promptNameSz = + (word32)WSTRLEN((char*)ssh->kbAuth.promptName); + authData.sf.keyboard.promptInstruction = ssh->kbAuth.promptInstruction; + authData.sf.keyboard.promptInstructionSz = + (word32)WSTRLEN((char*)ssh->kbAuth.promptInstruction); + authData.sf.keyboard.promptLanguage = ssh->kbAuth.promptLanguage; + authData.sf.keyboard.promptLanguageSz = + (word32)WSTRLEN((char*)ssh->kbAuth.promptLanguage); + authData.sf.keyboard.prompts = ssh->kbAuth.prompts; + authData.sf.keyboard.promptEcho = ssh->kbAuth.promptEcho; + authData.sf.keyboard.responseCount = 0; + + WLOG(WS_LOG_DEBUG, "SUAR: Calling the userauth callback"); + ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_KEYBOARD, &authData, + ssh->userAuthCtx); + + WFREE(ssh->kbAuth.promptName, ssh->ctx->heap, 0); + WFREE(ssh->kbAuth.promptInstruction, ssh->ctx->heap, 0); + WFREE(ssh->kbAuth.promptLanguage, ssh->ctx->heap, 0); + WFREE(ssh->kbAuth.promptEcho, ssh->ctx->heap, 0); + for (prompt = 0; prompt < ssh->kbAuth.promptCount; prompt++) { + WFREE((void*)ssh->kbAuth.prompts[prompt], ssh->ctx->heap, 0); + } + WFREE(ssh->kbAuth.prompts, ssh->ctx->heap, 0); + + if (ret != WOLFSSH_USERAUTH_SUCCESS) { + WLOG(WS_LOG_DEBUG, "SUAR: Couldn't get keyboard auth"); + ret = WS_FATAL_ERROR; + } + else if (ssh->kbAuth.promptCount != authData.sf.keyboard.responseCount) { + WLOG(WS_LOG_DEBUG, + "SUAR: Keyboard auth response count does not match request count"); + ret = WS_USER_AUTH_E; + } + else { + WLOG(WS_LOG_DEBUG, "SUAR: Callback successful keyboard"); + } + + payloadSz = MSG_ID_SZ; + + ret = PrepareUserAuthResponseKeyboard(ssh, &payloadSz, &authData); + if (ret == WS_SUCCESS) + ret = PreparePacket(ssh, payloadSz); + + output = ssh->outputBuffer.buffer; + idx = ssh->outputBuffer.length; + + output[idx++] = MSGID_USERAUTH_INFO_RESPONSE; + + if (ret == WS_SUCCESS) + ret = BuildUserAuthResponseKeyboard(ssh, output, &idx, &authData); + + if (ret == WS_SUCCESS) { + ssh->outputBuffer.length = idx; + ret = BundlePacket(ssh); + } + + if (ret == WS_SUCCESS) { + ret = wolfSSH_SendPacket(ssh); + } + + if (ret != WS_WANT_WRITE && ret != WS_SUCCESS) + PurgePacket(ssh); + + ForceZero(&authData, sizeof(WS_UserAuthData)); + + WLOG(WS_LOG_DEBUG, "Leaving SendUserAuthKeyboardResponse(), ret = %d", ret); + + return ret; +} int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig) { @@ -14007,8 +14553,12 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig) WMEMSET(keySig_ptr, 0, sizeof(WS_KeySignature)); keySig_ptr->keySigId = ID_NONE; keySig_ptr->heap = ssh->ctx->heap; - - if (ssh->ctx->userAuthCb != NULL) { + /* Callback happens later for keyboard auth */ + if (authType & WOLFSSH_USERAUTH_KEYBOARD) { + authId = ID_USERAUTH_KEYBOARD; + authType = WOLFSSH_USERAUTH_KEYBOARD; + } + else if (ssh->ctx->userAuthCb != NULL) { WLOG(WS_LOG_DEBUG, "SUAR: Calling the userauth callback"); WMEMSET(&authData, 0, sizeof(authData)); @@ -14031,9 +14581,11 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig) authData.type = authType; } } - /* fall into public key case if password case was not successful */ + /* fall into public key case if keyboard or password case was not + * successful */ if ((ret == WS_FATAL_ERROR || - !(authType & WOLFSSH_USERAUTH_PASSWORD)) && + (!(authType & WOLFSSH_USERAUTH_PASSWORD) && + !(authType & WOLFSSH_USERAUTH_KEYBOARD))) && (authType & WOLFSSH_USERAUTH_PUBLICKEY)) { ret = ssh->ctx->userAuthCb(WOLFSSH_USERAUTH_PUBLICKEY, &authData, ssh->userAuthCtx); @@ -14071,7 +14623,8 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig) ret = PrepareUserAuthRequestPublicKey(ssh, &payloadSz, &authData, keySig_ptr); } - else if (authId != ID_NONE && !ssh->userAuthPkDone) + else if (authId != ID_NONE && authId != ID_USERAUTH_KEYBOARD && + !ssh->userAuthPkDone) ret = WS_INVALID_ALGO_ID; } @@ -14110,6 +14663,14 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig) ret = BuildUserAuthRequestPassword(ssh, output, &idx, &authData); } + else if (authId == ID_USERAUTH_KEYBOARD) { + /* language tag, deprecated, should be empty */ + c32toa(0, output + idx); + idx += LENGTH_SZ; + /* submethods */ + c32toa(0, output + idx); + idx += LENGTH_SZ; + } else if (authId == ID_USERAUTH_PUBLICKEY) ret = BuildUserAuthRequestPublicKey(ssh, output, &idx, &authData, sigStart, sigStartIdx, keySig_ptr); @@ -14147,6 +14708,9 @@ static int GetAllowedAuth(WOLFSSH* ssh, char* authStr) int typeAllowed = 0; typeAllowed |= WOLFSSH_USERAUTH_PASSWORD; + if (ssh->ctx->keyboardAuthCb != NULL) { + typeAllowed |= WOLFSSH_USERAUTH_KEYBOARD; + } #if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) typeAllowed |= WOLFSSH_USERAUTH_PUBLICKEY; #endif @@ -14166,6 +14730,10 @@ static int GetAllowedAuth(WOLFSSH* ssh, char* authStr) WSTRNCAT(authStr, "password,", MAX_AUTH_STRING-1); } + if (typeAllowed & WOLFSSH_USERAUTH_KEYBOARD) { + WSTRNCAT(authStr, "keyboard-interactive,", MAX_AUTH_STRING-1); + } + /* remove last comma from the list */ return (int)XSTRLEN(authStr) - 1; } diff --git a/src/ssh.c b/src/ssh.c index 4fdff0c77..1f43d76ff 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -840,6 +840,24 @@ int wolfSSH_connect(WOLFSSH* ssh) return WS_FATAL_ERROR; } } + + while (ssh->serverState == SERVER_USERAUTH_ACCEPT_KEYBOARD) { + if ( (ssh->error = SendUserAuthKeyboardResponse(ssh)) < + WS_SUCCESS) { + WLOG(WS_LOG_DEBUG, connectError, "CLIENT_USERAUTH_SENT", + ssh->error); + return WS_FATAL_ERROR; + } + ssh->serverState = SERVER_USERAUTH_ACCEPT_KEYBOARD_NEXT; + while ((ssh->serverState < SERVER_USERAUTH_ACCEPT_KEYBOARD_DONE) && (ssh->serverState != SERVER_USERAUTH_ACCEPT_KEYBOARD) && (ssh->serverState != SERVER_USERAUTH_ACCEPT_DONE)) { + if (DoReceive(ssh) < WS_SUCCESS) { + WLOG(WS_LOG_DEBUG, connectError, + "CLIENT_USERAUTH_SENT", ssh->error); + return WS_FATAL_ERROR; + } + } + } + ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE; WLOG(WS_LOG_DEBUG, connectState, "SERVER_USERAUTH_ACCEPT_DONE"); NO_BREAK; @@ -1288,6 +1306,20 @@ int wolfSSH_SendDisconnect(WOLFSSH *ssh, word32 reason) return SendDisconnect(ssh, reason); } +void wolfSSH_SetKeyboardAuthPrompts(WOLFSSH_CTX* ctx, + WS_CallbackKeyboardAuthPrompts cb) +{ + if (ctx != NULL) { + ctx->keyboardAuthCb = cb; + } +} + +void wolfSSH_SetKeyboardAuthCtx(WOLFSSH* ssh, void* keyboardAuthCtx) +{ + if (ssh != NULL) { + ssh->keyboardAuthCtx = keyboardAuthCtx; + } +} void wolfSSH_SetUserAuth(WOLFSSH_CTX* ctx, WS_CallbackUserAuth cb) { diff --git a/tests/api.c b/tests/api.c index ce6a02ab7..a5a753cec 100644 --- a/tests/api.c +++ b/tests/api.c @@ -1555,6 +1555,150 @@ static void test_wolfSSH_QueryAlgoList(void) AssertIntEQ(WS_INVALID_ALGO_ID, k); } +#if defined(WOLFSSH_SFTP) && !defined(NO_WOLFSSH_CLIENT) && \ + !defined(SINGLE_THREADED) + +static byte* kbResponse = (byte*)"test"; +static word32 kbResponseLength = 4; + +static int keyboardUserAuth(byte authType, WS_UserAuthData* authData, void* ctx) +{ + (void) ctx; + int ret = WOLFSSH_USERAUTH_INVALID_AUTHTYPE; + + if (authType == WOLFSSH_USERAUTH_KEYBOARD) { + AssertIntEQ(1, authData->sf.keyboard.promptCount); + AssertStrEQ("KB Auth Password: ", authData->sf.keyboard.prompts[0]); + + authData->sf.keyboard.responseCount = 1; + authData->sf.keyboard.responseLengths = &kbResponseLength; + authData->sf.keyboard.responses = (byte**)&kbResponse; + ret = WS_SUCCESS; + } + return ret; +} + + +static void keyboard_client_connect(WOLFSSH_CTX** ctx, WOLFSSH** ssh, int port) +{ + SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID; + SOCKADDR_IN_T clientAddr; + socklen_t clientAddrSz = sizeof(clientAddr); + int ret; + char* host = (char*)wolfSshIp; + const char* username = "test"; + + if (ctx == NULL || ssh == NULL) { + return; + } + + *ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (*ctx == NULL) { + return; + } + + wolfSSH_SetUserAuth(*ctx, keyboardUserAuth); + *ssh = wolfSSH_new(*ctx); + if (*ssh == NULL) { + wolfSSH_CTX_free(*ctx); + *ctx = NULL; + return; + } + + build_addr(&clientAddr, host, port); + tcp_socket(&sockFd, ((struct sockaddr_in *)&clientAddr)->sin_family); + ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz); + if (ret != 0){ + wolfSSH_free(*ssh); + wolfSSH_CTX_free(*ctx); + *ctx = NULL; + *ssh = NULL; + return; + } + + ret = wolfSSH_SetUsername(*ssh, username); + if (ret == WS_SUCCESS) + ret = wolfSSH_set_fd(*ssh, (int)sockFd); + + if (ret == WS_SUCCESS) + ret = wolfSSH_connect(*ssh); + + if (ret != WS_SUCCESS){ + wolfSSH_free(*ssh); + wolfSSH_CTX_free(*ctx); + *ctx = NULL; + *ssh = NULL; + return; + } +} + +static void test_wolfSSH_KeyboardInteractive(void) +{ + func_args ser; + tcp_ready ready; + int argsCount; + WS_SOCKET_T clientFd; + + const char* args[10]; + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + + THREAD_TYPE serThread; + + WMEMSET(&ser, 0, sizeof(func_args)); + + argsCount = 0; + args[argsCount++] = "."; + args[argsCount++] = "-1"; + args[argsCount++] = "-i"; + args[argsCount++] = "test:test"; +#ifndef USE_WINDOWS_API + args[argsCount++] = "-p"; + args[argsCount++] = "0"; +#endif + ser.argv = (char**)args; + ser.argc = argsCount; + ser.signal = &ready; + InitTcpReady(ser.signal); + ThreadStart(echoserver_test, (void*)&ser, &serThread); + WaitTcpReady(&ready); + + keyboard_client_connect(&ctx, &ssh, ready.port); + AssertNotNull(ctx); + AssertNotNull(ssh); + + + argsCount = wolfSSH_shutdown(ssh); + if (argsCount == WS_SOCKET_ERROR_E) { + /* If the socket is closed on shutdown, peer is gone, this is OK. */ + argsCount = WS_SUCCESS; + } + +#if DEFAULT_HIGHWATER_MARK < 8000 + if (argsCount == WS_REKEYING) { + /* in cases where highwater mark is really small a re-key could happen */ + argsCount = WS_SUCCESS; + } +#endif + + AssertIntEQ(argsCount, WS_SUCCESS); + + /* close client socket down */ + clientFd = wolfSSH_get_fd(ssh); + WCLOSESOCKET(clientFd); + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +#ifdef WOLFSSH_ZEPHYR + /* Weird deadlock without this sleep */ + k_sleep(Z_TIMEOUT_TICKS(100)); +#endif + ThreadJoin(serThread); +} + +#else /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */ +static void test_wolfSSH_KeyboardInteractive(void) { ; } +#endif /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */ #endif /* WOLFSSH_TEST_BLOCK */ @@ -1591,6 +1735,7 @@ int wolfSSH_ApiTest(int argc, char** argv) test_wolfSSH_ReadKey(); test_wolfSSH_QueryAlgoList(); test_wolfSSH_SetAlgoList(); + test_wolfSSH_KeyboardInteractive(); /* SCP tests */ test_wolfSSH_SCP_CB(); diff --git a/tests/auth.c b/tests/auth.c new file mode 100644 index 000000000..214516998 --- /dev/null +++ b/tests/auth.c @@ -0,0 +1,569 @@ +/* auth.c + * + * Copyright (C) 2025 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 + +#ifdef WOLFSSL_USER_SETTINGS + #include +#else + #include +#endif +#include +#include + +#include +#include +#include +#define WOLFSSH_TEST_CLIENT +#define WOLFSSH_TEST_SERVER +#define WOLFSSH_TEST_LOCKING +#ifndef SINGLE_THREADED + #define WOLFSSH_TEST_THREADING +#endif +#include +#include "tests/auth.h" + +#ifndef WOLFSSH_NO_ABORT + #define WABORT() abort() +#else + #define WABORT() +#endif + +#define PrintError(description, result) do { \ + printf("\nERROR - %s line %d failed with:", __FILE__, __LINE__); \ + printf("\n expected: "); printf description; \ + printf("\n result: "); printf result; printf("\n\n"); \ +} while(0) + +#ifdef WOLFSSH_ZEPHYR +#define Fail(description, result) do { \ + PrintError(description, result); \ + WABORT(); \ +} while(0) +#else +#define Fail(description, result) do { \ + PrintError(description, result); \ + WFFLUSH(stdout); \ + WABORT(); \ +} while(0) +#endif + +#define Assert(test, description, result) if (!(test)) Fail(description, result) + +#define AssertTrue(x) Assert( (x), ("%s is true", #x), (#x " => FALSE")) +#define AssertFalse(x) Assert(!(x), ("%s is false", #x), (#x " => TRUE")) +#define AssertNotNull(x) Assert( (x), ("%s is not null", #x), (#x " => NULL")) + +#define AssertNull(x) do { \ + PEDANTIC_EXTENSION void* _x = (void*)(x); \ + \ + Assert(!_x, ("%s is null", #x), (#x " => %p", _x)); \ +} while(0) + +#define AssertInt(x, y, op, er) do { \ + int _x = (int)(x); \ + int _y = (int)(y); \ + Assert(_x op _y, ("%s " #op " %s", #x, #y), ("%d " #er " %d", _x, _y)); \ +} while(0) + +#define AssertIntEQ(x, y) AssertInt(x, y, ==, !=) +#define AssertIntNE(x, y) AssertInt(x, y, !=, ==) +#define AssertIntGT(x, y) AssertInt(x, y, >, <=) +#define AssertIntLT(x, y) AssertInt(x, y, <, >=) +#define AssertIntGE(x, y) AssertInt(x, y, >=, <) +#define AssertIntLE(x, y) AssertInt(x, y, <=, >) + +#define AssertStr(x, y, op, er) do { \ + const char* _x = (const char*)(x); \ + const char* _y = (const char*)(y); \ + int _z = (_x && _y) ? strcmp(_x, _y) : -1; \ + Assert(_z op 0, ("%s " #op " %s", #x, #y), \ + ("\"%s\" " #er " \"%s\"", _x, _y));\ +} while(0) + +#define AssertStrEQ(x, y) AssertStr(x, y, ==, !=) +#define AssertStrNE(x, y) AssertStr(x, y, !=, ==) +#define AssertStrGT(x, y) AssertStr(x, y, >, <=) +#define AssertStrLT(x, y) AssertStr(x, y, <, >=) +#define AssertStrGE(x, y) AssertStr(x, y, >=, <) +#define AssertStrLE(x, y) AssertStr(x, y, <=, >) + +#define AssertPtr(x, y, op, er) do { \ + PRAGMA_GCC_DIAG_PUSH \ + /* remarkably, without this inhibition, */ \ + /* the _Pragma()s make the declarations warn. */ \ + PRAGMA_GCC("GCC diagnostic ignored \"-Wdeclaration-after-statement\"") \ + /* inhibit "ISO C forbids conversion of function pointer */ \ + /* to object pointer type [-Werror=pedantic]" */ \ + PRAGMA_GCC("GCC diagnostic ignored \"-Wpedantic\"") \ + void* _x = (void*)(x); \ + void* _y = (void*)(y); \ + Assert(_x op _y, ("%s " #op " %s", #x, #y), ("%p " #er " %p", _x, _y)); \ + PRAGMA_GCC_DIAG_POP; \ +} while(0) + +#define AssertPtrEq(x, y) AssertPtr(x, y, ==, !=) +#define AssertPtrNE(x, y) AssertPtr(x, y, !=, ==) +#define AssertPtrGT(x, y) AssertPtr(x, y, >, <=) +#define AssertPtrLT(x, y) AssertPtr(x, y, <, >=) +#define AssertPtrGE(x, y) AssertPtr(x, y, >=, <) +#define AssertPtrLE(x, y) AssertPtr(x, y, <=, >) + +#define ES_ERROR(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + serverArgs->return_code = EXIT_FAILURE; \ + WOLFSSL_RETURN_FROM_THREAD(0); \ +} while(0) + +#define EXAMPLE_KEYLOAD_BUFFER_SZ 1200 + +#ifdef WOLFSSH_NO_ECDSA_SHA2_NISTP256 + #define ECC_PATH "./keys/server-key-ecc-521.der" +#else + #define ECC_PATH "./keys/server-key-ecc.der" +#endif + + +#if !defined(NO_WOLFSSH_SERVER) && !defined(NO_WOLFSSH_CLIENT) && \ + !defined(SINGLE_THREADED) && !defined(WOLFSSH_TEST_BLOCK) && \ + !defined(NO_FILESYSTEM) + +const char *testText1 = "test"; +const char *testText2 = "password"; + +byte *kbResponses[4]; +word32 kbResponseLengths[4]; +word32 kbResponseCount; +byte kbMultiRound = 0; +byte currentRound = 0; + +WS_UserAuthData_Keyboard promptData; + + +static int load_file(const char* fileName, byte* buf, word32* bufSz) +{ + WFILE* file; + word32 fileSz; + word32 readSz; + + if (fileName == NULL) return 0; + + if (WFOPEN(NULL, &file, fileName, "rb") != 0) + return 0; + WFSEEK(NULL, file, 0, WSEEK_END); + fileSz = (word32)WFTELL(NULL, file); + WREWIND(NULL, file); + + if (buf == NULL || fileSz > *bufSz) { + *bufSz = fileSz; + WFCLOSE(NULL, file); + return 0; + } + + readSz = (word32)WFREAD(NULL, buf, 1, fileSz, file); + WFCLOSE(NULL, file); + + if (readSz < fileSz) { + fileSz = 0; + } + + return fileSz; +} + +static int load_key(byte isEcc, byte* buf, word32 bufSz) +{ + word32 sz = 0; + +#ifndef NO_FILESYSTEM + const char* bufName; + bufName = isEcc ? ECC_PATH : "./keys/server-key-rsa.der" ; + sz = load_file(bufName, buf, &bufSz); +#else + /* using buffers instead */ + if (isEcc) { + if ((word32)sizeof_ecc_key_der_256 > bufSz) { + return 0; + } + WMEMCPY(buf, ecc_key_der_256, sizeof_ecc_key_der_256); + sz = sizeof_ecc_key_der_256; + } + else { + if ((word32)sizeof_rsa_key_der_2048 > bufSz) { + return 0; + } + WMEMCPY(buf, (byte*)rsa_key_der_2048, sizeof_rsa_key_der_2048); + sz = sizeof_rsa_key_der_2048; + } +#endif + + return sz; +} + + +static int serverUserAuth(byte authType, WS_UserAuthData* authData, void* ctx) +{ + (void) ctx; + if (authType != WOLFSSH_USERAUTH_KEYBOARD) { + return WOLFSSH_USERAUTH_FAILURE; + } + + if (authData->sf.keyboard.responseCount != kbResponseCount) { + return WOLFSSH_USERAUTH_FAILURE; + } + + for (word32 resp = 0; resp < kbResponseCount; resp++) { + if (authData->sf.keyboard.responseLengths[resp] != + kbResponseLengths[resp]) { + return WOLFSSH_USERAUTH_FAILURE; + + } + if (WSTRCMP((const char*)authData->sf.keyboard.responses[resp], + (const char*)kbResponses[resp]) != 0) { + return WOLFSSH_USERAUTH_FAILURE; + } + } + if (kbMultiRound && currentRound == 0) { + currentRound++; + kbResponses[0] = (byte*)testText2; + kbResponseLengths[0] = 8; + return WOLFSSH_USERAUTH_SUCCESS_ANOTHER; + } + return WOLFSSH_USERAUTH_SUCCESS; +} + +static int serverKeyboardCallback(WS_UserAuthData_Keyboard *kbAuth, void *ctx) +{ + (void) ctx; + WMEMCPY(kbAuth, &promptData, sizeof(WS_UserAuthData_Keyboard)); + + return WS_SUCCESS; +} + +static INLINE void SignalTcpReady(tcp_ready* ready, word16 port) +{ + pthread_mutex_lock(&ready->mutex); + ready->ready = 1; + ready->port = port; + pthread_cond_signal(&ready->cond); + pthread_mutex_unlock(&ready->mutex); +} + +static THREAD_RETURN WOLFSSH_THREAD server_thread(void* args) +{ + thread_args* serverArgs; + int ret; + word16 port = 0; + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + byte buf[EXAMPLE_KEYLOAD_BUFFER_SZ]; + byte* keyLoadBuf; + int peerEcc = 1; + word32 bufSz; + WS_SOCKET_T listenFd = WOLFSSH_SOCKET_INVALID; + WS_SOCKET_T clientFd = WOLFSSH_SOCKET_INVALID; + SOCKADDR_IN_T clientAddr; + socklen_t clientAddrSz = sizeof(clientAddr); + + serverArgs = (thread_args*) args; + serverArgs->return_code = EXIT_SUCCESS; + + promptData.promptCount = kbResponseCount; + promptData.promptName = NULL; + promptData.promptNameSz = 0; + promptData.promptInstruction = NULL; + promptData.promptInstructionSz = 0; + promptData.promptLanguage = NULL; + promptData.promptLanguageSz = 0; + if (kbResponseCount) { + promptData.prompts = WMALLOC(sizeof(char*) * kbResponseCount, NULL, 0); + promptData.promptLengths = WMALLOC(sizeof(word32) * kbResponseCount, NULL, 0); + promptData.promptEcho = WMALLOC(sizeof(byte) * kbResponseCount, NULL, 0); + for (word32 prompt = 0; prompt < kbResponseCount; prompt++) { + promptData.prompts[prompt] = (byte*)"Password: "; + promptData.promptLengths[prompt] = 10; + promptData.promptEcho[prompt] = 0; + } + } + else { + promptData.prompts = NULL; + promptData.promptLengths = NULL; + promptData.promptEcho = NULL; + } + + + tcp_listen(&listenFd, &port, 1); + SignalTcpReady(serverArgs->signal, port); + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (ctx == NULL) { + ES_ERROR("Couldn't allocate SSH CTX data.\n"); + } + + wolfSSH_SetUserAuth(ctx, serverUserAuth); + wolfSSH_SetKeyboardAuthPrompts(ctx, serverKeyboardCallback); + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { + ES_ERROR("Couldn't allocate SSH data.\n"); + } + keyLoadBuf = buf; + 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"); + } //wolfSSH_SetUserAuthCtx(ssh, &pwMapList); + //wolfSSH_SetKeyboardAuthCtx(ssh, &promptData); + + clientFd = accept(listenFd, (struct sockaddr*)&clientAddr, &clientAddrSz); + if (clientFd == -1) { + ES_ERROR("tcp accept failed"); + } + wolfSSH_set_fd(ssh, (int)clientFd); + + ret = wolfSSH_accept(ssh); + if (ret) { + ES_ERROR("wolfSSH Accept Error"); + } + + ret = wolfSSH_shutdown(ssh); + + if (promptData.promptCount > 0) { + WFREE(promptData.promptLengths, NULL, 0); + WFREE(promptData.prompts, NULL, 0); + WFREE(promptData.promptEcho, NULL, 0); + } + + if (wolfSSH_Cleanup() != WS_SUCCESS) { + ES_ERROR("Couldn't clean up wolfSSH.\n"); + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} + + +static int keyboardUserAuth(byte authType, WS_UserAuthData* authData, void* ctx) +{ + (void) ctx; + int ret = WOLFSSH_USERAUTH_INVALID_AUTHTYPE; + + if (authType == WOLFSSH_USERAUTH_KEYBOARD) { + AssertIntEQ(kbResponseCount, authData->sf.keyboard.promptCount); + for (word32 prompt = 0; prompt < kbResponseCount; prompt++) { + AssertStrEQ("Password: ", authData->sf.keyboard.prompts[prompt]); + } + + authData->sf.keyboard.responseCount = kbResponseCount; + authData->sf.keyboard.responseLengths = kbResponseLengths; + authData->sf.keyboard.responses = (byte**)kbResponses; + ret = WS_SUCCESS; + } + return ret; +} + +static int basic_client_connect(WOLFSSH_CTX** ctx, WOLFSSH** ssh, int port) +{ + SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID; + SOCKADDR_IN_T clientAddr; + socklen_t clientAddrSz = sizeof(clientAddr); + int ret = WS_SUCCESS; + char* host = (char*)wolfSshIp; + const char* username = "test"; + + if (ctx == NULL || ssh == NULL) { + return WS_BAD_ARGUMENT; + } + + *ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (*ctx == NULL) { + return WS_BAD_ARGUMENT; + } + + wolfSSH_SetUserAuth(*ctx, keyboardUserAuth); + *ssh = wolfSSH_new(*ctx); + if (*ssh == NULL) { + wolfSSH_CTX_free(*ctx); + *ctx = NULL; + return WS_MEMORY_E; + } + + build_addr(&clientAddr, host, port); + tcp_socket(&sockFd, ((struct sockaddr_in *)&clientAddr)->sin_family); + ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz); + if (ret != 0){ + wolfSSH_free(*ssh); + wolfSSH_CTX_free(*ctx); + *ctx = NULL; + *ssh = NULL; + return ret; + } + + ret = wolfSSH_SetUsername(*ssh, username); + if (ret == WS_SUCCESS) + ret = wolfSSH_set_fd(*ssh, (int)sockFd); + + if (ret == WS_SUCCESS) + ret = wolfSSH_connect(*ssh); + + if (ret != WS_SUCCESS){ + wolfSSH_free(*ssh); + wolfSSH_CTX_free(*ctx); + *ctx = NULL; + *ssh = NULL; + } + return ret; +} + +static void test_client(void) +{ + int ret; + thread_args serverArgs; + tcp_ready ready; + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + THREAD_TYPE serThread; + WS_SOCKET_T clientFd; + + serverArgs.signal = &ready; + InitTcpReady(serverArgs.signal); + ThreadStart(server_thread, (void*)&serverArgs, &serThread); + WaitTcpReady(&ready); + + ret = basic_client_connect(&ctx, &ssh, ready.port); + + if (ret == WS_SUCCESS) { + AssertNotNull(ctx); + AssertNotNull(ssh); + ret = wolfSSH_shutdown(ssh); + if (ret == WS_SOCKET_ERROR_E) { + /* fine on shutdown */ + ret = WS_SUCCESS; + } + } + AssertIntEQ(ret, WS_SUCCESS); + + /* close client socket down */ + clientFd = wolfSSH_get_fd(ssh); + WCLOSESOCKET(clientFd); + + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + + ThreadJoin(serThread); + AssertIntEQ(serverArgs.return_code, WS_SUCCESS); +} + +static void test_basic_KeyboardInteractive(void) +{ + printf("Testing single prompt / response\n"); + kbResponses[0] = (byte*)testText1; + kbResponseLengths[0] = 4; + kbResponseCount = 1; + + test_client(); +} + +static void test_empty_KeyboardInteractive(void) +{ + printf("Testing empty prompt / no response\n"); + kbResponses[0] = NULL; + kbResponseLengths[0] = 0; + kbResponseCount = 0; + + test_client(); +} + +static void test_multi_prompt_KeyboardInteractive(void) +{ + printf("Testing multiple prompts\n"); + kbResponses[0] = (byte*)testText1; + kbResponses[1] = (byte*)testText2; + kbResponseLengths[0] = 4; + kbResponseLengths[1] = 8; + kbResponseCount = 2; + + test_client(); +} + +static void test_multi_round_KeyboardInteractive(void) +{ + printf("Testing mutliple prompt rounds\n"); + kbResponses[0] = (byte*)testText1; + kbResponseLengths[0] = 4; + kbResponseCount = 1; + kbMultiRound = 1; + + test_client(); + AssertIntEQ(currentRound, 1); + currentRound = 0; + kbMultiRound = 0; +} + +#endif /* WOLFSSH_TEST_BLOCK */ + +int wolfSSH_AuthTest(int argc, char** argv) +{ + (void) argc; + (void) argv; + +#if defined(NO_WOLFSSH_SERVER) || defined(NO_WOLFSSH_CLIENT) || \ + defined(SINGLE_THREADED) || defined(WOLFSSH_TEST_BLOCK) || \ + defined(NO_FILESYSTEM) + return 77; +#else + AssertIntEQ(wolfSSH_Init(), WS_SUCCESS); + + //wolfSSH_Debugging_ON(); + + + #if defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,2) + { + int i; + for (i = 0; i < FIPS_CAST_COUNT; i++) { + AssertIntEQ(wc_RunCast_fips(i), WS_SUCCESS); + } + } + #endif /* HAVE_FIPS */ + + /* Add test calls here */ + test_basic_KeyboardInteractive(); + test_empty_KeyboardInteractive(); + test_multi_prompt_KeyboardInteractive(); + test_multi_round_KeyboardInteractive(); + + AssertIntEQ(wolfSSH_Cleanup(), WS_SUCCESS); + + return 0; +#endif +} + +#ifndef NO_AUTHTEST_MAIN_DRIVER +int main(int argc, char** argv) +{ + return wolfSSH_AuthTest(argc, argv); +} +#endif + + diff --git a/tests/auth.h b/tests/auth.h new file mode 100644 index 000000000..450779fcd --- /dev/null +++ b/tests/auth.h @@ -0,0 +1,33 @@ +/* auth.h + * + * Copyright (C) 2025 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 . + */ + +#ifndef _WOLFSSH_TESTS_AUTH_H_ +#define _WOLFSSH_TESTS_AUTH_H_ + +#include + +int wolfSSH_AuthTest(int argc, char** argv); + +typedef struct thread_args { + int return_code; + tcp_ready* signal; +} thread_args; + +#endif /* _WOLFSSH_TESTS_AUTH_H_ */ diff --git a/tests/include.am b/tests/include.am index e8b1b548e..6c8cba989 100644 --- a/tests/include.am +++ b/tests/include.am @@ -3,9 +3,9 @@ # All paths should be given relative to the root check_PROGRAMS += tests/unit.test tests/api.test \ - tests/testsuite.test + tests/testsuite.test tests/auth.test noinst_PROGRAMS += tests/unit.test tests/api.test \ - tests/testsuite.test + tests/testsuite.test tests/auth.test tests_unit_test_SOURCES = tests/unit.c tests/unit.h tests_unit_test_CPPFLAGS = -DNO_MAIN_DRIVER @@ -100,3 +100,34 @@ tests_testsuite_test_CPPFLAGS += -DWOLFSSH_CERTS endif tests_testsuite_test_LDADD = src/libwolfssh.la tests_testsuite_test_DEPENDENCIES = src/libwolfssh.la + + +tests_auth_test_SOURCES = tests/auth.c tests/auth.h +tests_auth_test_CPPFLAGS = -DNO_MAIN_DRIVER +if BUILD_KEYGEN +tests_auth_test_CPPFLAGS += -DWOLFSSH_KEYGEN +endif +if BUILD_SCP +tests_auth_test_CPPFLAGS += -DWOLFSSH_SCP +endif +if BUILD_SFTP +tests_auth_test_CPPFLAGS += -DWOLFSSH_SFTP +endif +if BUILD_TERM +tests_auth_test_CPPFLAGS += -DWOLFSSH_TERM +endif +if BUILD_SHELL +tests_auth_test_CPPFLAGS += -DWOLFSSH_SHELL +endif +if BUILD_AGENT +tests_auth_test_CPPFLAGS += -DWOLFSSH_AGENT +endif +if BUILD_FWD +tests_auth_test_CPPFLAGS += -DWOLFSSH_FWD +endif +if BUILD_CERTS +tests_auth_test_CPPFLAGS += -DWOLFSSH_CERTS +endif +tests_auth_test_LDADD = src/libwolfssh.la +tests_auth_test_DEPENDENCIES = src/libwolfssh.la + diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 84a8d17d4..13cf7191a 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -353,6 +353,7 @@ enum { /* UserAuth IDs */ ID_USERAUTH_PASSWORD, ID_USERAUTH_PUBLICKEY, + ID_USERAUTH_KEYBOARD, /* Channel Type IDs */ ID_CHANTYPE_SESSION, @@ -504,6 +505,7 @@ struct WOLFSSH_CTX { WS_CallbackUserAuth userAuthCb; /* User Authentication Callback */ WS_CallbackUserAuthTypes userAuthTypesCb; /* Authentication Types Allowed */ WS_CallbackUserAuthResult userAuthResultCb; /* User Authentication Result */ + WS_CallbackKeyboardAuthPrompts keyboardAuthCb; /* Keyboard auth prompts */ WS_CallbackHighwater highwaterCb; /* Data Highwater Mark Callback */ WS_CallbackGlobalReq globalReqCb; /* Global Request Callback */ WS_CallbackReqSuccess reqSuccessCb; /* Global Request Success Callback */ @@ -707,7 +709,7 @@ struct WOLFSSH { byte processReplyState; byte isKeying; byte authId; /* if using public key or password */ - byte supportedAuth[3]; /* supported auth IDs public key , password */ + byte supportedAuth[4]; /* supported auth IDs public key , password */ #ifdef WOLFSSH_SCP byte scpState; @@ -799,6 +801,7 @@ struct WOLFSSH { void* userAuthCtx; void* userAuthResultCtx; + void* keyboardAuthCtx; char* userName; word32 userNameSz; char* password; @@ -887,6 +890,7 @@ struct WOLFSSH { word32 exitStatus; #endif void* keyingCompletionCtx; + WS_UserAuthData_Keyboard kbAuth; }; @@ -995,6 +999,8 @@ WOLFSSH_LOCAL int SendServiceRequest(WOLFSSH*, byte); WOLFSSH_LOCAL int SendServiceAccept(WOLFSSH*, byte); WOLFSSH_LOCAL int SendExtInfo(WOLFSSH* ssh); WOLFSSH_LOCAL int SendUserAuthRequest(WOLFSSH*, byte, int); +WOLFSSH_LOCAL int SendUserAuthKeyboardResponse(WOLFSSH*); +WOLFSSH_LOCAL int SendUserAuthKeyboardRequest(WOLFSSH*, WS_UserAuthData*); WOLFSSH_LOCAL int SendUserAuthSuccess(WOLFSSH*); WOLFSSH_LOCAL int SendUserAuthFailure(WOLFSSH*, byte); WOLFSSH_LOCAL int SendUserAuthBanner(WOLFSSH*); @@ -1095,6 +1101,9 @@ enum ServerStates { SERVER_KEXINIT_DONE, SERVER_USERAUTH_REQUEST_DONE, SERVER_USERAUTH_ACCEPT_DONE, + SERVER_USERAUTH_ACCEPT_KEYBOARD, + SERVER_USERAUTH_ACCEPT_KEYBOARD_NEXT, + SERVER_USERAUTH_ACCEPT_KEYBOARD_DONE, SERVER_CHANNEL_OPEN_DONE, SERVER_DONE }; @@ -1143,6 +1152,8 @@ enum WS_MessageIds { MSGID_USERAUTH_BANNER = 53, MSGID_USERAUTH_PK_OK = 60, /* Public Key OK */ MSGID_USERAUTH_PW_CHRQ = 60, /* Password Change Request */ + MSGID_USERAUTH_INFO_REQUEST = 60, + MSGID_USERAUTH_INFO_RESPONSE = 61, MSGID_GLOBAL_REQUEST = 80, MSGID_REQUEST_SUCCESS = 81, diff --git a/wolfssh/ssh.h b/wolfssh/ssh.h index 8f6a2a115..40082d6ca 100644 --- a/wolfssh/ssh.h +++ b/wolfssh/ssh.h @@ -292,6 +292,22 @@ typedef struct WS_UserAuthData_Password { word32 newPasswordSz; } WS_UserAuthData_Password; +typedef struct WS_UserAuthData_Keyboard { + word32 promptCount; + word32 responseCount; + word32 promptNameSz; + word32 promptInstructionSz; + word32 promptLanguageSz; + byte* promptName; + byte* promptInstruction; + byte* promptLanguage; + word32* promptLengths; + word32* responseLengths; + byte* promptEcho; + byte** responses; + byte** prompts; +} WS_UserAuthData_Keyboard; + typedef struct WS_UserAuthData_PublicKey { const byte* dataToSign; const byte* publicKeyType; @@ -317,6 +333,7 @@ typedef struct WS_UserAuthData { union { WS_UserAuthData_Password password; WS_UserAuthData_PublicKey publicKey; + WS_UserAuthData_Keyboard keyboard; } sf; } WS_UserAuthData; @@ -328,6 +345,11 @@ WOLFSSH_API void wolfSSH_SetUserAuthTypes(WOLFSSH_CTX*, WOLFSSH_API void wolfSSH_SetUserAuthCtx(WOLFSSH*, void*); WOLFSSH_API void* wolfSSH_GetUserAuthCtx(WOLFSSH*); +typedef int (*WS_CallbackKeyboardAuthPrompts)(WS_UserAuthData_Keyboard*, void*); +WOLFSSH_API void wolfSSH_SetKeyboardAuthPrompts(WOLFSSH_CTX*, + WS_CallbackKeyboardAuthPrompts); +WOLFSSH_API void wolfSSH_SetKeyboardAuthCtx(WOLFSSH*, void*); + typedef int (*WS_CallbackUserAuthResult)(byte result, WS_UserAuthData* authData, void* userAuthResultCtx); WOLFSSH_API void wolfSSH_SetUserAuthResult(WOLFSSH_CTX* ctx, @@ -425,7 +447,8 @@ enum WS_FormatTypes { /* bit map */ #define WOLFSSH_USERAUTH_PASSWORD 0x01 #define WOLFSSH_USERAUTH_PUBLICKEY 0x02 -#define WOLFSSH_USERAUTH_NONE 0x04 +#define WOLFSSH_USERAUTH_KEYBOARD 0x04 +#define WOLFSSH_USERAUTH_NONE 0x08 enum WS_UserAuthResults { @@ -437,6 +460,7 @@ enum WS_UserAuthResults WOLFSSH_USERAUTH_REJECTED, WOLFSSH_USERAUTH_INVALID_PUBLICKEY, WOLFSSH_USERAUTH_PARTIAL_SUCCESS, + WOLFSSH_USERAUTH_SUCCESS_ANOTHER, WOLFSSH_USERAUTH_WOULD_BLOCK }; diff --git a/wolfssh/test.h b/wolfssh/test.h index 0d1e129b6..4061ca5b3 100644 --- a/wolfssh/test.h +++ b/wolfssh/test.h @@ -1199,9 +1199,9 @@ static void FreeBins(byte* b1, byte* b2, byte* b3, byte* b4) /* convert hex string to binary, store size, 0 success (free mem on failure) */ static int ConvertHexToBin(const char* h1, byte** b1, word32* b1Sz, - const char* h2, byte** b2, word32* b2Sz, - const char* h3, byte** b3, word32* b3Sz, - const char* h4, byte** b4, word32* b4Sz) + const char* h2, byte** b2, word32* b2Sz, + const char* h3, byte** b3, word32* b3Sz, + const char* h4, byte** b4, word32* b4Sz) { int ret; diff --git a/zephyr/samples/tests/wolfssh_user_settings.h b/zephyr/samples/tests/wolfssh_user_settings.h index 1cfbb585f..fdb1ecc67 100644 --- a/zephyr/samples/tests/wolfssh_user_settings.h +++ b/zephyr/samples/tests/wolfssh_user_settings.h @@ -34,6 +34,9 @@ extern "C" { #undef WOLFSSH_SCP #define WOLFSSH_SCP +#undef NO_AUTHTEST_MAIN_DRIVER +#define NO_AUTHTEST_MAIN_DRIVER + #undef NO_APITEST_MAIN_DRIVER #define NO_APITEST_MAIN_DRIVER diff --git a/zephyr/samples/tests/wolfssh_user_settings_nofs.h b/zephyr/samples/tests/wolfssh_user_settings_nofs.h index 5df7f407c..96bca523d 100644 --- a/zephyr/samples/tests/wolfssh_user_settings_nofs.h +++ b/zephyr/samples/tests/wolfssh_user_settings_nofs.h @@ -31,6 +31,9 @@ extern "C" { #undef WOLFSSH_SCP #define WOLFSSH_SCP +#undef NO_AUTHTEST_MAIN_DRIVER +#define NO_AUTHTEST_MAIN_DRIVER + #undef NO_APITEST_MAIN_DRIVER #define NO_APITEST_MAIN_DRIVER