Skip to content

Commit 5fb7be4

Browse files
ejohnstownphilljj
authored andcommitted
fix: validate keyboard-interactive prompts
RFC 4256 section 3.3 forbids an empty prompt, and RFC 4251 section 5 allows only 0 or 1 in a boolean field. Validation runs before the payload is sized, so the sizing and building passes agree. - reject a zero-length prompt or one with a NULL buffer - reject a prompt count with the arrays unset - reject a prompt over WOLFSSH_MAX_PROMPT_SZ, which the sizing pass would otherwise sum into a wrapped payload size - normalize the outgoing echo byte to 0 or 1 - cover the rejections in tests/api.c, the echo byte in tests/auth.c Issue: F-10583, F-10584
1 parent 689fec4 commit 5fb7be4

4 files changed

Lines changed: 130 additions & 2 deletions

File tree

src/internal.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17383,7 +17383,8 @@ static int BuildUserAuthRequestKeyboard(WOLFSSH* ssh, byte* output, word32* idx,
1738317383
WMEMCPY(output + begin, authData->sf.keyboard.prompts[entry],
1738417384
authData->sf.keyboard.promptLengths[entry]);
1738517385
begin += authData->sf.keyboard.promptLengths[entry];
17386-
output[begin] = authData->sf.keyboard.promptEcho[entry];
17386+
/* RFC 4251 booleans are 0 or 1. */
17387+
output[begin] = (authData->sf.keyboard.promptEcho[entry] != 0);
1738717388
begin++;
1738817389
}
1738917390
*idx = begin;
@@ -17441,6 +17442,35 @@ int SendUserAuthKeyboardRequest(WOLFSSH* ssh, WS_UserAuthData* authData)
1744117442
}
1744217443
}
1744317444

17445+
if (ret == WS_SUCCESS && authData->sf.keyboard.promptCount > 0) {
17446+
/* RFC 4256 section 3.3 forbids an empty prompt. Check before
17447+
* sizing, so both passes see the same entries. */
17448+
word32 entry;
17449+
17450+
if (authData->sf.keyboard.promptLengths == NULL ||
17451+
authData->sf.keyboard.prompts == NULL ||
17452+
authData->sf.keyboard.promptEcho == NULL) {
17453+
WLOG(WS_LOG_DEBUG, "SUAKR: keyboard setup left a prompt array "
17454+
"unset");
17455+
ret = WS_BAD_USAGE;
17456+
}
17457+
17458+
for (entry = 0; ret == WS_SUCCESS &&
17459+
entry < authData->sf.keyboard.promptCount; entry++) {
17460+
if (authData->sf.keyboard.promptLengths[entry] == 0 ||
17461+
authData->sf.keyboard.prompts[entry] == NULL) {
17462+
WLOG(WS_LOG_DEBUG, "SUAKR: prompt %u is empty", entry);
17463+
ret = WS_BAD_USAGE;
17464+
}
17465+
/* The sizing pass sums these into a word32. */
17466+
else if (authData->sf.keyboard.promptLengths[entry] >
17467+
WOLFSSH_MAX_PROMPT_SZ) {
17468+
WLOG(WS_LOG_DEBUG, "SUAKR: prompt %u too long", entry);
17469+
ret = WS_BAD_USAGE;
17470+
}
17471+
}
17472+
}
17473+
1744417474
if (ret == WS_SUCCESS) {
1744517475
ssh->kbAuth.promptCount = authData->sf.keyboard.promptCount;
1744617476
}

tests/api.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7792,6 +7792,84 @@ static void test_wolfSSH_KeyboardInteractive(void)
77927792
#else /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */
77937793
static void test_wolfSSH_KeyboardInteractive(void) { ; }
77947794
#endif /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */
7795+
7796+
#ifndef NO_WOLFSSH_SERVER
7797+
7798+
/* Supplies the prompt set the test installed as the userAuth context. */
7799+
static int emptyPromptUserAuth(byte authType, WS_UserAuthData* authData,
7800+
void* ctx)
7801+
{
7802+
if (authType == WOLFSSH_USERAUTH_KEYBOARD_SETUP) {
7803+
WMEMCPY(&authData->sf.keyboard, (WS_UserAuthData_Keyboard*)ctx,
7804+
sizeof(WS_UserAuthData_Keyboard));
7805+
return WOLFSSH_USERAUTH_SUCCESS;
7806+
}
7807+
return WOLFSSH_USERAUTH_FAILURE;
7808+
}
7809+
7810+
7811+
/* The sender must refuse a setup callback that supplies an empty prompt.
7812+
* Refused before sizing, so no keyed session is needed. */
7813+
static void test_wolfSSH_KeyboardInteractive_emptyPrompt(void)
7814+
{
7815+
WOLFSSH_CTX* ctx = NULL;
7816+
WOLFSSH* ssh = NULL;
7817+
WS_UserAuthData authData;
7818+
WS_UserAuthData_Keyboard prompts;
7819+
byte* promptText[1];
7820+
word32 promptLengths[1];
7821+
byte promptEcho[1];
7822+
7823+
promptText[0] = (byte*)"Password: ";
7824+
promptLengths[0] = 10;
7825+
promptEcho[0] = 0;
7826+
WMEMSET(&prompts, 0, sizeof(prompts));
7827+
prompts.promptCount = 1;
7828+
prompts.prompts = promptText;
7829+
prompts.promptLengths = promptLengths;
7830+
prompts.promptEcho = promptEcho;
7831+
7832+
AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL));
7833+
wolfSSH_SetUserAuth(ctx, emptyPromptUserAuth);
7834+
AssertNotNull(ssh = wolfSSH_new(ctx));
7835+
wolfSSH_SetUserAuthCtx(ssh, &prompts);
7836+
7837+
/* Control: the intact prompt set clears validation. It fails later, on
7838+
* an unkeyed session, but not as bad usage. */
7839+
WMEMSET(&authData, 0, sizeof(authData));
7840+
AssertIntNE(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
7841+
7842+
/* Zero-length prompt. */
7843+
promptLengths[0] = 0;
7844+
WMEMSET(&authData, 0, sizeof(authData));
7845+
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
7846+
7847+
/* Non-zero length, no buffer. */
7848+
promptLengths[0] = 10;
7849+
promptText[0] = NULL;
7850+
WMEMSET(&authData, 0, sizeof(authData));
7851+
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
7852+
7853+
/* A prompt longer than the payload bound. */
7854+
promptLengths[0] = WOLFSSH_MAX_PROMPT_SZ + 1;
7855+
promptText[0] = (byte*)"Password: ";
7856+
WMEMSET(&authData, 0, sizeof(authData));
7857+
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
7858+
7859+
/* Prompt count with the arrays unset. */
7860+
prompts.prompts = NULL;
7861+
prompts.promptLengths = NULL;
7862+
prompts.promptEcho = NULL;
7863+
WMEMSET(&authData, 0, sizeof(authData));
7864+
AssertIntEQ(SendUserAuthKeyboardRequest(ssh, &authData), WS_BAD_USAGE);
7865+
7866+
wolfSSH_free(ssh);
7867+
wolfSSH_CTX_free(ctx);
7868+
}
7869+
7870+
#else /* NO_WOLFSSH_SERVER */
7871+
static void test_wolfSSH_KeyboardInteractive_emptyPrompt(void) { ; }
7872+
#endif /* NO_WOLFSSH_SERVER */
77957873
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
77967874

