Skip to content

Commit effb203

Browse files
committed
client: mask control bytes in keyboard prompts
The example client prints the keyboard-interactive name, instruction and prompts through ClientPrintSafe(), which prints a '.' in place of each control character other than tab and newline, DEL, and each byte 0x80 and up, per RFC 4256 section 3.3 and RFC 4251 section 9.2. - tests: auth case capturing the printed strings Issue: F-15534
1 parent 77aba85 commit effb203

2 files changed

Lines changed: 93 additions & 3 deletions

File tree

‎examples/client/common.c‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,23 @@ int ClientPublicKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx)
466466
}
467467

468468

469+
#if defined(WOLFSSH_TERM) && defined(WOLFSSH_KEYBOARD_INTERACTIVE)
470+
/* Prints a server-supplied string with a '.' in place of each byte that is
471+
* a control character other than tab or newline, DEL, or 0x80 and up. */
472+
static void ClientPrintSafe(const byte* str)
473+
{
474+
byte c;
475+
476+
for (; *str != 0; str++) {
477+
c = *str;
478+
if ((c < 0x20 && c != '\t' && c != '\n') || c >= 0x7F)
479+
c = '.';
480+
putchar(c);
481+
}
482+
}
483+
#endif
484+
485+
469486
int ClientUserAuth(byte authType,
470487
WS_UserAuthData* authData,
471488
void* ctx)
@@ -577,11 +594,13 @@ int ClientUserAuth(byte authType,
577594

578595
if (authData->sf.keyboard.promptName &&
579596
authData->sf.keyboard.promptName[0] != '\0') {
580-
printf("%s\n", authData->sf.keyboard.promptName);
597+
ClientPrintSafe(authData->sf.keyboard.promptName);
598+
printf("\n");
581599
}
582600
if (authData->sf.keyboard.promptInstruction &&
583601
authData->sf.keyboard.promptInstruction[0] != '\0') {
584-
printf("%s\n", authData->sf.keyboard.promptInstruction);
602+
ClientPrintSafe(authData->sf.keyboard.promptInstruction);
603+
printf("\n");
585604
}
586605

587606
/* The server may send another INFO_REQUEST at any time. */
@@ -615,7 +634,7 @@ int ClientUserAuth(byte authType,
615634
* but test the one this function actually returns. */
616635
for (entry = 0; ret == WOLFSSH_USERAUTH_SUCCESS && entry < promptCount;
617636
entry++) {
618-
printf("%s", authData->sf.keyboard.prompts[entry]);
637+
ClientPrintSafe(authData->sf.keyboard.prompts[entry]);
619638
if (!authData->sf.keyboard.promptEcho[entry]) {
620639
ClientSetEcho(0);
621640
}

‎tests/auth.c‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2809,6 +2809,76 @@ static void test_ClientUserAuth_passwordWiped(void)
28092809
AssertNotNull(freopen("/dev/null", "r", stdin));
28102810
}
28112811

2812+
/* Server-supplied keyboard-interactive strings reach the terminal with a
2813+
* '.' for each control byte and each byte 0x80 and up. */
2814+
static void test_ClientUserAuth_keyboardFiltered(void)
2815+
{
2816+
WS_UserAuthData authData;
2817+
byte* prompts[1];
2818+
byte promptEcho[1];
2819+
const char* inName = "kb_filter_in.txt";
2820+
const char* outName = "kb_filter_out.txt";
2821+
WFILE* f = NULL;
2822+
WFILE* outFile = NULL;
2823+
char out[512];
2824+
size_t outSz;
2825+
size_t i;
2826+
int savedStdout;
2827+
int ret;
2828+
2829+
printf("Testing keyboard-interactive strings are filtered\n");
2830+
2831+
prompts[0] = (byte*)"p\rq\x9b\x7f: ";
2832+
promptEcho[0] = 1;
2833+
2834+
AssertIntEQ(WFOPEN(NULL, &f, inName, "w"), 0);
2835+
AssertNotNull(f);
2836+
AssertIntGT(fprintf(f, "resp\n"), 0);
2837+
WFCLOSE(NULL, f);
2838+
AssertNotNull(freopen(inName, "r", stdin));
2839+
AssertIntEQ(remove(inName), 0);
2840+
2841+
KbSetupAuthData(&authData, 1, prompts, promptEcho);
2842+
authData.sf.keyboard.promptName = (byte*)"a\x1b[2Jb";
2843+
authData.sf.keyboard.promptInstruction =
2844+
(byte*)"\xc2\x9b x \xc3\xa9\t\xa0\xe0\x82\x9b.";
2845+
2846+
/* Capture through the descriptor alone, leaving the stdout FILE and its
2847+
* buffering mode as they were. The capture file is unlinked while open
2848+
* so nothing is left behind if an assert aborts. */
2849+
AssertIntEQ(WFOPEN(NULL, &outFile, outName, "w+"), 0);
2850+
AssertNotNull(outFile);
2851+
AssertIntEQ(remove(outName), 0);
2852+
fflush(stdout);
2853+
savedStdout = dup(STDOUT_FILENO);
2854+
AssertTrue(savedStdout >= 0);
2855+
AssertTrue(dup2(fileno(outFile), STDOUT_FILENO) >= 0);
2856+
ret = ClientUserAuth(WOLFSSH_USERAUTH_KEYBOARD, &authData, NULL);
2857+
fflush(stdout);
2858+
AssertTrue(dup2(savedStdout, STDOUT_FILENO) >= 0);
2859+
close(savedStdout);
2860+
2861+
AssertIntEQ(ret, WOLFSSH_USERAUTH_SUCCESS);
2862+
AssertIntEQ(authData.sf.keyboard.responseCount, 1);
2863+
2864+
rewind(outFile);
2865+
outSz = fread(out, 1, sizeof(out) - 1, outFile);
2866+
WFCLOSE(NULL, outFile);
2867+
out[outSz] = '\0';
2868+
2869+
AssertNotNull(WSTRSTR(out, "a.[2Jb\n"));
2870+
AssertNotNull(WSTRSTR(out, ".. x ..\t.....\n"));
2871+
AssertNotNull(WSTRSTR(out, "p.q..: "));
2872+
for (i = 0; i < outSz; i++) {
2873+
AssertTrue((byte)out[i] < 0x7F);
2874+
AssertTrue((byte)out[i] >= 0x20 || out[i] == '\t' ||
2875+
out[i] == '\n');
2876+
}
2877+
2878+
ClientFreeBuffers(NULL, NULL, NULL);
2879+
AssertNotNull(freopen("/dev/null", "r", stdin));
2880+
}
2881+
28122882
#endif /* keyboard-interactive ClientUserAuth tests */
28132883

28142884
int wolfSSH_AuthTest(int argc, char** argv)
@@ -2901,6 +2971,7 @@ int wolfSSH_AuthTest(int argc, char** argv)
29012971
test_ClientUserAuth_keyboardZeroPrompts();
29022972
test_ClientUserAuth_keyboardRounds();
29032973
test_ClientUserAuth_passwordWiped();
2974+
test_ClientUserAuth_keyboardFiltered();
29042975
#endif
29052976

29062977
/* Unknown callback return value must not grant auth (issue 2486) */

0 commit comments

Comments
 (0)