77977875
#endif /* WOLFSSH_TEST_BLOCK */
@@ -7897,6 +7975,7 @@ int wolfSSH_ApiTest(int argc, char** argv)
78977975
#endif
78987976
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
78997977
test_wolfSSH_KeyboardInteractive();
7978+
test_wolfSSH_KeyboardInteractive_emptyPrompt();
79007979
#endif
79017980

79027981
/* SCP tests */

tests/auth.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1736,6 +1736,8 @@ word32 kbResponseCount;
17361736
byte kbMultiRound = 0;
17371737
byte currentRound = 0;
17381738
byte unbalanced = 0;
1739+
/* What the client must see for each prompt's echo byte. */
1740+
byte kbExpectedEcho = 1;
17391741

17401742
WS_UserAuthData_Keyboard promptData;
17411743

@@ -1832,7 +1834,9 @@ static THREAD_RETURN WOLFSSH_THREAD server_thread(void* args)
18321834
for (word32 prompt = 0; prompt < kbResponseCount; prompt++) {
18331835
promptData.prompts[prompt] = (byte*)"Password: ";
18341836
promptData.promptLengths[prompt] = 10;
1835-
promptData.promptEcho[prompt] = 0;
1837+
/* Not 0 or 1, so the client sees whether the sender canonicalized
1838+
* the boolean. */
1839+
promptData.promptEcho[prompt] = 0x42;
18361840
}
18371841
}
18381842
else {
@@ -1915,6 +1919,10 @@ static int keyboardUserAuth(byte authType, WS_UserAuthData* authData, void* ctx)
19151919
AssertIntEQ(kbResponseCount, authData->sf.keyboard.promptCount);
19161920
for (word32 prompt = 0; prompt < kbResponseCount; prompt++) {
19171921
AssertStrEQ("Password: ", authData->sf.keyboard.prompts[prompt]);
1922+
/* RFC 4251 section 5: whatever the server stored, the wire
1923+
* carries the canonical 0 or 1. */
1924+
AssertIntEQ(authData->sf.keyboard.promptEcho[prompt],
1925+
kbExpectedEcho);
19181926
}
19191927

19201928
authData->sf.keyboard.responseCount = kbResponseCount;
@@ -2235,6 +2243,8 @@ static void test_invalid_cb_keyboard(void)
22352243
kbResponses[0] = (byte*)testText1;
22362244
kbResponseLengths[0] = 4;
22372245
kbResponseCount = 1;
2246+
/* This server stores 0, which must arrive as 0. */
2247+
kbExpectedEcho = 0;
22382248

22392249
serverArgs.signal = &ready;
22402250
serverArgs.pubkeyServerCtx = NULL;
@@ -2272,6 +2282,7 @@ static void test_invalid_cb_keyboard(void)
22722282
ThreadJoin(serThread);
22732283
AssertIntNE(serverArgs.return_code, WS_SUCCESS); /* auth must NOT be granted */
22742284

2285+
kbExpectedEcho = 1;
22752286
FreeTcpReady(&ready);
22762287
}
22772288

wolfssh/settings.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ extern "C" {
8686
#define WOLFSSH_MAX_PROMPTS 64
8787
#endif
8888

89+
/* Maximum length of one prompt string. Bounds the request payload, which is
90+
* sized by summing the prompt lengths.
91+
*/
92+
93+
#if defined(WOLFSSH_KEYBOARD_INTERACTIVE) && !defined(WOLFSSH_MAX_PROMPT_SZ)
94+
#define WOLFSSH_MAX_PROMPT_SZ 1024
95+
#endif
96+
8997
#ifdef __cplusplus
9098
}
9199
#endif

0 commit comments

Comments
 (0)